vfs 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|