epath 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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