yobi 0.2.0 → 0.3.1

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: edd02fb1bbb985e22d5e32e8558b37f469809e9b5cdc079c6b4885e5ada5a775
4
- data.tar.gz: 4d6a90b324596e6fc375a1338fdba1c70d84a6bf9504b15513614c87458bd7c0
3
+ metadata.gz: 56739c72c75a1d5c856bbd619b781dd3986cf1b3c1447d4864edd19b9543d8d9
4
+ data.tar.gz: a5321fe628cab2f74011ce257a4eee380ed1fcb804456f8afa5482ac84c96b6f
5
5
  SHA512:
6
- metadata.gz: 9c11e446c74d42a68b7a34c61b4952451f0929d56114a6439ec6462f854019ea40d7844dd730371026a679f744a464600c997ce76b884de538a38ee879348efc
7
- data.tar.gz: e3245e59a13c6b9f8712824406f0844106e1495a543ff68cf2ac11210835ef480109755acf2019ca5b10708d46436bc1e58f7e9e122f450ce3dd4f40395695fb
6
+ metadata.gz: 9b2253d54dd64aa88658c7ddd53734a2edbcaf2623bb98258bc14d71d0d4a82bb3bcb447e6434bfb28505d2bfcd329988841d8f491ce7c3d1b13865b4a50b0f7
7
+ data.tar.gz: 7cd169b509d216a99b258180381c9caa9cd92837b822467781b01c3788cc37af4252d825b4de50a511b71a521a4e8d9bff8317a7f9c0da11b82847c6b65c2b8c
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [0.3.1] - 2026-08-10
2
+
3
+ ### Fixed
4
+
5
+ - `#forget`/`#init`/`#stats`/`#find`/`#snapshots`/`#key_list`/the `#cat_*` methods no longer raise a bare, uninformative `JSON::ParserError` when Restic exits with code 3 ("succeeded with warnings" - e.g. `#forget` failing to remove one or more snapshots) and mixes a plain-text diagnostic line into otherwise-JSON output. Raises `Yobi::ResticCommandFailed` instead, with the diagnostic still available in the error message.
6
+
7
+ ## [0.3.0] - 2026-08-03
8
+
9
+ ### Changed
10
+
11
+ - **Breaking:** `#init`/`#copy`'s `from_repository_file:` kwarg removed - `from_repo:` is now polymorphic, additionally accepting a `[:file, "..."]` tuple to read the source repository's URL from a file, alongside the existing plain URL String/`Repository` instance shapes.
12
+ - Invalid shapes for `password:`, `backend_credentials:`, `restic:`, `source:`/a `stdin_from_command` command, `new_password:`, `from_repo:`, and `from_password:` now raise `ArgumentError` with a message like `"invalid password shape"`, instead of a raw `NoMatchingPatternError`/`NoMatchingPatternKeyError`.
13
+ - `Repository#initialize`'s `password:`/`backend_credentials:` shapes are now validated eagerly at construction (and `from_password:`/`from_repo:` eagerly in `#init`/`#copy`), instead of lazily on the first `#env`/`#inspect`/argv-building call.
14
+
1
15
  ## [0.2.0] - 2026-08-03
2
16
 
3
17
  ### Added
data/README.md CHANGED
@@ -521,7 +521,7 @@ A shortcut for setting up a `#copy` destination: constructs a new `Repository` a
521
521
  mirror_repo.copy(from_repo: primary_repo, tags: "nightly")
