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 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