vfs 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/Rakefile +2 -2
  2. data/lib/vfs.rb +18 -0
  3. data/lib/vfs/entries/dir.rb +272 -0
  4. data/lib/vfs/entries/entry.rb +127 -0
  5. data/lib/vfs/entries/file.rb +185 -0
  6. data/lib/vfs/entries/universal_entry.rb +24 -0
  7. data/lib/vfs/entry_proxy.rb +38 -0
  8. data/lib/vfs/error.rb +4 -0
  9. data/lib/vfs/integration/string.rb +19 -0
  10. data/lib/vfs/path.rb +125 -0
  11. data/lib/vfs/storages/hash_fs.rb +194 -0
  12. data/lib/vfs/storages/local.rb +138 -0
  13. data/lib/vfs/storages/specification.rb +127 -0
  14. data/lib/vfs/support.rb +2 -0
  15. data/readme.md +110 -14
  16. data/spec/container_spec.rb +31 -0
  17. data/spec/dir_spec.rb +224 -0
  18. data/spec/entry_spec.rb +24 -0
  19. data/spec/file_spec.rb +189 -0
  20. data/spec/path_spec.rb +121 -0
  21. data/spec/spec_helper.rb +2 -9
  22. data/spec/storages/hash_fs_spec.rb +10 -0
  23. data/spec/storages/local_spec.rb +10 -0
  24. data/spec/universal_entry_spec.rb +42 -0
  25. metadata +25 -23
  26. data/lib/old/ssh.rb +0 -11
  27. data/lib/rsh.rb +0 -19
  28. data/lib/rsh/box.rb +0 -182
  29. data/lib/rsh/box/marks.rb +0 -29
  30. data/lib/rsh/drivers/abstract.rb +0 -15
  31. data/lib/rsh/drivers/local.rb +0 -48
  32. data/lib/rsh/drivers/ssh.rb +0 -147
  33. data/lib/rsh/gems.rb +0 -2
  34. data/lib/rsh/support.rb +0 -30
  35. data/spec/abstract_driver.rb +0 -82
  36. data/spec/abstract_driver/dir/dir2/file +0 -0
  37. data/spec/abstract_driver/local_file +0 -1
  38. data/spec/box_spec.rb +0 -109
  39. data/spec/box_spec/dir/dir2/file +0 -0
  40. data/spec/box_spec/local_file +0 -1
  41. data/spec/config.example.yml +0 -4
  42. data/spec/config.yml +0 -5
  43. data/spec/local_driver_spec.rb +0 -9
  44. data/spec/ssh_driver_spec.rb +0 -15
