epath 0.0.1 → 0.1.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.
@@ -0,0 +1,109 @@
1
+ class Path
2
+ # Returns last access time. See +File.atime+.
3
+ def atime
4
+ File.atime(@path)
5
+ end
6
+
7
+ # Returns last (directory entry, not file) change time. See +File.ctime+.
8
+ def ctime
9
+ File.ctime(@path)
10
+ end
11
+
12
+ # Returns last modification time. See +File.mtime+.
13
+ def mtime
14
+ File.mtime(@path)
15
+ end
16
+
17
+ # Changes permissions. See +File.chmod+.
18
+ def chmod(mode) File.chmod(mode, @path) end
19
+
20
+ # See +File.lchmod+.
21
+ def lchmod(mode) File.lchmod(mode, @path) end
22
+
23
+ # Change owner and group of file. See +File.chown+.
24
+ def chown(owner, group)
25
+ File.chown(owner, group, @path)
26
+ end
27
+
28
+ # See +File.lchown+.
29
+ def lchown(owner, group)
30
+ File.lchown(owner, group, @path)
31
+ end
32
+
33
+ # Returns "type" of file ("file", "directory", etc). See +File.ftype+.
34
+ def ftype
35
+ File.ftype(@path)
36
+ end
37
+
38
+ # Creates a hard link to +target+ and returns self.
39
+ #
40
+ # Raises Errno::EEXIST if self already exist.
41
+ # See +File.link+ (arguments are swapped).
42
+ def make_link(target)
43
+ File.link(target, @path)
44
+ self
45
+ end
46
+
47
+ # Read symbolic link. See +File.readlink+.
48
+ def readlink
49
+ Path.new(File.readlink(@path))
50
+ end
51
+
52
+ # Rename the file and returns the new Path. See +File.rename+.
53
+ def rename(to)
54
+ File.rename(@path, to)
55
+ Path(to)
56
+ end
57
+
58
+ # Returns a +File::Stat+ object. See +File.stat+.
59
+ def stat
60
+ File.stat(@path)
61
+ end
62
+
63
+ # See +File.lstat+.
64
+ def lstat
65
+ File.lstat(@path)
66
+ end
67
+
68
+ # See +File.size+.
69
+ def size
70
+ File.size(@path)
71
+ end
72
+
73
+ # Creates a symbolic link to +target+ and returns self.
74
+ #
75
+ # Raises Errno::EEXIST if self already exist.
76
+ # See +File.symlink+ (arguments are swapped).
77
+ def make_symlink(target)
78
+ File.symlink(target, @path)
79
+ self
80
+ end
81
+
82
+ # Truncate the file to +length+ bytes. See +File.truncate+.
83
+ def truncate(length) File.truncate(@path, length) end
84
+
85
+ # Update the access and modification times. See +File.utime+.
86
+ def utime(atime, mtime) File.utime(atime, mtime, @path) end
87
+
88
+ # See +File.expand_path+.
89
+ def expand_path(*args)
90
+ Path.new(File.expand_path(@path, *args))
91
+ end
92
+ alias :expand :expand_path
93
+
94
+ # Returns the real (absolute) path of +self+ in the actual
95
+ # filesystem not containing symlinks or useless dots.
96
+ #
97
+ # All components of the path must exist when this method is called.
98
+ def realpath(basedir=nil)
99
+ Path.new(real_path_internal(true, basedir))
100
+ end
101
+
102
+ # Returns the real (absolute) path of +self+ in the actual filesystem.
103
+ # The real path doesn't contain symlinks or useless dots.
104
+ #
105
+ # The last component of the real path can be nonexistent.
106
+ def realdirpath(basedir=nil)
107
+ Path.new(real_path_internal(false, basedir))
108
+ end
109
+ end
@@ -0,0 +1,12 @@
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
@@ -0,0 +1,149 @@
1
+ # All methods from FileTest and all predicates from File are included
2
+
3
+ class Path
4
+ # See +File.blockdev?+.
5
+ def blockdev?
6
+ File.blockdev?(@path)
7
+ end
8
+
9
+ # See +File.chardev?+.
10
+ def chardev?
11
+ File.chardev?(@path)
12
+ end
13
+
14
+ # See +File.executable?+.
15
+ def executable?
16
+ File.executable?(@path)
17
+ end
18
+
19
+ # See +File.executable_real?+.
20
+ def executable_real?
21
+ File.executable_real?(@path)
22
+ end
23
+
24
+ # See +File.exist?+.
25
+ def exist?
26
+ File.exist?(@path)
27
+ end
28
+ alias :exists? :exist?
29
+
30
+ # See +File.grpowned?+.
31
+ def grpowned?
32
+ File.grpowned?(@path)
33
+ end
34
+
35
+ # See +File.directory?+.
36
+ def directory?
37
+ File.directory?(@path)
38
+ end
39
+ alias :dir? :directory?
40
+
41
+ # See +File.file?+.
42
+ def file?
43
+ File.file?(@path)
44
+ end
45
+
46
+ # See +File.pipe?+.
47
+ def pipe?
48
+ File.pipe?(@path)
49
+ end
50
+
51
+ # See +File.socket?+.
52
+ def socket?
53
+ File.socket?(@path)
54
+ end
55
+
56
+ # See +File.owned?+.
57
+ def owned?
58
+ File.owned?(@path)
59
+ end
60
+
61
+ # See +File.readable?+.
62
+ def readable?
63
+ File.readable?(@path)
64
+ end
65
+
66
+ if File.respond_to? :world_readable?
67
+ # See +File.world_readable?+.
68
+ def world_readable?
69
+ File.world_readable?(@path)
70
+ end
71
+ else
72
+ def world_readable?
73
+ mode = File.stat(@path).mode & 0777
74
+ mode if (mode & 04).nonzero?
75
+ end
76
+ end
77
+
78
+ # See +File.readable_real?+.
79
+ def readable_real?
80
+ File.readable_real?(@path)
81
+ end
82
+
83
+ # See +File.setuid?+.
84
+ def setuid?
85
+ File.setuid?(@path)
86
+ end
87
+
88
+ # See +File.setgid?+.
89
+ def setgid?
90
+ File.setgid?(@path)
91
+ end
92
+
93
+ # See +File.size?+.
94
+ def size?
95
+ File.size?(@path)
96
+ end
97
+
98
+ # See +File.sticky?+.
99
+ def sticky?
100
+ File.sticky?(@path)
101
+ end
102
+
103
+ # See +File.symlink?+.
104
+ def symlink?
105
+ File.symlink?(@path)
106
+ end
107
+
108
+ # See +File.writable?+.
109
+ def writable?
110
+ File.writable?(@path)
111
+ end
112
+
113
+ if File.respond_to? :world_writable?
114
+ # See +File.world_writable?+.
115
+ def world_writable?
116
+ File.world_writable?(@path)
117
+ end
118
+ else
119
+ def world_writable?
120
+ mode = File.stat(@path).mode & 0777
121
+ mode if (mode & 02).nonzero?
122
+ end
123
+ end
124
+
125
+ # See +File.writable_real?+.
126
+ def writable_real?
127
+ File.writable_real?(@path)
128
+ end
129
+
130
+ # See +File.zero?+.
131
+ # empty? is not defined in File/FileTest, but is is clearer
132
+ def zero?
133
+ File.zero?(@path)
134
+ end
135
+ alias :empty? :zero?
136
+
137
+ # See +File.identical?+.
138
+ def identical?(path)
139
+ File.identical?(@path, path)
140
+ end
141
+
142
+ # Only in File, not FileTest
143
+
144
+ # Return +true+ if the receiver matches the given pattern.
145
+ # See +File.fnmatch?+.
146
+ def fnmatch?(pattern, *args)
147
+ File.fnmatch?(pattern, @path, *args)
148
+ end
149
+ end
@@ -0,0 +1,45 @@
1
+ require 'fileutils'
2
+
3
+ class Path
4
+ # Creates a full path, including any intermediate directories that don't yet exist.
5
+ # See +FileUtils.mkpath+.
6
+ def mkpath
7
+ FileUtils.mkpath(@path)
8
+ self
9
+ end
10
+ alias :mkdir_p :mkpath
11
+
12
+ # Deletes a directory and all beneath it. See +FileUtils.rm_r+.
13
+ def rmtree
14
+ # The name "rmtree" is borrowed from File::Path of Perl.
15
+ # File::Path provides "mkpath" and "rmtree".
16
+ FileUtils.rm_r(@path)
17
+ self
18
+ end
19
+ alias :rm_r :rmtree
20
+
21
+ def rm
22
+ FileUtils.rm(@path)
23
+ self
24
+ end
25
+
26
+ def rm_f
27
+ FileUtils.rm_f(@path)
28
+ self
29
+ end
30
+
31
+ def rm_rf
32
+ FileUtils.rm_rf(@path)
33
+ self
34
+ end
35
+
36
+ def touch
37
+ FileUtils.touch(@path)
38
+ self
39
+ end
40
+
41
+ def touch!
42
+ dirname.mkpath
43
+ touch
44
+ end
45
+ end
@@ -0,0 +1,21 @@
1
+ class Path
2
+ # Path#find is an iterator to traverse a directory tree in a depth first
3
+ # manner. It yields a Path for each file under "this" directory.
4
+ #
5
+ # Returns an enumerator if no block is given.
6
+ #
7
+ # Since it is implemented by +find.rb+, +Find.prune+ can be used
8
+ # to control the traversal.
9
+ #
10
+ # If +self+ is +.+, yielded paths begin with a filename in the
11
+ # current directory, not +./+.
12
+ def find # :yield: path
13
+ return to_enum(__method__) unless block_given?
14
+ require 'find'
15
+ if @path == '.'
16
+ Find.find(@path) { |f| yield Path.new(f.sub(%r{\A\./}, '')) }
17
+ else
18
+ Find.find(@path) { |f| yield Path.new(f) }
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,58 @@
1
+ class Path
2
+ class << self
3
+ def new(*args)
4
+ if args.size == 1 and Path === args[0]
5
+ args[0]
6
+ else
7
+ super(*args)
8
+ end
9
+ end
10
+ alias :[] :new
11
+
12
+ def to_proc
13
+ lambda { |path| new(path) }
14
+ end
15
+ end
16
+
17
+ # Compare this path with +other+. The comparison is string-based.
18
+ # Be aware that two different paths (+foo.txt+ and +./foo.txt+)
19
+ # can refer to the same file.
20
+ def == other
21
+ Path === other and @path == other.to_path
22
+ end
23
+ alias :eql? :==
24
+
25
+ # Provides for comparing paths, case-sensitively.
26
+ def <=>(other)
27
+ return nil unless Path === other
28
+ @path.tr('/', "\0") <=> other.to_s.tr('/', "\0")
29
+ end
30
+
31
+ def hash
32
+ @path.hash
33
+ end
34
+
35
+ # Return the path as a String.
36
+ def to_s
37
+ @path
38
+ end
39
+
40
+ # to_path is implemented so Path objects are usable with File.open, etc.
41
+ alias :to_path :to_s
42
+
43
+ alias :to_str :to_s if RUBY_VERSION < '1.9'
44
+
45
+ def to_sym
46
+ @path.to_sym
47
+ end
48
+
49
+ def inspect
50
+ "#<Path #{@path}>"
51
+ end
52
+ end
53
+
54
+ unless defined? NO_EPATH_GLOBAL_FUNCTION
55
+ def Path(*args)
56
+ Path.new(*args)
57
+ end
58
+ end
@@ -1,82 +1,156 @@
1
1
  # Path's low-level implementation based on Pathname
