io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,38 @@
1
+ module Vfs
2
+ class << self
3
+ def default_driver
4
+ ::Vfs::Drivers::Local.new
5
+ end
6
+
7
+ def to_entry
8
+ '/'.to_entry
9
+ end
10
+
11
+ def to_file
12
+ to_entry.file
13
+ end
14
+
15
+ def to_dir
16
+ to_entry.dir
17
+ end
18
+
19
+ # def [] path
20
+ # to_entry[path]
21
+ # end
22
+ # alias_method :/, :[]
23
+
24
+ %w(
25
+ entry dir file
26
+ entries dirs files
27
+ [] /
28
+ tmp
29
+ ).each do |m|
30
+ script = <<-RUBY
31
+ def #{m} *a, &b
32
+ to_entry.#{m} *a, &b
33
+ end
34
+ RUBY
35
+ eval script, binding, __FILE__, __LINE__
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,119 @@
1
+ # Vfs - Virtual File System
2
+
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 is kinda the same as ActiveRecord is for Relational Databases.
5
+
6
+ Currently, there are following implementations available:
7
+
8
+ - local file system
9
+ - remote file system (over ssh)
10
+
11
+ ## Goals
12
+
13
+ - **handy, simple and clean** API.
14
+ - same API for different storages (Local FS, SSH, Hadoop, or any other , ...).
15
+ - should work **simultaneously with different storages**.
16
+ - small codebase, easy to extend by others.
17
+ - simple storage-driver implementation, easy add new storage types (Hadoop DFS, LDAP, Document Oriented DB, In-Memory, ...).
18
+
19
+ **Performance**:
20
+
21
+ - sometimes there's extra call to check if file or dir exist before overriding it
22
+ - copy: right now it doesn't use FileUtils.cp_r, it walks on the directory tree and copy each entry individually, so it's probably a little slover.
23
+ - right now :move and :rename implemented ASAP by copy & destroy, will be fixed as soon as I'll have time to do it.
24
+
25
+ ## Installation
26
+
27
+ ``` bash
28
+ $ gem install vfs
29
+ $ gem install vos
30
+ ```
31
+
32
+ ## Code samples:
33
+
34
+ ``` ruby
35
+ require 'vfs' # Virtual File System
36
+ require 'vos' # Virtual Operating System
37
+
38
+ # Connections, let's deploy our 'cool_app' project from our local box to remote server
39
+
40
+ server = Box.new('cool_app.com') # it will use id_rsa, or You can add {user: 'me', password: 'secret'}
41
+ me = '~'.to_dir # handy shortcut for local FS
42
+
43
+ deploy_dir = server['apps/cool_app']
44
+ projects = me['projects']
45
+
46
+ # Working with dirs, copying dir from any source to any destination (local/remote/custom_storage_type)
47
+
48
+ projects['cool_app'].copy_to deploy_dir
49
+
50
+ # Working with files
51
+
52
+ dbc = deploy_dir.file('config/database.yml') # <= the 'config' dir not exist yet
53
+ dbc.write("user: root\npassword: secret") # <= now the 'database.yml' and parent 'config' has been created
54
+ dbc.read =~ /database/ # => false, we forgot to add the database
55
+ dbc.append("\ndatabase: mysql") # let's do it
56
+
57
+ dbc.update do |content| # and add host info
58
+ content + "\nhost: cool_app.com "
59
+ end
60
+
61
+ projects['cool_app/config/database.yml']. # or just overwrite it with our local dev version
62
+ copy_to! dbc
63
+ ```
64
+
65
+ There are also streaming support (read/write/append) with &block, please go to specs for details
66
+
67
+ # Checks
68
+
69
+ ``` ruby
70
+ deploy_dir['config'].exist? # => true
71
+ deploy_dir.dir('config').exist? # => true
72
+ deploy_dir.file('config').exist? # => false
73
+
74
+ deploy_dir['config'].dir? # => true
75
+ deploy_dir['config'].file? # => false
76
+ ```
77
+
78
+ # Navigation
79
+
80
+ ``` ruby
81
+ config = deploy_dir['config']
82
+ config.parent # => </apps/cool_app>
83
+ config['../..'] # => </>
84
+ config['../..'].dir? # => true
85
+
86
+ deploy_dir.entries # => list of dirs and files, also support &block
87
+ deploy_dir.files # => list of files, also support &block
88
+ deploy_dir.dirs # => list of dirs, also support &block
89
+ ```
90
+
91
+ For more please go to specs (create/update/move/copy/destroy/...)
92
+
93
+ ## Integration with [Vos][vos] (Virtual Operating System)
94
+
95
+ ```ruby
96
+ server['apps/cool_app'].bash 'rails production'
97
+ ```
98
+
99
+ For more details please go to [Vos][vos] project page.
100
+ Or checkout configuration I use to control my production servers [My Cluster][my_cluster] in conjunction with small
101
+ configuration tool [Cluster Management][cluster_management].
102
+
103
+ # Why?
104
+
105
+ To easy my work: with local FS, remote FS (cluster management, deployment automation), and some specific systems like Hadoop DFS.
106
+
107
+ Because the API of standard File/Dir/FileUtils classes are just terrible. And there's the reason for it - the goal of thouse tools
108
+ is to provide 1-to-1 clone of underlying OS API, instead of provididing handy tool.
109
+
110
+ And if you want to use remote FS - things are getting even worse and more complicated (Net::SSH & Net::SFTP use a little
111
+ different API than local FS, and you has to remember all thouse little quirks).
112
+
113
+ ## License
114
+
115
+ Copyright (c) Alexey Petrushin http://petrush.in, released under the MIT license.
116
+
117
+ [vos]: http://github.com/alexeypetrushin/vos
118
+ [cluster_management]: http://github.com/alexeypetrushin/cluster_management
119
+ [my_cluster]: http://github.com/alexeypetrushin/my_cluster
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Container' do
4
+ with_test_dir
5
+
6
+ it "should threat paths as UniversalEntry except it ends with '/'" do
7
+ test_dir.should_receive(:entry).with('tmp/a/b')
8
+ test_dir['tmp/a/b']
9
+
10
+ test_dir.should_receive(:dir).with('tmp/a/b')
11
+ test_dir['tmp/a/b/']
12
+ end
13
+
14
+ it '/' do
15
+ test_dir[:some_path].should == test_dir / :some_path
16
+ test_dir[:some_path][:another_path].should == test_dir / :some_path / :another_path
17
+ end
18
+
19
+ it "UniversalEntry should be wrapped inside of proxy, Dir and File should not" do
20
+ -> {test_dir.dir.proxy?}.should raise_error(NoMethodError)
21
+ -> {test_dir.file.proxy?}.should raise_error(NoMethodError)
22
+ test_dir.entry.proxy?.should be_true
23
+ end
24
+
25
+ it "sometimes it also should inexplicitly guess that path is a Dir instead of UniversalEntry (but still wrap it inside of Proxy)" do
26
+
27
+ dir = test_dir['tmp/a/..']
28
+ dir.proxy?.should be_true
29
+ dir.should be_a(Vfs::Dir)
30
+ end
31
+ end
@@ -0,0 +1,249 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Dir' do
4
+ with_test_dir
5
+
6
+ before do
7
+ @path = test_dir['a/b/c']
8
+ end
9
+
10
+ describe 'existence' do
11
+ it "should check only dirs" do
12
+ @path.should_not exist
13
+ @path.file.create
14
+ @path.should be_file
15
+ @path.dir.should_not exist
16
+ @path.dir.create
17
+ @path.should be_dir
18
+ @path.dir.should exist
19
+ end
20
+ end
21
+
22
+ it "should not respond to read and write methods" do
23
+ -> {@path.dir.read}.should raise_error(NoMethodError)
24
+ -> {@path.dir.write}.should raise_error(NoMethodError)
25
+ end
26
+
27
+ describe 'create' do
28
+ it 'should be chainable' do
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' do
44
+ @path.file.create
45
+ @path.should be_file
46
+ @path.dir.create
47
+ @path.should be_dir
48
+ end
49
+
50
+ it 'should not override existing dir with content' do
51
+ dir = @path.dir
52
+ dir.create
53
+ file = dir.file :file
54
+ file.create
55
+ file.should exist
56
+
57
+ dir.create
58
+ file.should exist
59
+ end
60
+ end
61
+
62
+ describe 'destroying' do
63
+ it "should destroy a file" do
64
+ @path.file.create
65
+ @path.dir.destroy
66
+ @path.entry.should_not exist
67
+ end
68
+
69
+ it "shouldn't raise if dir not exist" do
70
+ @path.dir.destroy
71
+ end
72
+
73
+ it 'should destroy recursivelly' do
74
+ dir = @path.dir
75
+ dir.create
76
+ dir.file(:file).write 'something'
77
+ dir.dir(:dir).create.tap do |dir|
78
+ dir.file(:file2).write 'something2'
79
+ end
80
+
81
+ dir.destroy
82
+ dir.should_not exist
83
+ end
84
+
85
+ it 'should be chainable' do
86
+ @path.dir.destroy.should == @path
87
+ end
88
+ end
89
+
90
+ describe 'entries, files, dirs' do
91
+ before do
92
+ @path.dir('dir').create
93
+ @path.dir('dir/another_dir').create
94
+ @path.file('file').create
95
+ end
96
+
97
+ it 'entries' do
98
+ -> {@path['non_existing'].entries}.should raise_error(Vfs::Error, /not exist/)
99
+ @path['non_existing'].entries(bang: false).should == []
100
+ @path.entries.to_set.should be_eql([@path.entry('dir'), @path.entry('file')].to_set)
101
+ list = []
102
+ @path.entries{|e| list << e}
103
+ list.to_set.should be_eql([@path.entry('dir'), @path.entry('file')].to_set)
104
+ end
105
+
106
+ it 'entries with type' do
107
+ @path.entries(type: true).to_set.should be_eql([@path.dir('dir'), @path.file('file')].to_set)
108
+ end
109
+
110
+ it "glob search support" do
111
+ @path.dir('dir_a').create
112
+ @path.file('file_a').create
113
+ @path.dir('dir_b').create
114
+ @path.entries('*_a').collect(&:name).sort.should == %w(dir_a file_a)
115
+ end
116
+
117
+ it 'should raise error if trying :entries on file' do
118
+ @path.file('some_file').create
119
+ -> {@path.dir('some_file').entries}.should raise_error(/File/)
120
+ end
121
+
122
+ it 'files' do
123
+ @path.files.should be_eql([@path.file('file')])
124
+ end
125
+
126
+ it 'dirs' do
127
+ @path.dirs.should be_eql([@path.dir('dir')])
128
+ end
129
+
130
+ it 'has? & include?' do
131
+ @path.include?('dir').should be_true
132
+ @path.include?('dir/another_dir').should be_true
133
+ @path.include?('file').should be_true
134
+ @path.include?('non_existing').should be_false
135
+ end
136
+
137
+ it 'empty?' do
138
+ @path.empty?.should be_false
139
+ @path.dir('empty_dir').create.empty?.should be_true
140
+ end
141
+ end
142
+
143
+ describe 'copying' do
144
+ before do
145
+ @from = @path.dir
146
+ @from.create
147
+ @from.file('file').write 'something'
148
+ @from.dir('dir').create.tap do |dir|
149
+ dir.file('file2').write 'something2'
150
+ end
151
+ end
152
+
153
+ it 'should not copy to itself' do
154
+ -> {@from.copy_to @from}.should raise_error(Vfs::Error, /itself/)
155
+ end
156
+
157
+ shared_examples_for 'copy_to behavior' do
158
+ it 'should copy to file and overwrite it' do
159
+ @from.copy_to @to.file
160
+ @to['file'].read.should == 'something'
161
+ end
162
+
163
+ it 'should override files' do
164
+ @from.copy_to @to
165
+
166
+ @from['dir/file2'].write 'another'
167
+ @from.copy_to @to
168
+ @to['dir/file2'].read.should == 'another'
169
+ end
170
+
171
+ it 'should copy to UniversalEntry (and overwrite)' do
172
+ @from.copy_to @to.entry
173
+
174
+ @from.copy_to @to.entry
175
+ @to['file'].read.should == 'something'
176
+ end
177
+
178
+ it "shouldn't delete existing content of directory" do
179
+ @to.dir.create
180
+ @to.file('existing_file').write 'existing_content'
181
+ @to.dir('existing_dir').create
182
+ @to.file('dir/existing_file2').write 'existing_content2'
183
+
184
+ @from.copy_to @to
185
+ # copied files
186
+ @to['file'].read.should == 'something'
187
+ @to['dir/file2'].read.should == 'something2'
188
+ # shouldn't delete already existing files
189
+ @to.file('existing_file').read.should == 'existing_content'
190
+ @to.dir('existing_dir').should exist
191
+ @to.file('dir/existing_file2').read.should == 'existing_content2'
192
+ end
193
+
194
+ it 'should be chainable' do
195
+ @from.copy_to(@to).should == @to
196
+ end
197
+
198
+ it "should override without deleting other files" do
199
+ @from.copy_to(@to).should == @to
200
+ @to.file('other_file').write 'other'
201
+
202
+ @from.copy_to(@to).should == @to
203
+ @to.file('other_file').read.should == 'other'
204
+ end
205
+
206
+ it "should raise error if try to copy file as dir" do
207
+ dir = @from.dir 'file'
208
+ dir.file?.should be_true
209
+ -> {dir.copy_to @to}.should raise_error(Vfs::Error)
210
+ end
211
+ end
212
+
213
+ describe 'general copy' do
214
+ it_should_behave_like 'copy_to behavior'
215
+
216
+ before do
217
+ # prevenging usage of :efficient_dir_copy
218
+ # Vfs::Dir.dont_use_efficient_dir_copy = true
219
+
220
+ @to = test_dir['to']
221
+ end
222
+ # after do
223
+ # Vfs::Dir.dont_use_efficient_dir_copy = false
224
+ # end
225
+ end
226
+
227
+ # describe 'effective copy' do
228
+ # it_should_behave_like 'copy_to behavior'
229
+ #
230
+ # before do
231
+ # @to = test_dir['to']
232
+ # end
233
+ # end
234
+ end
235
+
236
+ describe 'moving' do
237
+ it 'move_to' do
238
+ from, to = @path.file('from'), @path.file('to')
239
+ from.should_receive(:copy_to).with(to)
240
+ from.should_receive(:destroy).with()
241
+ from.move_to to
242
+ end
243
+
244
+ it 'should be chainable' do
245
+ from, to = @path.dir('from').create, @path.dir('to')
246
+ from.move_to(to).should == to
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Entry' do
4
+ with_test_dir
5
+
6
+ before do
7
+ @path = test_dir['a/b/c']
8
+ end
9
+
10
+ it "name" do
11
+ @path.name.should == 'c'
12
+ end
13
+
14
+ it "string integration" do
15
+ '/'.to_entry.path.should == '/'
16
+ 'a'.to_entry.path.should == "./a"
17
+ end
18
+
19
+ it 'tmp' do
20
+ tmp = test_dir.tmp
21
+ tmp.should be_dir
22
+ tmp.destroy
23
+
24
+ tmp = nil
25
+ test_dir.tmp do |path|
26
+ tmp = path
27
+ tmp.should be_dir
28
+ end
29
+ tmp.should_not exist
30
+ end
31
+
32
+ it 'should respond to local?' do
33
+ test_dir.should respond_to(:local?)
34
+ end
35
+
36
+ it 'created_at, updated_at, size' do
37
+ file = test_dir.file('file').write 'data'
38
+ file.created_at.class.should == Time
39
+ file.updated_at.class.should == Time
40
+ file.size.should == 4
41
+ end
42
+ end