hike 0.7.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  module Hike
2
- VERSION = "0.7.1"
2
+ VERSION = "1.0.0"
3
3
 
4
4
  autoload :Extensions, "hike/extensions"
5
5
  autoload :Index, "hike/index"
@@ -1,7 +1,17 @@
1
1
  require 'hike/normalized_array'
2
2
 
3
3
  module Hike
4
+ # `Extensions` is an internal collection for tracking extension names.
4
5
  class Extensions < NormalizedArray
6
+ # Extensions added to this array are normalized with a leading
7
+ # `.`.
8
+ #
9
+ # extensions << "js"
10
+ # extensions << ".css"
11
+ #
12
+ # extensions
13
+ # # => [".js", ".css"]
14
+ #
5
15
  def normalize_element(extension)
6
16
  if extension[/^\./]
7
17
  extension
@@ -1,11 +1,24 @@
1
1
  require 'pathname'
2
2
 
3
3
  module Hike
4
+ # `Index` is an internal cached variant of `Trail`. It assumes the
5
+ # file system does not change between `find` calls. All `stat` and
6
+ # `entries` calls are cached for the lifetime of the `Index` object.
4
7
  class Index
5
- attr_reader :paths, :extensions
8
+ # `Index#paths` is an immutable `Paths` collection.
9
+ attr_reader :paths
6
10
 
11
+ # `Index#extensions` is an immutable `Extensions` collection.
12
+ attr_reader :extensions
13
+
14
+ # `Index.new` is an internal method. Instead of constructing it
15
+ # directly, create a `Trail` and call `Trail#index`.
7
16
  def initialize(root, paths, extensions)
8
- @root = root
17
+ @root = root
18
+
19
+ # Freeze is used here so an error is throw if a mutator method
20
+ # is called on the array. Mutating `@paths` or `@extensions`
21
+ # would have unpredictable results.
9
22
  @paths = paths.dup.freeze
10
23
  @extensions = extensions.dup.freeze
11
24
  @pathnames = paths.map { |path| Pathname.new(path) }
@@ -15,14 +28,20 @@ module Hike
15
28
  @patterns = {}
16
29
  end
17
30
 
31
+ # `Index#root` returns root path as a `String`. This attribute is immutable.
18
32
  def root
19
33
  @root.to_s
20
34
  end
21
35
 
36
+ # `Index#index` returns `self` to be compatable with the `Trail` interface.
22
37
  def index
23
38
  self
24
39
  end
25
40
 
41
+ # The real implementation of `find`. `Trail#find` generates a one
42
+ # time index and delegates here.
43
+ #
44
+ # See `Trail#find` for usage.
26
45
  def find(*logical_paths, &block)
27
46
  if block_given?
28
47
  options = extract_options!(logical_paths)
@@ -55,6 +74,7 @@ module Hike
55
74
  logical_path.to_s =~ /^\.\.?\//
56
75
  end
57
76
 
77
+ # Finds logical path across all `paths`
58
78
  def find_in_paths(logical_path, &block)
59
79
  dirname, basename = logical_path.split
60
80
  @pathnames.each do |base_path|
@@ -62,13 +82,18 @@ module Hike
62
82
  end
63
83
  end
64
84
 
85
+ # Finds relative logical path, `../test/test_trail`. Requires a
86
+ # `base_path` for reference.
65
87
  def find_in_base_path(logical_path, base_path, &block)
66
88
  candidate = base_path.join(logical_path)
67
89
  dirname, basename = candidate.split
68
90
  match(dirname, basename, &block) if paths_contain?(dirname)
69
91
  end
70
92
 
93
+ # Checks if the path is actually on the file system and performs
94
+ # any syscalls if necessary.
71
95
  def match(dirname, basename)
96
+ # Potential `entries` syscall
72
97
  matches = entries(dirname)
73
98
 
74
99
  pattern = pattern_for(basename)
@@ -76,14 +101,19 @@ module Hike
76
101
 