2
2
 
3
- require 'fileutils'
4
-
5
3
  class Path
6
- # :stopdoc:
4
+ # @private
7
5
  SAME_PATHS = if File::FNM_SYSCASE.nonzero?
8
- proc {|a, b| a.casecmp(b).zero?}
6
+ lambda { |a,b| a.casecmp(b).zero? }
9
7
  else
10
- proc {|a, b| a == b}
8
+ lambda { |a,b| a == b }
11
9
  end
12
10
 
13
- # :startdoc:
11
+ def initialize(*parts)
12
+ path = parts.size > 1 ? File.join(parts) : parts.first
13
+ @path = case path
14
+ when Tempfile
15
+ @_tmpfile = path # We would not want it to be GC'd
16
+ path.path.dup
17
+ when String
18
+ path.dup
19
+ else
20
+ path.to_s.dup
21
+ end
22
+
23
+ validate(@path)
14
24
 
15
- def freeze() super; @path.freeze; self end
16
- def taint() super; @path.taint; self end
17
- def untaint() super; @path.untaint; self end
25
+ taint if @path.tainted?
26
+ @path.freeze
27
+ freeze
28
+ end
18
29
 
30
+ # Returns clean path of +self+ with consecutive slashes and useless dots removed.
31
+ # The filesystem is not accessed.
19
32
  #
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.
33
+ # If +consider_symlink+ is +true+, then a more conservative algorithm is used
34
+ # to avoid breaking symbolic linkages. This may retain more +..+
35
+ # entries than absolutely necessary, but without accessing the filesystem,
36
+ # this can't be avoided. See #realpath.
37
+ def cleanpath(consider_symlink=false)
38
+ if consider_symlink
39
+ cleanpath_conservative
40
+ else
41
+ cleanpath_aggressive
42
+ end
43
+ end
44
+
45
+ # #parent returns the parent directory.
23
46
  #
