bfs 0.7.6 → 0.8.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd67ecfdb038d1809be4193caf014dfd9e8bcba4e26a4cd04da3c64e61522ddc
4
- data.tar.gz: 17586a9b02836e8bdcbf4da6bc822d9c169cca9823b5817357dba1d4a5f1a072
3
+ metadata.gz: 5b84026761dc7be719468a7f2b76a206d21747eb2fc87811b5c9febab4054176
4
+ data.tar.gz: fc8438759bb6b51002b1565d812f316e71bbc5e829ded770f9f9c642acec8b73
5
5
  SHA512:
6
- metadata.gz: e1ab4426da76e5a0de997d9b1f355c549197e4bd199629a37f6f787a9dcd068020c04ed190f117461490bb75d86a4070b475d481cc679005de731dacabb8fd64
7
- data.tar.gz: 355cd6811da84829fad5f59927535b681c3f357968681d175c8a400f4e0b445920120daba944ea679dc965b582d65781063e2be199ffc372cda4e7df6739f89c
6
+ metadata.gz: 474d67a3cab3c887eb36ceba96b963ce6c6289b8042986c2a09882276e84252a68abc5e0f501b9e29616d015fbd4a31e4479fcadced45e67336e5c5a8383a9a9
7
+ data.tar.gz: dbb7755e5141715c2c105c0b074e607a8a28c8b12267caa878bfef79156184b942a4cf45888255cb9b0218d574e12e591edf4765f6f71af0a50b03e50d89967c
data/lib/bfs/blob.rb CHANGED
@@ -32,6 +32,9 @@ module BFS
32
32
  end
33
33
 
34
34
  # Creates the blob and opens it for writing.
35
+ # If a block is passed the writer is automatically committed in the end.
36
+ # If no block is passed, you must manually call #commit to persist the
37
+ # result.
35
38
  def create(**opts, &block)
36
39
  @bucket.create(path, **opts, &block)
37
40
  end
@@ -40,6 +40,11 @@ module BFS
40
40
  raise 'not implemented'
41
41
  end
42
42
 
43
+ # Iterates over the contents of a bucket using a glob pattern
44
+ def glob(_pattern = '**', **_opts)
45
+ raise 'not implemented'
46
+ end
47
+
43
48
  # Info returns the info for a single file
44
49
  def info(_path, **_opts)
45
50
  raise 'not implemented'
@@ -107,7 +112,7 @@ module BFS
107
112
  def norm_meta(meta)
108
113
  norm = {}
109
114
  meta.each do |key, value|
110
- nkey = key.to_s.downcase.split(/-/).map(&:capitalize).join('-')
115
+ nkey = key.to_s.downcase.split('-').map(&:capitalize).join('-')
111
116
  norm[nkey] = value
112
117
  end if meta.is_a?(Hash)
113
118
  norm
data/lib/bfs/bucket/fs.rb CHANGED
@@ -15,19 +15,30 @@ module BFS
15
15
 
16
16
  # Lists the contents of a bucket using a glob pattern
17
17
  def ls(pattern = '**/*', **_opts)
18
- Enumerator.new do |y|
19
- Pathname.glob(@root.join(pattern)) do |pname|
20
- y << trim_prefix(pname.to_s) if pname.file?
18
+ Enumerator.new do |acc|
19
+ walk(pattern) do |path, _|
20
+ acc << path
21
+ end
22
+ end
23
+ end
24
+
25
+ # Iterates over the contents of a bucket using a glob pattern
26
+ def glob(pattern = '**/*', **_opts)
27
+ Enumerator.new do |acc|
28
+ walk(pattern) do |path, stat|
29
+ acc << file_info(path, stat)
21
30
  end
22
31
  end
23
32
  end
24
33
 
25
34
  # Info returns the info for a single file
26
35
  def info(path, **_opts)
27
- full = @root.join(norm_path(path))
28
- path = trim_prefix(full.to_s)
29
- stat = full.stat
30
- BFS::FileInfo.new(path: path, size: stat.size, mtime: stat.mtime, mode: BFS.norm_mode(stat.mode))
36
+ norm = norm_path(path)
37
+ pn = @root.join(norm)
38
+ stat = pn.stat
39
+ raise BFS::FileNotFound, path unless stat.file?
40
+
41
+ file_info(norm, stat)
31
42
  rescue Errno::ENOENT
32
43
  raise BFS::FileNotFound, path
33
44
  end
@@ -42,7 +53,7 @@ module BFS
42
53
  full = @root.join(norm_path(path))