@@ -0,0 +1,127 @@
1
+ # use '$ gem install ruby_ext' to install.
2
+ require 'rspec_ext'
3
+ require 'ruby_ext'
4
+
5
+ shared_examples_for 'vfs storage' do
6
+ before :each do
7
+ @storage.open_fs do |fs|
8
+ @tmp_dir = fs.tmp
9
+ end
10
+ end
11
+
12
+ after :each do
13
+ @storage.open_fs do |fs|
14
+ attrs = fs.attributes(@tmp_dir)
15
+ fs.delete_dir @tmp_dir if attrs && attrs[:dir]
16
+ end
17
+ end
18
+
19
+ it 'should respond to :local?' do
20
+ @storage.open_fs{|fs| fs.should respond_to(:local?)}
21
+ end
22
+
23
+ it 'should have root dir' do
24
+ @storage.open_fs do |fs|
25
+ fs.attributes('/').subset(:file, :dir).should == {file: false, dir: true}
26
+ end
27
+ end
28
+
29
+ describe "files" do
30
+ before :each do
31
+ @remote_file = "#{@tmp_dir}/remote_file"
32
+ end
33
+
34
+ it "file attributes" do
35
+ @storage.open_fs do |fs|
36
+ fs.attributes(@remote_file).should == {}
37
+ fs.write_file(@remote_file, false){|w| w.call 'something'}
38
+ attrs = fs.attributes(@remote_file)
39
+ fs.attributes(@remote_file).subset(:file, :dir).should == {file: true, dir: false}
40
+ end
41
+ end
42
+
43
+ it "read, write & append" do
44
+ @storage.open_fs do |fs|
45
+ fs.write_file(@remote_file, false){|w| w.call 'something'}
46
+ fs.attributes(@remote_file)[:file].should be_true
47
+
48
+ data = ""
49
+ fs.read_file(@remote_file){|buff| data << buff}
50
+ data.should == 'something'
51
+
52
+ # append
53
+ fs.write_file(@remote_file, true){|w| w.call ' another'}
54
+ data = ""
55
+ fs.read_file(@remote_file){|buff| data << buff}
56
+ data.should == 'something another'
57
+ end
58
+ end
59
+
60
+ it "delete_file" do
61
+ @storage.open_fs do |fs|
62
+ fs.write_file(@remote_file, false){|w| w.call 'something'}
63
+ fs.attributes(@remote_file)[:file].should be_true
64
+ fs.delete_file(@remote_file)
65
+ fs.attributes(@remote_file).should == {}
66
+ end
67
+ end
68
+ end
69
+
70
+ describe 'directories' do
71
+ # before :each do
72
+ # @from_local, @remote_path, @to_local = "#{@local_dir}/dir", "#{@tmp_dir}/upload", "#{@local_dir}/download"
73
+ # end
74
+
75
+ before :each do
76
+ @remote_dir = "#{@tmp_dir}/some_dir"
77
+ end
78
+
79
+ it "directory_exist?, create_dir, delete_dir" do
80
+ @storage.open_fs do |fs|
81
+ fs.attributes(@remote_dir).should == {}
82
+ fs.create_dir(@remote_dir)
83
+ fs.attributes(@remote_dir).subset(:file, :dir).should == {file: false, dir: true}
84
+ fs.delete_dir(@remote_dir)
85
+ fs.attributes(@remote_dir).should == {}
86
+ end
87
+ end
88
+
89
+ it 'should delete not-empty directories' do
90
+ @storage.open_fs do |fs|
91
+ fs.create_dir(@remote_dir)
92
+ fs.create_dir("#{@remote_dir}/dir")
93
+ fs.write_file("#{@remote_dir}/dir/file", false){|w| w.call 'something'}
94
+ fs.delete_dir(@remote_dir)
95
+ fs.attributes(@remote_dir).should == {}
96
+ end
97
+ end
98
+
99
+ it 'each' do
100
+ @storage.open_fs do |fs|
101
+ list = {}
102
+ fs.each(@tmp_dir){|path, type| list[path] = type}
103
+ list.should be_empty
104
+
105
+ dir, file = "#{@tmp_dir}/dir", "#{@tmp_dir}/file"
106
+ fs.create_dir(dir)
107
+ fs.write_file(file, false){|w| w.call 'something'}
108
+
109
+ list = {}
110
+ fs.each(@tmp_dir){|path, type| list[path] = type}
111
+ list.should == {'dir' => :dir, 'file' => :file}
112
+ end
113
+ end
114
+
115
+ # it "upload_directory & download_directory" do
116
+ # upload_path_check = "#{@remote_path}/dir2/file"
117
+ # check_attributes upload_path_check, nil
118
+ # fs.upload_directory(@from_local, @remote_path)
119
+ # check_attributes upload_path_check, file: true, dir: false
120
+ #
121
+ # download_path_check = "#{@to_local}/dir2/file"
122
+ # File.exist?(download_path_check).should be_false
123
+ # fs.download_directory(@remote_path, @to_local)
124
+ # File.exist?(download_path_check).should be_true
125
+ # end
126
+ end
127
+ end
@@ -0,0 +1,2 @@
1
+ require 'fileutils'
2
+ require 'set'
data/readme.md CHANGED
@@ -1,21 +1,117 @@
1
- # Rsh - Tiny wrapper over Net::SSH/SFTP
1
+ # Vfs - Virtual File System
2
2
 
3
- Because they are too hard to use and have terrible API design.
3
+ Handy and simple abstraction over any storage that can represent concept of File and Directory (or at least part of it).
4
+ The Vfs for File System Storages is the same as ActiveRecord is for Relational Databases.
4
5
 
5
- box = Rsh::Box.new host: 'webapp.com', ssh: {user: 'root', password: 'secret'}
6
+ Currently, there are following implementations available:
6
7
 
