vfs 0.3.15 → 0.4.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 +1 -0
- data/lib/vfs.rb +5 -4
- data/lib/vfs/drivers/local.rb +179 -0
- data/lib/vfs/drivers/specification.rb +169 -0
- data/lib/vfs/entries/dir.rb +28 -57
- data/lib/vfs/entries/entry.rb +39 -21
- data/lib/vfs/entries/file.rb +20 -73
- data/lib/vfs/entries/universal_entry.rb +8 -8
- data/lib/vfs/integration.rb +30 -0
- data/lib/vfs/vfs.rb +2 -2
- data/readme.md +1 -1
- data/spec/container_spec.rb +12 -11
- data/spec/dir_spec.rb +44 -44
- data/spec/entry_spec.rb +6 -6
- data/spec/file_spec.rb +30 -46
- data/spec/misc_spec.rb +19 -0
- data/spec/spec_helper.rb +8 -8
- data/spec/storages/local_spec.rb +14 -14
- data/spec/universal_entry_spec.rb +32 -10
- metadata +6 -6
- data/lib/vfs/integration/string.rb +0 -20
- data/lib/vfs/storages/local.rb +0 -183
- data/lib/vfs/storages/specification.rb +0 -161
- data/lib/vfs/support.rb +0 -2
data/Rakefile
CHANGED
data/lib/vfs.rb
CHANGED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Vfs
|
4
|
+
module Drivers
|
5
|
+
class Local
|
6
|
+
class Writer
|
7
|
+
def initialize out; @out = out end
|
8
|
+
|
9
|
+
def write data
|
10
|
+
@out.write data
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
DEFAULT_BUFFER = 1000 * 1024
|
15
|
+
|
16
|
+
def initialize options = {}
|
17
|
+
options = options.clone
|
18
|
+
@root = options.delete(:root) || ''
|
19
|
+
raise "invalid options #{options}" unless options.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def open &block
|
23
|
+
block.call self if block
|
24
|
+
end
|
25
|
+
def close; end
|
26
|
+
|
27
|
+
attr_writer :buffer
|
28
|
+
def buffer
|
29
|
+
@buffer || DEFAULT_BUFFER
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Attributes
|
34
|
+
#
|
35
|
+
def attributes path
|
36
|
+
path = with_root path
|
37
|
+
|
38
|
+
stat = ::File.stat path
|
39
|
+
attrs = {}
|
40
|
+
attrs[:file] = !!stat.file?
|
41
|
+
attrs[:dir] = !!stat.directory?
|
42
|
+
|
43
|
+
# attributes special for file system
|
44
|
+
attrs[:created_at] = stat.ctime
|
45
|
+
attrs[:updated_at] = stat.mtime
|
46
|
+
attrs[:size] = stat.size if attrs[:file]
|
47
|
+
attrs
|
48
|
+
rescue Errno::ENOTDIR
|
49
|
+
nil
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_attributes path, attrs
|
55
|
+
# TODO2 set attributes
|
56
|
+
not_implemented
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
#
|
61
|
+
# File
|
62
|
+
#
|
63
|
+
def read_file path, &block
|
64
|
+
path = with_root path
|
65
|
+
::File.open path, 'r' do |is|
|
66
|
+
while buff = is.gets(self.buffer || DEFAULT_BUFFER)
|
67
|
+
block.call buff
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def write_file original_path, append, &block
|
73
|
+
path = with_root original_path
|
74
|
+
|
75
|
+
option = append ? 'a' : 'w'
|
76
|
+
::File.open path, "#{option}b" do |out|
|
77
|
+
block.call Writer.new(out)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def delete_file path
|
82
|
+
path = with_root path
|
83
|
+
::File.delete path
|
84
|
+
end
|
85
|
+
|
86
|
+
# def move_file from, to
|
87
|
+
# FileUtils.mv from, to
|
88
|
+
# end
|
89
|
+
|
90
|
+
|
91
|
+
#
|
92
|
+
# Dir
|
93
|
+
#
|
94
|
+
def create_dir path
|
95
|
+
path = with_root path
|
96
|
+
::Dir.mkdir path
|
97
|
+
end
|
98
|
+
|
99
|
+
def delete_dir original_path
|
100
|
+
path = with_root original_path
|
101
|
+
::FileUtils.rm_r path
|
102
|
+
end
|
103
|
+
|
104
|
+
def each_entry path, query, &block
|
105
|
+
path = with_root path
|
106
|
+
|
107
|
+
if query
|
108
|
+
path_with_trailing_slash = path == '/' ? path : "#{path}/"
|
109
|
+
::Dir["#{path_with_trailing_slash}#{query}"].each do |absolute_path|
|
110
|
+
name = absolute_path.sub path_with_trailing_slash, ''
|
111
|
+
block.call name, ->{::File.directory?(absolute_path) ? :dir : :file}
|
112
|
+
# if ::File.directory? absolute_path
|
113
|
+
# block.call relative_path, :dir
|
114
|
+
# else
|
115
|
+
# block.call relative_path, :file
|
116
|
+
# end
|
117
|
+
end
|
118
|
+
else
|
119
|
+
::Dir.foreach path do |name|
|
120
|
+
next if name == '.' or name == '..'
|
121
|
+
block.call name, ->{::File.directory?("#{path}/#{name}") ? :dir : :file}
|
122
|
+
# if ::File.directory? "#{path}/#{relative_name}"
|
123
|
+
# block.call relative_name, :dir
|
124
|
+
# else
|
125
|
+
# block.call relative_name, :file
|
126
|
+
# end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# def efficient_dir_copy from, to, override
|
132
|
+
# return false if override # FileUtils.cp_r doesn't support this behaviour
|
133
|
+
#
|
134
|
+
# from.driver.open_fs do |from_fs|
|
135
|
+
# to.driver.open_fs do |to_fs|
|
136
|
+
# if from_fs.local? and to_fs.local?
|
137
|
+
# FileUtils.cp_r from.path, to.path
|
138
|
+
# true
|
139
|
+
# else
|
140
|
+
# false
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
# end
|
144
|
+
# end
|
145
|
+
|
146
|
+
#
|
147
|
+
# Other
|
148
|
+
#
|
149
|
+
def local?; true end
|
150
|
+
|
151
|
+
def tmp &block
|
152
|
+
path = "/tmp/#{rand(10**6)}"
|
153
|
+
# tmp_dir = "#{::Dir.tmpdir}/#{rand(10**6)}"
|
154
|
+
if block
|
155
|
+
begin
|
156
|
+
::FileUtils.mkdir_p with_root(path)
|
157
|
+
block.call path
|
158
|
+
ensure
|
159
|
+
::FileUtils.rm_r with_root(path) if ::File.exist? with_root(path)
|
160
|
+
end
|
161
|
+
else
|
162
|
+
::FileUtils.mkdir_p with_root(path)
|
163
|
+
path
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def to_s; '' end
|
168
|
+
|
169
|
+
protected
|
170
|
+
def root
|
171
|
+
@root || raise('root not defined!')
|
172
|
+
end
|
173
|
+
|
174
|
+
def with_root path
|
175
|
+
path == '/' ? root : root + path
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'rspec_ext'
|
2
|
+
require 'ruby_ext'
|
3
|
+
|
4
|
+
shared_examples_for 'vfs driver basic' do
|
5
|
+
it 'should respond to :local?' do
|
6
|
+
@driver.should respond_to(:local?)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should provide open method" do
|
10
|
+
@driver.open
|
11
|
+
@driver.open{'result'}.should == 'result'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
shared_examples_for 'vfs driver attributes basic' do
|
16
|
+
it 'should have root dir' do
|
17
|
+
attrs = @driver.attributes('/')
|
18
|
+
attrs[:dir].should be_true
|
19
|
+
attrs[:file].should be_false
|
20
|
+
end
|
21
|
+
|
22
|
+
it "attributes should return nil if there's no entry" do
|
23
|
+
@driver.attributes('/non_existing_entry').should be_nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
shared_examples_for 'vfs driver files' do
|
28
|
+
it "file attributes" do
|
29
|
+
@driver.attributes('/file').should be_nil
|
30
|
+
|
31
|
+
@driver.write_file('/file', false){|w| w.write 'something'}
|
32
|
+
attrs = @driver.attributes('/file')
|
33
|
+
attrs[:file].should be_true
|
34
|
+
attrs[:dir].should be_false
|
35
|
+
end
|
36
|
+
|
37
|
+
it "read, write, append" do
|
38
|
+
# write
|
39
|
+
@driver.write_file('/file', false){|w| w.write 'something'}
|
40
|
+
@driver.attributes('/file')[:file].should == true
|
41
|
+
|
42
|
+
# read
|
43
|
+
data = ""
|
44
|
+
@driver.read_file('/file'){|buff| data << buff}
|
45
|
+
data.should == 'something'
|
46
|
+
|
47
|
+
# append
|
48
|
+
@driver.write_file('/file', true){|w| w.write ' another'}
|
49
|
+
data = ""
|
50
|
+
@driver.read_file('/file'){|buff| data << buff}
|
51
|
+
data.should == 'something another'
|
52
|
+
end
|
53
|
+
|
54
|
+
it "delete_file" do
|
55
|
+
@driver.write_file('/file', false){|w| w.write 'something'}
|
56
|
+
@driver.attributes('/file')[:file].should be_true
|
57
|
+
@driver.delete_file('/file')
|
58
|
+
@driver.attributes('/file').should be_nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
shared_examples_for 'vfs driver full attributes for files' do
|
63
|
+
it "attributes for files" do
|
64
|
+
@driver.write_file('/file', false){|w| w.write 'something'}
|
65
|
+
attrs = @driver.attributes('/file')
|
66
|
+
attrs[:file].should be_true
|
67
|
+
attrs[:dir].should be_false
|
68
|
+
attrs[:created_at].class.should == Time
|
69
|
+
attrs[:updated_at].class.should == Time
|
70
|
+
attrs[:size].should == 9
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
shared_examples_for 'vfs driver dirs' do
|
75
|
+
it "directory crud" do
|
76
|
+
@driver.attributes('/dir').should be_nil
|
77
|
+
|
78
|
+
@driver.create_dir('/dir')
|
79
|
+
attrs = @driver.attributes('/dir')
|
80
|
+
attrs[:file].should be_false
|
81
|
+
attrs[:dir].should be_true
|
82
|
+
|
83
|
+
@driver.delete_dir('/dir')
|
84
|
+
@driver.attributes('/dir').should be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should delete not-empty directories' do
|
88
|
+
@driver.create_dir('/dir')
|
89
|
+
@driver.create_dir('/dir/dir2')
|
90
|
+
@driver.write_file('/dir/dir2/file', false){|w| w.write 'something'}
|
91
|
+
@driver.attributes('/dir').should_not be_nil
|
92
|
+
|
93
|
+
@driver.delete_dir('/dir')
|
94
|
+
@driver.attributes('/dir').should be_nil
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'each' do
|
98
|
+
-> {@driver.each_entry('/not_existing_dir', nil){|path, type| list[path] = type}}.should raise_error
|
99
|
+
|
100
|
+
@driver.create_dir '/dir'
|
101
|
+
@driver.create_dir('/dir/dir2')
|
102
|
+
@driver.write_file('/dir/file', false){|w| w.write 'something'}
|
103
|
+
|
104
|
+
list = {}
|
105
|
+
@driver.each_entry '/dir', nil do |path, type|
|
106
|
+
type = type.call if type.is_a? Proc
|
107
|
+
list[path] = type
|
108
|
+
end
|
109
|
+
|
110
|
+
list.should == {'dir2' => :dir, 'file' => :file}
|
111
|
+
end
|
112
|
+
|
113
|
+
# it "upload_directory & download_directory" do
|
114
|
+
# upload_path_check = "#{@remote_path}/dir2/file"
|
115
|
+
# check_attributes upload_path_check, nil
|
116
|
+
# @driver.upload_directory(@from_local, @remote_path)
|
117
|
+
# check_attributes upload_path_check, file: true, dir: false
|
118
|
+
#
|
119
|
+
# download_path_check = "#{@to_local}/dir2/file"
|
120
|
+
# File.exist?(download_path_check).should be_false
|
121
|
+
# @driver.download_directory(@remote_path, @to_local)
|
122
|
+
# File.exist?(download_path_check).should be_true
|
123
|
+
# end
|
124
|
+
end
|
125
|
+
|
126
|
+
shared_examples_for 'vfs driver query' do
|
127
|
+
it 'each with query' do
|
128
|
+
@driver.create_dir '/dir'
|
129
|
+
@driver.create_dir('/dir/dir_a')
|
130
|
+
@driver.create_dir('/dir/dir_b')
|
131
|
+
@driver.write_file('/dir/file_a', false){|w| w.write 'something'}
|
132
|
+
|
133
|
+
list = {}
|
134
|
+
@driver.each_entry '/dir', '*_a' do |path, type|
|
135
|
+
type = type.call if type.is_a? Proc
|
136
|
+
list[path] = type
|
137
|
+
end
|
138
|
+
|
139
|
+
list.should == {'dir_a' => :dir, 'file_a' => :file}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
shared_examples_for 'vfs driver full attributes for dirs' do
|
144
|
+
it "attributes for dirs" do
|
145
|
+
@driver.create_dir('/dir')
|
146
|
+
attrs = @driver.attributes('/dir')
|
147
|
+
attrs[:file].should be_false
|
148
|
+
attrs[:dir].should be_true
|
149
|
+
attrs[:created_at].class.should == Time
|
150
|
+
attrs[:updated_at].class.should == Time
|
151
|
+
attrs.should_not include(:size)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
shared_examples_for 'vfs driver tmp dir' do
|
156
|
+
it "tmp dir" do
|
157
|
+
dir = @driver.tmp
|
158
|
+
@driver.attributes(dir).should_not be_nil
|
159
|
+
@driver.delete_dir dir
|
160
|
+
@driver.attributes(dir).should be_nil
|
161
|
+
|
162
|
+
dir = nil
|
163
|
+
@driver.tmp do |tmp_dir|
|
164
|
+
dir = tmp_dir
|
165
|
+
@driver.attributes(dir).should_not be_nil
|
166
|
+
end
|
167
|
+
@driver.attributes(dir).should be_nil
|
168
|
+
end
|
169
|
+
end
|
data/lib/vfs/entries/dir.rb
CHANGED
@@ -25,20 +25,16 @@ module Vfs
|
|
25
25
|
# CRUD
|
26
26
|
#
|
27
27
|
def create options = {}
|
28
|
-
|
28
|
+
driver.open do
|
29
29
|
try = 0
|
30
30
|
begin
|
31
31
|
try += 1
|
32
|
-
|
32
|
+
driver.create_dir path
|
33
33
|
rescue StandardError => error
|
34
34
|
entry = self.entry
|
35
35
|
attrs = entry.get
|
36
36
|
if attrs and attrs[:file] #entry.exist?
|
37
|
-
|
38
|
-
entry.destroy
|
39
|
-
else
|
40
|
-
raise Error, "entry #{self} already exist!"
|
41
|
-
end
|
37
|
+
entry.destroy
|
42
38
|
elsif attrs and attrs[:dir]
|
43
39
|
# dir already exist, no need to recreate it
|
44
40
|
return self
|
@@ -57,36 +53,9 @@ module Vfs
|
|
57
53
|
end
|
58
54
|
self
|
59
55
|
end
|
60
|
-
def create! options = {}
|
61
|
-
options[:override] = true
|
62
|
-
create options
|
63
|
-
end
|
64
56
|
|
65
57
|
def destroy options = {}
|
66
|
-
|
67
|
-
begin
|
68
|
-
fs.delete_dir path
|
69
|
-
rescue StandardError => e
|
70
|
-
attrs = get
|
71
|
-
if attrs and attrs[:file]
|
72
|
-
if options[:force]
|
73
|
-
file.destroy
|
74
|
-
else
|
75
|
-
raise Error, "can't destroy File #{dir} (You are trying to destroy it as if it's a Dir)"
|
76
|
-
end
|
77
|
-
elsif attrs and attrs[:dir]
|
78
|
-
# unknown internal error
|
79
|
-
raise e
|
80
|
-
else
|
81
|
-
# do nothing, file already not exist
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
self
|
86
|
-
end
|
87
|
-
def destroy! options = {}
|
88
|
-
options[:force] = true
|
89
|
-
destroy options
|
58
|
+
destroy_entry :dir, :file
|
90
59
|
end
|
91
60
|
|
92
61
|
|
@@ -98,19 +67,26 @@ module Vfs
|
|
98
67
|
options = args.last.is_a?(Hash) ? args.pop : {}
|
99
68
|
query = args.first
|
100
69
|
options[:bang] = true unless options.include? :bang
|
70
|
+
filter = options[:filter]
|
71
|
+
type_required = options[:type]
|
101
72
|
|
102
|
-
|
73
|
+
driver.open do
|
103
74
|
begin
|
104
75
|
list = []
|
105
|
-
# query option is optional and supported only for some
|
106
|
-
|
107
|
-
|
76
|
+
# query option is optional and supported only for some drivers (local driver for example)
|
77
|
+
driver.each_entry path, query do |name, type|
|
78
|
+
# for performance reasons some drivers may return the type of entry as
|
79
|
+
# optionally evaluated callback.
|
80
|
+
type = type.call if (filter or type_required) and type.is_a?(Proc)
|
81
|
+
|
82
|
+
next if filter and (filter != type)
|
83
|
+
|
108
84
|
entry = if type == :dir
|
109
85
|
dir(name)
|
110
86
|
elsif type == :file
|
111
87
|
file(name)
|
112
88
|
else
|
113
|
-
|
89
|
+
entry(name)
|
114
90
|
end
|
115
91
|
block ? block.call(entry) : (list << entry)
|
116
92
|
end
|
@@ -120,9 +96,10 @@ module Vfs
|
|
120
96
|
if attrs and attrs[:file]
|
121
97
|
raise Error, "can't query entries on File ('#{self}')!"
|
122
98
|
elsif attrs and attrs[:dir]
|
123
|
-
# unknown error
|
99
|
+
# some unknown error
|
124
100
|
raise error
|
125
101
|
else
|
102
|
+
# TODO2 remove :bang
|
126
103
|
raise Error, "'#{self}' not exist!" if options[:bang]
|
127
104
|
[]
|
128
105
|
end
|
@@ -153,7 +130,10 @@ module Vfs
|
|
153
130
|
alias_method :has?, :include?
|
154
131
|
|
155
132
|
def empty?
|
156
|
-
|
133
|
+
catch :break do
|
134
|
+
entries{|e| throw :break, false}
|
135
|
+
true
|
136
|
+
end
|
157
137
|
end
|
158
138
|
|
159
139
|
|
@@ -167,7 +147,6 @@ module Vfs
|
|
167
147
|
raise Error, "you can't copy to itself" if self == to
|
168
148
|
|
169
149
|
target = if to.is_a? File
|
170
|
-
raise Error, "can't copy Dir to File ('#{self}')!" unless options[:override]
|
171
150
|
to.dir
|
172
151
|
elsif to.is_a? Dir
|
173
152
|
to.dir #(name)
|
@@ -183,20 +162,12 @@ module Vfs
|
|
183
162
|
|
184
163
|
target
|
185
164
|
end
|
186
|
-
def copy_to! to, options = {}
|
187
|
-
options[:override] = true
|
188
|
-
copy_to to, options
|
189
|
-
end
|
190
165
|
|
191
166
|
def move_to to, options = {}
|
192
167
|
copy_to to, options
|
193
168
|
destroy options
|
194
169
|
to
|
195
170
|
end
|
196
|
-
def move_to! to, options = {}
|
197
|
-
options[:override] = true
|
198
|
-
move_to to, options
|
199
|
-
end
|
200
171
|
|
201
172
|
# class << self
|
202
173
|
# attr_accessor :dont_use_efficient_dir_copy
|
@@ -205,7 +176,7 @@ module Vfs
|
|
205
176
|
protected
|
206
177
|
def unefficient_dir_copy to, options
|
207
178
|
to.create options
|
208
|
-
entries options do |e|
|
179
|
+
entries options.merge(type: true) do |e|
|
209
180
|
if e.is_a? Dir
|
210
181
|
e.copy_to to.dir(e.name), options
|
211
182
|
elsif e.is_a? File
|
@@ -220,7 +191,7 @@ module Vfs
|
|
220
191
|
# def efficient_dir_copy to, options
|
221
192
|
# return false if self.class.dont_use_efficient_dir_copy
|
222
193
|
#
|
223
|
-
#
|
194
|
+
# driver.open do
|
224
195
|
# try = 0
|
225
196
|
# begin
|
226
197
|
# try += 1
|
@@ -271,11 +242,11 @@ module Vfs
|
|
271
242
|
# end
|
272
243
|
#
|
273
244
|
# def self.efficient_dir_copy from, to, override
|
274
|
-
# from.
|
275
|
-
#
|
245
|
+
# from.driver.open{
|
246
|
+
# driver.respond_to?(:efficient_dir_copy) and driver.efficient_dir_copy(from, to, override)
|
276
247
|
# } or
|
277
|
-
# to.
|
278
|
-
#
|
248
|
+
# to.driver.open{
|
249
|
+
# driver.respond_to?(:efficient_dir_copy) and driver.efficient_dir_copy(from, to, override)
|
279
250
|
# }
|
280
251
|
# end
|
281
252
|
end
|