Module: Philiprehberger::PathnameKit

Defined in:
lib/philiprehberger/pathname_kit.rb,
lib/philiprehberger/pathname_kit/version.rb

Defined Under Namespace

Classes: Error

Constant Summary collapse

VERSION =
'0.7.0'

Class Method Summary collapse

Class Method Details

.append(path, content) ⇒ String

Append content to a file, creating parent directories if needed. Creates the file if it does not exist.

Parameters:

  • path (String)

    the file path

  • content (String)

    the content to append

Returns:

  • (String)

    the path written

Raises:

  • (Error)

    if path is nil or empty



282
283
284
285
286
287
288
289
290
# File 'lib/philiprehberger/pathname_kit.rb', line 282

def self.append(path, content)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  path_str = path.to_s
  ensure_directory(File.dirname(path_str))
  File.open(path_str, 'a') { |f| f.write(content.to_s) }
  path_str
end

.atomic_write(path) {|IO| ... } ⇒ void

This method returns an undefined value.

Write content atomically by writing to a temp file then renaming.

Parameters:

  • path (String)

    the file path to write to

Yields:

  • (IO)

    the temporary file to write to

Raises:

  • (Error)

    if path is nil or empty



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/philiprehberger/pathname_kit.rb', line 20

def self.atomic_write(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  dir = File.dirname(path)
  ensure_directory(dir)

  temp = Tempfile.new(['atomic', File.extname(path)], dir)
  begin
    yield temp
    temp.close
    FileUtils.mv(temp.path, path)
  rescue StandardError
    temp.close
    temp.unlink
    raise
  end
end

.basename(path) ⇒ String

Get the filename component of a path (without directory).

Parameters:

  • path (String)

    the file path

Returns:

  • (String)

    the filename

Raises:

  • (Error)

    if path is nil or empty



394
395
396
397
398
399
# File 'lib/philiprehberger/pathname_kit.rb', line 394

def self.basename(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  File.basename(path.to_s)
end

.checksum(path, algorithm: :sha256) ⇒ String

Computes a digest checksum of a file.

Parameters:

  • path (String)

    file path

  • algorithm (Symbol) (defaults to: :sha256)

    :md5, :sha1, :sha256, or :sha512

Returns:

  • (String)

    hex digest string

Raises:

  • (PathnameKit::Error)

    if path is nil/empty, file doesn’t exist, or algorithm is invalid



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/philiprehberger/pathname_kit.rb', line 258

def self.checksum(path, algorithm: :sha256)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  path_str = path.to_s
  raise Error, "file does not exist: #{path_str}" unless File.exist?(path_str)

  digest_class = case algorithm
                 when :md5 then Digest::MD5
                 when :sha1 then Digest::SHA1
                 when :sha256 then Digest::SHA256
                 when :sha512 then Digest::SHA512
                 else raise Error, "unsupported algorithm: #{algorithm}"
                 end
  digest_class.file(path_str).hexdigest
end

.copy(src, dest) ⇒ String

Copies a file to a destination, creating parent directories as needed.

Parameters:

  • src (String)

    source file path

  • dest (String)

    destination file path

Returns:

  • (String)

    destination path

Raises:



150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/philiprehberger/pathname_kit.rb', line 150

def self.copy(src, dest)
  raise Error, 'source path cannot be nil' if src.nil?
  raise Error, 'source path cannot be empty' if src.to_s.empty?
  raise Error, 'destination path cannot be nil' if dest.nil?
  raise Error, 'destination path cannot be empty' if dest.to_s.empty?
  raise Error, "source file does not exist: #{src}" unless File.exist?(src.to_s)

  dest_str = dest.to_s
  FileUtils.mkdir_p(File.dirname(dest_str))
  FileUtils.cp(src.to_s, dest_str)
  dest_str
end

.directory?(path) ⇒ Boolean

Check if the given path is a directory.

Parameters:

  • path (String)

    the path to check

Returns:

  • (Boolean)

    true if the path is a directory

Raises:

  • (Error)

    if path is nil or empty



382
383
384
385
386
387
# File 'lib/philiprehberger/pathname_kit.rb', line 382

def self.directory?(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  File.directory?(path.to_s)
end

.dirname(path) ⇒ String

Get the directory component of a path.

Parameters:

  • path (String)

    the file path

Returns:

  • (String)

    the directory portion

Raises:

  • (Error)

    if path is nil or empty



406
407
408
409
410
411
# File 'lib/philiprehberger/pathname_kit.rb', line 406

def self.dirname(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  File.dirname(path.to_s)
end

.each_line(path) {|String| ... } ⇒ Enumerator

Stream a file line by line without loading it entirely into memory.

Parameters:

  • path (String)

    the file path

Yields:

  • (String)

    each line, including the trailing newline if present

Returns:

  • (Enumerator)

    when no block is given

Raises:

  • (Error)

    if path is nil/empty or the file does not exist



215
216
217
218
219
220
221
222
223
# File 'lib/philiprehberger/pathname_kit.rb', line 215

def self.each_line(path, &block)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?
  raise Error, "file not found: #{path}" unless File.exist?(path.to_s)

  return File.foreach(path.to_s).each unless block

  File.foreach(path.to_s, &block)
end

.empty?(path) ⇒ Boolean

Check if a file is empty (zero bytes).

Parameters:

  • path (String)

    the file path

Returns:

  • (Boolean)

    true if the file is empty

Raises:

  • (Error)

    if path is nil/empty or the file does not exist



314
315
316
317
318
319
320
# File 'lib/philiprehberger/pathname_kit.rb', line 314

def self.empty?(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?
  raise Error, "file not found: #{path}" unless File.exist?(path.to_s)

  File.empty?(path.to_s)
end

.ensure_directory(path) ⇒ void

This method returns an undefined value.

Ensure a directory exists, creating it and all parents if needed.

Parameters:

  • path (String)

    the directory path

Raises:

  • (Error)

    if path is nil or empty



44
45
46
47
48
49
# File 'lib/philiprehberger/pathname_kit.rb', line 44

def self.ensure_directory(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  FileUtils.mkdir_p(path)
end

.exists?(path) ⇒ Boolean

Check if a file or directory exists at the given path.

Parameters:

  • path (String)

    the file or directory path

Returns:

  • (Boolean)

    true if the path exists

Raises:

  • (Error)

    if path is nil or empty



370
371
372
373
374
375
# File 'lib/philiprehberger/pathname_kit.rb', line 370

def self.exists?(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  File.exist?(path.to_s)
end

.expand(path) ⇒ String

Expand a path to its absolute form with tilde expansion.

Parameters:

  • path (String)

    the file path

Returns:

  • (String)

    the expanded absolute path

Raises:

  • (Error)

    if path is nil or empty



339
340
341
342
343
344
# File 'lib/philiprehberger/pathname_kit.rb', line 339

def self.expand(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  File.expand_path(path.to_s)
end

.extension(path) ⇒ String

Get the file extension including the leading dot.

Parameters:

  • path (String)

    the file path

Returns:

  • (String)

    the extension (e.g. “.rb”) or empty string

Raises:

  • (Error)

    if path is nil or empty



327
328
329
330
331
332
# File 'lib/philiprehberger/pathname_kit.rb', line 327

def self.extension(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  File.extname(path.to_s)
end

.find(glob) ⇒ Array<String>

Find files matching a glob pattern.

Parameters:

  • glob (String)

    the glob pattern

Returns:

  • (Array<String>)

    matching file paths

Raises:

  • (Error)

    if glob is nil or empty



71
72
73
74
75
76
# File 'lib/philiprehberger/pathname_kit.rb', line 71

def self.find(glob)
  raise Error, 'glob cannot be nil' if glob.nil?
  raise Error, 'glob cannot be empty' if glob.to_s.empty?

  Dir.glob(glob)
end

.identical?(path1, path2) ⇒ Boolean

Compare two files by SHA-256 digest.

Parameters:

  • path1 (String)

    first file path

  • path2 (String)

    second file path

Returns:

  • (Boolean)

    true if files have identical contents

Raises:

  • (Error)

    if either path is nil/empty or does not exist



298
299
300
301
302
303
304
305
306
307
# File 'lib/philiprehberger/pathname_kit.rb', line 298

def self.identical?(path1, path2)
  raise Error, 'first path cannot be nil' if path1.nil?
  raise Error, 'first path cannot be empty' if path1.to_s.empty?
  raise Error, 'second path cannot be nil' if path2.nil?
  raise Error, 'second path cannot be empty' if path2.to_s.empty?
  raise Error, "file does not exist: #{path1}" unless File.exist?(path1.to_s)
  raise Error, "file does not exist: #{path2}" unless File.exist?(path2.to_s)

  Digest::SHA256.file(path1.to_s).hexdigest == Digest::SHA256.file(path2.to_s).hexdigest
end

.line_count(path) ⇒ Integer

Count the number of lines in a file.

Parameters:

  • path (String)

    the file path

Returns:

  • (Integer)

    the number of lines

Raises:

  • (Error)

    if path is nil or empty

  • (Error)

    if the file does not exist



113
114
115
116
117
118
119
# File 'lib/philiprehberger/pathname_kit.rb', line 113

def self.line_count(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?
  raise Error, "file not found: #{path}" unless File.exist?(path)

  File.readlines(path).size
end

.move(src, dest) ⇒ String

Moves a file to a destination, creating parent directories as needed.

Parameters:

  • src (String)

    source file path

  • dest (String)

    destination file path

Returns:

  • (String)

    destination path

Raises:



169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/philiprehberger/pathname_kit.rb', line 169

def self.move(src, dest)
  raise Error, 'source path cannot be nil' if src.nil?
  raise Error, 'source path cannot be empty' if src.to_s.empty?
  raise Error, 'destination path cannot be nil' if dest.nil?
  raise Error, 'destination path cannot be empty' if dest.to_s.empty?
  raise Error, "source file does not exist: #{src}" unless File.exist?(src.to_s)

  dest_str = dest.to_s
  FileUtils.mkdir_p(File.dirname(dest_str))
  FileUtils.mv(src.to_s, dest_str)
  dest_str
end

.mtime(path) ⇒ Time

Get the last modification time of a file or directory.

Parameters:

  • path (String)

    the file or directory path

Returns:

  • (Time)

    the modification time

Raises:

  • (Error)

    if path is nil/empty or does not exist



418
419
420
421
422
423
424
# File 'lib/philiprehberger/pathname_kit.rb', line 418

def self.mtime(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?
  raise Error, "path not found: #{path}" unless File.exist?(path.to_s)

  File.mtime(path.to_s)
end

.read(path) ⇒ String

Read a file’s contents.

Parameters:

  • path (String)

    the file path

Returns:

  • (String)

Raises:

  • (Error)

    if path is nil/empty or the file does not exist



187
188
189
190
191
192
193
# File 'lib/philiprehberger/pathname_kit.rb', line 187

def self.read(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?
  raise Error, "file not found: #{path}" unless File.exist?(path.to_s)

  File.read(path.to_s)
end

.relative_to(path, base) ⇒ String

Return path expressed relative to base as a String.

Both arguments may be String or Pathname. Inputs are wrapped in Pathname and expanded so relative inputs are resolved against the current working directory before the relative path is computed.

Parameters:

  • path (String, Pathname)

    the target path

  • base (String, Pathname)

    the base path to compute relativity against

Returns:

  • (String)

    the relative path

Raises:

  • (Error)

    if either argument is nil or empty



356
357
358
359
360
361
362
363
# File 'lib/philiprehberger/pathname_kit.rb', line 356

def self.relative_to(path, base)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?
  raise Error, 'base cannot be nil' if base.nil?
  raise Error, 'base cannot be empty' if base.to_s.empty?

  Pathname.new(path.to_s).expand_path.relative_path_from(Pathname.new(base.to_s).expand_path).to_s
end

.safe_delete(path) ⇒ Boolean

Safely delete a file, returning true if deleted and false if not found.

Parameters:

  • path (String)

    the file path to delete

Returns:

  • (Boolean)

    true if the file was deleted

Raises:

  • (Error)

    if path is nil or empty



56
57
58
59
60
61
62
63
64
# File 'lib/philiprehberger/pathname_kit.rb', line 56

def self.safe_delete(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  return false unless File.exist?(path)

  File.delete(path)
  true
end

.size(path) ⇒ Integer

Get the size of a file in bytes.

Parameters:

  • path (String)

    the file path

Returns:

  • (Integer)

Raises:

  • (Error)

    if path is nil/empty or the file does not exist



230
231
232
233
234
235
236
# File 'lib/philiprehberger/pathname_kit.rb', line 230

def self.size(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?
  raise Error, "file not found: #{path}" unless File.exist?(path.to_s)

  File.size(path.to_s)
end

.tail(path, n = 10) ⇒ Array<String>

Return the last n lines of a file as an Array.

Streams the file line by line through a bounded ring of size n so memory usage stays constant regardless of file size.

Parameters:

  • path (String)

    the file path

  • n (Integer) (defaults to: 10)

    number of trailing lines to return

Returns:

  • (Array<String>)

    the last n lines (or fewer if the file is shorter)

Raises:

  • (Error)

    if path is nil/empty, n is non-positive, or the file does not exist



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/philiprehberger/pathname_kit.rb', line 130

def self.tail(path, n = 10)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?
  raise Error, 'n must be positive' unless n.is_a?(Integer) && n.positive?
  raise Error, "file not found: #{path}" unless File.exist?(path)

  buffer = []
  File.foreach(path) do |line|
    buffer << line
    buffer.shift if buffer.size > n
  end
  buffer
end

.tempfile(ext = '.tmp') {|String| ... } ⇒ Object

Create a temporary file with a given extension and yield its path.

Parameters:

  • ext (String) (defaults to: '.tmp')

    the file extension (e.g. ‘.txt’)

Yields:

  • (String)

    the temporary file path

Returns:

  • (Object)

    the block return value



83
84
85
86
87
88
89
90
91
# File 'lib/philiprehberger/pathname_kit.rb', line 83

def self.tempfile(ext = '.tmp')
  temp = Tempfile.new(['tmp', ext])
  begin
    yield temp.path
  ensure
    temp.close
    temp.unlink
  end
end

.touch(path) ⇒ void

This method returns an undefined value.

Touch a file, creating it if it does not exist and updating its mtime.

Parameters:

  • path (String)

    the file path

Raises:

  • (Error)

    if path is nil or empty



98
99
100
101
102
103
104
105
# File 'lib/philiprehberger/pathname_kit.rb', line 98

def self.touch(path)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  dir = File.dirname(path)
  ensure_directory(dir)
  FileUtils.touch(path)
end

.with_tempdir {|String| ... } ⇒ Object

Create a temporary directory and yield its path. The directory and all its contents are removed when the block returns.

Yields:

  • (String)

    the temporary directory path

Returns:

  • (Object)

    the block return value



243
244
245
246
247
248
249
250
# File 'lib/philiprehberger/pathname_kit.rb', line 243

def self.with_tempdir
  dir = Dir.mktmpdir
  begin
    yield dir
  ensure
    FileUtils.rm_rf(dir)
  end
end

.write(path, content) ⇒ String

Atomically write content to a file, creating parent directories as needed.

Parameters:

  • path (String)

    the file path

  • content (String)

    the content to write

Returns:

  • (String)

    the path written

Raises:

  • (Error)

    if path is nil/empty



201
202
203
204
205
206
207
# File 'lib/philiprehberger/pathname_kit.rb', line 201

def self.write(path, content)
  raise Error, 'path cannot be nil' if path.nil?
  raise Error, 'path cannot be empty' if path.to_s.empty?

  atomic_write(path.to_s) { |f| f.write(content.to_s) }
  path.to_s
end