epath 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|