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 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