annotations2triannon 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.env_example +3 -3
- data/annotations2triannon.gemspec +1 -1
- data/bin/dms.rb +7 -60
- data/lib/annotations2triannon/annotation_list.rb +1 -1
- data/lib/annotations2triannon/annotation_tracker.rb +151 -0
- data/lib/annotations2triannon/configuration.rb +24 -1
- data/lib/annotations2triannon/iiif_collection.rb +1 -2
- data/lib/annotations2triannon/manifest.rb +1 -2
- data/lib/annotations2triannon/open_annotation.rb +54 -26
- data/lib/requires.rb +4 -3
- data/spec/lib/annotations2triannon/configuration_spec.rb +114 -2
- data/spec/lib/annotations2triannon/open_annotation_spec.rb +101 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e6da8313a7eb7e9653bd04afc165aa32c943fa9c
|
4
|
+
data.tar.gz: aa993a96c3a0d55eb4285a418dcec875c1cd93e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe085e82fa5c59527a126a00c59247eb98106117ba67367b83cad3b9b4db9dcda2182c1f8ad56dd8366c9da815d6b596dc064585d5adf992a1e5db72f1121c78
|
7
|
+
data.tar.gz: 1497a34f9cea9f48b0c583fa9d2ece23b3c16d68215e0ba728ad2caaf94c9980b2083d773fd16d436aa5b56807d12f9c2a0f61bb7466844b8166cbbb2eb833d9
|
data/.env_example
CHANGED
@@ -10,6 +10,7 @@ export DEBUG=false
|
|
10
10
|
|
11
11
|
# limit the depth of IIIF navigation; useful for development
|
12
12
|
# or testing a new data source.
|
13
|
+
export ANNO_LIMIT_RANDOM=true # use 0..limit or random sampling?
|
13
14
|
export ANNO_LIMIT_MANIFESTS=0 # 0 is all of them
|
14
15
|
export ANNO_LIMIT_ANNOLISTS=0 # 0 is all of them
|
15
16
|
export ANNO_LIMIT_OPENANNOS=0 # 0 is all of them
|
@@ -21,9 +22,8 @@ export ANNO_LOG_FILE='log/annotations2triannon.log'
|
|
21
22
|
# http://rtomayko.github.io/rack-cache/storage
|
22
23
|
export RACK_CACHE_ENABLED=true
|
23
24
|
export RACK_CACHE_VERBOSE=false
|
24
|
-
export RACK_CACHE_METASTORE='
|
25
|
-
|
26
|
-
export RACK_CACHE_ENTITYSTORE='file:/tmp/cache/anno_body'
|
25
|
+
export RACK_CACHE_METASTORE='file:tmp/cache/anno_meta'
|
26
|
+
export RACK_CACHE_ENTITYSTORE='file:tmp/cache/anno_body'
|
27
27
|
|
28
28
|
# Configure the triannon service
|
29
29
|
export TRIANNON_LOG_FILE='log/triannon_client.log'
|
data/bin/dms.rb
CHANGED
@@ -59,67 +59,14 @@ end
|
|
59
59
|
# -----------------------------------------------------------------------
|
60
60
|
# Annotation tracking using a file
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
# persist the anno_tracking data to a file
|
65
|
-
# @param anno_data [Hash]
|
66
|
-
def anno_tracking_save(anno_data)
|
67
|
-
begin
|
68
|
-
dump_json(ANNO_TRACKING_FILE, anno_data)
|
69
|
-
puts "Annotation records updated in: #{ANNO_TRACKING_FILE}"
|
70
|
-
rescue
|
71
|
-
msg = "FAILURE to save annotation tracking file #{ANNO_TRACKING_FILE}"
|
72
|
-
CONFIG.logger.error(msg)
|
73
|
-
end
|
74
|
-
end
|
62
|
+
anno_file = 'dms_annotation_tracking.json'
|
63
|
+
anno_tracker = Annotations2triannon::AnnotationTracker.new(anno_file)
|
75
64
|
|
76
|
-
# retrieve the anno_tracking data from a file
|
77
|
-
# @returns anno_data [Hash]
|
78
|
-
def anno_tracking_load
|
79
|
-
data = {}
|
80
|
-
begin
|
81
|
-
if File.exists? ANNO_TRACKING_FILE
|
82
|
-
if File.size(ANNO_TRACKING_FILE).to_i > 0
|
83
|
-
data = JSON.parse( File.read(ANNO_TRACKING_FILE) )
|
84
|
-
end
|
85
|
-
end
|
86
|
-
rescue
|
87
|
-
msg = "FAILURE to load annotation tracking file #{ANNO_TRACKING_FILE}"
|
88
|
-
CONFIG.logger.error(msg)
|
89
|
-
end
|
90
|
-
return data
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
# -----------------------------------------------------------------------
|
95
65
|
# DELETE previous annotations on triannon
|
96
|
-
|
97
66
|
puts "\nAnnotation cleanup:"
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
anno_lists.each_pair do |anno_list_uri, anno_list|
|
102
|
-
anno_list.each do |anno_data|
|
103
|
-
anno_uris << RDF::URI.new(anno_data['uri'])
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
anno_ids = anno_uris.collect {|uri| tc.annotation_id(uri) }
|
108
|
-
unless anno_ids.empty?
|
109
|
-
# Find the intersection of the DMS annotations previously
|
110
|
-
# created in triannon and the current set of annotations in triannon.
|
111
|
-
graph = tc.get_annotations
|
112
|
-
uris = tc.annotation_uris(graph)
|
113
|
-
ids = uris.collect {|uri| tc.annotation_id(uri)}
|
114
|
-
annos_to_remove = anno_ids & ids # intersection of arrays
|
115
|
-
annos_to_remove.each do |id|
|
116
|
-
success = tc.delete_annotation(id)
|
117
|
-
CONFIG.logger.error("FAILURE to delete #{id}") unless success
|
118
|
-
end
|
119
|
-
end
|
120
|
-
# Clear the record of the saved annotations
|
121
|
-
anno_tracking_save({})
|
122
|
-
|
67
|
+
anno_tracker.delete_annotations
|
68
|
+
anno_tracker.archive
|
69
|
+
anno_tracker.save({})
|
123
70
|
|
124
71
|
# -----------------------------------------------------------------------
|
125
72
|
# Loading IIIF annotations from a collection
|
@@ -194,10 +141,10 @@ text_annotations.each_pair do |m,anno_lists|
|
|
194
141
|
CONFIG.logger.error("FAILURE to POST #{oa.id}")
|
195
142
|
end
|
196
143
|
end
|
197
|
-
|
144
|
+
anno_tracker.save(anno_tracking)
|
198
145
|
end
|
199
146
|
end
|
200
|
-
|
147
|
+
anno_tracker.save(anno_tracking)
|
201
148
|
|
202
149
|
|
203
150
|
|
@@ -23,7 +23,7 @@ module Annotations2triannon
|
|
23
23
|
return @open_annotations unless @open_annotations.nil?
|
24
24
|
begin
|
25
25
|
oa_graphs = collect_open_annotations
|
26
|
-
oa_graphs =
|
26
|
+
oa_graphs = @@config.array_sampler(oa_graphs, @@config.limit_openannos)
|
27
27
|
oa_graphs.collect {|oa| Annotations2triannon::OpenAnnotation.new(oa)}
|
28
28
|
rescue => e
|
29
29
|
binding.pry if @@config.debug
|
@@ -0,0 +1,151 @@
|
|
1
|
+
|
2
|
+
module Annotations2triannon
|
3
|
+
|
4
|
+
# Annotation tracking
|
5
|
+
class AnnotationTracker
|
6
|
+
|
7
|
+
attr_accessor :anno_file
|
8
|
+
attr_accessor :config
|
9
|
+
|
10
|
+
def initialize(file_name='anno_tracking.json')
|
11
|
+
@config = Annotations2triannon.configuration
|
12
|
+
@anno_file = File.join(@config.log_path, file_name)
|
13
|
+
FileUtils.touch @anno_file
|
14
|
+
end
|
15
|
+
|
16
|
+
# Save the current tracking file to an archive file tagged by timestamp
|
17
|
+
def archive
|
18
|
+
# Date and time of day for calendar date (basic)
|
19
|
+
# %Y%m%dT%H%M%S%z => 20071119T083748-0600
|
20
|
+
time_stamp = DateTime.now.strftime('%Y%m%dT%H%M%S%z')
|
21
|
+
ext = File.extname(@anno_file)
|
22
|
+
archive = @anno_file.sub(ext, "_#{time_stamp}#{ext}")
|
23
|
+
FileUtils.copy(@anno_file, archive)
|
24
|
+
end
|
25
|
+
|
26
|
+
# retrieve the anno_tracking data from a file
|
27
|
+
# @returns data [Hash]
|
28
|
+
def load
|
29
|
+
begin
|
30
|
+
json_load(@anno_file) || {}
|
31
|
+
rescue
|
32
|
+
msg = "FAILURE to load annotation tracking file #{@anno_file}"
|
33
|
+
@config.logger.error(msg)
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Retrieve the annotation URIs from an anno_tracking data file
|
39
|
+
# Assumes the annotation tracking data is a hash with a structure:
|
40
|
+
# {
|
41
|
+
# manifest_uri: [annotation_list, annotation_list, ]
|
42
|
+
# }
|
43
|
+
# where each annotation_list is a hash with a structure:
|
44
|
+
# {
|
45
|
+
# anno_list_uri: [annotations, annotations, ]
|
46
|
+
# }
|
47
|
+
# where annotations is an array of hashes, with a structure:
|
48
|
+
# {
|
49
|
+
# uri: uri,
|
50
|
+
# chars: body_content_chars
|
51
|
+
# }
|
52
|
+
# and the uri is a triannon annotation URI.
|
53
|
+
# @returns data [Hash]
|
54
|
+
# @returns uris [Array<RDF::URI>] An array of URIs to delete from triannon
|
55
|
+
def load_uris
|
56
|
+
uris = []
|
57
|
+
data = load
|
58
|
+
data.each_pair do |manifest_uri, anno_lists|
|
59
|
+
anno_lists.each_pair do |anno_list_uri, anno_list|
|
60
|
+
anno_list.each do |anno_data|
|
61
|
+
uris << RDF::URI.new(anno_data['uri'])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
uris
|
66
|
+
end
|
67
|
+
|
68
|
+
# persist the anno_tracking data to a file
|
69
|
+
# @param data [Hash]
|
70
|
+
# @return success [Boolean]
|
71
|
+
def save(data)
|
72
|
+
begin
|
73
|
+
json_save(@anno_file, data)
|
74
|
+
puts "Annotation records updated in: #{@anno_file}"
|
75
|
+
return true
|
76
|
+
rescue
|
77
|
+
msg = "FAILURE to save annotation tracking file #{@anno_file}"
|
78
|
+
@config.logger.error(msg)
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# DELETE previous annotations loaded to triannon
|
84
|
+
# Accepts an input array of annotation URIs or finds them in the
|
85
|
+
# annotation tracking data and removes them from triannon (if they exist).
|
86
|
+
# Logs warnings or errors for annotations that do not exist or fail to DELETE.
|
87
|
+
# @parameter uris [Array<RDF::URI>] An array of URIs to delete from triannon
|
88
|
+
# @return status [Boolean]
|
89
|
+
def delete_annotations(uris=[])
|
90
|
+
raise ArgumentError, 'uris must be an Array<RDF::URI>' unless uris.instance_of? Array
|
91
|
+
tc = TriannonClient::TriannonClient.new
|
92
|
+
status = true
|
93
|
+
uris = load_uris if uris.empty?
|
94
|
+
anno_ids = uris.collect {|uri| tc.annotation_id(uri) }
|
95
|
+
# TODO: Enable intersection code below when a better set of annotations
|
96
|
+
# can be retrieved from triannon and/or Solr.
|
97
|
+
anno_ids.each do |id|
|
98
|
+
unless tc.delete_annotation(id)
|
99
|
+
@config.logger.error("FAILURE to delete #{id}")
|
100
|
+
status = false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
return status
|
104
|
+
|
105
|
+
# Find the intersection of the annotation URIs and
|
106
|
+
# the current set of annotations in triannon.
|
107
|
+
# Note: the triannon /annotations response may be a limited subset of
|
108
|
+
# annotations that does not include any of the previously submitted
|
109
|
+
# annotation IDs. If there are some annotations in the intersection,
|
110
|
+
# we have to assume that they are all present and proceed to delete
|
111
|
+
# them all.
|
112
|
+
# TODO: Use Solr to get a better list of current annotation URIs
|
113
|
+
# graph = tc.get_annotations
|
114
|
+
# uris = tc.annotation_uris(graph)
|
115
|
+
# ids = uris.collect {|uri| tc.annotation_id(uri)}
|
116
|
+
# annos_to_remove = anno_ids & ids # intersection of arrays
|
117
|
+
# if annos_to_remove.empty?
|
118
|
+
# @config.logger.warn("annotations were not found in triannon.")
|
119
|
+
# end
|
120
|
+
# if annos_to_remove.length < anno_ids.length
|
121
|
+
# @config.logger.warn("annotations are not current in triannon.")
|
122
|
+
# end
|
123
|
+
# annos_to_remove.each do |id|
|
124
|
+
# unless tc.delete_annotation(id)
|
125
|
+
# @config.logger.error("FAILURE to delete #{id}")
|
126
|
+
# status = false
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
# return status
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def json_save(filename, data)
|
136
|
+
File.open(filename,'w') do |f|
|
137
|
+
f.write(JSON.pretty_generate(data))
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def json_load(filename)
|
142
|
+
if File.exists? filename
|
143
|
+
if File.size(filename).to_i > 0
|
144
|
+
JSON.parse( File.read(filename) )
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
@@ -8,6 +8,7 @@ module Annotations2triannon
|
|
8
8
|
attr_reader :logger
|
9
9
|
|
10
10
|
attr_accessor :debug
|
11
|
+
attr_accessor :limit_random
|
11
12
|
attr_accessor :limit_manifests
|
12
13
|
attr_accessor :limit_annolists
|
13
14
|
attr_accessor :limit_openannos
|
@@ -18,7 +19,8 @@ module Annotations2triannon
|
|
18
19
|
@debug = env_boolean('DEBUG')
|
19
20
|
logger_init
|
20
21
|
|
21
|
-
# In development, enable options for
|
22
|
+
# In development, enable options for sampling the data
|
23
|
+
@limit_random = env_boolean('ANNO_LIMIT_RANDOM') # 0..limit or random sampling
|
22
24
|
@limit_manifests = ENV['ANNO_LIMIT_MANIFESTS'].to_i # 0 disables sampling
|
23
25
|
@limit_annolists = ENV['ANNO_LIMIT_ANNOLISTS'].to_i # 0 disables sampling
|
24
26
|
@limit_openannos = ENV['ANNO_LIMIT_OPENANNOS'].to_i # 0 disables sampling
|
@@ -27,6 +29,27 @@ module Annotations2triannon
|
|
27
29
|
redis_init
|
28
30
|
end
|
29
31
|
|
32
|
+
# Utility method for sampling annotation arrays, using either linear or
|
33
|
+
# random sampling of a subset of elements. The instance variable
|
34
|
+
# .limit_random is a configuration parameter that defines whether
|
35
|
+
# linear or random sampling is used.
|
36
|
+
# @param array [Array] An array to be sampled
|
37
|
+
# @param limit [Integer] The number of elements to sample
|
38
|
+
# @returns array [Array] A subset of the input array
|
39
|
+
def array_sampler(array, limit=0)
|
40
|
+
if limit > 0
|
41
|
+
if @limit_random
|
42
|
+
array.sample(limit)
|
43
|
+
else
|
44
|
+
limit = limit - 1
|
45
|
+
array[0..limit]
|
46
|
+
end
|
47
|
+
else
|
48
|
+
array
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
30
53
|
private
|
31
54
|
|
32
55
|
def env_boolean(var)
|
@@ -38,8 +38,7 @@ module Annotations2triannon
|
|
38
38
|
|
39
39
|
def manifest_uris(q)
|
40
40
|
uris = rdf.query(q).collect {|s| s.subject }
|
41
|
-
|
42
|
-
uris
|
41
|
+
@@config.array_sampler(uris, @@config.limit_manifests)
|
43
42
|
end
|
44
43
|
|
45
44
|
def query_iiif_manifests
|
@@ -76,8 +76,7 @@ module Annotations2triannon
|
|
76
76
|
|
77
77
|
def collect_annotation_list_uris(q)
|
78
78
|
uris = rdf.query(q).collect {|s| s.subject }
|
79
|
-
|
80
|
-
uris
|
79
|
+
@@config.array_sampler(uris, @@config.limit_annolists)
|
81
80
|
end
|
82
81
|
|
83
82
|
end
|
@@ -18,23 +18,24 @@ module Annotations2triannon
|
|
18
18
|
# @param id [UUID|URI|String] to identify an open annotation
|
19
19
|
def initialize(graph=RDF::Graph.new, id=nil)
|
20
20
|
@@agent ||= Annotations2triannon::AGENT
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
raise ArgumentError, 'graph must be an open annotation' unless is_annotation?
|
30
|
-
id.nil? ? @id = get_id : @id = id
|
31
|
-
end
|
21
|
+
set_graph(graph)
|
22
|
+
# The set_graph will set @graph and also set @id, using the
|
23
|
+
# graph subject with RDF.type of OA.Annotation. However, the
|
24
|
+
# id parameter for this init can override this default behavior,
|
25
|
+
# but it should be set after calling set_graph so it first has
|
26
|
+
# a chance to extract an ID from the graph. Once the @id is set,
|
27
|
+
# the set_graph method will not touch it again.
|
28
|
+
@id = parse_id(id)
|
32
29
|
end
|
33
30
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
31
|
+
# @see #parse_id
|
32
|
+
def id=(id)
|
33
|
+
@id = parse_id(id)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @see #set_graph
|
37
|
+
def graph=(graph)
|
38
|
+
set_graph(graph)
|
38
39
|
end
|
39
40
|
|
40
41
|
# @return [boolean] true if RDF.type is OA.Annotation, with OA.hasBody and OA.hasTarget
|
@@ -90,21 +91,11 @@ module Annotations2triannon
|
|
90
91
|
end
|
91
92
|
|
92
93
|
def body_graph
|
93
|
-
return @body_graph unless @body_graph.nil?
|
94
94
|
g = RDF::Graph.new
|
95
95
|
hasBody.each do |b|
|
96
96
|
@graph.query( [b, :p, :o] ).each_statement {|s| g << s}
|
97
|
-
# if b.uri?
|
98
|
-
# begin
|
99
|
-
# b_resource = Resource.new(b)
|
100
|
-
# b_resource.rdf.each_statement {|s| g << s}
|
101
|
-
# rescue
|
102
|
-
# # Nothing to be done here; the Resource#rdf method
|
103
|
-
# # will log errors in RDF retrieval
|
104
|
-
# end
|
105
|
-
# end
|
106
97
|
end
|
107
|
-
|
98
|
+
g
|
108
99
|
end
|
109
100
|
|
110
101
|
def body_contentAsText
|
@@ -268,6 +259,43 @@ module Annotations2triannon
|
|
268
259
|
@graph.dump(:ttl, standard_prefixes: true)
|
269
260
|
end
|
270
261
|
|
262
|
+
private
|
263
|
+
|
264
|
+
# Sets the annotation ID as an RDF::URI from id, but if the id is nil, it
|
265
|
+
# will try to get the ID from the graph and, as a last resort, it will
|
266
|
+
# generate a unique ID using a UUID.
|
267
|
+
# @param id [URI|UUID|String] to identify an open annotation
|
268
|
+
def parse_id(id=nil)
|
269
|
+
raise ArgumentError, 'id cannot be an empty String' if (id.instance_of?(String) && id.empty?)
|
270
|
+
if id.nil?
|
271
|
+
# Try to get the ID from the graph
|
272
|
+
q = [nil, RDF.type, OA.Annotation]
|
273
|
+
id = @graph.query(q).collect {|s| s.subject }.first
|
274
|
+
else
|
275
|
+
# Try to parse the ID as an RDF::URI
|
276
|
+
id = RDF::URI.parse(id)
|
277
|
+
end
|
278
|
+
# As a last resort, assign a UUID so @id will not be nil; an alternative
|
279
|
+
# could be to assign a blank node, using RDF::Node.new; TODO: provide a
|
280
|
+
# general configuration option to use blank nodes for OA graphs.
|
281
|
+
id ||= RDF::URI.parse(UUID.generate)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Sets the annotation graph as an RDF::Graph
|
285
|
+
# @param graph [RDF::Graph]
|
286
|
+
def set_graph(graph)
|
287
|
+
raise ArgumentError, 'graph must be RDF::Graph instance' unless graph.instance_of? RDF::Graph
|
288
|
+
@graph = graph
|
289
|
+
# The following code applies consequential rules to ensure the OA has an
|
290
|
+
# ID and that it is an OA graph. These rules should be invoked whenever
|
291
|
+
# the @graph is initialized or assigned by graph= accessor.
|
292
|
+
# Update the ID using the graph annotation URI, unless it is already set.
|
293
|
+
# The parse_id method depends on @graph to identify the graph ID.
|
294
|
+
@id ||= parse_id
|
295
|
+
# Ensure it's an open annotation; these methods depend on @id to be set.
|
296
|
+
insert_annotation unless is_annotation?
|
297
|
+
end
|
298
|
+
|
271
299
|
end
|
272
300
|
|
273
301
|
end
|
data/lib/requires.rb
CHANGED
@@ -10,7 +10,6 @@ require 'rest-client'
|
|
10
10
|
RestClient.proxy = ENV['http_proxy'] unless ENV['http_proxy'].nil?
|
11
11
|
RestClient.proxy = ENV['HTTP_PROXY'] unless ENV['HTTP_PROXY'].nil?
|
12
12
|
if ENV['RACK_CACHE_ENABLED'].to_s.upcase == 'TRUE'
|
13
|
-
require 'dalli'
|
14
13
|
require 'restclient/components'
|
15
14
|
require 'rack/cache'
|
16
15
|
# RestClient.enable Rack::CommonLogger
|
@@ -18,8 +17,9 @@ if ENV['RACK_CACHE_ENABLED'].to_s.upcase == 'TRUE'
|
|
18
17
|
# Enable the HTTP cache to store meta and entity data according
|
19
18
|
# to the env config values or the defaults given here. See
|
20
19
|
# http://rtomayko.github.io/rack-cache/configuration for available options.
|
21
|
-
metastore = ENV['RACK_CACHE_METASTORE'] || 'file
|
22
|
-
entitystore = ENV['RACK_CACHE_ENTITYSTORE'] || 'file
|
20
|
+
metastore = ENV['RACK_CACHE_METASTORE'] || 'file:tmp/cache/meta'
|
21
|
+
entitystore = ENV['RACK_CACHE_ENTITYSTORE'] || 'file:tmp/cache/body'
|
22
|
+
require 'dalli' if ((metastore =~ /memcache/) || (entitystore =~ /memcache/))
|
23
23
|
verbose = ENV['RACK_CACHE_VERBOSE'].to_s.upcase == 'TRUE' || false
|
24
24
|
RestClient.enable Rack::Cache,
|
25
25
|
:metastore => metastore, :entitystore => entitystore, :verbose => verbose
|
@@ -52,6 +52,7 @@ require_relative 'annotations2triannon/configuration'
|
|
52
52
|
require_relative 'annotations2triannon/resource'
|
53
53
|
require_relative 'annotations2triannon/manifest'
|
54
54
|
require_relative 'annotations2triannon/annotation_list'
|
55
|
+
require_relative 'annotations2triannon/annotation_tracker'
|
55
56
|
require_relative 'annotations2triannon/iiif_collection'
|
56
57
|
require_relative 'annotations2triannon/iiif_manifest'
|
57
58
|
require_relative 'annotations2triannon/iiif_annotation_list'
|
@@ -8,17 +8,129 @@ module Annotations2triannon
|
|
8
8
|
it 'default value is false' do
|
9
9
|
ENV['DEBUG'] = nil
|
10
10
|
config = Configuration.new
|
11
|
-
expect(config.debug).to
|
11
|
+
expect(config.debug).to be false
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
15
14
|
describe '#debug=' do
|
16
15
|
it 'can set value' do
|
16
|
+
ENV['DEBUG'] = nil
|
17
17
|
config = Configuration.new
|
18
|
+
expect(config.debug).to be false
|
18
19
|
config.debug = true
|
19
20
|
expect(config.debug).to be_truthy
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
24
|
+
describe '#limit_random' do
|
25
|
+
it 'default value is false' do
|
26
|
+
ENV['ANNO_LIMIT_RANDOM'] = nil
|
27
|
+
config = Configuration.new
|
28
|
+
expect(config.limit_random).to be false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
describe '#limit_random=' do
|
32
|
+
it 'can set value' do
|
33
|
+
ENV['ANNO_LIMIT_RANDOM'] = nil
|
34
|
+
config = Configuration.new
|
35
|
+
expect(config.limit_random).to be false
|
36
|
+
config.limit_random = true
|
37
|
+
expect(config.limit_random).to be true
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#limit_manifests' do
|
42
|
+
it 'default value is zero' do
|
43
|
+
ENV['ANNO_LIMIT_MANIFESTS'] = nil
|
44
|
+
config = Configuration.new
|
45
|
+
expect(config.limit_manifests).to eql(0)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
describe '#limit_manifests=' do
|
49
|
+
it 'can set value' do
|
50
|
+
ENV['ANNO_LIMIT_MANIFESTS'] = nil
|
51
|
+
config = Configuration.new
|
52
|
+
expect(config.limit_manifests).to eql(0)
|
53
|
+
config.limit_manifests = 10
|
54
|
+
expect(config.limit_manifests).to eql(10)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#limit_annolists' do
|
59
|
+
it 'default value is zero' do
|
60
|
+
ENV['ANNO_LIMIT_ANNOLISTS'] = nil
|
61
|
+
config = Configuration.new
|
62
|
+
expect(config.limit_annolists).to eql(0)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
describe '#limit_annolists=' do
|
66
|
+
it 'can set value' do
|
67
|
+
ENV['ANNO_LIMIT_ANNOLISTS'] = nil
|
68
|
+
config = Configuration.new
|
69
|
+
expect(config.limit_annolists).to eql(0)
|
70
|
+
config.limit_annolists = 10
|
71
|
+
expect(config.limit_annolists).to eql(10)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#limit_openannos' do
|
76
|
+
it 'default value is zero' do
|
77
|
+
ENV['ANNO_LIMIT_OPENANNOS'] = nil
|
78
|
+
config = Configuration.new
|
79
|
+
expect(config.limit_openannos).to eql(0)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
describe '#limit_openannos=' do
|
83
|
+
it 'can set value' do
|
84
|
+
ENV['ANNO_LIMIT_OPENANNOS'] = nil
|
85
|
+
config = Configuration.new
|
86
|
+
expect(config.limit_openannos).to eql(0)
|
87
|
+
config.limit_openannos = 10
|
88
|
+
expect(config.limit_openannos).to eql(10)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#array_sampler' do
|
93
|
+
it 'returns input array when limit=0, regardless of sampling method' do
|
94
|
+
config = Configuration.new
|
95
|
+
array = [*0..10]
|
96
|
+
limit = 0
|
97
|
+
config.limit_random = false
|
98
|
+
expect(config.array_sampler(array,limit)).to eql(array)
|
99
|
+
config.limit_random = true
|
100
|
+
expect(config.array_sampler(array,limit)).to eql(array)
|
101
|
+
end
|
102
|
+
it 'returns array subset when limit>0, regardless of sampling method' do
|
103
|
+
config = Configuration.new
|
104
|
+
array = [*0..10]
|
105
|
+
limit = 5
|
106
|
+
config.limit_random = false
|
107
|
+
samples = config.array_sampler(array,limit)
|
108
|
+
expect(samples.length).to eql(limit)
|
109
|
+
expect(samples.length < array.length).to be true
|
110
|
+
config.limit_random = true
|
111
|
+
samples = config.array_sampler(array,limit)
|
112
|
+
expect(samples.length).to eql(limit)
|
113
|
+
expect(samples.length < array.length).to be true
|
114
|
+
end
|
115
|
+
it 'returns array[0..(limit-1)] when limit>0, without random sampling' do
|
116
|
+
config = Configuration.new
|
117
|
+
array = [*0..10]
|
118
|
+
limit = 5
|
119
|
+
config.limit_random = false
|
120
|
+
samples = config.array_sampler(array,limit)
|
121
|
+
expect(samples.length).to eql(limit)
|
122
|
+
expect(samples).to eql(array[0..(limit-1)])
|
123
|
+
end
|
124
|
+
it 'returns random array subset when limit>0, with random sampling' do
|
125
|
+
config = Configuration.new
|
126
|
+
array = [*0..100]
|
127
|
+
limit = 5
|
128
|
+
config.limit_random = true
|
129
|
+
samples = config.array_sampler(array,limit)
|
130
|
+
expect(samples.length).to eql(limit)
|
131
|
+
expect(samples).not_to eql(array[0..(limit-1)])
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
23
135
|
end
|
24
136
|
end
|
@@ -35,6 +35,107 @@ describe Annotations2triannon::OpenAnnotation do
|
|
35
35
|
}' )
|
36
36
|
}
|
37
37
|
|
38
|
+
|
39
|
+
describe 'has constants:' do
|
40
|
+
it 'CONTENT vocabulary for http://www.w3.org/2011/content#' do
|
41
|
+
const = Annotations2triannon::OpenAnnotation::CONTENT
|
42
|
+
expect(const).to eql RDF::Vocab::CNT
|
43
|
+
end
|
44
|
+
it 'OA vocabulary for http://www.w3.org/ns/oa#' do
|
45
|
+
const = Annotations2triannon::OpenAnnotation::OA
|
46
|
+
expect(const).to eql RDF::Vocab::OA
|
47
|
+
end
|
48
|
+
it 'IIIF_CONTEXT for http://iiif.io/api/presentation/2/context.json' do
|
49
|
+
const = Annotations2triannon::OpenAnnotation::IIIF_CONTEXT
|
50
|
+
expect(const).to be_instance_of String
|
51
|
+
expect(const).to include('http://iiif.io/api/presentation/2/context.json')
|
52
|
+
end
|
53
|
+
it 'OA_CONTEXT for http://www.w3.org/ns/oa.jsonld' do
|
54
|
+
const = Annotations2triannon::OpenAnnotation::OA_CONTEXT
|
55
|
+
expect(const).to be_instance_of String
|
56
|
+
expect(const).to include('http://www.w3.org/ns/oa.jsonld')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context '#id' do
|
61
|
+
it 'returns an RDF::URI' do
|
62
|
+
expect(g1.id).to be_a RDF::URI
|
63
|
+
expect(g2.id).to be_a RDF::URI
|
64
|
+
expect(g3.id).to be_a RDF::URI
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context '#new' do
|
69
|
+
let(:g) { RDF::Graph.new }
|
70
|
+
context 'for default init' do
|
71
|
+
it 'sets the #id as an RDF::URI of a UUID' do
|
72
|
+
oa = Annotations2triannon::OpenAnnotation.new
|
73
|
+
expect(oa.id).to be_a RDF::URI
|
74
|
+
expect(UUID.validate(oa.id)).to be true
|
75
|
+
end
|
76
|
+
it 'sets the #graph as an RDF::Graph' do
|
77
|
+
oa = Annotations2triannon::OpenAnnotation.new
|
78
|
+
expect(oa.graph).to be_a RDF::Graph
|
79
|
+
end
|
80
|
+
it 'the #graph is an open annotation' do
|
81
|
+
oa = Annotations2triannon::OpenAnnotation.new
|
82
|
+
expect(oa.is_annotation?).to be true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
context 'for init with an RDF::Graph' do
|
86
|
+
it 'accepts an empty RDF::Graph' do
|
87
|
+
oa = Annotations2triannon::OpenAnnotation.new(g)
|
88
|
+
expect(oa.graph).to be_a RDF::Graph
|
89
|
+
end
|
90
|
+
it 'ensures an empty RDF::Graph is an open annotation' do
|
91
|
+
oa = Annotations2triannon::OpenAnnotation.new(g)
|
92
|
+
expect(oa.is_annotation?).to be true
|
93
|
+
end
|
94
|
+
it 'ensures a graph with an RDF.type is also an open annotation' do
|
95
|
+
id = RDF::URI.parse(UUID.generate)
|
96
|
+
g << [id, RDF.type, RDF::Vocab::SKOS.Concept]
|
97
|
+
oa = Annotations2triannon::OpenAnnotation.new(g)
|
98
|
+
expect(oa.is_annotation?).to be true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
context 'for init with an ID' do
|
102
|
+
it 'accepts a nil ID and sets the #id as an RDF::URI of a UUID' do
|
103
|
+
oa = Annotations2triannon::OpenAnnotation.new(g, nil)
|
104
|
+
expect(oa.id).to be_a RDF::URI
|
105
|
+
expect(UUID.validate(oa.id)).to be true
|
106
|
+
end
|
107
|
+
it 'accepts a String ID and parses it as an RDF::URI' do
|
108
|
+
id = 'abc'
|
109
|
+
oa = Annotations2triannon::OpenAnnotation.new(g, id)
|
110
|
+
expect(oa.id).to be_a RDF::URI
|
111
|
+
expect(oa.id).to eql RDF::URI.parse(id)
|
112
|
+
end
|
113
|
+
it 'accepts an RDF::URI and parses it as an RDF::URI' do
|
114
|
+
id = RDF::URI.parse('abc')
|
115
|
+
oa = Annotations2triannon::OpenAnnotation.new(g, id)
|
116
|
+
expect(oa.id).to be_a RDF::URI
|
117
|
+
expect(oa.id).to eql id
|
118
|
+
end
|
119
|
+
it 'accepts a UUID and parses it as an RDF::URI' do
|
120
|
+
id = UUID.generate
|
121
|
+
oa = Annotations2triannon::OpenAnnotation.new(g, id)
|
122
|
+
expect(oa.id).to be_a RDF::URI
|
123
|
+
expect(oa.id).to eql RDF::URI.parse(id)
|
124
|
+
end
|
125
|
+
it 'does NOT accept an empty String ID' do
|
126
|
+
expect{Annotations2triannon::OpenAnnotation.new(g, '')}.to raise_error
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context '#graph' do
|
132
|
+
it 'returns an RDF::Graph' do
|
133
|
+
expect(g1.graph).to be_a RDF::Graph
|
134
|
+
expect(g2.graph).to be_a RDF::Graph
|
135
|
+
expect(g3.graph).to be_a RDF::Graph
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
38
139
|
context '#annotatedBy' do
|
39
140
|
it 'returns an array' do
|
40
141
|
expect(g1.annotatedBy).to be_a Array
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: annotations2triannon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darren Weber
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -329,6 +329,7 @@ files:
|
|
329
329
|
- bin/revs_annotations2csv.sh
|
330
330
|
- lib/annotations2triannon.rb
|
331
331
|
- lib/annotations2triannon/annotation_list.rb
|
332
|
+
- lib/annotations2triannon/annotation_tracker.rb
|
332
333
|
- lib/annotations2triannon/configuration.rb
|
333
334
|
- lib/annotations2triannon/iiif_annotation_list.rb
|
334
335
|
- lib/annotations2triannon/iiif_collection.rb
|