522
522
  ```
523
523
 
524
- Replicates snapshots from another repository into this one. `from_repo:` accepts a plain URL String or a `Repository` instance. Given the latter, its own `#url`/`#password` are used automatically unless `from_password:` is given explicitly. Already-copied snapshots are skipped automatically on a repeat run. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#copy-instance_method) for the full list of options. Returns `true`.
524
+ Replicates snapshots from another repository into this one. `from_repo:` accepts a plain URL String, a `[:file, "..."]` tuple reading the URL from a file, or a `Repository` instance. Given the latter, its own `#url`/`#password` are used automatically unless `from_password:` is given explicitly. Already-copied snapshots are skipped automatically on a repeat run. See the [YARD docs](https://rubydoc.info/gems/yobi/Yobi/Repository#copy-instance_method) for the full list of options. Returns `true`.
525
525
 
526
526
  ## Mounting
527
527
 
@@ -83,6 +83,8 @@ module Yobi
83
83
  a.flag(:stdin_from_command)
84
84
  a.flag(:stdin_filename, filename)
85
85
  a.end_of_options.append(tokenize(command))
86
+ else
87
+ raise ArgumentError, "invalid source shape"
86
88
  end
87
89
  end
88
90
 
@@ -99,6 +101,8 @@ module Yobi
99
101
  Shellwords.split(value)
100
102
  in Array => values
101
103
  values.map(&:to_s)
104
+ else
105
+ raise ArgumentError, "invalid stdin_from_command command shape"
102
106
  end
103
107
  end
104
108
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
-
5
3
  module Yobi
6
4
  class Repository
7
5
  # `restic cat config`: the repository's own config document.
@@ -10,7 +8,7 @@ module Yobi
10
8
  # @raise [Yobi::RepositoryNotFound, Yobi::AuthenticationFailed]
11
9
  def cat_config
12
10
  execution = run_restic(build_argv("cat", "config"))
13
- JSON.parse(execution[:output].to_s)
11
+ parse_json_output(execution)
14
12
  end
15
13
  alias_method :config, :cat_config
16
14
 
@@ -21,7 +19,7 @@ module Yobi
21
19
  def cat_snapshot(id)
22
20
  id = id.id if id.is_a?(Yobi::Snapshot)
23
21
  execution = run_restic(build_argv("cat", "snapshot", id))
24
- JSON.parse(execution[:output].to_s)
22
+ parse_json_output(execution)
25
23
  end
26
24
 
27
25
  # `restic cat index ID`: one index file's own raw contents. IDs come
@@ -31,7 +29,7 @@ module Yobi
31
29
  # @return [Hash]
32
30
  def cat_index(id)
33
31
  execution = run_restic(build_argv("cat", "index", id))
34
- JSON.parse(execution[:output].to_s)
32
+ parse_json_output(execution)
35
33
  end
36
34
 
37
35
  # `restic cat key ID`: one key's own raw stored record.
@@ -41,7 +39,7 @@ module Yobi
41
39
  def cat_key(id)
42
40
  id = id.id if id.is_a?(Yobi::Key)
43
41
  execution = run_restic(build_argv("cat", "key", id))
44
- JSON.parse(execution[:output].to_s)
42
+ parse_json_output(execution)
45
43
  end
46
44
 
47
45
  # `restic cat tree snapshot:subfolder`: the raw tree object at a
@@ -58,7 +56,7 @@ module Yobi
58
56
  "#{snapshot_id}:#{subfolder}"
59
57
  end
60
58
  execution = run_restic(build_argv("cat", "tree", target))
61
- JSON.parse(execution[:output].to_s)
59
+ parse_json_output(execution)
62
60
  end
63
61
 
64
62
  # `restic cat pack ID`: one pack file's raw, still-encrypted bytes.
@@ -91,7 +89,7 @@ module Yobi
91
89
  # @return [Hash]
92
90
  def cat_masterkey_and_game_over_if_this_leaks
93
91
  execution = run_restic(build_argv("cat", "masterkey"))
94
- JSON.parse(execution[:output].to_s)
92
+ parse_json_output(execution)
95
93
  end
96
94
  end
97
95
  end
@@ -5,33 +5,31 @@ module Yobi
5
5
  # `restic copy`: replicates snapshots from another repository into
6
6
  # this one. Already-copied snapshots are skipped automatically.
7
7
  #
8
- # @param from_repo [String, Yobi::Repository] the source repository, or its URL
9
- # (given a `Repository`, its own `#url`/`#password` are used automatically)
8
+ # @param from_repo [String, Yobi::Repository, Array] the source repository's
9
+ # URL; a `Repository` instance (its own `#url`/`#password` are used
10
+ # automatically); or a `[:file, "..."]` tuple reading the URL from a file
10
11
  # @param from_password [String, Array, Symbol, #call, nil] the source
11
12
  # repository's password, same shape as {#initialize}'s `password:`
12
13
  # @param snapshot_ids [Array<String>, String] snapshots to copy; all of them if empty
13
14
  # @param from_key_hint [String, nil]
14
- # @param from_repository_file [String, nil] read the source repository's URL from a file
15
15
  # @param hosts [Array<String>, String] filter by hostname(s)
16
16
  # @param paths [Array<String>, String] filter by originally backed-up path(s)
17
17
  # @param tags [Array<String>, String] filter by tag(s)
18
18
  # @return [true]
19
- def copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil,
20
- from_repository_file: nil, hosts: [], paths: [], tags: [])
21
- if from_repo.is_a?(Repository)
22
- from_password = from_repo.password if from_password.nil?
23
- from_repo = from_repo.url
24
- end
19
+ def copy(from_repo:, from_password: nil, snapshot_ids: [], from_key_hint: nil, hosts: [], paths: [], tags: [])
20
+ from_repo_flag, from_repo_value = resolved_from_repo_flag(from_repo)
21
+ from_password = from_repo_password(from_repo) if from_password.nil?
22
+ validate_from_password_shape!(from_password)
25
23
 
