gemvault 0.1.5 → 0.2.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,33 @@
1
+ require "uri"
2
+ require "pathname"
3
+
4
+ module Gemvault
5
+ ##
6
+ # Translates vault locators into filesystem paths.
7
+ #
8
+ # A locator is whatever a user hands the CLI or a package manager as "the
9
+ # vault": a plain filesystem path, a <tt>file://</tt> URI, or a
10
+ # <tt>vault://</tt> URI. Anything else passes through unchanged.
11
+ module VaultPath
12
+ SCHEMES = ["file", "vault"].freeze
13
+
14
+ module_function
15
+
16
+ # :call-seq:
17
+ # resolve(locator) -> Pathname
18
+ #
19
+ # Resolves +locator+ to a Pathname. Two-slash relative forms such as
20
+ # <tt>file://vault.gemv</tt> parse their first segment as a URI host, so
21
+ # host and path are rejoined.
22
+ def resolve(locator)
23
+ begin
24
+ uri = URI.parse(locator.to_s)
25
+ return Pathname(locator.to_s) unless SCHEMES.include?(uri.scheme)
26
+
27
+ Pathname([uri.host, uri.path].compact.join)
28
+ rescue URI::InvalidURIError
29
+ Pathname(locator.to_s)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,4 @@
1
+ require "pathname"
1
2
  require "fileutils"
2
3
  require_relative "vault"
3
4
  require_relative "deprecation"
@@ -7,20 +8,33 @@ module Gemvault
7
8
  # existing backend and rewriting it through the current-format writer, then
8
9
  # atomically swapping the file into place. Preserves each gem's created_at.
9
10
  class VaultUpgrade
11
+ include FileUtils
12
+
10
13
  # The from/to/gem-count summary of an upgrade; also detects an
11
14
  # already-current (no-op) vault.
12
- Plan = Struct.new(:from_version, :to_version, :gem_count, keyword_init: true) do
13
- def no_op?
14
- from_version == to_version
15
+ class Plan < Data.define(:from_version, :to_version, :gem_count)
16
+ def no_op? = from_version == to_version
17
+
18
+ def to_s = "format #{from_version} -> #{to_version}"
19
+
20
+ def deconstruct_keys(_keys)
21
+ { from_version:, to_version:, gem_count:, no_op: no_op? }
15
22
  end
23
+ end
16
24
 
17
- def to_s
18
- "format #{from_version} -> #{to_version}"
25
+ # Copies gems from a source vault into a target vault, preserving each
26
+ # gem's stored timestamp. Holds the two endpoints so the per-gem call
27
+ # takes only the entry.
28
+ class GemCopy < Data.define(:source, :target)
29
+ def call(entry)
30
+ source.with_gem_file(entry) do |gem_path|
31
+ target.add(gem_path, created_at: entry.created_at)
32
+ end
19
33
  end
20
34
  end
21
35
 
22
36
  def initialize(path, backup: true)
23
- @path = File.expand_path(path)
37
+ @path = Pathname(path).expand_path
24
38
  @backup = backup
25
39
  end
26
40
 
@@ -44,43 +58,36 @@ module Gemvault
44
58
  private
45
59
 
46
60
  def backup!
47
- backup_path = "#{@path}.bak"
48
- if File.exist?(backup_path)
49
- raise Vault::Error, "Backup already exists: #{backup_path} (remove it or pass --no-backup)"
50
- end
61
+ backup_path = Pathname("#{@path}.bak")
62
+ raise Vault::Error, "Backup already exists: #{backup_path} (remove it or pass --no-backup)" if backup_path.exist?
51
63
 
52
- FileUtils.cp(@path, backup_path)
64
+ cp(@path, backup_path)
53
65
  end
54
66
 
55
67
  def rebuild(expected_count)
56
- tmp = "#{@path}.upgrading"
57
- FileUtils.rm_f(tmp)
68
+ tmp = Pathname("#{@path}.upgrading")
69
+ rm_f(tmp)
58
70
  copy_current_format(tmp)
59
- verify_count!(tmp, expected_count)
60
- File.rename(tmp, @path)
71
+ verify_count!(tmp:, expected_count:)
72
+ tmp.rename(@path)
61
73
  end
62
74
 
63
75
  def copy_current_format(tmp)
64
76
  Deprecation.silence do
65
77
  Vault.open(@path) do |old|
66
78
  target = Vault.new(tmp, create: true)
