bfs 0.7.0 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ff8d42e2ce3e53b671a1210c70ced9c25e96b5ca60effac77ada4868ef26d299
4
- data.tar.gz: 881dd9522385bc0417cb12bf6ab492a9f772dd091042130db614b6252ec29d24
3
+ metadata.gz: 61e0b8f8c56a60a7d3b933aacce3e476d400389bfa991b14b3adddf3af667108
4
+ data.tar.gz: ab0da145b04da234825c0c418a5e5dc0fc3028627431072df04c5f2f7f45b652
5
5
  SHA512:
6
- metadata.gz: abe8ef5d0579001860159f7485e0cb254d013e10575a2c5fd8b56e3ba94e3766d1b4db0f1843a3261dc50f8f30f15d16115fdabf976a441fb42a81711c4cea1a
7
- data.tar.gz: 98238249263bff71a4d48677296d3e4641d788ab355fdff0a4253792884daf38356cc3823c93b6b4f1bb33de234a80f0043b0c52db75b3d0ffea718d6b510fed
6
+ metadata.gz: 4ba809e64dfdab6a918a807323acffd3228783d5c4c6a3ecc9712011ca742a2510e830fe7c2cbf73f2b0d900bdd9d5a9386e133c16ba3170b6be972366db0405
7
+ data.tar.gz: 1999418b45ed5c090b7bf578f9ea6c8964eb49538b98068c6eff583480b1682d63ef61a04a3f9183ba54f027e9bd876b618d5297795c469c43127474e808449d
data/lib/bfs.rb CHANGED
@@ -4,6 +4,8 @@ require 'cgi'
4
4
  module BFS
5
5
  class FileInfo < Hash
6
6
  def initialize(**attrs)
7
+ super(nil)
8
+
7
9
  update(size: 0, mtime: Time.at(0), mode: 0, metadata: {})
8
10
  update(attrs)
9
11
  end
@@ -36,11 +38,24 @@ module BFS
36
38
  def self.register(*schemes, &resolver)
37
39
  @registry ||= {}
38
40
  schemes.each do |scheme|
41
+ scheme = scheme.to_s
42
+ raise(ArgumentError, "scheme #{scheme} is already registered") if @registry.key?(scheme)
43
+
39
44
  @registry[scheme] = resolver
40
45
  end
41
46
  end
42
47
 
43
- def self.resolve(url)
48
+ def self.unregister(*schemes)
49
+ @registry ||= {}
50
+ schemes.each do |scheme|
51
+ scheme = scheme.to_s
52
+ raise(ArgumentError, "scheme #{scheme} is not registered") unless @registry.key?(scheme)
53
+
54
+ @registry.delete(scheme)
55
+ end
56
+ end
57
+
58
+ def self.resolve(url, &block)
44
59
  url = url.is_a?(::URI) ? url.dup : URI.parse(url)
45
60
  rsl = @registry[url.scheme]
46
61
  raise ArgumentError, "Unable to resolve #{url}, scheme #{url.scheme} is not registered" unless rsl
@@ -49,7 +64,7 @@ module BFS
49
64
  CGI.parse(url.query.to_s).each do |key, values|
50
65
  opts[key.to_sym] = values.first
51
66
  end
52
- rsl.call(url, opts)
67
+ rsl.call(url, opts, block)
53
68
  end
54
69
 
55
70
  def self.norm_path(path)
@@ -64,6 +79,11 @@ module BFS
64
79
  mode = mode.to_i(8) if mode.is_a?(String)
65
80
  mode & 0o000777
66
81
  end
82
+
83
+ def self.defer(obj, method)
84
+ owner = Process.pid
85
+ ObjectSpace.define_finalizer(obj, ->(*) { obj.send(method) if Process.pid == owner })
86
+ end
67
87
  end
68
88
 
69
89
  require 'bfs/helpers'
@@ -3,12 +3,27 @@ module BFS
3
3
  class Blob
4
4
  attr_reader :path
