ro-crate 0.4.11 → 0.4.15

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/tests.yml +5 -0
  3. data/.ruby-version +1 -1
  4. data/Gemfile.lock +7 -7
  5. data/README.md +2 -0
  6. data/lib/ro_crate/json_ld_hash.rb +5 -0
  7. data/lib/ro_crate/model/crate.rb +53 -6
  8. data/lib/ro_crate/model/data_entity.rb +20 -7
  9. data/lib/ro_crate/model/directory.rb +5 -5
  10. data/lib/ro_crate/model/entity.rb +43 -4
  11. data/lib/ro_crate/model/entry.rb +2 -2
  12. data/lib/ro_crate/model/file.rb +6 -2
  13. data/lib/ro_crate/model/remote_entry.rb +2 -13
  14. data/lib/ro_crate/reader.rb +11 -8
  15. data/lib/ro_crate/writer.rb +4 -4
  16. data/lib/ro_crate.rb +1 -1
  17. data/ro_crate.gemspec +2 -2
  18. data/test/crate_test.rb +74 -3
  19. data/test/directory_test.rb +21 -21
  20. data/test/entity_test.rb +117 -3
  21. data/test/fixtures/biobb_hpc_workflows-condapack.zip +0 -0
  22. data/test/fixtures/conflicting_data_directory/info.txt +1 -0
  23. data/test/fixtures/conflicting_data_directory/nested.txt +1 -0
  24. data/test/fixtures/misc_data_entity_crate/ro-crate-metadata.json +33 -0
  25. data/test/fixtures/ro-crate-galaxy-sortchangecase/ro-crate-metadata.json +10 -3
  26. data/test/fixtures/unlinked_entity_crate/LICENSE +176 -0
  27. data/test/fixtures/unlinked_entity_crate/README.md +2 -0
  28. data/test/fixtures/unlinked_entity_crate/ro-crate-metadata.json +150 -0
  29. data/test/fixtures/unlinked_entity_crate/sort-and-change-case.ga +118 -0
  30. data/test/fixtures/unlinked_entity_crate/test/test1/input.bed +3 -0
  31. data/test/fixtures/unlinked_entity_crate/test/test1/output_exp.bed +3 -0
  32. data/test/fixtures/unlinked_entity_crate/test/test1/sort-and-change-case-test.yml +8 -0
  33. data/test/fixtures/workflow-0.2.0/ro-crate-metadata.jsonld +5 -5
  34. data/test/fixtures/workflow-0.2.0.zip +0 -0
  35. data/test/reader_test.rb +86 -58
  36. data/test/test_helper.rb +5 -1
  37. data/test/writer_test.rb +48 -0
  38. metadata +24 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62f3979b325743d31720f803dbf75690ad6b7b9bcf9f2cbf63d8200a2bb204ba
4
- data.tar.gz: 3a229e06492a3f9a90721799f43929d6a5ca01fc0a0ff34d917eeebff037d9d4
3
+ metadata.gz: 92f4b8cd765215082edea1be4e3c065a29ddcdbbc43a79576a4782ba8d797f07
4
+ data.tar.gz: d8936210124988edd8fd942c978a93ea6ee6f17fa1bcbfd3b6528e7475143ca1
5
5
  SHA512:
6
- metadata.gz: 242b8e326756d51ab583726db7fb94763286f2764bdf1f0523cd9dab5fa560fe4da3016a4a4b84b0cc6bcef4ec096aeae7dc2fd344ef6cb5ee045cf99a954ef9
7
- data.tar.gz: 4abaece91d997ac398ee627f639fc98c924768c255bac1bb6c9ce7f43fc03f5b018a2fc657008863c65537592261e85f697ef2c7e200c20621ba41ff0a933541
6
+ metadata.gz: cf88d846278c037fd4437db9f1cd5f3fd31d6901eeb161a38417a5919edaf9e013f28cfadea7a59bf77a687c5946f540ee6cfa28ccc001d473dac41eaf3c5f3e
7
+ data.tar.gz: ab8413b46ad23145ca6d68f2554cad6eacb9f2ecd349e890b6380295029da5426110e447c863170d997071a6dba3f76c72006ca6a90b9a456ac96858e114aa86
@@ -3,6 +3,10 @@ on: [push, pull_request]
3
3
  jobs:
4
4
  run-tests:
5
5
  runs-on: ubuntu-latest