67
- old.gem_entries.each { |entry| copy_gem(old, target, entry) }
79
+ copy = GemCopy.new(source: old, target:)
80
+ old.gem_entries.each { |entry| copy.call(entry) }
68
81
  target.close
69
82
  end
70
83
  end
71
84
  end
72
85
 
73
- def copy_gem(old, target, entry)
74
- old.with_gem_file(entry.name, entry.version, platform: entry.platform) do |gem_path|
75
- target.add(gem_path, created_at: entry.created_at)
76
- end
77
- end
78
-
79
- def verify_count!(tmp, expected_count)
86
+ def verify_count!(tmp:, expected_count:)
80
87
  actual = Vault.open(tmp, &:size)
81
88
  return if actual == expected_count
82
89
 
83
- FileUtils.rm_f(tmp)
90
+ rm_f(tmp)
84
91
  raise Vault::Error, "Upgrade verification failed: expected #{expected_count} gems, got #{actual}"
85
92
  end
86
93
  end
@@ -1,3 +1,3 @@
1
1
  module Gemvault
2
- VERSION = "0.1.5".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
data/lib/gemvault.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require_relative "gemvault/version"
2
2
  require_relative "gemvault/deprecation"
3
3
  require_relative "gemvault/vault"
4
+ require_relative "gemvault/vault_path"
4
5
 
5
6
  ##
6
7
  # Top-level namespace for the gemvault gem.
@@ -11,7 +11,7 @@ class Gem::Resolver::VaultSet < Gem::Resolver::Set
11
11
  end
12
12
 
13
13
  def find_all(req)
14
- @specs.filter_map { |tuple| index_spec_for(req, tuple) }
14
+ @specs.filter_map { |candidate| index_spec_for(candidate) if req.match?(candidate) }
15
15
  end
16
16
 
17
17
  def prefetch(reqs); end
@@ -22,19 +22,17 @@ class Gem::Resolver::VaultSet < Gem::Resolver::Set
22
22
 
23
23
  pp.breakable
24
24
 
25
- pp.seplist @specs do |tuple|
26
- pp.text tuple.full_name
25
+ pp.seplist @specs do |candidate|
26
+ pp.text candidate.full_name
27
27
  end
28
28
  end
29
29
  end
30
30
 
31
31
  private
32
32
 
33
- def index_spec_for(req, tuple)
34
- return unless req.match?(tuple)
35
-
33
+ def index_spec_for(candidate)
36
34
  Gem::Resolver::IndexSpecification.new(
37
- self, tuple.name, tuple.version, @source, tuple.platform
35
+ self, candidate.name, candidate.version, @source, candidate.platform
38
36
  )
39
37
  end
40
38
  end
@@ -1,8 +1,11 @@
1
1
  require "gemvault/vault"
2
- require "uri"
2
+ require "gemvault/vault_path"
3
+ require "gemvault/gem_entry"
4
+ require "pathname"
3
5
 
4
6
  ##
5
- # A source backed by a .gemv vault file (SQLite archive of .gem blobs).
7
+ # A source backed by a .gemv vault file: a tar archive of .gem files indexed
8
+ # by a manifest. Legacy SQLite vaults are read transparently.
6
9
  #
7
10
  # Used by the gemvault RubyGems plugin to support:
8
11
  #
@@ -10,12 +13,11 @@ require "uri"
10
13
  class Gem::Source::Vault < Gem::Source
11
14
  include Gem::UserInteraction
12
15
 
13
- VAULT_URI_SCHEMES = %w[file vault].freeze
14
-
15
16
  attr_reader :path
16
17
 
17
18
  def initialize(path)
18
- @path = File.expand_path(filesystem_path(path))
19
+ resolved = Gemvault::VaultPath.resolve(path)
20
+ @path = Pathname(resolved).expand_path.to_s
19
21
  super(@path)
20
22
  @uri = @path
21
23
  @specs = nil
@@ -24,7 +26,7 @@ class Gem::Source::Vault < Gem::Source
24
26
  def load_specs(type)
25
27
  verbose "Loading #{type} specs from vault at #{@path}"
26
28
  ensure_specs_loaded
27
- select_tuples(type)
29
+ select_candidates(type)
28
30
  end
29
31
 
30
32
  def fetch_spec(name_tuple)
@@ -38,17 +40,15 @@ class Gem::Source::Vault < Gem::Source
38
40
 
