virtualfs 0.0.2 → 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/Gemfile.lock +3 -3
- data/lib/virtualfs/backend.rb +4 -0
- data/lib/virtualfs/dir.rb +23 -4
- data/lib/virtualfs/file.rb +9 -1
- data/lib/virtualfs/github.rb +17 -10
- data/lib/virtualfs/local.rb +13 -10
- data/lib/virtualfs/version.rb +1 -1
- data/lib/virtualfs.rb +20 -0
- data/spec/local_spec.rb +120 -0
- metadata +5 -3
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
virtualfs (0.0.
|
4
|
+
virtualfs (0.0.2)
|
5
5
|
github_api (~> 0.8)
|
6
6
|
|
7
7
|
GEM
|
@@ -11,7 +11,7 @@ GEM
|
|
11
11
|
fakefs (0.4.2)
|
12
12
|
faraday (0.8.4)
|
13
13
|
multipart-post (~> 1.1)
|
14
|
-
github_api (0.8.
|
14
|
+
github_api (0.8.7)
|
15
15
|
faraday (~> 0.8.1)
|
16
16
|
hashie (~> 1.2.0)
|
17
17
|
multi_json (~> 1.4)
|
@@ -30,7 +30,7 @@ GEM
|
|
30
30
|
jwt (~> 0.1.4)
|
31
31
|
multi_json (~> 1.0)
|
32
32
|
rack (~> 1.2)
|
33
|
-
rack (1.4.
|
33
|
+
rack (1.4.4)
|
34
34
|
rspec (2.12.0)
|
35
35
|
rspec-core (~> 2.12.0)
|
36
36
|
rspec-expectations (~> 2.12.0)
|
data/lib/virtualfs/backend.rb
CHANGED
data/lib/virtualfs/dir.rb
CHANGED
@@ -1,19 +1,32 @@
|
|
1
1
|
module VirtualFS
|
2
2
|
class Dir
|
3
3
|
include Enumerable
|
4
|
-
attr_reader :path
|
5
4
|
|
6
5
|
def initialize(path, backend)
|
7
6
|
@path = path
|
7
|
+
@realpath = VirtualFS.realpath(path)
|
8
8
|
@backend = backend
|
9
9
|
end
|
10
10
|
|
11
|
+
def path
|
12
|
+
@realpath
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
name = @path.rpartition('/').last
|
17
|
+
name.empty? ? '/' : name
|
18
|
+
end
|
19
|
+
|
20
|
+
def directory?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
11
24
|
def entries
|
12
|
-
@backend.entries @
|
25
|
+
@backend.entries @realpath
|
13
26
|
end
|
14
27
|
|
15
28
|
def glob(pattern)
|
16
|
-
@backend.glob pattern, @
|
29
|
+
@backend.glob pattern, @realpath
|
17
30
|
end
|
18
31
|
|
19
32
|
def each(&block)
|
@@ -21,7 +34,13 @@ module VirtualFS
|
|
21
34
|
end
|
22
35
|
|
23
36
|
def inspect
|
24
|
-
|
37
|
+
if ['.','..'].include? name
|
38
|
+
p = @path
|
39
|
+
else
|
40
|
+
p = @realpath
|
41
|
+
end
|
42
|
+
|
43
|
+
"<#{@backend.class.name} '#{p}' (dir)>"
|
25
44
|
end
|
26
45
|
|
27
46
|
alias_method :[], :glob
|
data/lib/virtualfs/file.rb
CHANGED
@@ -3,10 +3,18 @@ module VirtualFS
|
|
3
3
|
attr_reader :path
|
4
4
|
|
5
5
|
def initialize(path, backend)
|
6
|
-
@path = path
|
6
|
+
@path = VirtualFS.realpath(path)
|
7
7
|
@backend = backend
|
8
8
|
end
|
9
9
|
|
10
|
+
def name
|
11
|
+
@path.rpartition('/').last
|
12
|
+
end
|
13
|
+
|
14
|
+
def directory?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
10
18
|
def method_missing(method, *args)
|
11
19
|
@stream ||= @backend.stream_for(@path)
|
12
20
|
@stream.send(method, *args)
|
data/lib/virtualfs/github.rb
CHANGED
@@ -11,27 +11,34 @@ module VirtualFS
|
|
11
11
|
def initialize(opts={})
|
12
12
|
super opts
|
13
13
|
|
14
|
-
@gh = ::Github.new
|
15
|
-
|
16
14
|
@user = opts.fetch(:user)
|
17
15
|
@repo = opts.fetch(:repo)
|
18
16
|
@branch = opts.fetch(:branch, 'master')
|
17
|
+
|
18
|
+
@gh = ::Github::GitData.new(opts)
|
19
19
|
end
|
20
20
|
|
21
|
-
def entries(path=
|
22
|
-
|
21
|
+
def entries(path='/')
|
22
|
+
path = '/' << path unless path.start_with? '/'
|
23
|
+
p = path[1..-1]
|
23
24
|
|
24
|
-
|
25
|
+
contents = path == '/' ? toplevel_contents : tree_contents(p)
|
26
|
+
dotfolders_for(path) + map_entries(contents.values, :path) { |item| item.type == 'tree' }
|
25
27
|
end
|
26
28
|
|
27
|
-
def glob(pattern, path=
|
28
|
-
|
29
|
+
def glob(pattern, path='/')
|
30
|
+
path = '/' << path unless path.start_with? '/'
|
31
|
+
p = path[1..-1]
|
29
32
|
|
33
|
+
contents = path == '/' ? internal_items : tree_contents(p, true)
|
30
34
|
map_entries(contents.select { |path, _| ::File.fnmatch(pattern, path, ::File::FNM_PATHNAME) }.values, :path) { |item| item.type == 'tree' }
|
31
35
|
end
|
32
36
|
|
33
37
|
def stream_for(path)
|
34
|
-
|
38
|
+
path = '/' << path unless path.start_with? '/'
|
39
|
+
p = path[1..-1]
|
40
|
+
|
41
|
+
item = internal_items.fetch(p)
|
35
42
|
raise 'Not a file' unless item.type == 'blob'
|
36
43
|
|
37
44
|
StringIO.new internal_blob(item.sha)
|
@@ -43,7 +50,7 @@ module VirtualFS
|
|
43
50
|
|
44
51
|
def internal_items
|
45
52
|
cache do
|
46
|
-
@gh.
|
53
|
+
@gh.trees.get(@user, @repo, @branch, :recursive => true).tree.reduce({}) { |hash, item| hash[item.path] = item; hash }
|
47
54
|
end
|
48
55
|
end
|
49
56
|
|
@@ -57,7 +64,7 @@ module VirtualFS
|
|
57
64
|
|
58
65
|
def internal_blob(sha)
|
59
66
|
cache do
|
60
|
-
Base64.decode64 @gh.
|
67
|
+
Base64.decode64 @gh.blobs.get(@user, @repo, sha).content
|
61
68
|
end
|
62
69
|
end
|
63
70
|
end
|
data/lib/virtualfs/local.rb
CHANGED
@@ -8,30 +8,33 @@ module VirtualFS
|
|
8
8
|
def initialize(opts={})
|
9
9
|
super opts
|
10
10
|
|
11
|
-
@path = opts.fetch(:path)
|
11
|
+
@path = ::File.realpath(opts.fetch(:path))
|
12
12
|
end
|
13
13
|
|
14
|
-
def entries(path=
|
15
|
-
p = path
|
14
|
+
def entries(path='/')
|
15
|
+
p = VirtualFS.realpath(path)
|
16
16
|
|
17
|
-
contents = ::Dir.
|
17
|
+
contents = ::Dir.entries(::File.join(@path, p)).map { |entry| ::File.join(p, entry) }
|
18
18
|
|
19
19
|
cache do
|
20
|
-
|
21
|
-
map_entries(contents.slice(2, contents.length)) { |path| ::File.directory? path }
|
20
|
+
map_entries(contents) { |path| ::File.directory?(::File.join(@path, path)) }
|
22
21
|
end
|
23
22
|
end
|
24
23
|
|
25
|
-
def glob(pattern, path=
|
26
|
-
p = path
|
24
|
+
def glob(pattern, path='/')
|
25
|
+
p = VirtualFS.realpath(path)
|
26
|
+
|
27
|
+
contents = ::Dir.glob(::File.join(@path, p, pattern)).map { |entry| entry.sub(@path, '') }
|
27
28
|
|
28
29
|
cache do
|
29
|
-
map_entries(
|
30
|
+
map_entries(contents) { |path| ::File.directory?(::File.join(@path, path)) }
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
34
|
def stream_for(path)
|
34
|
-
|
35
|
+
path = VirtualFS.realpath(path)
|
36
|
+
|
37
|
+
StringIO.new( ::File.open(::File.join(@path, path), 'r') { |io| io.read } )
|
35
38
|
end
|
36
39
|
|
37
40
|
alias_method :[], :glob
|
data/lib/virtualfs/version.rb
CHANGED
data/lib/virtualfs.rb
CHANGED
@@ -1,5 +1,25 @@
|
|
1
1
|
require "virtualfs/version"
|
2
2
|
|
3
|
+
module VirtualFS
|
4
|
+
@@SELF_RX = /(\/|^)\.(\/|$)/
|
5
|
+
@@PARENT_RX = /([^\/.]+\/|^\/?)\.\.(\/|$)/
|
6
|
+
|
7
|
+
def self.realpath(path)
|
8
|
+
return nil if path.nil?
|
9
|
+
|
10
|
+
# Remove . dirs
|
11
|
+
p = path.gsub @@SELF_RX, '\2'
|
12
|
+
|
13
|
+
# Remove .. dirs
|
14
|
+
while p =~ @@PARENT_RX
|
15
|
+
p.gsub! @@PARENT_RX, ''
|
16
|
+
end
|
17
|
+
|
18
|
+
# Keep a slash at the beginning, remove at the end
|
19
|
+
'/' << p.sub(%r{^/+}, '').chomp('/')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
3
23
|
# Backends
|
4
24
|
require "virtualfs/local"
|
5
25
|
require "virtualfs/github"
|
data/spec/local_spec.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'virtualfs'
|
2
|
+
require 'fakefs/safe'
|
3
|
+
|
4
|
+
class FakeFS::File
|
5
|
+
def self.realpath(p)
|
6
|
+
RealFile.realpath(p)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe VirtualFS::Local do
|
11
|
+
let(:fs) { VirtualFS::Local.new :path => '/' }
|
12
|
+
|
13
|
+
context 'when the directory is empty' do
|
14
|
+
before :all do
|
15
|
+
FakeFS.activate!
|
16
|
+
FakeFS::FileSystem.clear
|
17
|
+
end
|
18
|
+
after :all do
|
19
|
+
FakeFS.deactivate!
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'contains only the . and .. entries' do
|
23
|
+
fs.entries.map(&:name).should == ['.', '..']
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'glob is empty' do
|
27
|
+
fs.glob('*').should be_empty
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when the directory is not empty' do
|
32
|
+
before :all do
|
33
|
+
FakeFS.activate!
|
34
|
+
FakeFS::FileSystem.clear
|
35
|
+
|
36
|
+
FileUtils.mkdir_p '/a/b'
|
37
|
+
FileUtils.mkdir_p '/b'
|
38
|
+
FileUtils.touch '/content.txt'
|
39
|
+
FileUtils.touch '/test.txt'
|
40
|
+
FileUtils.touch '/a/test.txt'
|
41
|
+
FileUtils.touch '/b/test.txt'
|
42
|
+
FileUtils.touch '/a/b/test.txt'
|
43
|
+
|
44
|
+
File.open('/content.txt', "w") do |file|
|
45
|
+
file.puts 'Line 1'
|
46
|
+
file.puts 'Line 2'
|
47
|
+
file.puts 'Line 3'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
after :all do
|
51
|
+
FakeFS.deactivate!
|
52
|
+
end
|
53
|
+
|
54
|
+
it '#[] works exactly like #glob' do
|
55
|
+
fs['*'].map(&:path).should == fs.glob('*').map(&:path)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'passing the exact filename returns a list of 1 element' do
|
59
|
+
result = fs['test.txt']
|
60
|
+
|
61
|
+
result.should be_instance_of(Array)
|
62
|
+
result.length.should eq(1)
|
63
|
+
result.first.name.should == 'test.txt'
|
64
|
+
end
|
65
|
+
|
66
|
+
it '. points to the same directory' do
|
67
|
+
fs['.'].first.path.should eq('/')
|
68
|
+
fs['a'].first.glob('.').first.path.should == '/a'
|
69
|
+
end
|
70
|
+
|
71
|
+
it '.. on root level point to the same directory' do
|
72
|
+
fs['..'].first.path.should == '/'
|
73
|
+
end
|
74
|
+
|
75
|
+
it '.. points to the parent directory when not in root directory' do
|
76
|
+
fs['a'].first.glob('..').first.path.should == '/'
|
77
|
+
end
|
78
|
+
|
79
|
+
it '#entries lists the top level' do
|
80
|
+
names = fs.entries.map(&:name)
|
81
|
+
|
82
|
+
names.should include('.')
|
83
|
+
names.should include('..')
|
84
|
+
names.should include('a')
|
85
|
+
names.should include('b')
|
86
|
+
names.should include('content.txt')
|
87
|
+
names.should include('test.txt')
|
88
|
+
end
|
89
|
+
|
90
|
+
it '#glob does not list . and ..' do
|
91
|
+
names = fs['*'].map(&:name)
|
92
|
+
|
93
|
+
names.should_not include('.')
|
94
|
+
names.should_not include('..')
|
95
|
+
end
|
96
|
+
|
97
|
+
it '#glob supports recursive patterns' do
|
98
|
+
paths = fs['**/*.txt'].map(&:path)
|
99
|
+
|
100
|
+
paths.should include('/a/test.txt')
|
101
|
+
paths.should include('/b/test.txt')
|
102
|
+
paths.should include('/a/b/test.txt')
|
103
|
+
end
|
104
|
+
|
105
|
+
it '#glob returns an empty list when looking for a nonexistent file' do
|
106
|
+
fs['nonexistent.txt'].should be_empty
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'reopens the stream every time' do
|
110
|
+
fs['content.txt'].first.readline.should eq("Line 1\n")
|
111
|
+
fs['content.txt'].first.readline.should eq("Line 1\n")
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'does not reopen the stream when assigned to a variable' do
|
115
|
+
s = fs['content.txt'].first
|
116
|
+
s.readline.should eq("Line 1\n")
|
117
|
+
s.readline.should eq("Line 2\n")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: virtualfs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: github_api
|
@@ -81,6 +81,7 @@ files:
|
|
81
81
|
- lib/virtualfs/local.rb
|
82
82
|
- lib/virtualfs/runtime_cache.rb
|
83
83
|
- lib/virtualfs/version.rb
|
84
|
+
- spec/local_spec.rb
|
84
85
|
- virtualfs.gemspec
|
85
86
|
homepage: https://github.com/evoL/virtualfs
|
86
87
|
licenses: []
|
@@ -106,4 +107,5 @@ rubygems_version: 1.8.24
|
|
106
107
|
signing_key:
|
107
108
|
specification_version: 3
|
108
109
|
summary: Allows accessing remote datastores in a unified way.
|
109
|
-
test_files:
|
110
|
+
test_files:
|
111
|
+
- spec/local_spec.rb
|