hike 0.7.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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