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.
@@ -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
@@ -0,0 +1,11 @@
1
+ class Path
2
+ module VERSION
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ PATCH = 0
6
+ STAGE = nil
7
+ STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.').freeze
8
+
9
+ def self.to_s; STRING end
10
+ end
11
+ end