ro-crate 0.4.4 → 0.4.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e3f58e7b54e7d04913b3ab7ed778fe0c2fbfa01c9571ec7d5c62d9fb7ec9555
4
- data.tar.gz: 5b544b0a343c185571a2fccc1438bfd53426b7f6a02d4605b4d7a7f517058560
3
+ metadata.gz: 88f4e08570b547ac985a759af19d619747dd78a9001cd7994f314a42c014e001
4
+ data.tar.gz: e3711f54d12c1b4d6b1d68d16923dd051ae2b3a01a28a1d1e155a471bb84bfab
5
5
  SHA512:
6
- metadata.gz: e12c059af0471d6423fc0af754713d66f0d654d3196d331fc2d3a8a7d9f6642e8e7fada4749ad7fa2b7478cc64be4652b09aba2d1851d2d7f44100d1d52a6b14
7
- data.tar.gz: c0c2654d4261aad3f2e9db64b64338af9425e7aea73a92848a98561bf27ea3f0489a926857efc5e80990475b17b42a499657cad292e8b0e8d7593be647ab6d80
6
+ metadata.gz: 2a468ad85761945fc782b5ccb6943e4d1b53c7bb528dba59ed19f93ab2af08017f212d93bf47f9af983b6f00fd304dd1b2edfaef5e096f04ffdb181fd75352e5
7
+ data.tar.gz: 89edb2f44a6842a7409c2e3107e76b3c401533738c32e6c18faecc6c9c29e6fd71a0d06fbe20f67fdec62564d00b3c168da36b29662ab8badfe652d923b7ab58
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ro-crate (0.4.4)
4
+ ro-crate (0.4.9)
5
5
  addressable (~> 2.7.0)
6
6
  rubyzip (~> 2.0.0)
7
7
 
@@ -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.format_id(id)
6
+ def self.format_local_id(id)
7
7
  i = super
8
- begin
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
  ##
@@ -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
@@ -68,22 +73,23 @@ module ROCrate
68
73
  #
69
74
  # @param source_directory [String, Pathname, ::File,] The source directory that will be included in the crate.
70
75
  # @param create_entities [Boolean] Whether to create data entities for the added content, or just include them anonymously.
76
+ # @param include_hidden [Boolean] Whether to include hidden files, i.e. those prefixed by a `.` (period).
71
77
  #
72
78
  # @return [Array<DataEntity>] Any entities that were created from the directory contents. Will be empty if `create_entities` was false.
73
- def add_all(source_directory, create_entities = true)
79
+ def add_all(source_directory, create_entities = true, include_hidden: false)
74
80
  added = []
75
81
 
76
- list_all_files(source_directory).each do |rel_path|
77
- source_path = Pathname.new(::File.join(source_directory, rel_path)).expand_path
78
- if create_entities
82
+ if create_entities
83
+ list_all_files(source_directory, include_hidden: include_hidden).each do |rel_path|
84
+ source_path = Pathname.new(::File.join(source_directory, rel_path)).expand_path
79
85
  if source_path.directory?
80
86
  added << add_directory(source_path, rel_path)
81
87
  else
82
88
  added << add_file(source_path, rel_path)
83
89
  end
84
- else
85
- populate_entries(Pathname.new(::File.expand_path(source_directory)))
86
90
  end
91
+ else
92
+ populate_entries(Pathname.new(::File.expand_path(source_directory)), include_hidden: include_hidden)
87
93
  end
88
94
 
89
95
  added
@@ -216,16 +222,20 @@ module ROCrate
216
222
 
217
223
  alias_method :own_entries, :entries
218
224
  ##
219
- # A map of all the files/directories contained in the RO-Crate, where the key is the destination path within the crate
220
- # and the value is an Entry where the source data can be read.
225
+ # # The RO-Crate's "payload" of the crate - a map of all the files/directories contained in the RO-Crate, where the
226
+ # key is the destination path within the crate and the value is an Entry where the source data can be read.
221
227
  #
222
228
  # @return [Hash{String => Entry}>]
223
229
  def entries
224
- entries = {}
225
-
226
- (default_entities | data_entities).each do |entity|
227
- (entity == self ? own_entries : entity.entries).each do |path, io|
228
- entries[path] = io
230
+ # Gather a map of entries, starting from the crate itself, then any directory data entities, then finally any
231
+ # file data entities. This ensures in the case of a conflict, the more "specific" data entities take priority.
232
+ entries = own_entries
233
+ non_self_entities = default_entities.reject { |e| e == self }
234
+ sorted_entities = (non_self_entities | data_entities).sort_by { |e| e.is_a?(ROCrate::Directory) ? 0 : 1 }
235
+
236
+ sorted_entities.each do |entity|
237
+ entity.entries.each do |path, entry|
238
+ entries[path] = entry
229
239
  end
230
240
  end
231
241
 