43
54
  FileUtils.mkdir_p(full.dirname.to_s)
44
55
 
45
- BFS::TempWriter.new(full, encoding: encoding, perm: perm) do |temp|
56
+ BFS::Writer.new(full, encoding: encoding, perm: perm) do |temp|
46
57
  FileUtils.mv temp, full.to_s
47
58
  end.perform(&block)
48
59
  end
@@ -93,6 +104,19 @@ module BFS
93
104
  rescue Errno::ENOENT
94
105
  raise BFS::FileNotFound, norm_path(src)
95
106
  end
107
+
108
+ private
109
+
110
+ def walk(pattern)
111
+ Pathname.glob(@root.join(pattern)) do |pn|
112
+ stat = pn.stat
113
+ yield(trim_prefix(pn.to_s), stat) if stat.file?
114
+ end
115
+ end
116
+
117
+ def file_info(path, stat)
118
+ BFS::FileInfo.new(path: path, size: stat.size, mtime: stat.mtime, mode: BFS.norm_mode(stat.mode))
119
+ end
96
120
  end
97
121
  end
98
122
  end
@@ -1,5 +1,6 @@
1
1
  require 'bfs'
2
2
  require 'stringio'
3
+ require 'delegate'
3
4
 
4
5
  module BFS
5
6
  module Bucket
@@ -8,34 +9,18 @@ module BFS
8
9
  Entry = Struct.new(:io, :mtime, :content_type, :metadata)
9
10
 
10
11
  class Writer < DelegateClass(::StringIO)
11
- def initialize(encoding:, &closer)
12
- @closer = closer
12
+ include BFS::Writer::Mixin
13
+
14
+ def initialize(encoding:, &on_commit)
15
+ @on_commit = on_commit
13
16
 
14
17
  sio = StringIO.new
15
18
  sio.set_encoding(encoding)
16
19
  super sio
17
20
  end
18
21
 
19
- def close
20
- super.tap do
21
- @closer&.call(self)
22
- end
23
- end
24
-
25
- def close!
26
- __getobj__.close
27
- end
28
-
29
- def perform
30
- return self unless block_given?
31
-
32
- begin
33
- yield self
34
- close
35
- ensure
36
- close!
37
- end
38
- end
22
+ alias close! close
23
+ alias commit_ref __getobj__
39
24
  end
40
25
 
41
26
  def initialize(**opts)
@@ -51,8 +36,17 @@ module BFS
51
36
  # Lists the contents of a bucket using a glob pattern
52
37
  def ls(pattern = '**/*', **_opts)
53
38
  Enumerator.new do |y|
54
- @files.each_key do |key|
55
- y << key if File.fnmatch?(pattern, key, File::FNM_PATHNAME)
39
+ @files.each_key do |path|
40
+ y << path if File.fnmatch?(pattern, path, File::FNM_PATHNAME)
41
+ end
42
+ end
43
+ end
44
+
45
+ # Iterates over the contents of a bucket using a glob pattern
46
+ def glob(pattern = '**/*', **_opts)
47
+ Enumerator.new do |y|
48
+ @files.each_key do |path|
49
+ y << file_info(path) if File.fnmatch?(pattern, path, File::FNM_PATHNAME)
56
50
  end
57
51
  end
58
52
  end
@@ -62,8 +56,7 @@ module BFS
62
56
  path = norm_path(path)
63
57
  raise BFS::FileNotFound, path unless @files.key?(path)
64
58
 
65
- entry = @files[path]
66
- BFS::FileInfo.new(path: path, size: entry.io.size, mtime: entry.mtime, content_type: entry.content_type, metadata: entry.metadata)
59
+ file_info(path)
67
60
  end
68
61
 
69
62
  # Creates a new file and opens it for writing.
@@ -99,6 +92,13 @@ module BFS
99
92
  def rm(path, **_opts)
100
93
  @files.delete(norm_path(path))
101
94
  end
95
+
96
+ private
97
+
98
+ def file_info(path)
99
+ entry = @files[path]
100
+ BFS::FileInfo.new path: path, size: entry.io.size, mtime: entry.mtime, content_type: entry.content_type, metadata: entry.metadata
101
+ end
102
102
  end
103
103
  end
104
104
  end
data/lib/bfs/helpers.rb CHANGED
@@ -2,33 +2,45 @@ require 'tempfile'
2
2
  require 'delegate'
3
3
 
4
4
  module BFS