5
5
 
6
+ # Behaves like new, but accepts an optional block.
7
+ # If a block is given, blobs are automatically closed after the block is yielded.
8
+ def self.open(url)
9
+ blob = new(url)
10
+ return blob unless block_given?
11
+
12
+ begin
13
+ yield blob
14
+ ensure
15
+ blob.close
16
+ end
17
+ end
18
+
6
19
  def initialize(url)
7
20
  url = url.is_a?(::URI) ? url.dup : URI.parse(url)
8
21
  @path = BFS.norm_path(url.path)
9
22
 
10
23
  url.path = '/'
11
24
  @bucket = BFS.resolve(url)
25
+
26
+ BFS.defer(self, :close)
12
27
  end
13
28
 
14
29
  # Info returns the blob info.
@@ -34,7 +49,7 @@ module BFS
34
49
 
35
50
  # Shortcut method to read the contents of the blob.
36
51
  def read(**opts)
37
- open(**opts, &:read)
52
+ self.open(**opts, &:read)
38
53
  end
39
54
 
40
55
  # Shortcut method to write data to blob.
@@ -5,6 +5,19 @@ module BFS
5
5
  class Abstract
6
6
  attr_reader :encoding, :perm
7
7
 
8
+ # Behaves like new, but accepts an optional block.
9
+ # If a block is given, buckets are automatically closed after the block is yielded.
10
+ def self.open(*args, **opts)
11
+ bucket = new(*args, **opts)
12
+ return bucket unless block_given?
13
+
14
+ begin
15
+ yield bucket
16
+ ensure
17
+ bucket.close
18
+ end
19
+ end
20
+
8
21
  # Initializes a new bucket
9
22
  # @param [Hash] opts options
10
23
  # @option opts [String] :encoding Custom encoding. Default: Encoding.default_external.
@@ -18,6 +31,8 @@ module BFS
18
31
  when String
19
32
  @perm = perm.to_i(8)
20
33
  end
34
+
35
+ BFS.defer(self, :close)
21
36
  end
22
37
 
23
38
  # Lists the contents of a bucket using a glob pattern
@@ -51,7 +66,7 @@ module BFS
51
66
  # @param [String] path The path to read from.
52
67
  # @param [Hash] opts Additional options, see #open.
53
68
  def read(path, **opts)
54
- open(path, **opts, &:read)
69
+ self.open(path, **opts, &:read)
55
70
  end
56
71
 
57
72
  # Shortcut method to write data to path
@@ -68,7 +83,7 @@ module BFS
68
83
  # @param [String] src The source path.
69
84
  # @param [String] dst The destination path.
70
85
  def cp(src, dst, **opts)
71
- open(src, **opts) do |r|
86
+ self.open(src, **opts) do |r|
72
87
  create(dst, **opts) do |w|
73
88
  IO.copy_stream(r, w)
74
89
  end
@@ -42,14 +42,9 @@ module BFS
42
42
  full = @root.join(norm_path(path))
43
43
  FileUtils.mkdir_p(full.dirname.to_s)
44
44
 
45
- temp = BFS::TempWriter.new(full, encoding: encoding, perm: perm) {|t| FileUtils.mv t, full.to_s }
46
- return temp unless block
47
-
48
- begin
49
- yield temp
50
- ensure
51
- temp.close
52
- end
45
+ BFS::TempWriter.new(full, encoding: encoding, perm: perm) do |temp|
46
+ FileUtils.mv temp, full.to_s
47
+ end.perform(&block)
53
48
  end
54
49
 
55
50
  # Opens an existing file for reading
@@ -102,7 +97,7 @@ module BFS
102
97
  end
103
98
  end
104
99
 
105
- BFS.register('file') do |url, opts|
100
+ BFS.register('file') do |url, opts, block|
106
101
  parts = [url.host, url.path].compact
107
- BFS::Bucket::FS.new File.join(*parts), **opts
102
+ BFS::Bucket::FS.open(File.join(*parts), **opts, &block)
108
103
  end
