ocfl 0.4.1 → 0.5.0

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: 664b853647982b285f37270e111f75fe19fce52217c50eebed68c6e23340c6fc
4
- data.tar.gz: 32fa2fc95e646671dde3f4b0e3349ad84b77816f4087abe63623ae3d59028e20
3
+ metadata.gz: 734c2a614db8c30eb86ddd0c7a32b6c10d813adb9ae5244d50614f35244c64cd
4
+ data.tar.gz: 4b5daffdbd581fe38974975a7f9e6dfcef734d078833b0eb030d750aacc7b18e
5
5
  SHA512:
6
- metadata.gz: 1f023ae4faca8ac12ba46782a68a099c04a9623f9ed11ce26005ae96a1d259dfe7ca6c0cc2039c20154dda3bed64d9faa8f4181327920a5ebec8b6a597f4f7b1
7
- data.tar.gz: 33db1c161b1e24ba2859c98c65a5071745f2815956903ca45ad7faff8f1c8046040156d42d2483fa832379fd4cf880b39880d40c1b231ba0b44c6a85dc6ddb60
6
+ metadata.gz: 8c67987902f4adc67c8c250af66568872b9f3e9ca9cd9a3df60f26702e4ac16bfb6aea9e44628f427fe910af548ebc534436005e857f15cc3d47e4a31cc667a3
7
+ data.tar.gz: 857a0a20ee5a57c6b7087ef43264dbe2d3fd9b258175cc9449885a4b0eb1d56823cfe87eccad9bb86c5efc26ce5c2433e0465d6c5b0e425523668a18a7131e6a
data/.rubocop.yml CHANGED
@@ -1,3 +1,8 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rake
4
+ - rubocop-rspec
5
+
1
6
  AllCops:
2
7
  TargetRubyVersion: 3.1
3
8
 
@@ -17,8 +22,14 @@ Metrics/BlockLength:
17
22
  - describe
18
23
  - context
19
24
 
20
- Style/StringConcatenation:
21
- Enabled: false # Too many false positives with Pathname#+
25
+ RSpec/MultipleExpectations:
26
+ Enabled: false
27
+
28
+ RSpec/ExampleLength:
29
+ Max: 10
30
+
31
+ RSpec/NestedGroups:
32
+ Max: 4
22
33
 
23
34
  Gemspec/DeprecatedAttributeAssignment: # new in 1.30
24
35
  Enabled: true
@@ -219,4 +230,148 @@ Style/SuperWithArgsParentheses: # new in 1.58
219
230
  Style/SwapValues: # new in 1.1
220
231
  Enabled: true
221
232
  Style/YAMLFileRead: # new in 1.53