@@ -3,7 +3,7 @@ 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
- def self.format_id(id)
6
+ def self.format_local_id(id)
7
7
  super.chomp('/')
8
8
  end
9
9
 
@@ -4,7 +4,7 @@ module ROCrate
4
4
  class Directory < DataEntity
5
5
  properties(%w[name contentSize dateModified encodingFormat identifier sameAs])
6
6
 
7
- def self.format_id(id)
7
+ def self.format_local_id(id)
8
8
  super + '/'
9
9
  end
10
10
 
@@ -21,7 +21,7 @@ module ROCrate
21
21
 
22
22
  if source_directory
23
23
  source_directory = Pathname.new(::File.expand_path(source_directory))
24
- @entry = source_directory
24
+ @entry = Entry.new(source_directory)
25
25
  populate_entries(source_directory)
26
26
  crate_path = source_directory.basename.to_s if crate_path.nil?
27
27
  end
@@ -30,8 +30,8 @@ module ROCrate
30
30
  end
31
31
 
32
32
  ##
33
- # A map of all the files/directories under this directory, where the key is the destination path within the crate
34
- # 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.
35
35
  #
36
36
  # @return [Hash{String => Entry}>]
37
37
  def entries
@@ -51,13 +51,14 @@ module ROCrate
51
51
  # Populate this directory with files/directories from a given source directory on disk.
52
52
  #
53
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).
54
55
  #
55
56
  # @return [Hash{String => Entry}>] The files/directories that were populated.
56
57
  # The key is the relative path of the file/directory, and the value is an Entry object where data can be read etc.
57
- def populate_entries(source_directory)
58
+ def populate_entries(source_directory, include_hidden: false)
58
59
  raise 'Not a directory' unless ::File.directory?(source_directory)
59
60
  @directory_entries = {}
60
- list_all_files(source_directory).each do |rel_path|
61
+ list_all_files(source_directory, include_hidden: include_hidden).each do |rel_path|
61
62
  source_path = Pathname.new(::File.join(source_directory, rel_path)).expand_path
62
63
  @directory_entries[rel_path] = Entry.new(source_path)
63
64
  end
@@ -69,8 +70,10 @@ module ROCrate
69
70
  ::File.join(filepath, relative_path)
70
71
  end
71
72
 
72
- def list_all_files(source_directory)
73
- Dir.chdir(source_directory) { Dir.glob('**/*', ::File::FNM_DOTMATCH) }.reject do |path|
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|
74
77
  path == '.' || path == '..' || path.end_with?('/.')
75
78
  end
76
79
  end
@@ -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
- def self.format_id(id)
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
 
@@ -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.
@@ -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 of zip file.
6
+ # Reads an RO-Crate from a directory or zip file.
7
7
  #
8
- # @param source [String, ::File, Pathname] The source location for the crate.
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
- if ::File.directory?(source)
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.unzip_to(source, target)
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(source) do |zipfile|
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,7 +78,7 @@ 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)
@@ -55,6 +93,8 @@ module ROCrate
55
93
  # @param source [String, ::File, Pathname] The location of the directory.
56
94
  # @return [Crate] The RO-Crate.
57
95
  def self.read_directory(source)
96
+ raise "Not a directory!" unless ::File.directory?(source)
97
+
58
98
  source = ::File.expand_path(source)
59
99
  metadata_file = Dir.entries(source).detect { |entry| entry == ROCrate::Metadata::IDENTIFIER ||
60
100
  entry == ROCrate::Metadata::IDENTIFIER_1_0 }
@@ -107,6 +147,7 @@ module ROCrate
107
147
  crate.metadata.properties = entity_hash.delete(ROCrate::Metadata::IDENTIFIER)
108
148
  preview_properties = entity_hash.delete(ROCrate::Preview::IDENTIFIER)
109
149
  crate.preview.properties = preview_properties if preview_properties
150
+ crate.add_all(source, false)
110
151
  extract_data_entities(crate, source, entity_hash).each do |entity|
111
152
  crate.add_data_entity(entity)
112
153
  end
data/ro_crate.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'ro-crate'
3
- s.version = '0.4.4'
3
+ s.version = '0.4.9'
4
4
  s.summary = 'Create, manipulate, read RO-Crates.'
5
5
  s.authors = ['Finn Bacall']
6
6
  s.email = 'finn.bacall@manchester.ac.uk'
data/test/crate_test.rb CHANGED
@@ -201,7 +201,7 @@ class CrateTest < Test::Unit::TestCase
201
201
 
202
202
  test 'can add an entire directory tree as data entities' do
203
203
  crate = ROCrate::Crate.new
204
- entities = crate.add_all(fixture_file('directory').path)
204
+ entities = crate.add_all(fixture_file('directory').path, include_hidden: true)
205
205
 
206
206
  paths = crate.entries.keys
207
207
  assert_equal 11, paths.length
@@ -234,7 +234,7 @@ class CrateTest < Test::Unit::TestCase
234
234
 