24
- def == other
25
- Path === other and @path == other.to_s
47
+ # This is same as <tt>self + '..'</tt>.
48
+ def parent
49
+ self + '..'
26
50
  end
27
- alias eql? ==
28
51
 
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")
52
+ # Path#+ appends a path fragment to this one to produce a new Path.
53
+ #
54
+ # p1 = Path.new("/usr") # => #<Path /usr>
55
+ # p2 = p1 + "bin/ruby" # => #<Path /usr/bin/ruby>
56
+ # p3 = p1 + "/etc/passwd" # => #<Path /etc/passwd>
57
+ #
58
+ # This method doesn't access the file system, it is pure string manipulation.
59
+ def +(other)
60
+ Path.new(plus(@path, other.to_s))
33
61
  end
34
62
 
35
- def hash # :nodoc:
36
- @path.hash
63
+ # Path#join joins paths.
64
+ #
65
+ # <tt>path0.join(path1, ..., pathN)</tt> is the same as
66
+ # <tt>path0 + path1 + ... + pathN</tt>.
67
+ def join(*args)
68
+ args.unshift self
69
+ result = Path.new(args.pop)
70
+ return result if result.absolute?
71
+ args.reverse_each { |arg|
72
+ arg = Path.new(arg)
73
+ result = arg + result
74
+ return result if result.absolute?
75
+ }
76
+ result
37
77
  end