39
41
  def download(spec, dir = Dir.pwd)
40
42
  verbose "Extracting #{spec.file_name} from vault at #{@path}"
41
- cache_dir = File.join(dir, "cache")
42
- FileUtils.mkdir_p(cache_dir)
43
-
44
- dest = File.join(cache_dir, spec.file_name)
43
+ dest = Pathname(dir).join("cache").tap(&:mkpath).join(spec.file_name)
45
44
 
46
45
  Gemvault::Vault.open(@path) do |vault|
47
- data = vault.gem_data(spec.name, spec.version.to_s, platform: spec.platform.to_s)
48
- File.binwrite(dest, data)
46
+ entry = entry_for(spec)
47
+ bytes = vault.gem_data(entry)
48
+ dest.binwrite(bytes)
49
49
  end
50
50
 
51
- dest
51
+ dest.to_s
52
52
  end
53
53
 
54
54
  def dependency_resolver_set(prerelease = nil)
@@ -91,35 +91,32 @@ class Gem::Source::Vault < Gem::Source
91
91
 
92
92
  private
93
93
 
94
- def filesystem_path(path)
95
- uri = URI.parse(path.to_s)
96
- VAULT_URI_SCHEMES.include?(uri.scheme) ? uri.path : path.to_s
97
- rescue URI::InvalidURIError
98
- path.to_s
94
+ def entry_for(spec)
95
+ Gemvault::GemEntry.from_spec(spec)
99
96
  end
100
97
 
101
- def select_tuples(type)
98
+ def select_candidates(type)
102
99
  case type
103
- when :released then released_tuples
104
- when :prerelease then prerelease_tuples
105
- when :latest then latest_tuples
100
+ when :released then released_candidates
101
+ when :prerelease then prerelease_candidates
102
+ when :latest then latest_candidates
106
103
  else @specs.keys
107
104
  end
108
105
  end
109
106
 
110
- def released_tuples
111
- @specs.keys.reject { |tuple| tuple.version.prerelease? }
107
+ def released_candidates
108
+ @specs.keys.reject { |candidate| candidate.version.prerelease? }
112
109
  end
113
110
 
114
- def prerelease_tuples
115
- @specs.keys.select { |tuple| tuple.version.prerelease? }
111
+ def prerelease_candidates
112
+ @specs.keys.select { |candidate| candidate.version.prerelease? }
116
113
  end
117
114
 
118
- def latest_tuples
115
+ def latest_candidates
119
116
  @specs.keys
120
- .group_by { |tuple| [tuple.name, tuple.platform] }
117
+ .group_by { |candidate| [candidate.name, candidate.platform] }
121
118
  .values
122
- .map { |tuples| tuples.max_by(&:version) }
119
+ .map { |versions| versions.max_by(&:version) }
123
120
  end
124
121
 
125
122
  def ensure_specs_loaded
@@ -128,9 +125,8 @@ class Gem::Source::Vault < Gem::Source
128
125
  @specs = {}
129
126
  Gemvault::Vault.open(@path) do |vault|
130
127
  vault.gem_entries.each do |entry|
131
- spec = vault.spec_from_blob(entry.name, entry.version, entry.platform)
132
- tuple = spec.name_tuple
133
- @specs[tuple] = spec
128
+ spec = vault.spec_from_blob(entry)
129
+ @specs[spec.name_tuple] = spec
134
130
  end
135
131
  end
136
132
  end
@@ -31,8 +31,7 @@ module Gemvault
31
31
  Gem::OptionParser.accept Gem::URI::HTTP do |value|
32
32
  next value if value.to_s.end_with?(".gemv")
33
33
 
34
- uri = parse_uri(value)
35
- validate_scheme!(uri, value)
34
+ validate_uri!(value)
36
35
  value
37
36
  end
38
37
  end
@@ -40,12 +39,15 @@ module Gemvault
40
39
  private
41
40
 
42
41
  def parse_uri(value)
43
- Gem::URI.parse value
44
- rescue Gem::URI::InvalidURIError
45
- raise Gem::OptionParser::InvalidArgument, value
42
+ begin
43
+ Gem::URI.parse value
44
+ rescue Gem::URI::InvalidURIError
45
+ raise Gem::OptionParser::InvalidArgument, value
46
+ end
46
47
  end
47
48
 