7
- box.upload_directory '/my_project', '/apps/my_project'
8
- box.bash 'nohup /apps/my_project/server_start'
9
-
10
- Honestly my wrapper also not very good. I would like to make API looks like the ['rush'][rush] gem (made by Adam Wiggins)
11
- but it requires a lots of time, maybe I'll do it later.
12
- So, for now it's just a small wrapper to do ssh/io operations not so painfull.
8
+ - local file system
9
+ - remote file system (over ssh)
10
+
11
+ ## Goals
12
+
13
+ - **handy, simple and clean** API.
14
+ - **high performance** - the same as by using low-level storage API, there should be no extra calls **.
15
+ - same API for different storages (Local FS, SSH, Hadoop, or any other , ...).
16
+ - should work **simultaneously with different storages**.
17
+ - small codebase, easy to extend by others.
18
+ - simple storage-driver implementation, easy add new storage types (Hadoop DFS, LDAP, Document Oriented DB, In-Memory, ...).
19
+
20
+ ** all methods should have the same performance as native system calls, except for :move and :rename. Right now they are implemented
21
+ ASAP by using copy+destroy approach, will be fixed as soon as I'll have free time to do it.
22
+
23
+ ## Code samples:
24
+ gem 'vfs' # Virtual File System
25
+ require 'vfs'
26
+
27
+ gem 'vos' # Virtual Operating System
28
+ require 'vos'
29
+
30
+
31
+ # Connections, let's deploy our 'cool_app' project from our local box to remote server
32
+ server = Vfs::Box.new(host: 'cool_app.com', ssh: {user: 'me', password: 'secret'})
33
+ me = '~'.to_dir
34
+
35
+ cool_app = server['apps/cool_app']
36
+ projects = me['projects']
37
+
38
+
39
+ # Working with dirs, copying dir from any source to any destination (local/remote/custom_storage_type)
40
+ projects['cool_app'].copy_to cool_app
41
+
42
+
43
+ # Working with files
44
+ dbc = cool_app.file('config/database.yml') # <= the 'config' dir not exist yet
45
+ dbc.write("user: root\npassword: secret") # <= now the 'database.yml' and parent 'config' has been created
46
+ dbc.content =~ /database/ # => false, we forgot to add the database
47
+ dbc.append("\ndatabase: mysql") # let's do it
48
+
49
+ dbc.update do |content| # and add host info
50
+ content + "\nhost: cool_app.com "
51
+ end
52
+
53
+ projects['cool_app/config/database.yml']. # or just overwrite it with our local dev version
54
+ copy_to! dbc
55
+
56
+ # there are also streaming support (read/write/append), please go to specs for docs
57
+
58
+
59
+ # Checks
60
+ cool_app['config'].exist? # => true
61
+ cool_app.dir('config').exist? # => true
62
+ cool_app.file('config').exist? # => false
13
63
 
64
+ cool_app['config'].dir? # => true
65
+ cool_app['config'].file? # => false
66
+
67
+
68
+ # Navigation
69
+ config = cool_app['config']
70
+ config.parent # => </apps/cool_app>
71
+ config['../..'] # => </>
72
+ config['../..'].dir? # => true
73
+
74
+ cool_app.entries # => list of dirs and files, also support &block
75
+ cool_app.files # => list of files, also support &block
76
+ cool_app.dirs # => list of dirs, also support &block
77
+
78
+
79
+ # For more please go to specs (create/update/move/copy/destroy/...)
80
+
81
+ ## Integration with [Vos][vos] (Virtual Operating System)
82
+
83
+ server['apps/cool_app'].bash 'rails production'
84
+
85
+ For more details please go to [Vos][vos] project page.
86
+
87
+ # Why?
88
+
89
+ To easy my work: with local FS, remote FS (cluster management, deployment automation), and some specific systems like Hadoop DFS.
90
+
91
+ Because the API of standard File/Dir/FileUtils classes are just terrible. And there's the reason for it - the goal of thouse tools
92
+ is to provide 1-to-1 clone of underlying OS API, instead of provididing handy tool.
93
+
94
+ And if you want to use remote FS - things are getting even worse and more complicated (Net::SSH & Net::SFTP use a little
95
+ different API than local FS, and you has to remember all thouse little quirks).
96
+
14
97
  ## TODO
15
98
 
16
- - remove ssh.remote and use only open/close
17
- - introduce Entity/Dir/File (the same as in Rush)
18
- - allow to move files between any Boxes, not only between local and remote.
19
- - add support for moving dirs.
99
+ ### v 0.1 (all done)
100
+
101
+ - Vos: Dir.bash
102
+ - File.append
103
+ - list of entries/files/dirs
104
+ - support for efficient copy for Local and SSH storages
105
+
106
+ ### v 0.2 (not started)
107
+
108
+ - efficient (not copy/destroy) versions of move_to, rename
109
+ - glob search for directories: Dir['**/*.yml']
110
+ - access via attributes and helpers for unix chmod
111
+ - add storages: remote FS over HTTP.
112
+
113
+ ### future
114
+
115
+ - add storages: Hadoop DFS, MongoDB, Amazon S3
20
116
 
