diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6e8a6a5..fceb845 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -38,6 +38,10 @@ RSpec/VerifiedDoubles: Exclude: - 'spec/lib/iiif_manifest/display_image_spec.rb' +RSpec/ExampleLength: + Exclude: + - 'spec/lib/iiif_manifest/manifest_factory_spec.rb' + # Offense count: 20 Style/Documentation: Exclude: @@ -56,6 +60,7 @@ Style/Documentation: - 'lib/iiif_manifest/manifest_builder/record_property_builder.rb' - 'lib/iiif_manifest/manifest_builder/resource_builder.rb' - 'lib/iiif_manifest/manifest_builder/sequence_builder.rb' + - 'lib/iiif_manifest/manifest_builder/structure_builder.rb' - 'lib/iiif_manifest/manifest_builder.rb' - 'lib/iiif_manifest/manifest_factory.rb' - 'lib/iiif_manifest/manifest_service_locator.rb' @@ -67,6 +72,10 @@ Style/MethodMissing: Exclude: - 'lib/iiif_manifest/manifest_builder/composite_builder.rb' +Metrics/ClassLength: + Exclude: + - 'lib/iiif_manifest/manifest_service_locator.rb' + # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: MinSize, SupportedStyles. diff --git a/README.md b/README.md index 864a8b6..4ee151c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,12 @@ Additionally, it ***must*** have a `#description` method that returns a string. Additionally it ***should*** implement `#manifest_url` that shows where the manifest can be found. -Finally, it ***may*** implement `#sequence_rendering` to contain an array of hashes for file downloads to be offered at sequences level. Each hash must contain "@id", "format" (mime type) and "label" (eg. `{ "@id" => "download url", "format" => "application/pdf", "label" => "user friendly label" }`). +Additionally it ***may*** implement `#sequence_rendering` to contain an array of hashes for file downloads to be offered at sequences level. Each hash must contain "@id", "format" (mime type) and "label" (eg. `{ "@id" => "download url", "format" => "application/pdf", "label" => "user friendly label" }`). + +Finally, It ***may*** implement `ranges`, which returns an array of objects which +represent a table of contents or similar structure, each of which responds to +`label`, `ranges`, and `file_set_presenters`. + For example: @@ -37,10 +42,33 @@ For example: def description 'a brief description' end - + def sequence_rendering: [{"@id" => "http://test.host/file_set/id/download", "format" => "application/pdf", "label" => "Download"}] end + + def ranges + [ + ManifestRange.new( + label: "Table of Contents", + ranges: [ + ManifestRange.new( + label: "Chapter 1", + file_set_presenters: @pages + ) + ] + ) + ] + end + end + + class ManifestRange + attr_reader :label, :ranges, :file_set_presenters + def initialize(label:, ranges: [], file_set_presenters: []) + @label = label + @ranges = ranges + @file_set_presenters = file_set_presenters + end end ``` diff --git a/iiif_manifest.gemspec b/iiif_manifest.gemspec index cfa3239..4ed4427 100644 --- a/iiif_manifest.gemspec +++ b/iiif_manifest.gemspec @@ -27,4 +27,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'rubocop' spec.add_development_dependency 'rubocop-rspec' + spec.add_development_dependency 'pry-byebug' end diff --git a/lib/iiif_manifest/manifest_builder.rb b/lib/iiif_manifest/manifest_builder.rb index 8b70bff..e48d9cd 100644 --- a/lib/iiif_manifest/manifest_builder.rb +++ b/lib/iiif_manifest/manifest_builder.rb @@ -9,6 +9,7 @@ require_relative 'manifest_builder/record_property_builder' require_relative 'manifest_builder/resource_builder' require_relative 'manifest_builder/sequence_builder' +require_relative 'manifest_builder/structure_builder' module IIIFManifest class ManifestBuilder diff --git a/lib/iiif_manifest/manifest_builder/structure_builder.rb b/lib/iiif_manifest/manifest_builder/structure_builder.rb new file mode 100644 index 0000000..4da3747 --- /dev/null +++ b/lib/iiif_manifest/manifest_builder/structure_builder.rb @@ -0,0 +1,76 @@ +module IIIFManifest + class ManifestBuilder + class StructureBuilder + attr_reader :record + def initialize(record) + @record = record + end + + def apply(manifest) + top_ranges.each_with_index do |top_range| + manifest['structures'] ||= [] + manifest['structures'] = RangeBuilder.new(top_range, record, true).apply(manifest['structures']) + end + manifest + end + + def top_ranges + record.try(:ranges) || [] + end + end + class RangeBuilder + attr_reader :record, :parent + delegate :file_set_presenters, to: :record + def initialize(record, parent, top = false) + @record = record + @parent = parent + @top = top + build_range + end + + def path + "#{parent.manifest_url}/range/r#{index}" + end + + def index + @index ||= SecureRandom.uuid + end + + def apply(manifest) + manifest << range + sub_ranges.map do |sub_range| + manifest = sub_range.apply(manifest) + end + manifest + end + + def build_range + range['@id'] = path + range['label'] = record.label + range['viewingHint'] = 'top' if top? + range['ranges'] = sub_ranges.map(&:path) + range['canvases'] = canvas_builders.map(&:path) + end + + def sub_ranges + @sub_ranges ||= record.ranges.map do |sub_range| + RangeBuilder.new(sub_range, parent) + end + end + + def canvas_builders + @canvas_builders ||= file_set_presenters.map do |file_set_presenter| + CanvasBuilder.new(file_set_presenter, parent) + end + end + + def range + @range ||= IIIF::Presentation::Range.new + end + + def top? + @top + end + end + end +end diff --git a/lib/iiif_manifest/manifest_service_locator.rb b/lib/iiif_manifest/manifest_service_locator.rb index 33b97ad..ae632bb 100644 --- a/lib/iiif_manifest/manifest_service_locator.rb +++ b/lib/iiif_manifest/manifest_service_locator.rb @@ -39,6 +39,7 @@ def manifest_builders composite_builder_factory.new( record_property_builder, sequence_builder, + structure_builder, composite_builder: composite_builder ) end @@ -82,6 +83,10 @@ def record_property_builder ManifestBuilder::RecordPropertyBuilder end + def structure_builder + ManifestBuilder::StructureBuilder + end + def sequence_builder InjectedFactory.new( ManifestBuilder::SequenceBuilder, diff --git a/spec/lib/iiif_manifest/manifest_factory_spec.rb b/spec/lib/iiif_manifest/manifest_factory_spec.rb index 2854e24..18cc7b2 100644 --- a/spec/lib/iiif_manifest/manifest_factory_spec.rb +++ b/spec/lib/iiif_manifest/manifest_factory_spec.rb @@ -27,6 +27,24 @@ def work_presenters def manifest_url "http://test.host/books/#{@id}/manifest" end + + def ranges + @ranges ||= + [ + ManifestRange.new(label: 'Table of Contents', ranges: [ + ManifestRange.new(label: 'Chapter 1', file_set_presenters: []) + ]) + ] + end + end + + class ManifestRange + attr_reader :label, :ranges, :file_set_presenters + def initialize(label:, ranges: [], file_set_presenters: []) + @label = label + @ranges = ranges + @file_set_presenters = file_set_presenters + end end class DisplayImagePresenter @@ -74,6 +92,22 @@ def display_image expect(IIIFManifest::ManifestBuilder::CanvasBuilder).to have_received(:new) .exactly(1).times.with(file_presenter, anything) end + it 'builds a structure if it can' do + allow(book_presenter).to receive(:file_set_presenters).and_return([file_presenter]) + allow(book_presenter.ranges[0].ranges[0]).to receive(:file_set_presenters).and_return([file_presenter]) + + expect(result['structures'].length).to eq 2 + structure = result['structures'].first + expect(structure['label']).to eq 'Table of Contents' + expect(structure['viewingHint']).to eq 'top' + expect(structure['canvases']).to be_blank + expect(structure['ranges'].length).to eq 1 + expect(structure['ranges'][0]).not_to eq structure['@id'] + + sub_range = result['structures'].last + expect(sub_range['ranges']).to be_blank + expect(sub_range['canvases'].length).to eq 1 + end end context 'where there is a no sequence_rendering method' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c1a9a32..275fdf3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,2 +1,3 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'iiif_manifest' +require 'pry'