6
+ strategy:
7
+ matrix:
8
+ ruby: ['2.6', '2.7']
9
+ fail-fast: false
6
10
  steps:
7
11
  - name: Checkout
8
12
  uses: actions/checkout@v2.3.1 # If you're using actions/checkout@v2 you must set persist-credentials to false in most cases for the deployment to work correctly.
@@ -11,6 +15,7 @@ jobs:
11
15
  - name: Setup Ruby
12
16
  uses: ruby/setup-ruby@v1
13
17
  with:
18
+ ruby-version: ${{ matrix.ruby }}
14
19
  bundler-cache: true # runs 'bundle install' and caches installed gems automatically
15
20
  - name: Run tests
16
21
  run: bundle exec rake test
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.6.6
1
+ ruby-2.7.5
data/Gemfile.lock CHANGED
@@ -1,21 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ro-crate (0.4.11)
5
- addressable (~> 2.7.0)
4
+ ro-crate (0.4.15)
5
+ addressable (>= 2.7, < 2.9)
6
6
  rubyzip (~> 2.0.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- addressable (2.7.0)
11
+ addressable (2.8.0)
12
12
  public_suffix (>= 2.0.2, < 5.0)
13
13
  crack (0.4.3)
14
14
  safe_yaml (~> 1.0.0)
15
15
  docile (1.3.5)
16
16
  hashdiff (1.0.1)
17
- power_assert (0.4.1)
18
- public_suffix (4.0.3)
17
+ power_assert (1.1.3)
18
+ public_suffix (4.0.6)
19
19
  rake (13.0.0)
20
20
  rubyzip (2.0.0)
21
21
  safe_yaml (1.0.5)
@@ -25,7 +25,7 @@ GEM
25
25
  simplecov_json_formatter (~> 0.1)
26
26
  simplecov-html (0.12.3)
27
27
  simplecov_json_formatter (0.1.2)
28
- test-unit (3.2.3)
28
+ test-unit (3.2.9)
29
29
  power_assert
30
30
  webmock (3.8.3)
31
31
  addressable (>= 2.3.6)
@@ -45,4 +45,4 @@ DEPENDENCIES
45
45
  yard (~> 0.9.25)
46
46
 
47
47
  BUNDLED WITH
48
- 1.17.3
48
+ 2.3.6
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # ro-crate-ruby
2
2
 
3
+ ![Tests](https://github.com/ResearchObject/ro-crate-ruby/actions/workflows/tests.yml/badge.svg)
4
+
3
5
  This is a WIP gem for creating, manipulating and reading RO-Crates (conforming to version 1.1 of the specification).
4
6
 
5
7
  * RO-Crate - https://researchobject.github.io/ro-crate/
@@ -2,6 +2,11 @@ module ROCrate
2
2
  ##
3
3
  # A wrapper class for Hash that adds methods to dereference Entities within an RO-Crate.
4
4
  class JSONLDHash < ::Hash
5
+ def self.[](graph, content = {})
6
+ @graph = graph
7
+ super(stringified(content))
8
+ end
9
+
5
10
  def initialize(graph, content = {})
6
11
  @graph = graph
7
12
  super()
@@ -25,6 +25,15 @@ module ROCrate
25
25
  super(self, nil, id, properties)
26
26
  end
27
27
 
28
+ ##
29
+ # Lookup an Entity using the given ID (in this Entity's crate).
30
+ #
31
+ # @param id [String] The ID to query.
32
+ # @return [Entity, nil]
33
+ def dereference(id)
34
+ entities.detect { |e| e.canonical_id == crate.resolve_id(id) } if id
35
+ end
36
+
28
37
  ##
29
38
  # Create a new file and add it to the crate.
30
39
  #
@@ -229,32 +238,70 @@ module ROCrate
229
238
  entity.class.new(crate, entity.id, entity.raw_properties)
230
239
  end
231
240
 
232
- alias_method :own_entries, :entries
241
+ alias_method :own_payload, :payload
233
242
  ##
234
- # # The RO-Crate's "payload" of the crate - a map of all the files/directories contained in the RO-Crate, where the
235
- # key is the destination path within the crate and the value is an Entry where the source data can be read.
243
+ # The file payload of the RO-Crate - a map of all the files/directories contained in the RO-Crate, where the
244
+ # key is the path relative to the crate's root, and the value is an Entry where the source data can be read.
236
245
  #
237
246
  # @return [Hash{String => Entry}>]
238
- def entries
247
+ def payload
239
248
  # Gather a map of entries, starting from the crate itself, then any directory data entities, then finally any
240
249
  # file data entities. This ensures in the case of a conflict, the more "specific" data entities take priority.
241
- entries = own_entries
250
+ entries = own_payload
242
251
  non_self_entities = default_entities.reject { |e| e == self }
243
252
  sorted_entities = (non_self_entities | data_entities).sort_by { |e| e.is_a?(ROCrate::Directory) ? 0 : 1 }
244
253
 
245
254
  sorted_entities.each do |entity|
246
- entity.entries.each do |path, entry|
255
+ entity.payload.each do |path, entry|
247
256
  entries[path] = entry
248
257
  end
249
258
  end
250
259
 
251
260
  entries
252
261
  end
262
+ alias_method :entries, :payload
253
263
 
254
264
  def get_binding
255
265
  binding
256
266
  end
257
267
 
268
+ ##
269
+ # Remove the entity from the RO-Crate.
270
+ #
271
+ # @param entity [Entity, String] The entity or ID of an entity to remove from the crate.
272
+ # @param remove_orphaned [Boolean] Should linked contextual entities also be removed from the crate they are left
273
+ # dangling (nothing else is linked to them)?
274
+ #
275
+ # @return [Entity, nil] The entity that was deleted, or nil if nothing was deleted.
276
+ def delete(entity, remove_orphaned: true)
277
+ entity = dereference(entity) if entity.is_a?(String)
278
+ return unless entity
279
+
280
+ deleted = data_entities.delete(entity) || contextual_entities.delete(entity)
281
+
282
+ if deleted && remove_orphaned
283
+ crate_entities = crate.linked_entities(deep: true)
284
+ to_remove = (entity.linked_entities(deep: true) - crate_entities)
285
+ to_remove.each(&:delete)
286
+ end
287
+
288
+ deleted
289
+ end
290
+
291
+ ##
292
+ # Remove any contextual entities that are not linked from any other entity.
293
+ # Optionally takes a block to decide whether the given entity should be removed or not, otherwise removes all
294
+ # unlinked entities.
295
+ # @yieldparam [ContextualEntity] entity An unlinked contextual entity.
296
+ # @yieldparam [Boolean] remove Should this entity be removed?
297
+ #
298
+ # @return [Array<ContextualEntity>] The entities that were removed.
299
+ def gc(&block)
300
+ unlinked_entities = contextual_entities - metadata.linked_entities(deep: true)
301
+
302
+ unlinked_entities.select(&block).each { |e| e.delete(remove_orphaned: false) }
303
+ end
304
+
258
305
  private
259
306
 
260
307
  def full_entry_path(relative_path)
@@ -5,10 +5,6 @@ module ROCrate
5
5
  class DataEntity < Entity
6
6
  properties(%w[name contentSize dateModified encodingFormat identifier sameAs author])
7
7
 
8
- def self.format_local_id(id)
9
- super.chomp('/')
10
- end
11
-
12
8
  ##
13
9
  # Return an appropriate specialization of DataEntity for the given properties.
14
10
  # @param props [Hash] Set of properties to try and infer the type from.
@@ -18,18 +14,35 @@ module ROCrate
18
14
  type = [type] unless type.is_a?(Array)
19
15
  if type.include?('Dataset')
20
16
  ROCrate::Directory
21
- else
17
+ elsif type.include?('File')
22
18
  ROCrate::File
19
+ else
20
+ self
23
21
  end
24
22
  end
25
23
 
26
24
  ##
27
- # A map of all the files/directories associated with this DataEntity.
25
+ # Create a new ROCrate::DataEntity. This entity represents something that is neither a file or directory, but
26
+ # still constitutes part of the crate.
27
+ # PLEASE NOTE, the new data entity will not be added to the crate. To do this, call Crate#add_data_entity.
28
+ #
29
+ # @param crate [Crate] The RO-Crate that owns this directory.
30
+ # @param source [nil] Ignored. For compatibility with the File and Directory constructor signatures.
31
+ # @param id [String, nil] An ID to identify this DataEntity, or nil to auto-generate an appropriate one,
32
+ # (or determine via the properties param)
33
+ # @param properties [Hash{String => Object}] A hash of JSON-LD properties to associate with this DataEntity.
34
+ def initialize(crate, source = nil, id = nil, properties = {})
35
+ super(crate, id, properties)
36
+ end
37
+
38
+ ##
39
+ # The payload of all the files/directories associated with this DataEntity, mapped by their relative file path.
28
40
  #
29
41
  # @return [Hash{String => Entry}>] The key is the location within the crate, and the value is an Entry.
30
- def entries
42
+ def payload
31
43
  {}
32
44
  end
45
+ alias_method :entries, :payload
33
46
 
34
47
  ##
35
48
  # A disk-safe filepath based on the ID of this DataEntity.
@@ -3,11 +3,11 @@ module ROCrate
3
3
  # A data entity that represents a directory of potentially many files and subdirectories (or none).
4
4
  class Directory < DataEntity
5
5
  def self.format_local_id(id)
6
- super + '/'
6
+ super.chomp('/') + '/'
7
7
  end
8
8
 
9
9
  ##
10
- # Create a new Directory. PLEASE NOTE, the new directory will not be added to the crate. To do this, call
10
+ # Create a new ROCrate::Directory. PLEASE NOTE, the new directory will not be added to the crate. To do this, call
11
11
  # Crate#add_data_entity, or just use Crate#add_directory.
12
12
  #
13
13
  # @param crate [Crate] The RO-Crate that owns this directory.
@@ -24,15 +24,15 @@ module ROCrate
24
24
  crate_path = source_directory.basename.to_s if crate_path.nil?
25
25
  end
26
26
 
27
- super(crate, crate_path, properties)
27
+ super(crate, nil, crate_path, properties)
28
28
  end
29
29
 
30
30
  ##
31
- # The "payload" of this directory - a map of all the files/directories, where the key is the destination path
31
+ # The payload of this directory - a map of all the files/directories, where the key is the destination path
32
32
  # within the crate and the value is an Entry where the source data can be read.
33
33
  #
34
34
  # @return [Hash{String => Entry}>]
35
- def entries
35
+ def payload
36
36
  entries = {}
37
37
  entries[filepath.chomp('/')] = @entry if @entry
38
38
 
@@ -129,16 +129,26 @@ module ROCrate
129
129
  # @param id [String] The ID to query.
130
130
  # @return [Entity, nil]
131
131
  def dereference(id)
132
- crate.entities.detect { |e| e.canonical_id == crate.resolve_id(id) } if id
132
+ crate.dereference(id)
133
133
  end
134
-
135
134
  alias_method :get, :dereference
136
135
 
136
+ ##
137
+ # Remove this entity from the RO-Crate.
138
+ #
139
+ # @param remove_orphaned [Boolean] Should linked contextual entities also be removed from the crate (if nothing else is linked to them)?
140
+ #
141
+ # @return [Entity, nil] This entity, or nil if nothing was deleted.
142
+ def delete(remove_orphaned: true)
143
+ crate.delete(self, remove_orphaned: remove_orphaned)
144
+ end
145
+
137
146
  def id
138
147
  @properties['@id']
139
148
  end
140
149
 
141
150
  def id=(id)
151
+ @canonical_id = nil
142
152
  @properties['@id'] = self.class.format_id(id)
143
153
  end
144
154
 
@@ -190,13 +200,13 @@ module ROCrate
190
200
  #
191
201
  # @return [Addressable::URI]
192
202
  def canonical_id
193
- crate.resolve_id(id)
203
+ @canonical_id ||= crate.resolve_id(id)
194
204
  end
195
205
 
196
206
  ##
197
207
  # Is this entity local to the crate or an external reference?
198
208
  #
199
- # @return [boolean]
209
+ # @return [Boolean]
200
210
  def external?
201
211
  crate.canonical_id.host != canonical_id.host
202
212
  end
@@ -226,6 +236,35 @@ module ROCrate
226
236
  @properties.has_type?(type)
227
237
  end
228
238
 
239
+ ##
240
+ # Gather a list of entities linked to this one through its properties.
241
+ # @param deep [Boolean] If false, only consider direct links, otherwise consider transitive links.
242
+ # @param linked [Hash{String => Entity}] Discovered entities, mapped by their ID, to avoid loops when recursing.
243
+ # @return [Array<Entity>]
244
+ def linked_entities(deep: false, linked: {})
245
+ properties.each_key do |key|
246
+ value = properties[key] # We're doing this to call the JSONLDHash#[] method which wraps
247
+ value = [value] if value.is_a?(JSONLDHash)
248
+
249
+ if value.is_a?(Array)
250
+ value.each do |v|
251
+ if v.is_a?(JSONLDHash) && !linked.key?(v['@id'])
252
+ entity = v.dereference
253
+ next unless entity
254
+ linked[entity.id] = entity
255
+ if deep
256
+ entity.linked_entities(deep: true, linked: linked).each do |e|
257
+ linked[e.id] = e
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
264
+
265
+ linked.values.compact
266
+ end
267
+
229
268
  private
230
269
 
231
270
  def default_properties
@@ -14,10 +14,10 @@ module ROCrate
14
14
  end
15
15
 
16
16
  ##
17
- # Write the source to the destination via a buffer.
17
+ # Write the entry's source to the destination via a buffer.
18
18
  #
19
19
  # @param dest [#write] An IO-like destination to write to.
20
- def write(dest)
20
+ def write_to(dest)
21
21
  input = source
22
22
  input = input.open('rb') if input.is_a?(Pathname)
23
23
  while (buff = input.read(4096))
@@ -2,6 +2,10 @@ module ROCrate
2
2
  ##
3
3
  # A data entity that represents a single file.
4
4
  class File < DataEntity
5
+ def self.format_local_id(id)
6
+ super.chomp('/')
7
+ end
8
+
5
9
  ##
6
10
  # Create a new ROCrate::File. PLEASE NOTE, the new file will not be added to the crate. To do this, call
7
11
  # Crate#add_data_entity, or just use Crate#add_file.
@@ -32,7 +36,7 @@ module ROCrate
32
36
  crate_path = source.to_s if source.is_a?(URI) && source.absolute?
33
37
  end
34
38
 
35
- super(crate, crate_path, properties)
39
+ super(crate, nil, crate_path, properties)
36
40
 
37
41
  if source.is_a?(URI) && source.absolute?
38
42
  @entry = RemoteEntry.new(source)
@@ -56,7 +60,7 @@ module ROCrate
56
60
  # (for compatibility with Directory#entries)
57
61
  #
58
62
  # @return [Hash{String => Entry}>] The key is the location within the crate, and the value is an Entry.
59
- def entries
63
+ def payload
60
64
  remote? ? {} : { filepath => source }
61
65
  end
62
66
 
@@ -2,7 +2,7 @@ module ROCrate
2
2
  ##
3
3
  # A class to represent a reference within an RO-Crate, to a remote file held on the internet somewhere.
4
4
  # It handles the actual reading/writing of bytes.
5
- class RemoteEntry
5
+ class RemoteEntry < Entry
6
6
  attr_reader :uri
7
7
 
8
8
  ##
@@ -13,22 +13,11 @@ module ROCrate
13
13
  @uri = uri
14
14
  end
15
15
 
16
- def write(dest)
17
- raise 'Cannot write to a remote entry!'
18
- end
19
-
20
- ##
21
- # Read from the source.
22
- #
23
- def read
24
- source.read
25
- end
26
-
27
16
  ##
28
17
  # @return [IO] An IO object for the remote resource.
29
18
  #
30
19
  def source
31
- open(uri)
20
+ uri.open
32
21
  end
33
22
 
34
23
  ##
@@ -181,9 +181,10 @@ module ROCrate
181
181
  crate.metadata.properties = entity_hash.delete(ROCrate::Metadata::IDENTIFIER)
182
182
  crate.metadata.context = context
183
183
  preview_properties = entity_hash.delete(ROCrate::Preview::IDENTIFIER)
184
- if preview_properties
185
- preview_path = ::File.join(source, ROCrate::Preview::IDENTIFIER)
186
- crate.preview = ROCrate::Preview.new(crate, ::File.exists?(preview_path) ? Pathname.new(preview_path) : nil, preview_properties)
184
+ preview_path = ::File.join(source, ROCrate::Preview::IDENTIFIER)
185
+ preview_path = ::File.exists?(preview_path) ? Pathname.new(preview_path) : nil
186
+ if preview_properties || preview_path
187
+ crate.preview = ROCrate::Preview.new(crate, preview_path, preview_properties || {})
187
188
  end
188
189
  crate.add_all(source, false)
189
190
  end
@@ -244,10 +245,10 @@ module ROCrate
244
245
  fullpath = ::File.join(source, i)
245
246
  path = Pathname.new(fullpath) if ::File.exist?(fullpath)
246
247
  end
247
- unless path
248
- warn "Missing file/directory: #{id}, skipping..."
249
- return nil
250
- end
248
+ # unless path
249
+ # warn "Missing file/directory: #{id}, skipping..."
250
+ # return nil
251
+ # end
251
252
  end
252
253
 
253
254
  entity_class.new(crate, path, decoded_id, entity_props)
@@ -261,7 +262,9 @@ module ROCrate
261
262
  # mapped by its @id, or nil if nothing is found.
262
263
  def self.extract_metadata_entity(entities)
263
264
  key = entities.detect do |_, props|
264
- props.dig('conformsTo', '@id')&.start_with?(ROCrate::Metadata::RO_CRATE_BASE)
265
+ conforms = props['conformsTo']
266
+ conforms = [conforms] unless conforms.is_a?(Array)
267
+ conforms.compact.any? { |c| c['@id']&.start_with?(ROCrate::Metadata::RO_CRATE_BASE) }
265
268
  end&.first
266
269
 
267
270
  return entities.delete(key) if key
@@ -16,14 +16,14 @@ module ROCrate
16
16
  # @param overwrite [Boolean] Whether or not to overwrite existing files.
17
17
  def write(dir, overwrite: true)
18
18
  FileUtils.mkdir_p(dir) # Make any parent directories
19
- @crate.entries.each do |path, entry|
19
+ @crate.payload.each do |path, entry|
20
20
  fullpath = ::File.join(dir, path)
21
21
  next if !overwrite && ::File.exist?(fullpath)
22
22
  next if entry.directory?
23
23
  FileUtils.mkdir_p(::File.dirname(fullpath))
24
24
  temp = Tempfile.new('ro-crate-temp')
25
25
  begin
26
- entry.write(temp)
26
+ entry.write_to(temp)
27
27
  temp.close
28
28
  FileUtils.mv(temp, fullpath)
29
29
  ensure
@@ -38,9 +38,9 @@ module ROCrate
38
38
  # @param destination [String, ::File] The destination where to write the RO-Crate zip.
39
39
  def write_zip(destination)
40
40
  Zip::File.open(destination, Zip::File::CREATE) do |zip|
41
- @crate.entries.each do |path, entry|
41
+ @crate.payload.each do |path, entry|
42
42
  next if entry.directory?
43
- zip.get_output_stream(path) { |s| entry.write(s) }
43
+ zip.get_output_stream(path) { |s| entry.write_to(s) }
44
44
  end
45
45
  end
46
46
  end
data/lib/ro_crate.rb CHANGED
@@ -10,8 +10,8 @@ require 'ro_crate/json_ld_hash'
10
10
  require 'ro_crate/model/entity'
11
11
  require 'ro_crate/model/data_entity'
12
12
  require 'ro_crate/model/file'
13
- require 'ro_crate/model/remote_entry'
14
13
  require 'ro_crate/model/entry'
14
+ require 'ro_crate/model/remote_entry'
15
15
  require 'ro_crate/model/directory'
16
16
  require 'ro_crate/model/metadata'
17
17
  require 'ro_crate/model/preview_generator'
data/ro_crate.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'ro-crate'
3
- s.version = '0.4.11'
3
+ s.version = '0.4.15'
4
4
  s.summary = 'Create, manipulate, read RO-Crates.'
5
5
  s.authors = ['Finn Bacall']
6
6
  s.email = 'finn.bacall@manchester.ac.uk'
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.homepage = 'https://github.com/ResearchObject/ro-crate-ruby'
9
9
  s.require_paths = ['lib']
10
10
  s.licenses = ['MIT']
11
- s.add_runtime_dependency 'addressable', '~> 2.7.0'
11
+ s.add_runtime_dependency 'addressable', '>= 2.7', '< 2.9'
12
12
  s.add_runtime_dependency 'rubyzip', '~> 2.0.0'
13
13
  s.add_development_dependency 'rake', '~> 13.0.0'
14
14
  s.add_development_dependency 'test-unit', '~> 3.2.3'
data/test/crate_test.rb CHANGED
@@ -215,7 +215,7 @@ class CrateTest < Test::Unit::TestCase
215
215
  crate = ROCrate::Crate.new
216
216
  entities = crate.add_all(fixture_file('directory').path, include_hidden: true)
217
217
 
218
- paths = crate.entries.keys
218
+ paths = crate.payload.keys
219
219
  assert_equal 11, paths.length
220
220
  assert_includes paths, 'data'
221
221
  assert_includes paths, 'root.txt'
@@ -250,7 +250,7 @@ class CrateTest < Test::Unit::TestCase
250
250
 
251
251
  assert_empty entities
252
252
 
253
- paths = crate.entries.keys
253
+ paths = crate.payload.keys
254
254
  assert_equal 11, paths.length
255
255
  assert_includes paths, 'data'
256
256
  assert_includes paths, 'root.txt'
@@ -278,7 +278,7 @@ class CrateTest < Test::Unit::TestCase
278
278
  crate = ROCrate::Crate.new
279
279
  entities = crate.add_all(fixture_file('directory').path)
280
280
 
281
- paths = crate.entries.keys
281
+ paths = crate.payload.keys
282
282
  assert_equal 8, paths.length
283
283
  assert_includes paths, 'data'
284
284
  assert_includes paths, 'root.txt'
@@ -305,4 +305,75 @@ class CrateTest < Test::Unit::TestCase
305
305
 
306
306
  assert_equal "5678\n", crate.dereference('data/info.txt').source.read
307
307
  end
308
+
309
+ test 'can delete entities' do
310
+ crate = ROCrate::Crate.new
311
+ file = crate.add_file(StringIO.new(''), 'file')
312
+ person = crate.add_person('#bob', { name: 'Bob' })
313
+ file.author = person
314
+
315
+ assert crate.delete(file)
316
+ assert_not_include crate.entities, file
317
+ assert_not_include crate.entities, person
318
+ end
319
+
320
+ test 'can delete entities by id' do
321
+ crate = ROCrate::Crate.new
322
+ file = crate.add_file(StringIO.new(''), 'file')
323
+ person = crate.add_person('#bob', { name: 'Bob' })
324
+ file.author = person
325
+
326
+ assert crate.delete('file')
327
+ assert_not_include crate.entities, file
328
+ assert_not_include crate.entities, person
329
+ end
330
+
331
+ test 'legacy entries method still returns same result as payload' do
332
+ crate = ROCrate::Crate.new
333
+ crate.add_all(fixture_file('directory').path)
334
+
335
+ paths = crate.entries.keys
336
+ assert_equal 8, paths.length
337
+ assert_includes paths, 'data'
338
+ assert_includes paths, 'root.txt'
339
+ assert_includes paths, 'info.txt'
340
+ assert_includes paths, 'data/binary.jpg'
341
+ assert_includes paths, 'data/info.txt'
342
+ assert_includes paths, 'data/nested.txt'
343
+ assert_not_includes paths, '.dotfile'
344
+ assert_not_includes paths, '.dir'
345
+ assert_not_includes paths, '.dir/test.txt'
346
+ assert_includes paths, 'ro-crate-metadata.json'
347
+ assert_includes paths, 'ro-crate-preview.html'
348
+
349
+ assert_equal crate.payload.keys, crate.entries.keys
350
+ end
351
+
352
+ test 'can garbage collect unlinked entities' do
353
+ crate = ROCrate::Reader.read(fixture_file('unlinked_entity_crate').path)
354
+
355
+ unlinked = crate.gc
356
+ assert_equal 2, unlinked.length
357
+ ids = unlinked.map(&:id)
358
+ assert_includes ids, '#joe'
359
+ assert_includes ids, '#joehouse'
360
+ assert_not_includes crate.contextual_entities, unlinked.first
361
+ assert_not_includes crate.contextual_entities, unlinked.last
362
+ assert_nil crate.get('#joe')
363
+ assert_nil crate.get('#joehouse')
364
+ end
365
+
366
+ test 'can conditionally garbage collect unlinked entities using a block' do
367
+ crate = ROCrate::Reader.read(fixture_file('unlinked_entity_crate').path)
368
+
369
+ unlinked = crate.gc do |entity|
370
+ entity.is_a?(ROCrate::Person)
371
+ end
372
+ assert_equal 1, unlinked.length
373
+ ids = unlinked.map(&:id)
374
+ assert_includes ids, '#joe'
375
+ assert_not_includes crate.contextual_entities, unlinked.first
376
+ assert_nil crate.get('#joe')
377
+ assert crate.get('#joehouse')
378
+ end
308
379
  end