resync 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +42 -0
  3. data/.rubocop.yml +23 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +2 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.md +22 -0
  8. data/README.md +92 -0
  9. data/Rakefile +56 -0
  10. data/example.rb +100 -0
  11. data/lib/resync/capability_list.rb +85 -0
  12. data/lib/resync/change_dump.rb +15 -0
  13. data/lib/resync/change_dump_manifest.rb +15 -0
  14. data/lib/resync/change_list.rb +15 -0
  15. data/lib/resync/change_list_index.rb +26 -0
  16. data/lib/resync/link.rb +87 -0
  17. data/lib/resync/metadata.rb +112 -0
  18. data/lib/resync/resource.rb +72 -0
  19. data/lib/resync/resource_dump.rb +15 -0
  20. data/lib/resync/resource_dump_manifest.rb +15 -0
  21. data/lib/resync/resource_list.rb +15 -0
  22. data/lib/resync/resource_list_index.rb +15 -0
  23. data/lib/resync/shared/augmented.rb +76 -0
  24. data/lib/resync/shared/base_resource_list.rb +117 -0
  25. data/lib/resync/shared/descriptor.rb +135 -0
  26. data/lib/resync/shared/sitemap_index.rb +32 -0
  27. data/lib/resync/shared/sorted_resource_list.rb +60 -0
  28. data/lib/resync/source_description.rb +14 -0
  29. data/lib/resync/types/change.rb +14 -0
  30. data/lib/resync/types/change_frequency.rb +18 -0
  31. data/lib/resync/types.rb +6 -0
  32. data/lib/resync/version.rb +4 -0
  33. data/lib/resync/xml.rb +216 -0
  34. data/lib/resync/xml_parser.rb +65 -0
  35. data/lib/resync.rb +4 -0
  36. data/resync.gemspec +36 -0
  37. data/spec/acceptance/xml_parser_spec.rb +1049 -0
  38. data/spec/data/examples/README.md +1 -0
  39. data/spec/data/examples/example-1.xml +12 -0
  40. data/spec/data/examples/example-12.xml +25 -0
  41. data/spec/data/examples/example-13.xml +25 -0
  42. data/spec/data/examples/example-14.xml +23 -0
  43. data/spec/data/examples/example-15.xml +21 -0
  44. data/spec/data/examples/example-16.xml +24 -0
  45. data/spec/data/examples/example-17.xml +39 -0
  46. data/spec/data/examples/example-18.xml +25 -0
  47. data/spec/data/examples/example-19.xml +28 -0
  48. data/spec/data/examples/example-2.xml +18 -0
  49. data/spec/data/examples/example-20.xml +22 -0
  50. data/spec/data/examples/example-21.xml +31 -0
  51. data/spec/data/examples/example-22.xml +41 -0
  52. data/spec/data/examples/example-23.xml +41 -0
  53. data/spec/data/examples/example-24.xml +28 -0
  54. data/spec/data/examples/example-25.xml +21 -0
  55. data/spec/data/examples/example-26.xml +18 -0
  56. data/spec/data/examples/example-27.xml +36 -0
  57. data/spec/data/examples/example-28.xml +34 -0
  58. data/spec/data/examples/example-29.xml +27 -0
  59. data/spec/data/examples/example-3.xml +17 -0
  60. data/spec/data/examples/example-30.xml +18 -0
  61. data/spec/data/examples/example-31.xml +16 -0
  62. data/spec/data/examples/example-32.xml +22 -0
  63. data/spec/data/examples/example-33.xml +22 -0
  64. data/spec/data/examples/example-4.xml +10 -0
  65. data/spec/data/examples/example-5.xml +18 -0
  66. data/spec/data/examples/example-6.xml +21 -0
  67. data/spec/data/examples/example-7.xml +13 -0
  68. data/spec/data/examples/example-8.xml +12 -0
  69. data/spec/data/resourcesync.xsd +148 -0
  70. data/spec/data/siteindex.xsd +75 -0
  71. data/spec/data/sitemap.xsd +116 -0
  72. data/spec/rspec_custom_matchers.rb +89 -0
  73. data/spec/spec_helper.rb +31 -0
  74. data/spec/todo.rb +11 -0
  75. data/spec/unit/resync/capability_list_spec.rb +138 -0
  76. data/spec/unit/resync/change_dump_manifest_spec.rb +75 -0
  77. data/spec/unit/resync/change_dump_spec.rb +61 -0
  78. data/spec/unit/resync/change_list_index_spec.rb +49 -0
  79. data/spec/unit/resync/change_list_spec.rb +75 -0
  80. data/spec/unit/resync/link_spec.rb +93 -0
  81. data/spec/unit/resync/metadata_spec.rb +169 -0
  82. data/spec/unit/resync/resource_dump_manifest_spec.rb +59 -0
  83. data/spec/unit/resync/resource_dump_spec.rb +62 -0
  84. data/spec/unit/resync/resource_list_index_spec.rb +53 -0
  85. data/spec/unit/resync/resource_list_spec.rb +60 -0
  86. data/spec/unit/resync/resource_spec.rb +176 -0
  87. data/spec/unit/resync/shared/augmented_examples.rb +58 -0
  88. data/spec/unit/resync/shared/base_resource_list_examples.rb +103 -0
  89. data/spec/unit/resync/shared/descriptor_examples.rb +122 -0
  90. data/spec/unit/resync/shared/descriptor_spec.rb +33 -0
  91. data/spec/unit/resync/shared/sorted_list_examples.rb +134 -0
  92. data/spec/unit/resync/shared/uri_field_examples.rb +36 -0
  93. data/spec/unit/resync/source_description_spec.rb +55 -0
  94. data/spec/unit/resync/xml/timenode_spec.rb +48 -0
  95. data/spec/unit/resync/xml/xml_spec.rb +40 -0
  96. data/spec/unit/resync/xml_parser_spec.rb +82 -0
  97. metadata +340 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d976bb155b49ce7339df955bfa20d30392d22b5e
