stash-wrapper 0.1.1
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 +7 -0
- data/.gitignore +46 -0
- data/.rubocop.yml +24 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -0
- data/.yardopts +1 -0
- data/CHANGES.md +7 -0
- data/Gemfile +3 -0
- data/LICENSE.md +22 -0
- data/README.md +230 -0
- data/Rakefile +47 -0
- data/data/kernel3_to_oaidc.xsl +220 -0
- data/example.rb +52 -0
- data/lib/stash/wrapper.rb +6 -0
- data/lib/stash/wrapper/descriptive_node.rb +38 -0
- data/lib/stash/wrapper/embargo.rb +30 -0
- data/lib/stash/wrapper/embargo_type.rb +10 -0
- data/lib/stash/wrapper/identifier.rb +21 -0
- data/lib/stash/wrapper/identifier_type.rb +13 -0
- data/lib/stash/wrapper/inventory.rb +22 -0
- data/lib/stash/wrapper/license.rb +30 -0
- data/lib/stash/wrapper/module_info.rb +13 -0
- data/lib/stash/wrapper/size.rb +23 -0
- data/lib/stash/wrapper/size_unit.rb +11 -0
- data/lib/stash/wrapper/stash_administrative.rb +33 -0
- data/lib/stash/wrapper/stash_file.rb +57 -0
- data/lib/stash/wrapper/stash_wrapper.rb +130 -0
- data/lib/stash/wrapper/version.rb +24 -0
- data/spec/.rubocop.yml +7 -0
- data/spec/data/metadata.xsd +380 -0
- data/spec/data/wrapper/mrtoai-wrapper.xml +73 -0
- data/spec/data/wrapper/stash_wrapper.xsd +322 -0
- data/spec/data/wrapper/wrapper-1.xml +56 -0
- data/spec/data/wrapper/wrapper-2-payload.xml +88 -0
- data/spec/data/wrapper/wrapper-2.xml +164 -0
- data/spec/rspec_custom_matchers.rb +69 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/unit/stash/wrapper/stash_wrapper_spec.rb +239 -0
- data/stash-wrapper.gemspec +39 -0
- metadata +245 -0
data/example.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'stash/wrapper'
|
4
|
+
|
5
|
+
include Stash::Wrapper
|
6
|
+
|
7
|
+
identifier = Identifier.new(
|
8
|
+
type: IdentifierType::DOI,
|
9
|
+
value: '10.14749/1407399498'
|
10
|
+
)
|
11
|
+
|
12
|
+
version = Version.new(
|
13
|
+
number: 1,
|
14
|
+
date: Date.new(2013, 8, 18),
|
15
|
+
note: 'Sample wrapped Datacite document'
|
16
|
+
)
|
17
|
+
|
18
|
+
license = License::CC_BY
|
19
|
+
|
20
|
+
embargo = Embargo.new(
|
21
|
+
type: EmbargoType::DOWNLOAD,
|
22
|
+
period: '1 year',
|
23
|
+
start_date: Date.new(2013, 8, 18),
|
24
|
+
end_date: Date.new(2014, 8, 18)
|
25
|
+
)
|
26
|
+
|
27
|
+
inventory = Inventory.new(
|
28
|
+
files: [
|
29
|
+
StashFile.new(
|
30
|
+
pathname: 'HSRC_MasterSampleII.dat', size_bytes: 12_345, mime_type: 'text/plain'
|
31
|
+
),
|
32
|
+
StashFile.new(
|
33
|
+
pathname: 'HSRC_MasterSampleII.csv', size_bytes: 67_890, mime_type: 'text/csv'
|
34
|
+
),
|
35
|
+
StashFile.new(
|
36
|
+
pathname: 'HSRC_MasterSampleII.sas7bdat', size_bytes: 123_456, mime_type: 'application/x-sas-data'
|
37
|
+
)
|
38
|
+
])
|
39
|
+
|
40
|
+
datacite_file = 'spec/data/wrapper/wrapper-2-payload.xml'
|
41
|
+
datacite_root = REXML::Document.new(File.read(datacite_file)).root
|
42
|
+
|
43
|
+
wrapper = StashWrapper.new(
|
44
|
+
identifier: identifier,
|
45
|
+
version: version,
|
46
|
+
license: license,
|
47
|
+
embargo: embargo,
|
48
|
+
inventory: inventory,
|
49
|
+
descriptive_elements: [datacite_root]
|
50
|
+
)
|
51
|
+
|
52
|
+
puts wrapper.write_xml
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'xml/mapping'
|
2
|
+
|
3
|
+
module Stash
|
4
|
+
module Wrapper
|
5
|
+
|
6
|
+
# Node class for `<st:stash_descriptive>` elements.
|
7
|
+
class DescriptiveNode < XML::Mapping::SingleAttributeNode
|
8
|
+
|
9
|
+
# See `XML::Mapping::SingleAttributeNode#initialize`
|
10
|
+
def initialize(*args)
|
11
|
+
path, *args = super(*args)
|
12
|
+
@path = ::XML::XXPath.new(path)
|
13
|
+
args
|
14
|
+
end
|
15
|
+
|
16
|
+
# Extracts the children of this element as an array.
|
17
|
+
# @param xml [REXML::Element] this `<st:stash_descriptive>` element
|
18
|
+
# @return [Array<REXML::Element>] the child elements
|
19
|
+
def extract_attr_value(xml)
|
20
|
+
default_when_xpath_err { @path.first(xml).elements.to_a }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Sets the array of elements representetd by this node
|
24
|
+
# as the children of the corresponding `<st:stash_descriptive>`
|
25
|
+
# element.
|
26
|
+
# @param xml [REXML::Element] this element
|
27
|
+
# @param value [Array<REXML::Element>] the child elements
|
28
|
+
def set_attr_value(xml, value)
|
29
|
+
parent = @path.first(xml, ensure_created: true)
|
30
|
+
value.each do |child|
|
31
|
+
parent.elements.add(child)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
XML::Mapping.add_node_class DescriptiveNode
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'xml/mapping_extensions'
|
2
|
+
require_relative 'embargo_type'
|
3
|
+
|
4
|
+
module Stash
|
5
|
+
module Wrapper
|
6
|
+
|
7
|
+
# Mapping class for `<st:embargo>`
|
8
|
+
class Embargo
|
9
|
+
include ::XML::Mapping
|
10
|
+
|
11
|
+
typesafe_enum_node :type, 'type', class: EmbargoType
|
12
|
+
text_node :period, 'period'
|
13
|
+
date_node :start_date, 'start', zulu: true
|
14
|
+
date_node :end_date, 'end', zulu: true
|
15
|
+
|
16
|
+
# Creates a new {Embargo} object
|
17
|
+
# @param type [EmbargoType] The embargo type
|
18
|
+
# @param period [String] The embargo period
|
19
|
+
# @param start_date [Date] The embargo start date
|
20
|
+
# @param end_date [Date] The embargo end date
|
21
|
+
def initialize(type:, period:, start_date:, end_date:)
|
22
|
+
self.type = type
|
23
|
+
self.period = period
|
24
|
+
self.start_date = start_date
|
25
|
+
self.end_date = end_date
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'xml/mapping_extensions'
|
2
|
+
require_relative 'identifier_type'
|
3
|
+
|
4
|
+
module Stash
|
5
|
+
module Wrapper
|
6
|
+
|
7
|
+
# Mapping class for `<st:identifier>`
|
8
|
+
class Identifier
|
9
|
+
include ::XML::Mapping
|
10
|
+
typesafe_enum_node :type, '@type', class: IdentifierType, default_value: nil
|
11
|
+
text_node :value, '.', default_value: nil
|
12
|
+
|
13
|
+
# Creates a new {Identifier}
|
14
|
+
def initialize(type:, value:)
|
15
|
+
self.type = type
|
16
|
+
self.value = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'xml/mapping'
|
2
|
+
require_relative 'stash_file'
|
3
|
+
|
4
|
+
module Stash
|
5
|
+
module Wrapper
|
6
|
+
|
7
|
+
# Mapping class for `<st:inventory>`
|
8
|
+
class Inventory
|
9
|
+
include ::XML::Mapping
|
10
|
+
|
11
|
+
array_node :files, 'file', class: StashFile, default_value: []
|
12
|
+
numeric_node :num_files, '@num_files', default_value: 0
|
13
|
+
|
14
|
+
# creates a new {Inventory} object
|
15
|
+
# @param files [List<StashFile>] The inventory of files
|
16
|
+
def initialize(files:)
|
17
|
+
self.files = files
|
18
|
+
self.num_files = files.size
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'xml/mapping'
|
2
|
+
require 'xml/mapping_extensions'
|
3
|
+
|
4
|
+
module Stash
|
5
|
+
module Wrapper
|
6
|
+
# Mapping class for `<st:license>`
|
7
|
+
class License
|
8
|
+
include ::XML::Mapping
|
9
|
+
|
10
|
+
text_node :name, 'name'
|
11
|
+
uri_node :uri, 'uri'
|
12
|
+
|
13
|
+
# Creates a new {License} object
|
14
|
+
# @param name [String] The license name
|
15
|
+
# @param uri [URI] The license URI
|
16
|
+
def initialize(name:, uri:)
|
17
|
+
self.name = name
|
18
|
+
self.uri = uri
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class License
|
23
|
+
# Convenience instance for the [CC-BY](https://creativecommons.org/licenses/by/4.0/legalcode) license
|
24
|
+
CC_BY = License.new(
|
25
|
+
name: 'Creative Commons Attribution 4.0 International (CC-BY)',
|
26
|
+
uri: URI('https://creativecommons.org/licenses/by/4.0/legalcode')
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Stash
|
2
|
+
# Code relating to the {https://dash.cdlib.org/stash_wrapper/ Stash wrapper format}
|
3
|
+
module Wrapper
|
4
|
+
# The name of this gem
|
5
|
+
NAME = 'stash-wrapper'
|
6
|
+
|
7
|
+
# The version of this gem
|
8
|
+
VERSION = '0.1.1'
|
9
|
+
|
10
|
+
# The copyright notice for this gem
|
11
|
+
COPYRIGHT = 'Copyright (c) 2016 The Regents of the University of California'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'xml/mapping'
|
2
|
+
require_relative 'size_unit'
|
3
|
+
|
4
|
+
module Stash
|
5
|
+
module Wrapper
|
6
|
+
|
7
|
+
# Mapping for `<st:size>`
|
8
|
+
class Size
|
9
|
+
include ::XML::Mapping
|
10
|
+
|
11
|
+
numeric_node :size, '.'
|
12
|
+
typesafe_enum_node :unit, '@unit', class: SizeUnit, default: SizeUnit::BYTE
|
13
|
+
|
14
|
+
# Creates a new {Size}
|
15
|
+
# @param bytes [Integer] the size in bytes
|
16
|
+
def initialize(bytes:)
|
17
|
+
self.size = bytes
|
18
|
+
self.unit = SizeUnit::BYTE
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'xml/mapping'
|
2
|
+
require_relative 'version'
|
3
|
+
require_relative 'license'
|
4
|
+
require_relative 'embargo'
|
5
|
+
require_relative 'inventory'
|
6
|
+
|
7
|
+
module Stash
|
8
|
+
module Wrapper
|
9
|
+
# Mapping for `<st:stash_administrative>`
|
10
|
+
class StashAdministrative
|
11
|
+
include ::XML::Mapping
|
12
|
+
object_node :version, 'version', class: Version
|
13
|
+
object_node :license, 'license', class: License
|
14
|
+
object_node :embargo, 'embargo', class: Embargo
|
15
|
+
object_node :inventory, 'inventory', class: Inventory
|
16
|
+
|
17
|
+
# Creates a new {StashAdministrative}
|
18
|
+
#
|
19
|
+
# @param version [Version] the version
|
20
|
+
# @param license [License] the license
|
21
|
+
# @param embargo [Embargo] the embargo. Note that per the schema,
|
22
|
+
# an `<st:embargo>` element is required even if there is no embargo
|
23
|
+
# on the dataset (in which case it should use {EmbargoType::NONE}).
|
24
|
+
# @param inventory [Inventory, nil] the (optional) file inventory
|
25
|
+
def initialize(version:, license:, embargo:, inventory: nil)
|
26
|
+
self.version = version
|
27
|
+
self.license = license
|
28
|
+
self.embargo = embargo
|
29
|
+
self.inventory = inventory
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'xml/mapping'
|
2
|
+
require 'xml/mapping_extensions'
|
3
|
+
|
4
|
+
module Stash
|
5
|
+
module Wrapper
|
6
|
+
# Mapping for `<st:file>`.
|
7
|
+
class StashFile
|
8
|
+
include ::XML::Mapping
|
9
|
+
|
10
|
+
root_element_name 'file'
|
11
|
+
|
12
|
+
text_node :pathname, 'pathname'
|
13
|
+
object_node :size, 'size'
|
14
|
+
mime_type_node :mime_type, 'mime_type'
|
15
|
+
|
16
|
+
# Creates a new {StashFile} object
|
17
|
+
#
|
18
|
+
# @param pathname [String] the pathname
|
19
|
+
# @param size_bytes [Integer] the size in bytes
|
20
|
+
# @param mime_type [MIME::Type, String] the MIME type, as either a
|
21
|
+
# `MIME::Type` object or a string
|
22
|
+
def initialize(pathname:, size_bytes:, mime_type:)
|
23
|
+
self.pathname = pathname
|
24
|
+
self.size_bytes = size_bytes
|
25
|
+
self.mime_type = mime_type
|
26
|
+
end
|
27
|
+
|
28
|
+
# Sets the MIME type, converting from a string if necessary
|
29
|
+
# @param value [MIME::Type, String] the MIME type, as either a
|
30
|
+
# `MIME::Type` object or a string
|
31
|
+
def mime_type=(value)
|
32
|
+
@mime_type = to_mime_type(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Sets the size in bytes, expanding it to a {Size} object
|
36
|
+
# @param bytes [Integer] the size in bytes
|
37
|
+
def size_bytes=(bytes)
|
38
|
+
self.size = Size.new(bytes: bytes)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Gets the size in bytes, extracting it from the {Size}
|
42
|
+
# @return [Integer] the size in bytes
|
43
|
+
def size_bytes
|
44
|
+
size.size
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def to_mime_type(value)
|
50
|
+
return nil unless value
|
51
|
+
return value if value.is_a?(MIME::Type)
|
52
|
+
mt_string = value.to_s
|
53
|
+
(mt = MIME::Types[mt_string].first) ? mt : MIME::Type.new(mt_string)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'xml/mapping'
|
2
|
+
|
3
|
+
require_relative 'descriptive_node'
|
4
|
+
require_relative 'identifier'
|
5
|
+
require_relative 'stash_administrative'
|
6
|
+
|
7
|
+
module Stash
|
8
|
+
module Wrapper
|
9
|
+
# Mapping for the root `<st:stash_wrapper>` element
|
10
|
+
class StashWrapper
|
11
|
+
include ::XML::Mapping
|
12
|
+
|
13
|
+
# The `stash_wrapper` namespace
|
14
|
+
NAMESPACE = 'http://dash.cdlib.org/stash_wrapper/'
|
15
|
+
|
16
|
+
# The `st` prefix for the `stash_wrapper` namespace
|
17
|
+
NAMESPACE_PREFIX = 'st'
|
18
|
+
|
19
|
+
root_element_name 'stash_wrapper'
|
20
|
+
|
21
|
+
object_node :identifier, 'identifier', class: Identifier
|
22
|
+
object_node :stash_administrative, 'stash_administrative', class: StashAdministrative
|
23
|
+
descriptive_node :stash_descriptive, 'stash_descriptive'
|
24
|
+
|
25
|
+
# Creates a new {StashWrapper}. As a convenience, constructs an
|
26
|
+
# internal {StashAdministrative} directly from the supplied
|
27
|
+
# administrative elements.
|
28
|
+
#
|
29
|
+
# @param identifier [Identifier] the identifier
|
30
|
+
# @param version [Version] the version
|
31
|
+
# @param license [License] the license
|
32
|
+
# @param embargo [Embargo] the embargo. Note that per the schema,
|
33
|
+
# an `<st:embargo>` element is required even if there is no embargo
|
34
|
+
# on the dataset (in which case it should use {EmbargoType::NONE}).
|
35
|
+
# @param inventory [Inventory, nil] the (optional) file inventory
|
36
|
+
# @param descriptive_elements [Array<REXML::Element>] the encapsulated
|
37
|
+
# XML metadata
|
38
|
+
def initialize(identifier:, version:, license:, embargo:, inventory: nil, descriptive_elements:) # rubocop:disable Metrics/ParameterLists
|
39
|
+
self.identifier = identifier
|
40
|
+
self.stash_administrative = StashAdministrative.new(
|
41
|
+
version: version,
|
42
|
+
license: license,
|
43
|
+
embargo: embargo,
|
44
|
+
inventory: inventory
|
45
|
+
)
|
46
|
+
self.stash_descriptive = descriptive_elements
|
47
|
+
end
|
48
|
+
|
49
|
+
def id_value
|
50
|
+
identifier.value
|
51
|
+
end
|
52
|
+
|
53
|
+
def version
|
54
|
+
stash_administrative.version
|
55
|
+
end
|
56
|
+
|
57
|
+
def version_number
|
58
|
+
version.version_number
|
59
|
+
end
|
60
|
+
|
61
|
+
def version_date
|
62
|
+
version.date
|
63
|
+
end
|
64
|
+
|
65
|
+
def license
|
66
|
+
stash_administrative.license
|
67
|
+
end
|
68
|
+
|
69
|
+
def license_name
|
70
|
+
license.name
|
71
|
+
end
|
72
|
+
|
73
|
+
def license_uri
|
74
|
+
license.uri
|
75
|
+
end
|
76
|
+
|
77
|
+
def embargo
|
78
|
+
stash_administrative.embargo
|
79
|
+
end
|
80
|
+
|
81
|
+
def embargo_type
|
82
|
+
embargo.type
|
83
|
+
end
|
84
|
+
|
85
|
+
def embargo_end_date
|
86
|
+
embargo.end_date
|
87
|
+
end
|
88
|
+
|
89
|
+
def inventory
|
90
|
+
stash_administrative.inventory
|
91
|
+
end
|
92
|
+
|
93
|
+
# Overrides `XML::Mapping#pre_save` to set the XML namespace and schema location.
|
94
|
+
def pre_save(options = { mapping: :_default })
|
95
|
+
xml = super(options)
|
96
|
+
xml.add_namespace(NAMESPACE)
|
97
|
+
xml.add_namespace('xsi', 'http://www.w3.org/2001/XMLSchema-instance')
|
98
|
+
xml.add_attribute('xsi:schemaLocation', 'http://dash.cdlib.org/stash_wrapper/ http://dash.cdlib.org/stash_wrapper/stash_wrapper.xsd')
|
99
|
+
xml
|
100
|
+
end
|
101
|
+
|
102
|
+
# Overrides `XML::Mapping#save_to_xml` to set the XML namespace prefix on all
|
103
|
+
# `stash_wrapper` elements. (The elements in `descriptive_elements` should retain
|
104
|
+
# their existing namespaces and prefixes, if any.)
|
105
|
+
def save_to_xml(options = { mapping: :_default })
|
106
|
+
elem = super(options)
|
107
|
+
set_prefix(prefix: NAMESPACE_PREFIX, elem: elem)
|
108
|
+
elem.add_namespace(nil) # clear the no-prefix namespace
|
109
|
+
elem.add_namespace(NAMESPACE_PREFIX, NAMESPACE)
|
110
|
+
elem
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.parse_xml(xml_str)
|
114
|
+
xml = REXML::Document.new(xml_str).root
|
115
|
+
load_from_xml(xml)
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def set_prefix(prefix:, elem:)
|
121
|
+
return unless elem.namespace == NAMESPACE
|
122
|
+
# name= with a prefixed name sets namespace by side effect and is the only way to actually output the prefix
|
123
|
+
elem.name = "#{prefix}:#{elem.name}"
|
124
|
+
elem.each_element { |e| set_prefix(prefix: prefix, elem: e) }
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|