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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/Gemfile +2 -0
- data/LICENSE +23 -0
- data/README.md +166 -0
- data/Rakefile +12 -0
- data/VERSION +1 -0
- data/lib/active_support/ordered_hash.rb +147 -0
- data/lib/iiif/hash_behaviours.rb +150 -0
- data/lib/iiif/presentation.rb +25 -0
- data/lib/iiif/presentation/abstract_resource.rb +75 -0
- data/lib/iiif/presentation/annotation.rb +25 -0
- data/lib/iiif/presentation/annotation_list.rb +28 -0
- data/lib/iiif/presentation/canvas.rb +45 -0
- data/lib/iiif/presentation/collection.rb +29 -0
- data/lib/iiif/presentation/image_resource.rb +115 -0
- data/lib/iiif/presentation/layer.rb +34 -0
- data/lib/iiif/presentation/manifest.rb +39 -0
- data/lib/iiif/presentation/range.rb +32 -0
- data/lib/iiif/presentation/resource.rb +21 -0
- data/lib/iiif/presentation/sequence.rb +35 -0
- data/lib/iiif/service.rb +418 -0
- data/osullivan.gemspec +27 -0
- data/spec/fixtures/manifests/complete_from_spec.json +171 -0
- data/spec/fixtures/manifests/minimal.json +40 -0
- data/spec/fixtures/manifests/service_only.json +11 -0
- data/spec/fixtures/vcr_cassettes/pul_loris_cassette.json +159 -0
- data/spec/integration/iiif/presentation/image_resource_spec.rb +123 -0
- data/spec/integration/iiif/service_spec.rb +211 -0
- data/spec/spec_helper.rb +104 -0
- data/spec/unit/active_support/ordered_hash_spec.rb +155 -0
- data/spec/unit/iiif/hash_behaviours_spec.rb +569 -0
- data/spec/unit/iiif/presentation/abstract_resource_spec.rb +133 -0
- data/spec/unit/iiif/presentation/annotation_list_spec.rb +7 -0
- data/spec/unit/iiif/presentation/annotation_spec.rb +7 -0
- data/spec/unit/iiif/presentation/canvas_spec.rb +40 -0
- data/spec/unit/iiif/presentation/collection_spec.rb +54 -0
- data/spec/unit/iiif/presentation/image_resource_spec.rb +13 -0
- data/spec/unit/iiif/presentation/layer_spec.rb +38 -0
- data/spec/unit/iiif/presentation/manifest_spec.rb +89 -0
- data/spec/unit/iiif/presentation/range_spec.rb +43 -0
- data/spec/unit/iiif/presentation/resource_spec.rb +16 -0
- data/spec/unit/iiif/presentation/sequence_spec.rb +110 -0
- data/spec/unit/iiif/presentation/shared_examples/abstract_resource_only_keys.rb +43 -0
- data/spec/unit/iiif/presentation/shared_examples/any_type_keys.rb +33 -0
- data/spec/unit/iiif/presentation/shared_examples/array_only_keys.rb +44 -0
- data/spec/unit/iiif/presentation/shared_examples/int_only_keys.rb +49 -0
- data/spec/unit/iiif/presentation/shared_examples/string_only_keys.rb +29 -0
- data/spec/unit/iiif/service_spec.rb +10 -0
- 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,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
|
+
|