ro-crate 0.4.11 → 0.4.12

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: 62f3979b325743d31720f803dbf75690ad6b7b9bcf9f2cbf63d8200a2bb204ba
4
- data.tar.gz: 3a229e06492a3f9a90721799f43929d6a5ca01fc0a0ff34d917eeebff037d9d4
3
+ metadata.gz: d9f081110d1e5b5e94674cb53d07262fbce50ec3f63437d5c4c1ecda8b04f835
4
+ data.tar.gz: 0cd710a9cd86063e706fd9cf338e5fdf55f5bc6993f306af4f50d6f9191a0d87
5
5
  SHA512:
6
- metadata.gz: 242b8e326756d51ab583726db7fb94763286f2764bdf1f0523cd9dab5fa560fe4da3016a4a4b84b0cc6bcef4ec096aeae7dc2fd344ef6cb5ee045cf99a954ef9
7
- data.tar.gz: 4abaece91d997ac398ee627f639fc98c924768c255bac1bb6c9ce7f43fc03f5b018a2fc657008863c65537592261e85f697ef2c7e200c20621ba41ff0a933541
6
+ metadata.gz: 8ce573fbd259108edbb528e24f0d9647cfee4454378d57a4b3392aaeb0dea80385b2828221a0979caa4733be0e2108a4a535725846f576c9ac97866f46911e47
7
+ data.tar.gz: ffc7aafbdccb3f4897ad2acae726714d7a313f02fd87e5c96173da2b12860dfa1318b9b323dde751e3ac10c9a66cf29effea383037bc4622eaf5113dd01bd426
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.12)
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)
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/
@@ -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,21 +238,21 @@ 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
@@ -255,6 +264,29 @@ module ROCrate
255
264
  binding
256
265
  end
257
266
 
267
+ ##
268
+ # Remove the entity from the RO-Crate.
269
+ #
270
+ # @param entity [Entity, String] The entity or ID of an entity to remove from the crate.
271
+ # @param remove_orphaned [Boolean] Should linked contextual entities also be removed from the crate they are left
272
+ # dangling (nothing else is linked to them)?
273
+ #
274
+ # @return [Entity, nil] The entity that was deleted, or nil if nothing was deleted.
275
+ def delete(entity, remove_orphaned: true)
276
+ entity = dereference(entity) if entity.is_a?(String)
277
+ return unless entity
278
+
279
+ deleted = data_entities.delete(entity) || contextual_entities.delete(entity)
280
+
281
+ if deleted && remove_orphaned
282
+ crate_entities = crate.linked_entities(deep: true)
283
+ to_remove = (entity.linked_entities(deep: true) - crate_entities)
284
+ to_remove.each(&:delete)
285
+ end
286
+
287
+ deleted
288
+ end
289
+
258
290
  private
259
291
 
260
292
  def full_entry_path(relative_path)
@@ -24,12 +24,13 @@ module ROCrate
24
24
  end
25
25
 
26
26
  ##
27
- # A map of all the files/directories associated with this DataEntity.
27
+ # The payload of all the files/directories associated with this DataEntity, mapped by their relative file path.
28
28
  #
29
29
  # @return [Hash{String => Entry}>] The key is the location within the crate, and the value is an Entry.
30
- def entries
30
+ def payload
31
31
  {}
32
32
  end
33
+ alias_method :entries, :payload
33
34
 
34
35
  ##
35
36
  # A disk-safe filepath based on the ID of this DataEntity.
@@ -28,11 +28,11 @@ module ROCrate
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,33 @@ 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 do |key, value|
246
+ value = [value] if value.is_a?(JSONLDHash)
247
+
248
+ if value.is_a?(Array)
249
+ value.each do |v|
250
+ if v.is_a?(JSONLDHash) && !linked.key?(v['@id'])
251
+ entity = v.dereference
252
+ linked[entity.id] = entity if entity
253
+ if deep
254
+ entity.linked_entities(deep: true, linked: linked).each do |e|
255
+ linked[e.id] = e
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
262
+
263
+ linked.values.compact
264
+ end
265
+
229
266
  private
