ro-crate 0.4.1 → 0.4.6

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/Gemfile.lock +1 -1
  4. data/README.md +11 -7
  5. data/lib/ro_crate/json_ld_hash.rb +1 -1
  6. data/lib/ro_crate/model/contextual_entity.rb +1 -1
  7. data/lib/ro_crate/model/crate.rb +54 -15
  8. data/lib/ro_crate/model/data_entity.rb +1 -1
  9. data/lib/ro_crate/model/directory.rb +39 -11
  10. data/lib/ro_crate/model/entity.rb +3 -3
  11. data/lib/ro_crate/model/entry.rb +1 -1
  12. data/lib/ro_crate/model/file.rb +5 -3
  13. data/lib/ro_crate/model/remote_entry.rb +1 -1
  14. data/lib/ro_crate/reader.rb +18 -15
  15. data/lib/ro_crate/ro-crate-preview.html.erb +3 -3
  16. data/lib/ro_crate/writer.rb +3 -3
  17. data/ro_crate.gemspec +2 -2
  18. data/test/crate_test.rb +95 -0
  19. data/test/fixtures/directory.zip +0 -0
  20. data/test/fixtures/directory/.dir/test.txt +1 -0
  21. data/test/fixtures/directory/.dotfile +1 -0
  22. data/test/fixtures/directory_crate/ro-crate-metadata.jsonld +7 -0
  23. data/test/fixtures/directory_crate/ro-crate-preview.html +66 -0
  24. data/test/fixtures/sparse_directory_crate.zip +0 -0
  25. data/test/fixtures/sparse_directory_crate/fish/data/binary.jpg +0 -0
  26. data/test/fixtures/sparse_directory_crate/fish/data/info.txt +1 -0
  27. data/test/fixtures/sparse_directory_crate/fish/data/nested.txt +1 -0
  28. data/test/fixtures/sparse_directory_crate/fish/info.txt +1 -0
  29. data/test/fixtures/sparse_directory_crate/fish/root.txt +1 -0
  30. data/test/fixtures/sparse_directory_crate/listed_file.txt +1 -0
  31. data/test/fixtures/sparse_directory_crate/ro-crate-metadata.jsonld +32 -0
  32. data/test/fixtures/sparse_directory_crate/ro-crate-preview.html +66 -0
  33. data/test/fixtures/sparse_directory_crate/unlisted_file.txt +1 -0
  34. data/test/reader_test.rb +36 -2
  35. data/test/writer_test.rb +33 -0
  36. metadata +16 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b5b7dfe4513a995ac2d5040a0fb94380d0a4da0a2181d2b0978a0dfd667715c
4
- data.tar.gz: c5f6a40b5d3eac383ddecfa79088ecf102cf54413000944c619b4936fd775247
3
+ metadata.gz: 4880110308b68d8bc35333b1fc8a8396ea30e36069f92e059b8c57ec3567d33e
4
+ data.tar.gz: 77a17a0436f2dea14254a3501f9db349eed5460851d94ab4e87adb7be83e1b38
5
5
  SHA512:
6
- metadata.gz: bf966c315804ae8855683eceac6ba725ad607682a630873f2d84ceee8471fe23c22b00127e9391d7b1737aa0e1c8c65e54be085e2a7c47a4436aece62fa56d0f
7
- data.tar.gz: 2464ebbbb77c3d71c9a47da2908c61f8d90cd76f043f00f748d1ccab41270b2d0ff9255ba06632dbdddf7d1d295e1204a314fc4f0cef69eb9419d213fc0a0232
6
+ metadata.gz: 711a45d7aaa63f8a5a7bba78e433d82875311472d6f4abfd6d388dd8565a7707036152c81de4059fd92da5b7851925caf9d808f39d115752e68eaddbdc85bac1
7
+ data.tar.gz: ce14efaf63e7224adc79cdb11d27bdec933be290ccf60196014cb7ce9380136451265a8f814e1ca7b70286fc32852024d429334bbaab18208d31095cd619dc9e
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- ruby-2.4.9
1
+ ruby-2.6.6
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ro-crate (0.4.1)
4
+ ro-crate (0.4.6)
5
5
  addressable (~> 2.7.0)
6
6
  rubyzip (~> 2.0.0)
7
7
 
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # ro-crate-ruby
2
2
 
3
- This is a WIP gem for creating, manipulating and reading RO crates (conforming to version 1.1 of the specification).
3
+ This is a WIP gem for creating, manipulating and reading RO-Crates (conforming to version 1.1 of the specification).
4
4
 
5
- * RO Crate - https://researchobject.github.io/ro-crate/
6
- * RO Crate spec (1.1) - https://researchobject.github.io/ro-crate/1.1/
5
+ * RO-Crate - https://researchobject.github.io/ro-crate/
6
+ * RO-Crate spec (1.1) - https://researchobject.github.io/ro-crate/1.1/
7
7
 
8
8
  ## Installation
9
9
 
@@ -31,13 +31,17 @@ crate = ROCrate::Crate.new
31
31
  crate.add_file(File.open('Gemfile')) # Using IO-like objects
32
32
  crate.add_file('README.md') # or paths
33
33
 
34
+ # Quickly add everything from a directory into the crate
35
+ crate = ROCrate::Crate.new
36
+ crate.add_all('workspace/secret_project/dataset123')
37
+
34
38
  # Write to a zip file
35
39
  ROCrate::Writer.new(crate).write_zip(File.new('ro_crate.zip', 'w'))
36
40
 
37
41
  # Write to a directory
38
42
  ROCrate::Writer.new(crate).write('./ro_crate_stuff')
39
43
 