26
24
  argv = build_argv("copy", snapshot_ids) do |a|
27
- a.flag(:from_repo, from_repo)
25
+ a.flag(from_repo_flag, from_repo_value)
28
26
  a.flag(:from_insecure_no_password) if from_password == :insecure_no_password
29
27
  a.flag(:from_key_hint, from_key_hint) unless from_key_hint.nil?
30
- a.flag(:from_repository_file, from_repository_file) unless from_repository_file.nil?
31
28
  a.repeat_flag(:host, hosts)
32
29
  a.repeat_flag(:path, paths)
33
30
  a.repeat_flag(:tag, tags)
34
31
  end
32
+
35
33
  run_restic(argv, extra_env: password_env(from_password, "RESTIC_FROM_PASSWORD"))
36
34
  true
37
35
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
3
  require "time"
5
4
 
6
5
  module Yobi
@@ -45,7 +44,7 @@ module Yobi
45
44
  a.end_of_options.append(patterns)
46
45
  end
47
46
  execution = run_restic(argv)
48
- JSON.parse(execution[:output].to_s).map { |raw| MatchesPerSnapshot.new(raw) }
47
+ parse_json_output(execution).map { |raw| MatchesPerSnapshot.new(raw) }
49
48
  end
50
49
  end
51
50
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
-
5
3
  module Yobi
6
4
  class Repository
7
5
  # `restic forget`: applies a retention policy, removing snapshots
@@ -69,7 +67,7 @@ module Yobi
69
67
  a.flag(:repack_smaller_than, repack_smaller_than) unless repack_smaller_than.nil?
70
68
  end
71
69
  execution = run_restic(argv)
72
- JSON.parse(execution[:output].to_s).map { |raw| ForgetGroup.new(raw) }
70
+ parse_json_output(execution).map { |raw| ForgetGroup.new(raw) }
73
71
  end
74
72
  end
75
73
 
@@ -1,38 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
-
5
3
  module Yobi
6
4
  class Repository
7
5
  # `restic init`: creates the repository at {#url}.
8
6
  #
9
- # @param copy_chunker_params [Boolean] copy chunker parameters from `from_repo:`/`from_repository_file:`
7
+ # @param copy_chunker_params [Boolean] copy chunker parameters from `from_repo:`
10
8
  # @param from_key_hint [String, nil]
11
9
  # @param from_password [String, Array, Symbol, #call, nil] the source
12
10
  # repository's password (when copying chunker params from one), same
13
11
  # shape as {#initialize}'s `password:`
14
- # @param from_repo [String, Yobi::Repository, nil] the source repository, or its URL
15
- # (given a `Repository`, its own `#url`/`#password` are used automatically)
16
- # @param from_repository_file [String, nil]
12
+ # @param from_repo [String, Yobi::Repository, Array, nil] the source repository's
13
+ # URL; a `Repository` instance (its own `#url`/`#password` are used
14
+ # automatically); or a `[:file, "..."]` tuple reading the URL from a file
17
15
  # @param repository_version [String, nil]
18
16
  # @return [Yobi::Initialized]