@@ -7,6 +7,36 @@ module BFS
7
7
  class InMem < Abstract
8
8
  Entry = Struct.new(:io, :mtime, :content_type, :metadata)
9
9
 
10
+ class Writer < DelegateClass(::StringIO)
11
+ def initialize(encoding:, &closer)
12
+ @closer = closer
13
+
14
+ sio = StringIO.new
15
+ sio.set_encoding(encoding)
16
+ super sio
17
+ end
18
+
19
+ def close
20
+ close!
21
+ @closer&.call(self)
22
+ end
23
+
24
+ def close!
25
+ __getobj__.close
26
+ end
27
+
28
+ def perform(&block)
29
+ return self unless block
30
+
31
+ begin
32
+ yield self
33
+ close
34
+ ensure
35
+ close!
36
+ end
37
+ end
38
+ end
39
+
10
40
  def initialize(**opts)
11
41
  super(**opts.dup)
12
42
  @files = {}
@@ -43,18 +73,9 @@ module BFS
43
73
  # @option opts [String] :content_type Custom content type.
44
74
  # @option opts [Hash] :metadata Metadata key-value pairs.
45
75
  def create(path, encoding: self.encoding, content_type: nil, metadata: nil, **_opts, &block)
46
- io = StringIO.new
47
- io.set_encoding(encoding)
48
-
49
- entry = Entry.new(io, Time.now, content_type, norm_meta(metadata))
50
- @files[norm_path(path)] = entry
51
- return io unless block
52
-
53
- begin
54
- yield(io)
55
- ensure
56
- io.close
57
- end
76
+ Writer.new(encoding: encoding) do |wio|
77
+ @files[norm_path(path)] = Entry.new(wio, Time.now, content_type, norm_meta(metadata))
78
+ end.perform(&block)
58
79
  end
59
80
 
60
81
  # Opens an existing file for reading
@@ -0,0 +1 @@
1
+ require 'bfs/bucket/fs'
@@ -4,19 +4,33 @@ require 'delegate'
4
4
  module BFS
5
5
  class TempWriter < DelegateClass(::Tempfile)
6
6
  def initialize(name, tempdir: nil, perm: nil, **opts, &closer)
7
- @closer = closer
8
- @tempfile = ::Tempfile.new(File.basename(name.to_s), tempdir, **opts)
9
- @tempfile.chmod(perm) if perm
10
- super @tempfile
7
+ @closer = closer
8
+
9
+ tempfile = ::Tempfile.new(File.basename(name.to_s), tempdir, **opts)
10
+ tempfile.chmod(perm) if perm
11
+ super tempfile
12
+ end
13
+
14
+ def perform(&block)
15
+ return self unless block
16
+
17
+ begin
18
+ yield self
19
+ close
20
+ ensure
21
+ close!
22
+ end
11
23
  end
12
24
 
13
25
  def close
14
26
  return if closed?
15
27
 
16
- path = @tempfile.path
17
- @tempfile.close
18
- @closer&.call(path)
19
- @tempfile.unlink
28
+ tempfile = __getobj__
29
+ tempfile.close
30
+ @closer&.call(tempfile.path)
31
+ true
32
+ ensure
33
+ tempfile.unlink
20
34
  end
21
35
  end
22
36
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe BFS::Blob do
3
+ RSpec.describe BFS::Blob, core: true do
4
4
  describe 'default' do
5
5
  let(:bucket) { BFS::Bucket::InMem.new }