230
267
 
231
268
  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))
@@ -56,7 +56,7 @@ module ROCrate
56
56
  # (for compatibility with Directory#entries)
57
57
  #
58
58
  # @return [Hash{String => Entry}>] The key is the location within the crate, and the value is an Entry.
59
- def entries
59
+ def payload
60
60
  remote? ? {} : { filepath => source }
61
61
  end
62
62
 
@@ -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,17 +13,6 @@ 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
  #
@@ -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
@@ -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.12'
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,26 @@ 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
308
330
  end
@@ -5,41 +5,41 @@ class DirectoryTest < Test::Unit::TestCase
5
5
  crate = ROCrate::Crate.new
6
6
  crate.add_directory(fixture_file('directory'))
7
7
 
8
- entries = crate.entries
8
+ payload = crate.payload
9
9
  base_path = ::File.dirname(fixture_file('directory'))
10
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/info.txt')), entries['directory/info.txt'].path
11
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/root.txt')), entries['directory/root.txt'].path
12
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data')), entries['directory/data'].path
13
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/info.txt')), entries['directory/data/info.txt'].path
14
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/nested.txt')), entries['directory/data/nested.txt'].path
15
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/binary.jpg')), entries['directory/data/binary.jpg'].path
10
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/info.txt')), payload['directory/info.txt'].path
11
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/root.txt')), payload['directory/root.txt'].path
12
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data')), payload['directory/data'].path
13
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/info.txt')), payload['directory/data/info.txt'].path
14
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/nested.txt')), payload['directory/data/nested.txt'].path
15
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/binary.jpg')), payload['directory/data/binary.jpg'].path
16
16
  end
17
17
 
18
18
  test 'adding directory via path' do
19
19
  crate = ROCrate::Crate.new
20
20
  crate.add_directory(fixture_file('directory').path.to_s)
21
21
 
22
- entries = crate.entries
22
+ payload = crate.payload
23
23
  base_path = ::File.dirname(fixture_file('directory'))
24
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/info.txt')), entries['directory/info.txt'].path
25
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/root.txt')), entries['directory/root.txt'].path
26
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data')), entries['directory/data'].path
27
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/info.txt')), entries['directory/data/info.txt'].path
28
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/nested.txt')), entries['directory/data/nested.txt'].path
29
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/binary.jpg')), entries['directory/data/binary.jpg'].path
24
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/info.txt')), payload['directory/info.txt'].path
25
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/root.txt')), payload['directory/root.txt'].path
26
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data')), payload['directory/data'].path
27
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/info.txt')), payload['directory/data/info.txt'].path
28
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/nested.txt')), payload['directory/data/nested.txt'].path
29
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/binary.jpg')), payload['directory/data/binary.jpg'].path
30
30
  end
31
31
 
32
32
  test 'adding to given path' do
33
33
  crate = ROCrate::Crate.new
34
34
  crate.add_directory(fixture_file('directory').path.to_s, 'fish')
35
35
 
36
- entries = crate.entries
36
+ payload = crate.payload
37
37
  base_path = ::File.dirname(fixture_file('directory'))
38
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/info.txt')), entries['fish/info.txt'].path
39
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/root.txt')), entries['fish/root.txt'].path
40
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data')), entries['fish/data'].path
41
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/info.txt')), entries['fish/data/info.txt'].path
42
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/nested.txt')), entries['fish/data/nested.txt'].path
43
- assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/binary.jpg')), entries['fish/data/binary.jpg'].path
38
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/info.txt')), payload['fish/info.txt'].path
39
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/root.txt')), payload['fish/root.txt'].path
40
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data')), payload['fish/data'].path
41
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/info.txt')), payload['fish/data/info.txt'].path
42
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/nested.txt')), payload['fish/data/nested.txt'].path
43
+ assert_equal ::File.expand_path(::File.join(base_path, 'directory/data/binary.jpg')), payload['fish/data/binary.jpg'].path
44
44
  end
