osullivan 0.0.2

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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +23 -0
  7. data/README.md +166 -0
  8. data/Rakefile +12 -0
  9. data/VERSION +1 -0
  10. data/lib/active_support/ordered_hash.rb +147 -0
  11. data/lib/iiif/hash_behaviours.rb +150 -0
  12. data/lib/iiif/presentation.rb +25 -0
  13. data/lib/iiif/presentation/abstract_resource.rb +75 -0
  14. data/lib/iiif/presentation/annotation.rb +25 -0
  15. data/lib/iiif/presentation/annotation_list.rb +28 -0
  16. data/lib/iiif/presentation/canvas.rb +45 -0
  17. data/lib/iiif/presentation/collection.rb +29 -0
  18. data/lib/iiif/presentation/image_resource.rb +115 -0
  19. data/lib/iiif/presentation/layer.rb +34 -0
  20. data/lib/iiif/presentation/manifest.rb +39 -0
  21. data/lib/iiif/presentation/range.rb +32 -0
  22. data/lib/iiif/presentation/resource.rb +21 -0
  23. data/lib/iiif/presentation/sequence.rb +35 -0
  24. data/lib/iiif/service.rb +418 -0
  25. data/osullivan.gemspec +27 -0
  26. data/spec/fixtures/manifests/complete_from_spec.json +171 -0
  27. data/spec/fixtures/manifests/minimal.json +40 -0
  28. data/spec/fixtures/manifests/service_only.json +11 -0
  29. data/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +159 -0
  30. data/spec/integration/iiif/presentation/image_resource_spec.rb +123 -0
  31. data/spec/integration/iiif/service_spec.rb +211 -0
  32. data/spec/spec_helper.rb +104 -0
  33. data/spec/unit/active_support/ordered_hash_spec.rb +155 -0
  34. data/spec/unit/iiif/hash_behaviours_spec.rb +569 -0
  35. data/spec/unit/iiif/presentation/abstract_resource_spec.rb +133 -0
  36. data/spec/unit/iiif/presentation/annotation_list_spec.rb +7 -0
  37. data/spec/unit/iiif/presentation/annotation_spec.rb +7 -0
  38. data/spec/unit/iiif/presentation/canvas_spec.rb +40 -0
  39. data/spec/unit/iiif/presentation/collection_spec.rb +54 -0
  40. data/spec/unit/iiif/presentation/image_resource_spec.rb +13 -0
  41. data/spec/unit/iiif/presentation/layer_spec.rb +38 -0
  42. data/spec/unit/iiif/presentation/manifest_spec.rb +89 -0
  43. data/spec/unit/iiif/presentation/range_spec.rb +43 -0
  44. data/spec/unit/iiif/presentation/resource_spec.rb +16 -0
  45. data/spec/unit/iiif/presentation/sequence_spec.rb +110 -0
  46. data/spec/unit/iiif/presentation/shared_examples/abstract_resource_only_keys.rb +43 -0
  47. data/spec/unit/iiif/presentation/shared_examples/any_type_keys.rb +33 -0
  48. data/spec/unit/iiif/presentation/shared_examples/array_only_keys.rb +44 -0
  49. data/spec/unit/iiif/presentation/shared_examples/int_only_keys.rb +49 -0
  50. data/spec/unit/iiif/presentation/shared_examples/string_only_keys.rb +29 -0
  51. data/spec/unit/iiif/service_spec.rb +10 -0
  52. metadata +246 -0