38
78
 
39
- # Return the path as a String.
40
- def to_s
41
- @path.dup
79
+ # #relative_path_from returns a relative path from the argument to the
80
+ # receiver. If +self+ is absolute, the argument must be absolute too.
81
+ # If +self+ is relative, the argument must be relative too.
82
+ #
83
+ # #relative_path_from doesn't access the filesystem. It assumes no symlinks.
84
+ #
85
+ # ArgumentError is raised when it cannot find a relative path.
86
+ def relative_path_from(base_directory)
87
+ dest_directory = cleanpath.to_s
88
+ base_directory = Path.new(base_directory).cleanpath.to_s
89
+ dest_prefix = dest_directory
90
+ dest_names = []
91
+ while r = chop_basename(dest_prefix)
92
+ dest_prefix, basename = r
93
+ dest_names.unshift basename if basename != '.'
94
+ end
95
+ base_prefix = base_directory
96
+ base_names = []
97
+ while r = chop_basename(base_prefix)
98
+ base_prefix, basename = r
99
+ base_names.unshift basename if basename != '.'
100
+ end
101
+ unless SAME_PATHS[dest_prefix, base_prefix]
102
+ raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}"
103
+ end
104
+ until dest_names.empty? or base_names.empty? or !SAME_PATHS[dest_names.first, base_names.first]
105
+ dest_names.shift
106
+ base_names.shift
107
+ end
108
+ if base_names.include? '..'
109
+ raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
110
+ end
111
+ base_names.fill('..')
112
+ relpath_names = base_names + dest_names
113
+ if relpath_names.empty?
114
+ Path.new('.')
115
+ else
116
+ Path.new(*relpath_names)
117
+ end
42
118
  end
43
119
 
44
- # to_path is implemented so Path objects are usable with File.open, etc.
45
- alias to_path to_s
120
+ # @private
121
+ module Helpers
122
+ private
46
123
 
47
- alias to_str to_s if RUBY_VERSION < '1.9'
124
+ # remove the leading . of +ext+ if present.
125
+ def pure_ext(ext)
126
+ ext.start_with?('.') ? ext[1..-1] : ext
127
+ end
48
128
 
49
- def inspect
50
- "#<Path #{@path}>"
129
+ # add a leading . to +ext+ if missing. Returns '' if +ext+ is empty.
130
+ def dotted_ext(ext)
131
+ (ext.empty? or ext.start_with?('.')) ? ext : ".#{ext}"
132
+ end
51
133
  end
52
134
 
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
135
+ include Helpers
136
+ extend Helpers
60
137
 
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)
138
+ private
139
+
140
+ def validate(path)
141
+ raise ArgumentError, "path contains a null byte: #{path.inspect}" if path.include? "\0"
142
+ path.gsub!(File::ALT_SEPARATOR, '/') if File::ALT_SEPARATOR
68
143
  end
69
144
 
70
145
  # chop_basename(path) -> [pre-basename, basename] or nil
71
146
  def chop_basename(path)