45
45
  end
data/test/entity_test.rb CHANGED
@@ -91,4 +91,118 @@ class EntityTest < Test::Unit::TestCase
91
91
  assert_equal "http://www.data.com/my%20crate/", ROCrate::Crate.format_id('http://www.data.com/my%20crate'), 'Crate ID should end with /'
92
92
  assert_equal "http://www.data.com/my%20crate/", ROCrate::Crate.format_id('http://www.data.com/my%20crate/')
93
93
  end
94
+
95
+ test 'linked entities' do
96
+ crate = ROCrate::Crate.new
97
+ file = crate.add_file(StringIO.new(''), 'file')
98
+ person = crate.add_person('#bob', { name: 'Bob' })
99
+ file.author = person
100
+
101
+ linked = file.linked_entities
102
+ assert_include linked, person
103
+ assert_equal 1, linked.length
104
+
105
+ linked = crate.linked_entities
106
+ assert_include linked, file
107
+ assert_equal 1, linked.length
108
+
109
+ linked = crate.linked_entities(deep: true)
110
+ assert_include linked, file
111
+ assert_include linked, person
112
+ assert_equal 2, linked.length
113
+ end
114
+
115
+ test 'deleting entities removes linked entities' do
116
+ crate = ROCrate::Crate.new
117
+ file = crate.add_file(StringIO.new(''), 'file')
118
+ person = crate.add_person('#bob', { name: 'Bob' })
119
+ file.author = person
120
+
121
+ assert file.delete
122
+ assert_not_include crate.entities, file
123
+ assert_not_include crate.entities, person
124
+ end
125
+
126
+ test 'deleting entities does not remove dangling entities if option set' do
127
+ crate = ROCrate::Crate.new
128
+ file = crate.add_file(StringIO.new(''), 'file')
129
+ person = crate.add_person('#bob', { name: 'Bob' })
130
+ file.author = person
131
+
132
+ assert file.delete(remove_orphaned: false)
133
+ assert_not_include crate.entities, file
134
+ assert_include crate.entities, person
135
+ end
136
+
137
+ test 'creating files in various ways' do
138
+ stub_request(:get, 'http://example.com/external_ref.txt').to_return(status: 200, body: 'file contents')
139
+
140
+ crate = ROCrate::Crate.new
141
+
142
+ f1 = nil
143
+ Dir.chdir(fixture_dir) { f1 = ROCrate::File.new(crate, 'data.csv') }
144
+ refute f1.source.remote?
145
+ refute f1.source.directory?
146
+ assert_equal 20, f1.source.read.length
147
+ t = Tempfile.new('tf1')
148
+ f1.source.write_to(t)
149
+ t.rewind
150
+ assert_equal 20, t.read.length
151
+
152
+ f2 = ROCrate::File.new(crate, fixture_file('info.txt'), { author: crate.add_person('bob', name: 'Bob').reference })
153
+ refute f2.source.remote?
154
+ refute f2.source.directory?
155
+ assert f2.source.read
156
+ assert_equal 6, f2.source.read.length
157
+ t = Tempfile.new('tf2')
158
+ f2.source.write_to(t)
159
+ t.rewind
160
+ assert_equal 6, t.read.length
161
+
162
+ f3 = ROCrate::File.new(crate, 'http://example.com/external_ref.txt')
163
+ assert f3.source.remote?
164
+ refute f3.source.directory?
165
+ assert f3.source.read
166
+ assert_equal 13, f3.source.read.length
167
+ t = Tempfile.new('tf3')
168
+ f3.source.write_to(t)
169
+ t.rewind
170
+ assert_equal 13, t.read.length
171
+
172
+ f3 = ROCrate::File.new(crate, 'http://example.com/external_ref.txt')
173
+ assert f3.source.remote?
174
+ refute f3.source.directory?
175
+ assert f3.source.read
176
+ assert_equal 13, f3.source.read.length
177
+ t = Tempfile.new('tf3')
178
+ f3.source.write_to(t)
179
+ t.rewind
180
+ assert_equal 13, t.read.length
181
+ end
182
+
183
+ test 'assigning and checking type' do
184
+ crate = ROCrate::Crate.new
185
+ file = ROCrate::File.new(crate, 'data.csv')
186
+
187
+ assert file.has_type?('File')
188
+
189
+ file.type = ['File', 'Workflow']
190
+
191
+ assert file.has_type?('Workflow')
192
+ assert file.has_type?('File')
193
+ refute file.has_type?('Banana')
194
+ end
195
+
196
+ test 'inspecting object truncates very long property list' do
197
+ crate = ROCrate::Crate.new
198
+ entity = ROCrate::ContextualEntity.new(crate, 'hello')
199
+
200
+ assert entity.inspect.start_with?("<#ROCrate::ContextualEntity arcp://uuid,")
201
+
202
+ entity['veryLong'] = ('123456789a' * 100)
203
+
204
+ ins = entity.inspect
205
+ assert ins.length < 1000
206
+ assert ins.end_with?('...>')
207
+ end
94
208
  end
