bfs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 40e2210b5438bc6bcbdb01204c8a573b674a80f94fdb5dee0cab48b989f77c5a
4
+ data.tar.gz: 558aab1337ec9959c8eac6c6d13c9b2fbbd2692243a1fbd0d3d5ebe9b4ef7c21
5
+ SHA512:
6
+ metadata.gz: e7047002ef910561427ddd5048f52a1460e1a15ca9752ae4497f0988fcdf0117fe17aa797b9c0946d231b4a58e62e94fb8d980ea327cc82323246e174fa5af0f
7
+ data.tar.gz: ff34edc0f19c435a1f520750beae893934c1921330fdaa3f105231d1082f749588c47e295b2891986c7820d21e97a3be7799060b0a067a4b979cc1cf39fd7069
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'bfs'
3
+ s.version = File.read(File.expand_path('../.version', __dir__)).strip
4
+ s.platform = Gem::Platform::RUBY
5
+
6
+ s.licenses = ['Apache-2.0']
7
+ s.summary = 'Multi-platform cloud bucket adapter'
8
+ s.description = 'Minimalist abstraction for bucket storage'
9
+
10
+ s.authors = ['Dimitrij Denissenko']
11
+ s.email = 'dimitrij@blacksquaremedia.com'
12
+ s.homepage = 'https://github.com/bsm/bfs.rb'
13
+
14
+ s.executables = []
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- spec/*`.split("\n")
17
+ s.require_paths = ['lib']
18
+ s.required_ruby_version = '>= 2.2.0'
19
+ end
@@ -0,0 +1,22 @@
1
+ require 'uri'
2
+
3
+ module BFS
4
+ FileInfo = Struct.new(:path, :size, :mtime)
5
+
6
+ def self.register(scheme, &resolver)
7
+ @registry ||= {}
8
+ @registry[scheme] = resolver
9
+ end
10
+
11
+ def self.resolve(url)
12
+ url = URI.parse(url) unless url.is_a?(::URI)
13
+ rsl = @registry[url.scheme]
14
+ raise ArgumentError, "Unable to resolve #{url}, scheme #{url.scheme} is not registered" unless rsl
15
+
16
+ rsl.call(url)
17
+ end
18
+ end
19
+
20
+ require 'bfs/helpers'
21
+ require 'bfs/bucket'
22
+ require 'bfs/errors'
@@ -0,0 +1,7 @@
1
+ module BFS
2
+ module Bucket
3
+ autoload :Abstract, 'bfs/bucket/abstract'
4
+ autoload :FS, 'bfs/bucket/fs'
5
+ autoload :InMem, 'bfs/bucket/in_mem'
6
+ end
7
+ end
@@ -0,0 +1,64 @@
1
+ require 'bfs'
2
+
3
+ module BFS
4
+ module Bucket
5
+ class Abstract
6
+ # Lists the contents of a bucket using a glob pattern
7
+ def ls(_pattern='**', _opts={})
8
+ raise 'not implemented'
9
+ end
10
+
11
+ # Info returns the info for a single file
12
+ def info(_path, _opts={})
13
+ raise 'not implemented'
14
+ end
15
+
16
+ # Creates a new file and opens it for writing
17
+ def create(_path, _opts={})
18
+ raise 'not implemented'
19
+ end
20
+
21
+ # Opens an existing file for reading
22
+ # May raise BFS::FileNotFound
23
+ def open(_path, _opts={})
24
+ raise 'not implemented'
25
+ end
26
+
27
+ # Deletes a file.
28
+ def rm(_path, _opts={})
29
+ raise 'not implemented'
30
+ end
31
+
32
+ # Shortcut method to read the contents of a file into memory
33
+ def read(path, opts={})
34
+ open(path, opts, &:read)
35
+ end
36
+
37
+ # Shortcut method to write data to path
38
+ def write(path, data, opts={})
39
+ create(path, opts) {|f| f.write data }
40
+ end
41
+
42
+ # Copies src to dst
43
+ def cp(src, dst, opts={})
44
+ open(src, opts) do |r|
45
+ create(dst, opts) do |w|
46
+ IO.copy_stream(r, w)
47
+ end
48
+ end
49
+ end
50
+
51
+ # Moves src to dst
52
+ def mv(src, dst, _opts={})
53
+ cp(src, dst)
54
+ rm(src)
55
+ end
56
+
57
+ protected
58
+
59
+ def norm_path(path)
60
+ path.gsub(File::SEPARATOR, '/').sub(%r{^/+}, '')
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,79 @@
1
+ require 'bfs'
2
+ require 'fileutils'
3
+ require 'pathname'
4
+
5
+ module BFS
6
+ module Bucket
7
+ # FS buckets are operating on the file system
8
+ class FS < Abstract
9
+ def initialize(root, _opts={})
10
+ @root = Pathname.new(root.to_s)
11
+ @prefix = "#{@root}/"
12
+ end
13
+
14
+ # Lists the contents of a bucket using a glob pattern
15
+ def ls(pattern='**/*', _opts={})
16
+ Pathname.glob(@root.join(pattern)).select(&:file?).map do |name|
17
+ name.to_s.sub(@prefix, '')
18
+ end
19
+ end
20
+
21
+ # Info returns the info for a single file
22
+ def info(path, _opts={})
23
+ name = @root.join(norm_path(path))
24
+ path = name.to_s.sub(@prefix, '')
25
+ BFS::FileInfo.new(path, name.size, name.mtime)
26
+ rescue Errno::ENOENT
27
+ raise BFS::FileNotFound, path
28
+ end
29
+
30
+ # Creates a new file and opens it for writing
31
+ def create(path, _opts={}, &block)
32
+ name = @root.join(norm_path(path))
33
+ FileUtils.mkdir_p(name.dirname.to_s)
34
+
35
+ temp = BFS::TempWriter.new(name) {|t| FileUtils.mv t, name.to_s }
36
+ return temp unless block
37
+
38
+ begin
39
+ yield temp
40
+ ensure
41
+ temp.close
42
+ end
43
+ end
44
+
45
+ # Opens an existing file for reading
46
+ def open(path, opts={}, &block)
47
+ path = norm_path(path)
48
+ name = @root.join(path)
49
+ name.open('r', opts, &block)
50
+ rescue Errno::ENOENT
51
+ raise BFS::FileNotFound, path
52
+ end
53
+
54
+ # Deletes a file.
55
+ def rm(path, _opts={})
56
+ name = @root.join(norm_path(path))
57
+ FileUtils.rm_f name.to_s
58
+ end
59
+
60
+ # Copies a file.
61
+ def cp(src, dst, _opts={})
62
+ src = norm_path(src)
63
+ dst = norm_path(dst)
64
+ FileUtils.cp @root.join(src).to_s, @root.join(dst).to_s
65
+ rescue Errno::ENOENT
66
+ raise BFS::FileNotFound, src
67
+ end
68
+
69
+ # Moves a file.
70
+ def mv(src, dst, _opts={})
71
+ src = norm_path(src)
72
+ dst = norm_path(dst)
73
+ FileUtils.mv @root.join(src).to_s, @root.join(dst).to_s
74
+ rescue Errno::ENOENT
75
+ raise BFS::FileNotFound, src
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,65 @@
1
+ require 'bfs'
2
+ require 'stringio'
3
+
4
+ module BFS
5
+ module Bucket
6
+ # InMem buckets are useful for tests
7
+ class InMem < Abstract
8
+ Entry = Struct.new(:io, :mtime)
9
+
10
+ def initialize
11
+ @files = {}
12
+ end
13
+
14
+ # Lists the contents of a bucket using a glob pattern
15
+ def ls(pattern='**/*', _opts={})
16
+ @files.each_key.select do |key|
17
+ File.fnmatch?(pattern, key, File::FNM_PATHNAME)
18
+ end
19
+ end
20
+
21
+ # Info returns the file info
22
+ def info(path, _opts={})
23
+ path = norm_path(path)
24
+ raise BFS::FileNotFound, path unless @files.key?(path)
25
+
26
+ entry = @files[path]
27
+ BFS::FileInfo.new(path, entry.io.size, entry.mtime)
28
+ end
29
+
30
+ # Creates a new file and opens it for writing
31
+ def create(path, _opts={}, &block)
32
+ io = StringIO.new
33
+ @files[norm_path(path)] = Entry.new(io, Time.now)
34
+ return io unless block
35
+
36
+ begin
37
+ yield(io)
38
+ ensure
39
+ io.close
40
+ end
41
+ end
42
+
43
+ # Opens an existing file for reading
44
+ def open(path, _opts={}, &block)
45
+ path = norm_path(path)
46
+ raise BFS::FileNotFound, path unless @files.key?(path)
47
+
48
+ io = @files[path].io
49
+ io.reopen(io.string)
50
+ return io unless block
51
+
52
+ begin
53
+ yield(io)
54
+ ensure
55
+ io.close
56
+ end
57
+ end
58
+
59
+ # Deletes a file.
60
+ def rm(path, _opts={})
61
+ @files.delete(norm_path(path))
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,10 @@
1
+ module BFS
2
+ class FileNotFound < StandardError
3
+ attr_reader :path
4
+
5
+ def initialize(path)
6
+ @path = path
7
+ super "File not found: #{path}"
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ require 'tempfile'
2
+
3
+ module BFS
4
+ class TempWriter
5
+ def initialize(name, &closer)
6
+ @closer = closer
7
+ @tempfile = ::Tempfile.new(File.basename(name.to_s))
8
+ end
9
+
10
+ def path
11
+ @tempfile.path
12
+ end
13
+
14
+ def write(data)
15
+ @tempfile.write(data)
16
+ end
17
+
18
+ def close
19
+ path = @tempfile.path
20
+ @tempfile.close
21
+ @closer.call(path) if @closer
22
+ @tempfile.unlink
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe BFS::Bucket::FS do
4
+ let(:tmpdir) { Dir.mktmpdir }
5
+ after { FileUtils.rm_rf tmpdir }
6
+ subject { described_class.new(tmpdir) }
7
+
8
+ it_behaves_like 'a bucket'
9
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe BFS::Bucket::InMem do
4
+ it_behaves_like 'a bucket'
5
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bfs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dimitrij Denissenko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-10-10 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Minimalist abstraction for bucket storage
14
+ email: dimitrij@blacksquaremedia.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - bfs.gemspec
20
+ - lib/bfs.rb
21
+ - lib/bfs/bucket.rb
22
+ - lib/bfs/bucket/abstract.rb
23
+ - lib/bfs/bucket/fs.rb
24
+ - lib/bfs/bucket/in_mem.rb
25
+ - lib/bfs/errors.rb
26
+ - lib/bfs/helpers.rb
27
+ - spec/bfs/bucket/fs_spec.rb
28
+ - spec/bfs/bucket/in_mem_spec.rb
29
+ homepage: https://github.com/bsm/bfs.rb
30
+ licenses:
31
+ - Apache-2.0
32
+ metadata: {}
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 2.2.0
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 2.7.6
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: Multi-platform cloud bucket adapter
53
+ test_files:
54
+ - spec/bfs/bucket/fs_spec.rb
55
+ - spec/bfs/bucket/in_mem_spec.rb