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,25 @@
1
+ require File.join(File.dirname(__FILE__), 'service')
2
+ %w{
3
+ abstract_resource
4
+ annotation
5
+ annotation_list
6
+ canvas
7
+ collection
8
+ layer
9
+ manifest
10
+ resource
11
+ image_resource
12
+ sequence
13
+ range
14
+ }.each do |f|
15
+ require File.join(File.dirname(__FILE__), 'presentation', f)
16
+ end
17
+
18
+ module IIIF
19
+ module Presentation
20
+ CONTEXT ||= 'http://iiif.io/api/presentation/2/context.json'
21
+
22
+ class MissingRequiredKeyError < StandardError; end
23
+ class IllegalValueError < StandardError; end
24
+ end
25
+ end
@@ -0,0 +1,75 @@
1
+ require File.join(File.dirname(__FILE__), '../service')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class AbstractResource < Service
6
+
7
+ # Every subclass should override the following five methods where
8
+ # appropriate, see Subclasses for how.
9
+ def required_keys
10
+ %w{ @type }
11
+ end
12
+
13
+ def any_type_keys # these are allowed on all classes
14
+ %w{ label description thumbnail attribution license logo see_also
15
+ related within }
16
+ end
17
+
18
+ def string_only_keys
19
+ %w{ viewing_hint } # should any of the any_type_keys be here?
20
+ end
21
+
22
+ def array_only_keys
23
+ %w{ metadata }
24
+ end
25
+
26
+ def abstract_resource_only_keys
27
+ super + [ { key: 'service', type: IIIF::Service } ]
28
+ end
29
+
30
+ def hash_only_keys
31
+ %w{ }
32
+ end
33
+
34
+ def int_only_keys
35
+ %w{ }
36
+ end
37
+
38
+ # Not every subclass is allowed to have viewingDirect, but when it is,
39
+ # it must be one of these values
40
+ def legal_viewing_direction_values
41
+ %w{ left-to-right right-to-left top-to-bottom bottom-to-top }
42
+ end
43
+
44
+ # Initialize a Presentation node
45
+ # @param [Hash] hsh - Anything in this hash will be added to the Object.'
46
+ # Order is only guaranteed if an ActiveSupport::OrderedHash is passed.
47
+ # @param [boolean] include_context (default: false). Pass true if the'
48
+ # context should be included.
49
+ def initialize(hsh={})
50
+ if self.class == IIIF::Presentation::AbstractResource
51
+ raise "#{self.class} is an abstract class. Please use one of its subclasses."
52
+ end
53
+ super(hsh)
54
+ end
55
+
56
+
57
+ # Options:
58
+ # * force: (true|false). Skips validations.
59
+ # * include_context: (true|false). Adds the @context to the top of the
60
+ # document if it doesn't exist. Default: true.
61
+ # * sort_json_ld_keys: (true|false). Brings all properties starting with
62
+ # '@'. Default: true. to the top of the document and sorts them.
63
+ def to_ordered_hash(opts={})
64
+ include_context = opts.fetch(:include_context, true)
65
+ if include_context && !self.has_key?('@context')
66
+ self['@context'] = IIIF::Presentation::CONTEXT
67
+ end
68
+ super(opts)
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
75
+
@@ -0,0 +1,25 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Annotation < AbstractResource
6
+
7
+ TYPE = 'oa:Annotation'
8
+
9
+ def required_keys
10
+ super + %w{ motivation }
11
+ end
12
+
13
+ def abstract_resource_only_keys
14
+ super + [ { key: 'resource', type: IIIF::Presentation::Resource } ]
15
+ end
16
+
17
+ def initialize(hsh={})
18
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
19
+ hsh['motivation'] = 'sc:painting' unless hsh.has_key? 'motivation'
20
+ super(hsh)
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class AnnotationList < AbstractResource
6
+
7
+ TYPE = 'sc:AnnotationList'
8
+
9
+ def required_keys
10
+ super + %w{ @id }
11
+ end
12
+
13
+ def array_only_keys;
14
+ super + %w{ resources };
15
+ end
16
+
17
+ def initialize(hsh={})
18
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
19
+ super(hsh)
20
+ end
21
+
22
+ def validate
23
+ # Each member or resources must be a kind of Annotation
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,45 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Canvas < AbstractResource
6
+
7
+ # TODO (?) a simple 'Image Canvas' constructor.
8
+
9
+ TYPE = 'sc:Canvas'
10
+
11
+ def required_keys
12
+ super + %w{ @id width height label }
13
+ end
14
+
15
+ def any_type_keys
16
+ super + %w{ }
17
+ end
18
+
19
+ def array_only_keys
20
+ super + %w{ images other_content }
21
+ end
22
+
23
+ # TODO: test and validate
24
+ def int_only_keys
25
+ super + %w{ width height }
26
+ end
27
+
28
+ def legal_viewing_hint_values
29
+ super + %w{ non-paged }
30
+ end
31
+
32
+ def initialize(hsh={})
33
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
34
+ super(hsh)
35
+ end
36
+
37
+ def validate
38
+ # all members of images must be an annotation
39
+ # all members of otherContent must be an annotation list
40
+ super
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Collection < AbstractResource
6
+
7
+ TYPE = 'sc:Collection'
8
+
9
+ def required_keys
10
+ super + %w{ @id label }
11
+ end
12
+
13
+ def array_only_keys
14
+ super + %w{ collections manifests }
15
+ end
16
+
17
+ def initialize(hsh={})
18
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
19
+ super(hsh)
20
+ end
21
+
22
+ def validate
23
+ # each member of collections and manifests must be a Hash
24
+ # each member of collections and manifests MUST have @id, @type, and label
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,115 @@
1
+ require File.join(File.dirname(__FILE__), 'resource')
2
+ require 'faraday'
3
+ require 'json'
4
+
5
+ module IIIF
6
+ module Presentation
7
+ class ImageResource < Resource
8
+
9
+ TYPE = 'dctypes:Image'
10
+
11
+ def int_only_keys
12
+ super + %w{ width height }
13
+ end
14
+
15
+ def initialize(hsh={})
16
+ hsh['@type'] = 'dcterms:Image' unless hsh.has_key? '@type'
17
+ super(hsh)
18
+ end
19
+
20
+ class << self
21
+ IMAGE_API_DEFAULT_PARAMS = '/full/!200,200/0/default.jpg'
22
+ IMAGE_API_CONTEXT = 'http://iiif.io/api/image/2/context.json'
23
+ DEFAULT_FORMAT = 'image/jpeg'
24
+ # Create a new ImageResource that includes a IIIF Image API Service
25
+ # See http://iiif.io/api/presentation/2.0/#image-resources
26
+ #
27
+ # Params
28
+ # * :service_id (required) - The base URI for the image on the image
29
+ # server.
30
+ # * :resource_id - The id for the resource; if supplied this should
31
+ # resolve to an actual image. Default:
32
+ # "#{:service_id}/full/!200,200/0/default.jpg"
33
+ # * :format - The format of the image that is returned when
34
+ # `:resource_id` is resolved. Default: 'image/jpeg'
35
+ # * :height (Integer)
36
+ # * :profile (String)
37
+ # * :width (Integer) - If width, height, and profile are not supplied,
38
+ # this method will try to get the info from the server (based on
39
+ # :resource_id) and raise an Exception if this is not possible for
40
+ # some reason.
41
+ # * :copy_info (bool)- Even if width and height are supplied, try to
42
+ # get the info.json from the server and copy it in. Default: false
43
+ #
44
+ # Raises:
45
+ # * KeyError if `:service_id` is not supplied
46
+ # * Expections related to HTTP problems if a call to an image server fails
47
+ #
48
+ # The result is something like this:
49
+ #
50
+ # {
51
+ # "@id":"http://www.example.org/iiif/book1/res/page1.jpg",
52
+ # "@type":"dctypes:Image",
53
+ # "format":"image/jpeg",
54
+ # "service": {
55
+ # "@context": "http://iiif.io/api/image/2/context.json",
56
+ # "@id":"http://www.example.org/images/book1-page1",
57
+ # "profile":"http://iiif.io/api/image/2/profiles/level2.json",
58
+ # },
59
+ # "height":2000,
60
+ # "width":1500
61
+ # }
62
+ #
63
+ def create_image_api_image_resource(params={})
64
+
65
+ service_id = params.fetch(:service_id)
66
+ resource_id_default = "#{service_id}#{IMAGE_API_DEFAULT_PARAMS}"
67
+ resource_id = params.fetch(:resource_id, resource_id_default)
68
+ format = params.fetch(:format, DEFAULT_FORMAT)
69
+ height = params.fetch(:height, nil)
70
+ profile = params.fetch(:profile, nil)
71
+ width = params.fetch(:width, nil)
72
+ copy_info = params.fetch(:copy_info, false)
73
+
74
+ have_whp = [width, height, profile].all? { |prop| !prop.nil? }
75
+
76
+ remote_info = get_info(service_id) if !have_whp || copy_info
77
+
78
+ resource = self.new
79
+ resource['@id'] = resource_id
80
+ resource.format = format
81
+ resource.width = width.nil? ? remote_info['width'] : width
82
+ resource.height = height.nil? ? remote_info['height'] : height
83
+ resource.service = Service.new
84
+ if copy_info
85
+ resource.service.merge!(remote_info)
86
+ else
87
+ resource.service['@context'] = IMAGE_API_CONTEXT
88
+ resource.service['@id'] = service_id
89
+ if profile.nil?
90
+ if remote_info['profile'].kind_of?(Array)
91
+ resource.service['profile'] = remote_info['profile'][0]
92
+ else
93
+ resource.service['profile'] = remote_info['profile'][0]
94
+ end
95
+ else
96
+ resource.service['profile'] = profile
97
+ end
98
+ end
99
+ return resource
100
+ end
101
+
102
+ protected
103
+ def get_info(svc_id)
104
+ conn = Faraday.new("#{svc_id}/info.json") do |c|
105
+ c.use Faraday::Response::RaiseError
106
+ c.use Faraday::Adapter::NetHttp
107
+ end
108
+ resp = conn.get # raises exceptions that indicate HTTP problems
109
+ JSON.parse(resp.body)
110
+ end
111
+ end
112
+
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,34 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Layer < AbstractResource
6
+
7
+ TYPE = 'sc:Layer'
8
+
9
+ def required_keys
10
+ super + %w{ @id label }
11
+ end
12
+
13
+ def array_only_keys
14
+ super + %w{ other_content }
15
+ end
16
+
17
+ def string_only_keys
18
+ super + %w{ viewing_direction } # should any of the any_type_keys be here?
19
+ end
20
+
21
+ def initialize(hsh={})
22
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
23
+ super(hsh)
24
+ end
25
+
26
+ def validate
27
+ # Must all members of otherContent and images must be a URI (string), or
28
+ # can they be inline?
29
+ super
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,39 @@
1
+ require File.join(File.dirname(__FILE__), 'abstract_resource')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Manifest < AbstractResource
6
+
7
+ TYPE = 'sc:Manifest'
8
+
9
+ def required_keys
10
+ super + %w{ @id label }
11
+ end
12
+
13
+ def string_only_keys
14
+ super + %w{ viewing_direction }
15
+ end
16
+
17
+ def array_only_keys
18
+ super + %w{ sequences structures }
19
+ end
20
+
21
+ def legal_viewing_hint_values
22
+ %w{ individuals paged continuous }
23
+ end
24
+
25
+ def initialize(hsh={})
26
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
27
+ super(hsh)
28
+ end
29
+
30
+ def validate
31
+ # TODO: check types of sequences and structure members
32
+
33
+ super
34
+ end
35
+
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,32 @@
1
+ require File.join(File.dirname(__FILE__), 'sequence')
2
+
3
+ module IIIF
4
+ module Presentation
5
+ class Range < Sequence
6
+
7
+ TYPE = 'sc:Range'
8
+
9
+ def required_keys
10
+ super + %w{ @id label }
11
+ end
12
+
13
+ def array_only_keys
14
+ super + %w{ ranges }
15
+ end
16
+
17
+ def legal_viewing_hint_values
18
+ super + %w{ top }
19
+ end
20
+
21
+ def initialize(hsh={})
22
+ hsh['@type'] = TYPE unless hsh.has_key? '@type'
23
+ super(hsh)
24
+ end
25
+
26
+ def validate
27
+ # Values of the ranges array must be strings
28
+ end
29
+
30
+ end
31
+ end
32
+ end