@@ -0,0 +1 @@
1
+ No, I am nested!
data/test/reader_test.rb CHANGED
@@ -88,12 +88,12 @@ class ReaderTest < Test::Unit::TestCase
88
88
  test 'reading from zip with directories' do
89
89
  crate = ROCrate::Reader.read_zip(fixture_file('directory.zip'))
90
90
 
91
- assert crate.entries['fish/info.txt']
92
- assert_equal '1234', crate.entries['fish/info.txt'].source.read.chomp
93
- assert crate.entries['fish/root.txt']
94
- assert crate.entries['fish/data/info.txt']
95
- assert crate.entries['fish/data/nested.txt']
96
- assert crate.entries['fish/data/binary.jpg']
91
+ assert crate.payload['fish/info.txt']
92
+ assert_equal '1234', crate.payload['fish/info.txt'].source.read.chomp
93
+ assert crate.payload['fish/root.txt']
94
+ assert crate.payload['fish/data/info.txt']
95
+ assert crate.payload['fish/data/nested.txt']
96
+ assert crate.payload['fish/data/binary.jpg']
97
97
  assert_equal ['./', 'fish/', 'ro-crate-metadata.jsonld', 'ro-crate-preview.html'], crate.entities.map(&:id).sort
98
98
  end
99
99
 
@@ -101,22 +101,22 @@ class ReaderTest < Test::Unit::TestCase
101
101
  Dir.mktmpdir('test-1234-banana') do |dir|
102
102
  crate = ROCrate::Reader.read_zip(fixture_file('directory.zip'), target_dir: dir)
103
103
 
104
- assert crate.entries['fish/info.txt']
105
- assert crate.entries['fish/info.txt'].source.to_s.include?('/test-1234-banana')
104
+ assert crate.payload['fish/info.txt']
105
+ assert crate.payload['fish/info.txt'].source.to_s.include?('/test-1234-banana')
106
106
  end
107
107
  end
108
108
 
109
109
  test 'reading from directory with directories' do
110
110
  crate = ROCrate::Reader.read_directory(fixture_file('directory_crate').path)
111
111
 
112
- assert crate.entries.values.all? { |e| e.is_a?(ROCrate::Entry) }
113
- assert crate.entries['fish/info.txt']
114
- assert_equal '1234', crate.entries['fish/info.txt'].source.read.chomp
115
- refute crate.entries['fish/root.txt'].directory?
116
- assert crate.entries['fish/data'].directory?
117
- assert crate.entries['fish/data/info.txt']
118
- refute crate.entries['fish/data/nested.txt'].remote?
119
- assert crate.entries['fish/data/binary.jpg']
112
+ assert crate.payload.values.all? { |e| e.is_a?(ROCrate::Entry) }
113
+ assert crate.payload['fish/info.txt']
114
+ assert_equal '1234', crate.payload['fish/info.txt'].source.read.chomp
115
+ refute crate.payload['fish/root.txt'].directory?
116
+ assert crate.payload['fish/data'].directory?
117
+ assert crate.payload['fish/data/info.txt']
118
+ refute crate.payload['fish/data/nested.txt'].remote?
119
+ assert crate.payload['fish/data/binary.jpg']
120
120
  assert_equal ['./', 'fish/', 'ro-crate-metadata.jsonld', 'ro-crate-preview.html'], crate.entities.map(&:id).sort