19
- def init(copy_chunker_params: false, from_key_hint: nil, from_password: nil, from_repo: nil,
20
- from_repository_file: nil, repository_version: nil)
21
- if from_repo.is_a?(Repository)
22
- from_password = from_repo.password if from_password.nil?
23
- from_repo = from_repo.url
17
+ def init(copy_chunker_params: false, from_key_hint: nil, from_password: nil, from_repo: nil, repository_version: nil)
18
+ from_repo_flag, from_repo_value = resolved_from_repo_flag(from_repo) unless from_repo.nil?
19
+
20
+ if from_repo_value
21
+ from_password = from_repo_password(from_repo) if from_password.nil?
22
+ validate_from_password_shape!(from_password)
24
23
  end
25
24
 
26
25
  argv = build_argv("init") do |a|
27
26
  a.flag(:copy_chunker_params) if copy_chunker_params
28
27
  a.flag(:from_insecure_no_password) if from_password == :insecure_no_password
29
28
  a.flag(:from_key_hint, from_key_hint) unless from_key_hint.nil?
30
- a.flag(:from_repo, from_repo) unless from_repo.nil?
31
- a.flag(:from_repository_file, from_repository_file) unless from_repository_file.nil?
29
+ a.flag(from_repo_flag, from_repo_value) if from_repo_flag
32
30
  a.flag(:repository_version, repository_version) unless repository_version.nil?
33
31
  end
32
+
34
33
  execution = run_restic(argv, extra_env: password_env(from_password, "RESTIC_FROM_PASSWORD"))
35
- Initialized.new(JSON.parse(execution[:output].to_s))
34
+ Initialized.new(parse_json_output(execution))
36
35
  end
37
36
 
38
37
  # Constructs a new {Yobi::Repository} at `url`, already initialized with
@@ -24,7 +24,7 @@ module Yobi
24
24
  argv = build_argv("key", "add") do |a|
25
25
  a.flag(:host, host) unless host.nil?
26
26
  a.flag(:user, user) unless user.nil?
27
- a.flag(flag_name, flag_value) unless flag_name.nil?
27
+ a.flag(flag_name, flag_value)
28
28
  end
29
29
  run_restic(argv)
30
30
  end
@@ -37,7 +37,7 @@ module Yobi
37
37
  # @return [Array<Yobi::Key>]
38
38
  def key_list
39
39
  execution = run_restic(build_argv("key", "list"))
40
- JSON.parse(execution[:output].to_s).map { |raw| Key.new(raw) }
40
+ parse_json_output(execution).map { |raw| Key.new(raw) }
41
41
  end
42
42
  alias_method :keys, :key_list
43
43
 
@@ -55,7 +55,7 @@ module Yobi
55
55
  argv = build_argv("key", "passwd") do |a|
56
56
  a.flag(:host, host) unless host.nil?
57
57
  a.flag(:user, user) unless user.nil?
58
- a.flag(flag_name, flag_value) unless flag_name.nil?
58
+ a.flag(flag_name, flag_value)
59
59
  end
60
60
  run_restic(argv)
61
61
  end
@@ -88,6 +88,8 @@ module Yobi
88
88
  with_temp_password_file(literal) { |path| yield :new_password_file, path }
89
89
  in callable if callable.respond_to?(:call)
90
90
  with_temp_password_file(callable.call.to_s) { |path| yield :new_password_file, path }
91
+ else
92
+ raise ArgumentError, "invalid new_password shape"
91
93
  end
92
94
  end
93
95
 
@@ -90,7 +90,7 @@ module Yobi
90
90
  self.class.dispatch(exit_code: status.exitstatus, output: output, argv: argv)
91
91
  end
92
92
  rescue Errno::ENOENT
93
- output&.file&.close
93
+ output.file.close
94
94
  raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
95
95
  end
96
96
 
@@ -23,7 +23,7 @@ module Yobi
23
23
  a.flag(:latest, latest) unless latest.nil?
24
24
  end
25
25
  execution = run_restic(argv)
26
- JSON.parse(execution[:output].to_s).map { |raw| Snapshot.new(raw) }
26
+ parse_json_output(execution).map { |raw| Snapshot.new(raw) }
27
27
  end
28
28
  end
29
29
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
-
5
3
  module Yobi
6
4
  class Repository
7
5
  # @private