48
- def validate_scheme!(uri, value)
49
+ def validate_uri!(value)
50
+ uri = parse_uri(value)
49
51
  return if VALID_URI_SCHEMES.include?(uri.scheme)
50
52
 
51
53
  schemes = VALID_URI_SCHEMES.map { |scheme| "#{scheme}://" }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gemvault
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Gillis
@@ -9,20 +9,6 @@ bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: bundler
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: '2.0'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - ">="
24
- - !ruby/object:Gem::Version
25
- version: '2.0'
26
12
  - !ruby/object:Gem::Dependency
27
13
  name: command_kit
28
14
  requirement: !ruby/object:Gem::Requirement
@@ -46,6 +32,7 @@ executables:
46
32
  extensions: []
47
33
  extra_rdoc_files: []
48
34
  files:
35
+ - ".containerignore"
49
36
  - ".rspec"
50
37
  - ".rubocop.yml"
51
38
  - ".ruby-version"
@@ -64,7 +51,9 @@ files:
64
51
  - exe/gemvault
65
52
  - issues.rec
66
53
  - lib/bundler/plugin/vault_source.rb
54
+ - lib/bundler/plugin/vaulted_gem.rb
67
55
  - lib/gemvault.rb
56
+ - lib/gemvault/archive_entry.rb
68
57
  - lib/gemvault/cli.rb
69
58
  - lib/gemvault/cli/command.rb
70
59
  - lib/gemvault/cli/commands/add.rb
@@ -80,11 +69,13 @@ files:
80
69
  - lib/gemvault/gem_extraction.rb
81
70
  - lib/gemvault/gem_reference.rb
82
71
  - lib/gemvault/gem_reference/any_version.rb
72
+ - lib/gemvault/gem_reference/parser.rb
83
73
  - lib/gemvault/gem_reference/specific_version.rb
84
74
  - lib/gemvault/manifest.rb
85
- - lib/gemvault/tar_archive.rb
75
+ - lib/gemvault/tarball.rb
86
76
  - lib/gemvault/tarvault.rb
87
77
  - lib/gemvault/vault.rb
78
+ - lib/gemvault/vault_path.rb
88
79
  - lib/gemvault/vault_session.rb
89
80
  - lib/gemvault/vault_upgrade.rb
90
81
  - lib/gemvault/version.rb
@@ -1,66 +0,0 @@
1
- require "rubygems/package"
2
- require "tempfile"
3
- require "fileutils"
4
- require_relative "manifest"
5
-
6
- module Gemvault
7
- # The tar file backing a Tarvault. Reads named entries and rewrites the
8
- # whole archive atomically: tar has no index and the leading manifest entry
9
- # changes size on every mutation, so mutation means a full rewrite.
10
- class TarArchive
11
- def initialize(path)
12
- @path = path
13
- end
14
-
15
- def read(name)
16
- bytes = nil
17
- each_entry { |entry| bytes = entry.read if entry.full_name == name }
18
- bytes
19
- end
20
-
21
- def gem_pairs
22
- pairs = []
23
- each_entry do |entry|
24
- pairs << [entry.full_name, entry.read] unless entry.full_name == Manifest::FILENAME
25
- end
26
- pairs
27
- end
28
-
29
- def write(pairs)
30
- tmp = Tempfile.create(["tarvault", ".tar"], File.dirname(@path))
31
- write_entries(tmp, pairs)
32
- tmp.flush
33
- tmp.fsync
34
- tmp.close
35
- File.rename(tmp.path, @path)
36
- rescue StandardError
37
- cleanup(tmp)
38
- raise
39
- end
40
-
41
- private
42
-
43
- def each_entry(&)
44
- File.open(@path, "rb") do |io|
45
- Gem::Package::TarReader.new(io) { |reader| reader.each_entry(&) }
46
- end
47
- end
48
-
49
- def write_entries(io, pairs)
50
- Gem::Package::TarWriter.new(io) do |writer|
51
- pairs.each { |name, bytes| add_entry(writer, name, bytes) }
52
- end
53
- end
54
-
55
- def add_entry(writer, name, bytes)
56
- writer.add_file_simple(name, 0o644, bytes.bytesize) { |dest| dest.write(bytes) }
57
- end
58
-
59
- def cleanup(tmp)
60
- return unless tmp
61
-
62
- tmp.close unless tmp.closed?
63
- FileUtils.rm_f(tmp.path)
64
- end
65
- end
66
- end