121
121
  end
122
122
 
@@ -162,37 +162,38 @@ class ReaderTest < Test::Unit::TestCase
162
162
  assert ext_file.source.is_a?(ROCrate::RemoteEntry)
163
163
  assert_equal 'http://example.com/external_ref.txt', ext_file.id
164
164
  assert_equal 'file contents', ext_file.source.read
165
+ assert crate.preview.source.source.is_a?(ROCrate::PreviewGenerator)
165
166
  end
166
167
 
167
168
  test 'reading from directory with unlisted files' do
168
169
  crate = ROCrate::Reader.read_directory(fixture_file('sparse_directory_crate').path)
169
170
 
170
- assert_equal 11, crate.entries.count
171
- assert crate.entries['listed_file.txt']
172
- assert crate.entries['unlisted_file.txt']
173
- assert crate.entries['fish']
174
- assert_equal '1234', crate.entries['fish/info.txt'].source.read.chomp
175
- refute crate.entries['fish/root.txt'].directory?
176
- assert crate.entries['fish/data'].directory?
177
- assert crate.entries['fish/data/info.txt']
178
- refute crate.entries['fish/data/nested.txt'].remote?
179
- assert crate.entries['fish/data/binary.jpg']
171
+ assert_equal 11, crate.payload.count
172
+ assert crate.payload['listed_file.txt']
173
+ assert crate.payload['unlisted_file.txt']
174
+ assert crate.payload['fish']
175
+ assert_equal '1234', crate.payload['fish/info.txt'].source.read.chomp
176
+ refute crate.payload['fish/root.txt'].directory?
177
+ assert crate.payload['fish/data'].directory?
178
+ assert crate.payload['fish/data/info.txt']
179
+ refute crate.payload['fish/data/nested.txt'].remote?
180
+ assert crate.payload['fish/data/binary.jpg']
180
181
  assert_equal ['./', 'listed_file.txt', 'ro-crate-metadata.jsonld', 'ro-crate-preview.html'], crate.entities.map(&:id).sort
181
182
  end
182
183
 
183
184
  test 'reading from a zip with unlisted files' do
184
185
  crate = ROCrate::Reader.read_zip(fixture_file('sparse_directory_crate.zip').path)
185
186
 
186
- assert_equal 11, crate.entries.count
187
- assert crate.entries['listed_file.txt']
188
- assert crate.entries['unlisted_file.txt']
189
- assert crate.entries['fish']
190
- assert_equal '1234', crate.entries['fish/info.txt'].source.read.chomp
191
- refute crate.entries['fish/root.txt'].directory?
192
- assert crate.entries['fish/data'].directory?
193
- assert crate.entries['fish/data/info.txt']
194
- refute crate.entries['fish/data/nested.txt'].remote?
195
- assert crate.entries['fish/data/binary.jpg']
187
+ assert_equal 11, crate.payload.count
188
+ assert crate.payload['listed_file.txt']
189
+ assert crate.payload['unlisted_file.txt']
190
+ assert crate.payload['fish']
191
+ assert_equal '1234', crate.payload['fish/info.txt'].source.read.chomp
192
+ refute crate.payload['fish/root.txt'].directory?
193
+ assert crate.payload['fish/data'].directory?
194
+ assert crate.payload['fish/data/info.txt']
195
+ refute crate.payload['fish/data/nested.txt'].remote?
196
+ assert crate.payload['fish/data/binary.jpg']
196
197
  assert_equal ['./', 'listed_file.txt', 'ro-crate-metadata.jsonld', 'ro-crate-preview.html'], crate.entities.map(&:id).sort
