rubypath 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e7333efb5dc342354d834cbbca3ce7fd6bc4e77
|
4
|
+
data.tar.gz: fa82812dd3e0b30f7989b7677f2b5f3d9e02e041
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c57d75be4a166f7ed70675668de9bb1a9284097b11006be7e5ebb0fd173e2f937582c4a2a974be25c360886ee764be5b7fc921d258a1273f54ff26b8f377dc21
|
7
|
+
data.tar.gz: 9d5ae45a83991d9feaf746d64fd8b5a8e908637fb6a6e1515eb6a440f7be09a5ac6acd378bac56c2750c1a844431311e5cbaa800ddcc26d099d45ee8651b64bd
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# Changes
|
2
|
+
|
3
|
+
## 1.0.1
|
4
|
+
|
5
|
+
* Fix issues with gem packaging
|
6
|
+
|
7
|
+
## 1.0.0
|
8
|
+
|
9
|
+
* Dump to semantic versioning
|
10
|
+
* Internal code style changes
|
11
|
+
|
12
|
+
## 0.3.2
|
13
|
+
|
14
|
+
* Fix infinite loop bug in #relative_from
|
15
|
+
* Fix handling of trailing slash in #cleanpath (and depended methods)
|
16
|
+
|
17
|
+
## 0.3.1
|
18
|
+
|
19
|
+
* Fix missing require 'tmpdir' for using with mocked backend
|
20
|
+
|
21
|
+
## 0.3.0
|
22
|
+
|
23
|
+
* Add #unlink
|
24
|
+
* Add #cleanpath
|
25
|
+
* Add #rmtree, #rmtree!, and safe variants
|
26
|
+
|
27
|
+
## 0.2.0
|
28
|
+
|
29
|
+
* Add #relative_from
|
30
|
+
|
31
|
+
## 0.1.0
|
32
|
+
|
33
|
+
* Initial release
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
GNU LESSER GENERAL PUBLIC LICENSE
|
2
|
+
Version 3, 29 June 2007
|
3
|
+
|
4
|
+
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
5
|
+
Everyone is permitted to copy and distribute verbatim copies
|
6
|
+
of this license document, but changing it is not allowed.
|
7
|
+
|
8
|
+
|
9
|
+
This version of the GNU Lesser General Public License incorporates
|
10
|
+
the terms and conditions of version 3 of the GNU General Public
|
11
|
+
License, supplemented by the additional permissions listed below.
|
12
|
+
|
13
|
+
0. Additional Definitions.
|
14
|
+
|
15
|
+
As used herein, "this License" refers to version 3 of the GNU Lesser
|
16
|
+
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
17
|
+
General Public License.
|
18
|
+
|
19
|
+
"The Library" refers to a covered work governed by this License,
|
20
|
+
other than an Application or a Combined Work as defined below.
|
21
|
+
|
22
|
+
An "Application" is any work that makes use of an interface provided
|
23
|
+
by the Library, but which is not otherwise based on the Library.
|
24
|
+
Defining a subclass of a class defined by the Library is deemed a mode
|
25
|
+
of using an interface provided by the Library.
|
26
|
+
|
27
|
+
A "Combined Work" is a work produced by combining or linking an
|
28
|
+
Application with the Library. The particular version of the Library
|
29
|
+
with which the Combined Work was made is also called the "Linked
|
30
|
+
Version".
|
31
|
+
|
32
|
+
The "Minimal Corresponding Source" for a Combined Work means the
|
33
|
+
Corresponding Source for the Combined Work, excluding any source code
|
34
|
+
for portions of the Combined Work that, considered in isolation, are
|
35
|
+
based on the Application, and not on the Linked Version.
|
36
|
+
|
37
|
+
The "Corresponding Application Code" for a Combined Work means the
|
38
|
+
object code and/or source code for the Application, including any data
|
39
|
+
and utility programs needed for reproducing the Combined Work from the
|
40
|
+
Application, but excluding the System Libraries of the Combined Work.
|
41
|
+
|
42
|
+
1. Exception to Section 3 of the GNU GPL.
|
43
|
+
|
44
|
+
You may convey a covered work under sections 3 and 4 of this License
|
45
|
+
without being bound by section 3 of the GNU GPL.
|
46
|
+
|
47
|
+
2. Conveying Modified Versions.
|
48
|
+
|
49
|
+
If you modify a copy of the Library, and, in your modifications, a
|
50
|
+
facility refers to a function or data to be supplied by an Application
|
51
|
+
that uses the facility (other than as an argument passed when the
|
52
|
+
facility is invoked), then you may convey a copy of the modified
|
53
|
+
version:
|
54
|
+
|
55
|
+
a) under this License, provided that you make a good faith effort to
|
56
|
+
ensure that, in the event an Application does not supply the
|
57
|
+
function or data, the facility still operates, and performs
|
58
|
+
whatever part of its purpose remains meaningful, or
|
59
|
+
|
60
|
+
b) under the GNU GPL, with none of the additional permissions of
|
61
|
+
this License applicable to that copy.
|
62
|
+
|
63
|
+
3. Object Code Incorporating Material from Library Header Files.
|
64
|
+
|
65
|
+
The object code form of an Application may incorporate material from
|
66
|
+
a header file that is part of the Library. You may convey such object
|
67
|
+
code under terms of your choice, provided that, if the incorporated
|
68
|
+
material is not limited to numerical parameters, data structure
|
69
|
+
layouts and accessors, or small macros, inline functions and templates
|
70
|
+
(ten or fewer lines in length), you do both of the following:
|
71
|
+
|
72
|
+
a) Give prominent notice with each copy of the object code that the
|
73
|
+
Library is used in it and that the Library and its use are
|
74
|
+
covered by this License.
|
75
|
+
|
76
|
+
b) Accompany the object code with a copy of the GNU GPL and this license
|
77
|
+
document.
|
78
|
+
|
79
|
+
4. Combined Works.
|
80
|
+
|
81
|
+
You may convey a Combined Work under terms of your choice that,
|
82
|
+
taken together, effectively do not restrict modification of the
|
83
|
+
portions of the Library contained in the Combined Work and reverse
|
84
|
+
engineering for debugging such modifications, if you also do each of
|
85
|
+
the following:
|
86
|
+
|
87
|
+
a) Give prominent notice with each copy of the Combined Work that
|
88
|
+
the Library is used in it and that the Library and its use are
|
89
|
+
covered by this License.
|
90
|
+
|
91
|
+
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
92
|
+
document.
|
93
|
+
|
94
|
+
c) For a Combined Work that displays copyright notices during
|
95
|
+
execution, include the copyright notice for the Library among
|
96
|
+
these notices, as well as a reference directing the user to the
|
97
|
+
copies of the GNU GPL and this license document.
|
98
|
+
|
99
|
+
d) Do one of the following:
|
100
|
+
|
101
|
+
0) Convey the Minimal Corresponding Source under the terms of this
|
102
|
+
License, and the Corresponding Application Code in a form
|
103
|
+
suitable for, and under terms that permit, the user to
|
104
|
+
recombine or relink the Application with a modified version of
|
105
|
+
the Linked Version to produce a modified Combined Work, in the
|
106
|
+
manner specified by section 6 of the GNU GPL for conveying
|
107
|
+
Corresponding Source.
|
108
|
+
|
109
|
+
1) Use a suitable shared library mechanism for linking with the
|
110
|
+
Library. A suitable mechanism is one that (a) uses at run time
|
111
|
+
a copy of the Library already present on the user's computer
|
112
|
+
system, and (b) will operate properly with a modified version
|
113
|
+
of the Library that is interface-compatible with the Linked
|
114
|
+
Version.
|
115
|
+
|
116
|
+
e) Provide Installation Information, but only if you would otherwise
|
117
|
+
be required to provide such information under section 6 of the
|
118
|
+
GNU GPL, and only to the extent that such information is
|
119
|
+
necessary to install and execute a modified version of the
|
120
|
+
Combined Work produced by recombining or relinking the
|
121
|
+
Application with a modified version of the Linked Version. (If
|
122
|
+
you use option 4d0, the Installation Information must accompany
|
123
|
+
the Minimal Corresponding Source and Corresponding Application
|
124
|
+
Code. If you use option 4d1, you must provide the Installation
|
125
|
+
Information in the manner specified by section 6 of the GNU GPL
|
126
|
+
for conveying Corresponding Source.)
|
127
|
+
|
128
|
+
5. Combined Libraries.
|
129
|
+
|
130
|
+
You may place library facilities that are a work based on the
|
131
|
+
Library side by side in a single library together with other library
|
132
|
+
facilities that are not Applications and are not covered by this
|
133
|
+
License, and convey such a combined library under terms of your
|
134
|
+
choice, if you do both of the following:
|
135
|
+
|
136
|
+
a) Accompany the combined library with a copy of the same work based
|
137
|
+
on the Library, uncombined with any other library facilities,
|
138
|
+
conveyed under the terms of this License.
|
139
|
+
|
140
|
+
b) Give prominent notice with the combined library that part of it
|
141
|
+
is a work based on the Library, and explaining where to find the
|
142
|
+
accompanying uncombined form of the same work.
|
143
|
+
|
144
|
+
6. Revised Versions of the GNU Lesser General Public License.
|
145
|
+
|
146
|
+
The Free Software Foundation may publish revised and/or new versions
|
147
|
+
of the GNU Lesser General Public License from time to time. Such new
|
148
|
+
versions will be similar in spirit to the present version, but may
|
149
|
+
differ in detail to address new problems or concerns.
|
150
|
+
|
151
|
+
Each version is given a distinguishing version number. If the
|
152
|
+
Library as you received it specifies that a certain numbered version
|
153
|
+
of the GNU Lesser General Public License "or any later version"
|
154
|
+
applies to it, you have the option of following the terms and
|
155
|
+
conditions either of that published version or of any later version
|
156
|
+
published by the Free Software Foundation. If the Library as you
|
157
|
+
received it does not specify a version number of the GNU Lesser
|
158
|
+
General Public License, you may choose any version of the GNU Lesser
|
159
|
+
General Public License ever published by the Free Software Foundation.
|
160
|
+
|
161
|
+
If the Library as you received it specifies that a proxy can decide
|
162
|
+
whether future versions of the GNU Lesser General Public License shall
|
163
|
+
apply, that proxy's public statement of acceptance of any version is
|
164
|
+
permanent authorization for you to choose that version for the
|
165
|
+
Library.
|
data/lib/rubypath.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubypath/version'
|
4
|
+
require 'tmpdir'
|
5
|
+
|
6
|
+
#
|
7
|
+
class Path
|
8
|
+
require 'rubypath/identity'
|
9
|
+
require 'rubypath/construction'
|
10
|
+
require 'rubypath/comparison'
|
11
|
+
require 'rubypath/extensions'
|
12
|
+
|
13
|
+
require 'rubypath/path_operations'
|
14
|
+
require 'rubypath/path_predicates'
|
15
|
+
require 'rubypath/file_operations'
|
16
|
+
require 'rubypath/file_predicates'
|
17
|
+
require 'rubypath/dir_operations'
|
18
|
+
require 'rubypath/io_operations'
|
19
|
+
|
20
|
+
require 'rubypath/mock'
|
21
|
+
require 'rubypath/backend'
|
22
|
+
end
|
23
|
+
|
24
|
+
module Kernel
|
25
|
+
# rubocop:disable Style/MethodName
|
26
|
+
def Path(*args)
|
27
|
+
Path.new(*args)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Path
|
4
|
+
class Backend
|
5
|
+
class << self
|
6
|
+
def instance
|
7
|
+
@instance ||= new
|
8
|
+
end
|
9
|
+
|
10
|
+
def delegate(mth)
|
11
|
+
define_method mth do |*args|
|
12
|
+
backend.send mth, *args
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def mock(*args, &block)
|
17
|
+
instance.mock(*args, &block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_accessor :backend
|
22
|
+
def initialize
|
23
|
+
self.backend = Backend::Sys.new
|
24
|
+
end
|
25
|
+
|
26
|
+
# rubocop:disable Metrics/MethodLength
|
27
|
+
def mock(opts = {}, &block)
|
28
|
+
if opts[:root]
|
29
|
+
# Use real file system scoped to given directory (chroot like)
|
30
|
+
if opts[:root] == :tmp
|
31
|
+
::Dir.mktmpdir('rubypath') do |path|
|
32
|
+
use_backend Backend::Sys.new(path), &block
|
33
|
+
end
|
34
|
+
else
|
35
|
+
use_backend Backend::Sys.new(opts[:root]), &block
|
36
|
+
end
|
37
|
+
else
|
38
|
+
# Use mock FS
|
39
|
+
use_backend Backend::Mock.new, &block
|
40
|
+
end
|
41
|
+
end
|
42
|
+
# rubocop:enable Metrics/MethodLength
|
43
|
+
|
44
|
+
def use_backend(be)
|
45
|
+
old_backend = backend
|
46
|
+
self.backend = be
|
47
|
+
yield
|
48
|
+
backend.quit if backend.respond_to? :quit
|
49
|
+
self.backend = old_backend
|
50
|
+
end
|
51
|
+
|
52
|
+
delegate :expand_path
|
53
|
+
delegate :getwd
|
54
|
+
delegate :exists?
|
55
|
+
delegate :mkdir
|
56
|
+
delegate :mkpath
|
57
|
+
delegate :directory?
|
58
|
+
delegate :file?
|
59
|
+
delegate :touch
|
60
|
+
delegate :write
|
61
|
+
delegate :read
|
62
|
+
delegate :mtime
|
63
|
+
delegate :mtime=
|
64
|
+
delegate :entries
|
65
|
+
delegate :glob
|
66
|
+
delegate :atime
|
67
|
+
delegate :atime=
|
68
|
+
delegate :umask
|
69
|
+
delegate :umask=
|
70
|
+
delegate :mode
|
71
|
+
delegate :chmod
|
72
|
+
delegate :unlink
|
73
|
+
delegate :rmtree
|
74
|
+
delegate :rmtree!
|
75
|
+
delegate :safe_rmtree
|
76
|
+
delegate :safe_rmtree!
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def invoke_backend(mth, *args)
|
82
|
+
args << self if args.empty?
|
83
|
+
self.class.send :invoke_backend, mth, *args
|
84
|
+
end
|
85
|
+
|
86
|
+
class << self
|
87
|
+
private
|
88
|
+
|
89
|
+
def invoke_backend(mth, *args)
|
90
|
+
Backend.instance.send mth, *args
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
require 'rubypath/backend/mock'
|
95
|
+
require 'rubypath/backend/sys'
|
96
|
+
end
|
@@ -0,0 +1,362 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Path
|
4
|
+
class Backend
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
6
|
+
class Mock
|
7
|
+
attr_reader :user, :homes
|
8
|
+
|
9
|
+
# @!group Virtual File System Configuration
|
10
|
+
|
11
|
+
# Set user that owns the current process.
|
12
|
+
def current_user=(user)
|
13
|
+
@user = user.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
# Define new home directories. Must be given as has a hash that is
|
17
|
+
# interpreted as a user name to home directory mapping.
|
18
|
+
attr_writer :homes
|
19
|
+
|
20
|
+
# Set current working directory.
|
21
|
+
attr_writer :cwd
|
22
|
+
|
23
|
+
# @!group Internal Methods
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@user = 'root'
|
27
|
+
@homes = {'root' => '/root'}
|
28
|
+
@cwd = '/root'
|
29
|
+
@umask = 0o022
|
30
|
+
end
|
31
|
+
|
32
|
+
def home(user)
|
33
|
+
homes.fetch(user) do
|
34
|
+
raise ArgumentError.new "user #{user} doesn't exist"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @!group Backend Operations
|
39
|
+
|
40
|
+
def expand_path(path, base = getwd)
|
41
|
+
# rubocop:disable RegexpMatch
|
42
|
+
if %r{^~(?<name>[^/]*)(/(?<rest>.*))?$} =~ path
|
43
|
+
::File.expand_path rest.to_s, home(name.empty? ? user : name)
|
44
|
+
else
|
45
|
+
::File.expand_path(path, base)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
alias expand expand_path
|
49
|
+
# rubocop:enable all
|
50
|
+
|
51
|
+
def getwd
|
52
|
+
@cwd ||= '/'
|
53
|
+
end
|
54
|
+
|
55
|
+
def file?(path)
|
56
|
+
lookup(path).is_a?(File)
|
57
|
+
end
|
58
|
+
|
59
|
+
def directory?(path)
|
60
|
+
lookup(path).is_a?(Dir)
|
61
|
+
end
|
62
|
+
|
63
|
+
def exists?(path)
|
64
|
+
lookup(path) ? true : false
|
65
|
+
end
|
66
|
+
|
67
|
+
def mkdir(path)
|
68
|
+
return if path.to_s == '/'
|
69
|
+
|
70
|
+
node = lookup_parent! path
|
71
|
+
dir = node.lookup ::File.basename path
|
72
|
+
return if dir.is_a?(Dir)
|
73
|
+
|
74
|
+
if dir.nil?
|
75
|
+
node.add Dir.new(self, ::File.basename(path))
|
76
|
+
else
|
77
|
+
raise ArgumentError.new \
|
78
|
+
"Node #{dir.path} exists and is no directory."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def mkpath(path)
|
83
|
+
path = expand_path(path)
|
84
|
+
::Pathname.new(path).descend do |p|
|
85
|
+
mkdir(p.to_s)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def touch(path)
|
90
|
+
node = lookup_parent! path
|
91
|
+
file = node.lookup ::File.basename path
|
92
|
+
if file
|
93
|
+
file.mtime = Time.now
|
94
|
+
else
|
95
|
+
node.add File.new(self, ::File.basename(path))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def write(path, content, *args) # rubocop:disable AbcSize, MethodLength
|
100
|
+
node = lookup_parent! path
|
101
|
+
file = node.lookup ::File.basename(path)
|
102
|
+
unless file
|
103
|
+
file = File.new self, ::File.basename(path)
|
104
|
+
node.add file
|
105
|
+
end
|
106
|
+
|
107
|
+
case file
|
108
|
+
when File
|
109
|
+
if args.empty?
|
110
|
+
file.content = String.new(content)
|
111
|
+
else
|
112
|
+
offset = args[0].to_i
|
113
|
+
file.content[offset, content.length] = content
|
114
|
+
end
|
115
|
+
file.mtime = Time.now
|
116
|
+
when Dir
|
117
|
+
raise Errno::EISDIR.new path
|
118
|
+
else
|
119
|
+
raise ArgumentError.new
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def mtime(path)
|
124
|
+
lookup!(path).mtime
|
125
|
+
end
|
126
|
+
|
127
|
+
def mtime=(path, time)
|
128
|
+
lookup!(path).mtime = time
|
129
|
+
end
|
130
|
+
|
131
|
+
def atime(path)
|
132
|
+
lookup!(path).atime
|
133
|
+
end
|
134
|
+
|
135
|
+
def atime=(path, time)
|
136
|
+
lookup!(path).atime = time
|
137
|
+
end
|
138
|
+
|
139
|
+
def read(path, *args)
|
140
|
+
file = lookup_file!(path)
|
141
|
+
file.atime = Time.now
|
142
|
+
content = file.content
|
143
|
+
if args[0]
|
144
|
+
length = args[0].to_i
|
145
|
+
offset = args[1] ? args[1].to_i : 0
|
146
|
+
content = content.slice(offset, length)
|
147
|
+
end
|
148
|
+
content
|
149
|
+
end
|
150
|
+
|
151
|
+
def entries(path)
|
152
|
+
node = lookup_dir! path
|
153
|
+
node.children.map(&:name) + %w[. ..]
|
154
|
+
end
|
155
|
+
|
156
|
+
def glob(pattern, flags = 0)
|
157
|
+
root.all.select do |node|
|
158
|
+
::File.fnmatch pattern, node.path, (flags | ::File::FNM_PATHNAME)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
attr_reader :umask
|
163
|
+
|
164
|
+
def umask=(mask)
|
165
|
+
@umask = Integer(mask)
|
166
|
+
end
|
167
|
+
|
168
|
+
def mode(path)
|
169
|
+
lookup!(path).mode
|
170
|
+
end
|
171
|
+
|
172
|
+
def unlink(path) # rubocop:disable MethodLength
|
173
|
+
node = lookup_parent!(path)
|
174
|
+
file = node.lookup ::File.basename path
|
175
|
+
case file
|
176
|
+
when Dir
|
177
|
+
raise Errno::EISDIR.new path
|
178
|
+
when File
|
179
|
+
node.children.delete(file)
|
180
|
+
when nil
|
181
|
+
raise Errno::ENOENT.new path
|
182
|
+
else
|
183
|
+
raise ArgumentError.new "Unknown node #{node.inspect} for #unlink."
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def rmtree(path)
|
188
|
+
node = lookup path
|
189
|
+
case node
|
190
|
+
when Dir, File
|
191
|
+
lookup_parent!(path).children.delete(node)
|
192
|
+
when nil
|
193
|
+
nil
|
194
|
+
else
|
195
|
+
raise ArgumentError.new "Unknown node #{node.inspect} for #rmtree."
|
196
|
+
end
|
197
|
+
end
|
198
|
+
alias safe_rmtree rmtree
|
199
|
+
|
200
|
+
def rmtree!(path)
|
201
|
+
node = lookup path
|
202
|
+
case node
|
203
|
+
when Dir, File
|
204
|
+
lookup_parent!(path).children.delete(node)
|
205
|
+
when nil
|
206
|
+
raise Errno::ENOENT.new path
|
207
|
+
else
|
208
|
+
raise ArgumentError.new "Unknown node #{node.inspect} for #rmtree."
|
209
|
+
end
|
210
|
+
end
|
211
|
+
alias safe_rmtree! rmtree!
|
212
|
+
|
213
|
+
# @!group Internal Virtual File System
|
214
|
+
|
215
|
+
# Return root node.
|
216
|
+
def root
|
217
|
+
@root ||= Dir.new(self, '')
|
218
|
+
end
|
219
|
+
|
220
|
+
def to_lookup(path)
|
221
|
+
path = expand path
|
222
|
+
path.sub(%r{^/+}, '')
|
223
|
+
end
|
224
|
+
|
225
|
+
def lookup(path)
|
226
|
+
root.lookup to_lookup path
|
227
|
+
end
|
228
|
+
|
229
|
+
def lookup!(path)
|
230
|
+
node = lookup path
|
231
|
+
return node if node
|
232
|
+
|
233
|
+
raise Errno::ENOENT.new path
|
234
|
+
end
|
235
|
+
|
236
|
+
def lookup_file!(path)
|
237
|
+
node = lookup! path
|
238
|
+
case node
|
239
|
+
when File
|
240
|
+
node
|
241
|
+
when Dir
|
242
|
+
raise Errno::EISDIR.new path
|
243
|
+
else
|
244
|
+
raise ArgumentError.new "NOT A FILE: #{path}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def lookup_dir!(path)
|
249
|
+
node = lookup! path
|
250
|
+
return node if node.is_a?(Dir)
|
251
|
+
|
252
|
+
raise Errno::ENOENT.new path
|
253
|
+
end
|
254
|
+
|
255
|
+
def lookup_parent!(path)
|
256
|
+
node = lookup ::File.dirname expand path
|
257
|
+
|
258
|
+
if node && node.is_a?(Dir)
|
259
|
+
node
|
260
|
+
elsif node
|
261
|
+
raise Errno::ENOTDIR.new path
|
262
|
+
else
|
263
|
+
raise Errno::ENOENT.new path
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
#
|
268
|
+
class Node
|
269
|
+
attr_reader :sys, :name, :parent
|
270
|
+
attr_accessor :mtime, :atime, :mode
|
271
|
+
|
272
|
+
def initialize(backend, name, _ops = {})
|
273
|
+
@sys = backend
|
274
|
+
@name = name
|
275
|
+
@mtime = Time.now
|
276
|
+
@atime = Time.now
|
277
|
+
end
|
278
|
+
|
279
|
+
def mtime=(time)
|
280
|
+
if time.is_a?(Time)
|
281
|
+
@mtime = time
|
282
|
+
else
|
283
|
+
raise "Not Time but `#{time.inspect}` " \
|
284
|
+
"of `#{time.class.name}` given."
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def lookup(_path)
|
289
|
+
raise NotImplementError.new 'Subclass responsibility.'
|
290
|
+
end
|
291
|
+
|
292
|
+
def added(parent)
|
293
|
+
@parent = parent
|
294
|
+
end
|
295
|
+
|
296
|
+
def path
|
297
|
+
parent ? "#{parent.path}/#{name}" : name
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
#
|
302
|
+
class Dir < Node
|
303
|
+
def initialize(backend, name, opts = {})
|
304
|
+
super
|
305
|
+
self.mode = 0o777 - backend.umask
|
306
|
+
end
|
307
|
+
|
308
|
+
def lookup(path) # rubocop:disable MethodLength
|
309
|
+
name, rest = path.to_s.split('/', 2).map(&:to_s)
|
310
|
+
|
311
|
+
if name.nil?
|
312
|
+
if rest.nil?
|
313
|
+
self
|
314
|
+
else
|
315
|
+
lookup rest
|
316
|
+
end
|
317
|
+
else
|
318
|
+
child = children.find {|c| c.name == name }
|
319
|
+
if child
|
320
|
+
rest.nil? ? child : child.lookup(rest)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def add(node)
|
326
|
+
if children.any? {|c| c.name == node.name }
|
327
|
+
raise ArgumentError.new "Node #{path}/#{node.name} already exists."
|
328
|
+
end
|
329
|
+
|
330
|
+
children << node
|
331
|
+
node.added self
|
332
|
+
end
|
333
|
+
|
334
|
+
def all
|
335
|
+
children.reduce([]) do |memo, child|
|
336
|
+
memo << child
|
337
|
+
memo += child.all if child.is_a?(Dir)
|
338
|
+
memo
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def children
|
343
|
+
@children ||= []
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
#
|
348
|
+
class File < Node
|
349
|
+
attr_accessor :content
|
350
|
+
|
351
|
+
def initialize(backend, name, opts = {})
|
352
|
+
super
|
353
|
+
self.mode = 0o666 - backend.umask
|
354
|
+
end
|
355
|
+
|
356
|
+
def lookup(_path)
|
357
|
+
nil
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|