bfs 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.
@@ -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