72
147
  base = File.basename(path)
73
- if /\A#{SEPARATOR_PAT}?\z/o =~ base
148
+ if base.empty? or base == '/'
74
149
  return nil
75
150
  else
76
151
  return path[0, path.rindex(base)], base
77
152
  end
78
153
  end
79
- private :chop_basename
80
154
 
81
155
  # split_names(path) -> prefix, [name, ...]
82
156
  def split_names(path)
@@ -87,41 +161,47 @@ class Path
87
161
  end
88
162
  return path, names
89
163
  end
90
- private :split_names
91
164
 
92
165
  def prepend_prefix(prefix, relpath)
93
166
  if relpath.empty?
94
167
  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
168
+ elsif prefix.include? '/'
169
+ add_trailing_separator(File.dirname(prefix)) + relpath
99
170
  else
100
171
  prefix + relpath
101
172
  end
102
173
  end
103
- private :prepend_prefix
104
174
 
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
175
+ def has_trailing_separator?(path)
176
+ if r = chop_basename(path)
177
+ pre, basename = r
178
+ pre.length + basename.length < path.length
116
179
  else
117
- cleanpath_aggressive
180
+ false
181
+ end
182
+ end
183
+
184
+ def add_trailing_separator(path)
185
+ if File.basename(path + 'a') == 'a'
186
+ path
187
+ else
188
+ path + '/'
189
+ end
190
+ end
191
+
192
+ def del_trailing_separator(path)
193
+ if r = chop_basename(path)
194
+ pre, basename = r
195
+ pre + basename
196
+ elsif /\/+\z/o =~ path
197
+ $` + File.dirname(path)[/\/*\z/o]
198
+ else
199
+ path
118
200
  end
119
201
  end
120
202
 
121
- #
122
203
  # Clean the path simply by resolving and removing excess "." and ".." entries.
123
204
  # Nothing more, nothing less.
124
- #
125
205
  def cleanpath_aggressive
126
206
  path = @path
127
207
  names = []
@@ -140,45 +220,11 @@ class Path
140
220
  end
141
221
  end
142
222
  end
143
- if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
223
+ if File.basename(pre).include? '/'
144
224
  names.shift while names[0] == '..'
145
225
  end
146
226
  Path.new(prepend_prefix(pre, File.join(*names)))
147
227
  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
228
 
183
229
  def cleanpath_conservative
184
230
  path = @path
@@ -188,7 +234,7 @@ class Path
188
234
  pre, base = r
189
235
  names.unshift base if base != '.'
190
236
  end
191
- if /#{SEPARATOR_PAT}/o =~ File.basename(pre)
237
+ if File.basename(pre).include? '/'
192
238
  names.shift while names[0] == '..'
193
239
  end
194
240
  if names.empty?
@@ -205,13 +251,11 @@ class Path
205
251
  end
206
252
  end
207
253
  end
208
- private :cleanpath_conservative
209
254
 
210
255
  if File.respond_to?(:realpath) and File.respond_to?(:realdirpath)
211
256
  def real_path_internal(strict = false, basedir = nil)
212
257
  strict ? File.realpath(@path, basedir) : File.realdirpath(@path, basedir)
213
258
  end
214
- private :real_path_internal
215
259
  else
216
260
  def realpath_rec(prefix, unresolved, h, strict, last = true)
217
261
  resolved = []
@@ -254,7 +298,6 @@ class Path
254
298
  end
255
299
  return prefix, *resolved
256
300
  end
257
- private :realpath_rec
258
301
 
259
302
  def real_path_internal(strict = false, basedir = nil)
260
303
  path = @path
@@ -267,151 +310,6 @@ class Path
267
310
  prefix, *names = realpath_rec(prefix, names, {}, strict)
268
311
  prepend_prefix(prefix, File.join(*names))
269
312
  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
313
  end
416
314
 
417
315
  def plus(path1, path2) # -> path
@@ -441,7 +339,7 @@ class Path
441
339
  basename_list2.shift
442
340
  end
443
341
  r1 = chop_basename(prefix1)
444
- if !r1 && /#{SEPARATOR_PAT}/o =~ File.basename(prefix1)
342
+ if !r1 && File.basename(prefix1).include?('/')
445
343
  while !basename_list2.empty? && basename_list2.first == '..'
446
344
  index_list2.shift
447
345
  basename_list2.shift
@@ -454,420 +352,4 @@ class Path
454
352
  r1 ? prefix1 : File.dirname(prefix1)
455
353
  end
456
354
  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
355
  end