osullivan 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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,211 @@
1
+ require 'active_support/inflector'
2
+ require 'json'
3
+
4
+ describe IIIF::Service do
5
+
6
+ let(:fixtures_dir) { File.join(File.dirname(__FILE__), '../../fixtures') }
7
+ let(:manifest_from_spec_path) { File.join(fixtures_dir, 'manifests/complete_from_spec.json') }
8
+
9
+ describe 'self.parse' do
10
+ it 'works from a file' do
11
+ s = described_class.parse(manifest_from_spec_path)
12
+ expect(s['label']).to eq 'Book 1'
13
+ end
14
+ it 'works from a string of JSON' do
15
+ file = File.open(manifest_from_spec_path, 'rb')
16
+ json_string = file.read
17
+ file.close
18
+ s = described_class.parse(json_string)
19
+ expect(s['label']).to eq 'Book 1'
20
+ end
21
+ describe 'works from a hash' do
22
+ it 'plain old' do
23
+ h = JSON.parse(IO.read(manifest_from_spec_path))
24
+ s = described_class.parse(h)
25
+ expect(s['label']).to eq 'Book 1'
26
+ end
27
+ it 'ActiveSupport::OrderedHash' do
28
+ h = JSON.parse(IO.read(manifest_from_spec_path))
29
+ oh = ActiveSupport::OrderedHash[h]
30
+ s = described_class.parse(oh)
31
+ expect(s['label']).to eq 'Book 1'
32
+ end
33
+ end
34
+ it 'turns camels to snakes' do
35
+ s = described_class.parse(manifest_from_spec_path)
36
+ expect(s.keys.include?('see_also')).to be_truthy
37
+ expect(s.keys.include?('seeAlso')).to be_falsey
38
+ end
39
+ end
40
+
41
+ describe 'self#from_ordered_hash' do
42
+ let(:fixture) { JSON.parse('{
43
+ "@context": "http://iiif.io/api/presentation/2/context.json",
44
+ "@id": "http://example.com/manifest",
45
+ "@type": "sc:Manifest",
46
+ "label": "My Manifest",
47
+ "service": {
48
+ "@context": "http://iiif.io/api/image/2/context.json",
49
+ "@id":"http://www.example.org/images/book1-page1",
50
+ "profile":"http://iiif.io/api/image/2/profiles/level2.json"
51
+ },
52
+ "some_other_thing": {
53
+ "foo" : "bar"
54
+ },
55
+ "seeAlso": {
56
+ "@id": "http://www.example.org/library/catalog/book1.marc",
57
+ "format": "application/marc"
58
+ },
59
+ "sequences": [
60
+ {
61
+ "@id":"http://www.example.org/iiif/book1/sequence/normal",
62
+ "@type":"sc:Sequence",
63
+ "label":"Current Page Order",
64
+
65
+ "viewingDirection":"left-to-right",
66
+ "viewingHint":"paged",
67
+ "startCanvas": "http://www.example.org/iiif/book1/canvas/p2",
68
+
69
+ "canvases": [
70
+ {
71
+ "@id": "http://example.com/canvas",
72
+ "@type": "sc:Canvas",
73
+ "width": 10,
74
+ "height": 20,
75
+ "label": "My Canvas",
76
+ "otherContent": [
77
+ {
78
+ "@id": "http://example.com/content",
79
+ "@type":"sc:AnnotationList",
80
+ "motivation": "sc:painting"
81
+ }
82
+ ]
83
+ }
84
+ ]
85
+ }
86
+ ]
87
+ }')
88
+ }
89
+ it 'doesn\'t raise a NoMethodError when we check the keys' do
90
+ expect { described_class.from_ordered_hash(fixture) }.to_not raise_error
91
+ end
92
+ it 'turns the fixture into a Manifest instance' do
93
+ expected_klass = IIIF::Presentation::Manifest
94
+ parsed = described_class.from_ordered_hash(fixture)
95
+ expect(parsed.class).to be expected_klass
96
+ end
97
+ it 'turns keys without "@type" into an OrderedHash' do
98
+ expected_klass = ActiveSupport::OrderedHash
99
+ parsed = described_class.from_ordered_hash(fixture)
100
+ expect(parsed['some_other_thing'].class).to be expected_klass
101
+ end
102
+
103
+ it 'turns services into Services' do
104
+ expected_klass = IIIF::Service
105
+ parsed = described_class.from_ordered_hash(fixture)
106
+ expect(parsed['service'].class).to be expected_klass
107
+ end
108
+
109
+ it 'round-trips' do
110
+ fp = '/tmp/osullivan-spec.json'
111
+ parsed = described_class.from_ordered_hash(fixture)
112
+ File.open(fp,'w') do |f|
113
+ f.write(parsed.to_json)
114
+ end
115
+ from_file = IIIF::Service.parse('/tmp/osullivan-spec.json')
116
+ File.delete(fp)
117
+ # is this sufficient?
118
+ expect(parsed.to_ordered_hash.to_a - from_file.to_ordered_hash.to_a).to eq []
119
+ expect(from_file.to_ordered_hash.to_a - parsed.to_ordered_hash.to_a).to eq []
120
+ end
121
+ it 'turns each memeber of "sequences" into an instance of Sequence' do
122
+ expected_klass = IIIF::Presentation::Sequence
123
+ parsed = described_class.from_ordered_hash(fixture)
124
+ parsed['sequences'].each do |s|
125
+ expect(s.class).to be expected_klass
126
+ end
127
+ end
128
+ it 'turns each member of sequences/canvaes in an instance of Canvas' do
129
+ expected_klass = IIIF::Presentation::Canvas
130
+ parsed = described_class.from_ordered_hash(fixture)
131
+ parsed['sequences'].each do |s|
132
+ s.canvases.each do |c|
133
+ expect(c.class).to be expected_klass
134
+ end
135
+ end
136
+ end
137
+ it 'turns the keys into snakes' do
138
+ expect(described_class.from_ordered_hash(fixture).has_key?('seeAlso')).to be_falsey
139
+ expect(described_class.from_ordered_hash(fixture).has_key?('see_also')).to be_truthy
140
+ end
141
+ it 'copies over plain-old key-values' do
142
+ parsed = described_class.from_ordered_hash(fixture)
143
+ expect(parsed['label']).to eq 'My Manifest'
144
+ end
145
+
146
+ end
147
+
148
+ describe '#to_ordered_hash' do
149
+ let(:logo_uri) { 'http://www.example.org/logos/institution1.jpg' }
150
+ let(:within_uri) { 'http://www.example.org/collections/books/' }
151
+ let(:see_also) { 'http://www.example.org/library/catalog/book1.xml' }
152
+
153
+ describe 'it puts the json-ld keys at the top' do
154
+ let(:extra_props) { [
155
+ ['label','foo'],
156
+ ['logo','http://example.com/logo.jpg'],
157
+ ['within','http://example.com/something']
158
+ ] }
159
+ let(:sorted_ld_keys) {
160
+ subject.keys.select { |k| k.start_with?('@') }.sort!
161
+ }
162
+ before(:each) {
163
+ extra_props.reverse.each do |k,v|
164
+ subject.unshift(k,v)
165
+ end
166
+ }
167
+
168
+ it 'by default' do
169
+ (0..extra_props.length-1).each do |i|
170
+ expect(subject.keys[i]).to eq(extra_props[i][0])
171
+ end
172
+ oh = subject.to_ordered_hash
173
+ (0..sorted_ld_keys.length-1).each do |i|
174
+ expect(oh.keys[i]).to eq(sorted_ld_keys[i])
175
+ end
176
+ end
177
+ it 'unless you say not to' do
178
+ (0..extra_props.length-1).each do |i|
179
+ expect(subject.keys[i]).to eq(extra_props[i][0])
180
+ end
181
+ oh = subject.to_ordered_hash(sort_json_ld_keys: false)
182
+ (0..extra_props.length-1).each do |i|
183
+ expect(oh.keys[i]).to eq(extra_props[i][0])
184
+ end
185
+ end
186
+ end
187
+
188
+ describe 'removes empty keys' do
189
+ it 'if they\'re arrays' do
190
+ subject['logo'] = logo_uri
191
+ subject['within'] = []
192
+ ordered_hash = subject.to_ordered_hash
193
+ expect(ordered_hash.has_key?('within')).to be_falsey
194
+ end
195
+ it 'if they\'re nil' do
196
+ subject['logo'] = logo_uri
197
+ subject['within'] = nil
198
+ ordered_hash = subject.to_ordered_hash
199
+ expect(ordered_hash.has_key?('within')).to be_falsey
200
+ end
201
+ end
202
+
203
+ it 'converts snake_case keys to camelCase' do
204
+ subject['see_also'] = logo_uri
205
+ subject['within'] = within_uri
206
+ ordered_hash = subject.to_ordered_hash
207
+ expect(ordered_hash.keys.include?('seeAlso')).to be_truthy
208
+ end
209
+ end
210
+
211
+ end
@@ -0,0 +1,104 @@
1
+ require 'iiif/presentation'
2
+ require 'coveralls'
3
+ Dir["#{File.dirname(__FILE__)}/unit/iiif/presentation/shared_examples/*.rb"].each do |f|
4
+ require f
5
+ end
6
+ require 'vcr'
7
+
8
+ VCR.configure do |c|
9
+ c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
10
+ c.hook_into :webmock
11
+ c.configure_rspec_metadata!
12
+ end
13
+
14
+ Coveralls.wear!
15
+
16
+ # This file was generated by the `rspec --init` command. Conventionally, all
17
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
18
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
19
+ # file to always be loaded, without a need to explicitly require it in any files.
20
+ #
21
+ # Given that it is always loaded, you are encouraged to keep this file as
22
+ # light-weight as possible. Requiring heavyweight dependencies from this file
23
+ # will add to the boot time of your test suite on EVERY test run, even for an
24
+ # individual file that may not need all of that loaded. Instead, consider making
25
+ # a separate helper file that requires the additional dependencies and performs
26
+ # the additional setup, and require it from the spec files that actually need it.
27
+ #
28
+ # The `.rspec` file also contains a few flags that are not defaults but that
29
+ # users commonly want.
30
+ #
31
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
32
+ RSpec.configure do |config|
33
+ # rspec-expectations config goes here. You can use an alternate
34
+ # assertion/expectation library such as wrong or the stdlib/minitest
35
+ # assertions if you prefer.
36
+ config.expect_with :rspec do |expectations|
37
+ # This option will default to `true` in RSpec 4. It makes the `description`
38
+ # and `failure_message` of custom matchers include text for helper methods
39
+ # defined using `chain`, e.g.:
40
+ # be_bigger_than(2).and_smaller_than(4).description
41
+ # # => "be bigger than 2 and smaller than 4"
42
+ # ...rather than:
43
+ # # => "be bigger than 2"
44
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
45
+ end
46
+
47
+ # rspec-mocks config goes here. You can use an alternate test double
48
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
49
+ config.mock_with :rspec do |mocks|
50
+ # Prevents you from mocking or stubbing a method that does not exist on
51
+ # a real object. This is generally recommended, and will default to
52
+ # `true` in RSpec 4.
53
+ mocks.verify_partial_doubles = true
54
+ end
55
+
56
+ # The settings below are suggested to provide a good initial experience
57
+ # with RSpec, but feel free to customize to your heart's content.
58
+ =begin
59
+ # These two settings work together to allow you to limit a spec run
60
+ # to individual examples or groups you care about by tagging them with
61
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
62
+ # get run.
63
+ config.filter_run :focus
64
+ config.run_all_when_everything_filtered = true
65
+
66
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
67
+ # For more details, see:
68
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
69
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
70
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
71
+ config.disable_monkey_patching!
72
+
73
+ # This setting enables warnings. It's recommended, but in some cases may
74
+ # be too noisy due to issues in dependencies.
75
+ config.warnings = true
76
+
77
+ # Many RSpec users commonly either run the entire suite or an individual
78
+ # file, and it's useful to allow more verbose output when running an
79
+ # individual spec file.
80
+ if config.files_to_run.one?
81
+ # Use the documentation formatter for detailed output,
82
+ # unless a formatter has already been configured
83
+ # (e.g. via a command-line flag).
84
+ config.default_formatter = 'doc'
85
+ end
86
+
87
+ # Print the 10 slowest examples and example groups at the
88
+ # end of the spec run, to help surface which specs are running
89
+ # particularly slow.
90
+ config.profile_examples = 10
91
+
92
+ # Run specs in random order to surface order dependencies. If you find an
93
+ # order dependency and want to debug it, you can fix the order by providing
94
+ # the seed, which is printed after each run.
95
+ # --seed 1234
96
+ config.order = :random
97
+
98
+ # Seed global randomization in this process using the `--seed` CLI option.
99
+ # Setting this allows you to use `--seed` to deterministically reproduce
100
+ # test failures related to randomization by passing the same `--seed` value
101
+ # as the one that triggered the failure.
102
+ Kernel.srand config.seed
103
+ =end
104
+ end
@@ -0,0 +1,155 @@
1
+ describe ActiveSupport::OrderedHash do
2
+
3
+ describe '#camelize_keys' do
4
+ before(:each) do
5
+ @uri = 'http://www.example.org/descriptions/book1.xml'
6
+ @within_uri = 'http://www.example.org/collections/books/'
7
+ subject['see_also'] = @uri
8
+ subject['within'] = @within_uri
9
+ end
10
+ it 'changes snake_case keys to camelCase' do
11
+ subject.camelize_keys # #send gets past protection
12
+ expect(subject.keys.include?('seeAlso')).to be_truthy
13
+ expect(subject.keys.include?('see_also')).to be_falsey
14
+ end
15
+ it 'keeps the right values' do
16
+ subject.camelize_keys
17
+ expect(subject['seeAlso']).to eq @uri
18
+ expect(subject['within']).to eq @within_uri
19
+ end
20
+ it 'keeps things in the same position' do
21
+ see_also_position = subject.keys.index('see_also')
22
+ within_position = subject.keys.index('within')
23
+ subject.camelize_keys
24
+ expect(subject.keys[see_also_position]).to eq 'seeAlso'
25
+ expect(subject.keys[within_position]).to eq 'within'
26
+ end
27
+
28
+ end
29
+
30
+ describe '#snakeize_keys' do
31
+ before(:each) do
32
+ @uri = 'http://www.example.org/descriptions/book1.xml'
33
+ @within_uri = 'http://www.example.org/collections/books/'
34
+ subject['seeAlso'] = @uri
35
+ subject['within'] = @within_uri
36
+ end
37
+ it 'changes camelCase keys to snake_case' do
38
+ subject.snakeize_keys
39
+ expect(subject.keys.include?('see_also')).to be_truthy
40
+ expect(subject.keys.include?('seeAlso')).to be_falsey
41
+ end
42
+ it 'keeps the right values' do
43
+ subject.snakeize_keys
44
+ expect(subject['see_also']).to eq @uri
45
+ expect(subject['within']).to eq @within_uri
46
+ end
47
+ it 'keeps things in the same position' do
48
+ see_also_position = subject.keys.index('seeAlso')
49
+ within_position = subject.keys.index('within')
50
+ subject.snakeize_keys
51
+ expect(subject.keys[see_also_position]).to eq 'see_also'
52
+ expect(subject.keys[within_position]).to eq 'within'
53
+ end
54
+ end
55
+
56
+ describe 'insertion patches' do
57
+
58
+ let (:init_data) { [ ['wubble', 'fred'], ['baz', 'qux'], ['grault','garply'] ] }
59
+
60
+ subject do
61
+ hsh = ActiveSupport::OrderedHash.new
62
+ init_data.each { |e| hsh[e[0]] = e[1] }
63
+ hsh
64
+ end
65
+
66
+ describe '#insert' do
67
+ it 'inserts as expected' do
68
+ subject.insert(2, 'quux', 'corge')
69
+ expect(subject[subject.keys[0]]).to eq 'fred'
70
+ expect(subject[subject.keys[1]]).to eq 'qux'
71
+ expect(subject[subject.keys[2]]).to eq 'corge'
72
+ expect(subject[subject.keys[3]]).to eq 'garply'
73
+ end
74
+ it 'returns the instance' do
75
+ expect(subject.insert(1, 'quux','corge')).to eq subject
76
+ end
77
+ it 'raises IndexError if a negative index is too small' do
78
+ expect { subject.insert(-5, 'quux','corge') }.to raise_error IndexError
79
+ end
80
+ it 'puts index -1 on the end' do
81
+ subject.insert(-1, 'thud','wibble')
82
+ expect(subject[subject.keys.last]).to eq 'wibble'
83
+ end
84
+ end
85
+
86
+ describe '#insert_before' do
87
+ it 'inserts in the expected place with a supplied key' do
88
+ subject.insert_before(existing_key: 'grault', new_key: 'quux', value: 'corge')
89
+ expect(subject.keys).to eq ['wubble','baz','quux','grault']
90
+ end
91
+ it 'inserts in the expected place with a supplied block' do
92
+ subject.insert_before(new_key: 'quux', value: 'corge') { |k,v| k.start_with?('g') }
93
+ expect(subject.keys).to eq ['wubble','baz','quux','grault']
94
+ end
95
+ it 'returns the instance' do
96
+ expect(subject.insert_before(existing_key: 'grault', new_key: 'quux', value: 'corge')).to be subject
97
+ end
98
+ describe 'raises KeyError' do
99
+ it 'when the supplied existing key is not found' do
100
+ expect { subject.insert_before(existing_key: 'foo', new_key: 'quux', value: 'corge') }.to raise_error KeyError
101
+ end
102
+ it 'when the supplied new key already exists' do
103
+ expect { subject.insert_before(existing_key: 'grault', new_key: 'wubble', value: 'corge') }.to raise_error KeyError
104
+ end
105
+ end
106
+ end
107
+
108
+ describe '#insert_after' do
109
+ it 'inserts in the expected place with a supplied key' do
110
+ subject.insert_after(existing_key: 'baz', new_key: 'quux', value: 'corge')
111
+ expect(subject.keys).to eq ['wubble','baz','quux','grault']
112
+ end
113
+ it 'inserts in the expected place with a supplied block' do
114
+ subject.insert_after(new_key: 'quux', value: 'corge') { |k,v| k.start_with?('g') }
115
+ expect(subject.keys).to eq ['wubble','baz','quux','grault']
116
+ end
117
+ it 'returns the instance' do
118
+ expect(subject.insert_after(existing_key: 'baz', new_key: 'quux', value: 'corge')).to be subject
119
+ end
120
+ describe 'raises KeyError' do
121
+ it 'when the supplied existing key is not found' do
122
+ expect { subject.insert_after(existing_key: 'foo', new_key: 'quux', value: 'corge') }.to raise_error KeyError
123
+ end
124
+ it 'when the supplied new key already exists' do
125
+ expect { subject.insert_after(existing_key: 'grault', new_key: 'wubble', value: 'corge') }.to raise_error KeyError
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '#unshift' do
131
+ it 'adds an entry to the front of the object' do
132
+ subject.unshift('thud','wibble')
133
+ expect(subject[subject.keys[0]]).to eq 'wibble'
134
+ end
135
+ it 'returns the instance' do
136
+ expect(subject.unshift('thud','wibble')).to be subject
137
+ end
138
+ end
139
+
140
+ describe '#remove_empties' do
141
+ it 'if they\'re arrays' do
142
+ subject[:wubble] = []
143
+ subject.remove_empties
144
+ expect(subject.has_key?(:wubble)).to be_falsey
145
+ end
146
+ it 'if they\'re nil' do
147
+ subject[:wubble] = nil
148
+ subject.remove_empties
149
+ expect(subject.has_key?(:wubble)).to be_falsey
150
+ end
151
+ end
152
+
153
+ end
154
+ end
155
+