epath 0.3.0 → 0.4.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/README.md +2 -2
- data/lib/epath.rb +1 -0
- data/lib/epath/compatibility.rb +60 -0
- data/lib/epath/dir.rb +8 -4
- data/lib/epath/file.rb +21 -4
- data/lib/epath/identity.rb +99 -5
- data/lib/epath/implementation.rb +72 -225
- data/lib/epath/parts.rb +1 -3
- data/lib/epath/predicates.rb +3 -7
- data/lib/epath/require_tree.rb +36 -16
- data/lib/epath/version.rb +1 -1
- metadata +52 -34
- data/lib/epath/file_dir.rb +0 -12
data/README.md
CHANGED
@@ -164,10 +164,10 @@ String (not using a path library), Pathname, or another library.
|
|
164
164
|
To this intend, [`Path + config`](http://rubydoc.info/github/eregon/epath/master/Path#%2B-class_method) allows to configure the behavior of `Path#+`.
|
165
165
|
|
166
166
|
Coming from String, one should use `Path + :string`, and run ruby with the verbose option (`-w`),
|
167
|
-
which will show
|
167
|
+
which will show where `+` is used as String concatenation.
|
168
168
|
|
169
169
|
Coming from a path library using `+` as #join, one should just use the default (`Path + :warning`),
|
170
|
-
which will show
|
170
|
+
which will show where `+` is used.
|
171
171
|
|
172
172
|
## Status
|
173
173
|
|
data/lib/epath.rb
CHANGED
@@ -79,6 +79,7 @@ class Path
|
|
79
79
|
|
80
80
|
# Whether +self+ is inside +ancestor+, such that +ancestor+ is an ancestor of +self+.
|
81
81
|
# This is pure String manipulation. Paths should be absolute.
|
82
|
+
# +self+ is considered to be inside itself.
|
82
83
|
def inside? ancestor
|
83
84
|
@path == ancestor.to_s or @path.start_with?("#{ancestor}/")
|
84
85
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Path
|
2
|
+
private
|
3
|
+
|
4
|
+
if File.respond_to?(:realpath) and File.respond_to?(:realdirpath)
|
5
|
+
def real_path_internal(strict = false, basedir = nil)
|
6
|
+
strict ? File.realpath(@path, basedir) : File.realdirpath(@path, basedir)
|
7
|
+
end
|
8
|
+
else
|
9
|
+
def realpath_rec(prefix, unresolved, h, strict, last = true)
|
10
|
+
resolved = []
|
11
|
+
until unresolved.empty?
|
12
|
+
n = unresolved.shift
|
13
|
+
if n == '..'
|
14
|
+
resolved.pop
|
15
|
+
else
|
16
|
+
path = prepend_prefix(prefix, resolved + [n])
|
17
|
+
if h.include? path
|
18
|
+
if h[path] == :resolving
|
19
|
+
raise Errno::ELOOP.new(path)
|
20
|
+
else
|
21
|
+
prefix, *resolved = h[path]
|
22
|
+
end
|
23
|
+
else
|
24
|
+
begin
|
25
|
+
s = File.lstat(path)
|
26
|
+
rescue Errno::ENOENT => e
|
27
|
+
raise e if strict || !last || !unresolved.empty?
|
28
|
+
resolved << n
|
29
|
+
break
|
30
|
+
end
|
31
|
+
if s.symlink?
|
32
|
+
h[path] = :resolving
|
33
|
+
link_prefix, link_names = split_names(File.readlink(path))
|
34
|
+
if link_prefix == '' # if link is relative
|
35
|
+
link_prefix, link_names = prefix, resolved.concat(link_names)
|
36
|
+
end
|
37
|
+
prefix, *resolved = h[path] = realpath_rec(link_prefix, link_names, h, strict, unresolved.empty?)
|
38
|
+
else
|
39
|
+
resolved << n
|
40
|
+
h[path] = [prefix, *resolved]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
return prefix, *resolved
|
46
|
+
end
|
47
|
+
|
48
|
+
def real_path_internal(strict = false, basedir = nil)
|
49
|
+
path = @path
|
50
|
+
path = File.join(basedir, path) if basedir and relative?
|
51
|
+
prefix, names = split_names(path)
|
52
|
+
if prefix == ''
|
53
|
+
prefix, names2 = split_names(Dir.pwd)
|
54
|
+
names = names2.concat(names)
|
55
|
+
end
|
56
|
+
prefix, *names = realpath_rec(prefix, names, {}, strict)
|
57
|
+
prepend_prefix(prefix, names)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/epath/dir.rb
CHANGED
@@ -4,11 +4,11 @@ class Path
|
|
4
4
|
|
5
5
|
# Returns or yields Path objects. See +Dir.glob+.
|
6
6
|
# @yieldparam [Path] path
|
7
|
-
def glob(
|
7
|
+
def glob(pattern, flags = 0)
|
8
8
|
if block_given?
|
9
|
-
Dir.glob(
|
9
|
+
Dir.glob(pattern, flags) { |f| yield new(f) }
|
10
10
|
else
|
11
|
-
Dir.glob(
|
11
|
+
Dir.glob(pattern, flags).map(&Path)
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -58,7 +58,11 @@ class Path
|
|
58
58
|
# Returns or yields Path objects. See +Dir.glob+.
|
59
59
|
# @yieldparam [Path] path
|
60
60
|
def glob(pattern, flags = 0)
|
61
|
-
|
61
|
+
if block_given?
|
62
|
+
Dir.glob(join(pattern), flags) { |f| yield Path.new(f) }
|
63
|
+
else
|
64
|
+
Dir.glob(join(pattern), flags).map(&Path)
|
65
|
+
end
|
62
66
|
end
|
63
67
|
|
64
68
|
# Return the entries (files and subdirectories) in the directory.
|
data/lib/epath/file.rb
CHANGED
@@ -18,10 +18,14 @@ class Path
|
|
18
18
|
end
|
19
19
|
|
20
20
|
# Changes permissions of +path+. See +File.chmod+.
|
21
|
-
def chmod(mode)
|
21
|
+
def chmod(mode)
|
22
|
+
File.chmod(mode, @path)
|
23
|
+
end
|
22
24
|
|
23
25
|
# Changes permissions of +path+, not following symlink. See +File.lchmod+.
|
24
|
-
def lchmod(mode)
|
26
|
+
def lchmod(mode)
|
27
|
+
File.lchmod(mode, @path)
|
28
|
+
end
|
25
29
|
|
26
30
|
# Changes the owner and group of the file. See +File.chown+.
|
27
31
|
def chown(owner, group)
|
@@ -84,10 +88,23 @@ class Path
|
|
84
88
|
end
|
85
89
|
|
86
90
|
# Truncates the file to +length+ bytes. See +File.truncate+.
|
87
|
-
def truncate(length)
|
91
|
+
def truncate(length)
|
92
|
+
File.truncate(@path, length)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Removes a file using +File.unlink+.
|
96
|
+
# This is incompatible with Pathname#unlink,
|
97
|
+
# which can also remove directories.
|
98
|
+
# Use {#rmdir} or {#rm_r} for directories.
|
99
|
+
def unlink
|
100
|
+
File.unlink @path
|
101
|
+
end
|
102
|
+
alias :delete :unlink
|
88
103
|
|
89
104
|
# Updates the access and modification times. See +File.utime+.
|
90
|
-
def utime(atime, mtime)
|
105
|
+
def utime(atime, mtime)
|
106
|
+
File.utime(atime, mtime, @path)
|
107
|
+
end
|
91
108
|
|
92
109
|
# Expands +path+, making it absolute.
|
93
110
|
# If the path is relative, it is expanded with the current working directory,
|
data/lib/epath/identity.rb
CHANGED
@@ -4,8 +4,8 @@ class Path
|
|
4
4
|
|
5
5
|
# Creates a new Path. See {#initialize}.
|
6
6
|
def new(*args)
|
7
|
-
if args.size == 1 and Path === args
|
8
|
-
args
|
7
|
+
if args.size == 1 and Path === args.first
|
8
|
+
args.first
|
9
9
|
else
|
10
10
|
super(*args)
|
11
11
|
end
|
@@ -18,10 +18,65 @@ class Path
|
|
18
18
|
def to_proc
|
19
19
|
lambda { |path| new(path) }
|
20
20
|
end
|
21
|
+
|
22
|
+
# Whether +object+ looks like a path.
|
23
|
+
# The current test checks if the object responds to
|
24
|
+
# #to_path, #path or #to_str.
|
25
|
+
def like? object
|
26
|
+
[:to_path, :path, :to_str].any? { |meth| object.respond_to? meth }
|
27
|
+
end
|
28
|
+
|
29
|
+
# A matcher responding to #===. Useful for case clauses, grep, etc.
|
30
|
+
# See {Path.like?}.
|
31
|
+
#
|
32
|
+
# case obj
|
33
|
+
# when Path.like then Path(obj)
|
34
|
+
# # ...
|
35
|
+
# end
|
36
|
+
def like
|
37
|
+
@like ||= begin
|
38
|
+
matcher = Object.new
|
39
|
+
def matcher.===(object)
|
40
|
+
Path.like?(object)
|
41
|
+
end
|
42
|
+
matcher
|
43
|
+
end
|
44
|
+
end
|
21
45
|
end
|
22
46
|
|
23
47
|
# @!group Identity
|
24
48
|
|
49
|
+
# Creates a new Path.
|
50
|
+
# If multiple arguments are given, they are joined with File.join.
|
51
|
+
# The path will have File::ALT_SEPARATOR replaced with '/' and
|
52
|
+
# if it begins with a '~', it will be expanded (using File.expand_path).
|
53
|
+
# Accepts an Array of Strings, a Tempfile, anything that respond to #path,
|
54
|
+
# #to_path or #to_str with a String and defaults to calling #to_s.
|
55
|
+
#
|
56
|
+
# @param parts [Array<String>, Tempfile, #to_path, #path, #to_str, #to_s] the path-like object(s)
|
57
|
+
def initialize(*parts)
|
58
|
+
path = parts.size > 1 ? File.join(*parts) : parts.first
|
59
|
+
@path = case path
|
60
|
+
when Tempfile
|
61
|
+
@_tmpfile = path # We would not want it to be GC'd
|
62
|
+
path.path.dup
|
63
|
+
when String
|
64
|
+
path.dup
|
65
|
+
else
|
66
|
+
if path.respond_to? :to_path and String === path.to_path
|
67
|
+
path.to_path.dup
|
68
|
+
elsif path.respond_to? :path and String === path.path
|
69
|
+
path.path.dup
|
70
|
+
elsif path.respond_to? :to_str and String === path.to_str
|
71
|
+
path.to_str.dup
|
72
|
+
else
|
73
|
+
path.to_s.dup
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
init
|
78
|
+
end
|
79
|
+
|
25
80
|
# Returns the +path+ as a String.
|
26
81
|
# {#path} is implemented for better readability (+file.path+ instead of +file.to_s+) and as an accessor.
|
27
82
|
# {#to_path} is implemented so Path objects are usable with +open+, etc.
|
@@ -59,11 +114,50 @@ class Path
|
|
59
114
|
def inspect
|
60
115
|
"#<Path #{@path}>"
|
61
116
|
end
|
117
|
+
|
118
|
+
# YAML loading.
|
119
|
+
def yaml_initialize(tag, ivars)
|
120
|
+
@path = ivars['path']
|
121
|
+
init
|
122
|
+
end
|
123
|
+
|
124
|
+
# Psych loading.
|
125
|
+
def init_with(coder)
|
126
|
+
@path = coder['path']
|
127
|
+
init
|
128
|
+
end
|
129
|
+
|
130
|
+
# JSON dumping.
|
131
|
+
def to_json(*args)
|
132
|
+
{
|
133
|
+
'json_class' => 'Path',
|
134
|
+
'data' => @path
|
135
|
+
}.to_json(*args)
|
136
|
+
end
|
137
|
+
|
138
|
+
# JSON loading.
|
139
|
+
def self.json_create json
|
140
|
+
new json['data']
|
141
|
+
end
|
142
|
+
|
143
|
+
# Marshal dumping.
|
144
|
+
def marshal_dump
|
145
|
+
@path
|
146
|
+
end
|
147
|
+
|
148
|
+
# Marshal loading.
|
149
|
+
def marshal_load path
|
150
|
+
@path = path
|
151
|
+
init
|
152
|
+
end
|
62
153
|
end
|
63
154
|
|
64
155
|
unless defined? NO_EPATH_GLOBAL_FUNCTION
|
65
|
-
|
66
|
-
|
67
|
-
Path
|
156
|
+
module Kernel
|
157
|
+
# A shorthand method to create a {Path}. Same as {Path.new}.
|
158
|
+
def Path(*args)
|
159
|
+
Path.new(*args)
|
160
|
+
end
|
161
|
+
private :Path
|
68
162
|
end
|
69
163
|
end
|
data/lib/epath/implementation.rb
CHANGED
@@ -8,61 +8,6 @@ class Path
|
|
8
8
|
lambda { |a,b| a == b }
|
9
9
|
end
|
10
10
|
|
11
|
-
# Creates a new Path.
|
12
|
-
# If multiple arguments are given, they are joined with File.join.
|
13
|
-
# The path will have File::ALT_SEPARATOR replaced with '/' and
|
14
|
-
# if it begins with a '~', it will be expanded (using File.expand_path).
|
15
|
-
def initialize(*parts)
|
16
|
-
path = parts.size > 1 ? File.join(*parts) : parts.first
|
17
|
-
@path = case path
|
18
|
-
when Tempfile
|
19
|
-
@_tmpfile = path # We would not want it to be GC'd
|
20
|
-
path.path.dup
|
21
|
-
when String
|
22
|
-
path.dup
|
23
|
-
else
|
24
|
-
path.to_s.dup
|
25
|
-
end
|
26
|
-
|
27
|
-
init
|
28
|
-
end
|
29
|
-
|
30
|
-
# YAML loading.
|
31
|
-
def yaml_initialize(tag, ivars)
|
32
|
-
@path = ivars['path']
|
33
|
-
init
|
34
|
-
end
|
35
|
-
|
36
|
-
# Psych loading.
|
37
|
-
def init_with(coder)
|
38
|
-
@path = coder['path']
|
39
|
-
init
|
40
|
-
end
|
41
|
-
|
42
|
-
# JSON dumping.
|
43
|
-
def to_json(*args)
|
44
|
-
{
|
45
|
-
'json_class' => 'Path',
|
46
|
-
'data' => @path
|
47
|
-
}.to_json(*args)
|
48
|
-
end
|
49
|
-
|
50
|
-
# JSON loading.
|
51
|
-
def self.json_create json
|
52
|
-
new json['data']
|
53
|
-
end
|
54
|
-
|
55
|
-
# Marshal dumping.
|
56
|
-
def marshal_dump
|
57
|
-
@path
|
58
|
-
end
|
59
|
-
|
60
|
-
# Marshal loading.
|
61
|
-
def marshal_load path
|
62
|
-
@path = path
|
63
|
-
init
|
64
|
-
end
|
65
|
-
|
66
11
|
# Returns a cleaned version of +self+ with consecutive slashes and useless dots removed.
|
67
12
|
# The filesystem is not accessed.
|
68
13
|
#
|
@@ -143,16 +88,13 @@ class Path
|
|
143
88
|
# path0.join(path1, ..., pathN)
|
144
89
|
# # is the same as
|
145
90
|
# path0 / path1 / ... / pathN
|
146
|
-
def join(*
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
args.reverse_each { |arg|
|
151
|
-
arg = Path.new(arg)
|
152
|
-
result = arg / result
|
91
|
+
def join(*paths)
|
92
|
+
result = nil
|
93
|
+
paths.reverse_each { |path|
|
94
|
+
result = Path.new(path) / result
|
153
95
|
return result if result.absolute?
|
154
96
|
}
|
155
|
-
result
|
97
|
+
self / result
|
156
98
|
end
|
157
99
|
|
158
100
|
# #relative_path_from returns a relative path from the argument to the
|
@@ -163,37 +105,23 @@ class Path
|
|
163
105
|
#
|
164
106
|
# ArgumentError is raised when it cannot find a relative path.
|
165
107
|
def relative_path_from(base_directory)
|
166
|
-
|
167
|
-
|
168
|
-
dest_prefix =
|
169
|
-
|
170
|
-
|
171
|
-
dest_prefix, basename = r
|
172
|
-
dest_names.unshift basename if basename != '.'
|
173
|
-
end
|
174
|
-
base_prefix = base_directory
|
175
|
-
base_names = []
|
176
|
-
while r = chop_basename(base_prefix)
|
177
|
-
base_prefix, basename = r
|
178
|
-
base_names.unshift basename if basename != '.'
|
179
|
-
end
|
108
|
+
dest = clean.path
|
109
|
+
base = Path.new(base_directory).clean.path
|
110
|
+
dest_prefix, dest_names = split_names(dest)
|
111
|
+
base_prefix, base_names = split_names(base)
|
112
|
+
|
180
113
|
unless SAME_PATHS[dest_prefix, base_prefix]
|
181
|
-
raise ArgumentError, "different prefix: #{
|
114
|
+
raise ArgumentError, "different prefix: #{self.inspect} and #{base_directory.inspect}"
|
182
115
|
end
|
183
|
-
|
116
|
+
while d = dest_names.first and b = base_names.first and SAME_PATHS[d, b]
|
184
117
|
dest_names.shift
|
185
118
|
base_names.shift
|
186
119
|
end
|
187
|
-
if base_names.include? '..'
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
if relpath_names.empty?
|
193
|
-
Path.new('.')
|
194
|
-
else
|
195
|
-
Path.new(*relpath_names)
|
196
|
-
end
|
120
|
+
raise ArgumentError, "base_directory has ..: #{base_directory.inspect}" if base_names.include? '..'
|
121
|
+
# the number of names left in base is the ones we have to climb
|
122
|
+
names = base_names.fill('..').concat(dest_names)
|
123
|
+
return Path.new('.') if names.empty?
|
124
|
+
Path.new(*names)
|
197
125
|
end
|
198
126
|
alias :relative_to :relative_path_from
|
199
127
|
alias :% :relative_path_from
|
@@ -243,41 +171,43 @@ class Path
|
|
243
171
|
end
|
244
172
|
end
|
245
173
|
|
174
|
+
def is_absolute?(path)
|
175
|
+
path.start_with?('/') or (path =~ /\A[a-zA-Z]:\// and is_root?($&))
|
176
|
+
end
|
177
|
+
|
178
|
+
def is_root?(path)
|
179
|
+
chop_basename(path) == nil and path.include?('/')
|
180
|
+
end
|
181
|
+
|
246
182
|
# split_names(path) -> prefix, [name, ...]
|
247
183
|
def split_names(path)
|
248
184
|
names = []
|
249
185
|
while r = chop_basename(path)
|
250
186
|
path, basename = r
|
251
|
-
names.unshift basename
|
187
|
+
names.unshift basename if basename != '.'
|
252
188
|
end
|
253
189
|
return path, names
|
254
190
|
end
|
255
191
|
|
256
|
-
def prepend_prefix(prefix,
|
192
|
+
def prepend_prefix(prefix, relnames)
|
193
|
+
relpath = File.join(*relnames)
|
257
194
|
if relpath.empty?
|
258
195
|
File.dirname(prefix)
|
259
196
|
elsif prefix.include? '/'
|
260
|
-
|
197
|
+
# safe because File.dirname returns a new String
|
198
|
+
add_trailing_separator(File.dirname(prefix)) << relpath
|
261
199
|
else
|
262
200
|
prefix + relpath
|
263
201
|
end
|
264
202
|
end
|
265
203
|
|
266
204
|
def has_trailing_separator?(path)
|
267
|
-
|
268
|
-
pre, basename = r
|
269
|
-
pre.length + basename.length < path.length
|
270
|
-
else
|
271
|
-
false
|
272
|
-
end
|
205
|
+
!is_root?(path) and path.end_with?('/')
|
273
206
|
end
|
274
207
|
|
275
|
-
def add_trailing_separator(path)
|
276
|
-
|
277
|
-
|
278
|
-
else
|
279
|
-
path + '/'
|
280
|
-
end
|
208
|
+
def add_trailing_separator(path) # mutates path
|
209
|
+
path << '/' unless path.end_with? '/'
|
210
|
+
path
|
281
211
|
end
|
282
212
|
|
283
213
|
def del_trailing_separator(path)
|
@@ -291,51 +221,41 @@ class Path
|
|
291
221
|
end
|
292
222
|
end
|
293
223
|
|
224
|
+
# remove '..' segments since root's parent is root
|
225
|
+
def remove_root_parents(prefix, names)
|
226
|
+
names.shift while names.first == '..' if is_root?(prefix)
|
227
|
+
end
|
228
|
+
|
294
229
|
# Clean the path simply by resolving and removing excess "." and ".." entries.
|
295
230
|
# Nothing more, nothing less.
|
296
231
|
def cleanpath_aggressive
|
297
|
-
|
232
|
+
pre = @path
|
298
233
|
names = []
|
299
|
-
pre = path
|
300
234
|
while r = chop_basename(pre)
|
301
235
|
pre, base = r
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
236
|
+
if base == '.'
|
237
|
+
# do nothing, it can be ignored
|
238
|
+
elsif names.first == '..' and base != '..'
|
239
|
+
# base can be ignored as we go back to its parent
|
240
|
+
names.shift
|
306
241
|
else
|
307
|
-
|
308
|
-
names.shift
|
309
|
-
else
|
310
|
-
names.unshift base
|
311
|
-
end
|
242
|
+
names.unshift base
|
312
243
|
end
|
313
244
|
end
|
314
|
-
|
315
|
-
|
316
|
-
end
|
317
|
-
Path.new(prepend_prefix(pre, File.join(*names)))
|
245
|
+
remove_root_parents(pre, names)
|
246
|
+
Path.new(prepend_prefix(pre, names))
|
318
247
|
end
|
319
248
|
|
320
249
|
def cleanpath_conservative
|
321
250
|
path = @path
|
322
|
-
names =
|
323
|
-
pre
|
324
|
-
while r = chop_basename(pre)
|
325
|
-
pre, base = r
|
326
|
-
names.unshift base if base != '.'
|
327
|
-
end
|
328
|
-
if File.basename(pre).include? '/'
|
329
|
-
names.shift while names[0] == '..'
|
330
|
-
end
|
251
|
+
pre, names = split_names(path)
|
252
|
+
remove_root_parents(pre, names)
|
331
253
|
if names.empty?
|
332
254
|
Path.new(File.dirname(pre))
|
333
255
|
else
|
334
|
-
if names.last != '..'
|
335
|
-
|
336
|
-
|
337
|
-
result = prepend_prefix(pre, File.join(*names))
|
338
|
-
if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path)
|
256
|
+
names << '.' if names.last != '..' and File.basename(path) == '.'
|
257
|
+
result = prepend_prefix(pre, names)
|
258
|
+
if names.last != '.' and names.last != '..' and has_trailing_separator?(path)
|
339
259
|
Path.new(add_trailing_separator(result))
|
340
260
|
else
|
341
261
|
Path.new(result)
|
@@ -343,104 +263,31 @@ class Path
|
|
343
263
|
end
|
344
264
|
end
|
345
265
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
end
|
350
|
-
else
|
351
|
-
def realpath_rec(prefix, unresolved, h, strict, last = true)
|
352
|
-
resolved = []
|
353
|
-
until unresolved.empty?
|
354
|
-
n = unresolved.shift
|
355
|
-
if n == '.'
|
356
|
-
next
|
357
|
-
elsif n == '..'
|
358
|
-
resolved.pop
|
359
|
-
else
|
360
|
-
path = prepend_prefix(prefix, File.join(*(resolved + [n])))
|
361
|
-
if h.include? path
|
362
|
-
if h[path] == :resolving
|
363
|
-
raise Errno::ELOOP.new(path)
|
364
|
-
else
|
365
|
-
prefix, *resolved = h[path]
|
366
|
-
end
|
367
|
-
else
|
368
|
-
begin
|
369
|
-
s = File.lstat(path)
|
370
|
-
rescue Errno::ENOENT => e
|
371
|
-
raise e if strict || !last || !unresolved.empty?
|
372
|
-
resolved << n
|
373
|
-
break
|
374
|
-
end
|
375
|
-
if s.symlink?
|
376
|
-
h[path] = :resolving
|
377
|
-
link_prefix, link_names = split_names(File.readlink(path))
|
378
|
-
if link_prefix == ''
|
379
|
-
prefix, *resolved = h[path] = realpath_rec(prefix, resolved + link_names, h, strict, unresolved.empty?)
|
380
|
-
else
|
381
|
-
prefix, *resolved = h[path] = realpath_rec(link_prefix, link_names, h, strict, unresolved.empty?)
|
382
|
-
end
|
383
|
-
else
|
384
|
-
resolved << n
|
385
|
-
h[path] = [prefix, *resolved]
|
386
|
-
end
|
387
|
-
end
|
388
|
-
end
|
389
|
-
end
|
390
|
-
return prefix, *resolved
|
391
|
-
end
|
266
|
+
def plus(prefix, rel)
|
267
|
+
return rel if is_absolute?(rel)
|
268
|
+
_, names = split_names(rel)
|
392
269
|
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
prefix,
|
397
|
-
if
|
398
|
-
prefix, names2 = split_names(Dir.pwd)
|
399
|
-
names = names2 + names
|
400
|
-
end
|
401
|
-
prefix, *names = realpath_rec(prefix, names, {}, strict)
|
402
|
-
prepend_prefix(prefix, File.join(*names))
|
403
|
-
end
|
404
|
-
end
|
270
|
+
loop do
|
271
|
+
# break if that was the last segment
|
272
|
+
break unless r = chop_basename(prefix)
|
273
|
+
prefix, name = r
|
274
|
+
next if name == '.'
|
405
275
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
basename_list2 = []
|
410
|
-
while r2 = chop_basename(prefix2)
|
411
|
-
prefix2, basename2 = r2
|
412
|
-
index_list2.unshift prefix2.length
|
413
|
-
basename_list2.unshift basename2
|
414
|
-
end
|
415
|
-
return path2 if prefix2 != ''
|
416
|
-
prefix1 = path1
|
417
|
-
while true
|
418
|
-
while !basename_list2.empty? && basename_list2.first == '.'
|
419
|
-
index_list2.shift
|
420
|
-
basename_list2.shift
|
421
|
-
end
|
422
|
-
break unless r1 = chop_basename(prefix1)
|
423
|
-
prefix1, basename1 = r1
|
424
|
-
next if basename1 == '.'
|
425
|
-
if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..'
|
426
|
-
prefix1 = prefix1 + basename1
|
276
|
+
# break if we can't resolve anymore
|
277
|
+
if name == '..' or names.first != '..'
|
278
|
+
prefix << name
|
427
279
|
break
|
428
280
|
end
|
429
|
-
|
430
|
-
basename_list2.shift
|
281
|
+
names.shift
|
431
282
|
end
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
end
|
438
|
-
end
|
439
|
-
if !basename_list2.empty?
|
440
|
-
suffix2 = path2[index_list2.first..-1]
|
441
|
-
r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2
|
283
|
+
|
284
|
+
remove_root_parents(prefix, names)
|
285
|
+
has_prefix = chop_basename(prefix)
|
286
|
+
if names.empty?
|
287
|
+
has_prefix ? prefix : File.dirname(prefix)
|
442
288
|
else
|
443
|
-
|
289
|
+
suffix = File.join(*names)
|
290
|
+
has_prefix ? File.join(prefix, suffix) : prefix + suffix
|
444
291
|
end
|
445
292
|
end
|
446
293
|
end
|
data/lib/epath/parts.rb
CHANGED
data/lib/epath/predicates.rb
CHANGED
@@ -3,16 +3,12 @@ class Path
|
|
3
3
|
|
4
4
|
# Whether a path is absolute.
|
5
5
|
def absolute?
|
6
|
-
|
6
|
+
is_absolute?(@path)
|
7
7
|
end
|
8
8
|
|
9
9
|
# Whether a path is relative.
|
10
10
|
def relative?
|
11
|
-
|
12
|
-
while r = chop_basename(path)
|
13
|
-
path, = r
|
14
|
-
end
|
15
|
-
path == ''
|
11
|
+
not absolute?
|
16
12
|
end
|
17
13
|
|
18
14
|
# #root? is a predicate for root directories. I.e. it returns +true+ if the
|
@@ -21,7 +17,7 @@ class Path
|
|
21
17
|
# It doesn't access actual filesystem. So it may return +false+ for some
|
22
18
|
# paths which points to roots such as +/usr/..+.
|
23
19
|
def root?
|
24
|
-
|
20
|
+
is_root?(@path)
|
25
21
|
end
|
26
22
|
|
27
23
|
# #mountpoint? returns +true+ if +self+ points to a mountpoint.
|
data/lib/epath/require_tree.rb
CHANGED
@@ -1,22 +1,42 @@
|
|
1
1
|
class Path
|
2
2
|
# @!group Requiring
|
3
3
|
|
4
|
-
# Requires all .rb files recursively
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
4
|
+
# Requires all .rb files recursively under +directory+
|
5
|
+
# (or the current file's directory if not given).
|
6
|
+
#
|
7
|
+
# The order of requires is alphabetical,
|
8
|
+
# but files having the same basename as a directory
|
9
|
+
# are required before files in this directory.
|
10
|
+
#
|
11
|
+
# # in bar.rb
|
12
|
+
# Path.require_tree
|
13
|
+
# # require in this order:
|
14
|
+
# # foo.rb
|
15
|
+
# # foo/ext.rb
|
16
|
+
# # foo/sub.rb
|
17
|
+
#
|
18
|
+
# @param directory [String] the directory to search,
|
19
|
+
# or the current file's directory.
|
20
|
+
# @option options [Array<String>] :except ([])
|
21
|
+
# a list of prefixes to ignore, relative to +directory+.
|
22
|
+
def self.require_tree(directory = nil, options = {})
|
23
|
+
directory, options = nil, directory if Hash === directory
|
24
|
+
source = Path.file(caller)
|
25
|
+
directory = Path.relative(directory || source.dir, caller)
|
26
|
+
except = options[:except] || []
|
14
27
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
28
|
+
directory.glob('**/*.rb').reject { |path|
|
29
|
+
except.any? { |prefix| (path % directory).path.start_with?(prefix) }
|
30
|
+
}.sort! { |a,b|
|
31
|
+
if b.inside?(a.rm_ext)
|
32
|
+
-1
|
33
|
+
elsif a.inside?(b.rm_ext)
|
34
|
+
+1
|
35
|
+
else
|
36
|
+
a <=> b
|
37
|
+
end
|
38
|
+
}.each { |file|
|
39
|
+
require file.path unless source == file
|
40
|
+
}
|
21
41
|
end
|
22
42
|
end
|
data/lib/epath/version.rb
CHANGED
metadata
CHANGED
@@ -1,41 +1,49 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: epath
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 15
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- eregon
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
date: 2012-07-04 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
15
22
|
name: rspec
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
|
-
requirements:
|
19
|
-
- - ! '>='
|
20
|
-
- !ruby/object:Gem::Version
|
21
|
-
version: '0'
|
22
|
-
type: :development
|
23
23
|
prerelease: false
|
24
|
-
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
30
35
|
description:
|
31
36
|
email: eregontp@gmail.com
|
32
37
|
executables: []
|
38
|
+
|
33
39
|
extensions: []
|
40
|
+
|
34
41
|
extra_rdoc_files: []
|
35
|
-
|
42
|
+
|
43
|
+
files:
|
44
|
+
- lib/epath/compatibility.rb
|
36
45
|
- lib/epath/dir.rb
|
37
46
|
- lib/epath/file.rb
|
38
|
-
- lib/epath/file_dir.rb
|
39
47
|
- lib/epath/file_predicates.rb
|
40
48
|
- lib/epath/fileutils.rb
|
41
49
|
- lib/epath/find.rb
|
@@ -51,29 +59,39 @@ files:
|
|
51
59
|
- README.md
|
52
60
|
- LICENSE
|
53
61
|
- epath.gemspec
|
62
|
+
has_rdoc: true
|
54
63
|
homepage: https://github.com/eregon/epath
|
55
64
|
licenses: []
|
65
|
+
|
56
66
|
post_install_message:
|
57
67
|
rdoc_options: []
|
58
|
-
|
68
|
+
|
69
|
+
require_paths:
|
59
70
|
- lib
|
60
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
72
|
none: false
|
62
|
-
requirements:
|
63
|
-
- -
|
64
|
-
- !ruby/object:Gem::Version
|
65
|
-
|
66
|
-
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 3
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
81
|
none: false
|
68
|
-
requirements:
|
69
|
-
- -
|
70
|
-
- !ruby/object:Gem::Version
|
71
|
-
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
72
89
|
requirements: []
|
90
|
+
|
73
91
|
rubyforge_project:
|
74
|
-
rubygems_version: 1.
|
92
|
+
rubygems_version: 1.6.2
|
75
93
|
signing_key:
|
76
94
|
specification_version: 3
|
77
95
|
summary: a Path manipulation library
|
78
96
|
test_files: []
|
79
|
-
|
97
|
+
|