ro-crate 0.4.6 → 0.4.11
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/Gemfile.lock +8 -8
- data/README.md +37 -0
- data/lib/ro_crate.rb +1 -0
- data/lib/ro_crate/model/contextual_entity.rb +2 -14
- data/lib/ro_crate/model/crate.rb +14 -0
- data/lib/ro_crate/model/data_entity.rb +3 -3
- data/lib/ro_crate/model/directory.rb +1 -3
- data/lib/ro_crate/model/entity.rb +24 -2
- data/lib/ro_crate/model/file.rb +1 -3
- data/lib/ro_crate/model/metadata.rb +9 -1
- data/lib/ro_crate/model/organization.rb +1 -1
- data/lib/ro_crate/model/preview.rb +3 -15
- data/lib/ro_crate/model/preview_generator.rb +40 -0
- data/lib/ro_crate/reader.rb +122 -26
- data/ro_crate.gemspec +2 -2
- data/test/crate_test.rb +12 -0
- data/test/entity_test.rb +21 -0
- data/test/fixtures/nested_directory.zip +0 -0
- data/test/fixtures/ro-crate-galaxy-sortchangecase/LICENSE +176 -0
- data/test/fixtures/ro-crate-galaxy-sortchangecase/README.md +6 -0
- data/test/fixtures/ro-crate-galaxy-sortchangecase/ro-crate-metadata.json +133 -0
- data/test/fixtures/ro-crate-galaxy-sortchangecase/sort-and-change-case.ga +118 -0
- data/test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/input.bed +3 -0
- data/test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/output_exp.bed +3 -0
- data/test/fixtures/ro-crate-galaxy-sortchangecase/test/test1/sort-and-change-case-test.yml +8 -0
- data/test/fixtures/sparse_directory_crate/ro-crate-preview.html +60 -59
- data/test/reader_test.rb +56 -0
- data/test/writer_test.rb +22 -2
- metadata +13 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62f3979b325743d31720f803dbf75690ad6b7b9bcf9f2cbf63d8200a2bb204ba
|
4
|
+
data.tar.gz: 3a229e06492a3f9a90721799f43929d6a5ca01fc0a0ff34d917eeebff037d9d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 242b8e326756d51ab583726db7fb94763286f2764bdf1f0523cd9dab5fa560fe4da3016a4a4b84b0cc6bcef4ec096aeae7dc2fd344ef6cb5ee045cf99a954ef9
|
7
|
+
data.tar.gz: 4abaece91d997ac398ee627f639fc98c924768c255bac1bb6c9ce7f43fc03f5b018a2fc657008863c65537592261e85f697ef2c7e200c20621ba41ff0a933541
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ro-crate (0.4.
|
4
|
+
ro-crate (0.4.11)
|
5
5
|
addressable (~> 2.7.0)
|
6
6
|
rubyzip (~> 2.0.0)
|
7
7
|
|
@@ -12,19 +12,19 @@ GEM
|
|
12
12
|
public_suffix (>= 2.0.2, < 5.0)
|
13
13
|
crack (0.4.3)
|
14
14
|
safe_yaml (~> 1.0.0)
|
15
|
-
docile (1.3.
|
15
|
+
docile (1.3.5)
|
16
16
|
hashdiff (1.0.1)
|
17
|
-
json (2.3.1)
|
18
17
|
power_assert (0.4.1)
|
19
18
|
public_suffix (4.0.3)
|
20
19
|
rake (13.0.0)
|
21
20
|
rubyzip (2.0.0)
|
22
21
|
safe_yaml (1.0.5)
|
23
|
-
simplecov (0.
|
22
|
+
simplecov (0.21.2)
|
24
23
|
docile (~> 1.1)
|
25
|
-
|
26
|
-
|
27
|
-
simplecov-html (0.
|
24
|
+
simplecov-html (~> 0.11)
|
25
|
+
simplecov_json_formatter (~> 0.1)
|
26
|
+
simplecov-html (0.12.3)
|
27
|
+
simplecov_json_formatter (0.1.2)
|
28
28
|
test-unit (3.2.3)
|
29
29
|
power_assert
|
30
30
|
webmock (3.8.3)
|
@@ -39,7 +39,7 @@ PLATFORMS
|
|
39
39
|
DEPENDENCIES
|
40
40
|
rake (~> 13.0.0)
|
41
41
|
ro-crate!
|
42
|
-
simplecov (~> 0.
|
42
|
+
simplecov (~> 0.21.2)
|
43
43
|
test-unit (~> 3.2.3)
|
44
44
|
webmock (~> 3.8.3)
|
45
45
|
yard (~> 0.9.25)
|
data/README.md
CHANGED
@@ -17,6 +17,43 @@ and run `bundle install`.
|
|
17
17
|
|
18
18
|
## Usage
|
19
19
|
|
20
|
+
This gem consists a hierarchy of classes to model RO-Crate "entities": the crate itself, data entities
|
21
|
+
(files and directory) and contextual entities (with a limited set of specializations, such as `ROCrate::Person`).
|
22
|
+
They are all descendents of the `ROCrate::Entity` class, with the `ROCrate::Crate` class representing the crate itself.
|
23
|
+
|
24
|
+
The `ROCrate::Reader` class handles reading of RO-Crates into the above model, from a Zip file or directory.
|
25
|
+
|
26
|
+
The `ROCrate::Writer` class can write out an `ROCrate::Crate` instance into a Zip file or directory.
|
27
|
+
|
28
|
+
**Note:** for performance reasons, the gem is currently not linked-data aware and will allow you to set properties that
|
29
|
+
are not semantically valid.
|
30
|
+
|
31
|
+
### Entities
|
32
|
+
Entities correspond to entries in the `@graph` of the RO-Crate's metadata JSON-LD file. Each entity class is
|
33
|
+
basically a wrapper around a set of JSON properties, with some convenience methods for getting/setting some
|
34
|
+
commonly used properties (`crate.name = "My first crate"`).
|
35
|
+
|
36
|
+
These convenience getter/setter methods will automatically handle turning objects into references and adding them to the
|
37
|
+
`@graph` if necessary.
|
38
|
+
|
39
|
+
##### Getting/Setting Arbitrary Properties of Entities
|
40
|
+
As well as using the pre-defined getter/setter methods, you can get/set arbitrary properties like so.
|
41
|
+
|
42
|
+
To set the "creativeWorkStatus" property of the RO-Crate itself to a string literal:
|
43
|
+
```ruby
|
44
|
+
crate['creativeWorkStatus'] = 'work-in-progress'
|
45
|
+
```
|
46
|
+
|
47
|
+
If you want to reference other entities in the crate, you can get a JSON-LD reference from an entity object by using the `reference` method:
|
48
|
+
```ruby
|
49
|
+
joe = crate.add_person('joe', { name: 'Joe Bloggs' }) # Add the entity to the @graph
|
50
|
+
crate['copyrightHolder'] = joe.reference # Reference the entity from the "copyrightHolder" property
|
51
|
+
```
|
52
|
+
and to resolve those references back to the object, use the `dereference` method:
|
53
|
+
```ruby
|
54
|
+
joe = crate['copyrightHolder'].dereference
|
55
|
+
```
|
56
|
+
|
20
57
|
### Documentation
|
21
58
|
|
22
59
|
[Click here for API documentation](https://www.researchobject.org/ro-crate-ruby/).
|
data/lib/ro_crate.rb
CHANGED
@@ -14,6 +14,7 @@ require 'ro_crate/model/remote_entry'
|
|
14
14
|
require 'ro_crate/model/entry'
|
15
15
|
require 'ro_crate/model/directory'
|
16
16
|
require 'ro_crate/model/metadata'
|
17
|
+
require 'ro_crate/model/preview_generator'
|
17
18
|
require 'ro_crate/model/preview'
|
18
19
|
require 'ro_crate/model/crate'
|
19
20
|
require 'ro_crate/model/contextual_entity'
|
@@ -3,21 +3,9 @@ module ROCrate
|
|
3
3
|
# A class to represent a "Contextual Entity" within an RO-Crate.
|
4
4
|
# Contextual Entities are used to describe and provide context to the Data Entities within the crate.
|
5
5
|
class ContextualEntity < Entity
|
6
|
-
def self.
|
6
|
+
def self.format_local_id(id)
|
7
7
|
i = super
|
8
|
-
|
9
|
-
uri = URI(id)
|
10
|
-
rescue ArgumentError
|
11
|
-
uri = nil
|
12
|
-
end
|
13
|
-
|
14
|
-
if uri&.absolute?
|
15
|
-
i
|
16
|
-
elsif i.start_with?('#')
|
17
|
-
i
|
18
|
-
else
|
19
|
-
"##{i}"
|
20
|
-
end
|
8
|
+
i.start_with?('#') ? i : "##{i}"
|
21
9
|
end
|
22
10
|
|
23
11
|
##
|
data/lib/ro_crate/model/crate.rb
CHANGED
@@ -8,6 +8,11 @@ module ROCrate
|
|
8
8
|
properties(%w[name datePublished author license identifier distribution contactPoint publisher description url hasPart])
|
9
9
|
|
10
10
|
def self.format_id(id)
|
11
|
+
i = super(id)
|
12
|
+
i.end_with?('/') ? i : "#{i}/"
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.format_local_id(id)
|
11
16
|
return id if id == IDENTIFIER
|
12
17
|
super
|
13
18
|
end
|
@@ -163,6 +168,15 @@ module ROCrate
|
|
163
168
|
@preview ||= ROCrate::Preview.new(self)
|
164
169
|
end
|
165
170
|
|
171
|
+
##
|
172
|
+
# Set the RO-Crate preview file
|
173
|
+
# @param preview [Preview] the preview to set.
|
174
|
+
#
|
175
|
+
# @return [Preview]
|
176
|
+
def preview=(preview)
|
177
|
+
@preview = claim(preview)
|
178
|
+
end
|
179
|
+
|
166
180
|
##
|
167
181
|
# All the entities within the crate. Includes contextual entities, data entities, the crate itself and its metadata file.
|
168
182
|
#
|
@@ -3,7 +3,9 @@ module ROCrate
|
|
3
3
|
# A class to represent a "Data Entity" within an RO-Crate.
|
4
4
|
# Data Entities are the actual physical files and directories within the Crate.
|
5
5
|
class DataEntity < Entity
|
6
|
-
|
6
|
+
properties(%w[name contentSize dateModified encodingFormat identifier sameAs author])
|
7
|
+
|
8
|
+
def self.format_local_id(id)
|
7
9
|
super.chomp('/')
|
8
10
|
end
|
9
11
|
|
@@ -13,8 +15,6 @@ module ROCrate
|
|
13
15
|
# @return [Class]
|
14
16
|
def self.specialize(props)
|
15
17
|
type = props['@type']
|
16
|
-
id = props['@id']
|
17
|
-
abs = URI(id)&.absolute? rescue false
|
18
18
|
type = [type] unless type.is_a?(Array)
|
19
19
|
if type.include?('Dataset')
|
20
20
|
ROCrate::Directory
|
@@ -2,9 +2,7 @@ module ROCrate
|
|
2
2
|
##
|
3
3
|
# A data entity that represents a directory of potentially many files and subdirectories (or none).
|
4
4
|
class Directory < DataEntity
|
5
|
-
|
6
|
-
|
7
|
-
def self.format_id(id)
|
5
|
+
def self.format_local_id(id)
|
8
6
|
super + '/'
|
9
7
|
end
|
10
8
|
|
@@ -29,12 +29,34 @@ module ROCrate
|
|
29
29
|
end
|
30
30
|
|
31
31
|
##
|
32
|
-
# Format the given ID with rules appropriate for this type.
|
32
|
+
# Format the given ID with rules appropriate for this type if it is local/relative, leave as-is if absolute.
|
33
|
+
#
|
34
|
+
# @param id [String] The candidate ID to be formatted.
|
35
|
+
# @return [String] The formatted ID.
|
36
|
+
def self.format_id(id)
|
37
|
+
begin
|
38
|
+
uri = URI(id)
|
39
|
+
rescue ArgumentError, URI::InvalidURIError
|
40
|
+
uri = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
if uri&.absolute?
|
44
|
+
id
|
45
|
+
else
|
46
|
+
format_local_id(id)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Format the given local ID with rules appropriate for this type.
|
33
52
|
# For example:
|
34
53
|
# * contextual entities MUST be absolute URIs, or begin with: #
|
35
54
|
# * files MUST NOT begin with ./
|
36
55
|
# * directories MUST NOT begin with ./ (except for the crate itself), and MUST end with /
|
37
|
-
|
56
|
+
#
|
57
|
+
# @param id [String] The candidate local ID to be formatted.
|
58
|
+
# @return [String] The formatted local ID.
|
59
|
+
def self.format_local_id(id)
|
38
60
|
Addressable::URI.escape(id.sub(/\A\.\//, '')) # Remove initial ./ if present
|
39
61
|
end
|
40
62
|
|
data/lib/ro_crate/model/file.rb
CHANGED
@@ -2,14 +2,12 @@ module ROCrate
|
|
2
2
|
##
|
3
3
|
# A data entity that represents a single file.
|
4
4
|
class File < DataEntity
|
5
|
-
properties(%w[name contentSize dateModified encodingFormat identifier sameAs])
|
6
|
-
|
7
5
|
##
|
8
6
|
# Create a new ROCrate::File. PLEASE NOTE, the new file will not be added to the crate. To do this, call
|
9
7
|
# Crate#add_data_entity, or just use Crate#add_file.
|
10
8
|
#
|
11
9
|
# @param crate [Crate] The RO-Crate that owns this file.
|
12
|
-
# @param source [String, Pathname, ::File,
|
10
|
+
# @param source [String, Pathname, ::File, URI, nil, #read] The source on the disk (or on the internet if a URI) where this file will be read.
|
13
11
|
# @param crate_path [String] The relative path within the RO-Crate where this file will be written.
|
14
12
|
# @param properties [Hash{String => Object}] A hash of JSON-LD properties to associate with this file.
|
15
13
|
def initialize(crate, source, crate_path = nil, properties = {})
|
@@ -17,7 +17,15 @@ module ROCrate
|
|
17
17
|
# @return [String] The rendered JSON-LD as a "prettified" string.
|
18
18
|
def generate
|
19
19
|
graph = crate.entities.map(&:properties).reject(&:empty?)
|
20
|
-
JSON.pretty_generate('@context' =>
|
20
|
+
JSON.pretty_generate('@context' => context, '@graph' => graph)
|
21
|
+
end
|
22
|
+
|
23
|
+
def context
|
24
|
+
@context || CONTEXT
|
25
|
+
end
|
26
|
+
|
27
|
+
def context= c
|
28
|
+
@context = c
|
21
29
|
end
|
22
30
|
|
23
31
|
private
|
@@ -12,26 +12,14 @@ module ROCrate
|
|
12
12
|
# @return [String]
|
13
13
|
attr_accessor :template
|
14
14
|
|
15
|
-
def initialize(crate, properties = {})
|
15
|
+
def initialize(crate, source = nil, properties = {})
|
16
|
+
source ||= PreviewGenerator.new(self)
|
16
17
|
@template = nil
|
17
|
-
super(crate,
|
18
|
-
end
|
19
|
-
|
20
|
-
##
|
21
|
-
# Generate the crate's `ro-crate-preview.html`.
|
22
|
-
# @return [String] The rendered HTML as a string.
|
23
|
-
def generate
|
24
|
-
b = crate.get_binding
|
25
|
-
renderer = ERB.new(template || ::File.read(DEFAULT_TEMPLATE))
|
26
|
-
renderer.result(b)
|
18
|
+
super(crate, source, IDENTIFIER, properties)
|
27
19
|
end
|
28
20
|
|
29
21
|
private
|
30
22
|
|
31
|
-
def source
|
32
|
-
Entry.new(StringIO.new(generate))
|
33
|
-
end
|
34
|
-
|
35
23
|
def default_properties
|
36
24
|
{
|
37
25
|
'@id' => IDENTIFIER,
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module ROCrate
|
4
|
+
##
|
5
|
+
# A class to handle generation of an RO-Crate's preview HTML in an IO-like way (to fit into an Entry).
|
6
|
+
class PreviewGenerator
|
7
|
+
##
|
8
|
+
# @param preview [Preview] The RO-Crate preview object.
|
9
|
+
def initialize(preview)
|
10
|
+
@preview = preview
|
11
|
+
end
|
12
|
+
|
13
|
+
def read(*args)
|
14
|
+
io.read(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Generate the crate's `ro-crate-preview.html`.
|
19
|
+
# @return [String] The rendered HTML as a string.
|
20
|
+
def generate
|
21
|
+
b = crate.get_binding
|
22
|
+
renderer = ERB.new(template)
|
23
|
+
renderer.result(b)
|
24
|
+
end
|
25
|
+
|
26
|
+
def template
|
27
|
+
@preview.template || ::File.read(Preview::DEFAULT_TEMPLATE)
|
28
|
+
end
|
29
|
+
|
30
|
+
def crate
|
31
|
+
@preview.crate
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def io
|
37
|
+
@io ||= StringIO.new(generate)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/ro_crate/reader.rb
CHANGED
@@ -3,29 +3,67 @@ module ROCrate
|
|
3
3
|
# A class to handle reading of RO-Crates from Zip files or directories.
|
4
4
|
class Reader
|
5
5
|
##
|
6
|
-
# Reads an RO-Crate from a directory
|
6
|
+
# Reads an RO-Crate from a directory or zip file.
|
7
7
|
#
|
8
|
-
# @param source [String, ::File, Pathname] The
|
8
|
+
# @param source [String, ::File, Pathname, #read] The location of the zip or directory, or an IO-like object containing a zip.
|
9
9
|
# @param target_dir [String, ::File, Pathname] The target directory where the crate should be unzipped (if its a Zip file).
|
10
10
|
# @return [Crate] The RO-Crate.
|
11
11
|
def self.read(source, target_dir: Dir.mktmpdir)
|
12
12
|
raise "Not a directory!" unless ::File.directory?(target_dir)
|
13
|
-
|
13
|
+
begin
|
14
|
+
is_dir = ::File.directory?(source)
|
15
|
+
rescue TypeError
|
16
|
+
is_dir = false
|
17
|
+
end
|
18
|
+
|
19
|
+
if is_dir
|
14
20
|
read_directory(source)
|
15
21
|
else
|
16
22
|
read_zip(source, target_dir: target_dir)
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
26
|
+
##
|
27
|
+
# Extract the contents of the given Zip file/data to the given directory.
|
28
|
+
#
|
29
|
+
# @param source [String, ::File, Pathname, #read] The location of the zip file, or an IO-like object.
|
30
|
+
# @param target [String, ::File, Pathname] The target directory where the file should be unzipped.
|
31
|
+
def self.unzip_to(source, target)
|
32
|
+
source = Pathname.new(::File.expand_path(source)) if source.is_a?(String)
|
33
|
+
|
34
|
+
if source.is_a?(Pathname) || source.respond_to?(:path)
|
35
|
+
unzip_file_to(source, target)
|
36
|
+
else
|
37
|
+
unzip_io_to(source, target)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# Extract the given Zip file data to the given directory.
|
43
|
+
#
|
44
|
+
# @param source [#read] An IO-like object containing a Zip file.
|
45
|
+
# @param target [String, ::File, Pathname] The target directory where the file should be unzipped.
|
46
|
+
def self.unzip_io_to(io, target)
|
47
|
+
Dir.chdir(target) do
|
48
|
+
Zip::InputStream.open(io) do |input|
|
49
|
+
while (entry = input.get_next_entry)
|
50
|
+
unless ::File.exist?(entry.name) || entry.name_is_directory?
|
51
|
+
FileUtils::mkdir_p(::File.dirname(entry.name))
|
52
|
+
::File.binwrite(entry.name, input.read)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
20
59
|
##
|
21
60
|
# Extract the contents of the given Zip file to the given directory.
|
22
61
|
#
|
23
62
|
# @param source [String, ::File, Pathname] The location of the zip file.
|
24
63
|
# @param target [String, ::File, Pathname] The target directory where the file should be unzipped.
|
25
|
-
def self.
|
26
|
-
source = ::File.expand_path(source)
|
64
|
+
def self.unzip_file_to(file_or_path, target)
|
27
65
|
Dir.chdir(target) do
|
28
|
-
Zip::File.open(
|
66
|
+
Zip::File.open(file_or_path) do |zipfile|
|
29
67
|
zipfile.each do |entry|
|
30
68
|
unless ::File.exist?(entry.name)
|
31
69
|
FileUtils::mkdir_p(::File.dirname(entry.name))
|
@@ -40,13 +78,16 @@ module ROCrate
|
|
40
78
|
# Reads an RO-Crate from a zip file. It first extracts the Zip file to a temporary directory, and then calls
|
41
79
|
# #read_directory.
|
42
80
|
#
|
43
|
-
# @param source [String, ::File, Pathname] The location of the zip file.
|
81
|
+
# @param source [String, ::File, Pathname, #read] The location of the zip file, or an IO-like object.
|
44
82
|
# @param target_dir [String, ::File, Pathname] The target directory where the crate should be unzipped.
|
45
83
|
# @return [Crate] The RO-Crate.
|
46
84
|
def self.read_zip(source, target_dir: Dir.mktmpdir)
|
47
85
|
unzip_to(source, target_dir)
|
48
86
|
|
49
|
-
|
87
|
+
# Traverse the unzipped directory to try and find the crate's root
|
88
|
+
root_dir = detect_root_directory(target_dir)
|
89
|
+
|
90
|
+
read_directory(root_dir)
|
50
91
|
end
|
51
92
|
|
52
93
|
##
|
@@ -55,13 +96,19 @@ module ROCrate
|
|
55
96
|
# @param source [String, ::File, Pathname] The location of the directory.
|
56
97
|
# @return [Crate] The RO-Crate.
|
57
98
|
def self.read_directory(source)
|
99
|
+
raise "Not a directory!" unless ::File.directory?(source)
|
100
|
+
|
58
101
|
source = ::File.expand_path(source)
|
59
102
|
metadata_file = Dir.entries(source).detect { |entry| entry == ROCrate::Metadata::IDENTIFIER ||
|
60
103
|
entry == ROCrate::Metadata::IDENTIFIER_1_0 }
|
61
104
|
|
62
105
|
if metadata_file
|
63
|
-
|
64
|
-
|
106
|
+
metadata_json = ::File.read(::File.join(source, metadata_file))
|
107
|
+
metadata = JSON.parse(metadata_json)
|
108
|
+
entities = entities_from_metadata(metadata)
|
109
|
+
context = metadata['@context']
|
110
|
+
|
111
|
+
build_crate(entities, source, context: context)
|
65
112
|
else
|
66
113
|
raise 'No metadata found!'
|
67
114
|
end
|
@@ -70,10 +117,9 @@ module ROCrate
|
|
70
117
|
##
|
71
118
|
# Extracts all the entities from the @graph of the RO-Crate Metadata.
|
72
119
|
#
|
73
|
-
# @param
|
120
|
+
# @param metadata [Hash] A Hash containing the parsed metadata JSON.
|
74
121
|
# @return [Hash{String => Hash}] A Hash of all the entities, mapped by their @id.
|
75
|
-
def self.entities_from_metadata(
|
76
|
-
metadata = JSON.parse(metadata_json)
|
122
|
+
def self.entities_from_metadata(metadata)
|
77
123
|
graph = metadata['@graph']
|
78
124
|
|
79
125
|
if graph
|
@@ -86,6 +132,7 @@ module ROCrate
|
|
86
132
|
# Do some normalization...
|
87
133
|
entities[ROCrate::Metadata::IDENTIFIER] = extract_metadata_entity(entities)
|
88
134
|
raise "No metadata entity found in @graph!" unless entities[ROCrate::Metadata::IDENTIFIER]
|
135
|
+
entities[ROCrate::Preview::IDENTIFIER] = extract_preview_entity(entities)
|
89
136
|
entities[ROCrate::Crate::IDENTIFIER] = extract_root_entity(entities)
|
90
137
|
raise "No root entity (with @id: #{entities[ROCrate::Metadata::IDENTIFIER].dig('about', '@id')}) found in @graph!" unless entities[ROCrate::Crate::IDENTIFIER]
|
91
138
|
|
@@ -96,25 +143,49 @@ module ROCrate
|
|
96
143
|
end
|
97
144
|
|
98
145
|
##
|
99
|
-
# Create
|
146
|
+
# Create and populate crate from the given set of entities.
|
100
147
|
#
|
101
148
|
# @param entity_hash [Hash{String => Hash}] A Hash containing all the entities in the @graph, mapped by their @id.
|
102
149
|
# @param source [String, ::File, Pathname] The location of the RO-Crate being read.
|
150
|
+
# @param crate_class [Class] The class to use to instantiate the crate,
|
151
|
+
# useful if you have created a subclass of ROCrate::Crate that you want to use. (defaults to ROCrate::Crate).
|
152
|
+
# @param context [nil, String, Array, Hash] A custom JSON-LD @context (parsed), or nil to use default.
|
103
153
|
# @return [Crate] The RO-Crate.
|
104
|
-
def self.build_crate(entity_hash, source)
|
105
|
-
|
154
|
+
def self.build_crate(entity_hash, source, crate_class: ROCrate::Crate, context:)
|
155
|
+
crate = initialize_crate(entity_hash, source, crate_class: crate_class, context: context)
|
156
|
+
|
157
|
+
extract_data_entities(crate, source, entity_hash).each do |entity|
|
158
|
+
crate.add_data_entity(entity)
|
159
|
+
end
|
160
|
+
|
161
|
+
# The remaining entities in the hash must be contextual.
|
162
|
+
extract_contextual_entities(crate, entity_hash).each do |entity|
|
163
|
+
crate.add_contextual_entity(entity)
|
164
|
+
end
|
165
|
+
|
166
|
+
crate
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Initialize a crate from the given set of entities.
|
171
|
+
#
|
172
|
+
# @param entity_hash [Hash{String => Hash}] A Hash containing all the entities in the @graph, mapped by their @id.
|
173
|
+
# @param source [String, ::File, Pathname] The location of the RO-Crate being read.
|
174
|
+
# @param crate_class [Class] The class to use to instantiate the crate,
|
175
|
+
# useful if you have created a subclass of ROCrate::Crate that you want to use. (defaults to ROCrate::Crate).
|
176
|
+
# @param context [nil, String, Array, Hash] A custom JSON-LD @context (parsed), or nil to use default.
|
177
|
+
# @return [Crate] The RO-Crate.
|
178
|
+
def self.initialize_crate(entity_hash, source, crate_class: ROCrate::Crate, context:)
|
179
|
+
crate_class.new.tap do |crate|
|
106
180
|
crate.properties = entity_hash.delete(ROCrate::Crate::IDENTIFIER)
|
107
181
|
crate.metadata.properties = entity_hash.delete(ROCrate::Metadata::IDENTIFIER)
|
182
|
+
crate.metadata.context = context
|
108
183
|
preview_properties = entity_hash.delete(ROCrate::Preview::IDENTIFIER)
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
crate.add_data_entity(entity)
|
113
|
-
end
|
114
|
-
# The remaining entities in the hash must be contextual.
|
115
|
-
extract_contextual_entities(crate, entity_hash).each do |entity|
|
116
|
-
crate.add_contextual_entity(entity)
|
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)
|
117
187
|
end
|
188
|
+
crate.add_all(source, false)
|
118
189
|
end
|
119
190
|
end
|
120
191
|
|
@@ -186,8 +257,8 @@ module ROCrate
|
|
186
257
|
##
|
187
258
|
# Extract the metadata entity from the entity hash, according to the rules defined here:
|
188
259
|
# https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#finding-the-root-data-entity
|
189
|
-
# @return [Hash{String => Hash}] A Hash containing (hopefully) one value, the metadata entity's properties
|
190
|
-
# mapped by its @id.
|
260
|
+
# @return [nil, Hash{String => Hash}] A Hash containing (hopefully) one value, the metadata entity's properties
|
261
|
+
# mapped by its @id, or nil if nothing is found.
|
191
262
|
def self.extract_metadata_entity(entities)
|
192
263
|
key = entities.detect do |_, props|
|
193
264
|
props.dig('conformsTo', '@id')&.start_with?(ROCrate::Metadata::RO_CRATE_BASE)
|
@@ -202,6 +273,13 @@ module ROCrate
|
|
202
273
|
entities.delete(ROCrate::Metadata::IDENTIFIER_1_0))
|
203
274
|
end
|
204
275
|
|
276
|
+
##
|
277
|
+
# Extract the ro-crate-preview entity from the entity hash.
|
278
|
+
# @return [Hash{String => Hash}] A Hash containing the preview entity's properties mapped by its @id, or nil if nothing is found.
|
279
|
+
def self.extract_preview_entity(entities)
|
280
|
+
entities.delete("./#{ROCrate::Preview::IDENTIFIER}") || entities.delete(ROCrate::Preview::IDENTIFIER)
|
281
|
+
end
|
282
|
+
|
205
283
|
##
|
206
284
|
# Extract the root entity from the entity hash, according to the rules defined here:
|
207
285
|
# https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#finding-the-root-data-entity
|
@@ -212,5 +290,23 @@ module ROCrate
|
|
212
290
|
raise "Metadata entity does not reference any root entity" unless root_id
|
213
291
|
entities.delete(root_id)
|
214
292
|
end
|
293
|
+
|
294
|
+
##
|
295
|
+
# Finds an RO-Crate's root directory (where `ro-crate-metdata.json` is located) within a given directory.
|
296
|
+
#
|
297
|
+
# @param source [String, ::File, Pathname] The location of the directory.
|
298
|
+
# @return [Pathname, nil] The path to the root, or nil if not found.
|
299
|
+
def self.detect_root_directory(source)
|
300
|
+
Pathname(source).find do |entry|
|
301
|
+
if entry.file?
|
302
|
+
name = entry.basename.to_s
|
303
|
+
if name == ROCrate::Metadata::IDENTIFIER || name == ROCrate::Metadata::IDENTIFIER_1_0
|
304
|
+
return entry.parent
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
nil
|
310
|
+
end
|
215
311
|
end
|
216
312
|
end
|