rubypath 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +165 -0
- data/README.md +29 -0
- data/doc/file.README.html +111 -0
- data/lib/rubypath/backend/mock.rb +315 -0
- data/lib/rubypath/backend/sys.rb +139 -0
- data/lib/rubypath/backend.rb +87 -0
- data/lib/rubypath/comparison.rb +22 -0
- data/lib/rubypath/construction.rb +109 -0
- data/lib/rubypath/dir_operations.rb +76 -0
- data/lib/rubypath/extensions.rb +157 -0
- data/lib/rubypath/file_operations.rb +164 -0
- data/lib/rubypath/file_predicates.rb +32 -0
- data/lib/rubypath/identity.rb +59 -0
- data/lib/rubypath/io_operations.rb +82 -0
- data/lib/rubypath/mock.rb +42 -0
- data/lib/rubypath/path_operations.rb +253 -0
- data/lib/rubypath/path_predicates.rb +61 -0
- data/lib/rubypath/version.rb +11 -0
- data/lib/rubypath.rb +28 -0
- data/rubypath.gemspec +22 -0
- data/spec/rubypath/comparison_spec.rb +46 -0
- data/spec/rubypath/construction_spec.rb +101 -0
- data/spec/rubypath/dir_operations_spec.rb +146 -0
- data/spec/rubypath/extensions_spec.rb +270 -0
- data/spec/rubypath/file_operations_spec.rb +385 -0
- data/spec/rubypath/file_predicates_spec.rb +66 -0
- data/spec/rubypath/identity_spec.rb +21 -0
- data/spec/rubypath/io_operations_spec.rb +126 -0
- data/spec/rubypath/path_operations_spec.rb +383 -0
- data/spec/rubypath/path_predicates_spec.rb +75 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/support/describe_method.rb +18 -0
- data/spec/support/with_backend.rb +31 -0
- metadata +107 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
class Path
|
2
|
+
# @!group File Operations
|
3
|
+
|
4
|
+
# Return base name without path.
|
5
|
+
#
|
6
|
+
# @return [String] Base name.
|
7
|
+
#
|
8
|
+
def name
|
9
|
+
::File.basename internal_path
|
10
|
+
end
|
11
|
+
alias_method :basename, :name
|
12
|
+
|
13
|
+
# Create new file at pointed location or update modification time if file
|
14
|
+
# exists.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# Path('/path/to/file.txt').touch
|
18
|
+
# #=> <Path:"/path/to/file.txt">
|
19
|
+
#
|
20
|
+
# @return [Path] Path to touched file.
|
21
|
+
#
|
22
|
+
def touch(*args)
|
23
|
+
with_path(*args) do |path|
|
24
|
+
invoke_backend :touch, path
|
25
|
+
Path path
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create a file at pointed location and all missing parent directories.
|
30
|
+
#
|
31
|
+
# Given arguments will be joined with current path before directories and
|
32
|
+
# file is created.
|
33
|
+
#
|
34
|
+
# If file already exists nothing will be done.
|
35
|
+
#
|
36
|
+
# @example
|
37
|
+
# Path('/path/to/file.txt').mkfile
|
38
|
+
# #=> <Path:"/path/to/file.txt">
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# Path('/').mkfile('path', 'to', 'file.txt')
|
42
|
+
# #=> <Path:"/path/to/file.txt">
|
43
|
+
#
|
44
|
+
# @return [Path] Path to created or existent file.
|
45
|
+
#
|
46
|
+
def mkfile(*args)
|
47
|
+
with_path(*args) do |path|
|
48
|
+
if !path.exists? && path.parent && !path.parent.exists?
|
49
|
+
path.parent.mkpath
|
50
|
+
end
|
51
|
+
|
52
|
+
if path.exists?
|
53
|
+
raise Errno::ENOENT.new path.to_s unless path.file?
|
54
|
+
else
|
55
|
+
path.touch
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Search for a file in current directory or parent directories.
|
61
|
+
#
|
62
|
+
# Given search pattern can either be a regular expression or a shell glob
|
63
|
+
# expression.
|
64
|
+
#
|
65
|
+
# @example
|
66
|
+
# Path.cwd.lookup('project.{yml,yaml}')
|
67
|
+
# #=> <Path:"/path/config.yml">
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# Path.cwd.lookup(/config(_\d+).ya?ml/)
|
71
|
+
# #=> <Path:"/path/config_354.yaml">
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# Path('~').lookup('*config', ::File::FNM_DOTMATCH)
|
75
|
+
# #=> <Path:"/gome/user/.gitconfig">
|
76
|
+
#
|
77
|
+
# @param pattern [String|RegExp] Expression file name must match.
|
78
|
+
# @param flags [Integer] Additional flags. See {::File.fnmatch}.
|
79
|
+
# Defaults to `File::FNM_EXTGLOB`.
|
80
|
+
# @return [Path] Path to found file or nil.
|
81
|
+
#
|
82
|
+
def lookup(pattern, flags = ::File::FNM_EXTGLOB)
|
83
|
+
expand.ascend do |path|
|
84
|
+
case pattern
|
85
|
+
when String
|
86
|
+
path.entries.each do |c|
|
87
|
+
return path.join(c) if ::File.fnmatch?(pattern, c.name, flags)
|
88
|
+
end
|
89
|
+
when Regexp
|
90
|
+
path.entries.each do |c|
|
91
|
+
return path.join(c) if pattern =~ c.name
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return file modification time.
|
100
|
+
#
|
101
|
+
# @return [Time] Time of last modification.
|
102
|
+
#
|
103
|
+
def mtime
|
104
|
+
invoke_backend :mtime
|
105
|
+
end
|
106
|
+
|
107
|
+
# Set last modification time.
|
108
|
+
#
|
109
|
+
# @param [Time] Time of last modification.
|
110
|
+
#
|
111
|
+
def mtime=(time)
|
112
|
+
invoke_backend :mtime=, internal_path, time
|
113
|
+
end
|
114
|
+
|
115
|
+
# Return file access time.
|
116
|
+
#
|
117
|
+
# @return [Time] Time of last access.
|
118
|
+
#
|
119
|
+
def atime
|
120
|
+
invoke_backend :atime
|
121
|
+
end
|
122
|
+
|
123
|
+
# Set last access time.
|
124
|
+
#
|
125
|
+
# @param [Time] Time of last access.
|
126
|
+
#
|
127
|
+
def atime=(time)
|
128
|
+
invoke_backend :atime=, internal_path, time
|
129
|
+
end
|
130
|
+
|
131
|
+
def mode
|
132
|
+
invoke_backend :mode
|
133
|
+
end
|
134
|
+
|
135
|
+
def chmod(mode)
|
136
|
+
invoke_backend :chmod, internal_path, mode
|
137
|
+
end
|
138
|
+
|
139
|
+
class << self
|
140
|
+
|
141
|
+
# Read or set process umask.
|
142
|
+
#
|
143
|
+
# @overload umask
|
144
|
+
# Read process umask.
|
145
|
+
#
|
146
|
+
# @return [Integer] Process umask.
|
147
|
+
#
|
148
|
+
# @overload umask(mask)
|
149
|
+
# Set process umask.
|
150
|
+
#
|
151
|
+
# @param mask [Integer] New process umask.
|
152
|
+
#
|
153
|
+
# @see File.umask
|
154
|
+
#
|
155
|
+
def umask(mask = nil)
|
156
|
+
if mask
|
157
|
+
invoke_backend :set_umask, mask
|
158
|
+
else
|
159
|
+
invoke_backend :get_umask
|
160
|
+
end
|
161
|
+
end
|
162
|
+
alias_method :umask=, :umask
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Path
|
2
|
+
# @!group File Predicates
|
3
|
+
|
4
|
+
# Check if path points to file.
|
5
|
+
#
|
6
|
+
# @return [Boolean] True if path is a file.
|
7
|
+
# @see ::File.file?
|
8
|
+
#
|
9
|
+
def file?
|
10
|
+
invoke_backend :file?
|
11
|
+
end
|
12
|
+
|
13
|
+
# Check if path points to an existing location.
|
14
|
+
#
|
15
|
+
# @return [Boolean] True if path exists.
|
16
|
+
# @see ::File.exists?
|
17
|
+
#
|
18
|
+
def exists?
|
19
|
+
invoke_backend :exists?
|
20
|
+
end
|
21
|
+
alias_method :exist?, :exists?
|
22
|
+
alias_method :existent?, :exists?
|
23
|
+
|
24
|
+
# Check if path points to a directory.
|
25
|
+
#
|
26
|
+
# @return [Boolean] True if path is a directory.
|
27
|
+
# @see ::File.directory?
|
28
|
+
#
|
29
|
+
def directory?
|
30
|
+
invoke_backend :directory?
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Path
|
2
|
+
# @!group Identity
|
3
|
+
|
4
|
+
# Return path as string. String will be duped before it gets returned and
|
5
|
+
# cannot be used to modify the path object.
|
6
|
+
#
|
7
|
+
# @return [String] Path as string.
|
8
|
+
def path
|
9
|
+
internal_path.dup
|
10
|
+
end
|
11
|
+
alias_method :to_path, :path
|
12
|
+
alias_method :to_str, :path
|
13
|
+
alias_method :to_s, :path
|
14
|
+
|
15
|
+
# Return a useful object string representation.
|
16
|
+
#
|
17
|
+
# @return [String] Useful object representation
|
18
|
+
def inspect
|
19
|
+
"<#{self.class.name}:#{internal_path}>"
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
# Return internal path object without duping.
|
27
|
+
#
|
28
|
+
# Must not be modified to not change internal state.
|
29
|
+
#
|
30
|
+
# @return [String] Internal path.
|
31
|
+
# @see #path
|
32
|
+
#
|
33
|
+
def internal_path
|
34
|
+
@path
|
35
|
+
end
|
36
|
+
|
37
|
+
# If arguments are provided the current path will be joined with given
|
38
|
+
# arguments to the result will be yielded. If no arguments are given the
|
39
|
+
# current path will be yielded.
|
40
|
+
#
|
41
|
+
# Internal helper method.
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# def handle_both(*args)
|
45
|
+
# with_path(*args) do |path|
|
46
|
+
# # do something
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# Returns whatever the block returns.
|
51
|
+
#
|
52
|
+
def with_path(*args)
|
53
|
+
if args.any?
|
54
|
+
yield join(*args)
|
55
|
+
else
|
56
|
+
yield self
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
class Path
|
2
|
+
# @!group IO Operations
|
3
|
+
|
4
|
+
# Write given content to file.
|
5
|
+
#
|
6
|
+
# @overload write(content, [..])
|
7
|
+
# Write given content to file. An existing file will be truncated otherwise
|
8
|
+
# a file will be created.
|
9
|
+
#
|
10
|
+
# Additional arguments will be passed to {::IO.write}.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# Path('/path/to/file.txt').write('CONTENT')
|
14
|
+
# #=> 7
|
15
|
+
#
|
16
|
+
# @param content [String] Content to write to file.
|
17
|
+
#
|
18
|
+
# @overload write(content, offset, [..])
|
19
|
+
# Write content at specific position in file. Content will be replaced
|
20
|
+
# starting at given offset.
|
21
|
+
#
|
22
|
+
# Additional arguments will be passed to {::IO.write}.
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# path.write('CONTENT', 4)
|
26
|
+
# #=> 7
|
27
|
+
# path.read
|
28
|
+
# #=> "1234CONTENT2345678"
|
29
|
+
#
|
30
|
+
# @param content [String] Content to write to file.
|
31
|
+
# @param offset [Integer] Offset where to start writing. If nil file will
|
32
|
+
# be truncated.
|
33
|
+
#
|
34
|
+
# @see IO.write
|
35
|
+
# @return [Path] Self.
|
36
|
+
#
|
37
|
+
def write(content, *args)
|
38
|
+
invoke_backend :write, self, content, *args
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Read file content from disk.
|
43
|
+
#
|
44
|
+
# @overload read([..])
|
45
|
+
# Read all content from file.
|
46
|
+
#
|
47
|
+
# Additional arguments will be passed to {::IO.read}.
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# Path('file.txt').read
|
51
|
+
# #=> "CONTENT"
|
52
|
+
#
|
53
|
+
# @overload read(length, [..])
|
54
|
+
# Read given amount of bytes from file.
|
55
|
+
#
|
56
|
+
# Additional arguments will be passed to {::IO.read}.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# Path('file.txt').read(4)
|
60
|
+
# #=> "CONT"
|
61
|
+
#
|
62
|
+
# @param length [Integer] Number of bytes to read.
|
63
|
+
#
|
64
|
+
# @overload read(length, offset, [..])
|
65
|
+
# Read given amount of bytes from file starting at given offset.
|
66
|
+
#
|
67
|
+
# Additional arguments will be passed to {::IO.read}.
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# Path('file.txt').read(4, 2)
|
71
|
+
# #=> "NTEN"
|
72
|
+
#
|
73
|
+
# @param length [Integer] Number of bytes to read.
|
74
|
+
# @param offset [Integer] Where to start reading.
|
75
|
+
#
|
76
|
+
# @see IO.read
|
77
|
+
# @return [String] Read content.
|
78
|
+
#
|
79
|
+
def read(*args)
|
80
|
+
invoke_backend :read, self, *args
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Path
|
2
|
+
class << self
|
3
|
+
# @!group Mocking / Virtual File System
|
4
|
+
|
5
|
+
# Configure current path backend. Can be used to configure specified
|
6
|
+
# test scenario. If no virtual or scoped path backend is set the default
|
7
|
+
# one will be used.
|
8
|
+
#
|
9
|
+
# Do not forget to use mock file system in your specs:
|
10
|
+
# See more {Backend.mock}.
|
11
|
+
#
|
12
|
+
# around do |example|
|
13
|
+
# Path::Backend.mock &example
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# *Note*: Not all operations are supported.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# Path.mock do |root|
|
20
|
+
# root.mkpath '/a/b/c/d/e'
|
21
|
+
# root.touch '/a/b/test.txt'
|
22
|
+
# root.join('/a/c/lorem.yaml').write YAML.dump({'lorem' => 'ipsum'})
|
23
|
+
# #...
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# @example Configure backend (only with virtual file system)
|
27
|
+
# Path.mock do |root, backend|
|
28
|
+
# backend.current_user = 'test'
|
29
|
+
# backend.homes = {'test' => '/path/to/test/home'}
|
30
|
+
# #...
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @yield |root, backend| Yield file system root path and current backend.
|
34
|
+
# @yieldparam root [Path] Root path of current packend.
|
35
|
+
# @yieldparam backend [Backend] Current backend.
|
36
|
+
#
|
37
|
+
def mock(opts = {})
|
38
|
+
yield Path('/'), Backend.instance.backend if block_given?
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
class Path
|
2
|
+
# @!group Path Operations
|
3
|
+
|
4
|
+
# Join path with given arguments.
|
5
|
+
#
|
6
|
+
# @overload initialize([[Path, String, #to_path, #path, #to_s], ...]
|
7
|
+
# Join all given arguments to build a new path.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# Path('/').join('test', %w(a b), 5, Pathname.new('file'))
|
11
|
+
# # => <Path:"/test/a/b/5/file">
|
12
|
+
#
|
13
|
+
# @return [Path]
|
14
|
+
#
|
15
|
+
def join(*args)
|
16
|
+
parts = args.flatten
|
17
|
+
case parts.size
|
18
|
+
when 0
|
19
|
+
self
|
20
|
+
when 1
|
21
|
+
join = Path parts.shift
|
22
|
+
join.absolute? ? join : Path(::File.join(path, join.path))
|
23
|
+
else
|
24
|
+
join(parts.shift).join(*parts)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Iterate over all path components.
|
29
|
+
#
|
30
|
+
# @overload each_component
|
31
|
+
# Return a enumerator to iterate over all path components.
|
32
|
+
#
|
33
|
+
# @example Iterate over path components using a enumerator
|
34
|
+
# enum = Path('/path/to/file.txt').each_component
|
35
|
+
# enum.each{|fn| puts fn}
|
36
|
+
# # => "path"
|
37
|
+
# # => "to"
|
38
|
+
# # => "file.txt"
|
39
|
+
#
|
40
|
+
# @example Map each path component and create a new path
|
41
|
+
# path = Path('/path/to/file.txt')
|
42
|
+
# Path path.each_component.map{|fn| fn.length}
|
43
|
+
# # => <Path:"/4/2/8">
|
44
|
+
#
|
45
|
+
# @return [Enumerator] Return a enumerator for all path components.
|
46
|
+
#
|
47
|
+
# @overload each_component(&block)
|
48
|
+
# Yield given block for each path components.
|
49
|
+
#
|
50
|
+
# @example Print each file name
|
51
|
+
# Path('/path/to/file.txt').each_component{|fn| puts fn}
|
52
|
+
# # => "path"
|
53
|
+
# # => "to"
|
54
|
+
# # => "file.txt"
|
55
|
+
#
|
56
|
+
# @param block [Proc] Block to invoke with each path component.
|
57
|
+
# If no block is given an enumerator will returned.
|
58
|
+
# @return [self] Self.
|
59
|
+
#
|
60
|
+
def each_component(opts = {}, &block)
|
61
|
+
rv = if opts[:empty]
|
62
|
+
# split eats leading slashes
|
63
|
+
ary = path.split(Path.separator)
|
64
|
+
# so add an empty string if path ends with slash
|
65
|
+
ary << '' if path[-1] == Path.separator
|
66
|
+
ary.each(&block)
|
67
|
+
else
|
68
|
+
Pathname(path).each_filename(&block)
|
69
|
+
end
|
70
|
+
block ? self : rv
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return an array with all path components.
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
# Path('path/to/file').components
|
77
|
+
# # => ["path", "to", "file"]
|
78
|
+
#
|
79
|
+
# @example
|
80
|
+
# Path('/path/to/file').components
|
81
|
+
# # => ["path", "to", "file"]
|
82
|
+
#
|
83
|
+
# @return [Array<String>] File names.
|
84
|
+
#
|
85
|
+
def components
|
86
|
+
each_component.to_a
|
87
|
+
end
|
88
|
+
|
89
|
+
# Converts a pathname to an absolute pathname. Given arguments will be
|
90
|
+
# joined to current path before expanding path. Relative paths are referenced
|
91
|
+
# from the current working directory of the process unless the `:base` option
|
92
|
+
# is set, which will be used as the starting point.
|
93
|
+
#
|
94
|
+
# The given pathname may start with a "~", which expands to the process
|
95
|
+
# owner's home directory (the environment variable HOME must be set
|
96
|
+
# correctly). "~user" expands to the named user's home directory.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# Path('path/to/../tmp').expand
|
100
|
+
# #=> <Path:"path/tmp">
|
101
|
+
#
|
102
|
+
# @example
|
103
|
+
# Path('~/tmp').expand
|
104
|
+
# #=> <Path:"/home/user/tmp">
|
105
|
+
#
|
106
|
+
# @example
|
107
|
+
# Path('~oma/tmp').expand
|
108
|
+
# #=> <Path:"/home/oma/tmp">
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# Path('~/tmp').expand('../file.txt')
|
112
|
+
# #=> <Path:"/home/user/file.txt">
|
113
|
+
#
|
114
|
+
# @return [Path] Expanded path.
|
115
|
+
# @see ::File#expand_path
|
116
|
+
#
|
117
|
+
def expand(*args)
|
118
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
119
|
+
|
120
|
+
with_path(*args) do |path|
|
121
|
+
base = Path.like_path(opts[:base] || Backend.instance.getwd)
|
122
|
+
expanded_path = Backend.instance.expand_path(path, base)
|
123
|
+
if expanded_path != internal_path
|
124
|
+
Path expanded_path
|
125
|
+
else
|
126
|
+
self
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
alias_method :expand_path, :expand
|
132
|
+
alias_method :absolute, :expand
|
133
|
+
alias_method :absolute_path, :expand
|
134
|
+
|
135
|
+
# Check if path consists of only a filename.
|
136
|
+
#
|
137
|
+
# @example
|
138
|
+
# Path('file.txt').only_filename?
|
139
|
+
# #=> true
|
140
|
+
#
|
141
|
+
# @return [Boolean] True if path consists of only a filename.
|
142
|
+
#
|
143
|
+
def only_filename?
|
144
|
+
internal_path.index(Path.separator).nil?
|
145
|
+
end
|
146
|
+
|
147
|
+
# Return path to parent directory. If path is already an absolute or relative
|
148
|
+
# root nil will be returned.
|
149
|
+
#
|
150
|
+
# @example Get parent directory:
|
151
|
+
# Path.new('/path/to/file').dir.path
|
152
|
+
# #=> '/path/to'
|
153
|
+
#
|
154
|
+
# @example Try to get parent of absolute root:
|
155
|
+
# Path.new('/').dir
|
156
|
+
# #=> nil
|
157
|
+
#
|
158
|
+
# @example Try to get parent of relative root:
|
159
|
+
# Path.new('.').dir
|
160
|
+
# #=> nil
|
161
|
+
#
|
162
|
+
# @return [Path] Parent path or nil if path already points to an absolute
|
163
|
+
# or relative root.
|
164
|
+
#
|
165
|
+
def dirname
|
166
|
+
return nil if %w(. /).include? internal_path
|
167
|
+
|
168
|
+
dir = ::File.dirname internal_path
|
169
|
+
dir.empty? ? nil : self.class.new(dir)
|
170
|
+
end
|
171
|
+
|
172
|
+
alias_method :parent, :dirname
|
173
|
+
|
174
|
+
# Yield given block for path and each ancestor.
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# Path('/path/to/file.txt').ascend{|path| p path}
|
178
|
+
# #<Path:/path/to/file.txt>
|
179
|
+
# #<Path:/path/to>
|
180
|
+
# #<Path:/path>
|
181
|
+
# #<Path:/>
|
182
|
+
# #=> <Path:/path/to/file.txt>
|
183
|
+
#
|
184
|
+
# @example
|
185
|
+
# Path('path/to/file.txt').ascend{|path| p path}
|
186
|
+
# #<Path:path/to/file.txt>
|
187
|
+
# #<Path:path/to>
|
188
|
+
# #<Path:path>
|
189
|
+
# #<Path:.>
|
190
|
+
# #=> <Path:path/to/file.txt>
|
191
|
+
#
|
192
|
+
# @yield |path| Yield path and ancestors.
|
193
|
+
# @yieldparam path [Path] Path or ancestor.
|
194
|
+
# @return [Path] Self.
|
195
|
+
#
|
196
|
+
def ascend
|
197
|
+
return to_enum(:ascend) unless block_given?
|
198
|
+
|
199
|
+
path = self
|
200
|
+
loop do
|
201
|
+
yield path
|
202
|
+
break unless (path = path.parent)
|
203
|
+
end
|
204
|
+
|
205
|
+
self
|
206
|
+
end
|
207
|
+
|
208
|
+
alias_method :each_ancestors, :ascend
|
209
|
+
|
210
|
+
# Return an array of all ancestors.
|
211
|
+
#
|
212
|
+
# @example
|
213
|
+
# Path('/path/to/file').ancestors
|
214
|
+
# # => [<Path:/path/to/file.txt>, <Path:/path/to>, <Path:/path>, <Path:/>]
|
215
|
+
#
|
216
|
+
# @return [Array<Path>] All ancestors.
|
217
|
+
#
|
218
|
+
def ancestors
|
219
|
+
each_ancestors.to_a
|
220
|
+
end
|
221
|
+
|
222
|
+
# Return given path as a relative path by just striping leading slashes.
|
223
|
+
#
|
224
|
+
# @example
|
225
|
+
# Path.new('/path/to/file').as_relative
|
226
|
+
# #=> <Path 'path/to/file'>
|
227
|
+
#
|
228
|
+
# @return [Path] Path transformed to relative path.
|
229
|
+
#
|
230
|
+
def as_relative
|
231
|
+
if (rel_path = internal_path.gsub(/^\/+/, '')) != internal_path
|
232
|
+
Path rel_path
|
233
|
+
else
|
234
|
+
self
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Return given path as a absolute path by just prepending a leading slash.
|
239
|
+
#
|
240
|
+
# @example
|
241
|
+
# Path.new('path/to/file').as_absolute
|
242
|
+
# #=> <Path '/path/to/file'>
|
243
|
+
#
|
244
|
+
# @return [Path] Path transformed to absolute path.
|
245
|
+
#
|
246
|
+
def as_absolute
|
247
|
+
if internal_path[0] != '/'
|
248
|
+
Path "/#{internal_path}"
|
249
|
+
else
|
250
|
+
self
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Path
|
2
|
+
# @!group Path Predicates
|
3
|
+
|
4
|
+
# Check if path is an absolute path.
|
5
|
+
#
|
6
|
+
# An absolute path is a path with a leading slash.
|
7
|
+
#
|
8
|
+
# @return [Boolean] True if path is absolute.
|
9
|
+
# @see #relative?
|
10
|
+
#
|
11
|
+
def absolute?
|
12
|
+
internal_path[0] == '/'
|
13
|
+
end
|
14
|
+
|
15
|
+
# Check if path is a relative path.
|
16
|
+
#
|
17
|
+
# A relative path does not start with a slash.
|
18
|
+
#
|
19
|
+
# @return [Boolean] True if path is relative.
|
20
|
+
# @see #absolute?
|
21
|
+
#
|
22
|
+
def relative?
|
23
|
+
!absolute?
|
24
|
+
end
|
25
|
+
|
26
|
+
# @overload mountpoint?([Path, String], ...)
|
27
|
+
# Join current and given paths and check if resulting
|
28
|
+
# path points to a mountpoint.
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# Path('/').mountpoint?('tmp')
|
32
|
+
# #=> true
|
33
|
+
#
|
34
|
+
# @overload mountpoint?
|
35
|
+
# Check if current path is a mountpoint.
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# Path('/tmp').mountpoint?
|
39
|
+
# #=> true
|
40
|
+
#
|
41
|
+
# @return [Boolean] True if path is a mountpoint, false otherwise.
|
42
|
+
# @see Pathname#mountpoint?
|
43
|
+
#
|
44
|
+
def mountpoint?(*args)
|
45
|
+
with_path(*args) do |path|
|
46
|
+
Backend.instance.mountpoint? path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Check if file or directory is a dot file.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# Path("~/.gitconfig").dotfile?
|
54
|
+
# #=> true
|
55
|
+
#
|
56
|
+
# @return [Boolean] True if file is a dot file otherwise false.
|
57
|
+
#
|
58
|
+
def dotfile?
|
59
|
+
name[0] == '.'
|
60
|
+
end
|
61
|
+
end
|