virtualfs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ *.pstore
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in virtualfs.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ virtualfs (0.0.1)
5
+ github_api (~> 0.8)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.1.3)
11
+ fakefs (0.4.2)
12
+ faraday (0.8.4)
13
+ multipart-post (~> 1.1)
14
+ github_api (0.8.6)
15
+ faraday (~> 0.8.1)
16
+ hashie (~> 1.2.0)
17
+ multi_json (~> 1.4)
18
+ nokogiri (~> 1.5.2)
19
+ oauth2
20
+ hashie (1.2.0)
21
+ httpauth (0.2.0)
22
+ jwt (0.1.5)
23
+ multi_json (>= 1.0)
24
+ multi_json (1.5.0)
25
+ multipart-post (1.1.5)
26
+ nokogiri (1.5.6)
27
+ oauth2 (0.8.0)
28
+ faraday (~> 0.8)
29
+ httpauth (~> 0.1)
30
+ jwt (~> 0.1.4)
31
+ multi_json (~> 1.0)
32
+ rack (~> 1.2)
33
+ rack (1.4.1)
34
+ rspec (2.12.0)
35
+ rspec-core (~> 2.12.0)
36
+ rspec-expectations (~> 2.12.0)
37
+ rspec-mocks (~> 2.12.0)
38
+ rspec-core (2.12.2)
39
+ rspec-expectations (2.12.1)
40
+ diff-lcs (~> 1.1.3)
41
+ rspec-mocks (2.12.1)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ fakefs (~> 0.4)
48
+ rspec (~> 2.12)
49
+ virtualfs!
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Rafał Hirsz
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # VirtualFS
2
+
3
+ This is a library that allows you to access various datastores in a unified way. Local FS and Github support in included, feel free to contibute other backends.
4
+
5
+ The API is designed to be compatible with the standard File and Dir APIs. Currently data access is read-only.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'virtualfs'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install virtualfs
20
+
21
+ ## Usage
22
+
23
+ ### Read a file for a local directory
24
+
25
+ fs = VirtualFS::Local.new :path => '/home/evol/hello'
26
+ file = fs["hello_world"].first # fs[pattern] is an alias for fs.glob(pattern)
27
+
28
+ file.read
29
+
30
+ ### List the root directory of a Github repository
31
+
32
+ fs = VirtualFS::Github.new :user => 'evoL', :repo => 'virtualfs'
33
+ fs.entries
34
+
35
+ ### Find recursively files in subfolders
36
+
37
+ fs = VirtualFS::Github.new :user => 'evoL', :repo => 'virtualfs'
38
+ rb_files = fs.glob('**/*.rb')
39
+
40
+ ### Caching
41
+
42
+ cache = VirtualFS::FileCache.new :filename => 'file.cache', :expires_after => 3600
43
+ fs = VirtualFS::Github.new :user => 'evoL', :repo => 'virtualfs', :cache => cache
44
+
45
+ Available cache providers are `RuntimeCache` and `FileCache`.
46
+
47
+ ## The Anatomy of a Backend
48
+
49
+ A backend is a subclass of `VirtualFS::Backend`. It has to define the following methods:
50
+
51
+ - `#entries` - lists the contents of a directory, like `Dir.entries`. Optionally accepts a `path` parameter, which specifies the path to the directory that will be listed. By default it lists the root directory of the backend.
52
+ - `#glob` - lists files mathing the `pattern`, like `Dir.glob`. Optionally accepts a `path` parameter.
53
+ - `#stream_for` - returns an `IO` object for the file specified by the `path` parameter.
54
+
55
+ The arrays returned by `#entries` and `#glob` are arrays of `VirtualFS::Dir` and `VirtualFS::File` objects, which mimic the API of the respective standard library classes. The `VirtualFS::Backend` class provides the `#map_entries` method for easy creation of those lists within backends.
56
+
57
+ Basic usage:
58
+
59
+ map_entries(paths) { |path| is_directory? path }
60
+
61
+ Instead of an array of strings you can supply an array of any objects, as long as they have a method for getting the path. The default method for getting the path of an object is `#to_s`.
62
+
63
+ map_entries(objects, :get_path) { |obj| obj.is_directory? }
64
+
65
+ Also, it is possible to cache the results of your code in the backend. It's simple:
66
+
67
+ cache { get_your_remote_data }
68
+
69
+ The remote data will then be cached using the user specified cache provider.
70
+
71
+ ## Contributing
72
+
73
+ 1. Fork it
74
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
75
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
76
+ 4. Push to the branch (`git push origin my-new-feature`)
77
+ 5. Create new Pull Request
78
+
79
+ ## Copyright
80
+
81
+ (c) 2013 Rafał Hirsz. This work is licensed under the terms of the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,49 @@
1
+ require 'virtualfs/dir'
2
+ require 'virtualfs/file'
3
+
4
+ module VirtualFS
5
+ class NullCache
6
+ def cache(key)
7
+ yield
8
+ end
9
+ end
10
+
11
+ class Backend
12
+ def initialize(opts={})
13
+ @cache = opts[:cache] || NullCache.new
14
+ end
15
+
16
+ def cache(&proc)
17
+ local_variable_values = eval('local_variables.map { |var| eval(var.to_s) }', proc.binding)
18
+ key = caller[0] << local_variable_values.inspect
19
+ @cache.cache(key, &proc)
20
+ end
21
+
22
+ def map_entries(paths, method=:to_s, &dir_criteria)
23
+ paths.map do |path|
24
+ if dir_criteria.call(path)
25
+ VirtualFS::Dir.new(path.send(method), self)
26
+ else
27
+ VirtualFS::File.new(path.send(method), self)
28
+ end
29
+ end
30
+ end
31
+
32
+ def entries(path=nil)
33
+ # override this
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def glob(pattern, path=nil)
38
+ # override this
39
+ raise NotImplementedError
40
+ end
41
+
42
+ def stream_for(path)
43
+ # override this
44
+ raise NotImplementedError
45
+ end
46
+
47
+ alias_method :[], :glob
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ module VirtualFS
2
+ class Dir
3
+ include Enumerable
4
+ attr_reader :path
5
+
6
+ def initialize(path, backend)
7
+ @path = path
8
+ @backend = backend
9
+ end
10
+
11
+ def entries
12
+ @backend.entries @path
13
+ end
14
+
15
+ def glob(pattern)
16
+ @backend.glob pattern, @path
17
+ end
18
+
19
+ def each(&block)
20
+ entries.each(&block)
21
+ end
22
+
23
+ def inspect
24
+ "<#{@backend.class.name} '#{@path}' (dir)>"
25
+ end
26
+
27
+ alias_method :[], :glob
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ module VirtualFS
2
+ class File
3
+ attr_reader :path
4
+
5
+ def initialize(path, backend)
6
+ @path = path
7
+ @backend = backend
8
+ end
9
+
10
+ def method_missing(method, *args)
11
+ @stream ||= @backend.stream_for(@path)
12
+ @stream.send(method, *args)
13
+ end
14
+
15
+ def inspect
16
+ "<#{@backend.class.name} '#{@path}'>"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ require 'pstore'
2
+
3
+ module VirtualFS
4
+ class FileCache
5
+ attr_accessor :expires_after
6
+
7
+ def initialize(opts={})
8
+ @expires_after = opts[:expires_after] || -1
9
+
10
+ @store = PStore.new(opts[:filename] || 'virtualfs.pstore')
11
+ end
12
+
13
+ def cache(key, &proc)
14
+ access_time = Time.now
15
+
16
+ @store.transaction do
17
+ data = @store[key] || {:time => access_time, :content => nil}
18
+
19
+ # Invalidate cache after some time
20
+ if (@expires_after > -1) && ((access_time - data[:time]) > @expires_after)
21
+ data = {:time => access_time, :content => nil}
22
+ end
23
+
24
+ data[:content] ||= yield
25
+ @store[key] = data
26
+
27
+ data[:content]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,62 @@
1
+ require 'virtualfs/backend'
2
+
3
+ require 'base64'
4
+ require 'stringio'
5
+ require 'github_api'
6
+
7
+ module VirtualFS
8
+ class Github < Backend
9
+ attr_reader :user, :repo, :branch
10
+
11
+ def initialize(opts={})
12
+ super opts
13
+
14
+ @gh = ::Github.new
15
+
16
+ @user = opts.fetch(:user)
17
+ @repo = opts.fetch(:repo)
18
+ @branch = opts.fetch(:branch, 'master')
19
+ end
20
+
21
+ def entries(path=nil)
22
+ contents = path.nil? ? toplevel_contents : tree_contents(path)
23
+
24
+ map_entries(contents.values, :path) { |item| item.type == 'tree' }
25
+ end
26
+
27
+ def glob(pattern, path=nil)
28
+ contents = path.nil? ? internal_items : tree_contents(path, true)
29
+
30
+ map_entries(contents.select { |path, _| ::File.fnmatch(pattern, path, ::File::FNM_PATHNAME) }.values, :path) { |item| item.type == 'tree' }
31
+ end
32
+
33
+ def stream_for(path)
34
+ item = internal_items.fetch(path)
35
+ raise 'Not a file' unless item.type == 'blob'
36
+
37
+ StringIO.new internal_blob(item.sha)
38
+ end
39
+
40
+ private
41
+
42
+ def internal_items
43
+ cache do
44
+ @gh.git_data.trees.get(@user, @repo, @branch, :recursive => true).tree.reduce({}) { |hash, item| hash[item.path] = item; hash }
45
+ end
46
+ end
47
+
48
+ def tree_contents(tree, recursive=false)
49
+ internal_items.select { |item, _| item.start_with?("#{tree}/") && (recursive || !item.slice((tree.length+1)..-1).include?('/')) }
50
+ end
51
+
52
+ def toplevel_contents
53
+ internal_items.reject { |item, _| item.include? '/' }
54
+ end
55
+
56
+ def internal_blob(sha)
57
+ cache do
58
+ Base64.decode64 @gh.git_data.blobs.get(@user, @repo, sha).content
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,39 @@
1
+ require 'virtualfs/backend'
2
+ require 'virtualfs/dir'
3
+ require 'virtualfs/file'
4
+ require 'stringio'
5
+
6
+ module VirtualFS
7
+ class Local < Backend
8
+ def initialize(opts={})
9
+ super opts
10
+
11
+ @path = opts.fetch(:path)
12
+ end
13
+
14
+ def entries(path=nil)
15
+ p = path || @path
16
+
17
+ contents = ::Dir.glob(::File.join(p, '*'), ::File::FNM_DOTMATCH)
18
+
19
+ cache do
20
+ # The slice is for removing the '.' and '..' entries
21
+ map_entries(contents.slice(2, contents.length)) { |path| ::File.directory? path }
22
+ end
23
+ end
24
+
25
+ def glob(pattern, path=nil)
26
+ p = path || @path
27
+
28
+ cache do
29
+ map_entries(::Dir.glob(::File.join(p, pattern))) { |path| ::File.directory? path }
30
+ end
31
+ end
32
+
33
+ def stream_for(path)
34
+ StringIO.new( open(::File.join(@path, path), 'r') { |io| io.read } )
35
+ end
36
+
37
+ alias_method :[], :glob
38
+ end
39
+ end
@@ -0,0 +1,25 @@
1
+ module VirtualFS
2
+ class RuntimeCache
3
+ attr_accessor :expires_after
4
+
5
+ def initialize(opts={})
6
+ @expires_after = opts[:expires_after] || -1
7
+
8
+ @store = {}
9
+ @times = {}
10
+ end
11
+
12
+ def cache(key, &proc)
13
+ access_time = Time.now
14
+
15
+ # Invalidate cache after some time
16
+ if (@expires_after > -1) && ((access_time - (@times[key] || access_time)) > @expires_after)
17
+ @times[key] = access_time
18
+ @store[key] = nil
19
+ end
20
+
21
+ @times[key] ||= access_time
22
+ @store[key] ||= yield
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module VirtualFS
2
+ VERSION = "0.0.1"
3
+ end
data/lib/virtualfs.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "virtualfs/version"
2
+
3
+ # Backends
4
+ require "virtualfs/local"
5
+ require "virtualfs/github"
6
+
7
+ # Caches
8
+ require "virtualfs/runtime_cache"
9
+ require "virtualfs/file_cache"
data/virtualfs.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/virtualfs/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Rafał Hirsz"]
6
+ gem.email = ["rafal@hirsz.co"]
7
+ gem.summary = %q{Allows accessing remote datastores in a unified way.}
8
+ gem.homepage = "https://github.com/evoL/virtualfs"
9
+
10
+ gem.files = `git ls-files`.split($\)
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.name = "virtualfs"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = VirtualFS::VERSION
16
+
17
+ gem.add_dependency 'github_api', '~> 0.8'
18
+
19
+ gem.add_development_dependency 'rspec', '~> 2.12'
20
+ gem.add_development_dependency 'fakefs', '~> 0.4'
21
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: virtualfs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rafał Hirsz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: github_api
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '0.8'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '0.8'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.12'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.12'
46
+ - !ruby/object:Gem::Dependency
47
+ name: fakefs
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '0.4'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.4'
62
+ description:
63
+ email:
64
+ - rafal@hirsz.co
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - Gemfile.lock
72
+ - LICENSE
73
+ - README.md
74
+ - Rakefile
75
+ - lib/virtualfs.rb
76
+ - lib/virtualfs/backend.rb
77
+ - lib/virtualfs/dir.rb
78
+ - lib/virtualfs/file.rb
79
+ - lib/virtualfs/file_cache.rb
80
+ - lib/virtualfs/github.rb
81
+ - lib/virtualfs/local.rb
82
+ - lib/virtualfs/runtime_cache.rb
83
+ - lib/virtualfs/version.rb
84
+ - virtualfs.gemspec
85
+ homepage: https://github.com/evoL/virtualfs
86
+ licenses: []
87
+ post_install_message:
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 1.8.24
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Allows accessing remote datastores in a unified way.
109
+ test_files: []