vfs 0.0.4 → 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.
- data/Rakefile +2 -2
- data/lib/vfs.rb +18 -0
- data/lib/vfs/entries/dir.rb +272 -0
- data/lib/vfs/entries/entry.rb +127 -0
- data/lib/vfs/entries/file.rb +185 -0
- data/lib/vfs/entries/universal_entry.rb +24 -0
- data/lib/vfs/entry_proxy.rb +38 -0
- data/lib/vfs/error.rb +4 -0
- data/lib/vfs/integration/string.rb +19 -0
- data/lib/vfs/path.rb +125 -0
- data/lib/vfs/storages/hash_fs.rb +194 -0
- data/lib/vfs/storages/local.rb +138 -0
- data/lib/vfs/storages/specification.rb +127 -0
- data/lib/vfs/support.rb +2 -0
- data/readme.md +110 -14
- data/spec/container_spec.rb +31 -0
- data/spec/dir_spec.rb +224 -0
- data/spec/entry_spec.rb +24 -0
- data/spec/file_spec.rb +189 -0
- data/spec/path_spec.rb +121 -0
- data/spec/spec_helper.rb +2 -9
- data/spec/storages/hash_fs_spec.rb +10 -0
- data/spec/storages/local_spec.rb +10 -0
- data/spec/universal_entry_spec.rb +42 -0
- metadata +25 -23
- data/lib/old/ssh.rb +0 -11
- data/lib/rsh.rb +0 -19
- data/lib/rsh/box.rb +0 -182
- data/lib/rsh/box/marks.rb +0 -29
- data/lib/rsh/drivers/abstract.rb +0 -15
- data/lib/rsh/drivers/local.rb +0 -48
- data/lib/rsh/drivers/ssh.rb +0 -147
- data/lib/rsh/gems.rb +0 -2
- data/lib/rsh/support.rb +0 -30
- data/spec/abstract_driver.rb +0 -82
- data/spec/abstract_driver/dir/dir2/file +0 -0
- data/spec/abstract_driver/local_file +0 -1
- data/spec/box_spec.rb +0 -109
- data/spec/box_spec/dir/dir2/file +0 -0
- data/spec/box_spec/local_file +0 -1
- data/spec/config.example.yml +0 -4
- data/spec/config.yml +0 -5
- data/spec/local_driver_spec.rb +0 -9
- data/spec/ssh_driver_spec.rb +0 -15
@@ -0,0 +1,24 @@
|
|
1
|
+
module Vfs
|
2
|
+
class UniversalEntry < Entry
|
3
|
+
#
|
4
|
+
# Attributes
|
5
|
+
#
|
6
|
+
def exist?
|
7
|
+
attrs = get
|
8
|
+
!!(attrs[:dir] or attrs[:file])
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
#
|
13
|
+
# CRUD
|
14
|
+
#
|
15
|
+
def destroy
|
16
|
+
storage.open_fs do |fs|
|
17
|
+
attrs = get
|
18
|
+
fs.delete_dir path if attrs[:dir]
|
19
|
+
fs.delete_file path if attrs[:file]
|
20
|
+
end
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#
|
2
|
+
# It allows you dynamically (magically) switch between UniversalEntry/Dir/File
|
3
|
+
#
|
4
|
+
module Vfs
|
5
|
+
class EntryProxy < BasicObject
|
6
|
+
attr_reader :_target
|
7
|
+
# WRAP = [:[], :entry, :dir, :file].to_set
|
8
|
+
|
9
|
+
def initialize entry
|
10
|
+
raise 'something wrong happening here!' if entry.respond_to?(:proxy?) and entry.proxy?
|
11
|
+
self._target = entry
|
12
|
+
end
|
13
|
+
|
14
|
+
def proxy?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
protected :==, :equal?, :!, :!=
|
19
|
+
protected
|
20
|
+
attr_writer :_target
|
21
|
+
|
22
|
+
def method_missing m, *a, &b
|
23
|
+
unless _target.respond_to? m
|
24
|
+
if ::Vfs::UniversalEntry.method_defined? m
|
25
|
+
self.target = _target.entry
|
26
|
+
elsif ::Vfs::Dir.method_defined? m
|
27
|
+
self._target = _target.dir
|
28
|
+
elsif ::Vfs::File.method_defined? m
|
29
|
+
self._target = _target.file
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
_target.send m, *a, &b
|
34
|
+
|
35
|
+
# return WRAP.include?(m) ? EntryProxy.new(result) : result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/vfs/error.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
class String
|
2
|
+
def to_entry_on storage = nil
|
3
|
+
path = self
|
4
|
+
storage ||= Vfs::Storages::Local.new
|
5
|
+
|
6
|
+
Vfs::Dir.new(storage, '/')[path]
|
7
|
+
end
|
8
|
+
alias_method :to_entry, :to_entry_on
|
9
|
+
|
10
|
+
def to_file_on storage = nil
|
11
|
+
to_entry_on(storage).file
|
12
|
+
end
|
13
|
+
alias_method :to_file, :to_file_on
|
14
|
+
|
15
|
+
def to_dir_on storage = nil
|
16
|
+
to_entry_on(storage).dir
|
17
|
+
end
|
18
|
+
alias_method :to_dir, :to_dir_on
|
19
|
+
end
|
data/lib/vfs/path.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
module Vfs
|
2
|
+
class Path < String
|
3
|
+
def initialize path = '/', options = {}
|
4
|
+
if options[:skip_normalization]
|
5
|
+
super path
|
6
|
+
@probably_dir = options[:probably_dir]
|
7
|
+
else
|
8
|
+
Path.validate! path
|
9
|
+
path, probably_dir = Path.normalize_to_string path
|
10
|
+
raise "invalid path '#{path}' (you are outside of the root)!" unless path
|
11
|
+
super path
|
12
|
+
@probably_dir = probably_dir
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def + path = ''
|
17
|
+
path = path.to_s
|
18
|
+
Path.validate! path, false
|
19
|
+
|
20
|
+
if Path.absolute?(path)
|
21
|
+
Path.normalize path
|
22
|
+
elsif path.empty?
|
23
|
+
self
|
24
|
+
else
|
25
|
+
Path.normalize "#{self}#{'/' unless self == '/'}#{path}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def parent
|
30
|
+
self + '..'
|
31
|
+
end
|
32
|
+
|
33
|
+
def probably_dir?
|
34
|
+
!!@probably_dir
|
35
|
+
end
|
36
|
+
|
37
|
+
def name
|
38
|
+
unless @name
|
39
|
+
root = self[0..0]
|
40
|
+
@name ||= split('/').last || root
|
41
|
+
end
|
42
|
+
@name
|
43
|
+
end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
def absolute? path
|
47
|
+
path =~ /^[\/~]|^\.$|^\.\//
|
48
|
+
end
|
49
|
+
|
50
|
+
def valid? path, forbid_relative = true, &block
|
51
|
+
result, err = if forbid_relative and !absolute?(path)
|
52
|
+
[false, "path must be started with '/', '~', or '.'"]
|
53
|
+
elsif path =~ /.+[\/~]$|\/\.$/
|
54
|
+
[false, "path can't be ended with '/', '~', or '/.'"]
|
55
|
+
elsif path =~ /\/[\/~]|\/\.\//
|
56
|
+
[false, "path can't include '/./', '/~/', '//' combinations!"]
|
57
|
+
elsif path =~ /.+[~]|\/\.\//
|
58
|
+
[false, "'~', or '.' can be present only at the begining of string"]
|
59
|
+
else
|
60
|
+
[true, nil]
|
61
|
+
end
|
62
|
+
|
63
|
+
block.call err if block and !result and err
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
def normalize path
|
68
|
+
path, probably_dir = normalize_to_string path
|
69
|
+
unless path
|
70
|
+
nil
|
71
|
+
else
|
72
|
+
Path.new(path, skip_normalization: true, probably_dir: probably_dir)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate! path, forbid_relative = true
|
77
|
+
valid?(path, forbid_relative){|error| raise "invalid path '#{path}' (#{error})!"}
|
78
|
+
end
|
79
|
+
|
80
|
+
def normalize_to_string path
|
81
|
+
root = path[0..0]
|
82
|
+
result, probably_dir = [], false
|
83
|
+
|
84
|
+
parts = path.split('/')[1..-1]
|
85
|
+
if parts
|
86
|
+
parts.each do |part|
|
87
|
+
if part == '..' and root != '.'
|
88
|
+
return nil, false unless result.size > 0
|
89
|
+
result.pop
|
90
|
+
probably_dir ||= true
|
91
|
+
# elsif part == '.'
|
92
|
+
# # do nothing
|
93
|
+
else
|
94
|
+
result << part
|
95
|
+
probably_dir &&= false
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
normalized_path = result.join('/')
|
100
|
+
|
101
|
+
probably_dir ||= true if normalized_path.empty?
|
102
|
+
|
103
|
+
return "#{root}#{'/' unless root == '/' or normalized_path.empty?}#{normalized_path}", probably_dir
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# protected
|
108
|
+
# def delete_dir_mark
|
109
|
+
# path = path.to_s.sub(%r{/$}, '')
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
#
|
113
|
+
# def root_path? path
|
114
|
+
# path =~ /^[#{ROOT_SYMBOLS}]$/
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# def split_path path
|
118
|
+
# path.split(/#{ROOT_SYMBOLS}/)
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# def dir_mark? path
|
122
|
+
# path =~ %r{/$}
|
123
|
+
# end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
#
|
2
|
+
# Dirty and uneficient In Memory FS, mainly for tests.
|
3
|
+
#
|
4
|
+
module Vfs
|
5
|
+
module Storages
|
6
|
+
class HashFs < Hash
|
7
|
+
def initialize
|
8
|
+
super
|
9
|
+
self['/'] = {dir: true}
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def open_fs &block
|
14
|
+
block.call self
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Attributes
|
19
|
+
#
|
20
|
+
def attributes path
|
21
|
+
base, name = split_path path
|
22
|
+
|
23
|
+
# if path == '/'
|
24
|
+
# return {dir: true, file: false}
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
stat = cd(base)[name]
|
28
|
+
attrs = {}
|
29
|
+
attrs[:file] = !!stat[:file]
|
30
|
+
attrs[:dir] = !!stat[:dir]
|
31
|
+
attrs
|
32
|
+
rescue Exception
|
33
|
+
{}
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_attributes path, attrs
|
37
|
+
raise 'not supported'
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
#
|
42
|
+
# File
|
43
|
+
#
|
44
|
+
def read_file path, &block
|
45
|
+
base, name = split_path path
|
46
|
+
assert cd(base)[name], :include?, :file
|
47
|
+
block.call cd(base)[name][:content]
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_file path, append, &block
|
51
|
+
base, name = split_path path
|
52
|
+
|
53
|
+
os = if append
|
54
|
+
file = cd(base)[name]
|
55
|
+
file ? file[:content] : ''
|
56
|
+
else
|
57
|
+
assert_not cd(base), :include?, name
|
58
|
+
''
|
59
|
+
end
|
60
|
+
writer = -> buff {os << buff}
|
61
|
+
block.call writer
|
62
|
+
|
63
|
+
cd(base)[name] = {file: true, content: os}
|
64
|
+
end
|
65
|
+
|
66
|
+
def delete_file path
|
67
|
+
base, name = split_path path
|
68
|
+
assert cd(base)[name], :include?, :file
|
69
|
+
cd(base).delete name
|
70
|
+
end
|
71
|
+
|
72
|
+
# def move_file path
|
73
|
+
# raise 'not supported'
|
74
|
+
# end
|
75
|
+
|
76
|
+
|
77
|
+
#
|
78
|
+
# Dir
|
79
|
+
#
|
80
|
+
def create_dir path
|
81
|
+
base, name = split_path path
|
82
|
+
assert_not cd(base), :include?, name
|
83
|
+
cd(base)[name] = {dir: true}
|
84
|
+
end
|
85
|
+
|
86
|
+
def delete_dir path
|
87
|
+
base, name = split_path path
|
88
|
+
assert cd(base)[name], :include?, :dir
|
89
|
+
# empty = true
|
90
|
+
# cd(base)[name].each do |key, value|
|
91
|
+
# empty = false if key.is_a? String
|
92
|
+
# end
|
93
|
+
# raise 'you are trying to delete not empty dir!' unless empty
|
94
|
+
cd(base).delete name
|
95
|
+
end
|
96
|
+
|
97
|
+
# def move_dir path
|
98
|
+
# raise 'not supported'
|
99
|
+
# end
|
100
|
+
|
101
|
+
def each path, &block
|
102
|
+
base, name = split_path path
|
103
|
+
assert cd(base)[name], :include?, :dir
|
104
|
+
cd(base)[name].each do |relative_name, content|
|
105
|
+
next if relative_name.is_a? Symbol
|
106
|
+
if content[:dir]
|
107
|
+
block.call relative_name, :dir
|
108
|
+
else
|
109
|
+
block.call relative_name, :file
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def efficient_dir_copy from, to
|
115
|
+
from.storage.open_fs do |from_fs|
|
116
|
+
to.storage.open_fs do |to_fs|
|
117
|
+
if from_fs == to_fs
|
118
|
+
for_spec_helper_effective_copy_used
|
119
|
+
|
120
|
+
from_base, from_name = split_path from.path
|
121
|
+
assert cd(from_base)[from_name], :include?, :dir
|
122
|
+
|
123
|
+
to_base, to_name = split_path to.path
|
124
|
+
assert_not cd(to_base), :include?, to_name
|
125
|
+
|
126
|
+
cd(to_base)[to_name] = cd(from_base)[from_name]
|
127
|
+
|
128
|
+
true
|
129
|
+
else
|
130
|
+
false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
def for_spec_helper_effective_copy_used; end
|
136
|
+
|
137
|
+
# def upload_directory from_local_path, to_remote_path
|
138
|
+
# FileUtils.cp_r from_local_path, to_remote_path
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# def download_directory from_remote_path, to_local_path
|
142
|
+
# FileUtils.cp_r from_remote_path, to_local_path
|
143
|
+
# end
|
144
|
+
|
145
|
+
|
146
|
+
#
|
147
|
+
# Other
|
148
|
+
#
|
149
|
+
def local?; true end
|
150
|
+
|
151
|
+
def to_s; 'hash_fs' end
|
152
|
+
|
153
|
+
def tmp &block
|
154
|
+
tmp_dir = "/tmp_#{rand(10**6)}"
|
155
|
+
create_dir tmp_dir
|
156
|
+
if block
|
157
|
+
begin
|
158
|
+
block.call tmp_dir
|
159
|
+
ensure
|
160
|
+
delete_dir tmp_dir
|
161
|
+
end
|
162
|
+
else
|
163
|
+
tmp_dir
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
protected
|
168
|
+
def assert obj, method, arg
|
169
|
+
raise "#{obj} should #{method} #{arg}" unless obj.send method, arg
|
170
|
+
end
|
171
|
+
|
172
|
+
def assert_not obj, method, arg
|
173
|
+
raise "#{obj} should not #{method} #{arg}" if obj.send method, arg
|
174
|
+
end
|
175
|
+
|
176
|
+
def split_path path
|
177
|
+
parts = path[1..-1].split('/')
|
178
|
+
parts.unshift '/'
|
179
|
+
name = parts.pop
|
180
|
+
return parts, name
|
181
|
+
end
|
182
|
+
|
183
|
+
def cd parts
|
184
|
+
current = self
|
185
|
+
iterator = parts.clone
|
186
|
+
while iterator.first
|
187
|
+
current = current[iterator.first]
|
188
|
+
iterator.shift
|
189
|
+
end
|
190
|
+
current
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Vfs
|
4
|
+
module Storages
|
5
|
+
class Local
|
6
|
+
module LocalVfsHelper
|
7
|
+
DEFAULT_BUFFER = 1024*128
|
8
|
+
|
9
|
+
attr_writer :buffer
|
10
|
+
def buffer
|
11
|
+
@buffer || DEFAULT_BUFFER
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# Attributes
|
16
|
+
#
|
17
|
+
def attributes path
|
18
|
+
stat = ::File.stat path
|
19
|
+
attrs = {}
|
20
|
+
attrs[:file] = stat.file?
|
21
|
+
attrs[:dir] = stat.directory?
|
22
|
+
attrs
|
23
|
+
rescue Errno::ENOENT
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_attributes path, attrs
|
28
|
+
raise 'not supported'
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
#
|
33
|
+
# File
|
34
|
+
#
|
35
|
+
def read_file path, &block
|
36
|
+
::File.open path, 'r' do |is|
|
37
|
+
while buff = is.gets(self.buffer || DEFAULT_BUFFER)
|
38
|
+
block.call buff
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_file path, append, &block
|
44
|
+
option = append ? 'a' : 'w'
|
45
|
+
::File.open path, option do |os|
|
46
|
+
writer = -> buff {os.write buff}
|
47
|
+
block.call writer
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def delete_file path
|
52
|
+
::File.delete path
|
53
|
+
end
|
54
|
+
|
55
|
+
# def move_file from, to
|
56
|
+
# FileUtils.mv from, to
|
57
|
+
# end
|
58
|
+
|
59
|
+
|
60
|
+
#
|
61
|
+
# Dir
|
62
|
+
#
|
63
|
+
def create_dir path
|
64
|
+
::Dir.mkdir path
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete_dir path
|
68
|
+
FileUtils.rm_r path
|
69
|
+
end
|
70
|
+
|
71
|
+
def each path, &block
|
72
|
+
::Dir.foreach path do |relative_name|
|
73
|
+
next if relative_name == '.' or relative_name == '..'
|
74
|
+
if ::File.directory? "#{path}/#{relative_name}"
|
75
|
+
block.call relative_name, :dir
|
76
|
+
else
|
77
|
+
block.call relative_name, :file
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def efficient_dir_copy from, to
|
83
|
+
from.storage.open_fs do |from_fs|
|
84
|
+
to.storage.open_fs do |to_fs|
|
85
|
+
if from_fs.local? and to_fs.local?
|
86
|
+
FileUtils.cp_r from.path, to.path
|
87
|
+
true
|
88
|
+
else
|
89
|
+
false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# def move_dir path
|
96
|
+
# raise 'not supported'
|
97
|
+
# end
|
98
|
+
|
99
|
+
# def upload_directory from_local_path, to_remote_path
|
100
|
+
# FileUtils.cp_r from_local_path, to_remote_path
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# def download_directory from_remote_path, to_local_path
|
104
|
+
# FileUtils.cp_r from_remote_path, to_local_path
|
105
|
+
# end
|
106
|
+
|
107
|
+
|
108
|
+
#
|
109
|
+
# Other
|
110
|
+
#
|
111
|
+
def local?; true end
|
112
|
+
|
113
|
+
def tmp &block
|
114
|
+
tmp_dir = "#{::Dir.tmpdir}/#{rand(10**3)}"
|
115
|
+
if block
|
116
|
+
begin
|
117
|
+
create_dir tmp_dir
|
118
|
+
block.call tmp_dir
|
119
|
+
ensure
|
120
|
+
delete_dir tmp_dir
|
121
|
+
end
|
122
|
+
else
|
123
|
+
create_dir tmp_dir
|
124
|
+
tmp_dir
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def to_s; '' end
|
129
|
+
end
|
130
|
+
|
131
|
+
include LocalVfsHelper
|
132
|
+
|
133
|
+
def open_fs &block
|
134
|
+
block.call self
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|