ro-crate 0.4.11 → 0.4.15

Sign up to get free protection for your applications and to get access to all the features.
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