Module: Sevgi::Function::File

Included in:
Sevgi::Function
Defined in:
lib/sevgi/function/file.rb

Overview

File-system helpers used by build scripts and DSL support code.

Instance Method Summary collapse

Instance Method Details

#changed?(file, content) {|content| ... } ⇒ Boolean

Checks whether a file would change if written with content.

Parameters:

  • file (String)

    file path to compare

  • content (String)

    proposed file content

Yields:

  • optional normalization filter applied to both old and new content

Yield Parameters:

  • content (String)

    content to normalize

Yield Returns:

Returns:

  • (Boolean)

    true when the file is missing or content differs

Raises:

  • (Errno::EACCES)

    when the file cannot be read



18
19
20
21
22
23
24
25
# File 'lib/sevgi/function/file.rb', line 18

def changed?(file, content, &filter)
  return true unless ::File.exist?(file)

  old_content = ::File.read(file)
  old_content, content = [old_content, content].map(&filter) if filter

  Digest::SHA1.digest(old_content) != Digest::SHA1.digest(content)
end

#existing(file, extensions) ⇒ String?

Finds an existing file by exact path or by trying default extensions.

Parameters:

  • file (String)

    file path or extensionless basename

  • extensions (Array<String>)

    extensions to try when file has no extension

Returns:

  • (String, nil)

    matching file path, or nil when no file is found



31
32
33
34
35
36
37
# File 'lib/sevgi/function/file.rb', line 31

def existing(file, extensions)
  return file if ::File.exist?(file)
  return nil unless ::File.extname(file).empty?
  return nil if extensions.empty?

  extensions.map { |ext| "#{file}.#{ext}" }.detect { |file| ::File.exist?(file) }
end

#existing!(file, extensions) ⇒ String

Finds an existing file or raises.

Parameters:

  • file (String)

    file path or extensionless basename

  • extensions (Array<String>)

    extensions to try when file has no extension

Returns:

  • (String)

    matching file path

Raises:



44
45
46
47
48
# File 'lib/sevgi/function/file.rb', line 44

def existing!(file, extensions)
  existing(file, extensions).tap do |found|
    ArgumentError.("No matching file(s) found: #{file}") unless found
  end
end

#existing_map(*files, extensions: []) ⇒ Hash{String => String, nil}

Maps each non-nil input file to an existing path lookup result.

Parameters:

  • files (Array<String, nil>)

    file paths or extensionless basenames

  • extensions (Array<String>) (defaults to: [])

    extensions to try when a file has no extension

Returns:

  • (Hash{String => String, nil})

    original file names mapped to found paths



54
55
56
57
58
# File 'lib/sevgi/function/file.rb', line 54

def existing_map(*files, extensions: [])
  {}.tap do |found|
    files.compact.each { |file| found[file] = existing(file, extensions) }
  end
end

#existing_map!(*files, extensions: []) ⇒ Hash{String => String}

Maps each non-nil input file to an existing path lookup result or raises.

Parameters:

  • files (Array<String, nil>)

    file paths or extensionless basenames

  • extensions (Array<String>) (defaults to: [])

    extensions to try when a file has no extension

Returns:

  • (Hash{String => String})

    original file names mapped to found paths

Raises:



66
67
68
69
70
71
72
73
# File 'lib/sevgi/function/file.rb', line 66

def existing_map!(...)
  found = F.existing_map(...)
  missings = found.select { |_, match| match.nil? }.keys

  ArgumentError.("No matching file(s) found: #{missings.join(", ")}") unless missings.empty?

  found
end

#out(content, *paths) {|content| ... } ⇒ String?

Writes content to a file when it changed, or prints to stdout without a path.

Parameters:

  • content (String)

    output content

  • paths (Array<String>)

    path components for the output file

Yields:

  • optional normalization filter used for change detection

Yield Parameters:

  • content (String)

    old or new content

Yield Returns:

Returns:

  • (String, nil)

    expanded file path when written, otherwise nil

Raises:

  • (Errno::EACCES)

    when the file cannot be read or written

  • (Errno::ENOENT)

    when the parent directory does not exist



84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/sevgi/function/file.rb', line 84

def out(content, *paths, &filter)
  if paths.empty?
    ::Kernel.puts(content)
  else
    file = ::File.expand_path(::File.join(*paths))
    output = "#{content.chomp}\n"

    return unless changed?(file, output, &filter)

    file.tap { ::File.write(file, output) }
  end
end

#qualify(file, default_extension) ⇒ String

Adds a default extension when a path has no extension.

Parameters:

  • file (String)

    file path

  • default_extension (String)

    extension to append without a leading dot

Returns:

  • (String)

    qualified file path



101
102
103
104
105
# File 'lib/sevgi/function/file.rb', line 101

def qualify(file, default_extension)
  return file unless ::File.extname(file).empty?

  "#{file}.#{default_extension}"
end

#subext(ext, *paths) ⇒ String

Replaces or removes the extension on a path.

Parameters:

  • ext (String, nil)

    replacement extension, without or with a leading dot

  • paths (Array<String>)

    path components

Returns:

  • (String)

    path with the replacement extension



111
112
113
114
115
116
117
118
119
120
# File 'lib/sevgi/function/file.rb', line 111

def subext(ext, *paths)
  path = ::File.join(*paths)

  return path if %w[. ..].include?(path)
  return path unless ext

  ext = ".#{ext}" unless ext.empty? || ext.start_with?(".")

  Pathname.new(path).sub_ext(ext).to_s
end

#touch(*paths) ⇒ String

Creates a file and any missing parent directories.

Parameters:

  • paths (Array<String>)

    path components for the file

Returns:

  • (String)

    touched file path

Raises:

  • (Errno::EACCES)

    when the file or parent directory cannot be created



126
127
128
129
130
131
# File 'lib/sevgi/function/file.rb', line 126

def touch(*paths)
  ::File.join(*paths).tap do |path|
    ::FileUtils.mkdir_p(::File.dirname(path))
    ::FileUtils.touch(path)
  end
end