class Hike::CachedTrail

`CachedTrail` is an internal cached variant of `Trail`. It assumes the file system does not change between `find` calls. All `stat` and `entries` calls are cached for the lifetime of the `CachedTrail` object.

Attributes

aliases[R]

`CachedTrail#aliases` is an immutable `Hash` mapping an extension to an `Array` of aliases.

extensions[R]

`CachedTrail#extensions` is an immutable `Extensions` collection.

paths[R]

`CachedTrail#paths` is an immutable `Paths` collection.

root[R]

`CachedTrail#root` returns root path as a `String`. This attribute is immutable.

Public Class Methods

new(root, paths, extensions, aliases) click to toggle source

`CachedTrail.new` is an internal method. Instead of constructing it directly, create a `Trail` and call `Trail#CachedTrail`.

# File lib/hike/cached_trail.rb, line 20
def initialize(root, paths, extensions, aliases)
  @root = root.to_s

  # Freeze is used here so an error is throw if a mutator method
  # is called on the array. Mutating `@paths`, `@extensions`, or
  # `@aliases` would have unpredictable results.
  @paths      = paths.dup.freeze
  @extensions = extensions.dup.freeze

  # Create a reverse mapping from extension to possible aliases.
  @aliases = aliases.dup.freeze
  @reverse_aliases = @aliases.inject({}) { |h, (k, a)|
    (h[a] ||= []) << k; h
  }

  @stats    = Hash.new { |h, k| h[k] = FileUtils.stat(k) }
  @entries  = Hash.new { |h, k| h[k] = FileUtils.entries(k) }
  @patterns = Hash.new { |h, k| h[k] = pattern_for(k) }
end

Public Instance Methods

cached() click to toggle source

`CachedTrail#cached` returns `self` to be compatable with the `Trail` interface.

# File lib/hike/cached_trail.rb, line 44
def cached
  self
end
Also aliased as: index
entries(path) click to toggle source

A cached version of `Dir.entries` that filters out `.` files and `~` swap files. Returns an empty `Array` if the directory does not exist.

# File lib/hike/cached_trail.rb, line 85
def entries(path)
  @entries[path]
end
find(*logical_paths) click to toggle source

The real implementation of `find`. `Trail#find` generates a one time cache and delegates here.

See `Trail#find` for usage.

# File lib/hike/cached_trail.rb, line 55
def find(*logical_paths)
  find_all(*logical_paths).first
end
find_all(*logical_paths, &block) click to toggle source

The real implementation of `find_all`. `Trail#find_all` generates a one time index and delegates here.

See `Trail#find_all` for usage.

# File lib/hike/cached_trail.rb, line 63
def find_all(*logical_paths, &block)
  return to_enum(__method__, *logical_paths) unless block_given?

  options = extract_options!(logical_paths)
  base_path = (options[:base_path] || root).to_s

  logical_paths.each do |logical_path|
    logical_path = logical_path.sub(/^\//, '')

    if relative?(logical_path)
      find_in_base_path(logical_path, base_path, &block)
    else
      find_in_paths(logical_path, &block)
    end
  end

  nil
end
index()

Deprecated alias for `cached`.

Alias for: cached
stat(path) click to toggle source

A cached version of `File.stat`. Returns nil if the file does not exist.

# File lib/hike/cached_trail.rb, line 91
def stat(path)
  @stats[path]
end

Protected Instance Methods

extract_options!(arguments) click to toggle source
# File lib/hike/cached_trail.rb, line 96
def extract_options!(arguments)
  arguments.last.is_a?(Hash) ? arguments.pop.dup : {}
end
find_in_base_path(logical_path, base_path, &block) click to toggle source

Finds relative logical path, `../test/test_trail`. Requires a `base_path` for reference.

# File lib/hike/cached_trail.rb, line 114
def find_in_base_path(logical_path, base_path, &block)
  candidate = File.expand_path(logical_path, base_path)
  dirname, basename = File.split(candidate)
  match(dirname, basename, &block) if paths_contain?(dirname)
end
find_in_paths(logical_path, &block) click to toggle source

Finds logical path across all `paths`

# File lib/hike/cached_trail.rb, line 105
def find_in_paths(logical_path, &block)
  dirname, basename = File.split(logical_path)
  @paths.each do |base_path|
    match(File.expand_path(dirname, base_path), basename, &block)
  end
end
match(dirname, basename) { |filename| ... } click to toggle source

Checks if the path is actually on the file system and performs any syscalls if necessary.

# File lib/hike/cached_trail.rb, line 122
def match(dirname, basename)
  # Potential `entries` syscall
  matches = @entries[dirname]

  pattern = @patterns[basename]
  matches = matches.select { |m| m =~ pattern }

  sort_matches(matches, basename).each do |path|
    filename = File.join(dirname, path)

    # Potential `stat` syscall
    stat = @stats[filename]

    # Exclude directories
    if stat && stat.file?
      yield filename
    end
  end
end
paths_contain?(dirname) click to toggle source

Returns true if `dirname` is a subdirectory of any of the `paths`

# File lib/hike/cached_trail.rb, line 143
def paths_contain?(dirname)
  paths.any? { |path| dirname[0, path.length] == path }
end
pattern_for(basename) click to toggle source

Returns a `Regexp` that matches the allowed extensions.

pattern_for("index.html") #=> /^index(.html|.htm)(.builder|.erb)*$/
# File lib/hike/cached_trail.rb, line 150
def pattern_for(basename)
  extname = File.extname(basename)
  aliases = @reverse_aliases[extname]

  if aliases
    basename = File.basename(basename, extname)
    aliases  = [extname] + aliases
    aliases_pattern = aliases.map { |e| Regexp.escape(e) }.join("|")
    basename_re = Regexp.escape(basename) + "(?:#{aliases_pattern})"
  else
    basename_re = Regexp.escape(basename)
  end

  extension_pattern = extensions.map { |e| Regexp.escape(e) }.join("|")
  /^#{basename_re}(?:#{extension_pattern})*$/
end
relative?(path) click to toggle source
# File lib/hike/cached_trail.rb, line 100
def relative?(path)
  path =~ /^\.\.?\//
end
sort_matches(matches, basename) click to toggle source

Sorts candidate matches by their extension priority. Extensions in the front of the `extensions` carry more weight.

# File lib/hike/cached_trail.rb, line 170
def sort_matches(matches, basename)
  extname = File.extname(basename)
  aliases = @reverse_aliases[extname] || []

  matches.sort_by do |match|
    extnames = match.sub(basename, '').scan(/\.[^.]+/)
    extnames.inject(0) do |sum, ext|
      if i = extensions.index(ext)
        sum + i + 1
      elsif i = aliases.index(ext)
        sum + i + 11
      else
        sum
      end
    end
  end
end