epath 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2011 Benoit Daloze
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # Path - Enhanced Pathname
2
+
3
+ Path is a library to manage paths.
4
+ It is similar to Pathname, but has some extra goodness.
5
+ The method names are intended to be short and explicit, and avoid too much duplication like having 'name' or 'path' in the method name.
6
+
7
+ I believe the object-oriented approach to manipulate paths is very elegant and useful.
8
+ Paths are naturally the subject of their methods and even if they are simple Strings behind, they carry way much more information and deserve a first-class status.
9
+
10
+ Also, using a path library like this avoid to remember in which class the functionality is implemented, everything is in one place (if not, please open an issue!).
11
+
12
+ ## API
13
+
14
+ All the useful methods of `File` (and so `IO`) and `Dir` should be included.
15
+ Most methods of `FileUtils` should be there too.
16
+
17
+ ### creation
18
+
19
+ ``` ruby
20
+ Path.new('/usr/bin')
21
+ Path['/usr/bin']
22
+ Path('/usr/bin') # unless NO_EPATH_GLOBAL_FUNCTION is defined
23
+
24
+ Path.new('/usr', 'bin')
25
+ %w[foo bar].map(&Path) # => [Path('foo'), Path('bar')]
26
+ ```
27
+
28
+ ``` ruby
29
+ Path.here # == Path(__FILE__).expand
30
+ Path.dir # == Path(File.dirname(__FILE__)).expand
31
+ Path.relative(path) # == Path(File.expand_path("../#{path}", __FILE__))
32
+ Path.home # == Path(File.expand_path('~'))
33
+ ```
34
+
35
+ ### temporary
36
+
37
+ ``` ruby
38
+ Path.tmpfile
39
+ Path.tmpdir
40
+ ```
41
+
42
+ ### aliases
43
+
44
+ * expand => expand_path
45
+ * relative_to => relative_path_from
46
+
47
+ ### parts
48
+
49
+ * base: basename(extname)
50
+ * dir: alias of dirname
51
+ * ext: extname without the leading dot
52
+ * /: join paths
53
+
54
+ ```ruby
55
+ Path('/usr')/'bin'
56
+ ```
57
+
58
+ These method names are too long, any idea to make them shorter and clear?
59
+
60
+ * without_extension
61
+ * replace_extension(new_ext)
62
+
63
+ ### glob
64
+
65
+ * entries: files under self, without . and ..
66
+ * glob: relative glob to self, yield absolute paths
67
+
68
+ ### structure
69
+
70
+ * ancestors: self and all the parent directories
71
+ * backfind: ascends the parents until it finds the given path
72
+
73
+ ``` ruby
74
+ # Path.backfind is Path.here.backfind
75
+ Path.backfind('lib') # => Path's lib folder
76
+ # it accepts XPath-like context
77
+ Path.backfind('.[.git]') # => the root of this repository
78
+ ```
79
+
80
+ ### IO
81
+
82
+ * read
83
+ * write(contents)
84
+ * append(contents)
85
+
86
+ ### management
87
+
88
+ * mkdir
89
+ * mkdir_p
90
+ * rm_rf
91
+
92
+ ### Incompatibilities with Pathname
93
+
94
+ * #entries never returns . and ..
95
+
96
+ ## Status
97
+
98
+ This is still in the early development stage, you should expect many additions and some changes.
99
+
100
+ ## Author
101
+
102
+ Benoit Daloze - eregon
103
+
104
+ ### Contributors
105
+
106
+ Bernard Lambeau - blambeau
data/epath.gemspec CHANGED
@@ -1,7 +1,9 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'epath'
3
- s.summary = 'summary'
3
+ s.summary = 'a Path manipulation library'
4
4
  s.author = 'eregon'
5
- s.files = %w[epath.gemspec]
6
- s.version = '0.0.0'
5
+ s.email = 'eregontp@gmail.com'
6
+ s.homepage = 'https://github.com/eregon/epath'
7
+ s.files = Dir['lib/**/*.rb'] + %w[README.md LICENSE epath.gemspec]
8
+ s.version = '0.0.1'
7
9
  end
