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 +19 -0
- data/README.md +106 -0
- data/epath.gemspec +5 -3
- data/lib/epath.rb +196 -0
- data/lib/epath/implementation.rb +873 -0
- metadata +10 -6
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 = '
|
3
|
+
s.summary = 'a Path manipulation library'
|
4
4
|
s.author = 'eregon'
|
5
|
-
s.
|
6
|
-
s.
|
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.
|
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-
|
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.
|
45
|
+
rubygems_version: 1.8.11
|
42
46
|
signing_key:
|
43
47
|
specification_version: 3
|
44
|
-
summary:
|
48
|
+
summary: a Path manipulation library
|
45
49
|
test_files: []
|