235
235
  test 'can create an RO-Crate using content from a given directory' do
236
236
  crate = ROCrate::Crate.new
237
- entities = crate.add_all(fixture_file('directory').path, false)
237
+ entities = crate.add_all(fixture_file('directory').path, false, include_hidden: true)
238
238
 
239
239
  assert_empty entities
240
240
 
@@ -261,4 +261,36 @@ class CrateTest < Test::Unit::TestCase
261
261
  assert_nil crate.dereference('data/nested.txt')
262
262
  assert_nil crate.dereference('.dotfile')
263
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
264
296
  end
data/test/entity_test.rb CHANGED
@@ -70,4 +70,25 @@ class EntityTest < Test::Unit::TestCase
70
70
  assert_equal({ '@id' => '#fred' }, person.reference)
71
71
  assert_equal(person.canonical_id, crate.author.canonical_id)
72
72
  end
73
+
74
+ test 'format various IDs' do
75
+ assert_equal "#Hello%20World/Goodbye%20World", ROCrate::ContextualEntity.format_id('#Hello World/Goodbye World')
76
+ assert_equal "#Hello%20World/Goodbye%20World", ROCrate::ContextualEntity.format_id('Hello World/Goodbye World')
77
+ assert_equal "#%F0%9F%98%8A", ROCrate::ContextualEntity.format_id("😊")
78
+
79
+ assert_equal "test123/hello.txt", ROCrate::File.format_id('./test123/hello.txt')
80
+ assert_equal "test123/hello.txt", ROCrate::File.format_id('./test123/hello.txt/')
81
+ assert_equal "http://www.data.com/my%20data.txt", ROCrate::File.format_id('http://www.data.com/my%20data.txt')
82
+ assert_equal "http://www.data.com/my%20data.txt/", ROCrate::File.format_id('http://www.data.com/my%20data.txt/'), 'Should not modify absolute URI for DataEntity'
83
+
84
+ assert_equal "my%20directory/", ROCrate::Directory.format_id('my directory')
85
+ assert_equal "my%20directory/", ROCrate::Directory.format_id('my directory/')
86
+ assert_equal 'http://www.data.com/my%20directory', ROCrate::Directory.format_id('http://www.data.com/my%20directory'), 'Should not modify absolute URI for DataEntity'
87
+ assert_equal 'http://www.data.com/my%20directory/', ROCrate::Directory.format_id('http://www.data.com/my%20directory/'), 'Should not modify absolute URI for DataEntity'
88
+
89
+ assert_equal "./", ROCrate::Crate.format_id('./')
90
+ assert_equal "cool%20crate/", ROCrate::Crate.format_id('./cool crate')
91
+ assert_equal "http://www.data.com/my%20crate/", ROCrate::Crate.format_id('http://www.data.com/my%20crate'), 'Crate ID should end with /'
92
+ assert_equal "http://www.data.com/my%20crate/", ROCrate::Crate.format_id('http://www.data.com/my%20crate/')
93
+ end
73
94
  end
@@ -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,56 @@ 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
198
+
199
+ test 'reading a zip from various object types' do
200
+ string_io = StringIO.new
201
+ string_io.write(::File.read(fixture_file('sparse_directory_crate.zip').path))
202
+ string_io.rewind
203
+ assert string_io.is_a?(StringIO)
204
+ assert_equal 11, ROCrate::Reader.read_zip(string_io).entries.count
205
+
206
+ path = Pathname.new(fixture_file('sparse_directory_crate.zip').path)
207
+ assert path.is_a?(Pathname)
208
+ assert_equal 11, ROCrate::Reader.read_zip(path).entries.count
209
+
210
+ file = ::File.open(fixture_file('sparse_directory_crate.zip').path)
211
+ assert file.is_a?(::File)
212
+ assert_equal 11, ROCrate::Reader.read_zip(file).entries.count
213
+
214
+ string = fixture_file('sparse_directory_crate.zip').path
215
+ assert string.is_a?(String)
216
+ assert_equal 11, ROCrate::Reader.read_zip(string).entries.count
217
+ end
164
218
  end
data/test/writer_test.rb CHANGED
@@ -112,4 +112,24 @@ class WriterTest < Test::Unit::TestCase
112
112
  assert_equal 2529, ::File.size(::File.join(dir, 'data', 'binary.jpg'))
113
113
  end
114
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
115
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.4
4
+ version: 0.4.9
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-02-24 00:00:00.000000000 Z
11
+ date: 2021-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -167,6 +167,16 @@ files:
167
167
  - test/fixtures/info.txt
168
168
  - test/fixtures/spaces/file with spaces.txt
169
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
170
180
  - test/fixtures/workflow-0.2.0.zip
171
181
  - test/fixtures/workflow-0.2.0/Dockerfile
172
182
  - test/fixtures/workflow-0.2.0/README.md