ro-crate 0.4.1 → 0.4.6

Sign up to get free protection for your applications and to get access to all the features.
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: []