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.
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,6 @@
1
+ module Stash
2
+ # Code relating to the {https://dash.cdlib.org/stash_wrapper/ Stash wrapper format}
3
+ module Wrapper
4
+ Dir.glob(File.expand_path('../wrapper/*.rb', __FILE__), &method(:require))
5
+ end
6
+ end
@@ -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,10 @@
1
+ require 'typesafe_enum'
2
+
3
+ module Stash
4
+ module Wrapper
5
+ # Controlled vocabulary for {Embargo#type}
6
+ class EmbargoType < TypesafeEnum::Base
7
+ [:NONE, :DOWNLOAD, :DESCRIPTION].each { |t| new t }
8
+ end
9
+ end
10
+ 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,13 @@
1
+ require 'typesafe_enum'
2
+
3
+ module Stash
4
+ module Wrapper
5
+ # Controlled vocabulary for {Identifier#type}
6
+ class IdentifierType < TypesafeEnum::Base
7
+ new :ARK, 'ARK'
8
+ new :DOI, 'DOI'
9
+ new :HANDLE, 'Handle'
10
+ new :URL, 'URL'
11
+ end
12
+ end
13
+ 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,11 @@
1
+ require 'typesafe_enum'
2
+
3
+ module Stash
4
+ module Wrapper
5
+ # Controlled vocabulary for {Size#unit}, with the single
6
+ # value {#BYTE}.
7
+ class SizeUnit < TypesafeEnum::Base
8
+ new :BYTE, 'B'
9
+ end
10
+ end
11
+ 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