ro-crate 0.4.11 → 0.4.12

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: 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