rubypath 1.0.0 → 1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/LICENSE.txt +165 -0
- data/lib/rubypath.rb +29 -0
- data/lib/rubypath/backend.rb +96 -0
- data/lib/rubypath/backend/mock.rb +362 -0
- data/lib/rubypath/backend/sys.rb +163 -0
- data/lib/rubypath/comparison.rb +21 -0
- data/lib/rubypath/construction.rb +115 -0
- data/lib/rubypath/dir_operations.rb +161 -0
- data/lib/rubypath/extensions.rb +162 -0
- data/lib/rubypath/file_operations.rb +193 -0
- data/lib/rubypath/file_predicates.rb +34 -0
- data/lib/rubypath/identity.rb +59 -0
- data/lib/rubypath/io_operations.rb +84 -0
- data/lib/rubypath/mock.rb +44 -0
- data/lib/rubypath/path_operations.rb +320 -0
- data/lib/rubypath/path_predicates.rb +63 -0
- data/lib/rubypath/version.rb +15 -0
- data/rubypath.gemspec +33 -0
- metadata +22 -3
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Path
|
4
|
+
# @!group File Extensions
|
5
|
+
|
6
|
+
# Return list of all file extensions.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# Path.new('/path/to/template.de.html.erb').extensions
|
10
|
+
# #=> ['de', 'html', 'erb']
|
11
|
+
#
|
12
|
+
# @return [Array<String>] List of file extensions.
|
13
|
+
#
|
14
|
+
def extensions
|
15
|
+
if dotfile?
|
16
|
+
name.split('.')[2..-1]
|
17
|
+
else
|
18
|
+
name.split('.')[1..-1]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
alias exts extensions
|
22
|
+
|
23
|
+
# Return last file extension.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# Path.new('/path/to/template.de.html.erb').extension
|
27
|
+
# #=> 'erb'
|
28
|
+
#
|
29
|
+
# @return [String] Last file extensions.
|
30
|
+
#
|
31
|
+
def extension
|
32
|
+
extensions.last
|
33
|
+
end
|
34
|
+
alias ext extension
|
35
|
+
|
36
|
+
# Return last file extension include dot character.
|
37
|
+
#
|
38
|
+
# @return [String] Ext name.
|
39
|
+
# @see ::File.extname
|
40
|
+
#
|
41
|
+
def extname
|
42
|
+
::File.extname name
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return the file name without any extensions.
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# Path("template.de.html.slim").pure_name
|
49
|
+
# #=> "template"
|
50
|
+
#
|
51
|
+
# @example
|
52
|
+
# Path("~/.gitconfig").pure_name
|
53
|
+
# #=> ".gitconfig"
|
54
|
+
#
|
55
|
+
# @return [String] File name without extensions.
|
56
|
+
#
|
57
|
+
def pure_name
|
58
|
+
if dotfile?
|
59
|
+
name.split('.', 3)[0..1].join('.')
|
60
|
+
else
|
61
|
+
name.split('.', 2)[0]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Replace file extensions with given new ones or by a given
|
66
|
+
# translation map.
|
67
|
+
#
|
68
|
+
# @overload replace_extensions(exts)
|
69
|
+
# Replace all extensions with given new ones. Number of given extensions
|
70
|
+
# does not need to match number of existing extensions.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# Path('file.de.txt').replace_extensions(%w(en html))
|
74
|
+
# #=> <Path "file.en.html">
|
75
|
+
#
|
76
|
+
# @example
|
77
|
+
# Path('file.de.mobile.html.haml').replace_extensions(%w(int txt))
|
78
|
+
# #=> <Path "file.int.txt">
|
79
|
+
#
|
80
|
+
# @param exts [Array<String>] New extensions.
|
81
|
+
#
|
82
|
+
# @overload replace_extensions(ext, [ext, [..]])
|
83
|
+
# Replace all extensions with given new ones. Number of given extensions
|
84
|
+
# does not need to match number of existing extensions.
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
# Path('file.de.txt').replace_extensions('en', 'html')
|
88
|
+
# #=> <Path "file.en.html">
|
89
|
+
#
|
90
|
+
# @example
|
91
|
+
# Path('file.de.mobile.html.haml').replace_extensions('en', 'html')
|
92
|
+
# #=> <Path "file.en.html">
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# Path('file.de.txt').replace_extensions('html')
|
96
|
+
# #=> <Path "file.html">
|
97
|
+
#
|
98
|
+
# @param ext [String] New extensions.
|
99
|
+
#
|
100
|
+
# @overload replace_extensions(map)
|
101
|
+
# Replace all matching extensions.
|
102
|
+
#
|
103
|
+
# @example
|
104
|
+
# Path('file.de.html.haml').replace_extensions('de'=>'en', 'haml'=>'slim')
|
105
|
+
# #=> <Path "file.en.html.slim">
|
106
|
+
#
|
107
|
+
# @param map [Hash<String, String>] Translation map as hash.
|
108
|
+
#
|
109
|
+
# @return [Path] Path to new filename.
|
110
|
+
#
|
111
|
+
# rubocop:disable AbcSize
|
112
|
+
# rubocop:disable CyclomaticComplexity
|
113
|
+
# rubocop:disable MethodLength
|
114
|
+
# rubocop:disable PerceivedComplexity
|
115
|
+
#
|
116
|
+
def replace_extensions(*args)
|
117
|
+
args.flatten!
|
118
|
+
extensions = self.extensions
|
119
|
+
|
120
|
+
if (replace = (args.last.is_a?(Hash) ? args.pop : nil))
|
121
|
+
if args.empty?
|
122
|
+
extensions.map! do |ext|
|
123
|
+
replace[ext] ? replace[ext].to_s : ext
|
124
|
+
end
|
125
|
+
else
|
126
|
+
raise ArgumentError.new 'Cannot replace extensions with array ' \
|
127
|
+
'and hash at the same time.'
|
128
|
+
end
|
129
|
+
else
|
130
|
+
extensions = args.map(&:to_s)
|
131
|
+
end
|
132
|
+
|
133
|
+
if extensions == self.extensions
|
134
|
+
self
|
135
|
+
elsif only_filename?
|
136
|
+
Path "#{pure_name}.#{extensions.join('.')}"
|
137
|
+
else
|
138
|
+
dirname.join "#{pure_name}.#{extensions.join('.')}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
# rubocop:enable all
|
142
|
+
|
143
|
+
# Replace last extension with one or multiple new extensions.
|
144
|
+
#
|
145
|
+
# @example
|
146
|
+
# Path('file.de.txt').replace_extension('html')
|
147
|
+
# #=> <Path "file.de.html">
|
148
|
+
#
|
149
|
+
# @example
|
150
|
+
# Path('file.de.txt').replace_extension('html', 'erb')
|
151
|
+
# #=> <Path "file.de.html.erb">
|
152
|
+
#
|
153
|
+
# @return [Path] Path to new filename.
|
154
|
+
#
|
155
|
+
def replace_extension(*args)
|
156
|
+
extensions = self.extensions
|
157
|
+
extensions.pop
|
158
|
+
extensions += args.flatten
|
159
|
+
|
160
|
+
replace_extensions extensions
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Path
|
4
|
+
# @!group File Operations
|
5
|
+
|
6
|
+
# Return base name without path.
|
7
|
+
#
|
8
|
+
# @return [String] Base name.
|
9
|
+
#
|
10
|
+
def name
|
11
|
+
::File.basename internal_path
|
12
|
+
end
|
13
|
+
alias basename name
|
14
|
+
|
15
|
+
# Create new file at pointed location or update modification time if file
|
16
|
+
# exists.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# Path('/path/to/file.txt').touch
|
20
|
+
# #=> <Path:"/path/to/file.txt">
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# Path('/path/to').touch('file.txt')
|
24
|
+
# #=> <Path:"/path/to/file.txt">
|
25
|
+
#
|
26
|
+
# @return [Path] Path to touched file.
|
27
|
+
#
|
28
|
+
def touch(*args)
|
29
|
+
with_path(*args) do |path|
|
30
|
+
invoke_backend :touch, path
|
31
|
+
Path path
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Removes file at current path.
|
36
|
+
#
|
37
|
+
# Raise an error if file does not exists or is a directory.
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# Path('/file.txt').touch.unlink
|
41
|
+
# #=> <Path /file.txt>
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
# Path('/file.txt').touch
|
45
|
+
# Path('/').unlink('file.txt')
|
46
|
+
# #=> <Path /file.txt>
|
47
|
+
#
|
48
|
+
# @return [Path] Unlinked path.
|
49
|
+
#
|
50
|
+
def unlink(*args)
|
51
|
+
with_path(*args) do |path|
|
52
|
+
invoke_backend :unlink, path
|
53
|
+
Path path
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Create a file at pointed location and all missing parent directories.
|
58
|
+
#
|
59
|
+
# Given arguments will be joined with current path before directories and
|
60
|
+
# file is created.
|
61
|
+
#
|
62
|
+
# If file already exists nothing will be done.
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# Path('/path/to/file.txt').mkfile
|
66
|
+
# #=> <Path:"/path/to/file.txt">
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# Path('/').mkfile('path', 'to', 'file.txt')
|
70
|
+
# #=> <Path:"/path/to/file.txt">
|
71
|
+
#
|
72
|
+
# @return [Path] Path to created or existent file.
|
73
|
+
#
|
74
|
+
def mkfile(*args) # rubocop:disable AbcSize
|
75
|
+
with_path(*args) do |path|
|
76
|
+
path.parent.mkpath if !path.exists? && path.parent && !path.parent.exists?
|
77
|
+
|
78
|
+
if path.exists?
|
79
|
+
raise Errno::ENOENT.new path.to_s unless path.file?
|
80
|
+
else
|
81
|
+
path.touch
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Search for a file in current directory or parent directories.
|
87
|
+
#
|
88
|
+
# Given search pattern can either be a regular expression or a shell glob
|
89
|
+
# expression.
|
90
|
+
#
|
91
|
+
# @example
|
92
|
+
# Path.cwd.lookup('project.{yml,yaml}')
|
93
|
+
# #=> <Path:"/path/config.yml">
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
# Path.cwd.lookup(/config(_\d+).ya?ml/)
|
97
|
+
# #=> <Path:"/path/config_354.yaml">
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
# Path('~').lookup('*config', ::File::FNM_DOTMATCH)
|
101
|
+
# #=> <Path:"/gome/user/.gitconfig">
|
102
|
+
#
|
103
|
+
# @param pattern [String|RegExp] Expression file name must match.
|
104
|
+
# @param flags [Integer] Additional flags. See {::File.fnmatch}.
|
105
|
+
# Defaults to `File::FNM_EXTGLOB`.
|
106
|
+
# @return [Path] Path to found file or nil.
|
107
|
+
#
|
108
|
+
def lookup(pattern, flags = nil) # rubocop:disable MethodLength
|
109
|
+
flags = self.class.default_glob_flags(flags)
|
110
|
+
|
111
|
+
expand.ascend do |path|
|
112
|
+
case pattern
|
113
|
+
when String
|
114
|
+
path.entries.each do |c|
|
115
|
+
return path.join(c) if ::File.fnmatch?(pattern, c.name, flags)
|
116
|
+
end
|
117
|
+
when Regexp
|
118
|
+
path.entries.each do |c|
|
119
|
+
# rubocop:disable RegexpMatch
|
120
|
+
return path.join(c) if pattern =~ c.name
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
# rubocop:enable all
|
128
|
+
|
129
|
+
# Return file modification time.
|
130
|
+
#
|
131
|
+
# @return [Time] Time of last modification.
|
132
|
+
#
|
133
|
+
def mtime
|
134
|
+
invoke_backend :mtime
|
135
|
+
end
|
136
|
+
|
137
|
+
# Set last modification time.
|
138
|
+
#
|
139
|
+
# @param [Time] Time of last modification.
|
140
|
+
#
|
141
|
+
def mtime=(time)
|
142
|
+
invoke_backend :mtime=, internal_path, time
|
143
|
+
end
|
144
|
+
|
145
|
+
# Return file access time.
|
146
|
+
#
|
147
|
+
# @return [Time] Time of last access.
|
148
|
+
#
|
149
|
+
def atime
|
150
|
+
invoke_backend :atime
|
151
|
+
end
|
152
|
+
|
153
|
+
# Set last access time.
|
154
|
+
#
|
155
|
+
# @param [Time] Time of last access.
|
156
|
+
#
|
157
|
+
def atime=(time)
|
158
|
+
invoke_backend :atime=, internal_path, time
|
159
|
+
end
|
160
|
+
|
161
|
+
def mode
|
162
|
+
invoke_backend :mode
|
163
|
+
end
|
164
|
+
|
165
|
+
def chmod(mode)
|
166
|
+
invoke_backend :chmod, internal_path, mode
|
167
|
+
end
|
168
|
+
|
169
|
+
class << self
|
170
|
+
# Read or set process umask.
|
171
|
+
#
|
172
|
+
# @overload umask
|
173
|
+
# Read process umask.
|
174
|
+
#
|
175
|
+
# @return [Integer] Process umask.
|
176
|
+
#
|
177
|
+
# @overload umask(mask)
|
178
|
+
# Set process umask.
|
179
|
+
#
|
180
|
+
# @param mask [Integer] New process umask.
|
181
|
+
#
|
182
|
+
# @see File.umask
|
183
|
+
#
|
184
|
+
def umask(mask = nil)
|
185
|
+
if mask
|
186
|
+
invoke_backend :umask=, mask
|
187
|
+
else
|
188
|
+
invoke_backend :umask
|
189
|
+
end
|
190
|
+
end
|
191
|
+
alias umask= umask
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Path
|
4
|
+
# @!group File Predicates
|
5
|
+
|
6
|
+
# Check if path points to file.
|
7
|
+
#
|
8
|
+
# @return [Boolean] True if path is a file.
|
9
|
+
# @see ::File.file?
|
10
|
+
#
|
11
|
+
def file?
|
12
|
+
invoke_backend :file?
|
13
|
+
end
|
14
|
+
|
15
|
+
# Check if path points to an existing location.
|
16
|
+
#
|
17
|
+
# @return [Boolean] True if path exists.
|
18
|
+
# @see ::File.exists?
|
19
|
+
#
|
20
|
+
def exists?
|
21
|
+
invoke_backend :exists?
|
22
|
+
end
|
23
|
+
alias exist? exists?
|
24
|
+
alias existent? exists?
|
25
|
+
|
26
|
+
# Check if path points to a directory.
|
27
|
+
#
|
28
|
+
# @return [Boolean] True if path is a directory.
|
29
|
+
# @see ::File.directory?
|
30
|
+
#
|
31
|
+
def directory?
|
32
|
+
invoke_backend :directory?
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Path
|
4
|
+
# @!group Identity
|
5
|
+
|
6
|
+
# Return path as string. String will be duped before it gets returned and
|
7
|
+
# cannot be used to modify the path object.
|
8
|
+
#
|
9
|
+
# @return [String] Path as string.
|
10
|
+
def path
|
11
|
+
internal_path.dup
|
12
|
+
end
|
13
|
+
alias to_path path
|
14
|
+
alias to_str path
|
15
|
+
alias to_s path
|
16
|
+
|
17
|
+
# Return a useful object string representation.
|
18
|
+
#
|
19
|
+
# @return [String] Useful object representation
|
20
|
+
def inspect
|
21
|
+
"<#{self.class.name}:#{internal_path}>"
|
22
|
+
end
|
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
|