@@ -0,0 +1,133 @@
1
+ require 'active_support/inflector'
2
+ require 'json'
3
+ require File.join(File.dirname(__FILE__), '../../../../lib/iiif/hash_behaviours')
4
+
5
+
6
+ describe IIIF::Presentation::AbstractResource do
7
+
8
+ let(:fixtures_dir) { File.join(File.dirname(__FILE__), '../../../fixtures') }
9
+ let(:manifest_from_spec_path) { File.join(fixtures_dir, 'manifests/complete_from_spec.json') }
10
+ let(:abstract_resource_subclass) do
11
+ Class.new(IIIF::Presentation::AbstractResource) do
12
+ include IIIF::HashBehaviours
13
+
14
+ def initialize(hsh={})
15
+ hsh['@type'] = 'a:SubClass' unless hsh.has_key?('@type')
16
+ super(hsh)
17
+ end
18
+
19
+ def required_keys
20
+ super + %w{ @id }
21
+ end
22
+ end
23
+ end
24
+
25
+ subject do
26
+ instance = abstract_resource_subclass.new
27
+ instance['@id'] = 'http://example.com/prefix/manifest/123'
28
+ instance
29
+ end
30
+
31
+ describe '#initialize' do
32
+ it 'raises an error if you try to instantiate AbstractResource' do
33
+ expect { IIIF::Presentation::AbstractResource.new }.to raise_error(RuntimeError)
34
+ end
35
+ it 'sets @type' do
36
+ expect(subject['@type']).to eq 'a:SubClass'
37
+ end
38
+ it 'can take any old hash' do
39
+ hsh = JSON.parse(IO.read(manifest_from_spec_path))
40
+ new_instance = abstract_resource_subclass.new(hsh)
41
+ expect(new_instance['label']).to eq 'Book 1'
42
+ end
43
+ end
44
+
45
+ describe 'A nested object (e.g. self[\'metdata\'])' do
46
+ it 'returns [] if not set' do
47
+ expect(subject.metadata).to eq([])
48
+ end
49
+ it 'is not in #to_ordered_hash at all if we access it but do not append to it' do
50
+ subject.metadata # touch it
51
+ expect(subject.metadata).to eq([])
52
+ expect(subject.to_ordered_hash.has_key?('metadata')).to be_falsey
53
+ end
54
+ it 'gets structured as we\'d expect' do
55
+ subject.metadata << {
56
+ 'label' => 'Author',
57
+ 'value' => 'Anne Author'
58
+ }
59
+ subject.metadata << {
60
+ 'label' => 'Published',
61
+ 'value' => [
62
+ {'@value'=> 'Paris, circa 1400', '@language'=>'en'},
63
+ {'@value'=> 'Paris, environ 14eme siecle', '@language'=>'fr'}
64
+ ]
65
+ }
66
+ expect(subject.metadata[0]['label']).to eq('Author')
67
+ expect(subject.metadata[1]['value'].length).to eq(2)
68
+ expect(subject.metadata[1]['value'].select { |e| e['@language'] == 'fr'}.last['@value']).to eq('Paris, environ 14eme siecle')
69
+ end
70
+
71
+ it 'roundtrips' do
72
+ subject.metadata << {
73
+ 'label' => 'Author',
74
+ 'value' => 'Anne Author'
75
+ }
76
+ subject.metadata << {
77
+ 'label' => 'Published',
78
+ 'value' => [
79
+ {'@value'=> 'Paris, circa 1400', '@language'=>'en'},
80
+ {'@value'=> 'Paris, environ 14eme siecle', '@language'=>'fr'}
81
+ ]
82
+ }
83
+ File.open('/tmp/osullivan-spec.json','w') do |f|
84
+ f.write(subject.to_json)
85
+ end
86
+ parsed = subject.class.parse('/tmp/osullivan-spec.json')
87
+ expect(parsed['metadata'][0]['label']).to eq('Author')
88
+ expect(parsed['metadata'][1]['value'].length).to eq(2)
89
+ expect(parsed['metadata'][1]['value'].select { |e| e['@language'] == 'fr'}.last['@value']).to eq('Paris, environ 14eme siecle')
90
+ File.delete('/tmp/osullivan-spec.json')
91
+ end
92
+ end
93
+
94
+ describe '#required_keys' do
95
+ it 'accumulates' do
96
+ expect(subject.required_keys).to eq %w{ @type @id }
97
+ end
98
+ end
99
+
100
+ describe '#to_ordered_hash' do
101
+ describe 'adds the @context' do
102
+ before(:each) { subject.delete('@context') }
103
+ it 'by default' do
104
+ expect(subject.has_key?('@context')).to be_falsey
105
+ expect(subject.to_ordered_hash.has_key?('@context')).to be_truthy
106
+ end
107
+ it 'unless you say not to' do
108
+ expect(subject.has_key?('@context')).to be_falsey
109
+ expect(subject.to_ordered_hash(include_context: false).has_key?('@context')).to be_falsey
110
+ end
111
+ it 'or it\'s already there' do
112
+ different_ctxt = 'http://example.org/context'
113
+ subject['@context'] = different_ctxt
114
+ oh = subject.to_ordered_hash
115
+ expect(oh['@context']).to eq different_ctxt
116
+ end
117
+ end
118
+
119
+ describe 'runs the validations' do
120
+ # Test this here because there's nothing to validate on the superclass (Subject)
121
+ let(:error) { IIIF::Presentation::MissingRequiredKeyError }
122
+ before(:each) { subject.delete('@id') }
123
+ it 'raises exceptions' do
124
+ expect { subject.to_ordered_hash }.to raise_error error
125
+ end
126
+ it 'unless you tell it not to' do
127
+ expect { subject.to_ordered_hash(force: true) }.to_not raise_error
128
+ end
129
+ end
130
+ end
131
+
132
+ end
133
+
@@ -0,0 +1,7 @@
1
+ describe IIIF::Presentation::AnnotationList do
2
+
3
+ describe "#{described_class}.define_methods_for_array_only_keys" do
4
+ it_behaves_like 'it has the appropriate methods for array-only keys'
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ describe IIIF::Presentation::Annotation do
2
+
3
+ describe "#{described_class}.define_methods_for_abstract_resource_only_keys" do
4
+ it_behaves_like 'it has the appropriate methods for abstract_resource_only_keys'
5
+ end
6
+
7
+ end
@@ -0,0 +1,40 @@
1
+ describe IIIF::Presentation::Canvas do
2
+
3
+ let(:fixed_values) do
4
+ {
5
+ "@context" => "http://iiif.io/api/presentation/2/context.json",
6
+ "@id" => "http://www.example.org/iiif/book1/canvas/p1",
7
+ "@type" => "sc:Canvas",
8
+ "label" => "p. 1",
9
+ "height" => 1000,
10
+ "width" => 750,
11
+ "images" => [ ],
12
+ "otherContent" => [ ]
13
+ }
14
+ end
15
+
16
+
17
+ describe '#initialize' do
18
+ it 'sets @type' do
19
+ expect(subject['@type']).to eq 'sc:Canvas'
20
+ end
21
+ end
22
+
23
+ describe "#{described_class}.int_only_keys" do
24
+ it_behaves_like 'it has the appropriate methods for integer-only keys'
25
+ end
26
+
27
+ describe "#{described_class}.define_methods_for_string_only_keys" do
28
+ it_behaves_like 'it has the appropriate methods for string-only keys'
29
+ end
30
+
31
+ describe "#{described_class}.define_methods_for_array_only_keys" do
32
+ it_behaves_like 'it has the appropriate methods for array-only keys'
33
+ end
34
+
35
+ describe "#{described_class}.define_methods_for_any_type_keys" do
36
+ it_behaves_like 'it has the appropriate methods for any-type keys'
37
+ end
38
+
39
+ end
40
+
@@ -0,0 +1,54 @@
1
+ describe IIIF::Presentation::Collection do
2
+
3
+ let(:fixed_values) do
4
+ {
5
+ '@context' => 'http://iiif.io/api/presentation/2/context.json',
6
+ '@id' => 'http://example.org/iiif/collection/top',
7
+ '@type' => 'sc:Collection',
8
+ 'label' => 'Top Level Collection for Example Organization',
9
+ 'description' => 'Description of Collection',
10
+ 'attribution' => 'Provided by Example Organization',
11
+
12
+ 'collections' => [
13
+ { '@id' => 'http://example.org/iiif/collection/part1',
14
+ '@type' => 'sc:Collection',
15
+ 'label' => 'Sub Collection 1'
16
+ },
17
+ { '@id' => 'http://example.org/iiif/collection/part2',
18
+ '@type' => 'sc:Collection',
19
+ 'label' => 'Sub Collection 2'
20
+ }
21
+ ],
22
+ 'manifests' => [
23
+ { '@id' => 'http://example.org/iiif/book1/manifest',
24
+ '@type' => 'sc:Manifest',
25
+ 'label' => 'Book 1'
26
+ }
27
+ ]
28
+ }
29
+ end
30
+
31
+ describe '#initialize' do
32
+ it 'sets @type to sc:Collection by default' do
33
+ expect(subject['@type']).to eq 'sc:Collection'
34
+ end
35
+ end
36
+
37
+ describe "#{described_class}.define_methods_for_array_only_keys" do
38
+ it_behaves_like 'it has the appropriate methods for array-only keys'
39
+ end
40
+
41
+ describe "#{described_class}.define_methods_for_string_only_keys" do
42
+ it_behaves_like 'it has the appropriate methods for string-only keys'
43
+ end
44
+
45
+ describe "#{described_class}.define_methods_for_any_type_keys" do
46
+ it_behaves_like 'it has the appropriate methods for any-type keys'
47
+ end
48
+
49
+ describe '#validate' do
50
+ end
51
+
52
+ end
53
+
54
+
@@ -0,0 +1,13 @@
1
+ describe IIIF::Presentation::ImageResource do
2
+
3
+ describe '#initialize' do
4
+ it 'sets @type to dcterms:Image' do
5
+ expect(subject['@type']).to eq 'dcterms:Image'
6
+ end
7
+ end
8
+
9
+ describe "#{described_class}.int_only_keys" do
10
+ it_behaves_like 'it has the appropriate methods for integer-only keys'
11
+ end
12
+
13
+ end
@@ -0,0 +1,38 @@
1
+ describe IIIF::Presentation::Layer do
2
+
3
+ let(:fixed_values) do
4
+ {
5
+ '@context' => 'http://iiif.io/api/presentation/2/context.json',
6
+ '@id' => 'http://www.example.org/iiif/book1/layer/transcription',
7
+ '@type' => 'sc:Layer',
8
+ 'label' => 'Diplomatic Transcription',
9
+ 'otherContent' => [
10
+ 'http://www.example.org/iiif/book1/list/l1',
11
+ 'http://www.example.org/iiif/book1/list/l2',
12
+ 'http://www.example.org/iiif/book1/list/l3',
13
+ 'http://www.example.org/iiif/book1/list/l4'
14
+ ]
15
+ }
16
+ end
17
+
18
+
19
+ describe '#initialize' do
20
+ it 'sets @type' do
21
+ expect(subject['@type']).to eq 'sc:Layer'
22
+ end
23
+ end
24
+
25
+ describe "#{described_class}.define_methods_for_string_only_keys" do
26
+ it_behaves_like 'it has the appropriate methods for string-only keys'
27
+ end
28
+
29
+ describe "#{described_class}.define_methods_for_array_only_keys" do
30
+ it_behaves_like 'it has the appropriate methods for array-only keys'
31
+ end
32
+
33
+ describe "#{described_class}.define_methods_for_any_type_keys" do
34
+ it_behaves_like 'it has the appropriate methods for any-type keys'
35
+ end
36
+
37
+ end
38
+
@@ -0,0 +1,89 @@
1
+ describe IIIF::Presentation::Manifest do
2
+
3
+ let(:subclass_subject) do
4
+ Class.new(IIIF::Presentation::Manifest) do
5
+ def initialize(hsh={})
6
+ hsh = { '@type' => 'a:SubClass' }
7
+ super(hsh)
8
+ end
9
+ end
10
+ end
11
+
12
+ let(:fixed_values) do
13
+ {
14
+ 'type' => 'a:SubClass',
15
+ 'id' => 'http://example.com/prefix/manifest/123',
16
+ 'context' => IIIF::Presentation::CONTEXT,
17
+ 'label' => 'Book 1',
18
+ 'description' => 'A longer description of this example book. It should give some real information.',
19
+ 'thumbnail' => {
20
+ '@id' => 'http://www.example.org/images/book1-page1/full/80,100/0/default.jpg',
21
+ 'service'=> {
22
+ '@context' => 'http://iiif.io/api/image/2/context.json',
23
+ '@id' => 'http://www.example.org/images/book1-page1',
24
+ 'profile' => 'http://iiif.io/api/image/2/level1.json'
25
+ }
26
+ },
27
+ 'attribution' => 'Provided by Example Organization',
28
+ 'license' => 'http://www.example.org/license.html',
29
+ 'logo' => 'http://www.example.org/logos/institution1.jpg',
30
+ 'see_also' => 'http://www.example.org/library/catalog/book1.xml',
31
+ 'service' => {
32
+ '@context' => 'http://example.org/ns/jsonld/context.json',
33
+ '@id' => 'http://example.org/service/example',
34
+ 'profile' => 'http://example.org/docs/example-service.html'
35
+ },
36
+ 'related' => {
37
+ '@id' => 'http://www.example.org/videos/video-book1.mpg',
38
+ 'format' => 'video/mpeg'
39
+ },
40
+ 'within' => 'http://www.example.org/collections/books/',
41
+ }
42
+ end
43
+
44
+ describe '#initialize' do
45
+ it 'sets @type to sc:Manifest by default' do
46
+ expect(subject['@type']).to eq 'sc:Manifest'
47
+ end
48
+ it 'allows subclasses to override @type' do
49
+ sub = subclass_subject.new
50
+ expect(sub['@type']).to eq 'a:SubClass'
51
+ end
52
+ end
53
+
54
+ describe '#required_keys' do
55
+ it 'accumulates' do
56
+ expect(subject.required_keys).to eq %w{ @type @id label }
57
+ end
58
+ end
59
+
60
+ describe '#validate' do
61
+ it 'raises an error if there is no @id' do
62
+ subject.label = 'Book 1'
63
+ expect { subject.validate }.to raise_error IIIF::Presentation::MissingRequiredKeyError
64
+ end
65
+ it 'raises an error if there is no label' do
66
+ subject['@id'] = 'http://www.example.org/iiif/book1/manifest'
67
+ expect { subject.validate }.to raise_error IIIF::Presentation::MissingRequiredKeyError
68
+ end
69
+ it 'raises an error if there is no @type' do
70
+ subject.delete('@type')
71
+ subject.label = 'Book 1'
72
+ subject['@id'] = 'http://www.example.org/iiif/book1/manifest'
73
+ expect { subject.validate }.to raise_error IIIF::Presentation::MissingRequiredKeyError
74
+ end
75
+ end
76
+
77
+ describe "#{described_class}.define_methods_for_array_only_keys" do
78
+ it_behaves_like 'it has the appropriate methods for array-only keys'
79
+ end
80
+
81
+ describe "#{described_class}.define_methods_for_string_only_keys" do
82
+ it_behaves_like 'it has the appropriate methods for string-only keys'
83
+ end
84
+
85
+ describe "#{described_class}.define_methods_for_any_type_keys" do
86
+ it_behaves_like 'it has the appropriate methods for any-type keys'
87
+ end
88
+
89
+ end
@@ -0,0 +1,43 @@
1
+ describe IIIF::Presentation::Range do
2
+
3
+ let(:fixed_values) do
4
+ {
5
+ '@id' => 'http://www.example.org/iiif/book1/range/r1',
6
+ '@type' => 'sc:Range',
7
+ 'label' => 'Introduction',
8
+ 'ranges' => [
9
+ 'http://www.example.org/iiif/book1/range/r1-1',
10
+ 'http://www.example.org/iiif/book1/range/r1-2'
11
+ ],
12
+ 'canvases' => [
13
+ 'http://www.example.org/iiif/book1/canvas/p1',
14
+ 'http://www.example.org/iiif/book1/canvas/p2',
15
+ 'http://www.example.org/iiif/book1/canvas/p3#xywh=0,0,750,300'
16
+ ]
17
+ }
18
+ end
19
+
20
+ describe '#initialize' do
21
+ it 'sets @type to sc:Range by default' do
22
+ expect(subject['@type']).to eq 'sc:Range'
23
+ end
24
+ end
25
+
26
+ describe "#{described_class}.define_methods_for_array_only_keys" do
27
+ it_behaves_like 'it has the appropriate methods for array-only keys'
28
+ end
29
+
30
+ describe "#{described_class}.define_methods_for_string_only_keys" do
31
+ it_behaves_like 'it has the appropriate methods for string-only keys'
32
+ end
33
+
34
+ describe "#{described_class}.define_methods_for_any_type_keys" do
35
+ it_behaves_like 'it has the appropriate methods for any-type keys'
36
+ end
37
+
38
+ describe '#validate' do
39
+ end
40
+
41
+ end
42
+
43
+
@@ -0,0 +1,16 @@
1
+ describe IIIF::Presentation::Resource do
2
+
3
+ describe "#{described_class}.define_methods_for_abstract_resource_only_keys" do
4
+ it_behaves_like 'it has the appropriate methods for abstract_resource_only_keys'
5
+ end
6
+
7
+ describe "#{described_class}.int_only_keys" do
8
+ it_behaves_like 'it has the appropriate methods for integer-only keys'
9
+ end
10
+
11
+ describe "#{described_class}.define_methods_for_string_only_keys" do
12
+ it_behaves_like 'it has the appropriate methods for string-only keys'
13
+ end
14
+
15
+ end
16
+