@@ -29,7 +27,7 @@ module Yobi
29
27
  a.repeat_flag(:tag, tags)
30
28
  end
31
29
  execution = run_restic(argv)
32
- RepositoryStats.new(JSON.parse(execution[:output].to_s))
30
+ RepositoryStats.new(parse_json_output(execution))
33
31
  end
34
32
  end
35
33
 
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "json"
3
4
  require "uri"
4
5
 
5
6
  module Yobi
@@ -23,9 +24,10 @@ module Yobi
23
24
  # responding to `#call` returning such a Hash
24
25
  # @param restic [Yobi::Restic, String, nil] a `Restic` instance to share,
25
26
  # a bare Restic binary path, or `nil` to create a default one
26
- # @raise [ArgumentError] if `password:` is `nil`
27
+ # @raise [ArgumentError] if `password:` is `nil`, or if `password:`/`backend_credentials:`/`restic:` is an invalid shape
27
28
  def initialize(url:, password:, backend_credentials: {}, restic: nil)
28
- raise ArgumentError, "password: is required (use :insecure_no_password if the repository truly has none)" if password.nil?
29
+ validate_password_shape!(password)
30
+ validate_backend_credentials_shape!(backend_credentials)
29
31
 
30
32
  @url, @extracted_rest_credentials = extract_rest_credentials(url)
31
33
  @password = password
@@ -61,6 +63,21 @@ module Yobi
61
63
  builder.to_a
62
64
  end
63
65
 
66
+ # Parses a command's captured output as JSON. If parsing fails, raises
67
+ # {Yobi::ResticCommandFailed} when `execution[:exit_code]` is `3`
68
+ # (Restic reported a problem alongside otherwise-JSON output), or
69
+ # re-raises the original +JSON::ParserError+ when it's `0`.
70
+ #
71
+ # @param execution [Hash] `{exit_code:, output:, argv:}`
72
+ # @return [Object] the parsed JSON
73
+ # @raise [Yobi::ResticCommandFailed] if parsing fails and `exit_code` is `3`
74
+ def parse_json_output(execution)
75
+ JSON.parse(execution[:output].to_s)
76
+ rescue JSON::ParserError
77
+ raise if execution[:exit_code].zero?
78
+ raise Yobi::ResticCommandFailed, execution
79
+ end
80
+
64
81
  def resolved_password
65
82
  password_env(password)
66
83
  end
@@ -119,6 +136,51 @@ module Yobi
119
136
  end
120
137
  end
121
138
 
139
+ def from_repo_password(from_repo)
140
+ from_repo.password if from_repo.is_a?(Repository)
141
+ end
142
+
143
+ def resolved_from_repo_flag(from_repo)
144
+ case from_repo
145
+ in Repository
146
+ [:from_repo, from_repo.url]
147
+ in String
148
+ [:from_repo, from_repo]
149
+ in [:file, String => path]
150
+ [:from_repository_file, path]
151
+ else
152
+ raise ArgumentError, "invalid from_repo shape"
153
+ end
154
+ end
155
+
156
+ def validate_password_shape!(value, name: "password")
157
+ case value
158
+ in nil
159
+ raise ArgumentError, "#{name}: is required (use :insecure_no_password if there truly is none)"
160
+ in :insecure_no_password | String | [:command, String] | [:file, String]
161
+ true
162
+ in callable if callable.respond_to?(:call)
163
+ true
164
+ else
165
+ raise ArgumentError, "invalid #{name} shape"
166
+ end
167
+ end
168
+
169
+ def validate_from_password_shape!(value)
170
+ validate_password_shape!(value, name: "from_password")
171
+ end
172
+
173
+ def validate_backend_credentials_shape!(value)
174
+ case value
175
+ in Hash
176
+ true
177
+ in callable if callable.respond_to?(:call)
178
+ true
179
+ else
180
+ raise ArgumentError, "invalid backend_credentials shape"
181
+ end
182
+ end
183
+
122
184
  def extract_rest_credentials(url)
123
185
  return [url, {}] unless url.start_with?("rest:")
124
186
 
@@ -139,6 +201,8 @@ module Yobi
139
201
  restic_or_restic_path
140
202
  in String | nil
141
203
  Yobi::Restic.new(restic_or_restic_path)