197
198
  end
198
199
 
@@ -201,30 +202,30 @@ class ReaderTest < Test::Unit::TestCase
201
202
  string_io.write(::File.read(fixture_file('sparse_directory_crate.zip').path))
202
203
  string_io.rewind
203
204
  assert string_io.is_a?(StringIO)
204
- assert_equal 11, ROCrate::Reader.read_zip(string_io).entries.count
205
+ assert_equal 11, ROCrate::Reader.read_zip(string_io).payload.count
205
206
 
206
207
  path = Pathname.new(fixture_file('sparse_directory_crate.zip').path)
207
208
  assert path.is_a?(Pathname)
208
- assert_equal 11, ROCrate::Reader.read_zip(path).entries.count
209
+ assert_equal 11, ROCrate::Reader.read_zip(path).payload.count
209
210
 
210
211
  file = ::File.open(fixture_file('sparse_directory_crate.zip').path)
211
212
  assert file.is_a?(::File)
212
- assert_equal 11, ROCrate::Reader.read_zip(file).entries.count
213
+ assert_equal 11, ROCrate::Reader.read_zip(file).payload.count
213
214
 
214
215
  string = fixture_file('sparse_directory_crate.zip').path
215
216
  assert string.is_a?(String)
216
- assert_equal 11, ROCrate::Reader.read_zip(string).entries.count
217
+ assert_equal 11, ROCrate::Reader.read_zip(string).payload.count
217
218
  end
218
219
 
219
220
  test 'reading from zip where the crate root is nested somewhere within' do
220
221
  crate = ROCrate::Reader.read_zip(fixture_file('nested_directory.zip'))
221
222
 
222
- assert crate.entries['fish/info.txt']
223
- assert_equal '1234', crate.entries['fish/info.txt'].source.read.chomp
224
- assert crate.entries['fish/root.txt']
225
- assert crate.entries['fish/data/info.txt']
226
- assert crate.entries['fish/data/nested.txt']
227
- assert crate.entries['fish/data/binary.jpg']
223
+ assert crate.payload['fish/info.txt']
224
+ assert_equal '1234', crate.payload['fish/info.txt'].source.read.chomp
225
+ assert crate.payload['fish/root.txt']
226
+ assert crate.payload['fish/data/info.txt']
227
+ assert crate.payload['fish/data/nested.txt']
228
+ assert crate.payload['fish/data/binary.jpg']
228
229
  assert_equal ['./', 'fish/', 'ro-crate-metadata.json', 'ro-crate-preview.html'], crate.entities.map(&:id).sort
229
230
  end
230
231
 
@@ -251,4 +252,10 @@ class ReaderTest < Test::Unit::TestCase
251
252
  }
252
253
  ], context
253
254
  end
255
+
256
+ test 'existing preview is used even if not mentioned in metadata' do
257
+ crate = ROCrate::Reader.read_zip(fixture_file('biobb_hpc_workflows-condapack.zip').path)
258
+ assert crate.preview.source.source.is_a?(Pathname)
259
+ assert_equal 80526, crate.preview.source.read.length
260
+ end
254
261
  end
data/test/test_helper.rb CHANGED
@@ -6,5 +6,9 @@ require 'ro_crate'
6
6
  require 'webmock/test_unit'
7
7
 
8
8
  def fixture_file(name, *args)
9
- ::File.open(::File.join(::File.dirname(__FILE__), 'fixtures', name), *args)
9
+ ::File.open(::File.join(fixture_dir, name), *args)
10
+ end
11
+
12
+ def fixture_dir
13
+ ::File.join(::File.dirname(__FILE__), 'fixtures')
10
14
  end
data/test/writer_test.rb CHANGED
@@ -47,8 +47,12 @@ class WriterTest < Test::Unit::TestCase
47
47
  end
48
48
 
49
49
  test 'writing to zip' do