222
- Enabled: true
233
+ Enabled: true
234
+ Style/MapIntoArray: # new in 1.63
235
+ Enabled: true
236
+ Performance/AncestorsInclude: # new in 1.7
237
+ Enabled: true
238
+ Performance/BigDecimalWithNumericArgument: # new in 1.7
239
+ Enabled: true
240
+ Performance/BlockGivenWithExplicitBlock: # new in 1.9
241
+ Enabled: true
242
+ Performance/CollectionLiteralInLoop: # new in 1.8
243
+ Enabled: true
244
+ Performance/ConcurrentMonotonicTime: # new in 1.12
245
+ Enabled: true
246
+ Performance/ConstantRegexp: # new in 1.9
247
+ Enabled: true
248
+ Performance/MapCompact: # new in 1.11
249
+ Enabled: true
250
+ Performance/MapMethodChain: # new in 1.19
251
+ Enabled: true
252
+ Performance/MethodObjectAsBlock: # new in 1.9
253
+ Enabled: true
254
+ Performance/RedundantEqualityComparisonBlock: # new in 1.10
255
+ Enabled: true
256
+ Performance/RedundantSortBlock: # new in 1.7
257
+ Enabled: true
258
+ Performance/RedundantSplitRegexpArgument: # new in 1.10
259
+ Enabled: true
260
+ Performance/RedundantStringChars: # new in 1.7
261
+ Enabled: true
262
+ Performance/ReverseFirst: # new in 1.7
263
+ Enabled: true
264
+ Performance/SortReverse: # new in 1.7
265
+ Enabled: true
266
+ Performance/Squeeze: # new in 1.7
267
+ Enabled: true
268
+ Performance/StringIdentifierArgument: # new in 1.13
269
+ Enabled: true
270
+ Performance/StringInclude: # new in 1.7
271
+ Enabled: true
272
+ Performance/Sum: # new in 1.8
273
+ Enabled: true
274
+ Capybara/ClickLinkOrButtonStyle: # new in 2.19
275
+ Enabled: true
276
+ Capybara/MatchStyle: # new in 2.17
277
+ Enabled: true
278
+ Capybara/NegationMatcher: # new in 2.14
279
+ Enabled: true
280
+ Capybara/RedundantWithinFind: # new in 2.20
281
+ Enabled: true
282
+ Capybara/SpecificActions: # new in 2.14
283
+ Enabled: true
284
+ Capybara/SpecificFinders: # new in 2.13
285
+ Enabled: true
286
+ Capybara/SpecificMatcher: # new in 2.12
287
+ Enabled: true
288
+ Capybara/RSpec/HaveSelector: # new in 2.19
289
+ Enabled: true
290
+ Capybara/RSpec/PredicateMatcher: # new in 2.19
291
+ Enabled: true
292
+ FactoryBot/AssociationStyle: # new in 2.23
293
+ Enabled: true
294
+ FactoryBot/ConsistentParenthesesStyle: # new in 2.14
295
+ Enabled: true
296
+ FactoryBot/ExcessiveCreateList: # new in 2.25
297
+ Enabled: true
298
+ FactoryBot/FactoryAssociationWithStrategy: # new in 2.23
299
+ Enabled: true
300
+ FactoryBot/FactoryNameStyle: # new in 2.16
301
+ Enabled: true
302
+ FactoryBot/IdSequence: # new in 2.24
303
+ Enabled: true
304
+ FactoryBot/RedundantFactoryOption: # new in 2.23
305
+ Enabled: true
306
+ FactoryBot/SyntaxMethods: # new in 2.7
307
+ Enabled: true
308
+ RSpecRails/AvoidSetupHook: # new in 2.4
309
+ Enabled: true
310
+ RSpecRails/HaveHttpStatus: # new in 2.12
311
+ Enabled: true
312
+ RSpecRails/InferredSpecType: # new in 2.14
313
+ Enabled: true
314
+ RSpecRails/MinitestAssertions: # new in 2.17
315
+ Enabled: true
316
+ RSpecRails/NegationBeValid: # new in 2.23
317
+ Enabled: true
318
+ RSpecRails/TravelAround: # new in 2.19
319
+ Enabled: true
320
+ RSpec/BeEmpty: # new in 2.20
321
+ Enabled: true
322
+ RSpec/BeEq: # new in 2.9.0
323
+ Enabled: true
324
+ RSpec/BeNil: # new in 2.9.0
325
+ Enabled: true
326
+ RSpec/ChangeByZero: # new in 2.11
327
+ Enabled: true
328
+ RSpec/ContainExactly: # new in 2.19
329
+ Enabled: true
330
+ RSpec/DuplicatedMetadata: # new in 2.16
331
+ Enabled: true
332
+ RSpec/EmptyMetadata: # new in 2.24
333
+ Enabled: true
334
+ RSpec/EmptyOutput: # new in 2.29
335
+ Enabled: true
336
+ RSpec/Eq: # new in 2.24
337
+ Enabled: true
338
+ RSpec/ExcessiveDocstringSpacing: # new in 2.5
339
+ Enabled: true
340
+ RSpec/IdenticalEqualityAssertion: # new in 2.4
341
+ Enabled: true
342
+ RSpec/IndexedLet: # new in 2.20
343
+ Enabled: true
344
+ RSpec/IsExpectedSpecify: # new in 2.27
345
+ Enabled: true
346
+ RSpec/MatchArray: # new in 2.19
347
+ Enabled: true
348
+ RSpec/MetadataStyle: # new in 2.24
349
+ Enabled: true
350
+ RSpec/NoExpectationExample: # new in 2.13
351
+ Enabled: true
352
+ RSpec/PendingWithoutReason: # new in 2.16
353
+ Enabled: true
354
+ RSpec/ReceiveMessages: # new in 2.23
355
+ Enabled: true
356
+ RSpec/RedundantAround: # new in 2.19
357
+ Enabled: true
358
+ RSpec/RedundantPredicateMatcher: # new in 2.26
359
+ Enabled: true
360
+ RSpec/RemoveConst: # new in 2.26
361
+ Enabled: true
362
+ RSpec/RepeatedSubjectCall: # new in 2.27
363
+ Enabled: true
364
+ RSpec/SkipBlockInsideExample: # new in 2.19
365
+ Enabled: true
366
+ RSpec/SortMetadata: # new in 2.14
367
+ Enabled: true
368
+ RSpec/SpecFilePathFormat: # new in 2.24
369
+ Enabled: true
370
+ RSpec/SpecFilePathSuffix: # new in 2.24
371
+ Enabled: true
372
+ RSpec/SubjectDeclaration: # new in 2.5
373
+ Enabled: true
374
+ RSpec/UndescriptiveLiteralsDescription: # new in 2.29
375
+ Enabled: true
376
+ RSpec/VerifiedDoubleReference: # new in 2.10.0
377
+ Enabled: true
data/README.md CHANGED
@@ -35,15 +35,29 @@ new_version.save
35
35
  directory.head