21
- [rush]: http://github.com/adamwiggins/rush
117
+ [vos]: http://github.com/alexeypetrushin/vos
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Container' do
4
+ before :each do
5
+ @fs = '/'.to_entry_on(Vfs::Storages::HashFs.new)
6
+ end
7
+
8
+ it "should threat paths as UniversalEntry except it ends with '/'" do
9
+ @fs.should_receive(:entry).with('/a/b')
10
+ @fs['/a/b']
11
+
12
+ @fs.should_receive(:dir).with('/a/b')
13
+ @fs['/a/b/']
14
+ end
15
+
16
+ it '/' do
17
+ @fs[:some_path].should == @fs / :some_path
18
+ end
19
+
20
+ it "UniversalEntry should be wrapped inside of proxy, Dir and File should not" do
21
+ -> {@fs.dir.proxy?}.should raise_error(NoMethodError)
22
+ -> {@fs.file.proxy?}.should raise_error(NoMethodError)
23
+ @fs.entry.proxy?.should be_true
24
+ end
25
+
26
+ it "sometimes it also should inexplicitly guess that path is a Dir instead of UniversalEntry (but still wrap it inside of Proxy)" do
27
+ dir = @fs['/a/..']
28
+ dir.proxy?.should be_true
29
+ dir.should be_a(Vfs::Dir)
30
+ end
31
+ end
data/spec/dir_spec.rb ADDED
@@ -0,0 +1,224 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Dir' do
4
+ before :each do
5
+ @fs = '/'.to_entry_on(Vfs::Storages::HashFs.new)
6
+ @path = @fs['/a/b/c']
7
+ end
8
+
9
+ describe 'existence' do
10
+ it "should check only dirs" do
11
+ @path.should_not exist
12
+ @path.file.create
13
+ @path.should be_file
14
+ @path.dir.should_not exist
15
+ @path.dir.create!
16
+ @path.should be_dir
17
+ @path.dir.should exist
18
+ end
19
+ end
20
+
21
+ it "should not respond to read and write methods" do
22
+ -> {@path.dir.read}.should raise_error(NoMethodError)
23
+ -> {@path.dir.write}.should raise_error(NoMethodError)
24
+ end
25
+
26
+ describe 'create' do
27
+ it 'should be chainable' do
28
+ @path.dir.create.should == @path
29
+ @path.dir.create!.should == @path
30
+ end
31
+
32
+ it 'should create parent dirs if not exists' do
33
+ @path.parent.should_not exist
34
+ @path.dir.create
35
+ @path.should be_dir
36
+ end
37
+
38
+ it 'should silently exit if dir already exist' do
39
+ @path.dir.create
40
+ @path.dir.create
41
+ end
42
+
43
+ it 'should override existing file if override specified' do
44
+ @path.file.create
45
+ @path.should be_file
46
+ -> {@path.dir.create}.should raise_error(Vfs::Error, /exist/)
47
+ @path.dir.create!
48
+ @path.should be_dir
49
+ end
50
+
51
+ it 'should override existing dir if override specified' do
52
+ @path.dir.create
53
+ @path.should be_dir
54
+ @path.dir.create!
55
+ @path.should be_dir
56
+ end
57
+ end
58
+
59
+ describe 'destroying' do
60
+ it "should raise error if it's trying to destroy a file (unless force specified)" do
61
+ @path.file.create
62
+ -> {@path.dir.destroy}.should raise_error(Vfs::Error, /can't destroy File/)
63
+ @path.dir.destroy!
64
+ @path.entry.should_not exist
65
+ end
66
+
67
+ it "shouldn't raise if dir not exist" do
68
+ @path.dir.destroy
69
+ end
70
+
71
+ it 'should destroy recursivelly' do
72
+ dir = @path.dir
73
+ dir.create
74
+ dir.file('file').write 'something'
75
+ dir.dir('dir').create.tap do |dir|
76
+ dir.file('file2').write 'something2'
77
+ end
78
+
79
+ dir.destroy
80
+ dir.should_not exist
81
+ end
82
+
83
+ it 'should be chainable' do
84
+ @path.dir.destroy.should == @path
85
+ @path.dir.destroy!.should == @path
86
+ end
87
+ end
88
+
89
+ describe 'content' do
90
+ before :each do
91
+ @path.dir('dir').create
92
+ @path.dir('dir/another_dir').create
93
+ @path.file('file').create
94
+ end
95
+
96
+ it 'entries' do
97
+ -> {@path['non_existing'].entries}.should raise_error(Vfs::Error, /not exist/)
98
+ @path['non_existing'].entries(bang: false).should == []
99
+ @path.entries.to_set.should be_eql([@path.dir('dir'), @path.file('file')].to_set)
100
+ list = []
101
+ @path.entries{|e| list << e}
102
+ list.to_set.should be_eql([@path.dir('dir'), @path.file('file')].to_set)
103
+ end
104
+
105
+ it 'should raise error if trying :entries on file' do
106
+ @path.file('some_file').create
107
+ -> {@path.dir('some_file').entries}.should raise_error(/File/)
108
+ end
109
+
110
+ it 'files' do
111
+ @path.files.should be_eql([@path.file('file')])
112
+ end
113
+
114
+ it 'dirs' do
115
+ @path.dirs.should be_eql([@path.dir('dir')])
116
+ end
117
+
118
+ it 'has? & include?' do
119
+ @path.include?('dir').should be_true
120
+ @path.include?('dir/another_dir').should be_true
121
+ @path.include?('file').should be_true
122
+ @path.include?('non_existing').should be_false
123
+ end
124
+ end
125
+
126
+ describe 'copying' do
127
+ before :each do
128
+ @from = @path.dir
129
+ @from.create
130
+ @from.file('file').write 'something'
131
+ @from.dir('dir').create.tap do |dir|
132
+ dir.file('file2').write 'something2'
133
+ end
134
+ end
135
+
136
+ it 'should not copy to itself' do
137
+ -> {@from.copy_to @from}.should raise_error(Vfs::Error, /itself/)
138
+ end
139
+
140
+ def check_copy_for to, error_re1, error_re2
141
+ begin
142
+ target = @from.copy_to to
143
+ target['file'].read.should == 'something'
144
+ target['dir/file2'].read.should == 'something2'
145
+ target.should == to
146
+ rescue Exception => e
147
+ raise e unless e.message =~ error_re1
148
+ end
149
+
150
+ @from['dir/file2'].write! 'another'
151
+ -> {@from.copy_to to}.should raise_error(Vfs::Error, error_re2)
152
+ target = @from.copy_to! to
153
+ target['file'].read.should == 'something'
154
+ target['dir/file2'].read.should == 'another'
155
+ end
156
+
157
+ describe 'general copy' do
158
+ before :each do
159
+ # we using here another HashFs storage, to prevent :effective_dir_copy to be used
160
+ @to = '/'.to_entry_on(Vfs::Storages::HashFs.new)['to']
161
+
162
+ @from.storage.should_not_receive(:for_spec_helper_effective_copy_used)
163
+ end
164
+
165
+ it 'should copy to file (and overwrite if forced)' do
166
+ check_copy_for @to.file, /can't copy Dir to File/, /can't copy Dir to File/
167
+ end
168
+
169
+ it 'should copy to dir (and overwrite if forced)' do
170
+ check_copy_for @to.dir, nil, /exist/
171
+ end
172
+
173
+ it 'should copy to UniversalEntry (and overwrite if forced)' do
174
+ check_copy_for @to.entry, nil, /exist/
175
+ end
176
+ end
177
+
178
+ describe 'effective copy' do
179
+ before :each do
180
+ # we using the same HashFs storage, so :effective_dir_copy will be used
181
+ @to = @fs['to']
182
+
183
+ @from.storage.should_receive(:for_spec_helper_effective_copy_used).at_least(1).times
184
+ end
185
+
186
+ it 'should copy to file (and overwrite if forced)' do
187
+ check_copy_for @to.file, /can't copy Dir to File/, /can't copy Dir to File/
188
+ end
189
+
190
+ it 'should copy to dir (and overwrite if forced)' do
191
+ check_copy_for @to.dir, nil, /exist/
192
+ end
193
+
194
+ it 'should copy to UniversalEntry (and overwrite if forced)' do
195
+ check_copy_for @to.entry, nil, /exist/
196
+ end
197
+ end
198
+
199
+ it 'should be chainable' do
200
+ to = @fs['to']
201
+ @from.copy_to(to).should == to
202
+ @from.copy_to!(to).should == to
203
+ end
204
+ end
205
+
206
+ describe 'moving' do
207
+ it 'move_to' do
208
+ from, to = @path.file('from'), @path.file('to')
209
+ from.should_receive(:copy_to).with(to, {})
210
+ from.should_receive(:destroy).with({})
211
+ from.move_to to
212
+
213
+ from.should_receive(:move_to).with(to, override: true)
214
+ from.move_to! to
215
+ end
216
+
217
+ it 'should be chainable' do
218
+ from, to = @path.dir('from').create, @path.dir('to')
219
+ from.move_to(to).should == to
220
+ from.create
221
+ from.move_to!(to).should == to
222
+ end
223
+ end
224
+ end