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 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 were `+` is used as String concatenation.
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 were `+` is used as #join.
170
+ which will show where `+` is used.
171
171
 
172
172
  ## Status
173
173
 
@@ -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
@@ -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(*args)
7
+ def glob(pattern, flags = 0)
8
8
  if block_given?
9
- Dir.glob(*args) { |f| yield new(f) }
9
+ Dir.glob(pattern, flags) { |f| yield new(f) }
10
10
  else
11
- Dir.glob(*args).map(&Path)
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
- Dir.glob(join(pattern), flags).map(&Path)
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.
@@ -18,10 +18,14 @@ class Path
18
18
  end
19
19
 
20
20
  # Changes permissions of +path+. See +File.chmod+.
21
- def chmod(mode) File.chmod(mode, @path) end
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) File.lchmod(mode, @path) end
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) File.truncate(@path, length) end
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) File.utime(atime, mtime, @path) end
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,
@@ -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[0]
8
- args[0]
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
- # A shorthand method to create a {Path}. Same as {Path.new}.
66
- def Path(*args)
67
- Path.new(*args)
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
@@ -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(*args)
147
- args.unshift self
148
- result = Path.new(args.pop)
149
- return result if result.absolute?
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
- dest_directory = clean.path
167
- base_directory = Path.new(base_directory).clean.path
168
- dest_prefix = dest_directory
169
- dest_names = []
170
- while r = chop_basename(dest_prefix)
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: #{dest_prefix.inspect} and #{base_directory.inspect}"
114
+ raise ArgumentError, "different prefix: #{self.inspect} and #{base_directory.inspect}"
182
115
  end
183
- until dest_names.empty? or base_names.empty? or !SAME_PATHS[dest_names.first, base_names.first]
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
- raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
189
- end
190
- base_names.fill('..')
191
- relpath_names = base_names + dest_names
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, relpath)
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
- add_trailing_separator(File.dirname(prefix)) + relpath
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
- if r = chop_basename(path)
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
- if File.basename(path + 'a') == 'a'
277
- path
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
- path = @path
232
+ pre = @path
298
233
  names = []
299
- pre = path
300
234
  while r = chop_basename(pre)
301
235
  pre, base = r
302
- case base
303
- when '.'
304
- when '..'
305
- names.unshift base
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
- if names[0] == '..'
308
- names.shift
309
- else
310
- names.unshift base
311
- end
242
+ names.unshift base
312
243
  end
313
244
  end
314
- if File.basename(pre).include? '/'
315
- names.shift while names[0] == '..'
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 = path
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 != '..' && File.basename(path) == '.'
335
- names << '.'
336
- end
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
- if File.respond_to?(:realpath) and File.respond_to?(:realdirpath)
347
- def real_path_internal(strict = false, basedir = nil)
348
- strict ? File.realpath(@path, basedir) : File.realdirpath(@path, basedir)
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
- def real_path_internal(strict = false, basedir = nil)
394
- path = @path
395
- path = File.join(basedir, path) if basedir and relative?
396
- prefix, names = split_names(path)
397
- if prefix == ''
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
- def plus(path1, path2) # -> path
407
- prefix2 = path2
408
- index_list2 = []
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
- index_list2.shift
430
- basename_list2.shift
281
+ names.shift
431
282
  end
432
- r1 = chop_basename(prefix1)
433
- if !r1 && File.basename(prefix1).include?('/')
434
- while !basename_list2.empty? && basename_list2.first == '..'
435
- index_list2.shift
436
- basename_list2.shift
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
- r1 ? prefix1 : File.dirname(prefix1)
289
+ suffix = File.join(*names)
290
+ has_prefix ? File.join(prefix, suffix) : prefix + suffix
444
291
  end
445
292
  end
446
293
  end
@@ -101,9 +101,7 @@ class Path
101
101
  # @yieldparam [Path] path
102
102
  def descend
103
103
  return to_enum(:descend) unless block_given?
104
- vs = []
105
- ascend { |v| vs << v }
106
- vs.reverse_each { |v| yield v }
104
+ ascend.reverse_each { |v| yield v }
107
105
  nil
108
106
  end
109
107
 
@@ -3,16 +3,12 @@ class Path
3
3
 
4
4
  # Whether a path is absolute.
5
5
  def absolute?
6
- !relative?
6
+ is_absolute?(@path)
7
7
  end
8
8
 
9
9
  # Whether a path is relative.
10
10
  def relative?
11
- path = @path
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
- !!(chop_basename(@path) == nil && @path.include?('/'))
20
+ is_root?(@path)
25
21
  end
26
22
 
27
23
  # #mountpoint? returns +true+ if +self+ points to a mountpoint.
@@ -1,22 +1,42 @@
1
1
  class Path
2
2
  # @!group Requiring
3
3
 
4
- # Requires all .rb files recursively (in alphabetic order)
5
- # under +directory+ (or this file's directory if not given).
6
- def self.require_tree(directory = nil)
7
- if directory
8
- new(directory).require_tree
9
- else
10
- file = Path.file(caller)
11
- file.dir.require_tree(file)
12
- end
13
- end
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
- # @api private
16
- # See {Path.require_tree}.
17
- # It is not a real private method because {Path.require_tree}
18
- # (so the {Path} class) needs to be able to call it.
19
- def require_tree(source = nil)
20
- glob('**/*.rb').sort.each { |file| require file.expand(dir).path unless file == source }
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
@@ -1,5 +1,5 @@
1
1
  class Path
2
2
  # The version of the gem.
3
3
  # Set here to avoid duplication and allow introspection.
4
- VERSION = '0.3.0'
4
+ VERSION = '0.4.0'
5
5
  end
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
- version: 0.3.0
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
- date: 2012-06-12 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
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
- version_requirements: !ruby/object:Gem::Requirement
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
25
  none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '0'
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
- files:
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
- require_paths:
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
- version: '0'
66
- required_rubygems_version: !ruby/object:Gem::Requirement
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
- version: '0'
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.8.23
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
- has_rdoc:
97
+
@@ -1,12 +0,0 @@
1
- class Path
2
- # Removes a file or directory, using +File.unlink+ or
3
- # +Dir.unlink+ as necessary.
4
- def unlink
5
- if directory?
6
- Dir.unlink @path
7
- else
8
- File.unlink @path
9
- end
10
- end
11
- alias :delete :unlink
12
- end