36
36
  # => 'v2'
37
37
 
38
- directory.path("v2", "ocfl.rbs")
38
+ # List file names that were part of a given version
39
+ directory.versions['v2'].file_names
40
+ # => ["ocfl.rbs"]
41
+
42
+ # Get the path of a file in a given version
43
+ directory.path(filepath: "ocfl.rbs", version: "v2")
44
+ # => <Pathname:/files/[object_root]/v2/content/ocfl.rbs>
45
+
46
+ # Get the path of a file in the head version
47
+ directory.path(filepath: "ocfl.rbs", version: :head)
39
48
  # => <Pathname:/files/[object_root]/v2/content/ocfl.rbs>
40
49
 
41
- directory.path(:head, "ocfl.rbs")
50
+ # Get the path of a file from the latest version in which it was changed
51
+ directory.path(filepath: "ocfl.rbs")
42
52
  # => <Pathname:/files/[object_root]/v2/content/ocfl.rbs>
43
53
 
44
54
  new_version = directory.overwrite_current_version
45
55
  new_version.copy_file('sig/ocfl.rbs')
46
56
  new_version.save
57
+
58
+ new_version = directory.clone_current_version
59
+ new_version.copy_file('Gemfile')
60
+ new_version.save
47
61
  ```
48
62
 
49
63
  ## Development
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
4
-
5
3
  module OCFL
6
4
  module Object
7
5
  # An OCFL Directory layout for a particular object.
@@ -19,15 +17,22 @@ module OCFL
19
17
 
20
18
  delegate :head, :versions, :manifest, to: :inventory
21
19
 
22
- def path(version, filename)
20
+ def path(filepath:, version: nil)
23
21
  version = head if version == :head
24
- relative_path = version_inventory(version).path(filename)
25
- object_root + relative_path
22
+ relative_path = if version
23
+ version_inventory(version).path(filepath)
24
+ else
25
+ inventory.path(filepath)
26
+ end
27
+
28
+ raise FileNotFound, "Path '#{filepath}' not found in #{version || "object"} inventory" if relative_path.nil?
29
+
30
+ object_root / relative_path
26
31
  end
27
32
 
28
33
  def inventory
29
34
  @inventory ||= begin
30
- data = InventoryLoader.load(object_root + "inventory.json")
35
+ data = InventoryLoader.load(object_root / "inventory.json")
31
36
  if data.success?
32
37
  Inventory.new(data: data.value!)
33
38
  else
@@ -44,7 +49,7 @@ module OCFL
44
49
 
45
50
  def version_inventory(version)
46
51
  @version_inventory[version] ||= begin
47
- data = InventoryLoader.load(object_root + version + "inventory.json")
52
+ data = InventoryLoader.load(object_root / version / "inventory.json")
48
53
  if data.success?
49
54
  Inventory.new(data: data.value!)
50
55
  else
@@ -60,12 +65,17 @@ module OCFL
60
65
  @inventory = nil
61
66
  @errors = nil
62
67
  @version_inventory_errors = {}
68
+ true
63
69
  end
64
70
 
65
71
  def begin_new_version
66
72
  DraftVersion.new(object_directory: self)
67
73
  end
68
74
 
75
+ def clone_current_version
76
+ DraftVersion.new(object_directory: self, state: head_inventory.state)
77
+ end
78
+
69
79
  def overwrite_current_version
70
80
  DraftVersion.new(object_directory: self, overwrite_head: true)
71
81
  end
@@ -82,7 +92,7 @@ module OCFL
82
92
  end
83
93
 
84
94
  def head_directory_valid?
85
- InventoryValidator.new(directory: object_root + inventory.head).valid? &&
95
+ InventoryValidator.new(directory: object_root / inventory.head).valid? &&
86
96
  !head_inventory.nil? # Ensures it could be loaded
87
97
  end
88
98
 
@@ -91,9 +101,8 @@ module OCFL
91
101
  end
92
102
 
93
103
  def namaste_file
94
- object_root + "0=ocfl_object_1.1"
104
+ object_root / "0=ocfl_object_1.1"
95
105
  end
96
106
  end
97
- # rubocop:enable Style/StringConcatenation
98
107
  end
99
108
  end
@@ -1,21 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
4
-
5
3
  module OCFL
6
4
  module Object
7
5
  # Creates a OCFL Directory layout for a particular object.
8
6
  class DirectoryBuilder
9
7
  class ObjectExists < Error; end
10
8
 
11
- def initialize(object_root:, id:)
9
+ def initialize(object_root:, id:, content_directory: nil)
12
10
  @object_root = Pathname.new(object_root)
13
11
  raise ObjectExists, "The directory `#{object_root}' already exists" if @object_root.exist?