77
102
  sort_matches(matches, basename).each do |path|
78
103
  pathname = dirname.join(path)
79
- stat = stat(pathname)
80
104
 
105
+ # Potential `stat` syscall
106
+ stat = stat(pathname)
107
+
108
+ # Exclude directories
81
109
  if stat && stat.file?
82
110
  yield pathname.to_s
83
111
  end
84
112
  end
85
113
  end
86
114
 
115
+ # A cached version of `File.stat`. Returns nil if the file does
116
+ # not exist.
87
117
  def stat(pathname)
88
118
  if @stats.key?(pathname)
89
119
  @stats[pathname]
@@ -96,16 +126,22 @@ module Hike
96
126
  end
97
127
  end
98
128
 
129
+ # A cached version of `Dir.entries` that filters out `.` and
130
+ # `..`. Returns an empty `Array` if the directory does not exist.
99
131
  def entries(pathname)
100
132
  @entries[pathname] ||= pathname.entries.reject { |entry| entry.to_s =~ /^\.\.?$/ }
101
133
  rescue Errno::ENOENT
102
134
  @entries[pathname] = []
103
135
  end
104
136
 
137
+ # Returns true if `dirname` is a subdirectory of any of the `paths`
105
138
  def paths_contain?(dirname)
106
139
  paths.any? { |path| dirname.to_s[0, path.length] == path }
107
140
  end
108
141
 
142
+ # Returns a `Regexp` that matches the allowed extensions.
143
+ #
144
+ # pattern_for("index.html") #=> /^index.html(.builder|.erb)+$/
109
145
  def pattern_for(basename)
110
146
  @patterns[basename] ||= begin
111
147
  extension_pattern = extensions.map { |e| Regexp.escape(e) }.join("|")
@@ -113,6 +149,9 @@ module Hike
113
149
  end
114
150
  end
115
151
 
152
+ # Sorts candidate matches by their extension
153
+ # priority. Extensions in the front of the `extensions` carry
154
+ # more weight.
116
155
  def sort_matches(matches, basename)
117
156
  matches.sort_by do |match|
118
157
  extnames = match.to_s[basename.to_s.length..-1].scan(/.[^.]+/)
@@ -1,4 +1,9 @@
1
1
  module Hike
2
+ # `NormalizedArray` is an internal abstract wrapper class that calls
3
+ # a callback `normalize_element` anytime an element is added to the
4
+ # Array.
5
+ #
6
+ # `Extensions` and `Paths` are subclasses of `NormalizedArray`.
2
7
  class NormalizedArray < Array
3
8
  def initialize
4
9
  super()
@@ -2,12 +2,22 @@ require 'pathname'
2
2
  require 'hike/normalized_array'
3
3
 
4
4
  module Hike
5
+ # `Paths` is an internal collection for tracking path strings.
5
6
  class Paths < NormalizedArray
6
7
  def initialize(root = ".")
7
8
  @root = Pathname.new(root)
8
9
  super()
9
10
  end
10
11
 
12
+ # Relative paths added to this array are expanded relative to `@root`.
13
+ #
14
+ # paths = Paths.new("/usr/local")
15
+ # paths << "tmp"
16
+ # paths << "/tmp"
17
+ #
18
+ # paths
19
+ # # => ["/usr/local/tmp", "/tmp"]
20
+ #
11
21
  def normalize_element(path)
12
22
  path = Pathname.new(path)
13
23
  path = @root.join(path) if path.relative?
@@ -4,25 +4,94 @@ require 'hike/index'
4
4
  require 'hike/paths'
5
5
 
6
6
  module Hike
7
+ # `Trail` is the public container class for holding paths and extensions.
7
8
  class Trail