5
- class TempWriter < DelegateClass(::Tempfile)
6
- def initialize(name, tempdir: nil, perm: nil, **opts, &closer)
7
- @closer = closer
5
+ class Writer < DelegateClass(::Tempfile)
6
+ module Mixin
7
+ def perform
8
+ return self unless block_given?
8
9
 
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
15
- return self unless block_given?
10
+ begin
11
+ yield self
12
+ commit
13
+ ensure
14
+ discard
15
+ end
16
+ end
16
17
 
17
- begin
18
- yield self
18
+ def commit
19
19
  close
20
+ return false if @on_commit.nil?
21
+
22
+ @on_commit.call(commit_ref)
23
+ true
20
24
  ensure
25
+ discard
26
+ end
27
+
28
+ def discard
29
+ @on_commit = nil
21
30
  close!
22
31
  end
23
32
  end
24
33
 
25
- def close
26
- return if closed?
34
+ include Mixin
27
35
 
28
- super.tap do
29
- @closer&.call(path)
30
- end
31
- unlink
36
+ def initialize(name, tempdir: nil, perm: nil, **opts, &on_commit)
37
+ @on_commit = on_commit
38
+
39
+ tempfile = ::Tempfile.new(File.basename(name.to_s), tempdir, **opts)
40
+ tempfile.chmod(perm) if perm
41
+ super tempfile
32
42
  end
43
+
44
+ alias commit_ref path
33
45
  end
34
46
  end
@@ -2,12 +2,15 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe BFS::Blob, core: true do
4
4
  describe 'default' do
5
+ subject { described_class.new('memtest://bucket/path/to/file.txt') }
6
+
5
7
  let(:bucket) { BFS::Bucket::InMem.new }
8
+
6
9
  before { allow(BFS).to receive(:resolve).and_return(bucket) }
7
- subject { described_class.new('memtest://bucket/path/to/file.txt') }
10
+
8
11
  after { subject.close }
9
12
 
10
- it 'should move' do
13
+ it 'moves' do
11
14
  expect(subject.path).to eq('path/to/file.txt')
12
15
  expect { subject.mv('/to/other/path.txt') }.to raise_error(BFS::FileNotFound)
13
16
 
@@ -16,7 +19,7 @@ RSpec.describe BFS::Blob, core: true do
16
19
  expect(subject.path).to eq('to/other/path.txt')
17
20
  end
18
21
 
19
- it 'should write/read' do
22
+ it 'write/reads' do
20
23
  expect { subject.read }.to raise_error(BFS::FileNotFound)
21
24
  subject.write('TESTDATA', content_type: 'text/plain', metadata: { 'x-key' => 'val' })
22
25
 
@@ -36,12 +39,14 @@ RSpec.describe BFS::Blob, core: true do
36
39
  end
37
40
 
38
41
  describe 'file system' do
42
+ subject { described_class.new("file:///#{path}") }
43
+
39
44
  let(:tmpdir) { Dir.mktmpdir }
40
45
  let(:path) { "#{tmpdir}/path/to/file.txt".sub('/', '') }
41
- after { FileUtils.rm_rf tmpdir }
42
- subject { described_class.new("file:///#{path}") }
43
46
 
44
- it 'should move' do
47
+ after { FileUtils.rm_rf tmpdir }
48
+
49
+ it 'moves' do
45
50
  expect(subject.path).to eq(path)
46
51
  expect { subject.mv("#{tmpdir}/to/other/path.txt") }.to raise_error(BFS::FileNotFound)
47
52
 
@@ -54,7 +59,7 @@ RSpec.describe BFS::Blob, core: true do
54
59
  ]
55
60
  end
56
61
 
57
- it 'should write/read' do
62
+ it 'write/reads' do
58
63
  expect { subject.read }.to raise_error(BFS::FileNotFound)
59
64
 
60
65
  subject.write('TESTDATA', content_type: 'text/plain', metadata: { 'x-key' => 'val' })
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe BFS::Bucket::Abstract, core: true do
4
- it 'should open with a block' do
4
+ it 'opens with a block' do
5
5
  sub_class = Class.new(described_class) do
6
6
  def close
7
7
  @closed = true
@@ -1,15 +1,17 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe BFS::Bucket::FS, core: true do
4
- let(:tmpdir) { Dir.mktmpdir }
5
- after { FileUtils.rm_rf tmpdir }
6
4
  subject { described_class.new(tmpdir) }
7
5
 
6
+ let(:tmpdir) { Dir.mktmpdir }
7
+
8
+ after { FileUtils.rm_rf tmpdir }
9
+
8
10
  it_behaves_like 'a bucket',