14
12
 
15
13
  @id = id
14
+ inventory = Inventory.new(
15
+ data: Inventory::InventoryStruct.new(
16
+ new_inventory_attrs.tap { |attrs| attrs[:contentDirectory] = content_directory if content_directory }
17
+ )
18
+ )
19
+ @object_directory = Directory.new(object_root:, inventory:)
16
20
  end
17
21
 
18
- attr_reader :id, :object_root, :object_directory
22
+ attr_reader :id, :inventory, :object_root, :object_directory
19
23
 
20
24
  def copy_file(...)
21
25
  create_object_directory
@@ -33,28 +37,33 @@ module OCFL
33
37
 
34
38
  # @return [Directory]
35
39
  def save
36
- version_path = object_root + "v1"
37
- FileUtils.mkdir_p(version_path) unless version_path.exist? # in case no files were added
38
-
39
- FileUtils.touch(object_root + "0=ocfl_object_1.1")
40
+ FileUtils.mkdir_p(object_root)
41
+ FileUtils.touch(object_directory.namaste_file)
40
42
  write_inventory
41
43
  object_directory
42
44
  end
43
45
 
44
46
  def version
45
- @version ||= begin
46
- data = Inventory::InventoryStruct.new(id:, version: "v0", type: Inventory::URI_1_1, digestAlgorithm: "sha512",
47
- head: "v0", versions: {}, manifest: {})
48
- inventory = Inventory.new(data:)
49
- @object_directory = Directory.new(object_root: @object_root, inventory:)
50
- DraftVersion.new(object_directory:)
51
- end
47
+ @version ||= DraftVersion.new(object_directory:)
52
48
  end