data/lib/epath.rb ADDED
@@ -0,0 +1,196 @@
1
+ # Enchanced Pathname
2
+ # Use the composite pattern with a Pathname
3
+
4
+ require File.expand_path('../epath/implementation', __FILE__)
5
+ require 'tempfile'
6
+
7
+ class Path
8
+ DOTS = %w[. ..]
9
+
10
+ class << self
11
+ def new(*args)
12
+ if args.size == 1 and Path === args[0]
13
+ args[0]
14
+ else
15
+ super(*args)
16
+ end
17
+ end
18
+ alias_method :[], :new
19
+
20
+ def to_proc
21
+ lambda { |path| new(path) }
22
+ end
23
+
24
+ def here(from = nil)
25
+ from ||= caller
26
+ new(from.first.split(/:\d+(?:$|:in)/).first).expand
27
+ end
28
+ alias_method :file, :here
29
+
30
+ def dir(from = nil)
31
+ from ||= caller
32
+ file(from).dir
33
+ end
34
+
35
+ def home
36
+ new(Dir.respond_to?(:home) ? Dir.home : new("~").expand)
37
+ end
38
+
39
+ def relative(path)
40
+ new(path).expand dir(caller)
41
+ end
42
+
43
+ def backfind(path)
44
+ here(caller).backfind(path)
45
+ end
46
+
47
+ def tmpfile(basename = '', tmpdir = nil, options = nil)
48
+ tempfile = Tempfile.new(basename, *[tmpdir, options].compact)
49
+ file = new tempfile
50
+ if block_given?
51
+ begin
52
+ yield file
53
+ ensure
54
+ tempfile.close
55
+ tempfile.unlink if file.exist?
56
+ end
57
+ end
58
+ file
59
+ end
60
+ alias_method :tempfile, :tmpfile
61
+
62
+ def tmpdir(prefix_suffix = nil, *rest)
63
+ require 'tmpdir'
64
+ dir = new Dir.mktmpdir(prefix_suffix, *rest)
65
+ if block_given?
66
+ begin
67
+ yield dir
68
+ ensure
69
+ FileUtils.remove_entry_secure(dir) rescue nil
70
+ end
71
+ end
72
+ dir
73
+ end
74
+ end
75
+
76
+ def initialize(*parts)
77
+ path = parts.size > 1 ? parts.join(File::SEPARATOR) : parts.first
78
+ if Tempfile === path
79
+ @_tmpfile = path # We would not want it to be GC'd
80
+ @path = path.path
81
+ elsif String === path
82
+ @path = path.dup
83
+ else
84
+ @path = path.to_s
85
+ end
86
+ taint if @path.tainted?
87
+ end
88
+
89
+ def / part
90
+ join part.to_s
91
+ end
92
+
93
+ def base # basename(extname)
94
+ basename(extname)
95
+ end
96
+
97
+ def ext # extname without leading .
98
+ ext = extname
99
+ ext.empty? ? ext : ext[1..-1]
100
+ end
101
+
102
+ def without_extension # rm_ext
103
+ dir / base
104
+ end
105
+
106
+ # NOTE: Pathname has a similar feature named sub_ext
107
+ # It might be a better method name
108
+ def replace_extension(ext)
109
+ ext = ".#{ext}" unless ext.start_with? '.'
110
+ Path.new(without_extension.to_s + ext)
111
+ end
112
+
113
+ def entries
114
+ (Dir.entries(@path) - DOTS).map { |entry| Path.new(@path, entry).cleanpath }
115
+ end
116
+
117
+ def glob(pattern, flags = 0)
118
+ Dir.glob(join(pattern), flags).map(&Path)
119
+ end
120
+
121
+ def rm
122
+ FileUtils.rm(@path)
123
+ self
124
+ end
125
+
126
+ def rm_f
127
+ FileUtils.rm_f(@path)
128
+ self
129
+ end
130
+
131
+ def rm_rf
132
+ FileUtils.rm_rf(@path)
133
+ self
134
+ end
135
+
136
+ def mkdir_p
137
+ FileUtils.mkdir_p(@path)
138
+ self
139
+ end
140
+
141
+ def write(contents, open_args = nil)
142
+ if IO.respond_to? :write
143
+ IO.write(@path, contents, *[open_args].compact)
144
+ else
145
+ open('w', *[open_args].compact) { |f| f.write(contents) }
146
+ end
147
+ end
148
+
149
+ def append(contents, open_args = nil)
150
+ if IO.respond_to? :write
151
+ open_args = (Array(open_args) << {:mode => 'a'})
152
+ IO.write(@path, contents, *open_args.compact)
153
+ else
154
+ open('a', *[open_args].compact) { |f| f.write(contents) }
155
+ end
156
+ end
157
+
158
+ def to_sym
159
+ to_s.to_sym
160
+ end
161
+
162
+ def relative_to other
163
+ relative_path_from Path.new other
164
+ end
165
+ alias_method :%, :relative_to
166
+
167
+ def ancestors
168
+ ancestors = lambda do |y|
169
+ y << path = expand
170
+ until (path = path.parent).root?
171
+ y << path
172
+ end
173
+ y << path
174
+ end
175
+ RUBY_VERSION > '1.9' ? Enumerator.new(&ancestors) : ancestors.call([])
176
+ end
177
+
178
+ def backfind(path)
179
+ condition = path[/\[(.*)\]$/, 1] || ''
180
+ path = $` unless condition.empty?
181
+
182
+ result = ancestors.find { |ancestor| (ancestor/path/condition).exist? }
183
+ result/path if result
184
+ end
185
+
186
+ alias_method :expand, :expand_path
187
+ alias_method :dir, :dirname
188
+ end
189
+
190
+ EPath = Path # to meet everyone's expectations
191
+
192
+ unless defined? NO_EPATH_GLOBAL_FUNCTION
193
+ def Path(*args)
194
+ Path.new(*args)
195
+ end
196
+ end
@@ -0,0 +1,873 @@
1
+ # Path's low-level implementation based on Pathname
2
+
3
+ require 'fileutils'
4
+
5
+ class Path
6
+ # :stopdoc:
7
+ SAME_PATHS = if File::FNM_SYSCASE.nonzero?
8
+ proc {|a, b| a.casecmp(b).zero?}
9
+ else
10
+ proc {|a, b| a == b}
11
+ end
12
+
13
+ # :startdoc:
14
+
15
+ def freeze() super; @path.freeze; self end
16
+ def taint() super; @path.taint; self end
17
+ def untaint() super; @path.untaint; self end
18
+
19
+ #
20
+ # Compare this pathname with +other+. The comparison is string-based.
21
+ # Be aware that two different paths (<tt>foo.txt</tt> and <tt>./foo.txt</tt>)
22
+ # can refer to the same file.
23
+ #
24
+ def == other
25
+ Path === other and @path == other.to_s
26
+ end
27
+ alias eql? ==
28
+
29
+ # Provides for comparing pathnames, case-sensitively.
30
+ def <=>(other)
31
+ return nil unless Path === other
32
+ @path.tr('/', "\0") <=> other.to_s.tr('/', "\0")
33
+ end
34
+
35
+ def hash # :nodoc:
36
+ @path.hash
37
+ end
38
+
39
+ # Return the path as a String.
40
+ def to_s
41
+ @path.dup
42
+ end
43
+
44
+ # to_path is implemented so Path objects are usable with File.open, etc.
45
+ alias to_path to_s
46
+
47
+ alias to_str to_s if RUBY_VERSION < '1.9'
48
+
49
+ def inspect
50
+ "#<Path #{@path}>"
51
+ end
52
+
53
+ if File::ALT_SEPARATOR
54
+ SEPARATOR_LIST = "#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}"
55
+ SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/
56
+ else
57
+ SEPARATOR_LIST = "#{Regexp.quote File::SEPARATOR}"
58
+ SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/
59
+ end
60
+
61
+ # Return a pathname which the extension of the basename is substituted by
62
+ # <i>repl</i>.
63
+ #
64
+ # If self has no extension part, <i>repl</i> is appended.
65
+ def sub_ext(repl)
66
+ ext = File.extname(@path)
67
+ Path.new(@path.chomp(ext) + repl)
68
+ end
69
+
70
+ # chop_basename(path) -> [pre-basename, basename] or nil
71
+ def chop_basename(path)
72
+ base = File.basename(path)
73
+ if /\A#{SEPARATOR_PAT}?\z/o =~ base
74
+ return nil
75
+ else
76
+ return path[0, path.rindex(base)], base
77
+ end
78
+ end
79
+ private :chop_basename
80
+
81
+ # split_names(path) -> prefix, [name, ...]
82
+ def split_names(path)
83
+ names = []
84
+ while r = chop_basename(path)
85
+ path, basename = r
86
+ names.unshift basename
87
+ end
88
+ return path, names
89
+ end
90
+ private :split_names
91
+
92
+ def prepend_prefix(prefix, relpath)
93
+ if relpath.empty?
94
+ File.dirname(prefix)
95
+ elsif /#{SEPARATOR_PAT}/o =~ prefix
96
+ prefix = File.dirname(prefix)
97
+ prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
98
+ prefix + relpath
99
+ else
100
+ prefix + relpath
101
+ end
102
+ end
103
+ private :prepend_prefix
104
+
105
+ # Returns clean pathname of +self+ with consecutive slashes and useless dots
106
+ # removed. The filesystem is not accessed.
107
+ #
108
+ # If +consider_symlink+ is +true+, then a more conservative algorithm is used
109
+ # to avoid breaking symbolic linkages. This may retain more <tt>..</tt>
110
+ # entries than absolutely necessary, but without accessing the filesystem,
111
+ # this can't be avoided. See #realpath.
112
+ #
113
+ def cleanpath(consider_symlink=false)
114
+ if consider_symlink
115
+ cleanpath_conservative
116
+ else
117
+ cleanpath_aggressive
118
+ end
119
+ end
120
+
121
+ #
122
+ # Clean the path simply by resolving and removing excess "." and ".." entries.
123
+ # Nothing more, nothing less.
124
+ #
125
+ def cleanpath_aggressive
126
+ path = @path
127
+ names = []
128
+ pre = path
129
+ while r = chop_basename(pre)
130
+ pre, base = r
131
+ case base
132
+ when '.'
133
+ when '..'
134
+ names.unshift base
135
+ else
136
+ if names[0] == '..'
137
+ names.shift
138
+ else
139
+ names.unshift base
140
+ end
141
+ end
142
+ end
143
+ if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
144
+ names.shift while names[0] == '..'
145
+ end
146
+ Path.new(prepend_prefix(pre, File.join(*names)))
147
+ end
148
+ private :cleanpath_aggressive
149
+
150
+ # has_trailing_separator?(path) -> bool
151
+ def has_trailing_separator?(path)
152
+ if r = chop_basename(path)
153
+ pre, basename = r
154
+ pre.length + basename.length < path.length
155
+ else
156
+ false
157
+ end
158
+ end
159
+ private :has_trailing_separator?
160
+
161
+ # add_trailing_separator(path) -> path
162
+ def add_trailing_separator(path)
163
+ if File.basename(path + 'a') == 'a'
164
+ path
165
+ else
166
+ File.join(path, "") # xxx: Is File.join is appropriate to add separator?
167
+ end
168
+ end
169
+ private :add_trailing_separator
170
+
171
+ def del_trailing_separator(path)
172
+ if r = chop_basename(path)
173
+ pre, basename = r
174
+ pre + basename
175
+ elsif /#{SEPARATOR_PAT}+\z/o =~ path
176
+ $` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o]
177
+ else
178
+ path
179
+ end
180
+ end
181
+ private :del_trailing_separator
182
+
183
+ def cleanpath_conservative
184
+ path = @path
185
+ names = []
186
+ pre = path
187
+ while r = chop_basename(pre)
188
+ pre, base = r
189
+ names.unshift base if base != '.'
190
+ end
191
+ if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
192
+ names.shift while names[0] == '..'
193
+ end
194
+ if names.empty?
195
+ Path.new(File.dirname(pre))
196
+ else
197
+ if names.last != '..' && File.basename(path) == '.'
198
+ names << '.'
199
+ end
200
+ result = prepend_prefix(pre, File.join(*names))
201
+ if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path)
202
+ Path.new(add_trailing_separator(result))
203
+ else
204
+ Path.new(result)
205
+ end
206
+ end
207
+ end
208
+ private :cleanpath_conservative
209
+
210
+ if File.respond_to?(:realpath) and File.respond_to?(:realdirpath)
211
+ def real_path_internal(strict = false, basedir = nil)
212
+ strict ? File.realpath(@path, basedir) : File.realdirpath(@path, basedir)
213
+ end
214
+ private :real_path_internal
215
+ else
216
+ def realpath_rec(prefix, unresolved, h, strict, last = true)
217
+ resolved = []
218
+ until unresolved.empty?
219
+ n = unresolved.shift
220
+ if n == '.'
221
+ next
222
+ elsif n == '..'
223
+ resolved.pop
224
+ else
225
+ path = prepend_prefix(prefix, File.join(*(resolved + [n])))
226
+ if h.include? path
227
+ if h[path] == :resolving
228
+ raise Errno::ELOOP.new(path)
229
+ else
230
+ prefix, *resolved = h[path]
231
+ end
232
+ else
233
+ begin
234
+ s = File.lstat(path)
235
+ rescue Errno::ENOENT => e
236
+ raise e if strict || !last || !unresolved.empty?
237
+ resolved << n
238
+ break
239
+ end
240
+ if s.symlink?
241
+ h[path] = :resolving
242
+ link_prefix, link_names = split_names(File.readlink(path))
243
+ if link_prefix == ''
244
+ prefix, *resolved = h[path] = realpath_rec(prefix, resolved + link_names, h, strict, unresolved.empty?)
245
+ else
246
+ prefix, *resolved = h[path] = realpath_rec(link_prefix, link_names, h, strict, unresolved.empty?)
247
+ end
248
+ else
249
+ resolved << n
250
+ h[path] = [prefix, *resolved]
251
+ end
252
+ end
253
+ end
254
+ end
255
+ return prefix, *resolved
256
+ end
257
+ private :realpath_rec
258
+
259
+ def real_path_internal(strict = false, basedir = nil)
260
+ path = @path
261
+ path = File.join(basedir, path) if basedir and relative?
262
+ prefix, names = split_names(path)
263
+ if prefix == ''
264
+ prefix, names2 = split_names(Dir.pwd)
265
+ names = names2 + names
266
+ end
267
+ prefix, *names = realpath_rec(prefix, names, {}, strict)
268
+ prepend_prefix(prefix, File.join(*names))
269
+ end
270
+ private :real_path_internal
271
+ end
272
+
273
+ #
274
+ # Returns the real (absolute) pathname of +self+ in the actual
275
+ # filesystem not containing symlinks or useless dots.
276
+ #
277
+ # All components of the pathname must exist when this method is
278
+ # called.
279
+ #
280
+ def realpath(basedir=nil)
281
+ Path.new(real_path_internal(true, basedir))
282
+ end
283
+
284
+ #
285
+ # Returns the real (absolute) pathname of +self+ in the actual filesystem.
286
+ # The real pathname doesn't contain symlinks or useless dots.
287
+ #
288
+ # The last component of the real pathname can be nonexistent.
289
+ #
290
+ def realdirpath(basedir=nil)
291
+ Path.new(real_path_internal(false, basedir))
292
+ end
293
+
294
+ # #parent returns the parent directory.
295
+ #
296
+ # This is same as <tt>self + '..'</tt>.
297
+ def parent
298
+ self + '..'
299
+ end
300
+
301
+ # #mountpoint? returns +true+ if <tt>self</tt> points to a mountpoint.
302
+ def mountpoint?
303
+ begin
304
+ stat1 = lstat
305
+ stat2 = parent.lstat
306
+ stat1.dev != stat2.dev or stat1.ino == stat2.ino
307
+ rescue Errno::ENOENT
308
+ false
309
+ end
310
+ end
311
+
312
+ #
313
+ # #root? is a predicate for root directories. I.e. it returns +true+ if the
314
+ # pathname consists of consecutive slashes.
315
+ #
316
+ # It doesn't access actual filesystem. So it may return +false+ for some
317
+ # pathnames which points to roots such as <tt>/usr/..</tt>.
318
+ #
319
+ def root?
320
+ !!(chop_basename(@path) == nil && /#{SEPARATOR_PAT}/o =~ @path)
321
+ end
322
+
323
+ # Predicate method for testing whether a path is absolute.
324
+ # It returns +true+ if the pathname begins with a slash.
325
+ def absolute?
326
+ !relative?
327
+ end
328
+
329
+ # The opposite of #absolute?
330
+ def relative?
331
+ path = @path
332
+ while r = chop_basename(path)
333
+ path, = r
334
+ end
335
+ path == ''
336
+ end
337
+
338
+ #
339
+ # Iterates over each component of the path.
340
+ #
341
+ # Path.new("/usr/bin/ruby").each_filename {|filename| ... }
342
+ # # yields "usr", "bin", and "ruby".
343
+ #
344
+ def each_filename # :yield: filename
345
+ return to_enum(__method__) unless block_given?
346
+ _, names = split_names(@path)
347
+ names.each {|filename| yield filename }
348
+ nil
349
+ end
350
+
351
+ # Iterates over and yields a new Path object
352
+ # for each element in the given path in descending order.
353
+ #
354
+ # Path.new('/path/to/some/file.rb').descend {|v| p v}
355
+ # #<Path:/>
356
+ # #<Path:/path>
357
+ # #<Path:/path/to>
358
+ # #<Path:/path/to/some>
359
+ # #<Path:/path/to/some/file.rb>
360
+ #
361
+ # Path.new('path/to/some/file.rb').descend {|v| p v}
362
+ # #<Path:path>
363
+ # #<Path:path/to>
364
+ # #<Path:path/to/some>
365
+ # #<Path:path/to/some/file.rb>
366
+ #
367
+ # It doesn't access actual filesystem.
368
+ def descend
369
+ vs = []
370
+ ascend {|v| vs << v }
371
+ vs.reverse_each {|v| yield v }
372
+ nil
373
+ end
374
+
375
+ # Iterates over and yields a new Path object
376
+ # for each element in the given path in ascending order.
377
+ #
378
+ # Path.new('/path/to/some/file.rb').ascend {|v| p v}
379
+ # #<Path:/path/to/some/file.rb>
380
+ # #<Path:/path/to/some>
381
+ # #<Path:/path/to>
382
+ # #<Path:/path>
383
+ # #<Path:/>
384
+ #
385
+ # Path.new('path/to/some/file.rb').ascend {|v| p v}
386
+ # #<Path:path/to/some/file.rb>
387
+ # #<Path:path/to/some>
388
+ # #<Path:path/to>
389
+ # #<Path:path>
390
+ #
391
+ # It doesn't access actual filesystem.
392
+ def ascend
393
+ path = @path
394
+ yield self
395
+ while r = chop_basename(path)
396
+ path, = r
397
+ break if path.empty?
398
+ yield Path.new(del_trailing_separator(path))
399
+ end
400
+ end
401
+
402
+ #
403
+ # Path#+ appends a pathname fragment to this one to produce a new Path
404
+ # object.
405
+ #
406
+ # p1 = Path.new("/usr") # Path:/usr
407
+ # p2 = p1 + "bin/ruby" # Path:/usr/bin/ruby
408
+ # p3 = p1 + "/etc/passwd" # Path:/etc/passwd
409
+ #
410
+ # This method doesn't access the file system; it is pure string manipulation.
411
+ #
412
+ def +(other)
413
+ other = Path.new(other) unless Path === other
414
+ Path.new(plus(@path, other.to_s))
415
+ end
416
+
417
+ def plus(path1, path2) # -> path
418
+ prefix2 = path2
419
+ index_list2 = []
420
+ basename_list2 = []
421
+ while r2 = chop_basename(prefix2)
422
+ prefix2, basename2 = r2
423
+ index_list2.unshift prefix2.length
424
+ basename_list2.unshift basename2
425
+ end
426
+ return path2 if prefix2 != ''
427
+ prefix1 = path1
428
+ while true
429
+ while !basename_list2.empty? && basename_list2.first == '.'
430
+ index_list2.shift
431
+ basename_list2.shift
432
+ end
433
+ break unless r1 = chop_basename(prefix1)
434
+ prefix1, basename1 = r1
435
+ next if basename1 == '.'
436
+ if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..'
437
+ prefix1 = prefix1 + basename1
438
+ break
439
+ end
440
+ index_list2.shift
441
+ basename_list2.shift
442
+ end
443
+ r1 = chop_basename(prefix1)
444
+ if !r1 && /#{SEPARATOR_PAT}/o =~ File.basename(prefix1)
445
+ while !basename_list2.empty? && basename_list2.first == '..'
446
+ index_list2.shift
447
+ basename_list2.shift
448
+ end
449
+ end
450
+ if !basename_list2.empty?
451
+ suffix2 = path2[index_list2.first..-1]
452
+ r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2
453
+ else
454
+ r1 ? prefix1 : File.dirname(prefix1)
455
+ end
456
+ end
457
+ private :plus
458
+
459
+ #
460
+ # Path#join joins pathnames.
461
+ #
462
+ # <tt>path0.join(path1, ..., pathN)</tt> is the same as
463
+ # <tt>path0 + path1 + ... + pathN</tt>.
464
+ #
465
+ def join(*args)
466
+ args.unshift self
467
+ result = args.pop
468
+ result = Path.new(result) unless Path === result
469
+ return result if result.absolute?
470
+ args.reverse_each {|arg|
471
+ arg = Path.new(arg) unless Path === arg
472
+ result = arg + result
473
+ return result if result.absolute?
474
+ }
475
+ result
476
+ end
477
+
478
+ #
479
+ # Returns the children of the directory (files and subdirectories, not
480
+ # recursive) as an array of Path objects. By default, the returned
481
+ # pathnames will have enough information to access the files. If you set
482
+ # +with_directory+ to +false+, then the returned pathnames will contain the
483
+ # filename only.
484
+ #
485
+ # For example:
486
+ # pn = Path("/usr/lib/ruby/1.8")
487
+ # pn.children
488
+ # # -> [ Path:/usr/lib/ruby/1.8/English.rb,
489
+ # Path:/usr/lib/ruby/1.8/Env.rb,
490
+ # Path:/usr/lib/ruby/1.8/abbrev.rb, ... ]
491
+ # pn.children(false)
492
+ # # -> [ Path:English.rb, Path:Env.rb, Path:abbrev.rb, ... ]
493
+ #
494
+ # Note that the results never contain the entries <tt>.</tt> and <tt>..</tt> in
495
+ # the directory because they are not children.
496
+ #
497
+ def children(with_directory=true)
498
+ with_directory = false if @path == '.'
499
+ result = []
500
+ Dir.foreach(@path) {|e|
501
+ next if e == '.' || e == '..'
502
+ if with_directory
503
+ result << Path.new(File.join(@path, e))
504
+ else
505
+ result << Path.new(e)
506
+ end
507
+ }
508
+ result
509
+ end
510
+
511
+ # Iterates over the children of the directory
512
+ # (files and subdirectories, not recursive).
513
+ # It yields Path object for each child.
514
+ # By default, the yielded pathnames will have enough information to access the files.
515
+ # If you set +with_directory+ to +false+, then the returned pathnames will contain the filename only.
516
+ #
517
+ # Path("/usr/local").each_child {|f| p f }
518
+ # #=> #<Path:/usr/local/share>
519
+ # # #<Path:/usr/local/bin>
520
+ # # #<Path:/usr/local/games>
521
+ # # #<Path:/usr/local/lib>
522
+ # # #<Path:/usr/local/include>
523
+ # # #<Path:/usr/local/sbin>
524
+ # # #<Path:/usr/local/src>
525
+ # # #<Path:/usr/local/man>
526
+ #
527
+ # Path("/usr/local").each_child(false) {|f| p f }
528
+ # #=> #<Path:share>
529
+ # # #<Path:bin>
530
+ # # #<Path:games>
531
+ # # #<Path:lib>
532
+ # # #<Path:include>
533
+ # # #<Path:sbin>
534
+ # # #<Path:src>
535
+ # # #<Path:man>
536
+ #
537
+ def each_child(with_directory=true, &b)
538
+ children(with_directory).each(&b)
539
+ end
540
+
541
+ #
542
+ # #relative_path_from returns a relative path from the argument to the
543
+ # receiver. If +self+ is absolute, the argument must be absolute too. If
544
+ # +self+ is relative, the argument must be relative too.
545
+ #
546
+ # #relative_path_from doesn't access the filesystem. It assumes no symlinks.
547
+ #
548
+ # ArgumentError is raised when it cannot find a relative path.
549
+ #
550
+ def relative_path_from(base_directory)
551
+ dest_directory = cleanpath.to_s
552
+ base_directory = base_directory.cleanpath.to_s
553
+ dest_prefix = dest_directory
554
+ dest_names = []
555
+ while r = chop_basename(dest_prefix)
556
+ dest_prefix, basename = r
557
+ dest_names.unshift basename if basename != '.'
558
+ end
559
+ base_prefix = base_directory
560
+ base_names = []
561
+ while r = chop_basename(base_prefix)
562
+ base_prefix, basename = r
563
+ base_names.unshift basename if basename != '.'
564
+ end
565
+ unless SAME_PATHS[dest_prefix, base_prefix]
566
+ raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}"
567
+ end
568
+ while !dest_names.empty? &&
569
+ !base_names.empty? &&
570
+ SAME_PATHS[dest_names.first, base_names.first]
571
+ dest_names.shift
572
+ base_names.shift
573
+ end
574
+ if base_names.include? '..'
575
+ raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
576
+ end
577
+ base_names.fill('..')
578
+ relpath_names = base_names + dest_names
579
+ if relpath_names.empty?
580
+ Path.new('.')
581
+ else
582
+ Path.new(File.join(*relpath_names))
583
+ end
584
+ end
585
+ end
586
+
587
+ class Path # * IO *
588
+ #
589
+ # #each_line iterates over the line in the file. It yields a String object
590
+ # for each line.
591
+ #
592
+ def each_line(*args, &block) # :yield: line
593
+ IO.foreach(@path, *args, &block)
594
+ end
595
+
596
+ # See <tt>IO.read</tt>. Returns all data from the file, or the first +N+ bytes
597
+ # if specified.
598
+ def read(*args) IO.read(@path, *args) end
599
+
600
+ # See <tt>IO.binread</tt>. Returns all the bytes from the file, or the first +N+
601
+ # if specified.
602
+ def binread(*args) IO.binread(@path, *args) end
603
+
604
+ # See <tt>IO.readlines</tt>. Returns all the lines from the file.
605
+ def readlines(*args) IO.readlines(@path, *args) end
606
+
607
+ # See <tt>IO.sysopen</tt>.
608
+ def sysopen(*args) IO.sysopen(@path, *args) end
609
+ end
610
+
611
+
612
+ class Path # * File *
613
+
614
+ # See <tt>File.atime</tt>. Returns last access time.
615
+ def atime() File.atime(@path) end
616
+
617
+ # See <tt>File.ctime</tt>. Returns last (directory entry, not file) change time.
618
+ def ctime() File.ctime(@path) end
619
+
620
+ # See <tt>File.mtime</tt>. Returns last modification time.
621
+ def mtime() File.mtime(@path) end
622
+
623
+ # See <tt>File.chmod</tt>. Changes permissions.
624
+ def chmod(mode) File.chmod(mode, @path) end
625
+
626
+ # See <tt>File.lchmod</tt>.
627
+ def lchmod(mode) File.lchmod(mode, @path) end
628
+
629
+ # See <tt>File.chown</tt>. Change owner and group of file.
630
+ def chown(owner, group) File.chown(owner, group, @path) end
631
+
632
+ # See <tt>File.lchown</tt>.
633
+ def lchown(owner, group) File.lchown(owner, group, @path) end
634
+
635
+ # See <tt>File.fnmatch</tt>. Return +true+ if the receiver matches the given
636
+ # pattern.
637
+ def fnmatch(pattern, *args) File.fnmatch(pattern, @path, *args) end
638
+
639
+ # See <tt>File.fnmatch?</tt> (same as #fnmatch).
640
+ def fnmatch?(pattern, *args) File.fnmatch?(pattern, @path, *args) end
641
+
642
+ # See <tt>File.ftype</tt>. Returns "type" of file ("file", "directory",
643
+ # etc).
644
+ def ftype() File.ftype(@path) end
645
+
646
+ # See <tt>File.link</tt>. Creates a hard link.
647
+ def make_link(old) File.link(old, @path) end
648
+
649
+ # See <tt>File.open</tt>. Opens the file for reading or writing.
650
+ def open(*args, &block) # :yield: file
651
+ File.open(@path, *args, &block)
652
+ end
653
+
654
+ # See <tt>File.readlink</tt>. Read symbolic link.
655
+ def readlink() Path.new(File.readlink(@path)) end
656
+
657
+ # See <tt>File.rename</tt>. Rename the file.
658
+ def rename(to) File.rename(@path, to) end
659
+
660
+ # See <tt>File.stat</tt>. Returns a <tt>File::Stat</tt> object.
661
+ def stat() File.stat(@path) end
662
+
663
+ # See <tt>File.lstat</tt>.
664
+ def lstat() File.lstat(@path) end
665
+
666
+ # See <tt>File.symlink</tt>. Creates a symbolic link.
667
+ def make_symlink(old) File.symlink(old, @path) end
668
+
669
+ # See <tt>File.truncate</tt>. Truncate the file to +length+ bytes.
670
+ def truncate(length) File.truncate(@path, length) end
671
+
672
+ # See <tt>File.utime</tt>. Update the access and modification times.
673
+ def utime(atime, mtime) File.utime(atime, mtime, @path) end
674
+
675
+ # See <tt>File.basename</tt>. Returns the last component of the path.
676
+ def basename(*args) Path.new(File.basename(@path, *args)) end
677
+
678
+ # See <tt>File.dirname</tt>. Returns all but the last component of the path.
679
+ def dirname() Path.new(File.dirname(@path)) end
680
+
681
+ # See <tt>File.extname</tt>. Returns the file's extension.
682
+ def extname() File.extname(@path) end
683
+
684
+ # See <tt>File.expand_path</tt>.
685
+ def expand_path(*args) Path.new(File.expand_path(@path, *args)) end
686
+
687
+ # See <tt>File.split</tt>. Returns the #dirname and the #basename in an
688
+ # Array.
689
+ def split() File.split(@path).map {|f| Path.new(f) } end
690
+ end
691
+
692
+
693
+ class Path # * FileTest *
694
+
695
+ # See <tt>FileTest.blockdev?</tt>.
696
+ def blockdev?() FileTest.blockdev?(@path) end
697
+
698
+ # See <tt>FileTest.chardev?</tt>.
699
+ def chardev?() FileTest.chardev?(@path) end
700
+
701
+ # See <tt>FileTest.executable?</tt>.
702
+ def executable?() FileTest.executable?(@path) end
703
+
704
+ # See <tt>FileTest.executable_real?</tt>.
705
+ def executable_real?() FileTest.executable_real?(@path) end
706
+
707
+ # See <tt>FileTest.exist?</tt>.
708
+ def exist?() FileTest.exist?(@path) end
709
+
710
+ # See <tt>FileTest.grpowned?</tt>.
711
+ def grpowned?() FileTest.grpowned?(@path) end
712
+
713
+ # See <tt>FileTest.directory?</tt>.
714
+ def directory?() FileTest.directory?(@path) end
715
+
716
+ # See <tt>FileTest.file?</tt>.
717
+ def file?() FileTest.file?(@path) end
718
+
719
+ # See <tt>FileTest.pipe?</tt>.
720
+ def pipe?() FileTest.pipe?(@path) end
721
+
722
+ # See <tt>FileTest.socket?</tt>.
723
+ def socket?() FileTest.socket?(@path) end
724
+
725
+ # See <tt>FileTest.owned?</tt>.
726
+ def owned?() FileTest.owned?(@path) end
727
+
728
+ # See <tt>FileTest.readable?</tt>.
729
+ def readable?() FileTest.readable?(@path) end
730
+
731
+ if FileTest.respond_to? :world_readable?
732
+ # See <tt>FileTest.world_readable?</tt>.
733
+ def world_readable?() FileTest.world_readable?(@path) end
734
+ else
735
+ def world_readable?
736
+ mode = File.stat(@path).mode & 0777
737
+ mode if (mode & 04).nonzero?
738
+ end
739
+ end
740
+
741
+ # See <tt>FileTest.readable_real?</tt>.
742
+ def readable_real?() FileTest.readable_real?(@path) end
743
+
744
+ # See <tt>FileTest.setuid?</tt>.
745
+ def setuid?() FileTest.setuid?(@path) end
746
+
747
+ # See <tt>FileTest.setgid?</tt>.
748
+ def setgid?() FileTest.setgid?(@path) end
749
+
750
+ # See <tt>FileTest.size</tt>.
751
+ def size() FileTest.size(@path) end
752
+
753
+ # See <tt>FileTest.size?</tt>.
754
+ def size?() FileTest.size?(@path) end
755
+
756
+ # See <tt>FileTest.sticky?</tt>.
757
+ def sticky?() FileTest.sticky?(@path) end
758
+
759
+ # See <tt>FileTest.symlink?</tt>.
760
+ def symlink?() FileTest.symlink?(@path) end
761
+
762
+ # See <tt>FileTest.writable?</tt>.
763
+ def writable?() FileTest.writable?(@path) end
764
+
765
+ if FileTest.respond_to? :world_writable?
766
+ # See <tt>FileTest.world_writable?</tt>.
767
+ def world_writable?() FileTest.world_writable?(@path) end
768
+ else
769
+ def world_writable?
770
+ mode = File.stat(@path).mode & 0777
771
+ mode if (mode & 02).nonzero?
772
+ end
773
+ end
774
+
775
+ # See <tt>FileTest.writable_real?</tt>.
776
+ def writable_real?() FileTest.writable_real?(@path) end
777
+
778
+ # See <tt>FileTest.zero?</tt>.
779
+ def zero?() FileTest.zero?(@path) end
780
+ end
781
+
782
+
783
+ class Path # * Dir *
784
+ class << self
785
+ # See <tt>Dir.glob</tt>. Returns or yields Path objects.
786
+ def glob(*args) # :yield: pathname
787
+ if block_given?
788
+ Dir.glob(*args) {|f| yield new(f) }
789
+ else
790
+ Dir.glob(*args).map {|f| new(f) }
791
+ end
792
+ end
793
+
794
+ # See <tt>Dir.getwd</tt>. Returns the current working directory as a Path.
795
+ def Path.getwd
796
+ new Dir.getwd
797
+ end
798
+
799
+ alias pwd getwd
800
+ end
801
+
802
+ # Iterates over the entries (files and subdirectories) in the directory. It
803
+ # yields a Path object for each entry.
804
+ def each_entry(&block) # :yield: pathname
805
+ Dir.foreach(@path) {|f| yield Path.new(f) }
806
+ end
807
+
808
+ # See <tt>Dir.mkdir</tt>. Create the referenced directory.
809
+ def mkdir(*args) Dir.mkdir(@path, *args) end
810
+
811
+ # See <tt>Dir.rmdir</tt>. Remove the referenced directory.
812
+ def rmdir() Dir.rmdir(@path) end
813
+
814
+ # See <tt>Dir.open</tt>.
815
+ def opendir(&block) # :yield: dir
816
+ Dir.open(@path, &block)
817
+ end
818
+ end
819
+
820
+
821
+ class Path # * Find *
822
+ #
823
+ # Path#find is an iterator to traverse a directory tree in a depth first
824
+ # manner. It yields a Path for each file under "this" directory.
825
+ #
826
+ # Since it is implemented by <tt>find.rb</tt>, <tt>Find.prune</tt> can be used
827
+ # to control the traversal.
828
+ #
829
+ # If +self+ is <tt>.</tt>, yielded pathnames begin with a filename in the
830
+ # current directory, not <tt>./</tt>.
831
+ #
832
+ def find # :yield: pathname
833
+ return to_enum(__method__) unless block_given?
834
+ require 'find'
835
+ if @path == '.'
836
+ Find.find(@path) {|f| yield Path.new(f.sub(%r{\A\./}, '')) }
837
+ else
838
+ Find.find(@path) {|f| yield Path.new(f) }
839
+ end
840
+ end
841
+ end
842
+
843
+
844
+ class Path # * FileUtils *
845
+ # See <tt>FileUtils.mkpath</tt>. Creates a full path, including any
846
+ # intermediate directories that don't yet exist.
847
+ def mkpath
848
+ FileUtils.mkpath(@path)
849
+ nil
850
+ end
851
+
852
+ # See <tt>FileUtils.rm_r</tt>. Deletes a directory and all beneath it.
853
+ def rmtree
854
+ # The name "rmtree" is borrowed from File::Path of Perl.
855
+ # File::Path provides "mkpath" and "rmtree".
856
+ FileUtils.rm_r(@path)
857
+ nil
858
+ end
859
+ end
860
+
861
+
862
+ class Path # * mixed *
863
+ # Removes a file or directory, using <tt>File.unlink</tt> or
864
+ # <tt>Dir.unlink</tt> as necessary.
865
+ def unlink()
866
+ begin
867
+ Dir.unlink @path
868
+ rescue Errno::ENOTDIR
869
+ File.unlink @path
870
+ end
871
+ end
872
+ alias delete unlink
873
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: epath
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,16 +9,20 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-07 00:00:00.000000000 Z
12
+ date: 2011-12-16 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
- email:
15
+ email: eregontp@gmail.com
16
16
  executables: []
17
17
  extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
+ - lib/epath/implementation.rb
21
+ - lib/epath.rb
22
+ - README.md
23
+ - LICENSE
20
24
  - epath.gemspec
21
- homepage:
25
+ homepage: https://github.com/eregon/epath
22
26
  licenses: []
23
27
  post_install_message:
24
28
  rdoc_options: []
@@ -38,8 +42,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
38
42
  version: '0'
39
43
  requirements: []
40
44
  rubyforge_project:
41
- rubygems_version: 1.8.10
45
+ rubygems_version: 1.8.11
42
46
  signing_key:
43
47
  specification_version: 3
44
- summary: summary
48
+ summary: a Path manipulation library
45
49
  test_files: []