204
+ else
205
+ raise ArgumentError, "invalid restic shape"
142
206
  end
143
207
  end
144
208
  end
data/lib/yobi/restic.rb CHANGED
@@ -262,7 +262,7 @@ module Yobi
262
262
  self.class.dispatch(exit_code: status.exitstatus, output: output, argv: argv)
263
263
  result
264
264
  rescue Errno::ENOENT
265
- output&.file&.close
265
+ output.file.close
266
266
  raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
267
267
  end
268
268
 
@@ -324,7 +324,7 @@ module Yobi
324
324
  _, status = Process.wait2(pid)
325
325
  {exit_code: status.exitstatus, output: output, argv: argv}
326
326
  rescue Errno::ENOENT
327
- output&.file&.close
327
+ output.file.close
328
328
  raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
329
329
  end
330
330
 
@@ -352,7 +352,7 @@ module Yobi
352
352
  {exit_code: wait_thr.value.exitstatus, output: output, argv: argv}
353
353
  end
354
354
  rescue Errno::ENOENT
355
- output&.file&.close
355
+ output.file.close
356
356
  raise Yobi::ResticNotFound.new(restic_path: restic_path, argv: argv)
357
357
  end
358
358
  end
data/lib/yobi/version.rb CHANGED
@@ -4,5 +4,5 @@
4
4
  # plain Ruby objects instead of shelling out to flags and raw JSON by hand.
5
5
  module Yobi
6
6
  # @return [String]
7
- VERSION = "0.2.0"
7
+ VERSION = "0.3.1"
8
8
  end
data/sig/yobi.rbs CHANGED
@@ -221,6 +221,7 @@ module Yobi
221
221
  type new_password = String | [:file, String] | :insecure_no_password | (^() -> String)
222
222
  type backend_credentials = Hash[String, String] | (^() -> Hash[String, String])
223
223
  type restic_or_path = Restic | String | nil
224
+ type from_repo = String | Repository | [:file, String]
224
225
 
225
226
  class Restic
226
227
  MINIMUM_VERSION: Gem::Version
@@ -295,7 +296,7 @@ module Yobi
295
296
  def env: () -> Hash[String, String]
296
297
 
297
298
  def init: (?copy_chunker_params: bool, ?from_key_hint: String?, ?from_password: from_password,
298
- ?from_repo: String | Repository | nil, ?from_repository_file: String?, ?repository_version: String?) -> Initialized
299
+ ?from_repo: from_repo?, ?repository_version: String?) -> Initialized
299
300
 
300
301
  def init_mirror: (url: String, password: password, ?backend_credentials: backend_credentials,
301
302
  ?restic: restic_or_path, ?repository_version: String?) -> Repository
@@ -335,8 +336,8 @@ module Yobi
335
336
  def check: (?hosts: Array[String] | String, ?paths: Array[String] | String, ?read_data: bool,
336
337
  ?read_data_subset: String?, ?tags: Array[String] | String, ?with_cache: bool) -> CheckOutcome
337
338
 
338
- def copy: (from_repo: String | Repository, ?from_password: from_password, ?snapshot_ids: Array[String] | String,
339
- ?from_key_hint: String?, ?from_repository_file: String?, ?hosts: Array[String] | String,
339
+ def copy: (from_repo: from_repo, ?from_password: from_password, ?snapshot_ids: Array[String] | String,
340
+ ?from_key_hint: String?, ?hosts: Array[String] | String,
340
341
  ?paths: Array[String] | String, ?tags: Array[String] | String) -> true
341
342
 
342
343
  def diff: (from: String, to: String, ?metadata: bool) -> DiffOutcome
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yobi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zia Perdana
@@ -61,7 +61,7 @@ metadata:
61
61
  homepage_uri: https://codeberg.org/ukazap/yobi
62
62
  source_code_uri: https://codeberg.org/ukazap/yobi
63
63
  changelog_uri: https://codeberg.org/ukazap/yobi/src/branch/main/CHANGELOG.md
64
- documentation_uri: https://rubydoc.info/gems/yobi
64
+ documentation_uri: https://rubydoc.info/gems/yobi/0.3.1
65
65
  rdoc_options: []
66
66
  require_paths:
67
67
  - lib