53
49
 
54
50
  def write_inventory
55
51
  version.save
56
52
  end
53
+
54
+ private
55
+
56
+ def new_inventory_attrs
57
+ {
58
+ id:,
59
+ version: "v0",
60
+ type: Inventory::URI_1_1,
61
+ digestAlgorithm: "sha512",
62
+ head: "v0",
63
+ versions: {},
64
+ manifest: {}
65
+ }
66
+ end
57
67
  end
58
- # rubocop:enable Style/StringConcatenation
59
68
  end
60
69
  end
@@ -1,16 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
4
-
5
3
  module OCFL
6
4
  module Object
7
5
  # A new OCFL version
8
6
  class DraftVersion
9
7
  # @params [Directory] object_directory
10
- def initialize(object_directory:, overwrite_head: false)
8
+ def initialize(object_directory:, overwrite_head: false, state: {})
11
9
  @object_directory = object_directory
12
10
  @manifest = object_directory.inventory.manifest.dup
13
- @state = {}
11
+ @state = state
14
12
 
15
13
  number = object_directory.head.delete_prefix("v").to_i
16
14
  @version_number = "v#{overwrite_head ? number : number + 1}"
@@ -40,27 +38,31 @@ module OCFL
40
38
  end
41
39
 
42
40
  def save
43
- inventory = build_inventory
44
- InventoryWriter.new(inventory:, path:).write
45
- FileUtils.cp(path + "inventory.json", object_directory.object_root)
46
- FileUtils.cp(path + "inventory.json.sha512", object_directory.object_root)
41
+ prepare_directory # only necessary if the version has no new content (deletes only)
42
+ write_inventory(build_inventory)
47
43
  object_directory.reload
48
44
  end
49
45
 
50
46
  private
51
47
 
48
+ def write_inventory(inventory)
49
+ InventoryWriter.new(inventory:, path:).write
50
+ FileUtils.cp(path / "inventory.json", object_directory.object_root)
51
+ FileUtils.cp(path / "inventory.json.sha512", object_directory.object_root)
52
+ end
53
+
52
54
  def copy_one(logical_file_path, incoming_path, destination_path)
53
55
  logical_file_path = File.join(destination_path, logical_file_path) unless destination_path.empty?
54
56
  add(incoming_path, logical_file_path:)
55
- parent_dir = (content_path + logical_file_path).parent
57
+ parent_dir = (content_path / logical_file_path).parent
56
58
  FileUtils.mkdir_p(parent_dir) unless parent_dir == content_path
57
- FileUtils.cp(incoming_path, content_path + logical_file_path)
59
+ FileUtils.cp(incoming_path, content_path / logical_file_path)
58
60
  end
59
61
 
60
62
  def add(incoming_path, logical_file_path: File.basename(incoming_path))
61
63
  digest = Digest::SHA512.file(incoming_path).to_s
62
64
  version_content_path = content_path.relative_path_from(object_directory.object_root)
63
- file_path_relative_to_root = (version_content_path + logical_file_path).to_s
65
+ file_path_relative_to_root = (version_content_path / logical_file_path).to_s
64
66
  @manifest[digest] = [file_path_relative_to_root]
65
67
  @state[digest] = [logical_file_path]
66
68
  end
@@ -81,11 +83,11 @@ module OCFL
81
83
  end
82
84
 
83
85
  def content_path
84
- path + "content"
86
+ path / object_directory.inventory.content_directory
85
87
  end
86
88
 
87
89
  def path
88
- object_directory.object_root + version_number
90
+ object_directory.object_root / version_number
89
91
  end
90
92
 
91
93
  def build_inventory
@@ -26,18 +26,25 @@ module OCFL
26
26
  attr_reader :errors, :data
27
27
 
28
28
  delegate :id, :head, :versions, :manifest, to: :data
29
+ delegate :state, to: :head_version
29
30
 
