virtualfs 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- virtualfs (0.0.1)
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.6)
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.1)
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)
@@ -29,6 +29,10 @@ module VirtualFS
29
29
  end
30
30
  end
31
31
 
32
+ def dotfolders_for(path)
33
+ [VirtualFS::Dir.new(::File.join(path, '.'), self), VirtualFS::Dir.new(::File.join(path, '..'), self)]
34
+ end
35
+
32
36
  def entries(path=nil)
33
37
  # override this
34
38
  raise NotImplementedError
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 @path
25
+ @backend.entries @realpath
13
26
  end
14
27
 
15
28
  def glob(pattern)
16
- @backend.glob pattern, @path
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
- "<#{@backend.class.name} '#{@path}' (dir)>"
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
@@ -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)
@@ -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=nil)
22
- contents = path.nil? ? toplevel_contents : tree_contents(path)
21
+ def entries(path='/')
22
+ path = '/' << path unless path.start_with? '/'
23
+ p = path[1..-1]
23
24
 
24
- map_entries(contents.values, :path) { |item| item.type == 'tree' }
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=nil)
28
- contents = path.nil? ? internal_items : tree_contents(path, true)
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
- item = internal_items.fetch(path)
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.git_data.trees.get(@user, @repo, @branch, :recursive => true).tree.reduce({}) { |hash, item| hash[item.path] = item; hash }
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.git_data.blobs.get(@user, @repo, sha).content
67
+ Base64.decode64 @gh.blobs.get(@user, @repo, sha).content
61
68
  end
62
69
  end
63
70
  end
@@ -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=nil)
15
- p = path || @path
14
+ def entries(path='/')
15
+ p = VirtualFS.realpath(path)
16
16
 
17
- contents = ::Dir.glob(::File.join(p, '*'), ::File::FNM_DOTMATCH)
17
+ contents = ::Dir.entries(::File.join(@path, p)).map { |entry| ::File.join(p, entry) }
18
18
 
19
19
  cache do
20
- # The slice is for removing the '.' and '..' entries
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=nil)
26
- p = path || @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(::Dir.glob(::File.join(p, pattern))) { |path| ::File.directory? path }
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
- StringIO.new( open(::File.join(@path, path), 'r') { |io| io.read } )
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
@@ -1,3 +1,3 @@
1
1
  module VirtualFS
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
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"
@@ -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.2
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-05 00:00:00.000000000 Z
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