6
6
  before { allow(BFS).to receive(:resolve).and_return(bucket) }
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe BFS::Bucket::Abstract, core: true do
4
+ it 'should open with a block' do
5
+ sub_class = Class.new(described_class) do
6
+ def close
7
+ @closed = true
8
+ end
9
+
10
+ def closed?
11
+ @closed == true
12
+ end
13
+ end
14
+
15
+ bucket = nil
16
+ result = sub_class.open do |bkt|
17
+ expect(bkt).not_to be_closed
18
+ bucket = bkt
19
+ 21
20
+ end
21
+ expect(result).to eq(21)
22
+ expect(bucket).to be_instance_of(sub_class)
23
+ expect(bucket).to be_closed
24
+ end
25
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe BFS::Bucket::FS do
3
+ RSpec.describe BFS::Bucket::FS, core: true do
4
4
  let(:tmpdir) { Dir.mktmpdir }
5
5
  after { FileUtils.rm_rf tmpdir }
6
6
  subject { described_class.new(tmpdir) }
@@ -15,6 +15,7 @@ RSpec.describe BFS::Bucket::FS do
15
15
  bucket = BFS.resolve("file://#{tmpdir}")
16
16
  expect(bucket).to be_instance_of(described_class)
17
17
  expect(bucket.ls.to_a).to eq(['test.txt'])
18
+ bucket.close
18
19
  end
19
20
 
20
21
  it 'should support custom perms on #initialize' do
@@ -1,5 +1,5 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe BFS::Bucket::InMem do
3
+ RSpec.describe BFS::Bucket::InMem, core: true do
4
4
  it_behaves_like 'a bucket'
5
5
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe BFS::TempWriter do
3
+ RSpec.describe BFS::TempWriter, core: true do
4
4
  let(:closer) { proc {} }
5
5
  subject { described_class.new 'test', &closer }
6
6
 
@@ -19,4 +19,9 @@ RSpec.describe BFS::TempWriter do
19
19
  expect(closer).to receive(:call).with(subject.path)
20
20
  expect(subject.close).to be_truthy
21
21
  end
22
+
23
+ it 'may skip closer block' do
24
+ expect(closer).not_to receive(:call)
25
+ expect(subject.close!).to be_truthy
26
+ end
22
27
  end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe BFS, core: true do
4
+ it 'should resolve' do
5
+ bucket = BFS.resolve("file://#{Dir.tmpdir}")
6
+ expect(bucket).to be_instance_of(BFS::Bucket::FS)
7
+ bucket.close
8
+ end
9
+
10
+ it 'should resolve with block' do
11
+ BFS.resolve("file://#{Dir.tmpdir}") do |bucket|
12
+ expect(bucket).to be_instance_of(BFS::Bucket::FS)
13
+ expect(bucket).to receive(:close)
14
+ end
15
+ end
16
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bfs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dimitrij Denissenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-28 00:00:00.000000000 Z
11
+ date: 2020-11-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Minimalist abstraction for bucket storage
14
14
  email: dimitrij@blacksquaremedia.com
@@ -24,11 +24,14 @@ files:
24
24
  - lib/bfs/bucket/fs.rb
25
25
  - lib/bfs/bucket/in_mem.rb
26
26
  - lib/bfs/errors.rb
27
+ - lib/bfs/fs.rb
27
28
  - lib/bfs/helpers.rb
28
29
  - spec/bfs/blob_spec.rb
30
+ - spec/bfs/bucket/abstract_spec.rb
29
31
  - spec/bfs/bucket/fs_spec.rb
30
32
  - spec/bfs/bucket/in_mem_spec.rb
31
33
  - spec/bfs/helpers_spec.rb
34
+ - spec/bfs_spec.rb
32
35
  homepage: https://github.com/bsm/bfs.rb
33
36
  licenses:
34
37
  - Apache-2.0
@@ -54,6 +57,8 @@ specification_version: 4
54
57
  summary: Multi-platform cloud bucket adapter
55
58
  test_files:
56
59
  - spec/bfs/blob_spec.rb
60
+ - spec/bfs/bucket/abstract_spec.rb
57
61
  - spec/bfs/bucket/fs_spec.rb
58
62
  - spec/bfs/bucket/in_mem_spec.rb
59
63
  - spec/bfs/helpers_spec.rb
64
+ - spec/bfs_spec.rb