aperitiiif 0.1.0
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/.reek.yml +10 -0
- data/.rspec +3 -0
- data/.rubocop.yml +28 -0
- data/.ruby-version +1 -0
- data/Gemfile +14 -0
- data/README.md +81 -0
- data/Rakefile +17 -0
- data/bin/aperitiiif +8 -0
- data/lib/aperitiiif/asset.rb +137 -0
- data/lib/aperitiiif/batch/assets.rb +33 -0
- data/lib/aperitiiif/batch/items.rb +24 -0
- data/lib/aperitiiif/batch/linters.rb +86 -0
- data/lib/aperitiiif/batch/records.rb +45 -0
- data/lib/aperitiiif/batch.rb +84 -0
- data/lib/aperitiiif/cli.rb +70 -0
- data/lib/aperitiiif/config.rb +52 -0
- data/lib/aperitiiif/error.rb +13 -0
- data/lib/aperitiiif/index.rb +58 -0
- data/lib/aperitiiif/item.rb +103 -0
- data/lib/aperitiiif/record.rb +51 -0
- data/lib/aperitiiif/template/index.html +67 -0
- data/lib/aperitiiif/utils.rb +48 -0
- data/lib/aperitiiif/version.rb +5 -0
- data/lib/aperitiiif.rb +20 -0
- metadata +195 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 02f6e4b4232068a021694eadacda55cde5757f54811d297bf0e73982bb50d0c1
|
4
|
+
data.tar.gz: ec553c98daa97c74fd8a3a315a8ec68e316ed3d5450b6a83d00c8bd5d38c97c5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5628ed423c165523a95ea40ca4a6a43b67ce791142f45c927b6858b1ae0bf7cb64ef33906589a07cae08d6f1e512cde751f940ec05db2316a2df2ad232634aad
|
7
|
+
data.tar.gz: 24b053c916baa29fde6dda1ca17b2d5a49356157217de7fe60271aad477b10792b65e12519a44ea5efb62ec84d8e9c9663bc8204d706e68d36db232c252ebc2a
|
data/.reek.yml
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.1
|
3
|
+
SuggestExtensions: false
|
4
|
+
NewCops: disable
|
5
|
+
|
6
|
+
Style/StringLiterals:
|
7
|
+
Enabled: true
|
8
|
+
EnforcedStyle: single_quotes
|
9
|
+
|
10
|
+
Style/IfUnlessModifier:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Style/StringLiteralsInInterpolation:
|
14
|
+
Enabled: true
|
15
|
+
EnforcedStyle: single_quotes
|
16
|
+
|
17
|
+
Layout/LineLength:
|
18
|
+
Max: 120
|
19
|
+
AllowedPatterns:
|
20
|
+
- 'raise'
|
21
|
+
|
22
|
+
Naming/RescuedExceptionsVariableName:
|
23
|
+
Enabled: false # conflicts with reek expectations
|
24
|
+
|
25
|
+
Metrics/BlockLength:
|
26
|
+
Exclude:
|
27
|
+
- '*.gemspec'
|
28
|
+
- 'spec/**/*'
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.1.2
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# aperitiiif-cli
|
2
|
+
|
3
|
+
gem-packaged commands for processing aperitiiif batches 🥂
|
4
|
+
|
5
|
+
[](https://github.com/nyu-dss/aperitiiif/actions/workflows/rspec.yml) [](https://github.com/nyu-dss/aperitiiif/actions/workflows/reek.yml) [](https://github.com/nyu-dss/aperitiiif/actions/workflows/rubocop.yml)
|
6
|
+
|
7
|
+
[](https://codeclimate.com/github/nyu-dss/aperitiiif/maintainability) [](https://codeclimate.com/github/nyu-dss/aperitiiif/test_coverage)
|
8
|
+
|
9
|
+
## Overview
|
10
|
+
|
11
|
+
<img alt="aperitiiif service diagram" src="https://nyu-dss.github.io/aperitiiif/media/aperitiiif.jpg" style="max-height:300px;width:auto" />
|
12
|
+
|
13
|
+
> View as [PDF](https://nyu-dss.github.io/aperitiiif/media/aperitiiif.pdf).
|
14
|
+
|
15
|
+
|
16
|
+
## Prerequisites
|
17
|
+
- [Ruby Version Manager](https://rvm.io/rvm/install)
|
18
|
+
- [Git](https://git-scm.com/downloads)
|
19
|
+
- [Vips](https://www.libvips.org/install.html)
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
### Recommended
|
24
|
+
|
25
|
+
It is highly recommended that you use the [aperitiiif-batch-template](https://github.com/nyu-dss/aperitiiif-batch-template) repo to create your new batch project. This method will include all the necessary Ruby dependencies and project structure.
|
26
|
+
|
27
|
+
### Manual
|
28
|
+
|
29
|
+
Alternatively, you can add the gem to your project's Gemfile:
|
30
|
+
|
31
|
+
``` ruby
|
32
|
+
gem 'aperitiiif', github: 'nyu-dss/aperitiiif-cli'
|
33
|
+
```
|
34
|
+
|
35
|
+
Then install by running the command:
|
36
|
+
|
37
|
+
``` sh
|
38
|
+
bundle install
|
39
|
+
```
|
40
|
+
|
41
|
+
## Usage
|
42
|
+
|
43
|
+
After your batch project is set up and you have installed the dependencies using Bundler, you will have access to the `aperitiiif` commands.
|
44
|
+
|
45
|
+
1. Check available commands
|
46
|
+
```sh
|
47
|
+
bundle exec aperitiiif --help
|
48
|
+
```
|
49
|
+
2. Check available batch commands
|
50
|
+
```sh
|
51
|
+
bundle exec aperitiiif batch --help
|
52
|
+
```
|
53
|
+
You will see something like:
|
54
|
+
```sh
|
55
|
+
➜ bundle exec aperitiiif batch --help
|
56
|
+
Commands:
|
57
|
+
aperitiiif batch build # build batch resources
|
58
|
+
aperitiiif batch help [COMMAND] # Describe subcommands or one specific subc...
|
59
|
+
aperitiiif batch lint # lint the batch
|
60
|
+
aperitiiif batch reset # reset the batch
|
61
|
+
```
|
62
|
+
|
63
|
+
## Development
|
64
|
+
|
65
|
+
Contributions should:
|
66
|
+
- Avoid code smells
|
67
|
+
- Follow style guide
|
68
|
+
- Update tests as needed
|
69
|
+
- Update documentation as needed
|
70
|
+
|
71
|
+
To ensure the above, this repo includes configuration for [reek](https://github.com/troessner/reek) and [rubocop](https://github.com/rubocop/rubocop) as well as [rspec](https://rspec.info/) tests. You can run them respectively with the following commands:
|
72
|
+
|
73
|
+
```sh
|
74
|
+
bundle exec reek
|
75
|
+
```
|
76
|
+
```sh
|
77
|
+
bundle exec rubocop -A
|
78
|
+
```
|
79
|
+
```sh
|
80
|
+
bundle exec rspec
|
81
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'reek/rake/task'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
begin
|
8
|
+
RSpec::Core::RakeTask.new :spec do |t|
|
9
|
+
t.fail_on_error = false
|
10
|
+
end
|
11
|
+
|
12
|
+
Reek::Rake::Task.new do |t|
|
13
|
+
t.fail_on_error = false
|
14
|
+
end
|
15
|
+
|
16
|
+
task default: :spec
|
17
|
+
end
|
data/bin/aperitiiif
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'iiif/presentation'
|
4
|
+
require 'mimemagic'
|
5
|
+
require 'vips'
|
6
|
+
|
7
|
+
# to do
|
8
|
+
module Aperitiiif
|
9
|
+
# to do
|
10
|
+
# has smell :reek:TooManyMethods
|
11
|
+
class Asset
|
12
|
+
attr_reader :parent_id
|
13
|
+
|
14
|
+
TARGET_EXT = '.jpg'
|
15
|
+
|
16
|
+
def initialize(parent_id, source, config)
|
17
|
+
@parent_id = parent_id
|
18
|
+
@source = source
|
19
|
+
@config = config
|
20
|
+
|
21
|
+
validate_source
|
22
|
+
validate_config
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_source
|
26
|
+
raise Aperitiiif::Error, "Asset source #{@source} does not exist" unless File.file? @source
|
27
|
+
raise Aperitiiif::Error, "Asset source #{@source} is not a valid format" unless Utils.valid_source? @source
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_config
|
31
|
+
raise Aperitiiif::Error, "No value found for 'namespace'. Check your config?" if @config.namespace.empty?
|
32
|
+
raise Aperitiiif::Error, "No value found for 'image_build_dir'. Check your config?" if @config.image_build_dir.empty?
|
33
|
+
raise Aperitiiif::Error, "No value found for 'presentation_api_url'. Check your config?" if @config.presentation_api_url.empty?
|
34
|
+
raise Aperitiiif::Error, "No value found for 'image_api_url'. Check your config?" if @config.image_api_url.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def id
|
38
|
+
@id ||= build_id
|
39
|
+
end
|
40
|
+
|
41
|
+
def target
|
42
|
+
@target ||= build_target
|
43
|
+
end
|
44
|
+
|
45
|
+
def width
|
46
|
+
@width ||= Vips::Image.new_from_file(@source).width
|
47
|
+
end
|
48
|
+
|
49
|
+
def height
|
50
|
+
@height ||= Vips::Image.new_from_file(@source).height
|
51
|
+
end
|
52
|
+
|
53
|
+
def target_mime
|
54
|
+
write_to_target
|
55
|
+
@target_mime ||= Utils.mime target
|
56
|
+
end
|
57
|
+
|
58
|
+
def source_mime
|
59
|
+
@source_mime ||= Utils.mime @source
|
60
|
+
end
|
61
|
+
|
62
|
+
def canvas_url
|
63
|
+
@canvas_url ||= "#{@config.presentation_api_url}/canvas/#{id}.json"
|
64
|
+
end
|
65
|
+
|
66
|
+
def thumbnail_url
|
67
|
+
@thumbnail_url ||= "#{@config.image_api_url}/#{id}/full/250,/0/default.jpg"
|
68
|
+
end
|
69
|
+
|
70
|
+
def annotation_url
|
71
|
+
@annotation_url ||= "#{@config.presentation_api_url}/annotation/#{id}.json"
|
72
|
+
end
|
73
|
+
|
74
|
+
def full_resource_url
|
75
|
+
@full_resource_url ||= "#{@config.image_api_url}/#{id}/full/full/0/default.jpg"
|
76
|
+
end
|
77
|
+
|
78
|
+
def service_url
|
79
|
+
@service_url ||= "#{@config.image_api_url}/#{id}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_id
|
83
|
+
id = Utils.basename_no_ext @source
|
84
|
+
id.prepend "#{@parent_id}_" unless id == @parent_id
|
85
|
+
id.prepend "#{@config.namespace}_"
|
86
|
+
end
|
87
|
+
|
88
|
+
def target_written? = File.file? target
|
89
|
+
|
90
|
+
def build_target = "#{@config.image_build_dir}/#{id}#{TARGET_EXT}"
|
91
|
+
|
92
|
+
def write_to_target
|
93
|
+
return false if target_written?
|
94
|
+
|
95
|
+
FileUtils.mkdir_p @config.image_build_dir
|
96
|
+
Vips::Image.new_from_file(@source).write_to_file target
|
97
|
+
end
|
98
|
+
|
99
|
+
def service
|
100
|
+
{
|
101
|
+
'@context' => 'http://iiif.io/api/image/2/context.json',
|
102
|
+
'@id' => service_url
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def annotation
|
107
|
+
opts = {
|
108
|
+
'@id' => annotation_url,
|
109
|
+
'on' => canvas_url,
|
110
|
+
'resource' => resource
|
111
|
+
}
|
112
|
+
IIIF::Presentation::Annotation.new opts
|
113
|
+
end
|
114
|
+
|
115
|
+
def resource
|
116
|
+
opts = {
|
117
|
+
'@id' => full_resource_url,
|
118
|
+
'@type' => 'dcterms:Image',
|
119
|
+
'service' => service,
|
120
|
+
'format' => target_mime
|
121
|
+
}
|
122
|
+
IIIF::Presentation::Resource.new opts
|
123
|
+
end
|
124
|
+
|
125
|
+
def canvas
|
126
|
+
opts = {
|
127
|
+
'@id' => canvas_url,
|
128
|
+
'label' => id,
|
129
|
+
'width' => width,
|
130
|
+
'height' => height,
|
131
|
+
'thumbnail' => thumbnail_url,
|
132
|
+
'images' => [annotation]
|
133
|
+
}
|
134
|
+
IIIF::Presentation::Canvas.new opts
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TO DO COMMENT
|
4
|
+
module Aperitiiif
|
5
|
+
# TO DO COMMENT
|
6
|
+
module Assets
|
7
|
+
def assets
|
8
|
+
@assets ||= map_to_assets
|
9
|
+
end
|
10
|
+
|
11
|
+
def child_assets(path)
|
12
|
+
return [path] if Utils.valid_source?(path)
|
13
|
+
return [] unless File.directory? path
|
14
|
+
|
15
|
+
Dir.glob("#{path}/*").select { |sub| Utils.valid_source? sub }
|
16
|
+
end
|
17
|
+
|
18
|
+
# has smell :reek:NestedIterators
|
19
|
+
def map_to_assets(mymap = asset_map)
|
20
|
+
mymap.flat_map { |id, vals| vals.map { |val| Asset.new id, val, config } }
|
21
|
+
end
|
22
|
+
|
23
|
+
def asset_map(dir = config.source_dir)
|
24
|
+
map = {}
|
25
|
+
Dir["#{dir}/*"].each do |path|
|
26
|
+
parent_id = Utils.parent_id(path, dir)
|
27
|
+
children = child_assets path
|
28
|
+
map[parent_id] = children unless children.empty?
|
29
|
+
end
|
30
|
+
map
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aperitiiif/item'
|
4
|
+
|
5
|
+
# TO DO COMMENT
|
6
|
+
module Aperitiiif
|
7
|
+
# TO DO COMMENT
|
8
|
+
module Items
|
9
|
+
def items
|
10
|
+
@items ||= items_from_assets
|
11
|
+
end
|
12
|
+
|
13
|
+
def items=(items)
|
14
|
+
@items = items
|
15
|
+
end
|
16
|
+
|
17
|
+
def items_from_assets(assets = self.assets)
|
18
|
+
grouped = assets.group_by(&:parent_id)
|
19
|
+
grouped.flat_map do |id, grouped_assets|
|
20
|
+
Item.new id, grouped_assets, config
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TO DO COMMENT
|
4
|
+
module Aperitiiif
|
5
|
+
# TO DO COMMENT
|
6
|
+
module Linters
|
7
|
+
def linters
|
8
|
+
@linters ||= [
|
9
|
+
method(:warn_nil_record_items),
|
10
|
+
method(:warn_duplicate_record_ids),
|
11
|
+
method(:warn_duplicate_image_names),
|
12
|
+
method(:warn_missing_labels),
|
13
|
+
method(:warn_stray_files)
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
def lint
|
18
|
+
puts 'Linting...'.colorize(:cyan)
|
19
|
+
puts 'Passed ✓'.colorize(:green) unless report_failures.any?
|
20
|
+
end
|
21
|
+
|
22
|
+
def report_failures
|
23
|
+
linters.map(&:call)
|
24
|
+
end
|
25
|
+
|
26
|
+
# has smell :reek:TooManyStatements
|
27
|
+
def warn_nil_record_items
|
28
|
+
nil_record_items = items.select { |item| item.record.blank? }
|
29
|
+
return false if nil_record_items.empty?
|
30
|
+
|
31
|
+
warn "Could not find record(s) for #{nil_record_items.length} items:".colorze(:orange)
|
32
|
+
nil_record_items.each { |item| puts Utils.rm_batch_namespace(item.id).colorize(:yellow) }
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
# has smell :reek:TooManyStatements
|
37
|
+
def warn_duplicate_record_ids
|
38
|
+
ids = records.map(&:id)
|
39
|
+
dup_ids = ids.select { |id| ids.count(id) > 1 }.uniq
|
40
|
+
return false if dup_ids.empty?
|
41
|
+
|
42
|
+
warn "Found #{dup_ids.length} non-unique record id value(s):".colorze(:orange)
|
43
|
+
dup_ids.each { |id| puts id.colorize(:yellow) }
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
# has smell :reek:DuplicateMethodCall
|
48
|
+
# has smell :reek:TooManyStatements
|
49
|
+
# rubocop: disable Metrics/AbcSize
|
50
|
+
def warn_duplicate_image_names
|
51
|
+
files = Dir.glob("#{config.source_dir}/**/*").select { |file| File.file? file }
|
52
|
+
files.map! { |file| Utils.rm_ext file.sub(config.source_dir, '') }
|
53
|
+
duplicate_names = files.select { |file| files.count(file) > 1 }.uniq
|
54
|
+
return false if duplicate_names.empty?
|
55
|
+
|
56
|
+
warn "Found #{duplicate_names.length} duplicate image name(s):".colorze(:orange)
|
57
|
+
duplicate_names.each { |name| puts Utils.prune_prefix_junk(name).colorize(:yellow) }
|
58
|
+
true
|
59
|
+
end
|
60
|
+
# rubocop: enable Metrics/AbcSize
|
61
|
+
|
62
|
+
# has smell :reek:TooManyStatements
|
63
|
+
def warn_missing_labels
|
64
|
+
no_label_records = records.select { |record| record.label.to_s.empty? }
|
65
|
+
return false if no_label_records.empty?
|
66
|
+
|
67
|
+
warn "Found #{no_label_records.length} record(s) missing labels (strongly encouraged):".colorze(:orange)
|
68
|
+
no_label_records.each { |record| puts record.id.colorize(:yellow) }
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
# has smell :reek:TooManyStatements
|
73
|
+
# rubocop:disable Metrics/AbcSize
|
74
|
+
def warn_stray_files
|
75
|
+
stray_files = Dir.glob("#{config.source_dir}/**/*")
|
76
|
+
stray_files.select! { |file| File.file?(file) && !file.end_with?(*ALLOWED_SRC_FORMATS) }
|
77
|
+
return false if stray_files.empty?
|
78
|
+
|
79
|
+
warn "Found #{stray_files.length} stray file(s) with unaccepted file types:".colorze(:orange)
|
80
|
+
stray_files.each { |file| puts file.colorize(:yellow) }
|
81
|
+
puts "(Accepted file extensions: #{ALLOWED_SRC_FORMATS.join(', ')})".colorize(:pink)
|
82
|
+
true
|
83
|
+
end
|
84
|
+
# rubocop:enable Metrics/AbcSize
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TO DO COMMENT
|
4
|
+
module Aperitiiif
|
5
|
+
# TO DO COMMENT
|
6
|
+
module Records
|
7
|
+
def records
|
8
|
+
@records ||= records_from_file
|
9
|
+
end
|
10
|
+
|
11
|
+
def find_record(item_id, records = self.records)
|
12
|
+
records.find { |record| record.id == item_id }
|
13
|
+
end
|
14
|
+
|
15
|
+
# has smell :reek:DuplicateMethodCall
|
16
|
+
def records_from_file(file = config.records_file, defaults = config.records_defaults)
|
17
|
+
return [] unless records_file_configured? file
|
18
|
+
return [] unless records_file_exists? file
|
19
|
+
|
20
|
+
Utils.csv_records file, defaults
|
21
|
+
end
|
22
|
+
|
23
|
+
def records_file_configured?(file = config.records_file)
|
24
|
+
return true if file.present?
|
25
|
+
|
26
|
+
warn 'WARNING:: No records file configured'.colorize(:yellow)
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def records_file_exists?(file = config.records_file)
|
31
|
+
return true if File.file?(file)
|
32
|
+
|
33
|
+
warn "WARNING:: Couldn't find records file #{file}".colorize(:yellow)
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_records!
|
38
|
+
self.items = items.map do |item|
|
39
|
+
record = find_record(item.id, records)
|
40
|
+
item.record = record if record.present?
|
41
|
+
item
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'parallel'
|
5
|
+
require 'ruby-progressbar'
|
6
|
+
require 'safe_yaml'
|
7
|
+
|
8
|
+
require 'aperitiiif/batch/assets'
|
9
|
+
require 'aperitiiif/batch/items'
|
10
|
+
require 'aperitiiif/batch/records'
|
11
|
+
require 'aperitiiif/batch/linters'
|
12
|
+
|
13
|
+
# TO DO COMMENT
|
14
|
+
module Aperitiiif
|
15
|
+
# TO DO COMMENT
|
16
|
+
class Batch
|
17
|
+
include Assets
|
18
|
+
include Items
|
19
|
+
include Linters
|
20
|
+
include Records
|
21
|
+
|
22
|
+
def initialize; end
|
23
|
+
|
24
|
+
DEFAULT_CONFIG_FILE = './config.yml'
|
25
|
+
|
26
|
+
def config
|
27
|
+
@config ||= load_config_file
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_config_file(file = DEFAULT_CONFIG_FILE)
|
31
|
+
@config = Config.new SafeYAML.load_file file
|
32
|
+
rescue StandardError
|
33
|
+
raise Aperitiiif::Error, "Cannot find file #{file}" unless File.file?(file)
|
34
|
+
|
35
|
+
raise Aperitiiif::Error, "Cannot read file #{file}. Is it valid yaml?"
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_config_hash(hash)
|
39
|
+
@config = Config.new hash
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset(dir = config.build_dir)
|
43
|
+
puts 'Resetting build...'.colorize(:cyan)
|
44
|
+
FileUtils.rm_rf dir
|
45
|
+
puts 'Done ✓'.colorize(:green)
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_target_assets(assets = self.assets)
|
49
|
+
msg = 'Writing target image TIFs'.colorize(:cyan)
|
50
|
+
Parallel.map(assets, in_threads: 4, progress: { format: "#{msg}: %c/%u | %P%" }, &:write_to_target)
|
51
|
+
end
|
52
|
+
|
53
|
+
# has smell :reek:TooManyStatements
|
54
|
+
def write_presentation_json(items = self.items)
|
55
|
+
msg = 'Writing IIIF Presentation JSON'.colorize(:cyan)
|
56
|
+
load_records!
|
57
|
+
Parallel.map(items, in_threads: 4, progress: { format: "#{msg}: %c/%u | %P%" }, &:write_presentation_json)
|
58
|
+
write_iiif_collection_json
|
59
|
+
end
|
60
|
+
|
61
|
+
def seed
|
62
|
+
{
|
63
|
+
'@id' => iiif_collection_url,
|
64
|
+
'label' => config.label,
|
65
|
+
'description' => config.batch_description,
|
66
|
+
'attribution' => config.batch_attribution
|
67
|
+
}.delete_if { |_key, val| val.blank? }
|
68
|
+
end
|
69
|
+
|
70
|
+
def iiif_collection
|
71
|
+
collection = IIIF::Presentation::Collection.new seed
|
72
|
+
collection.manifests = items.map(&:manifest)
|
73
|
+
collection
|
74
|
+
end
|
75
|
+
|
76
|
+
def iiif_collection_file = "#{config.presentation_build_dir}/#{config.namespace}/collection.json"
|
77
|
+
def iiif_collection_url = "#{config.presentation_api_url}/#{config.namespace}/collection.json"
|
78
|
+
def iiif_collection_written? = File.file? iiif_collection_file
|
79
|
+
|
80
|
+
def write_iiif_collection_json
|
81
|
+
Aperitiiif::Utils.mkfile_p iiif_collection_file, iiif_collection.to_json(pretty: true)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TO DO COMMENT
|
4
|
+
module Aperitiiif
|
5
|
+
# TO DO COMMENT
|
6
|
+
module CLI
|
7
|
+
# TO DO COMMENT
|
8
|
+
class SubCommandBase < Thor
|
9
|
+
def self.banner(command, _namespace, _subcommand)
|
10
|
+
"#{basename} #{subcommand_prefix} #{command.usage}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.subcommand_prefix
|
14
|
+
str = name.gsub(/.*::/, '')
|
15
|
+
str.gsub!(/^[A-Z]/) { |match| match[0].downcase }
|
16
|
+
str.gsub!(/[A-Z]/) { |match| "-#{match[0].downcase}" }
|
17
|
+
str
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# TO DO COMMENT
|
22
|
+
class Batch < SubCommandBase
|
23
|
+
desc 'build', 'build batch resources'
|
24
|
+
|
25
|
+
option :config, type: :string, default: ''
|
26
|
+
option :lint, type: :boolean
|
27
|
+
option :reset, type: :boolean
|
28
|
+
|
29
|
+
# has smells :reek:FeatureEnvy, :reek:TooManyStatements
|
30
|
+
# rubocop:disable Metrics/AbcSize
|
31
|
+
def build
|
32
|
+
batch = Aperitiiif::Batch.new
|
33
|
+
batch.load_config_file(options[:config]) if options[:config].present?
|
34
|
+
batch.reset if options[:reset]
|
35
|
+
batch.lint if options[:lint]
|
36
|
+
|
37
|
+
batch.write_target_assets
|
38
|
+
batch.write_presentation_json
|
39
|
+
index = Aperitiiif::Index.new batch
|
40
|
+
index.write type: :html
|
41
|
+
index.write type: :json
|
42
|
+
end
|
43
|
+
# rubocop:enable Metrics/AbcSize
|
44
|
+
|
45
|
+
desc 'reset', 'reset the batch'
|
46
|
+
def reset
|
47
|
+
batch = Aperitiiif::Batch.new
|
48
|
+
batch.reset
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'lint', 'lint the batch'
|
52
|
+
def lint
|
53
|
+
batch = Aperitiiif::Batch.new
|
54
|
+
batch.lint
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# TO DO COMMENT
|
59
|
+
class Main < Thor
|
60
|
+
map %w[--version -v] => :__print_version
|
61
|
+
desc '--version, -v', 'print the version'
|
62
|
+
def __print_version
|
63
|
+
puts Aperitiiif::VERSION
|
64
|
+
end
|
65
|
+
|
66
|
+
desc 'batch SUBCOMMANDS', 'batch subcommands'
|
67
|
+
subcommand 'batch', Batch
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# TO DO COMMENT
|
4
|
+
module Aperitiiif
|
5
|
+
# has smell :reek:Attribute
|
6
|
+
# TO DO COMMENT
|
7
|
+
class Config
|
8
|
+
attr_reader :namespace, :hash
|
9
|
+
|
10
|
+
DELEGATE = %i[puts p].freeze
|
11
|
+
DEFAULT_VALUES = {
|
12
|
+
'build_dir' => './build',
|
13
|
+
'source_dir' => './src/data',
|
14
|
+
'service_namespace' => 'aperitiiif-batch'
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def initialize(seed = {})
|
18
|
+
@hash = DEFAULT_VALUES.merge(seed)
|
19
|
+
@namespace = batch_namespace
|
20
|
+
|
21
|
+
validate
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing(method, *args, &block)
|
25
|
+
return super if DELEGATE.include? method
|
26
|
+
|
27
|
+
@hash.fetch method.to_s, ''
|
28
|
+
end
|
29
|
+
|
30
|
+
def respond_to_missing?(method, _args)
|
31
|
+
DELEGATE.include?(method) or super
|
32
|
+
end
|
33
|
+
|
34
|
+
# should check for more reqs
|
35
|
+
def validate
|
36
|
+
raise Aperitiiif::Error, "Config is missing a valid 'namespace' for the batch." if namespace.empty?
|
37
|
+
raise Aperitiiif::Error, "Config is missing 'label' for the batch." if label.empty?
|
38
|
+
end
|
39
|
+
|
40
|
+
def batch_namespace(dir = nil)
|
41
|
+
dir ||= FileUtils.pwd
|
42
|
+
name = dir.split(service_namespace).last
|
43
|
+
Utils.slugify name
|
44
|
+
end
|
45
|
+
|
46
|
+
def presentation_build_dir = "#{build_dir}/presentation"
|
47
|
+
def image_build_dir = "#{build_dir}/image"
|
48
|
+
def html_build_dir = "#{build_dir}/html"
|
49
|
+
def records_file = @hash.dig('records', 'file') || ''
|
50
|
+
def records_defaults = @hash.dig('records', 'defaults') || {}
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'liquid'
|
5
|
+
|
6
|
+
# TO DO COMMENT
|
7
|
+
module Aperitiiif
|
8
|
+
# TO DO COMMENT
|
9
|
+
class Index
|
10
|
+
VALID_TYPES = %i[json html].freeze
|
11
|
+
TEMPLATE_FILE = "#{__dir__}/template/index.html".freeze
|
12
|
+
|
13
|
+
attr_reader :config, :items
|
14
|
+
|
15
|
+
def initialize(batch)
|
16
|
+
@batch = batch
|
17
|
+
@config = batch.config
|
18
|
+
@items = batch.items
|
19
|
+
end
|
20
|
+
|
21
|
+
def path(type)
|
22
|
+
"#{@config.html_build_dir}/index.#{type}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# rubocop: disable Metrics/AbcSize
|
26
|
+
# has smell :reek:TooManyStatements
|
27
|
+
def write(**options)
|
28
|
+
raise Aperitiiif::Error, "Index#write is missing required 'type:' option" unless options.key? :type
|
29
|
+
raise Aperitiiif::Error, "Index#write 'type: #{options[:type]}' does not match available types #{VALID_TYPES}" unless VALID_TYPES.include? options[:type]
|
30
|
+
|
31
|
+
print "Writing #{options[:type]} index...".colorize(:cyan)
|
32
|
+
|
33
|
+
index = options[:type] == :html ? to_html : to_json
|
34
|
+
path = options&.[](:path) || path(options[:type])
|
35
|
+
|
36
|
+
Aperitiiif::Utils.mkfile_p path, index
|
37
|
+
|
38
|
+
puts "\r#{"Writing #{options[:type]} index:".colorize(:cyan)} #{'Done ✓'.colorize(:green)} "
|
39
|
+
end
|
40
|
+
# rubocop: enable Metrics/AbcSize
|
41
|
+
|
42
|
+
def to_json(items = self.items)
|
43
|
+
JSON.pretty_generate items.map(&:to_hash)
|
44
|
+
end
|
45
|
+
|
46
|
+
# has smell :reek:DuplicateMethodCall
|
47
|
+
def to_html(items = self.items, config = self.config)
|
48
|
+
vars = {
|
49
|
+
'items' => items.map(&:to_hash),
|
50
|
+
'config' => config.hash,
|
51
|
+
'iiif_collection_url' => @batch.iiif_collection_url,
|
52
|
+
'formatted_date' => Aperitiiif::Utils.formatted_datetime
|
53
|
+
}
|
54
|
+
template = Liquid::Template.parse File.open(TEMPLATE_FILE).read
|
55
|
+
template.render vars
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'iiif/presentation'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
# to do
|
7
|
+
module Aperitiiif
|
8
|
+
# to do
|
9
|
+
# has smell :reek:InstanceVariableAssumption
|
10
|
+
# has smell :reek:TooManyInstanceVariables
|
11
|
+
# has smell :reek:TooManyMethods
|
12
|
+
class Item
|
13
|
+
# has smell :reek:Attribute
|
14
|
+
attr_writer :record
|
15
|
+
attr_reader :id, :assets
|
16
|
+
|
17
|
+
def initialize(id, assets, config)
|
18
|
+
@id = id
|
19
|
+
@config = config
|
20
|
+
@assets = assets
|
21
|
+
|
22
|
+
validate_config
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_config
|
26
|
+
raise Aperitiiif::Error, "No value found for 'namespace'. Check your config?" if @config.namespace.empty?
|
27
|
+
raise Aperitiiif::Error, "No value found for 'presentation_api_url'. Check your config?" if @config.presentation_api_url.empty?
|
28
|
+
raise Aperitiiif::Error, "No value found for 'presentation_build_dir'. Check your config?" if @config.presentation_build_dir.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
def record
|
32
|
+
@record || default_record
|
33
|
+
end
|
34
|
+
|
35
|
+
def default_record
|
36
|
+
Record.new({ 'id' => id }, @config.records_defaults)
|
37
|
+
end
|
38
|
+
|
39
|
+
def manifest
|
40
|
+
@manifest ||= build_manifest
|
41
|
+
end
|
42
|
+
|
43
|
+
def label
|
44
|
+
record.label
|
45
|
+
end
|
46
|
+
|
47
|
+
def manifest_url
|
48
|
+
"#{@config.presentation_api_url}/#{@config.namespace}/#{@id}/manifest.json"
|
49
|
+
end
|
50
|
+
|
51
|
+
def thumbnail_url
|
52
|
+
return '' if assets.empty?
|
53
|
+
|
54
|
+
assets.first.thumbnail_url
|
55
|
+
end
|
56
|
+
|
57
|
+
def viewpoint_url
|
58
|
+
"https://dss.hosting.nyu.edu/viewpoint/mirador/#manifests[]=#{CGI.escape manifest_url}&theme=dark"
|
59
|
+
end
|
60
|
+
|
61
|
+
# has smell :reek: TooManyStatements
|
62
|
+
def seed
|
63
|
+
{
|
64
|
+
'@id' => manifest_url,
|
65
|
+
'label' => label,
|
66
|
+
'logo' => record.logo,
|
67
|
+
'description' => record.description,
|
68
|
+
'source' => record.source,
|
69
|
+
'metadata' => record.custom_metadata
|
70
|
+
}.delete_if { |_key, val| val.blank? }
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_manifest
|
74
|
+
manifest = IIIF::Presentation::Manifest.new seed
|
75
|
+
sequence = IIIF::Presentation::Sequence.new
|
76
|
+
sequence.canvases = assets.map(&:canvas)
|
77
|
+
manifest.sequences << sequence
|
78
|
+
manifest
|
79
|
+
end
|
80
|
+
|
81
|
+
def manifest_file
|
82
|
+
"#{@config.presentation_build_dir}/#{@config.namespace}/#{id}/manifest.json"
|
83
|
+
end
|
84
|
+
|
85
|
+
def manifest_written?
|
86
|
+
File.file? manifest_file
|
87
|
+
end
|
88
|
+
|
89
|
+
def write_presentation_json
|
90
|
+
Aperitiiif::Utils.mkfile_p manifest_file, manifest.to_json(pretty: true)
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_hash
|
94
|
+
{
|
95
|
+
'id' => id,
|
96
|
+
'label' => label,
|
97
|
+
'manifest_url' => manifest_url,
|
98
|
+
'thumbnail_url' => thumbnail_url,
|
99
|
+
'viewpoint_url' => viewpoint_url
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
# to do
|
6
|
+
module Aperitiiif
|
7
|
+
# to do
|
8
|
+
class Record
|
9
|
+
DELEGATE = %i[puts p].freeze
|
10
|
+
CUSTOM_METADATA_PREFIX = 'meta.'
|
11
|
+
|
12
|
+
def initialize(hash, defaults = {})
|
13
|
+
@hash = defaults.merge(hash)
|
14
|
+
@id = id
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_missing(method, *args, &block)
|
18
|
+
return super if DELEGATE.include? method
|
19
|
+
|
20
|
+
@hash.fetch method.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def respond_to_missing?(method, _args)
|
24
|
+
DELEGATE.include?(method) or super
|
25
|
+
end
|
26
|
+
|
27
|
+
def id
|
28
|
+
@hash.fetch 'id'
|
29
|
+
rescue KeyError
|
30
|
+
raise Aperitiiif::Error, "Record has no 'id'\n#{@hash.inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def label = @hash.fetch 'label', id
|
34
|
+
def logo = @hash.fetch 'logo', ''
|
35
|
+
def description = @hash.fetch 'description', ''
|
36
|
+
def source = @hash.fetch 'source', ''
|
37
|
+
|
38
|
+
def custom_metadata_keys
|
39
|
+
@hash.to_h.keys.select { |key| key.to_s.start_with?(CUSTOM_METADATA_PREFIX) } || []
|
40
|
+
end
|
41
|
+
|
42
|
+
def custom_metadata
|
43
|
+
custom_metadata_keys.map do |key|
|
44
|
+
{
|
45
|
+
'label' => key.to_s.delete_prefix(CUSTOM_METADATA_PREFIX),
|
46
|
+
'value' => @hash[key]
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang='en'>
|
3
|
+
<head>
|
4
|
+
<meta charset='UTF-8'>
|
5
|
+
<meta http-equiv='X-UA-Compatible' content='ie=edge'>
|
6
|
+
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
|
7
|
+
<title>{{ config.label }} | aperitiiif batch listing</title>
|
8
|
+
<link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css'>
|
9
|
+
<link rel='stylesheet' href='https://cdn.datatables.net/1.12.1/css/jquery.dataTables.min.css'>
|
10
|
+
</head>
|
11
|
+
<body>
|
12
|
+
<section class='hero is-info'>
|
13
|
+
<div class='hero-body'>
|
14
|
+
<div class='container'>
|
15
|
+
<h1 class='title'>{{ config.label }}</h1>
|
16
|
+
<p class='subtitle'>Aperitiiif Batch Index</p>
|
17
|
+
<p class='is-5 is-grey'>Last updated {{ formatted_date }}</p>
|
18
|
+
<p class='tags mt-5'>
|
19
|
+
<a target='_blank' class='tag is-danger is-light' href='{{ iiif_collection_url }}'>iiif collection</a>
|
20
|
+
<a target='_blank' class='tag is-link is-light' href='index.json'>index.json</a>
|
21
|
+
</p>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
</section>
|
25
|
+
<section class='section'>
|
26
|
+
<div class='container'>
|
27
|
+
<h2 class='is-size-4 mb-3'>Items ({{ items.size }})</h2>
|
28
|
+
<div class='table-container'>
|
29
|
+
<table id='table' class='table display is-hoverable is-striped is-bordered'>
|
30
|
+
<thead>
|
31
|
+
<tr>
|
32
|
+
<td>item id</td>
|
33
|
+
<td>label</td>
|
34
|
+
<td>thumbnnail</td>
|
35
|
+
<td>iiif manifest</td>
|
36
|
+
<td>view in viewpoint</td>
|
37
|
+
</tr>
|
38
|
+
</thead>
|
39
|
+
<tbody>
|
40
|
+
{% for item in items %}
|
41
|
+
<tr>
|
42
|
+
<td><b>{{ item.id }}</b></td>
|
43
|
+
<td>{{ item.label }}</td>
|
44
|
+
<td><a target='_blank' href='{{ item.thumbnail_url }}'><img style="height:100px;width:auto" class='lazy' data-original="{{ item.thumbnail_url }}"/></a></td>
|
45
|
+
<td><a target='_blank' href='{{ item.manifest_url }}'><img alt='Thumbnail {{ item.label }}' src='https://upload.wikimedia.org/wikipedia/commons/e/e8/International_Image_Interoperability_Framework_logo.png' style="width:25px"/></a></td>
|
46
|
+
<td><a target='_blank' class='is-size-7' href='{{ item.viewpoint_url }}'>{{ item.viewpoint_url }}</a></td>
|
47
|
+
</tr>
|
48
|
+
{% endfor %}
|
49
|
+
</tbody>
|
50
|
+
</table>
|
51
|
+
</table>
|
52
|
+
</div>
|
53
|
+
</section>
|
54
|
+
<script src='https://code.jquery.com/jquery-3.5.1.js'></script>
|
55
|
+
<script src='https://cdnjs.cloudflare.com/ajax/libs/jquery.lazyload/1.9.1/jquery.lazyload.min.js'></script>
|
56
|
+
<script src='https://cdn.datatables.net/1.12.1/js/jquery.dataTables.min.js'></script>
|
57
|
+
<script>
|
58
|
+
$(document).ready(function () {
|
59
|
+
$('#table').DataTable( {
|
60
|
+
drawCallback: function(){
|
61
|
+
$('img.lazy').lazyload();
|
62
|
+
}
|
63
|
+
});
|
64
|
+
});
|
65
|
+
</script>
|
66
|
+
</body>
|
67
|
+
</html>
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
|
5
|
+
# TO DO COMMENT
|
6
|
+
module Aperitiiif
|
7
|
+
# TO DO COMMENT
|
8
|
+
module Utils
|
9
|
+
def self.basename_no_ext(str) = rm_ext File.basename(str)
|
10
|
+
def self.rm_ext(str) = str.sub File.extname(str), ''
|
11
|
+
def self.parent_id(str, dir) = prune_prefix_junk(rm_ext(str.sub(dir, '')))
|
12
|
+
def self.parent_dir(str) = File.dirname(str).split('/').last
|
13
|
+
def self.prune_prefix_junk(str) = str.sub(/^(_|\W+)/, '')
|
14
|
+
def self.mime(path) = MimeMagic.by_magic(File.open(path)).to_s
|
15
|
+
def self.valid_source?(path) = File.file?(path) && path.end_with?(*ALLOWED_SRC_FORMATS)
|
16
|
+
|
17
|
+
def self.slugify(str)
|
18
|
+
str.downcase!
|
19
|
+
str.strip!
|
20
|
+
str.gsub! ' ', '-'
|
21
|
+
str.gsub!(/[^\w-]/, '')
|
22
|
+
prune_prefix_junk str
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.formatted_datetime(time = Time.now)
|
26
|
+
fmt_date = "#{time.month}/#{time.day}/#{time.year}"
|
27
|
+
fmt_time = "#{time.hour}:#{time.min.to_s.rjust(2, '0')}#{time.zone}"
|
28
|
+
"#{fmt_date} at #{fmt_time}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.item_assets_from_path(path)
|
32
|
+
return [path] if Utils.valid_source?(path)
|
33
|
+
return [] unless File.directory? path
|
34
|
+
|
35
|
+
Dir.glob("#{path}/*").select { |sub| Utils.valid_source? sub }
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.csv_records(file, defaults = {})
|
39
|
+
hashes = CSV.read(file, headers: true).map(&:to_hash)
|
40
|
+
hashes.map { |hash| Record.new hash.compact, defaults }
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.mkfile_p(path, contents)
|
44
|
+
FileUtils.mkdir_p File.dirname(path)
|
45
|
+
File.open(path, 'w') { |file| file.write contents }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/aperitiiif.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'colorize'
|
4
|
+
require 'thor'
|
5
|
+
|
6
|
+
require 'aperitiiif/asset'
|
7
|
+
require 'aperitiiif/batch'
|
8
|
+
require 'aperitiiif/cli'
|
9
|
+
require 'aperitiiif/config'
|
10
|
+
require 'aperitiiif/error'
|
11
|
+
require 'aperitiiif/index'
|
12
|
+
require 'aperitiiif/item'
|
13
|
+
require 'aperitiiif/record'
|
14
|
+
require 'aperitiiif/version'
|
15
|
+
require 'aperitiiif/utils'
|
16
|
+
|
17
|
+
# TO DO COMMENT
|
18
|
+
module Aperitiiif
|
19
|
+
ALLOWED_SRC_FORMATS = %w[jpg jpeg png tif tiff].freeze
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aperitiiif
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- mnyrop
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-09-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: iiif-presentation
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: liquid
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mimemagic
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: parallel
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: ruby-progressbar
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: ruby-vips
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: safe_yaml
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: thor
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description:
|
140
|
+
email:
|
141
|
+
- marii@nyu.edu
|
142
|
+
executables:
|
143
|
+
- aperitiiif
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- ".reek.yml"
|
148
|
+
- ".rspec"
|
149
|
+
- ".rubocop.yml"
|
150
|
+
- ".ruby-version"
|
151
|
+
- Gemfile
|
152
|
+
- README.md
|
153
|
+
- Rakefile
|
154
|
+
- bin/aperitiiif
|
155
|
+
- lib/aperitiiif.rb
|
156
|
+
- lib/aperitiiif/asset.rb
|
157
|
+
- lib/aperitiiif/batch.rb
|
158
|
+
- lib/aperitiiif/batch/assets.rb
|
159
|
+
- lib/aperitiiif/batch/items.rb
|
160
|
+
- lib/aperitiiif/batch/linters.rb
|
161
|
+
- lib/aperitiiif/batch/records.rb
|
162
|
+
- lib/aperitiiif/cli.rb
|
163
|
+
- lib/aperitiiif/config.rb
|
164
|
+
- lib/aperitiiif/error.rb
|
165
|
+
- lib/aperitiiif/index.rb
|
166
|
+
- lib/aperitiiif/item.rb
|
167
|
+
- lib/aperitiiif/record.rb
|
168
|
+
- lib/aperitiiif/template/index.html
|
169
|
+
- lib/aperitiiif/utils.rb
|
170
|
+
- lib/aperitiiif/version.rb
|
171
|
+
homepage: https://github.com/nyu-dss/aperitiiif-cli
|
172
|
+
licenses: []
|
173
|
+
metadata:
|
174
|
+
homepage_uri: https://github.com/nyu-dss/aperitiiif-cli
|
175
|
+
source_code_uri: https://github.com/nyu-dss/aperitiiif-cli
|
176
|
+
post_install_message:
|
177
|
+
rdoc_options: []
|
178
|
+
require_paths:
|
179
|
+
- lib
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
181
|
+
requirements:
|
182
|
+
- - ">="
|
183
|
+
- !ruby/object:Gem::Version
|
184
|
+
version: '3.1'
|
185
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - ">="
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
requirements: []
|
191
|
+
rubygems_version: 3.3.7
|
192
|
+
signing_key:
|
193
|
+
specification_version: 4
|
194
|
+
summary: ''
|
195
|
+
test_files: []
|