50
+ # Remote entries should not be written, so this 500 error should not affect anything.
51
+ stub_request(:get, 'http://example.com/external_ref.txt').to_return(status: 500)
52
+
50
53
  crate = ROCrate::Crate.new
51
54
  crate.add_file(fixture_file('info.txt'))
55
+ crate.add_file('http://example.com/external_ref.txt')
52
56
  crate.add_file(fixture_file('data.csv'), 'directory/data.csv')
53
57
 
54
58
  Tempfile.create do |file|
@@ -57,6 +61,7 @@ class WriterTest < Test::Unit::TestCase
57
61
  Zip::File.open(file) do |zipfile|
58
62
  assert zipfile.file.exist?(ROCrate::Metadata::IDENTIFIER)
59
63
  assert zipfile.file.exist?(ROCrate::Preview::IDENTIFIER)
64
+ refute zipfile.file.exist?('external_ref.txt')
60
65
  assert_equal 6, zipfile.file.size('info.txt')
61
66
  assert_equal 20, zipfile.file.size('directory/data.csv')
62
67
  end
@@ -152,4 +157,36 @@ class WriterTest < Test::Unit::TestCase
152
157
  end
153
158
  end
154
159
  end
160
+
161
+ test 'writing with conflicting paths in payload obeys specificity rules' do
162
+ crate = ROCrate::Crate.new
163
+
164
+ # Payload from crate
165
+ crate.add_all(fixture_file('directory').path, false)
166
+ Dir.mktmpdir do |dir|
167
+ ROCrate::Writer.new(crate).write(dir)
168
+
169
+ assert_equal "5678\n", ::File.read(::File.join(dir, 'data', 'info.txt'))
170
+ end
171
+
172
+ # Payload from crate + directory
173
+ crate.add_directory(fixture_file('conflicting_data_directory').path.to_s, 'data')
174
+ Dir.mktmpdir do |dir|
175
+ ROCrate::Writer.new(crate).write(dir)
176
+
177
+ assert_equal 'abcd', ::File.read(::File.join(dir, 'data', 'info.txt')), 'Directory payload should take priority over Crate.'
178
+ assert_equal "No, I am nested!\n", ::File.read(::File.join(dir, 'data', 'nested.txt')), 'Directory payload should take priority over Crate.'
179
+ assert ::File.exist?(::File.join(dir, 'data', 'binary.jpg'))
180
+ end
181
+
182
+ # Payload from crate + directory + file
183
+ crate.add_file(StringIO.new('xyz'), 'data/info.txt')
184
+ Dir.mktmpdir do |dir|
185
+ ROCrate::Writer.new(crate).write(dir)
186
+
187
+ assert_equal 'xyz', ::File.read(::File.join(dir, 'data', 'info.txt')), 'File payload should take priority over Crate and Directory.'
188
+ assert_equal "No, I am nested!\n", ::File.read(::File.join(dir, 'data', 'nested.txt')), 'Directory payload should take priority over Crate.'
189
+ assert ::File.exist?(::File.join(dir, 'data', 'binary.jpg'))
190
+ end
191
+ end
155
192
  end
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ro-crate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.11
4
+ version: 0.4.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Finn Bacall
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-30 00:00:00.000000000 Z
11
+ date: 2021-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.7'
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: 2.7.0
22
+ version: '2.9'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '2.7'
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: 2.7.0
32
+ version: '2.9'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: rubyzip
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -146,6 +152,9 @@ files:
146
152
  - test/crate_test.rb
147
153
  - test/directory_test.rb
148
154
  - test/entity_test.rb
155
+ - test/fixtures/biobb_hpc_workflows-condapack.zip
156
+ - test/fixtures/conflicting_data_directory/info.txt
157
+ - test/fixtures/conflicting_data_directory/nested.txt
149
158
  - test/fixtures/crate-spec1.1/file with spaces.txt
150
159
  - test/fixtures/crate-spec1.1/ro-crate-metadata.json
151
160
  - test/fixtures/data.csv