module Sprockets::PathUtils

Experimental RBS support (using type sampling data from the type_fusion project).

# sig/sprockets/path_utils.rbs

module Sprockets::PathUtils
  def split_subpath: (String path, String subpath) -> nil
end

def absolute_path?(path)

Delegate to Pathname since the logic gets complex.
On Windows, ALT_SEPARATOR is \
def absolute_path?(path)
  Pathname.new(path).absolute?
end

def absolute_path?(path)

def absolute_path?(path)
  path.start_with?(File::SEPARATOR)
end

def atomic_write(filename)

Returns nothing.

end
file.write('hello')
Utils.atomic_write('important.file') do |file|

don't want other processes or threads to see half-written files.
Public: Write to a file atomically. Useful for situations where you
def atomic_write(filename)
  dirname, basename = File.split(filename)
  basename = [
    basename,
    Thread.current.object_id,
    Process.pid,
    rand(1000000)
  ].join('.'.freeze)
  tmpname = File.join(dirname, basename)
  File.open(tmpname, 'wb+') do |f|
    yield f
  end
  File.rename(tmpname, filename)
ensure
  File.delete(tmpname) if File.exist?(tmpname)
end

def directory?(path)

Returns true path exists and is a directory.

path - String file path.

Public: Like `File.directory?`.
def directory?(path)
  if stat = self.stat(path)
    stat.directory?
  else
    false
  end
end

def entries(path)

Returns an empty `Array` if the directory does not exist.

path - String directory path

swap files.
Public: A version of `Dir.entries` that filters out `.` files and `~`
def entries(path)
  if File.directory?(path)
    entries = Dir.entries(path, encoding: Encoding.default_internal)
    entries.reject! { |entry|
      entry.start_with?(".".freeze) ||
        (entry.start_with?("#".freeze) && entry.end_with?("#".freeze)) ||
        entry.end_with?("~".freeze)
    }
    entries.sort!
    entries
  else
    []
  end
end

def file?(path)

Returns true path exists and is a file.

path - String file path.

Public: Like `File.file?`.
def file?(path)
  if stat = self.stat(path)
    stat.file?
  else
    false
  end
end

def find_matching_path_for_extensions(path, basename, extensions)

Returns an Array of [String path, Object value] matches.

# => ["app/assets/application.js", "application/javascript"]
find_matching_path_for_extensions("app/assets", "application", exts)
exts = { ".js" => "application/javascript" }

Examples

extensions - Hash of String extnames to values
basename - String basename of target file
path - String directory

Internal: Match paths in a directory against available extensions.
def find_matching_path_for_extensions(path, basename, extensions)
  matches = []
  entries(path).each do |entry|
    next unless File.basename(entry).start_with?(basename)
    extname, value = match_path_extname(entry, extensions)
    if basename == entry.chomp(extname)
      filename = File.join(path, entry)
      if file?(filename)
        matches << [filename, value]
      end
    end
  end
  matches
end

def find_upwards(basename, path, root = nil)

Returns String filename or nil.

root - String path to stop at (default: system root)
path - String path to start search: "app/assets/javascripts/app.js"
basename - String filename: ".sprocketsrc"

Internal: Find target basename checking upwards from path.
def find_upwards(basename, path, root = nil)
  path_parents(path, root).each do |dir|
    filename = File.join(dir, basename)
    return filename if file?(filename)
  end
  nil
end

def join(base, path)

Returns string path starting from base and ending at path

# => 'base/file.js'
join('base/path/', '../file.js')

Example

path - Extending path
base - Root path

Public: Joins path to base path.
def join(base, path)
  (Pathname.new(base) + path).to_s
end

def match_path_extname(path, extensions)

Returns [String extname, Object value] or nil nothing matched.

extensions - Hash of String extnames to values
path - String

Internal: Match path extnames against available extensions.
def match_path_extname(path, extensions)
  basename = File.basename(path)
  i = basename.index('.'.freeze)
  while i && i < basename.length - 1
    extname = basename[i..-1]
    if value = extensions[extname]
      return extname, value
    end
    i = basename.index('.'.freeze, i+1)
  end
  nil
end

def path_extnames(path)

Returns an Array of String extnames.

path - String

Internal: Get path's extensions.
def path_extnames(path)
  File.basename(path).scan(/\.[^.]+/)
end

def path_parents(path, root = nil)