30
31
  def content_directory
31
32
  data.contentDirectory || "content"
32
33
  end
33
34
 
34
- # @returns [String] the path to the file relative to the object root. (e.g. v2/content/image.tiff)
35
+ # @return [String,nil] the path to the file relative to the object root. (e.g. v2/content/image.tiff)
35
36
  def path(logical_path)
36
- sha = versions[head].state.find { |_sha, file_names| file_names.include?(logical_path) }&.first
37
+ matching_paths = manifest.values.flatten.select do |path|
38
+ path.match(%r{\Av\d+/#{content_directory}/#{logical_path}\z})
39
+ end
37
40
 
38
- return unless sha
41
+ return if matching_paths.empty?
39
42
 
40
- manifest.fetch(sha).first
43
+ matching_paths.max_by { |path| path[/\d+/].to_i }
44
+ end
45
+
46
+ def head_version
47
+ versions[head]
41
48
  end
42
49
  end
43
50
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
4
-
5
3
  module OCFL
6
4
  module Object
7
5
  # Checks to see that the inventory.json and it's checksum in a direcotory are valid
@@ -29,7 +27,7 @@ module OCFL
29
27
  end
30
28
 
31
29
  def inventory_checksum_file
32
- directory + "inventory.json.sha512"
30
+ directory / "inventory.json.sha512"
33
31
  end
34
32
 
35
33
  def inventory_file_checksum
@@ -37,9 +35,8 @@ module OCFL
37
35
  end
38
36
 
39
37
  def inventory_file
40
- directory + "inventory.json"
38
+ directory / "inventory.json"
41
39
  end
42
40
  end
43
- # rubocop:enable Style/StringConcatenation
44
41
  end
45
42
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "digest"
4
-
5
3
  module OCFL
6
4
  module Object
7
5
  # Writes a OCFL Inventory to json on disk
@@ -23,11 +21,11 @@ module OCFL
23
21
  end
24
22
 
25
23
  def inventory_file
26
- path + "inventory.json"
24
+ path / "inventory.json"
27
25
  end
28
26
 
29
27
  def checksum_file
30
- path + "inventory.json.sha512"
28
+ path / "inventory.json.sha512"
31
29
  end
32
30
 
33
31
  def update_inventory_checksum
@@ -35,6 +33,5 @@ module OCFL
35
33
  File.write(checksum_file, "#{digest} inventory.json")
36
34
  end
37
35
  end
38
- # rubocop:enable Style/StringConcatenation
39
36
  end
40
37
  end
@@ -17,6 +17,10 @@ module OCFL
17
17
  attribute :state, Types::Hash.map(Types::String, Types::Array.of(Types::String))
18
18
  attribute? :message, Types::String
19
19
  attribute? :user, User
20
+
21
+ def file_names
22
+ state.values.flatten
23
+ end
20
24
  end
21
25
  end
22
26
  end
data/lib/ocfl/object.rb CHANGED
@@ -3,5 +3,7 @@
3
3
  module OCFL
4
4
  # An OCFL Object is a group of one or more content files and administrative information
5
5
  # https://ocfl.io/1.1/spec/#object-spec
6
- module Object; end
6
+ module Object
7
+ class FileNotFound < RuntimeError; end
8
+ end
7
9
  end
data/lib/ocfl/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OCFL
4
- VERSION = "0.4.1"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/ocfl.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "zeitwerk"
4
- require "json"
4
+ require "active_support"
5
+ require "active_support/core_ext/module/delegation"
6
+ require "digest"
5
7
  require "dry/monads"
6
8
  require "dry-schema"
7
9
  require "dry-struct"
8
- require "active_support"
9
- require "active_support/core_ext/module/delegation"
10
+ require "json"
10
11
 
11
12
  loader = Zeitwerk::Loader.for_gem
12
13
  loader.inflector.inflect("ocfl" => "OCFL")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ocfl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-05-07 00:00:00.000000000 Z
11
+ date: 2024-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport