epath 0.0.0 → 0.0.1

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/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: []