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 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