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.
- data/lib/hike.rb +1 -1
- data/lib/hike/extensions.rb +10 -0
- data/lib/hike/index.rb +42 -3
- data/lib/hike/normalized_array.rb +5 -0
- data/lib/hike/paths.rb +10 -0
- data/lib/hike/trail.rb +74 -5
- metadata +5 -5
data/lib/hike.rb
CHANGED
data/lib/hike/extensions.rb
CHANGED
@@ -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
|
data/lib/hike/index.rb
CHANGED
@@ -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
|
-
|
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
|
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()
|
data/lib/hike/paths.rb
CHANGED
@@ -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?
|
data/lib/hike/trail.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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:
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
|
-
- 0
|
8
|
-
- 7
|
9
7
|
- 1
|
10
|
-
|
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-
|
18
|
+
date: 2011-04-28 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|