annotations2triannon 0.3.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf8a8db6a0fce3d99033b5a6f5c06c48debc16ce
4
- data.tar.gz: e76a1f81fc653fad3e592fe7696a490aa288ddf0
3
+ metadata.gz: e6da8313a7eb7e9653bd04afc165aa32c943fa9c
4
+ data.tar.gz: aa993a96c3a0d55eb4285a418dcec875c1cd93e2
5
5
  SHA512:
6
- metadata.gz: d4f8be59545e4488c5afbad2110d607780220a605979f07e50a3665a9aeeacee70b1d97a2e27afd6e1f4c59c920b7df8657f86e39703a93d1b30a769ca52236b
7
- data.tar.gz: c9cb96aa7143d99a650fe8e0d3fd8e93ba74b982807c349dbeea599eb6a2cfa03dd9fbce2838efce347217234bf7b8eb9c793e8dda2e140b905f94c73152810f
6
+ metadata.gz: fe085e82fa5c59527a126a00c59247eb98106117ba67367b83cad3b9b4db9dcda2182c1f8ad56dd8366c9da815d6b596dc064585d5adf992a1e5db72f1121c78
7
+ data.tar.gz: 1497a34f9cea9f48b0c583fa9d2ece23b3c16d68215e0ba728ad2caaf94c9980b2083d773fd16d436aa5b56807d12f9c2a0f61bb7466844b8166cbbb2eb833d9
@@ -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='memcached://localhost:11211/anno_meta'
25
- #export RACK_CACHE_METASTORE='file:/tmp/cache/anno_meta'
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'
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'annotations2triannon'
5
- s.version = '0.3.0'
5
+ s.version = '0.4.0'
6
6
  s.licenses = ['Apache-2.0']
7
7
  s.platform = Gem::Platform::RUBY
8
8
 
data/bin/dms.rb CHANGED
@@ -59,67 +59,14 @@ end
59
59
  # -----------------------------------------------------------------------
60
60
  # Annotation tracking using a file
61
61
 
62
- ANNO_TRACKING_FILE = File.join(CONFIG.log_path, 'dms_annotation_tracking.json')
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
- anno_tracking = anno_tracking_load
99
- anno_uris = []
100
- anno_tracking.each_pair do |manifest_uri, anno_lists|
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
- anno_tracking_save(anno_tracking)
144
+ anno_tracker.save(anno_tracking)
198
145
  end
199
146
  end
200
- anno_tracking_save(anno_tracking)
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 = oa_graphs.sample(@@config.limit_openannos) if @@config.limit_openannos > 0
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 random sampling the data
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
- uris = uris.sample(@@config.limit_manifests) if @@config.limit_manifests > 0
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
- uris = uris.sample(@@config.limit_annolists) if @@config.limit_annolists > 0
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
- raise ArgumentError, 'graph must be RDF::Graph instance' unless graph.instance_of? RDF::Graph
22
- if graph.empty?
23
- # create a new open annotation
24
- @graph = graph
25
- id.nil? ? @id = get_id : @id = RDF::URI.parse(id)
26
- insert_annotation
27
- else
28
- @graph = graph
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
- def get_id
35
- return @id unless @id.nil?
36
- q = [nil, RDF.type, OA.Annotation]
37
- @id = @graph.query(q).collect {|s| s.subject }.first || RDF::URI.parse(UUID.generate)
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
- @body_graph = g
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
@@ -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:/tmp/cache/meta'
22
- entitystore = ENV['RACK_CACHE_ENTITYSTORE'] || 'file:/tmp/cache/body'
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 be_falsey
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.3.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-15 00:00:00.000000000 Z
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