8
- attr_reader :paths, :extensions
9
+ # `Trail#paths` is a mutable `Paths` collection.
10
+ #
11
+ # trail = Hike::Trail.new
12
+ # trail.paths.push "~/Projects/hike/lib", "~/Projects/hike/test"
13
+ #
14
+ # The order of the paths is significant. Paths in the beginning of
15
+ # the collection will be checked first. In the example above,
16
+ # `~/Projects/hike/lib/hike.rb` would shadow the existent of
17
+ # `~/Projects/hike/test/hike.rb`.
18
+ attr_reader :paths
9
19
 
20
+ # `Trail#extensions` is a mutable `Extensions` collection.
21
+ #
22
+ # trail = Hike::Trail.new
23
+ # trail.paths.push "~/Projects/hike/lib"
24
+ # trail.extensions.push ".rb"
25
+ #
26
+ # Extensions allow you to find files by just their name omitting
27
+ # their extension. Is similar to Ruby's require mechanism that
28
+ # allows you to require files with specifiying `foo.rb`.
29
+ attr_reader :extensions
30
+
31
+ # A Trail accepts an optional root path that defaults to your
32
+ # current working directory. Any relative paths added to
33
+ # `Trail#paths` will expanded relative to the root.
10
34
  def initialize(root = ".")
11
35
  @root = Pathname.new(root).expand_path
12
36
  @paths = Paths.new(@root)
13
37
  @extensions = Extensions.new
14
38
  end
15
39
 
40
+ # `Trail#root` returns root path as a `String`. This attribute is immutable.
16
41
  def root
17
42
  @root.to_s
18
43
  end
19
44
 
20
- def index
21
- Index.new(root, paths, extensions)
22
- end
23
-
45
+ # `Trail#find` returns a the expand path for a logical path in the
46
+ # path collection.
47
+ #
48
+ # trail = Hike::Trail.new "~/Projects/hike"
49
+ # trail.extensions.push ".rb"
50
+ # trail.paths.push "lib", "test"
51
+ #
52
+ # trail.find "hike/trail"
53
+ # # => "~/Projects/hike/lib/hike/trail.rb"
54
+ #
55
+ # trail.find "test_trail"
56
+ # # => "~/Projects/hike/test/test_trail.rb"
57
+ #
58
+ # `find` accepts multiple fallback logical paths that returns the
59
+ # first match.
60
+ #
61
+ # trail.find "hike", "hike/index"
62
+ #
63
+ # is equivalent to
64
+ #
65
+ # trail.find("hike") || trail.find("hike/index")
66
+ #
67
+ # Though `find` always returns the first match, it is possible
68
+ # to iterate over all shadowed matches and fallbacks by supplying
69
+ # a block.
70
+ #
71
+ # trail.find("hike", "hike/index") { |path| warn path }
72
+ #
73
+ # This allows you to filter your matches by any condition.
74
+ #
75
+ # trail.find("application") do |path|
76
+ # return path if mime_type_for(path) == "text/css"
77
+ # end
78
+ #
24
79
  def find(*args, &block)
25
80
  index.find(*args, &block)
26
81
  end
82
+
83
+ # `Trail#index` returns an `Index` object that has the same
84
+ # interface as `Trail`. An `Index` is a cached `Trail` object that
85
+ # does not update when the file system changes. If you are
86
+ # confident that you are not making changes the paths you are
87
+ # searching, `index` will avoid excess system calls.
88
+ #
89
+ # index = trail.index
90
+ # index.find "hike/trail"
91
+ # index.find "test_trail"
92
+ #
93
+ def index
94
+ Index.new(root, paths, extensions)
95
+ end
27
96
  end
28
97
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hike
3
3
  version: !ruby/object:Gem::Version
4
- hash: 1
4
+ hash: 23
5
5
  prerelease:
6
6
  segments:
7
- - 0
8
- - 7
9
7
  - 1
10
- version: 0.7.1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Sam Stephenson
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-04-05 00:00:00 -05:00
18
+ date: 2011-04-28 00:00:00 -05:00
19
19
  default_executable:
20
20
  dependencies: []
21
21