40
- # Read an RO crate
44
+ # Read an RO-Crate
41
45
  crate = ROCrate::Reader.read('./an_ro_crate_directory')
42
46
 
43
47
  # Make some changes
@@ -55,9 +59,9 @@ ext_file = crate.add_external_file('https://example.com/my_file.txt')
55
59
  ROCrate::Writer.new(crate).write('./an_ro_crate_directory')
56
60
  ```
57
61
 
58
- ### RO Crate Preview
59
- A simple HTML preview page is generated when an RO Crate is written, containing a list of the crate's contents and some
60
- metadata. This preview is written to `ro-crate-preview.html` at the root of the RO Crate.
62
+ ### RO-Crate Preview
63
+ A simple HTML preview page is generated when an RO-Crate is written, containing a list of the crate's contents and some
64
+ metadata. This preview is written to `ro-crate-preview.html` at the root of the RO-Crate.
61
65
 
62
66
  The default template can be seen here [here](lib/ro_crate/ro-crate-preview.html.erb).
63
67
 
@@ -1,6 +1,6 @@
1
1
  module ROCrate
2
2
  ##
3
- # A wrapper class for Hash that adds methods to dereference Entities within an RO Crate.
3
+ # A wrapper class for Hash that adds methods to dereference Entities within an RO-Crate.
4
4
  class JSONLDHash < ::Hash
5
5
  def initialize(graph, content = {})
6
6
  @graph = graph
@@ -1,6 +1,6 @@
1
1
  module ROCrate
2
2
  ##
3
- # A class to represent a "Contextual Entity" within an RO Crate.
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
6
  def self.format_id(id)
@@ -1,6 +1,6 @@
1
1
  module ROCrate
2
2
  ##
3
- # A Ruby abstraction of an RO Crate.
3
+ # A Ruby abstraction of an RO-Crate.
4
4
  class Crate < Directory
5
5
  IDENTIFIER = './'.freeze
6
6
  attr_reader :data_entities
@@ -13,7 +13,7 @@ module ROCrate
13
13
  end
14
14
 
15
15
  ##
16
- # Initialize an empty RO Crate.
16
+ # Initialize an empty RO-Crate.
17
17
  def initialize(id = IDENTIFIER, properties = {})
18
18
  @data_entities = []
19
19
  @contextual_entities = []
@@ -24,7 +24,7 @@ module ROCrate
24
24
  # Create a new file and add it to the crate.
25
25
  #
26
26
  # @param source [String, Pathname, ::File, #read, nil] The source on the disk where this file will be read.
27
- # @param crate_path [String] The relative path within the RO crate where this file will be written.
27
+ # @param crate_path [String] The relative path within the RO-Crate where this file will be written.
28
28
  # @param entity_class [Class] The class to use to instantiate the Entity,
29
29
  # useful if you have created a subclass of ROCrate::File that you want to use. (defaults to ROCrate::File).
30
30
  # @param properties [Hash{String => Object}] A hash of JSON-LD properties to associate with this file.
@@ -51,7 +51,7 @@ module ROCrate
51
51
  # Create a new directory and add it to the crate.
52
52
  #
53
53
  # @param source_directory [String, Pathname, ::File, #read, nil] The source directory that will be included in the crate.
54
- # @param crate_path [String] The relative path within the RO crate where this directory will be written.
54
+ # @param crate_path [String] The relative path within the RO-Crate where this directory will be written.
55
55
  # @param entity_class [Class] The class to use to instantiate the Entity,
56
56
  # useful if you have created a subclass of ROCrate::Directory that you want to use. (defaults to ROCrate::Directory).
57
57
  # @param properties [Hash{String => Object}] A hash of JSON-LD properties to associate with this directory.
@@ -61,6 +61,35 @@ module ROCrate
61
61
  entity_class.new(self, source_directory, crate_path, properties).tap { |e| add_data_entity(e) }
62
62
  end
63
63
 
64
+ ##
65
+ # Recursively add the contents of the given source directory at the root of the crate.
66
+ # Useful for quickly RO-Crate-ifying a directory.
67
+ # Creates data entities for each file/directory discovered (excluding the top level directory itself) if `create_entities` is true.
68
+ #
69
+ # @param source_directory [String, Pathname, ::File,] The source directory that will be included in the crate.
70
+ # @param create_entities [Boolean] Whether to create data entities for the added content, or just include them anonymously.
71
+ # @param include_hidden [Boolean] Whether to include hidden files, i.e. those prefixed by a `.` (period).
72
+ #
73
+ # @return [Array<DataEntity>] Any entities that were created from the directory contents. Will be empty if `create_entities` was false.
74
+ def add_all(source_directory, create_entities = true, include_hidden: false)
75
+ added = []
76
+
77
+ if create_entities
78
+ list_all_files(source_directory, include_hidden: include_hidden).each do |rel_path|
79
+ source_path = Pathname.new(::File.join(source_directory, rel_path)).expand_path
80
+ if source_path.directory?
81
+ added << add_directory(source_path, rel_path)
82
+ else
83
+ added << add_file(source_path, rel_path)
84
+ end
85
+ end
86
+ else
87
+ populate_entries(Pathname.new(::File.expand_path(source_directory)), include_hidden: include_hidden)
88
+ end
89
+
90
+ added
91
+ end
92
+
64
93
  ##
65
94
  # Create a new ROCrate::Person and add it to the crate
66
95
  #
@@ -119,7 +148,7 @@ module ROCrate
119
148
  end
120
149
 
121
150
  ##
122
- # The RO crate metadata file
151
+ # The RO-Crate metadata file
123
152
  #
124
153
  # @return [Metadata]
125
154
  def metadata
@@ -127,7 +156,7 @@ module ROCrate
127
156
  end
128
157
 
129
158
  ##
130
- # The RO crate preview file
159
+ # The RO-Crate preview file
131
160
  #
132
161
  # @return [Preview]
133
162
  def preview
@@ -143,7 +172,7 @@ module ROCrate
143
172
  end
144
173
 
145
174
  ##
146
- # Entities for the metadata file and crate itself, which should be present in all RO crates.
175
+ # Entities for the metadata file and crate itself, which should be present in all RO-Crates.
147
176
  #
148
177
  # @return [Array<Entity>]
149
178
  def default_entities
@@ -186,18 +215,22 @@ module ROCrate
186
215
  entity.class.new(crate, entity.id, entity.raw_properties)
187
216
  end
188
217
 
218
+ alias_method :own_entries, :entries
189
219
  ##
190
- # A map of all the files/directories contained in the RO crate, where the key is the destination path within the crate
191
- # and the value is an Entry where the source data can be read.
220
+ # # The RO-Crate's "payload" of the crate - a map of all the files/directories contained in the RO-Crate, where the
221
+ # key is the destination path within the crate and the value is an Entry where the source data can be read.
192
222
  #
193
223
  # @return [Hash{String => Entry}>]
194
224
  def entries
195
- entries = {}
196
-
197
- (default_entities | data_entities).each do |entity|
198
- next if entity == self
199
- entity.entries.each do |path, io|
200
- entries[path] = io
225
+ # Gather a map of entries, starting from the crate itself, then any directory data entities, then finally any
226
+ # file data entities. This ensures in the case of a conflict, the more "specific" data entities take priority.
227
+ entries = own_entries
228
+ non_self_entities = default_entities.reject { |e| e == self }
229
+ sorted_entities = (non_self_entities | data_entities).sort_by { |e| e.is_a?(ROCrate::Directory) ? 0 : 1 }
230
+
231
+ sorted_entities.each do |entity|
232
+ entity.entries.each do |path, entry|
233
+ entries[path] = entry
201
234
  end
202
235
  end
203
236
 
@@ -207,5 +240,11 @@ module ROCrate
207
240
  def get_binding
208
241
  binding
209
242
  end
243
+
244
+ private
245
+
246
+ def full_entry_path(relative_path)
247
+ relative_path
248
+ end
210
249
  end
211
250
  end
@@ -1,6 +1,6 @@
1
1
  module ROCrate
2
2
  ##
3
- # A class to represent a "Data Entity" within an RO Crate.
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
  def self.format_id(id)
@@ -12,37 +12,34 @@ module ROCrate
12
12
  # Create a new Directory. PLEASE NOTE, the new directory will not be added to the crate. To do this, call
13
13
  # Crate#add_data_entity, or just use Crate#add_directory.
14
14
  #
15
- # @param crate [Crate] The RO crate that owns this directory.
15
+ # @param crate [Crate] The RO-Crate that owns this directory.
16
16
  # @param source_directory [String, Pathname, ::File, nil] The source directory that will be included in the crate.
17
- # @param crate_path [String] The relative path within the RO crate where this directory will be written.
17
+ # @param crate_path [String] The relative path within the RO-Crate where this directory will be written.
18
18
  # @param properties [Hash{String => Object}] A hash of JSON-LD properties to associate with this directory.
19
19
  def initialize(crate, source_directory = nil, crate_path = nil, properties = {})
20
20
  @directory_entries = {}
21
21
 
22
22
  if source_directory
23
- raise 'Not a directory' unless ::File.directory?(source_directory)
24
23
  source_directory = Pathname.new(::File.expand_path(source_directory))
24
+ @entry = Entry.new(source_directory)
25
+ populate_entries(source_directory)
25
26
  crate_path = source_directory.basename.to_s if crate_path.nil?
26
-
27
- Dir.chdir(source_directory) { Dir.glob('**/*') }.each do |rel_path|
28
- source_path = Pathname.new(::File.join(source_directory, rel_path)).expand_path
29
- @directory_entries[rel_path] = Entry.new(source_path)
30
- end
31
27
  end
32
28
 
33
29
  super(crate, crate_path, properties)
34
30
  end
35
31
 
36
32
  ##
37
- # A map of all the files/directories under this directory, where the key is the destination path within the crate
38
- # and the value is an Entry where the source data can be read.
33
+ # The "payload" of this directory - a map of all the files/directories, where the key is the destination path
34
+ # within the crate and the value is an Entry where the source data can be read.
39
35
  #
40
36
  # @return [Hash{String => Entry}>]
41
37
  def entries
42
38
  entries = {}
39
+ entries[filepath.chomp('/')] = @entry if @entry
43
40
 
44
41
  @directory_entries.each do |rel_path, entry|
45
- entries[::File.join(filepath, rel_path)] = entry
42
+ entries[full_entry_path(rel_path)] = entry
46
43
  end
47
44
 
48
45
  entries
@@ -50,6 +47,37 @@ module ROCrate
50
47
 
51
48
  private
52
49
 
50
+ ##
51
+ # Populate this directory with files/directories from a given source directory on disk.
52
+ #
53
+ # @param source_directory [Pathname] The source directory to populate from.
54
+ # @param include_hidden [Boolean] Whether to include hidden files, i.e. those prefixed by a `.` (period).
55
+ #
56
+ # @return [Hash{String => Entry}>] The files/directories that were populated.
57
+ # The key is the relative path of the file/directory, and the value is an Entry object where data can be read etc.
58
+ def populate_entries(source_directory, include_hidden: false)
59
+ raise 'Not a directory' unless ::File.directory?(source_directory)
60
+ @directory_entries = {}
61
+ list_all_files(source_directory, include_hidden: include_hidden).each do |rel_path|
62
+ source_path = Pathname.new(::File.join(source_directory, rel_path)).expand_path
63
+ @directory_entries[rel_path] = Entry.new(source_path)
64
+ end
65
+
66
+ @directory_entries
67
+ end
68
+
69
+ def full_entry_path(relative_path)
70
+ ::File.join(filepath, relative_path)
71
+ end
72
+
73
+ def list_all_files(source_directory, include_hidden: false)
74
+ args = ['**/*']
75
+ args << ::File::FNM_DOTMATCH if include_hidden
76
+ Dir.chdir(source_directory) { Dir.glob(*args) }.reject do |path|
77
+ path == '.' || path == '..' || path.end_with?('/.')
78
+ end
79
+ end
80
+
53
81
  def default_properties
54
82
  super.merge(
55
83
  '@id' => "#{SecureRandom.uuid}/",
@@ -1,7 +1,7 @@
1
1
  module ROCrate
2
2
  ##
3
- # A generic "Entity" within an RO Crate. It has an identifier and a set of properties, and will be referenced in the
4
- # RO Crate Metadata's @graph.
3
+ # A generic "Entity" within an RO-Crate. It has an identifier and a set of properties, and will be referenced in the
4
+ # RO-Crate Metadata's @graph.
5
5
  class Entity
6
6
  attr_reader :crate
7
7
  attr_reader :properties
@@ -60,7 +60,7 @@ module ROCrate
60
60
  ##
61
61
  # Automatically replace an Entity or Array of Entities with a reference or Array of references. Also associates
62
62
  # the Entity/Entities with the current crate. This is useful for maintaining the flat @graph of entities that the
63
- # RO crate metadata file requires.
63
+ # RO-Crate metadata file requires.
64
64
  #
65
65
  # @param value [Entity, Array<Entity>, Object] A value that may be reference or array of references.
66
66
  # @return [Hash, Array<Hash>, Object] Return a reference, Array of references, or just the object itself if
@@ -1,6 +1,6 @@
1
1
  module ROCrate
2
2
  ##
3
- # A class to represent a "physical" file or directory within an RO crate.
3
+ # A class to represent a "physical" file or directory within an RO-Crate.
4
4
  # It handles the actual reading/writing of bytes.
5
5
  class Entry
6
6
  attr_reader :source
@@ -8,9 +8,9 @@ module ROCrate
8
8
  # Create a new ROCrate::File. PLEASE NOTE, the new file will not be added to the crate. To do this, call
9
9
  # Crate#add_data_entity, or just use Crate#add_file.
10
10
  #
11
- # @param crate [Crate] The RO crate that owns this file.
11
+ # @param crate [Crate] The RO-Crate that owns this file.
12
12
  # @param source [String, Pathname, ::File, #read, URI, nil] The source on the disk (or on the internet if a URI) where this file will be read.
13
- # @param crate_path [String] The relative path within the RO crate where this file will be written.
13
+ # @param crate_path [String] The relative path within the RO-Crate where this file will be written.
14
14
  # @param properties [Hash{String => Object}] A hash of JSON-LD properties to associate with this file.
15
15
  def initialize(crate, source, crate_path = nil, properties = {})
16
16
  if crate_path.is_a?(Hash) && properties.empty?
@@ -52,7 +52,9 @@ module ROCrate
52
52
  end
53
53
 
54
54
  ##
55
- # A map of all the files/directories associated with this File. Should only be a single key and value.
55
+ # The "payload". A map with a single key and value, of the relative filepath within the crate, to the source on disk
56
+ # where the actual bytes of the file can be read. Blank if remote.
57
+ #
56
58
  # (for compatibility with Directory#entries)
57
59
  #
58
60
  # @return [Hash{String => Entry}>] The key is the location within the crate, and the value is an Entry.
@@ -1,6 +1,6 @@
1
1
  module ROCrate
2
2
  ##
3
- # A class to represent a reference within an RO crate, to a remote file held on the internet somewhere.
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
5
  class RemoteEntry
6
6
  attr_reader :uri
@@ -1,13 +1,13 @@
1
1
  module ROCrate
2
2
  ##
3
- # A class to handle reading of RO Crates from Zip files or directories.
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 of zip file.
6
+ # Reads an RO-Crate from a directory of zip file.
7
7
  #
8
8
  # @param source [String, ::File, Pathname] The source location for the crate.
9
9
  # @param target_dir [String, ::File, Pathname] The target directory where the crate should be unzipped (if its a Zip file).
10
- # @return [Crate] The RO Crate.
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
  if ::File.directory?(source)
@@ -37,12 +37,12 @@ module ROCrate
37
37
  end
38
38
 
39
39
  ##
40
- # Reads an RO Crate from a zip file. It first extracts the Zip file to a temporary directory, and then calls
40
+ # Reads an RO-Crate from a zip file. It first extracts the Zip file to a temporary directory, and then calls
41
41
  # #read_directory.
42
42
  #
43
43
  # @param source [String, ::File, Pathname] The location of the zip file.
44
44
  # @param target_dir [String, ::File, Pathname] The target directory where the crate should be unzipped.
45
- # @return [Crate] The RO Crate.
45
+ # @return [Crate] The RO-Crate.
46
46
  def self.read_zip(source, target_dir: Dir.mktmpdir)
47
47
  unzip_to(source, target_dir)
48
48
 
@@ -50,10 +50,10 @@ module ROCrate
50
50
  end
51
51
 
52
52
  ##
53
- # Reads an RO Crate from a directory.
53
+ # Reads an RO-Crate from a directory.
54
54
  #
55
55
  # @param source [String, ::File, Pathname] The location of the directory.
56
- # @return [Crate] The RO Crate.
56
+ # @return [Crate] The RO-Crate.
57
57
  def self.read_directory(source)
58
58
  source = ::File.expand_path(source)
59
59
  metadata_file = Dir.entries(source).detect { |entry| entry == ROCrate::Metadata::IDENTIFIER ||
@@ -68,7 +68,7 @@ module ROCrate
68
68
  end
69
69
 
70
70
  ##
71
- # Extracts all the entities from the @graph of the RO Crate Metadata.
71
+ # Extracts all the entities from the @graph of the RO-Crate Metadata.
72
72
  #
73
73
  # @param metadata_json [String] A string containing the metadata JSON.
74
74
  # @return [Hash{String => Hash}] A Hash of all the entities, mapped by their @id.
@@ -99,12 +99,15 @@ module ROCrate
99
99
  # Create a crate from the given set of entities.
100
100
  #
101
101
  # @param entity_hash [Hash{String => Hash}] A Hash containing all the entities in the @graph, mapped by their @id.
102
- # @param source [String, ::File, Pathname] The location of the RO Crate being read.
103
- # @return [Crate] The RO Crate.
102
+ # @param source [String, ::File, Pathname] The location of the RO-Crate being read.
103
+ # @return [Crate] The RO-Crate.
104
104
  def self.build_crate(entity_hash, source)
105
105
  ROCrate::Crate.new.tap do |crate|
106
106
  crate.properties = entity_hash.delete(ROCrate::Crate::IDENTIFIER)
107
107
  crate.metadata.properties = entity_hash.delete(ROCrate::Metadata::IDENTIFIER)
108
+ preview_properties = entity_hash.delete(ROCrate::Preview::IDENTIFIER)
109
+ crate.preview.properties = preview_properties if preview_properties
110
+ crate.add_all(source, false)
108
111
  extract_data_entities(crate, source, entity_hash).each do |entity|
109
112
  crate.add_data_entity(entity)
110
113
  end
@@ -118,8 +121,8 @@ module ROCrate
118
121
  ##
119
122
  # Discover data entities from the `hasPart` property of a crate, and create DataEntity objects for them.
120
123
  # Entities are looked up in the given `entity_hash` (and then removed from it).
121
- # @param crate [Crate] The RO Crate being read.
122
- # @param source [String, ::File, Pathname] The location of the RO Crate being read.
124
+ # @param crate [Crate] The RO-Crate being read.
125
+ # @param source [String, ::File, Pathname] The location of the RO-Crate being read.
123
126
  # @param entity_hash [Hash] A Hash containing all the entities in the @graph, mapped by their @id.
124
127
  # @return [Array<ROCrate::File, ROCrate::Directory>] The extracted DataEntity objects.
125
128
  def self.extract_data_entities(crate, source, entity_hash)
@@ -135,7 +138,7 @@ module ROCrate
135
138
 
136
139
  ##
137
140
  # Create appropriately specialized ContextualEntity objects from the given hash of entities and their properties.
138
- # @param crate [Crate] The RO Crate being read.
141
+ # @param crate [Crate] The RO-Crate being read.
139
142
  # @param entity_hash [Hash] A Hash containing all the entities in the @graph, mapped by their @id.
140
143
  # @return [Array<ContextualEntity>] The extracted ContextualEntity objects.
141
144
  def self.extract_contextual_entities(crate, entity_hash)
@@ -152,8 +155,8 @@ module ROCrate
152
155
 
153
156
  ##
154
157
  # Create a DataEntity of the given class.
155
- # @param crate [Crate] The RO Crate being read.
156
- # @param source [String, ::File, Pathname] The location of the RO Crate being read.
158
+ # @param crate [Crate] The RO-Crate being read.
159
+ # @param source [String, ::File, Pathname] The location of the RO-Crate being read.
157
160
  # @param entity_props [Hash] A Hash containing the entity's properties, including its @id.
158
161
  # @return [ROCrate::File, ROCrate::Directory, nil] The DataEntity object,
159
162
  # or nil if it referenced a local file that wasn't found.
@@ -1,13 +1,13 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title><%= name || "New RO Crate" %></title>
4
+ <title><%= name || "New RO-Crate" %></title>
5
5
  <script type="application/ld+json"><%= metadata.generate %></script>
6
6
  <meta name="generator" content="https://github.com/fbacall/ro-crate-ruby">
7
- <meta name="keywords" content="RO Crate">
7
+ <meta name="keywords" content="RO-Crate">
8
8
  </head>
9
9
  <body>
10
- <h1><%= name || "New RO Crate" %></h1>
10
+ <h1><%= name || "New RO-Crate" %></h1>
11
11
  <% if url %>
12
12
  <a href="<%= url %>" target="_blank"><%= url %></a>
13
13
  <% end %>
@@ -1,10 +1,10 @@
1
1
  module ROCrate
2
2
  ##
3
- # A class to handle writing of RO Crates to Zip files or directories.
3
+ # A class to handle writing of RO-Crates to Zip files or directories.
4
4
  class Writer
5
5
  ##
6
6
  # Initialize a new Writer for the given Crate.
7
- # @param crate [Crate] The RO Crate to be written.
7
+ # @param crate [Crate] The RO-Crate to be written.
8
8
  def initialize(crate)
9
9
  @crate = crate
10
10
  end
@@ -35,7 +35,7 @@ module ROCrate
35
35
  ##
36
36
  # Write the crate to a zip file.
37
37
  #
38
- # @param destination [String, ::File] The destination where to write the RO Crate zip.
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
41
  @crate.entries.each do |path, entry|
data/ro_crate.gemspec CHANGED
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'ro-crate'
3
- s.version = '0.4.1'
4
- s.summary = 'Create, manipulate, read RO crates.'
3
+ s.version = '0.4.6'
4
+ s.summary = 'Create, manipulate, read RO-Crates.'
5
5
  s.authors = ['Finn Bacall']
6
6
  s.email = 'finn.bacall@manchester.ac.uk'
7
7
  s.files = `git ls-files`.split("\n")
data/test/crate_test.rb CHANGED
@@ -198,4 +198,99 @@ class CrateTest < Test::Unit::TestCase
198
198
  assert_equal file, new_crate.get('some/file.txt')
199
199
  assert_equal file, new_crate.get('http://mycoolwebsite.golf/ro_crate/some/file.txt')
200
200
  end
201
+
202
+ test 'can add an entire directory tree as data entities' do
203
+ crate = ROCrate::Crate.new
204
+ entities = crate.add_all(fixture_file('directory').path, include_hidden: true)
205
+
206
+ paths = crate.entries.keys
207
+ assert_equal 11, paths.length
208
+ assert_includes paths, 'data'
209
+ assert_includes paths, 'root.txt'
210
+ assert_includes paths, 'info.txt'
211
+ assert_includes paths, 'data/binary.jpg'
212
+ assert_includes paths, 'data/info.txt'
213
+ assert_includes paths, 'data/nested.txt'
214
+ assert_includes paths, '.dotfile'
215
+ assert_includes paths, '.dir'
216
+ assert_includes paths, '.dir/test.txt'
217
+ assert_includes paths, 'ro-crate-metadata.json'
218
+ assert_includes paths, 'ro-crate-preview.html'
219
+
220
+ assert_equal 9, entities.length
221
+ assert_equal 'ROCrate::Directory', crate.dereference('data/').class.name
222
+ assert_equal 'ROCrate::File', crate.dereference('root.txt').class.name
223
+ assert_equal 'ROCrate::File', crate.dereference('info.txt').class.name
224
+ assert_equal 'ROCrate::File', crate.dereference('data/binary.jpg').class.name
225
+ assert_equal 'ROCrate::File', crate.dereference('data/info.txt').class.name
226
+ assert_equal 'ROCrate::File', crate.dereference('data/nested.txt').class.name
227
+ assert_equal 'ROCrate::File', crate.dereference('.dotfile').class.name
228
+ assert_equal 'ROCrate::Directory', crate.dereference('.dir/').class.name
229
+ assert_equal 'ROCrate::File', crate.dereference('.dir/test.txt').class.name
230
+
231
+ assert_equal "5678\n", crate.dereference('data/info.txt').source.read
232
+ assert_equal "Am I included?\n", crate.dereference('.dotfile').source.read
233
+ end
234
+
235
+ test 'can create an RO-Crate using content from a given directory' do
236
+ crate = ROCrate::Crate.new
237
+ entities = crate.add_all(fixture_file('directory').path, false, include_hidden: true)
238
+
239
+ assert_empty entities
240
+
241
+ paths = crate.entries.keys
242
+ assert_equal 11, paths.length
243
+ assert_includes paths, 'data'
244
+ assert_includes paths, 'root.txt'
245
+ assert_includes paths, 'info.txt'
246
+ assert_includes paths, 'data/binary.jpg'
247
+ assert_includes paths, 'data/info.txt'
248
+ assert_includes paths, 'data/nested.txt'
249
+ assert_includes paths, '.dotfile'
250
+ assert_includes paths, '.dir'
251
+ assert_includes paths, '.dir/test.txt'
252
+ assert_includes paths, 'ro-crate-metadata.json'
253
+ assert_includes paths, 'ro-crate-preview.html'
254
+
255
+ # Should not create any data entities
256
+ assert_nil crate.dereference('data/')
257
+ assert_nil crate.dereference('root.txt')
258
+ assert_nil crate.dereference('info.txt')
259
+ assert_nil crate.dereference('data/binary.jpg')
260
+ assert_nil crate.dereference('data/info.txt')
261
+ assert_nil crate.dereference('data/nested.txt')
262
+ assert_nil crate.dereference('.dotfile')
263
+ end
264
+
265
+ test 'can create an RO-Crate using content from a given directory, excluding hidden files' do
266
+ crate = ROCrate::Crate.new
267
+ entities = crate.add_all(fixture_file('directory').path)
268
+
269
+ paths = crate.entries.keys
270
+ assert_equal 8, paths.length
271
+ assert_includes paths, 'data'
272
+ assert_includes paths, 'root.txt'
273
+ assert_includes paths, 'info.txt'
274
+ assert_includes paths, 'data/binary.jpg'
275
+ assert_includes paths, 'data/info.txt'
276
+ assert_includes paths, 'data/nested.txt'
277
+ assert_not_includes paths, '.dotfile'
278
+ assert_not_includes paths, '.dir'
279
+ assert_not_includes paths, '.dir/test.txt'
280
+ assert_includes paths, 'ro-crate-metadata.json'
281
+ assert_includes paths, 'ro-crate-preview.html'
282
+
283
+ assert_equal 6, entities.length
284
+ assert_equal 'ROCrate::Directory', crate.dereference('data/').class.name
285
+ assert_equal 'ROCrate::File', crate.dereference('root.txt').class.name
286
+ assert_equal 'ROCrate::File', crate.dereference('info.txt').class.name
287
+ assert_equal 'ROCrate::File', crate.dereference('data/binary.jpg').class.name
288
+ assert_equal 'ROCrate::File', crate.dereference('data/info.txt').class.name
289
+ assert_equal 'ROCrate::File', crate.dereference('data/nested.txt').class.name
290
+ assert_nil crate.dereference('.dotfile')
291
+ assert_nil crate.dereference('.dir/')
292
+ assert_nil crate.dereference('.dir/test.txt')
293
+
294
+ assert_equal "5678\n", crate.dereference('data/info.txt').source.read
295
+ end
201
296
  end
Binary file
@@ -0,0 +1 @@
1
+ 123
@@ -0,0 +1 @@
1
+ Am I included?
@@ -8,6 +8,13 @@
8
8
  "@id": "./"
9
9
  }
10
10
  },
11
+ {
12
+ "@id": "ro-crate-preview.html",
13
+ "@type": "CreativeWork",
14
+ "about": {
15
+ "@id": "./"
16
+ }
17
+ },
11
18
  {
12
19
  "@id": "./",
13
20
  "@type": "Dataset",
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>New RO-Crate</title>
5
+ <script type="application/ld+json">{
6
+ "@context": "https://w3id.org/ro/crate/1.1/context",
7
+ "@graph": [
8
+ {
9
+ "@id": "ro-crate-metadata.jsonld",
10
+ "@type": "CreativeWork",
11
+ "about": {
12
+ "@id": "./"
13
+ }
14
+ },
15
+ {
16
+ "@id": "ro-crate-preview.html",
17
+ "@type": "CreativeWork",
18
+ "about": {
19
+ "@id": "./"
20
+ }
21
+ },
22
+ {
23
+ "@id": "./",
24
+ "@type": "Dataset",
25
+ "hasPart": [
26
+ {
27
+ "@id": "fish/"
28
+ }
29
+ ]
30
+ },
31
+ {
32
+ "@id": "fish/",
33
+ "@type": "Dataset"
34
+ }
35
+ ]
36
+ }</script>
37
+ <meta name="generator" content="https://github.com/fbacall/ro-crate-ruby">
38
+ <meta name="keywords" content="RO-Crate">
39
+ </head>
40
+ <body>
41
+ <h1>New RO-Crate</h1>
42
+
43
+ <p>
44
+
45
+ </p>
46
+ <dl>
47
+
48
+
49
+
50
+
51
+ </dl>
52
+
53
+ <h2>Contents</h2>
54
+ <ul>
55
+
56
+ <li id="__data_entity_fish/">
57
+
58
+ <strong>fish/</strong>
59
+
60
+
61
+
62
+ </li>
63
+
64
+ </ul>
65
+ </body>
66
+ </html>
@@ -0,0 +1 @@
1
+ I'm at the root
@@ -0,0 +1,32 @@
1
+ {
2
+ "@context": "https://w3id.org/ro/crate/1.0/context",
3
+ "@graph": [
4
+ {
5
+ "@id": "ro-crate-metadata.jsonld",
6
+ "@type": "CreativeWork",
7
+ "about": {
8
+ "@id": "./"
9
+ }
10
+ },
11
+ {
12
+ "@id": "ro-crate-preview.html",
13
+ "@type": "CreativeWork",
14
+ "about": {
15
+ "@id": "./"
16
+ }
17
+ },
18
+ {
19
+ "@id": "./",
20
+ "@type": "Dataset",
21
+ "hasPart": [
22
+ {
23
+ "@id": "listed_file.txt"
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "@id": "listed_file.txt",
29
+ "@type": "File"
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,66 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>New RO-Crate</title>
5
+ <script type="application/ld+json">{
6
+ "@context": "https://w3id.org/ro/crate/1.1/context",
7
+ "@graph": [
8
+ {
9
+ "@id": "ro-crate-metadata.jsonld",
10
+ "@type": "CreativeWork",
11
+ "about": {
12
+ "@id": "./"
13
+ }
14
+ },
15
+ {
16
+ "@id": "ro-crate-preview.html",
17
+ "@type": "CreativeWork",
18
+ "about": {
19
+ "@id": "./"
20
+ }
21
+ },
22
+ {
23
+ "@id": "./",
24
+ "@type": "Dataset",
25
+ "hasPart": [
26
+ {
27
+ "@id": "fish/"
28
+ }
29
+ ]
30
+ },
31
+ {
32
+ "@id": "fish/",
33
+ "@type": "Dataset"
34
+ }
35
+ ]
36
+ }</script>
37
+ <meta name="generator" content="https://github.com/fbacall/ro-crate-ruby">
38
+ <meta name="keywords" content="RO-Crate">
39
+ </head>
40
+ <body>
41
+ <h1>New RO-Crate</h1>
42
+
43
+ <p>
44
+
45
+ </p>
46
+ <dl>
47
+
48
+
49
+
50
+
51
+ </dl>
52
+
53
+ <h2>Contents</h2>
54
+ <ul>
55
+
56
+ <li id="__data_entity_fish/">
57
+
58
+ <strong>fish/</strong>
59
+
60
+
61
+
62
+ </li>
63
+
64
+ </ul>
65
+ </body>
66
+ </html>
data/test/reader_test.rb CHANGED
@@ -109,11 +109,13 @@ class ReaderTest < Test::Unit::TestCase
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) }
112
113
  assert crate.entries['fish/info.txt']
113
114
  assert_equal '1234', crate.entries['fish/info.txt'].source.read.chomp
114
- assert crate.entries['fish/root.txt']
115
+ refute crate.entries['fish/root.txt'].directory?
116
+ assert crate.entries['fish/data'].directory?
115
117
  assert crate.entries['fish/data/info.txt']
116
- assert crate.entries['fish/data/nested.txt']
118
+ refute crate.entries['fish/data/nested.txt'].remote?
117
119
  assert crate.entries['fish/data/binary.jpg']
118
120
  assert_equal ['./', 'fish/', 'ro-crate-metadata.jsonld', 'ro-crate-preview.html'], crate.entities.map(&:id).sort
119
121
  end
@@ -161,4 +163,36 @@ class ReaderTest < Test::Unit::TestCase
161
163
  assert_equal 'http://example.com/external_ref.txt', ext_file.id
162
164
  assert_equal 'file contents', ext_file.source.read
163
165
  end
166
+
167
+ test 'reading from directory with unlisted files' do
168
+ crate = ROCrate::Reader.read_directory(fixture_file('sparse_directory_crate').path)
169
+
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']
180
+ assert_equal ['./', 'listed_file.txt', 'ro-crate-metadata.jsonld', 'ro-crate-preview.html'], crate.entities.map(&:id).sort
181
+ end
182
+
183
+ test 'reading from a zip with unlisted files' do
184
+ crate = ROCrate::Reader.read_zip(fixture_file('sparse_directory_crate.zip').path)
185
+
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']
196
+ assert_equal ['./', 'listed_file.txt', 'ro-crate-metadata.jsonld', 'ro-crate-preview.html'], crate.entities.map(&:id).sort
197
+ end
164
198
  end
data/test/writer_test.rb CHANGED
@@ -99,4 +99,37 @@ class WriterTest < Test::Unit::TestCase
99
99
  end
100
100
  end
101
101
  end
102
+
103
+ test 'should write out same contents that it was created with' do
104
+ crate = ROCrate::Crate.new
105
+ crate.add_all(fixture_file('directory').path, false)
106
+
107
+ Dir.mktmpdir do |dir|
108
+ ROCrate::Writer.new(crate).write(dir)
109
+ assert ::File.exist?(::File.join(dir, ROCrate::Metadata::IDENTIFIER))
110
+ assert ::File.exist?(::File.join(dir, ROCrate::Preview::IDENTIFIER))
111
+ assert_equal 5, ::File.size(::File.join(dir, 'info.txt'))
112
+ assert_equal 2529, ::File.size(::File.join(dir, 'data', 'binary.jpg'))
113
+ end
114
+ end
115
+
116
+ test 'reading and writing out a directory crate produces an identical crate' do
117
+ fixture = fixture_file('sparse_directory_crate').path
118
+ Dir.mktmpdir do |dir|
119
+ dir = ::File.join(dir, 'new_directory')
120
+ crate = ROCrate::Reader.read(fixture)
121
+
122
+ ROCrate::Writer.new(crate).write(dir)
123
+ expected_files = Dir.chdir(fixture) { Dir.glob('**/*') }
124
+ actual_files = Dir.chdir(dir) { Dir.glob('**/*') }
125
+ assert_equal expected_files, actual_files
126
+ expected_files.each do |file|
127
+ next if file == 'ro-crate-metadata.jsonld' # Expected context gets updated.
128
+ next if file == 'ro-crate-preview.html' # RO-Crate preview format changed
129
+ abs_file_path = ::File.join(fixture, file)
130
+ next if ::File.directory?(abs_file_path)
131
+ assert_equal ::File.read(abs_file_path), ::File.read(::File.join(dir, file)), "#{file} didn't match"
132
+ end
133
+ end
134
+ end
102
135
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ro-crate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.6
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-01-18 00:00:00.000000000 Z
11
+ date: 2021-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -149,6 +149,8 @@ files:
149
149
  - test/fixtures/crate-spec1.1/ro-crate-metadata.json
150
150
  - test/fixtures/data.csv
151
151
  - test/fixtures/directory.zip
152
+ - test/fixtures/directory/.dir/test.txt
153
+ - test/fixtures/directory/.dotfile
152
154
  - test/fixtures/directory/data/binary.jpg
153
155
  - test/fixtures/directory/data/info.txt
154
156
  - test/fixtures/directory/data/nested.txt
@@ -160,10 +162,21 @@ files:
160
162
  - test/fixtures/directory_crate/fish/info.txt
161
163
  - test/fixtures/directory_crate/fish/root.txt
162
164
  - test/fixtures/directory_crate/ro-crate-metadata.jsonld
165
+ - test/fixtures/directory_crate/ro-crate-preview.html
163
166
  - test/fixtures/file with spaces.txt
164
167
  - test/fixtures/info.txt
165
168
  - test/fixtures/spaces/file with spaces.txt
166
169
  - test/fixtures/spaces/ro-crate-metadata.jsonld
170
+ - test/fixtures/sparse_directory_crate.zip
171
+ - test/fixtures/sparse_directory_crate/fish/data/binary.jpg
172
+ - test/fixtures/sparse_directory_crate/fish/data/info.txt
173
+ - test/fixtures/sparse_directory_crate/fish/data/nested.txt
174
+ - test/fixtures/sparse_directory_crate/fish/info.txt
175
+ - test/fixtures/sparse_directory_crate/fish/root.txt
176
+ - test/fixtures/sparse_directory_crate/listed_file.txt
177
+ - test/fixtures/sparse_directory_crate/ro-crate-metadata.jsonld
178
+ - test/fixtures/sparse_directory_crate/ro-crate-preview.html
179
+ - test/fixtures/sparse_directory_crate/unlisted_file.txt
167
180
  - test/fixtures/workflow-0.2.0.zip
168
181
  - test/fixtures/workflow-0.2.0/Dockerfile
169
182
  - test/fixtures/workflow-0.2.0/README.md
@@ -2414,5 +2427,5 @@ requirements: []
2414
2427
  rubygems_version: 3.0.8
2415
2428
  signing_key:
2416
2429
  specification_version: 4
2417
- summary: Create, manipulate, read RO crates.
2430
+ summary: Create, manipulate, read RO-Crates.
2418
2431
  test_files: []