iiif-presentation 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +2 -0
  6. data/LICENSE +23 -0
  7. data/README.md +173 -0
  8. data/Rakefile +12 -0
  9. data/VERSION +1 -0
  10. data/gemfiles/rails3.gemfile +5 -0
  11. data/gemfiles/rails4.gemfile +5 -0
  12. data/iiif-presentation.gemspec +28 -0
  13. data/lib/active_support/ordered_hash.rb +147 -0
  14. data/lib/iiif/hash_behaviours.rb +150 -0
  15. data/lib/iiif/presentation.rb +25 -0
  16. data/lib/iiif/presentation/abstract_resource.rb +75 -0
  17. data/lib/iiif/presentation/annotation.rb +25 -0
  18. data/lib/iiif/presentation/annotation_list.rb +28 -0
  19. data/lib/iiif/presentation/canvas.rb +45 -0
  20. data/lib/iiif/presentation/collection.rb +29 -0
  21. data/lib/iiif/presentation/image_resource.rb +115 -0
  22. data/lib/iiif/presentation/layer.rb +34 -0
  23. data/lib/iiif/presentation/manifest.rb +39 -0
  24. data/lib/iiif/presentation/range.rb +32 -0
  25. data/lib/iiif/presentation/resource.rb +21 -0
  26. data/lib/iiif/presentation/sequence.rb +35 -0
  27. data/lib/iiif/service.rb +418 -0
  28. data/spec/fixtures/manifests/complete_from_spec.json +171 -0
  29. data/spec/fixtures/manifests/minimal.json +40 -0
  30. data/spec/fixtures/manifests/service_only.json +11 -0
  31. data/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +159 -0
  32. data/spec/integration/iiif/presentation/image_resource_spec.rb +123 -0
  33. data/spec/integration/iiif/service_spec.rb +211 -0
  34. data/spec/spec_helper.rb +104 -0
  35. data/spec/unit/active_support/ordered_hash_spec.rb +155 -0
  36. data/spec/unit/iiif/hash_behaviours_spec.rb +569 -0
  37. data/spec/unit/iiif/presentation/abstract_resource_spec.rb +133 -0
  38. data/spec/unit/iiif/presentation/annotation_list_spec.rb +7 -0
  39. data/spec/unit/iiif/presentation/annotation_spec.rb +7 -0
  40. data/spec/unit/iiif/presentation/canvas_spec.rb +40 -0
  41. data/spec/unit/iiif/presentation/collection_spec.rb +54 -0
  42. data/spec/unit/iiif/presentation/image_resource_spec.rb +13 -0
  43. data/spec/unit/iiif/presentation/layer_spec.rb +38 -0
  44. data/spec/unit/iiif/presentation/manifest_spec.rb +89 -0
  45. data/spec/unit/iiif/presentation/range_spec.rb +43 -0
  46. data/spec/unit/iiif/presentation/resource_spec.rb +16 -0
  47. data/spec/unit/iiif/presentation/sequence_spec.rb +110 -0
  48. data/spec/unit/iiif/presentation/shared_examples/abstract_resource_only_keys.rb +43 -0
  49. data/spec/unit/iiif/presentation/shared_examples/any_type_keys.rb +33 -0
  50. data/spec/unit/iiif/presentation/shared_examples/array_only_keys.rb +44 -0
  51. data/spec/unit/iiif/presentation/shared_examples/int_only_keys.rb +49 -0
  52. data/spec/unit/iiif/presentation/shared_examples/string_only_keys.rb +29 -0
  53. data/spec/unit/iiif/service_spec.rb +10 -0
  54. metadata +262 -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
+