Returns an Array of String paths.

root - String path to stop at (default: system root)
path - String absolute filename or directory

Internal: Returns all parents for path
def path_parents(path, root = nil)
  root = "#{root}#{File::SEPARATOR}" if root
  parents = []
  loop do
    parent = File.dirname(path)
    break if parent == path
    break if root && !path.start_with?(root)
    parents << path = parent
  end
  parents
end

def paths_split(paths, filename)

Returns [String root, String path]

filename - String path of file expected to be in one of the paths.
paths - Array of String paths

Internal: Detect root path and base for file in a set of paths.
def paths_split(paths, filename)
  paths.each do |path|
    if subpath = split_subpath(path, filename)
      return path, subpath
    end
  end
  nil
end

def relative_path?(path)

Returns true if path is relative, otherwise false.

path - String path.

Starts with "./" or "../".
Public: Check if path is explicitly relative.
def relative_path?(path)
  path.match?(/^\.\.?($|#{SEPARATOR_PATTERN})/) ? true : false
end

def relative_path_from(start, dest)

Returns relative String path from `start` to `dest`

dest - String destination path
start - String start path (file or dir)

Public: Get relative path from `start` to `dest`.
def relative_path_from(start, dest)
  start, dest = Pathname.new(start), Pathname.new(dest)
  start = start.dirname unless start.directory?
  dest.relative_path_from(start).to_s
end

def set_pipeline(path, mime_exts, pipeline_exts, pipeline)

Returns string path with pipeline parsed in

# => 'path/some.file.debug.js.erb'
set_pipeline('path/some.file.source.js.erb', config[:mime_exts], config[:pipeline_exts], :debug)

# => 'path/file.source.js.erb'
set_pipeline('path/file.js.erb', config[:mime_exts], config[:pipeline_exts], :source)

Examples

pipeline - Pipeline
extensions - List of file extensions
path - String path

Public: Sets pipeline for path
def set_pipeline(path, mime_exts, pipeline_exts, pipeline)
  extension, _ = match_path_extname(path, mime_exts)
  path.chomp!(extension)
  pipeline_old, _ = match_path_extname(path, pipeline_exts)
  path.chomp!(pipeline_old)
  "#{path}.#{pipeline}#{extension}"
end

def split_subpath(path, subpath)

Experimental RBS support (using type sampling data from the type_fusion project).

def split_subpath: (String path, String subpath) -> nil

This signature was generated using 1 sample from 1 application.

subpath is outside of path.
Returns relative String path if subpath is a subpath of path, or nil if

subpath - String subpath of path
path - String path

Internal: Get relative path for root path and subpath.
def split_subpath(path, subpath)
  return "" if path == subpath
  path = File.join(path, ''.freeze)
  if subpath&.start_with?(path)
    subpath[path.length..-1]
  else
    nil
  end
end

def stat(path)

Returns nil if the file does not exist.

path - String file or directory path

Public: Like `File.stat`.
def stat(path)
  if File.exist?(path)
    File.stat(path.to_s)
  else
    nil
  end
end

def stat_directory(dir)

Returns an Enumerator of [path, stat].

dir - A String directory

Public: Stat all the files under a directory.
def stat_directory(dir)
  return to_enum(__method__, dir) unless block_given?
  self.entries(dir).each do |entry|
    path = File.join(dir, entry)
    if stat = self.stat(path)
      yield path, stat
    end
  end
  nil
end

def stat_sorted_tree(dir, &block)

Returns an Enumerator of [path, stat].

dir - A String directory

order.
Public: Recursive stat all the files under a directory in alphabetical
def stat_sorted_tree(dir, &block)
  return to_enum(__method__, dir) unless block_given?
  self.stat_directory(dir).sort_by { |path, stat|
    stat.directory? ? "#{path}/" : path
  }.each do |path, stat|
    yield path, stat
    if stat.directory?
      stat_sorted_tree(path, &block)
    end
  end
  nil
end

def stat_tree(dir, &block)

Returns an Enumerator of [path, stat].

dir - A String directory

Public: Recursive stat all the files under a directory.
def stat_tree(dir, &block)
  return to_enum(__method__, dir) unless block_given?
  self.stat_directory(dir) do |path, stat|
    yield path, stat
    if stat.directory?
      stat_tree(path, &block)
    end
  end
  nil
end