rubypath 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|