4
+ data.tar.gz: 3c3fdaebce38202912ce7d56805a429578f77b1d
5
+ SHA512:
6
+ metadata.gz: d567ccc2977f4c46ea357765bb46b72d40a8ee20673c2fdd9f76e66f3cc1e5ca18c16f7fe9cbeb51788121b1f33b70bec6e0ba7b71fcf09eae431ee4a3875afa
7
+ data.tar.gz: f97193935fc1dc19de8e715a9502e6afc2cd150528eb71a471acb41be24bd7a95ab8e139f9245026d1ee45607e79aeb05ce231a6902599261994659ca14ed3ae
data/.gitignore ADDED
@@ -0,0 +1,42 @@
1
+ # Ruby defaults
2
+
3
+ /.bundle/
4
+ /.yardoc
5
+ /Gemfile.lock
6
+ /_yardoc/
7
+ /coverage/
8
+ /doc/
9
+ /pkg/
10
+ /spec/reports/
11
+ /tmp/
12
+ *.bundle
13
+ *.so
14
+ *.o
15
+ *.a
16
+ mkmf.log
17
+
18
+ # Rails engine
19
+
20
+ .bundle/
21
+ log/*.log
22
+ spec/dummy/db/*.sqlite3
23
+ spec/dummy/db/*.sqlite3-journal
24
+ spec/dummy/log/*.log
25
+ spec/dummy/tmp/
26
+ spec/dummy/.sass-cache
27
+
28
+ # IntellJ
29
+
30
+ *.iml
31
+ *.ipr
32
+ *.iws
33
+ .rakeTasks
34
+ .idea
35
+
36
+ # Emacs
37
+
38
+ *~
39
+
40
+ # Mac OS
41
+
42
+ .DS_Store
data/.rubocop.yml ADDED
@@ -0,0 +1,23 @@
1
+ # Disable line-length check; it's too easy for the cure to be worse than the disease
2
+ Metrics/LineLength:
3
+ Enabled: False
4
+
5
+ # Disable problematic module documentation check (see https://github.com/bbatsov/rubocop/issues/947)
6
+ Style/Documentation:
7
+ Enabled: false
8
+
9
+ # Allow one line around class body (Style/EmptyLines will still disallow two or more)
10
+ Style/EmptyLinesAroundClassBody:
11
+ Enabled: false
12
+
13
+ # Allow one line around module body (Style/EmptyLines will still disallow two or more)
14
+ Style/EmptyLinesAroundModuleBody:
15
+ Enabled: false
16
+
17
+ # Allow one line around block body (Style/EmptyLines will still disallow two or more)
18
+ Style/EmptyLinesAroundBlockBody:
19
+ Enabled: false
20
+
21
+ # Allow %r notation for regexes with a single / character
22
+ Style/RegexpLiteral:
23
+ MaxSlashes: 0
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.2
data/.travis.yml ADDED
@@ -0,0 +1,2 @@
1
+ language: ruby
2
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 The Regents of the University of California
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # resync
2
+
3
+ A Ruby gem for working with the [ResourceSync](http://www.openarchives.org/rs/1.0/resourcesync) web synchronization framework.
4
+
5
+ It consists of the following:
6
+
7
+ - Classes corresponding to the major document types defined in the ResourceSync specification, such as [Resource Lists](http://www.openarchives.org/rs/1.0/resourcesync#ResourceList), [Change Lists](http://www.openarchives.org/rs/1.0/resourcesync#ChangeList), [Source Descriptions](http://www.openarchives.org/rs/1.0/resourcesync#SourceDesc) and so on. Each of these classes has a `load_from_xml` method that can parse the corresponding XML document (as an `REXML::Element`), and a `save_to_xml` method that can serialize an instance of that class to XML (as an `REXML::Element`).
8
+ - Classes for the [major sub-structures](http://www.openarchives.org/rs/1.0/resourcesync#DocumentFormats) of those documents, such as the `<url>` and `<sitemap>` tags (subsumed under the [Resource](lib/resync/resource.rb) class) defined by the Sitemap specification, as well as the ResourceSync-specific `<rs:ln>` and `<rs:md>` tags (the [Link](lib/resync/link.rb) and [Metadata](lib/resync/metadata.rb) classes, respectively).
9
+ - An [XMLParser](lib/resync/xml_parser.rb) class that can take a ResourceSync-augmented Sitemap document (in the form of an `REXML::Element`, an `REXML::Document`, a string, an `IO`, or something sufficiently `IO`-like that `REXML::Document` can parse it) and produce an instance of the appropriate class based on the `capability` attribute in the root element's metadata.
10
+
11
+ ## Status
12
+
13
+ This is a work in progress. We welcome bug reports and feature requests (particularly on the document creation side, which our use cases haven't really explored).
14
+
15
+ ## Usage
16
+
17
+ ### Parsing a ResourceSync document
18
+
19
+ ```ruby
20
+ require 'resync'
21
+
22
+ data = File.read('my-capability-list.xml')
23
+ capability_list = Resync::XMLParser.parse(data)
24
+ ```
25
+
26
+ ### Writing a ResourceSync document
27
+
28
+ ```ruby
29
+ require 'resync'
30
+
31
+ change_list = Resync::ChangeList.new(
32
+ links: [ Resync::Link.new(rel='up', href='http://example.com/my-dataset/my-capability-list.xml') ],
33
+ metadata: Resync::Metadata.new(
34
+ capability = 'changelist',
35
+ from_time = Time.utc(2013, 1, 3)
36
+ )
37
+ resources: [
38
+ # ... generate list of changes here ...
39
+ ]
40
+ )
41
+ xml = change_list.save_to_xml
42
+ formatter = REXML::Formatters::Pretty.new
43
+ formatter.write(xml, $stdout)
44
+ ```
45
+
46
+ ## See also
47
+
48
+ [resync-client](https://github.com/dmolesUC3/resync-client), a Ruby client library for ResourceSync.
49
+
50
+ ## Limitations
51
+
52
+ ### Structural inconvenience and unnecessary repetition
53
+
54
+ There are certain well-specified relationships between elements: most document types should always have a link with an `up` relationship, many resources should have metadata with a defined `capability` attribute, and so on. In some cases there are convenience getters for these attributes on the 'parent' object (e.g. you can ask for the `capability` directly without violating the law of Demeter), but there generally aren't corresponding convenience setters, or convenience initializer parameters.
55
+
56
+ Document types (`ChangeList`, `ResourceList`, etc.) will create a `Metadata` with the appropriate capability for themselves if none is specified, but if they're initialized with one that doesn't declare a capability, they'll raise an exception rather than fill it in (just as they'll raise an exception if the wrong capability is specified).
57
+
58
+ ### Logical relationships between elements
59
+
60
+ A `ChangeList` should contain only resources with `Metadata` declaring a `change` type. The resources in a `ResourceDumpManifest` should each declare a `path` indicating their locations in the ZIP file. `resync` doesn't currently do anything to enforce, validate, or assist in compliance with these and similar restrictions.
61
+
62
+ (An exception: document types will complain if initialized with `Metadata` having the wrong capability.)
63
+
64
+ ### Time attribute requirements
65
+
66
+ The required/forbidden time attributes defined in Appendix A,
67
+ "[Time Attribute Requirements](http://www.openarchives.org/rs/1.0/resourcesync#TimeAttributeReqs)",
68
+ of the ResourceSync specification are not enforced; it's possible to
69
+ create, e.g., a `ResourceList` with a `from_time` on its metadata, or a `ChangeList` with members whose metadata does not declare a `modified_time`, even though both scenarios are forbidden by the specification.
70
+
71
+ ### Value restrictions from XML schemata
72
+
73
+ The [ResourceSync schema](http://www.openarchives.org/rs/0.9.1/resourcesync.xsd) defines restrictions on the values of several attributes:
74
+
75
+ - Path values must start with a slash, must not end with a slash
76
+ - Priorities must be positive and < 1,000,000
77
+ - Link relation types must conform with [RFC 5988](http://tools.ietf.org/html/rfc5988)
78
+
79
+ The [Sitemap](http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd) and [Sitemap index](http://www.sitemaps.org/schemas/sitemap/0.9/siteindex.xsd) schemas also define some restrictions:
80
+
81
+ - URIs have a minimum length of 12 and a max of 2048 characters.
82
+ - Priorities must be in the range 0.0-1.0 (inclusive)
83
+
84
+ None of these restrictions are currently enforced by `resync`.
85
+
86
+ ### Element order
87
+
88
+ When reading a ResourceSync document from XML and writing it back out, `<rs:ln>` elements will always appear before `<rs:md>` elements, regardless of their order in the original source.
89
+
90
+ ### Namespace weirdness
91
+
92
+ The [XML::Mapping](https://github.com/multi-io/xml-mapping) library `resync` uses doesn't support namespaces, so namespace handling in `resync` is a bit hacky. In particular, you may see strange behavior when using `<rs:ln>`, `<rs:md>`, `<url>`, or `<sitemap>` tags outside the context of a `<urlset>`/`<sitemapindex>`.
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ # ------------------------------------------------------------
2
+ # RSpec
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+
7
+ namespace :spec do
8
+
9
+ desc 'Run all unit tests'
10
+ RSpec::Core::RakeTask.new(:unit) do |task|
11
+ task.rspec_opts = %w(--color --format documentation --order default)
12
+ task.pattern = 'unit/**/*_spec.rb'
13
+ end
14
+
15
+ desc 'Run all acceptance tests'
16
+ RSpec::Core::RakeTask.new(:acceptance) do |task|
17
+ ENV['COVERAGE'] = nil
18
+ task.rspec_opts = %w(--color --format documentation --order default)
19
+ task.pattern = 'acceptance/**/*_spec.rb'
20
+ end
21
+
22
+ task all: [:unit, :acceptance]
23
+ end
24
+
25
+ desc 'Run all tests'
26
+ task spec: 'spec:all'
27
+
28
+ # ------------------------------------------------------------
29
+ # Coverage
30
+
31
+ desc 'Run all unit tests with coverage'
32
+ task :coverage do
33
+ ENV['COVERAGE'] = 'true'
34
+ Rake::Task['spec:unit'].execute
35
+ end
36
+
37
+ # ------------------------------------------------------------
38
+ # RuboCop
39
+
40
+ require 'rubocop/rake_task'
41
+ RuboCop::RakeTask.new
42
+
43
+ # ------------------------------------------------------------
44
+ # TODOs
45
+
46
+ desc 'List TODOs (from spec/todo.rb)'
47
+ RSpec::Core::RakeTask.new(:todo) do |task|
48
+ task.rspec_opts = %w(--color --format documentation --order default)
49
+ task.pattern = 'todo.rb'
50
+ end
51
+
52
+ # ------------------------------------------------------------
53
+ # Defaults
54
+
55
+ desc 'Run unit tests, check test coverage, run acceptance tests, check code style'
56
+ task default: [:coverage, 'spec:acceptance', :rubocop]
data/example.rb ADDED
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Note: This assumes we're running from the root of the resync project
4
+ $LOAD_PATH << File.dirname(__FILE__)
5
+ require 'lib/resync'
6
+
7
+ # ------------------------------------------------------------
8
+ # Reading a capability list
9
+
10
+ puts "\n------------------------------------------------------------"
11
+ puts "A capability list:\n"
12
+
13
+ capabilitylist_xml = '<?xml version="1.0" encoding="UTF-8"?>
14
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
15
+ xmlns:rs="http://www.openarchives.org/rs/terms/">
16
+ <rs:ln rel="describedby"
17
+ href="http://example.com/info_about_set1_of_resources.xml"/>
18
+ <rs:ln rel="up"
19
+ href="http://example.com/resourcesync_description.xml"/>
20
+ <rs:md capability="capabilitylist"/>
21
+ <url>
22
+ <loc>http://example.com/dataset1/resourcelist.xml</loc>
23
+ <rs:md capability="resourcelist"/>
24
+ </url>
25
+ <url>
26
+ <loc>http://example.com/dataset1/resourcedump.xml</loc>
27
+ <rs:md capability="resourcedump"/>
28
+ </url>
29
+ <url>
30
+ <loc>http://example.com/dataset1/changelist.xml</loc>
31
+ <rs:md capability="changelist"/>
32
+ </url>
33
+ <url>
34
+ <loc>http://example.com/dataset1/changedump.xml</loc>
35
+ <rs:md capability="changedump"/>
36
+ </url>
37
+ </urlset>'
38
+
39
+ capability_list = Resync::XMLParser.parse(capabilitylist_xml)
40
+ puts ' Links:'
41
+ capability_list.links.each do |l|
42
+ puts " #{l.rel}: #{l.uri}"
43
+ end
44
+ puts ' Resources:'
45
+ capability_list.resources.each do |r|
46
+ puts " #{r.uri} (#{r.capability})"
47
+ end
48
+
49
+ # ------------------------------------------------------------
50
+ # Creating a changelist
51
+
52
+ puts "\n------------------------------------------------------------"
53
+ puts "A change list:\n\n"
54
+
55
+ change_list = Resync::ChangeList.new(
56
+ links: [
57
+ Resync::Link.new(
58
+ rel: 'up',
59
+ uri: 'http://example.com/dataset1/capabilitylist.xml'
60
+ )
61
+ ],
62
+ metadata: Resync::Metadata.new(
63
+ capability: 'changelist',
64
+ from_time: Time.utc(2013, 1, 3)
65
+ ),
66
+ resources: [
67
+ Resync::Resource.new(
68
+ uri: 'http://example.com/res4',
69
+ modified_time: Time.utc(2013, 1, 3, 17),
70
+ metadata: Resync::Metadata.new(
71
+ change: Resync::Types::Change::UPDATED,
72
+ hashes: { 'sha-256' => 'f4OxZX_x_DFGFDgghgdfb6rtSx-iosjf6735432nklj' },
73
+ length: 56_778,
74
+ mime_type: 'application/json'
75
+ ),
76
+ links: [
77
+ Resync::Link.new(
78
+ rel: 'http://www.openarchives.org/rs/terms/patch',
79
+ uri: 'http://example.com/res4-json-patch',
80
+ modified_time: Time.utc(2013, 1, 3, 17),
81
+ hashes: { 'sha-256' => 'f4OxZX_x_DFGFDgghgdfb6rtSx-iosjf6735432nklj' },
82
+ length: 73,
83
+ mime_type: 'application/json-patch'
84
+ )
85
+ ]
86
+ ),
87
+ Resync::Resource.new(
88
+ uri: 'http://example.com/res5-full.tiff',
89
+ modified_time: Time.utc(2013, 1, 3, 18),
90
+ metadata: Resync::Metadata.new(
91
+ change: Resync::Types::Change::DELETED
92
+ )
93
+ )
94
+ ]
95
+ )
96
+
97
+ xml = change_list.save_to_xml
98
+ formatter = REXML::Formatters::Pretty.new
99
+ formatter.write(xml, $stdout)
100
+ puts
@@ -0,0 +1,85 @@
1
+ require_relative 'shared/base_resource_list'
2
+ require_relative 'xml'
3
+
4
+ module Resync
5
+ # A capability list. See section 9,
6
+ # "{http://www.openarchives.org/rs/1.0/resourcesync#CapabilityList Advertising Capabilities}",
7
+ # in the ResourceSync specification.
8
+ class CapabilityList < BaseResourceList
9
+ include ::XML::Mapping
10
+
11
+ # The capability provided by this type.
12
+ CAPABILITY = 'capabilitylist'
13
+
14
+ # ------------------------------------------------------------
15
+ # Initializer
16
+
17
+ # Creates a new +BaseResourceList+.
18
+ #
19
+ # @param resources [Array<Resource>] The +<url>+ or +<sitemap>+ elements contained in this list.
20
+ # All resources must have a capability, and there can be no more than one resource for each
21
+ # specified capability.
22
+ # @param links [Array<Link>] Related links (+<rs:ln>+).
23
+ # @param metadata [Metadata] Metadata about this list. The +capability+ of the metadata must
24
+ # be +'capabilitylist'+.
25
+ # @raise [ArgumentError] if a provided resource does not have a +capability+ attribute.
26
+ # @raise [ArgumentError] if more than one provided resource has the same +capability+ attribute.
27
+ # @raise [ArgumentError] if the specified metadata does not have the correct +capability+ attribute.
28
+ def initialize(resources: [], links: [], metadata: nil)
29
+ @source_descripton = source_description_from(links)
30
+ super(resources: resources, links: links, metadata: metadata)
31
+ end
32
+
33
+ # ------------------------------------------------------------
34
+ # Custom accessors
35
+
36
+ # Sets the +resources+ list. +nil+ is treated as an empty list.
37
+ # @raise [ArgumentError] if a provided resource does not have a +capability+ attribute.
38
+ # @raise [ArgumentError] if more than one provided resource has the same +capability+ attribute.
39
+ def resources=(value)
40
+ resources = value || []
41
+ @capabilities = to_capability_map(resources)
42
+ @resources = @capabilities.values
43
+ end
44
+
45
+ # Gets the resource for the specified capability.
46
+ #
47
+ # @param capability [String] The capability.
48
+ # @return [Resource] the resource providing the capability, or +nil+ if
49
+ # there is no resource with that capability in this list.
50
+ def resource_for(capability:)
51
+ @capabilities[capability]
52
+ end
53
+
54
+ # Gets the URI of the description of the source whose capabilities are identified by this list.
55
+ #
56
+ # @return [URI] the URI of the description of the source whose capabilities are identified
57
+ # by this list. See section 8,
58
+ # "{http://www.openarchives.org/rs/1.0/resourcesync#SourceDesc Describing the Source}",
59
+ # in the ResourceSync specification.
60
+ def source_description
61
+ @source_description ||= source_description_from(links)
62
+ end
63
+
64
+ # ------------------------------
65
+ # Conversions
66
+
67
+ def to_capability_map(resources)
68
+ capabilities = {}
69
+ (resources || []).each do |resource|
70
+ capability = resource.capability
71
+ fail ArgumentError, "No capability found for resource with URI #{resource.uri}" unless capability
72
+ fail ArgumentError, "Duplicate resource for capability #{capability}" if capabilities.key?(capability)
73
+ capabilities[capability] = resource
74
+ end
75
+ capabilities
76
+ end
77
+
78
+ def source_description_from(links)
79
+ return nil unless links
80
+ desc = links.map { |link| link.uri if link.rel == 'up' }.compact.first
81
+ fail ArgumentError, "No source descrption (<link rel='up'/>) provided" unless desc
82
+ desc
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'shared/sorted_resource_list'
2
+ require_relative 'xml'
3
+
4
+ module Resync
5
+ # A change dump. See section 13.1,
6
+ # "{http://www.openarchives.org/rs/1.0/resourcesync#ChangeDump Change Dump}",
7
+ # in the ResourceSync specification.
8
+ class ChangeDump < SortedResourceList
9
+ include ::XML::Mapping
10
+
11
+ # The capability provided by this type.
12
+ CAPABILITY = 'changedump'
13
+
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'shared/sorted_resource_list'
2
+ require_relative 'xml'
3
+
4
+ module Resync
5
+ # A change dump manifest. See section 13.2,
6
+ # "{http://www.openarchives.org/rs/1.0/resourcesync#ChangeDumpManifest Change Dump Manifest}",
7
+ # in the ResourceSync specification.
8
+ class ChangeDumpManifest < SortedResourceList
9
+ include ::XML::Mapping
10
+
11
+ # The capability provided by this type.
12
+ CAPABILITY = 'changedump-manifest'
13
+
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'shared/sorted_resource_list'
2
+ require_relative 'xml'
3
+
4
+ module Resync
5
+ # A change list. See section 12.1,
6
+ # "{http://www.openarchives.org/rs/1.0/resourcesync#ChangeList Change List}",
7
+ # in the ResourceSync specification.
8
+ class ChangeList < SortedResourceList
9
+ include ::XML::Mapping
10
+
11
+ # The capability provided by this type.
12
+ CAPABILITY = 'changelist'
13
+
14
+ end
15
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'shared/sorted_resource_list'
2
+ require_relative 'shared/sitemap_index'
3
+
4
+ module Resync
5
+ # A change list index. See section 12.2,
6
+ # "{http://www.openarchives.org/rs/1.0/resourcesync#ChangeListIndex Change List Index}",
7
+ # in the ResourceSync specification.
8
+ class ChangeListIndex < SortedResourceList
9
+ include ::XML::Mapping
10
+ include SitemapIndex
11
+
12
+ # The capability provided by this type.
13
+ CAPABILITY = 'changelist'
14
+
15
+ # use_mapping :sitemapindex
16
+ # root_element_name 'sitemapindex'
17
+ # array_node :resources, 'sitemap', class: Resource, default_value: [], sub_mapping: :_default
18
+ #
19
+ # # Ensures that an index is always written as a +<sitemapindex>+.
20
+ # # Overrides +::XML::Mapping.save_to_xml+.
21
+ # def save_to_xml(options = { mapping: :_default })
22
+ # options = options.merge(mapping: :sitemapindex)
23
+ # super(options)
24
+ # end
25
+ end
26
+ end
@@ -0,0 +1,87 @@
1
+ require_relative 'shared/descriptor'
2
+ require_relative 'xml'
3
+
4
+ module Resync
5
+
6
+ # A link to a related resource. See "Linking to Related Resources"
7
+ # under section 5.1,
8
+ # {http://www.openarchives.org/rs/1.0/resourcesync#SourcePers Synchronization Processes: Source Perspective}
9
+ # in the ResourceSync specification.
10
+ #
11
+ # @!attribute [rw] rel
12
+ # @return [String] the relationship of the linked resource to the original
13
+ # resource. See {http://tools.ietf.org/html/rfc5988 RFC 5988}, "Web Linking".
14
+ # for information on link relation types.
15
+ # @!attribute [rw] uri
16
+ # @return [URI] the URI of the linked resource.
17
+ # @!attribute [rw] priority
18
+ # @return [Integer] the priority of the linked resource among links with the
19
+ # same relation type. Values should be in the range 1-999,999 (inclusive).
20
+ # Lower values indicate higher priorities.
21
+ class Link < Descriptor
22
+ include ::XML::Mapping
23
+
24
+ # ------------------------------------------------------------
25
+ # Attributes
26
+
27
+ root_element_name 'ln'
28
+
29
+ text_node :rel, '@rel', default_value: nil
30
+ uri_node :uri, '@href', default_value: nil
31
+ numeric_node :priority, '@pri', default_value: nil
32
+
33
+ # ------------------------------------------------------------
34
+ # Initializer
35
+
36
+ # @param rel [String] the relationship of the linked resource to the
37
+ # original resource. See {http://tools.ietf.org/html/rfc5988 RFC 5988},
38
+ # "Web Linking". for information on link relation types.
39
+ # @param uri [URI] the URI of the linked resource.
40
+ # @param priority [Integer] the priority of the linked resource among links
41
+ # with the same relation type. Values should be in the range
42
+ # 1-999,999 (inclusive). Lower values indicate higher priorities.
43
+ # @param modified_time [Time] The date and time when the referenced resource was last modified.
44
+ # @param length [Integer] The content length of the referenced resource.
45
+ # @param mime_type [MIME::Type] The media type of the referenced resource.
46
+ # @param encoding [String] Any content encoding (if any) applied to the data in the
47
+ # referenced resource (e.g. for compression)
48
+ # @param hashes [Hash<String, String>] Fixity information for the referenced
49
+ # resource, as a map from hash algorithm tokens (e.g. +md5+, +sha-256+)
50
+ # to hex-encoded digest values.
51
+ # @param path [String] For +ResourceDumpManifests+ and +ChangeDumpManifests+,
52
+ # the path to the referenced resource within the dump ZIP file.
53
+ # @raise [URI::InvalidURIError] if +uri+ cannot be converted to a URI.
54
+ def initialize( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
55
+ rel:,
56
+ uri:,
57
+
58
+ priority: nil,
59
+
60
+ modified_time: nil,
61
+
62
+ length: nil,
63
+ mime_type: nil,
64
+ encoding: nil,
65
+ hashes: {},
66
+
67
+ path: nil
68
+ )
69
+ super(modified_time: modified_time, length: length, mime_type: mime_type, encoding: encoding, hashes: hashes, path: path)
70
+
71
+ self.rel = rel
72
+ self.uri = uri
73
+ self.priority = priority
74
+ end
75
+
76
+ # ------------------------------------------------------------
77
+ # Custom setters
78
+
79
+ # Sets the URI of the linked resource. Strings will be converted to +URI+ objects.
80
+ # @param value [URI, String] the URI.
81
+ # @raise [URI::InvalidURIError] if +value+ cannot be converted to a URI.
82
+ def uri=(value)
83
+ @uri = XML.to_uri(value)
84
+ end
85
+
86
+ end
87
+ end