ro-crate 0.5.2 → 0.6.0
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 +4 -4
- data/.github/workflows/docs.yml +1 -1
- data/.github/workflows/tests.yml +2 -2
- data/.ruby-version +1 -1
- data/README.md +3 -3
- data/Rakefile +0 -9
- data/lib/ro_crate/model/crate.rb +9 -2
- data/lib/ro_crate/model/directory.rb +2 -3
- data/lib/ro_crate/model/metadata.rb +38 -5
- data/lib/ro_crate/reader.rb +78 -24
- data/lib/ro_crate/ro-crate-preview.html.erb +28 -12
- data/lib/ro_crate/writer.rb +7 -3
- data/ro_crate.gemspec +7 -6
- data/test/crate_test.rb +29 -0
- data/test/fixtures/just_a_zip.zip +0 -0
- data/test/fixtures/multi_metadata_crate.crate.zip +0 -0
- data/test/fixtures/singleton-haspart/a_file +1 -0
- data/test/fixtures/singleton-haspart/ro-crate-metadata.json +57 -0
- data/test/fixtures/unsafe/absolute1.zip +0 -0
- data/test/fixtures/unsafe/relative0.zip +0 -0
- data/test/preview_test.rb +48 -0
- data/test/reader_test.rb +65 -8
- data/test/test_helper.rb +28 -1
- data/test/writer_test.rb +35 -0
- metadata +29 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2c4aabc50994541bbcbb2071b805f457e47c9e30176299fdde40741aa5ff1094
|
|
4
|
+
data.tar.gz: 0f4d7df6b4eb21961446ce2e3c05fe337fa7ed9e161eb21a7651b8ea6292f988
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f590b0e08e5f813474e4c46e3230b01999d17cf349645b8c86285c8dbe3cd371563405e1e1143a7b68d115764373847ace352029beed404a5cc069e64ef40dab
|
|
7
|
+
data.tar.gz: 972a1e14b6faa4986bad9138fdd39861bf54af81dd5678354f84cc0f4bbc3b50252e38cf948d013bb6a979365dedc74e81c48661115f0cd3f408fcdd0744f777
|
data/.github/workflows/docs.yml
CHANGED
|
@@ -7,7 +7,7 @@ jobs:
|
|
|
7
7
|
runs-on: ubuntu-latest
|
|
8
8
|
steps:
|
|
9
9
|
- name: Checkout
|
|
10
|
-
uses: actions/checkout@
|
|
10
|
+
uses: actions/checkout@v6
|
|
11
11
|
with:
|
|
12
12
|
persist-credentials: false
|
|
13
13
|
- name: Setup Ruby
|
data/.github/workflows/tests.yml
CHANGED
|
@@ -5,11 +5,11 @@ jobs:
|
|
|
5
5
|
runs-on: ubuntu-latest
|
|
6
6
|
strategy:
|
|
7
7
|
matrix:
|
|
8
|
-
ruby: ['2.
|
|
8
|
+
ruby: ['2.7', '3.0', '3.1', '3.2', '3.3', '3.4', '4.0']
|
|
9
9
|
fail-fast: false
|
|
10
10
|
steps:
|
|
11
11
|
- name: Checkout
|
|
12
|
-
uses: actions/checkout@
|
|
12
|
+
uses: actions/checkout@v6
|
|
13
13
|
with:
|
|
14
14
|
persist-credentials: false
|
|
15
15
|
- name: Setup Ruby
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
ruby-3.
|
|
1
|
+
ruby-3.4.9
|
data/README.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
This is a WIP gem for creating, manipulating and reading RO-Crates (conforming to version 1.
|
|
5
|
+
This is a WIP gem for creating, manipulating and reading RO-Crates (conforming to version 1.2 of the specification). RO-Crates produced by older versions (1.0, 1.1) of the spec can still be read.
|
|
6
6
|
|
|
7
|
-
* RO-Crate - https://researchobject.
|
|
8
|
-
* RO-Crate spec (1.
|
|
7
|
+
* RO-Crate - https://www.researchobject.org/ro-crate/
|
|
8
|
+
* RO-Crate spec (1.2) - https://www.researchobject.org/ro-crate/specification/1.2/
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
data/Rakefile
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
require 'bundler/gem_tasks'
|
|
2
2
|
require 'rake/testtask'
|
|
3
|
-
require 'rdoc/task'
|
|
4
3
|
|
|
5
4
|
desc 'Default: run unit tests.'
|
|
6
5
|
task default: :test
|
|
@@ -13,14 +12,6 @@ Rake::TestTask.new(:test) do |t|
|
|
|
13
12
|
t.warning = false
|
|
14
13
|
end
|
|
15
14
|
|
|
16
|
-
Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
17
|
-
rdoc.rdoc_dir = 'rdoc'
|
|
18
|
-
rdoc.title = 'Devise'
|
|
19
|
-
rdoc.options << '--line-numbers' << '--inline-source'
|
|
20
|
-
rdoc.rdoc_files.include('README.md')
|
|
21
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
22
|
-
end
|
|
23
|
-
|
|
24
15
|
task :console do
|
|
25
16
|
require 'irb'
|
|
26
17
|
require 'irb/completion'
|
data/lib/ro_crate/model/crate.rb
CHANGED
|
@@ -21,9 +21,16 @@ module ROCrate
|
|
|
21
21
|
|
|
22
22
|
##
|
|
23
23
|
# Initialize an empty RO-Crate.
|
|
24
|
-
|
|
24
|
+
#
|
|
25
|
+
# @param id [String] The crate's identifier.
|
|
26
|
+
# @param properties [Hash] Initial properties for the root data entity.
|
|
27
|
+
# @param version [String] RO-Crate spec version to declare (default: ROCrate::Metadata::DEFAULT_VERSION).
|
|
28
|
+
# Must be one of ROCrate::Metadata::SUPPORTED_VERSIONS.
|
|
29
|
+
def initialize(id = IDENTIFIER, properties = {}, version: ROCrate::Metadata::DEFAULT_VERSION)
|
|
30
|
+
ROCrate::Metadata.warn_unrecognized_version(version)
|
|
25
31
|
@data_entities = Set.new
|
|
26
32
|
@contextual_entities = Set.new
|
|
33
|
+
@metadata_version = version
|
|
27
34
|
super(self, nil, id, properties)
|
|
28
35
|
end
|
|
29
36
|
|
|
@@ -168,7 +175,7 @@ module ROCrate
|
|
|
168
175
|
#
|
|
169
176
|
# @return [Metadata]
|
|
170
177
|
def metadata
|
|
171
|
-
@metadata ||= ROCrate::Metadata.new(self)
|
|
178
|
+
@metadata ||= ROCrate::Metadata.new(self, {}, version: @metadata_version || ROCrate::Metadata::DEFAULT_VERSION)
|
|
172
179
|
end
|
|
173
180
|
|
|
174
181
|
##
|
|
@@ -74,9 +74,8 @@ module ROCrate
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def list_all_files(source_directory, include_hidden: false)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
Dir.chdir(source_directory) { Dir.glob(*args) }.reject do |path|
|
|
77
|
+
flags = include_hidden ? ::File::FNM_DOTMATCH : 0
|
|
78
|
+
Dir.glob('**/*', flags, base: source_directory).reject do |path|
|
|
80
79
|
path == '.' || path == '..' || path.end_with?('/.')
|
|
81
80
|
end
|
|
82
81
|
end
|
|
@@ -5,13 +5,46 @@ module ROCrate
|
|
|
5
5
|
IDENTIFIER = 'ro-crate-metadata.json'.freeze
|
|
6
6
|
IDENTIFIER_1_0 = 'ro-crate-metadata.jsonld'.freeze # 1.0 spec identifier
|
|
7
7
|
RO_CRATE_BASE = 'https://w3id.org/ro/crate/'
|
|
8
|
-
CONTEXT = "#{RO_CRATE_BASE}1.1/context".freeze
|
|
9
|
-
SPEC = "#{RO_CRATE_BASE}1.1".freeze
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
SUPPORTED_VERSIONS = %w[1.0 1.0-DRAFT 1.1 1.1-DRAFT 1.2 1.2-DRAFT].freeze
|
|
10
|
+
DEFAULT_VERSION = '1.2'.freeze
|
|
11
|
+
|
|
12
|
+
CONTEXT = "#{RO_CRATE_BASE}#{DEFAULT_VERSION}/context".freeze
|
|
13
|
+
SPEC = "#{RO_CRATE_BASE}#{DEFAULT_VERSION}".freeze
|
|
14
|
+
|
|
15
|
+
attr_reader :version
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# Emit a warning if the given version is not in SUPPORTED_VERSIONS.
|
|
19
|
+
# Does not raise — unrecognized versions are still accepted so the library
|
|
20
|
+
# stays forward-compatible with future spec versions that need no changes.
|
|
21
|
+
def self.warn_unrecognized_version(v)
|
|
22
|
+
return if SUPPORTED_VERSIONS.include?(v)
|
|
23
|
+
warn "Unrecognized RO-Crate version: #{v.inspect}. Known versions: #{SUPPORTED_VERSIONS.join(', ')}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def initialize(crate, properties = {}, version: DEFAULT_VERSION)
|
|
27
|
+
self.class.warn_unrecognized_version(version)
|
|
28
|
+
@version = version
|
|
12
29
|
super(crate, nil, IDENTIFIER, properties)
|
|
13
30
|
end
|
|
14
31
|
|
|
32
|
+
##
|
|
33
|
+
# Update the spec version this metadata declares.
|
|
34
|
+
# Used by the Reader to preserve the version of a parsed crate.
|
|
35
|
+
def version=(v)
|
|
36
|
+
self.class.warn_unrecognized_version(v)
|
|
37
|
+
@version = v
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def context_url
|
|
41
|
+
"#{RO_CRATE_BASE}#{@version}/context"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def spec_url
|
|
45
|
+
"#{RO_CRATE_BASE}#{@version}"
|
|
46
|
+
end
|
|
47
|
+
|
|
15
48
|
##
|
|
16
49
|
# Generate the crate's `ro-crate-metadata.jsonld`.
|
|
17
50
|
# @return [String] The rendered JSON-LD as a "prettified" string.
|
|
@@ -21,7 +54,7 @@ module ROCrate
|
|
|
21
54
|
end
|
|
22
55
|
|
|
23
56
|
def context
|
|
24
|
-
@context ||
|
|
57
|
+
@context || context_url
|
|
25
58
|
end
|
|
26
59
|
|
|
27
60
|
def context= c
|
|
@@ -39,7 +72,7 @@ module ROCrate
|
|
|
39
72
|
'@id' => IDENTIFIER,
|
|
40
73
|
'@type' => 'CreativeWork',
|
|
41
74
|
'about' => { '@id' => crate.id },
|
|
42
|
-
'conformsTo' => { '@id' =>
|
|
75
|
+
'conformsTo' => { '@id' => spec_url }
|
|
43
76
|
}
|
|
44
77
|
end
|
|
45
78
|
end
|
data/lib/ro_crate/reader.rb
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
require 'zip/version'
|
|
2
|
+
|
|
1
3
|
module ROCrate
|
|
2
4
|
##
|
|
3
5
|
# A class to handle reading of RO-Crates from Zip files or directories.
|
|
4
6
|
class Reader
|
|
7
|
+
LEGACY_EXTRACT = Zip::VERSION.start_with?('2.').freeze
|
|
8
|
+
|
|
5
9
|
##
|
|
6
10
|
# Reads an RO-Crate from a directory or zip file.
|
|
7
11
|
#
|
|
@@ -42,15 +46,15 @@ module ROCrate
|
|
|
42
46
|
#
|
|
43
47
|
# @param source [#read] An IO-like object containing a Zip file.
|
|
44
48
|
# @param target [String, ::File, Pathname] The target directory where the file should be unzipped.
|
|
45
|
-
def self.unzip_io_to(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
def self.unzip_io_to(source, target)
|
|
50
|
+
target_path = Pathname(target)
|
|
51
|
+
Zip::InputStream.open(source) do |input|
|
|
52
|
+
while (entry = input.get_next_entry)
|
|
53
|
+
next if entry.name_is_directory?
|
|
54
|
+
dest = safe_join(target_path, entry.name)
|
|
55
|
+
next if dest.exist?
|
|
56
|
+
FileUtils.mkdir_p(dest.dirname)
|
|
57
|
+
::File.binwrite(dest, input.read)
|
|
54
58
|
end
|
|
55
59
|
end
|
|
56
60
|
end
|
|
@@ -60,15 +64,14 @@ module ROCrate
|
|
|
60
64
|
#
|
|
61
65
|
# @param source [String, ::File, Pathname] The location of the zip file.
|
|
62
66
|
# @param target [String, ::File, Pathname] The target directory where the file should be unzipped.
|
|
63
|
-
def self.unzip_file_to(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
end
|
|
67
|
+
def self.unzip_file_to(source, target)
|
|
68
|
+
target_path = Pathname(target)
|
|
69
|
+
Zip::File.open(source) do |zipfile|
|
|
70
|
+
zipfile.each do |entry|
|
|
71
|
+
dest = safe_join(target_path, entry.name)
|
|
72
|
+
next if dest.exist?
|
|
73
|
+
FileUtils.mkdir_p(dest.dirname)
|
|
74
|
+
LEGACY_EXTRACT ? entry.extract(dest) : entry.extract(entry.name, destination_directory: target_path)
|
|
72
75
|
end
|
|
73
76
|
end
|
|
74
77
|
end
|
|
@@ -87,6 +90,7 @@ module ROCrate
|
|
|
87
90
|
|
|
88
91
|
# Traverse the unzipped directory to try and find the crate's root
|
|
89
92
|
root_dir = detect_root_directory(target_dir)
|
|
93
|
+
raise ROCrate::ReadException, "No metadata found!" unless root_dir
|
|
90
94
|
|
|
91
95
|
read_directory(root_dir)
|
|
92
96
|
end
|
|
@@ -184,7 +188,10 @@ module ROCrate
|
|
|
184
188
|
def self.initialize_crate(entity_hash, source, crate_class: ROCrate::Crate, context:)
|
|
185
189
|
crate_class.new.tap do |crate|
|
|
186
190
|
crate.properties = entity_hash.delete(ROCrate::Crate::IDENTIFIER)
|
|
187
|
-
|
|
191
|
+
metadata_props = entity_hash.delete(ROCrate::Metadata::IDENTIFIER)
|
|
192
|
+
crate.metadata.properties = metadata_props
|
|
193
|
+
parsed_version = extract_version(metadata_props)
|
|
194
|
+
crate.metadata.version = parsed_version if parsed_version
|
|
188
195
|
crate.metadata.context = context
|
|
189
196
|
preview_properties = entity_hash.delete(ROCrate::Preview::IDENTIFIER)
|
|
190
197
|
preview_path = ::File.join(source, ROCrate::Preview::IDENTIFIER)
|
|
@@ -204,7 +211,9 @@ module ROCrate
|
|
|
204
211
|
# @param entity_hash [Hash] A Hash containing all the entities in the @graph, mapped by their @id.
|
|
205
212
|
# @return [Array<ROCrate::File, ROCrate::Directory>] The extracted DataEntity objects.
|
|
206
213
|
def self.extract_data_entities(crate, source, entity_hash)
|
|
207
|
-
|
|
214
|
+
parts = crate.raw_properties['hasPart'] || []
|
|
215
|
+
parts = [parts] unless parts.is_a?(Array)
|
|
216
|
+
parts.map do |ref|
|
|
208
217
|
entity_props = entity_hash.delete(ref['@id'])
|
|
209
218
|
next unless entity_props
|
|
210
219
|
entity_class = ROCrate::DataEntity.specialize(entity_props)
|
|
@@ -263,7 +272,7 @@ module ROCrate
|
|
|
263
272
|
|
|
264
273
|
##
|
|
265
274
|
# Extract the metadata entity from the entity hash, according to the rules defined here:
|
|
266
|
-
# https://www.researchobject.org/ro-crate/1.
|
|
275
|
+
# https://www.researchobject.org/ro-crate/specification/1.2/root-data-entity.html#finding-the-root-data-entity
|
|
267
276
|
# @return [nil, Hash{String => Hash}] A Hash containing (hopefully) one value, the metadata entity's properties
|
|
268
277
|
# mapped by its @id, or nil if nothing is found.
|
|
269
278
|
def self.extract_metadata_entity(entities)
|
|
@@ -282,6 +291,24 @@ module ROCrate
|
|
|
282
291
|
entities.delete(ROCrate::Metadata::IDENTIFIER_1_0))
|
|
283
292
|
end
|
|
284
293
|
|
|
294
|
+
##
|
|
295
|
+
# Extract the spec version from the metadata entity's `conformsTo`.
|
|
296
|
+
# Looks for an `@id` matching `https://w3id.org/ro/crate/<version>` and returns `<version>`.
|
|
297
|
+
# @param metadata_props [Hash, nil] The metadata entity's properties.
|
|
298
|
+
# @return [String, nil] The parsed version string, or nil if not found.
|
|
299
|
+
def self.extract_version(metadata_props)
|
|
300
|
+
return nil unless metadata_props
|
|
301
|
+
conforms = metadata_props['conformsTo']
|
|
302
|
+
conforms = [conforms] unless conforms.is_a?(Array)
|
|
303
|
+
conforms.compact.each do |c|
|
|
304
|
+
id = c.is_a?(Hash) ? c['@id'] : c
|
|
305
|
+
next unless id&.start_with?(ROCrate::Metadata::RO_CRATE_BASE)
|
|
306
|
+
version = id.sub(ROCrate::Metadata::RO_CRATE_BASE, '').split('/').first
|
|
307
|
+
return version if version && !version.empty?
|
|
308
|
+
end
|
|
309
|
+
nil
|
|
310
|
+
end
|
|
311
|
+
|
|
285
312
|
##
|
|
286
313
|
# Extract the ro-crate-preview entity from the entity hash.
|
|
287
314
|
# @return [Hash{String => Hash}] A Hash containing the preview entity's properties mapped by its @id, or nil if nothing is found.
|
|
@@ -291,7 +318,7 @@ module ROCrate
|
|
|
291
318
|
|
|
292
319
|
##
|
|
293
320
|
# Extract the root entity from the entity hash, according to the rules defined here:
|
|
294
|
-
# https://www.researchobject.org/ro-crate/1.
|
|
321
|
+
# https://www.researchobject.org/ro-crate/specification/1.2/root-data-entity.html#finding-the-root-data-entity
|
|
295
322
|
# @return [Hash{String => Hash}] A Hash containing (hopefully) one value, the root entity's properties,
|
|
296
323
|
# mapped by its @id.
|
|
297
324
|
def self.extract_root_entity(entities)
|
|
@@ -301,21 +328,48 @@ module ROCrate
|
|
|
301
328
|
end
|
|
302
329
|
|
|
303
330
|
##
|
|
304
|
-
# Finds an RO-Crate's root directory (where `ro-crate-
|
|
331
|
+
# Finds an RO-Crate's root directory (where `ro-crate-metadata.json` is located) within a given directory.
|
|
305
332
|
#
|
|
306
333
|
# @param source [String, ::File, Pathname] The location of the directory.
|
|
307
334
|
# @return [Pathname, nil] The path to the root, or nil if not found.
|
|
308
335
|
def self.detect_root_directory(source)
|
|
309
|
-
|
|
336
|
+
queue = [source]
|
|
337
|
+
until queue.empty?
|
|
338
|
+
entry = Pathname(queue.shift)
|
|
310
339
|
if entry.file?
|
|
311
340
|
name = entry.basename.to_s
|
|
312
341
|
if name == ROCrate::Metadata::IDENTIFIER || name == ROCrate::Metadata::IDENTIFIER_1_0
|
|
313
342
|
return entry.parent
|
|
314
343
|
end
|
|
344
|
+
elsif entry.directory?
|
|
345
|
+
queue += entry.children
|
|
315
346
|
end
|
|
316
347
|
end
|
|
317
348
|
|
|
318
349
|
nil
|
|
319
350
|
end
|
|
351
|
+
|
|
352
|
+
##
|
|
353
|
+
# Safely joins a desired file path onto a base directory, raising an exception if the path attempts to traverse
|
|
354
|
+
# outside it.
|
|
355
|
+
#
|
|
356
|
+
# @param base [Pathname] The base directory where the file will go.
|
|
357
|
+
# @param path [String] The desired file path.
|
|
358
|
+
#
|
|
359
|
+
# @raise [ROCrate::ReadException] Raised if an unsafe path is given.
|
|
360
|
+
#
|
|
361
|
+
# @return [Pathname] The safely joined base + path.
|
|
362
|
+
def self.safe_join(base, path)
|
|
363
|
+
dest = base.join(path)
|
|
364
|
+
# Guard against zip-slip attacks.
|
|
365
|
+
begin
|
|
366
|
+
unsafe = dest.expand_path.relative_path_from(base.expand_path).each_filename.first == '..'
|
|
367
|
+
rescue ArgumentError # Handle unjoinable paths, e.g. on different drives.
|
|
368
|
+
unsafe = true
|
|
369
|
+
end
|
|
370
|
+
raise ROCrate::ReadException, "Unsafe path in zip entry: #{path}" if unsafe
|
|
371
|
+
|
|
372
|
+
dest
|
|
373
|
+
end
|
|
320
374
|
end
|
|
321
375
|
end
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
<%
|
|
2
|
+
def entity_to_html(entity)
|
|
3
|
+
if entity.is_a?(Array)
|
|
4
|
+
if entity.length == 1
|
|
5
|
+
entity_to_html(entity.first)
|
|
6
|
+
else
|
|
7
|
+
"<ul><li>#{entity.map { |e| entity_to_html(e) }.join('</li><li>')}</li></ul>"
|
|
8
|
+
end
|
|
9
|
+
elsif entity.is_a?(ROCrate::Entity)
|
|
10
|
+
label = entity['name'] || entity.id
|
|
11
|
+
if entity.external?
|
|
12
|
+
"<a href=\"#{entity.id}\" target=\"_blank\">#{label}</a>"
|
|
13
|
+
else
|
|
14
|
+
label
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
entity
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
%>
|
|
1
21
|
<!DOCTYPE html>
|
|
2
22
|
<html lang="en">
|
|
3
23
|
<head>
|
|
@@ -18,36 +38,32 @@
|
|
|
18
38
|
<dl>
|
|
19
39
|
<% if author %>
|
|
20
40
|
<dt>Author</dt>
|
|
21
|
-
<dd><%= author %></dd>
|
|
41
|
+
<dd><%= entity_to_html author %></dd>
|
|
22
42
|
<% end %>
|
|
23
43
|
<% if contact_point %>
|
|
24
44
|
<dt>Contact</dt>
|
|
25
|
-
<dd><%= contact_point %></dd>
|
|
45
|
+
<dd><%= entity_to_html contact_point %></dd>
|
|
26
46
|
<% end %>
|
|
27
47
|
<% if publisher %>
|
|
28
48
|
<dt>Publisher</dt>
|
|
29
|
-
<dd><%= publisher %></dd>
|
|
49
|
+
<dd><%= entity_to_html publisher %></dd>
|
|
30
50
|
<% end %>
|
|
31
51
|
<% if license %>
|
|
32
52
|
<dt>License</dt>
|
|
33
|
-
<dd><%= license %></dd>
|
|
53
|
+
<dd><%= entity_to_html license %></dd>
|
|
34
54
|
<% end %>
|
|
35
55
|
</dl>
|
|
36
56
|
|
|
37
57
|
<h2>Contents</h2>
|
|
38
58
|
<ul>
|
|
39
59
|
<% data_entities.each do |data_entity| %>
|
|
40
|
-
<li
|
|
41
|
-
|
|
42
|
-
<strong><a href="<%= data_entity.id %>" target="_blank"><%= data_entity.name || data_entity.id %></a></strong>
|
|
43
|
-
<% else %>
|
|
44
|
-
<strong><%= data_entity.name || data_entity.id %></strong>
|
|
45
|
-
<% end %>
|
|
60
|
+
<li>
|
|
61
|
+
<strong><%= entity_to_html data_entity %></strong>
|
|
46
62
|
<% if data_entity.content_size %>
|
|
47
|
-
<br/>Size: <%= data_entity.content_size %>
|
|
63
|
+
<br/>Size: <%= entity_to_html data_entity.content_size %>
|
|
48
64
|
<% end %>
|
|
49
65
|
<% if data_entity.encoding_format %>
|
|
50
|
-
<br/>Format: <%= data_entity.encoding_format %>
|
|
66
|
+
<br/>Format: <%= entity_to_html data_entity.encoding_format %>
|
|
51
67
|
<% end %>
|
|
52
68
|
</li>
|
|
53
69
|
<% end %>
|
data/lib/ro_crate/writer.rb
CHANGED
|
@@ -14,9 +14,11 @@ module ROCrate
|
|
|
14
14
|
#
|
|
15
15
|
# @param dir [String] A path for the directory for the crate to be written to. All parent directories will be created.
|
|
16
16
|
# @param overwrite [Boolean] Whether or not to overwrite existing files.
|
|
17
|
-
|
|
17
|
+
# @param skip_preview [Boolean] Whether or not to skip generation of the RO-Crate preview HTML file.
|
|
18
|
+
def write(dir, overwrite: true, skip_preview: false)
|
|
18
19
|
FileUtils.mkdir_p(dir) # Make any parent directories
|
|
19
20
|
@crate.payload.each do |path, entry|
|
|
21
|
+
next if skip_preview && entry&.source.is_a?(ROCrate::PreviewGenerator)
|
|
20
22
|
fullpath = ::File.join(dir, path)
|
|
21
23
|
next if !overwrite && ::File.exist?(fullpath)
|
|
22
24
|
next if entry.directory?
|
|
@@ -40,10 +42,12 @@ module ROCrate
|
|
|
40
42
|
# Write the crate to a zip file.
|
|
41
43
|
#
|
|
42
44
|
# @param destination [String, ::File] The destination where to write the RO-Crate zip.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
# @param skip_preview [Boolean] Whether or not to skip generation of the RO-Crate preview HTML file.
|
|
46
|
+
def write_zip(destination, skip_preview: false)
|
|
47
|
+
Zip::File.open(destination, create: true) do |zip|
|
|
45
48
|
@crate.payload.each do |path, entry|
|
|
46
49
|
next if entry.directory?
|
|
50
|
+
next if skip_preview && entry&.source.is_a?(ROCrate::PreviewGenerator)
|
|
47
51
|
if entry.symlink?
|
|
48
52
|
zip.add(path, entry.path) if entry.path
|
|
49
53
|
else
|
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.
|
|
3
|
+
s.version = '0.6.0'
|
|
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,12 +8,13 @@ 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.
|
|
12
|
-
s.add_runtime_dependency '
|
|
13
|
-
s.
|
|
11
|
+
s.required_ruby_version = '>= 2.7.0'
|
|
12
|
+
s.add_runtime_dependency 'addressable', '>= 2.7', '< 3'
|
|
13
|
+
s.add_runtime_dependency 'rubyzip', '>= 2.3', '< 4'
|
|
14
|
+
s.add_development_dependency 'rake', '~> 13.4.2'
|
|
14
15
|
s.add_development_dependency 'test-unit', '~> 3.5.3'
|
|
15
16
|
s.add_development_dependency 'simplecov', '~> 0.21.2'
|
|
16
17
|
s.add_development_dependency 'yard', '~> 0.9.25'
|
|
17
|
-
s.add_development_dependency 'webmock', '~> 3.
|
|
18
|
-
s.add_development_dependency 'rexml', '~> 3.
|
|
18
|
+
s.add_development_dependency 'webmock', '~> 3.26.2'
|
|
19
|
+
s.add_development_dependency 'rexml', '~> 3.4.4'
|
|
19
20
|
end
|
data/test/crate_test.rb
CHANGED
|
@@ -377,4 +377,33 @@ class CrateTest < Test::Unit::TestCase
|
|
|
377
377
|
assert_nil crate.get('#joe')
|
|
378
378
|
assert crate.get('#joehouse')
|
|
379
379
|
end
|
|
380
|
+
|
|
381
|
+
test 'defaults to RO-Crate spec 1.2' do
|
|
382
|
+
crate = ROCrate::Crate.new
|
|
383
|
+
assert_equal '1.2', crate.metadata.version
|
|
384
|
+
assert_equal 'https://w3id.org/ro/crate/1.2/context', crate.metadata.context
|
|
385
|
+
assert_equal 'https://w3id.org/ro/crate/1.2', crate.metadata.spec_url
|
|
386
|
+
assert_equal({ '@id' => 'https://w3id.org/ro/crate/1.2' }, crate.metadata.properties['conformsTo'])
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
test 'can write older spec version' do
|
|
390
|
+
crate = ROCrate::Crate.new(ROCrate::Crate::IDENTIFIER, {}, version: '1.1')
|
|
391
|
+
assert_equal '1.1', crate.metadata.version
|
|
392
|
+
assert_equal 'https://w3id.org/ro/crate/1.1/context', crate.metadata.context
|
|
393
|
+
assert_equal({ '@id' => 'https://w3id.org/ro/crate/1.1' }, crate.metadata.properties['conformsTo'])
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
test 'warns but accepts unrecognized spec version' do
|
|
397
|
+
original_stderr = $stderr
|
|
398
|
+
begin
|
|
399
|
+
$stderr = StringIO.new
|
|
400
|
+
crate = ROCrate::Crate.new(ROCrate::Crate::IDENTIFIER, {}, version: '1.5')
|
|
401
|
+
err = $stderr.string
|
|
402
|
+
assert_match(/Unrecognized RO-Crate version/, err)
|
|
403
|
+
assert_equal '1.5', crate.metadata.version
|
|
404
|
+
assert_equal 'https://w3id.org/ro/crate/1.5', crate.metadata.spec_url
|
|
405
|
+
ensure
|
|
406
|
+
$stderr = original_stderr
|
|
407
|
+
end
|
|
408
|
+
end
|
|
380
409
|
end
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
123
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"@context": [
|
|
3
|
+
"https://w3id.org/ro/crate/1.1/context",
|
|
4
|
+
"https://w3id.org/ro/terms/workflow-run/context"
|
|
5
|
+
],
|
|
6
|
+
"@graph": [
|
|
7
|
+
{
|
|
8
|
+
"@id": "./",
|
|
9
|
+
"@type": "Dataset",
|
|
10
|
+
"datePublished": "2025-01-28T02:44:51.523Z",
|
|
11
|
+
"hasPart": {
|
|
12
|
+
"@id": "a_file"
|
|
13
|
+
},
|
|
14
|
+
"dateCreated": "2025-01-28T02:45:05.906Z",
|
|
15
|
+
"dateModified": "2025-01-28T02:44:51.523Z",
|
|
16
|
+
"description": "Something",
|
|
17
|
+
"license": {
|
|
18
|
+
"@id": "https://spdx.org/licenses/MIT"
|
|
19
|
+
},
|
|
20
|
+
"mainEntity": {
|
|
21
|
+
"@id": "a_file"
|
|
22
|
+
},
|
|
23
|
+
"name": "An RO-Crate containing just a single item"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"@id": "ro-crate-metadata.json",
|
|
27
|
+
"@type": "CreativeWork",
|
|
28
|
+
"conformsTo": [
|
|
29
|
+
{
|
|
30
|
+
"@id": "https://w3id.org/ro/crate/1.1"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"about": {
|
|
37
|
+
"@id": "./"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"@id": "a_file",
|
|
42
|
+
"@type": [
|
|
43
|
+
"File",
|
|
44
|
+
"ComputationalWorkflow",
|
|
45
|
+
"SoftwareSourceCode"
|
|
46
|
+
],
|
|
47
|
+
"contentSize": 4,
|
|
48
|
+
"description": "A workflow",
|
|
49
|
+
"encodingFormat": "text/plain",
|
|
50
|
+
"name": "a_file",
|
|
51
|
+
"programmingLanguage": {
|
|
52
|
+
"@id": "https://example.com/workflow_format"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'test_helper'
|
|
3
|
+
|
|
4
|
+
class PreviewTest < Test::Unit::TestCase
|
|
5
|
+
test 'simple attributes' do
|
|
6
|
+
crate = ROCrate::Crate.new
|
|
7
|
+
crate.author = 'Finn'
|
|
8
|
+
|
|
9
|
+
html = crate.preview.source.read
|
|
10
|
+
assert_includes html, '<dd>Finn</dd>'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
test 'list attributes' do
|
|
14
|
+
crate = ROCrate::Crate.new
|
|
15
|
+
crate.author = ['Finn', 'Josiah']
|
|
16
|
+
|
|
17
|
+
html = crate.preview.source.read
|
|
18
|
+
assert_includes html, '<dd><ul><li>Finn</li><li>Josiah</li></ul></dd>'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
test 'entity attributes' do
|
|
22
|
+
crate = ROCrate::Crate.new
|
|
23
|
+
crate.author = crate.add_person('https://orcid.org/0000-0002-0048-3300', name: 'Finn')
|
|
24
|
+
|
|
25
|
+
html = crate.preview.source.read
|
|
26
|
+
assert_includes html, '<dd><a href="https://orcid.org/0000-0002-0048-3300" target="_blank">Finn</a></dd>'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
test 'complex attributes' do
|
|
30
|
+
crate = ROCrate::Crate.new
|
|
31
|
+
crate.author = [crate.add_person('https://orcid.org/0000-0002-0048-3300', name: 'Finn'), 'Josiah']
|
|
32
|
+
|
|
33
|
+
html = crate.preview.source.read
|
|
34
|
+
|
|
35
|
+
assert_includes html, '<dd><ul><li><a href="https://orcid.org/0000-0002-0048-3300" target="_blank">Finn</a></li><li>Josiah</li></ul></dd>'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
test 'files' do
|
|
39
|
+
crate = ROCrate::Crate.new
|
|
40
|
+
crate.add_file(fixture_file('info.txt'))
|
|
41
|
+
crate.add_external_file('https://raw.githubusercontent.com/ResearchObject/ro-crate-ruby/master/README.md')
|
|
42
|
+
|
|
43
|
+
html = crate.preview.source.read
|
|
44
|
+
|
|
45
|
+
assert_includes html, '<strong>info.txt</strong>'
|
|
46
|
+
assert_includes html, '<strong><a href="https://raw.githubusercontent.com/ResearchObject/ro-crate-ruby/master/README.md" target="_blank">https://raw.githubusercontent.com/ResearchObject/ro-crate-ruby/master/README.md</a></strong>'
|
|
47
|
+
end
|
|
48
|
+
end
|
data/test/reader_test.rb
CHANGED
|
@@ -358,6 +358,11 @@ class ReaderTest < Test::Unit::TestCase
|
|
|
358
358
|
ROCrate::Reader.read(fixture_file('broken/missing_file'))
|
|
359
359
|
end
|
|
360
360
|
assert_include e.message, 'not found in crate: file1.txt'
|
|
361
|
+
|
|
362
|
+
e = check_exception(ROCrate::ReadException) do
|
|
363
|
+
ROCrate::Reader.read(fixture_file('just_a_zip.zip').path)
|
|
364
|
+
end
|
|
365
|
+
assert_include e.message, 'No metadata found'
|
|
361
366
|
end
|
|
362
367
|
|
|
363
368
|
test 'tolerates arcp identifier on root data entity (and missing hasPart)' do
|
|
@@ -367,18 +372,70 @@ class ReaderTest < Test::Unit::TestCase
|
|
|
367
372
|
assert_empty crate.data_entities
|
|
368
373
|
end
|
|
369
374
|
|
|
370
|
-
|
|
375
|
+
test 'reads first metadata file it encounters' do
|
|
376
|
+
crate = ROCrate::Reader.read(fixture_file('multi_metadata_crate.crate.zip').path)
|
|
377
|
+
|
|
378
|
+
assert_equal 'At the root', crate.name
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
test 'reads crate with singleton hasPart' do
|
|
382
|
+
crate = ROCrate::Reader.read(fixture_file('singleton-haspart').path)
|
|
383
|
+
|
|
384
|
+
data = crate.data_entities
|
|
385
|
+
assert_equal 1, data.length
|
|
386
|
+
assert_equal 'a_file', data.first.name
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
test 'protect against zip-slip' do
|
|
390
|
+
Dir.mktmpdir do |dir|
|
|
391
|
+
subdir = ::File.join(dir, 'subdir')
|
|
392
|
+
::Dir.mkdir(subdir)
|
|
393
|
+
|
|
394
|
+
# Relative
|
|
395
|
+
e = check_exception(ROCrate::ReadException) do
|
|
396
|
+
ROCrate::Reader.unzip_file_to(fixture_file('unsafe/relative0.zip').path, subdir)
|
|
397
|
+
end
|
|
398
|
+
assert_include e.message, 'Unsafe path in zip entry: ../moo'
|
|
399
|
+
refute ::File.exist?(::File.join(dir, 'moo'))
|
|
400
|
+
|
|
401
|
+
e = check_exception(ROCrate::ReadException) do
|
|
402
|
+
ROCrate::Reader.unzip_io_to(fixture_file('unsafe/relative0.zip'), subdir)
|
|
403
|
+
end
|
|
404
|
+
assert_include e.message, 'Unsafe path in zip entry: ../moo'
|
|
405
|
+
refute ::File.exist?(::File.join(dir, 'moo'))
|
|
371
406
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
407
|
+
# Absolute
|
|
408
|
+
e = check_exception(ROCrate::ReadException) do
|
|
409
|
+
ROCrate::Reader.unzip_file_to(fixture_file('unsafe/absolute1.zip').path, subdir)
|
|
410
|
+
end
|
|
411
|
+
assert_include e.message, 'Unsafe path in zip entry: /tmp/moo'
|
|
412
|
+
|
|
413
|
+
e = check_exception(ROCrate::ReadException) do
|
|
414
|
+
ROCrate::Reader.unzip_io_to(fixture_file('unsafe/absolute1.zip'), subdir)
|
|
415
|
+
end
|
|
416
|
+
assert_include e.message, 'Unsafe path in zip entry: /tmp/moo'
|
|
417
|
+
|
|
418
|
+
# Simulate ArgumentError in safe_join
|
|
375
419
|
begin
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
420
|
+
original_expand_path = Pathname.instance_method(:expand_path)
|
|
421
|
+
Pathname.define_method(:expand_path) do |*args|
|
|
422
|
+
raise ArgumentError, 'Oh no'
|
|
423
|
+
end
|
|
424
|
+
e = check_exception(ROCrate::ReadException) do
|
|
425
|
+
ROCrate::Reader.unzip_file_to(fixture_file('unsafe/absolute1.zip').path, subdir)
|
|
426
|
+
end
|
|
427
|
+
assert_include e.message, 'Unsafe path in zip entry: /tmp/moo'
|
|
428
|
+
ensure
|
|
429
|
+
Pathname.define_method(:expand_path, original_expand_path)
|
|
379
430
|
end
|
|
380
431
|
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
test 'reads spec 1.1 RO-Crate and preserves version' do
|
|
435
|
+
crate = ROCrate::Reader.read(fixture_file('crate-spec1.1').path)
|
|
381
436
|
|
|
382
|
-
|
|
437
|
+
assert_equal '1.1', crate.metadata.version
|
|
438
|
+
assert_equal 'https://w3id.org/ro/crate/1.1', crate.metadata.spec_url
|
|
439
|
+
assert_equal 'https://w3id.org/ro/crate/1.1/context', crate.metadata.context_url
|
|
383
440
|
end
|
|
384
441
|
end
|
data/test/test_helper.rb
CHANGED
|
@@ -5,10 +5,37 @@ require 'test/unit'
|
|
|
5
5
|
require 'ro_crate'
|
|
6
6
|
require 'webmock/test_unit'
|
|
7
7
|
|
|
8
|
+
class Test::Unit::TestCase
|
|
9
|
+
def teardown
|
|
10
|
+
self._opened_files.each do |f|
|
|
11
|
+
f.close unless f.closed?
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def _opened_files
|
|
16
|
+
@opened_files ||= []
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
8
20
|
def fixture_file(name, *args)
|
|
9
|
-
::File.open(::File.join(fixture_dir, name), *args)
|
|
21
|
+
f = ::File.open(::File.join(fixture_dir, name), *args)
|
|
22
|
+
self._opened_files << f
|
|
23
|
+
f
|
|
10
24
|
end
|
|
11
25
|
|
|
12
26
|
def fixture_dir
|
|
13
27
|
::File.join(::File.dirname(__FILE__), 'fixtures')
|
|
14
28
|
end
|
|
29
|
+
|
|
30
|
+
def check_exception(exception_class)
|
|
31
|
+
e = nil
|
|
32
|
+
assert_raise(exception_class) do
|
|
33
|
+
begin
|
|
34
|
+
yield
|
|
35
|
+
rescue exception_class => e
|
|
36
|
+
raise e
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
e
|
|
41
|
+
end
|
data/test/writer_test.rb
CHANGED
|
@@ -274,4 +274,39 @@ class WriterTest < Test::Unit::TestCase
|
|
|
274
274
|
end
|
|
275
275
|
end
|
|
276
276
|
end
|
|
277
|
+
|
|
278
|
+
test 'skip generating the preview in a directory' do
|
|
279
|
+
crate = ROCrate::Crate.new
|
|
280
|
+
crate.add_file(fixture_file('info.txt'))
|
|
281
|
+
crate.add_file(StringIO.new('just a string!'), 'notice.txt')
|
|
282
|
+
crate.add_file(fixture_file('data.csv'), 'directory/data.csv')
|
|
283
|
+
|
|
284
|
+
Dir.mktmpdir do |dir|
|
|
285
|
+
ROCrate::Writer.new(crate).write(dir, skip_preview: true)
|
|
286
|
+
assert ::File.exist?(::File.join(dir, ROCrate::Metadata::IDENTIFIER))
|
|
287
|
+
refute ::File.exist?(::File.join(dir, ROCrate::Preview::IDENTIFIER))
|
|
288
|
+
assert_equal 6, ::File.size(::File.join(dir, 'info.txt'))
|
|
289
|
+
assert_equal 14, ::File.size(::File.join(dir, 'notice.txt'))
|
|
290
|
+
assert_equal 20, ::File.size(::File.join(dir, 'directory', 'data.csv'))
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
test 'skip generating the preview in a zip file' do
|
|
295
|
+
crate = ROCrate::Crate.new
|
|
296
|
+
crate.add_directory(fixture_file('directory').path.to_s, 'fish')
|
|
297
|
+
|
|
298
|
+
Tempfile.create do |file|
|
|
299
|
+
ROCrate::Writer.new(crate).write_zip(file, skip_preview: true)
|
|
300
|
+
|
|
301
|
+
Zip::File.open(file) do |zipfile|
|
|
302
|
+
assert zipfile.file.exist?(ROCrate::Metadata::IDENTIFIER)
|
|
303
|
+
refute zipfile.file.exist?(ROCrate::Preview::IDENTIFIER)
|
|
304
|
+
assert zipfile.file.exist? 'fish/info.txt'
|
|
305
|
+
assert zipfile.file.exist? 'fish/root.txt'
|
|
306
|
+
assert zipfile.file.exist? 'fish/data/info.txt'
|
|
307
|
+
assert zipfile.file.exist? 'fish/data/nested.txt'
|
|
308
|
+
assert zipfile.file.exist? 'fish/data/binary.jpg'
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
277
312
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ro-crate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Finn Bacall
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: addressable
|
|
@@ -19,7 +18,7 @@ dependencies:
|
|
|
19
18
|
version: '2.7'
|
|
20
19
|
- - "<"
|
|
21
20
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: '
|
|
21
|
+
version: '3'
|
|
23
22
|
type: :runtime
|
|
24
23
|
prerelease: false
|
|
25
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -29,35 +28,41 @@ dependencies:
|
|
|
29
28
|
version: '2.7'
|
|
30
29
|
- - "<"
|
|
31
30
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
31
|
+
version: '3'
|
|
33
32
|
- !ruby/object:Gem::Dependency
|
|
34
33
|
name: rubyzip
|
|
35
34
|
requirement: !ruby/object:Gem::Requirement
|
|
36
35
|
requirements:
|
|
37
|
-
- - "
|
|
36
|
+
- - ">="
|
|
38
37
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 2.
|
|
38
|
+
version: '2.3'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '4'
|
|
40
42
|
type: :runtime
|
|
41
43
|
prerelease: false
|
|
42
44
|
version_requirements: !ruby/object:Gem::Requirement
|
|
43
45
|
requirements:
|
|
44
|
-
- - "
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '2.3'
|
|
49
|
+
- - "<"
|
|
45
50
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
51
|
+
version: '4'
|
|
47
52
|
- !ruby/object:Gem::Dependency
|
|
48
53
|
name: rake
|
|
49
54
|
requirement: !ruby/object:Gem::Requirement
|
|
50
55
|
requirements:
|
|
51
56
|
- - "~>"
|
|
52
57
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: 13.
|
|
58
|
+
version: 13.4.2
|
|
54
59
|
type: :development
|
|
55
60
|
prerelease: false
|
|
56
61
|
version_requirements: !ruby/object:Gem::Requirement
|
|
57
62
|
requirements:
|
|
58
63
|
- - "~>"
|
|
59
64
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: 13.
|
|
65
|
+
version: 13.4.2
|
|
61
66
|
- !ruby/object:Gem::Dependency
|
|
62
67
|
name: test-unit
|
|
63
68
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -106,29 +111,28 @@ dependencies:
|
|
|
106
111
|
requirements:
|
|
107
112
|
- - "~>"
|
|
108
113
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: 3.
|
|
114
|
+
version: 3.26.2
|
|
110
115
|
type: :development
|
|
111
116
|
prerelease: false
|
|
112
117
|
version_requirements: !ruby/object:Gem::Requirement
|
|
113
118
|
requirements:
|
|
114
119
|
- - "~>"
|
|
115
120
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: 3.
|
|
121
|
+
version: 3.26.2
|
|
117
122
|
- !ruby/object:Gem::Dependency
|
|
118
123
|
name: rexml
|
|
119
124
|
requirement: !ruby/object:Gem::Requirement
|
|
120
125
|
requirements:
|
|
121
126
|
- - "~>"
|
|
122
127
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: 3.
|
|
128
|
+
version: 3.4.4
|
|
124
129
|
type: :development
|
|
125
130
|
prerelease: false
|
|
126
131
|
version_requirements: !ruby/object:Gem::Requirement
|
|
127
132
|
requirements:
|
|
128
133
|
- - "~>"
|
|
129
134
|
- !ruby/object:Gem::Version
|
|
130
|
-
version: 3.
|
|
131
|
-
description:
|
|
135
|
+
version: 3.4.4
|
|
132
136
|
email: finn.bacall@manchester.ac.uk
|
|
133
137
|
executables: []
|
|
134
138
|
extensions: []
|
|
@@ -200,7 +204,9 @@ files:
|
|
|
200
204
|
- test/fixtures/directory_crate/ro-crate-preview.html
|
|
201
205
|
- test/fixtures/file with spaces.txt
|
|
202
206
|
- test/fixtures/info.txt
|
|
207
|
+
- test/fixtures/just_a_zip.zip
|
|
203
208
|
- test/fixtures/misc_data_entity_crate/ro-crate-metadata.json
|
|
209
|
+
- test/fixtures/multi_metadata_crate.crate.zip
|
|
204
210
|
- test/fixtures/nested_directory.zip
|
|
205
211
|
- test/fixtures/ro-crate-galaxy-sortchangecase/LICENSE
|
|
206
212
|
- test/fixtures/ro-crate-galaxy-sortchangecase/README.md
|
|
@@ -209,6 +215,8 @@ files:
|
|
|
209
215
|
- test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/input.bed
|
|
210
216
|
- test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/output_exp.bed
|
|
211
217
|
- test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/sort-and-change-case-test.yml
|
|
218
|
+
- test/fixtures/singleton-haspart/a_file
|
|
219
|
+
- test/fixtures/singleton-haspart/ro-crate-metadata.json
|
|
212
220
|
- test/fixtures/spaces/file with spaces.txt
|
|
213
221
|
- test/fixtures/spaces/ro-crate-metadata.jsonld
|
|
214
222
|
- test/fixtures/sparse_directory_crate.zip
|
|
@@ -229,6 +237,8 @@ files:
|
|
|
229
237
|
- test/fixtures/unlinked_entity_crate/test/test1/input.bed
|
|
230
238
|
- test/fixtures/unlinked_entity_crate/test/test1/output_exp.bed
|
|
231
239
|
- test/fixtures/unlinked_entity_crate/test/test1/sort-and-change-case-test.yml
|
|
240
|
+
- test/fixtures/unsafe/absolute1.zip
|
|
241
|
+
- test/fixtures/unsafe/relative0.zip
|
|
232
242
|
- test/fixtures/uri_heavy_crate/main.nf
|
|
233
243
|
- test/fixtures/uri_heavy_crate/ro-crate-metadata.json
|
|
234
244
|
- test/fixtures/uri_heavy_crate/ro-crate-preview.html
|
|
@@ -2462,6 +2472,7 @@ files:
|
|
|
2462
2472
|
- test/fixtures/workflow-test-fixture-symlink/concat_two_files.ga
|
|
2463
2473
|
- test/fixtures/workflow-test-fixture-symlink/diagram.png
|
|
2464
2474
|
- test/fixtures/workflow-test-fixture-symlink/images/workflow-diagram.png
|
|
2475
|
+
- test/preview_test.rb
|
|
2465
2476
|
- test/reader_test.rb
|
|
2466
2477
|
- test/test_helper.rb
|
|
2467
2478
|
- test/writer_test.rb
|
|
@@ -2469,7 +2480,6 @@ homepage: https://github.com/ResearchObject/ro-crate-ruby
|
|
|
2469
2480
|
licenses:
|
|
2470
2481
|
- MIT
|
|
2471
2482
|
metadata: {}
|
|
2472
|
-
post_install_message:
|
|
2473
2483
|
rdoc_options: []
|
|
2474
2484
|
require_paths:
|
|
2475
2485
|
- lib
|
|
@@ -2477,15 +2487,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
2477
2487
|
requirements:
|
|
2478
2488
|
- - ">="
|
|
2479
2489
|
- !ruby/object:Gem::Version
|
|
2480
|
-
version:
|
|
2490
|
+
version: 2.7.0
|
|
2481
2491
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
2482
2492
|
requirements:
|
|
2483
2493
|
- - ">="
|
|
2484
2494
|
- !ruby/object:Gem::Version
|
|
2485
2495
|
version: '0'
|
|
2486
2496
|
requirements: []
|
|
2487
|
-
rubygems_version: 3.
|
|
2488
|
-
signing_key:
|
|
2497
|
+
rubygems_version: 3.6.9
|
|
2489
2498
|
specification_version: 4
|
|
2490
2499
|
summary: Create, manipulate, read RO-Crates.
|
|
2491
2500
|
test_files: []
|