9
11
  content_type: false,
10
12
  metadata: false
11
13
 
12
- it 'should resolve from URL' do
14
+ it 'resolves from URL' do
13
15
  File.open(File.join(tmpdir, 'test.txt'), 'wb') {|f| f.write 'TESTDATA' }
14
16
 
15
17
  bucket = BFS.resolve("file://#{tmpdir}")
@@ -18,14 +20,14 @@ RSpec.describe BFS::Bucket::FS, core: true do
18
20
  bucket.close
19
21
  end
20
22
 
21
- it 'should support custom perms on #initialize' do
23
+ it 'supports custom perms on #initialize' do
22
24
  blob = BFS::Blob.new("file://#{tmpdir}/test.txt?perm=0666")
23
25
  blob.create {|w| w.write 'foo' }
24
26
  expect(blob.info.mode).to eq(0o666)
25
27
  blob.close
26
28
  end
27
29
 
28
- it 'should support custom perms on #create' do
30
+ it 'supports custom perms on #create' do
29
31
  blob = BFS::Blob.new("file://#{tmpdir}/test.txt")
30
32
  blob.create(perm: 0o666) {|w| w.write 'foo' }
31
33
  expect(blob.info.mode).to eq(0o666)
@@ -1,28 +1,29 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe BFS::TempWriter, core: true do
4
- let(:closer) { proc {} }
5
- subject { described_class.new 'test', &closer }
3
+ RSpec.describe BFS::Writer, core: true do
4
+ subject { described_class.new 'test', &on_commit }
6
5
 
7
- it 'should behave like a File' do
8
- missing = ::File.public_instance_methods - subject.public_methods
9
- expect(missing).to be_empty
10
- end
6
+ let(:on_commit) { proc { true } }
11
7
 
12
- it 'should support custom params' do
13
- subject = described_class.new 'test', perm: 0o640, &closer
8
+ it 'supports custom params' do
9
+ subject = described_class.new 'test', perm: 0o640, &on_commit
14
10
  expect(subject.stat.mode).to eq(0o100640)
15
- expect(subject.close).to be_truthy
11
+ expect(subject.commit).to be(true)
16
12
  end
17
13
 
18
- it 'should execute a closer block' do
19
- expect(closer).to receive(:call).with(subject.path).once
20
- expect(subject.close).to be_truthy
21
- expect(subject.close).to be_nil
14
+ it 'executes a on_commit block' do
15
+ expect(on_commit).to receive(:call).with(subject.path).once
16
+ expect(subject.commit).to be(true)
17
+ expect(subject.commit).to be(false)
22
18
  end
23
19
 
24
- it 'may skip closer block' do
25
- expect(closer).not_to receive(:call)
26
- expect(subject.close!).to be_truthy
20
+ it 'may skip on_commit block' do
21
+ expect(on_commit).not_to receive(:call)
22
+ expect(subject.discard).to be(true)
23
+ end
24
+
25
+ it 'does not auto-commit on close' do
26
+ expect(on_commit).not_to receive(:call)
27
+ expect(subject.close).to be_nil
27
28
  end
28
29
  end
data/spec/bfs_spec.rb CHANGED
@@ -1,14 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe BFS, core: true do
4
- it 'should resolve' do
5
- bucket = BFS.resolve("file://#{Dir.tmpdir}")
4
+ it 'resolves' do
5
+ bucket = described_class.resolve("file://#{Dir.tmpdir}")
6
6
  expect(bucket).to be_instance_of(BFS::Bucket::FS)
7
7
  bucket.close
8
8
  end
9
9
 
10
- it 'should resolve with block' do
11
- BFS.resolve("file://#{Dir.tmpdir}") do |bucket|
10
+ it 'resolves with block' do
11
+ described_class.resolve("file://#{Dir.tmpdir}") do |bucket|
12
12
  expect(bucket).to be_instance_of(BFS::Bucket::FS)
13
13
  expect(bucket).to receive(:close)
14
14
  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.6
4
+ version: 0.8.4
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-11-10 00:00:00.000000000 Z
11
+ date: 2021-05-27 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Minimalist abstraction for bucket storage
14
14
  email: dimitrij@blacksquaremedia.com
@@ -51,7 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
51
  - !ruby/object:Gem::Version
52
52
  version: '0'
53
53
  requirements: []
54
- rubygems_version: 3.1.2
54
+ rubygems_version: 3.2.15
55
55
  signing_key:
56
56
  specification_version: 4
57
57
  summary: Multi-platform cloud bucket adapter