ro-bundle 0.1.0 → 0.2.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.
@@ -1,5 +1,5 @@
1
1
  #------------------------------------------------------------------------------
2
- # Copyright (c) 2014 The University of Manchester, UK.
2
+ # Copyright (c) 2014, 2015 The University of Manchester, UK.
3
3
  #
4
4
  # BSD Licenced. See LICENCE.rdoc for details.
5
5
  #
@@ -10,8 +10,7 @@
10
10
  module ROBundle
11
11
 
12
12
  # A class to represent an Annotation in a Research Object.
13
- class Annotation
14
- include Provenance
13
+ class Annotation < ManifestEntry
15
14
 
16
15
  # :call-seq:
17
16
  # new(target, content = nil)
@@ -24,25 +23,67 @@ module ROBundle
24
23
  # An annotation id is a UUID prefixed with "urn:uuid" as per
25
24
  # {RFC4122}[http://www.ietf.org/rfc/rfc4122.txt].
26
25
  def initialize(object, content = nil)
26
+ super()
27
+
27
28
  if object.instance_of?(Hash)
28
29
  @structure = object
30
+ @structure[:about] = [*@structure[:about]]
29
31
  init_provenance_defaults(@structure)
30
32
  else
31
33
  @structure = {}
32
- @structure[:about] = object
33
- @structure[:annotation] = UUID.generate(:urn)
34
+ @structure[:about] = [*object]
35
+ @structure[:uri] = UUID.generate(:urn)
34
36
  @structure[:content] = content
35
37
  end
36
38
  end
37
39
 
38
40
  # :call-seq:
39
- # target
41
+ # annotates?(target) -> true or false
42
+ #
43
+ # Does this annotation object annotate the supplied target?
44
+ def annotates?(target)
45
+ @structure[:about].include?(target)
46
+ end
47
+
48
+ # :call-seq:
49
+ # target -> String or Array
50
+ #
51
+ # The identifier(s) for the annotated resource. This is considered the
52
+ # target of the annotation, that is the resource (or resources) the
53
+ # annotation content is "somewhat about".
40
54
  #
41
- # The identifier for the annotated resource. This is considered the target
42
- # of the annotation, that is the resource the annotation content is
43
- # "somewhat about".
55
+ # The target can either be a singleton or a list of targets.
44
56
  def target
45
- @structure[:about]
57
+ about = @structure[:about]
58
+ about.length == 1 ? about[0] : about.dup
59
+ end
60
+
61
+ # :call-seq:
62
+ # add_target(new_target, ...) -> added target(s)
63
+ #
64
+ # Add a new target, or targets, to this annotation.
65
+ #
66
+ # The target(s) added are returned.
67
+ def add_target(add)
68
+ @structure[:about] += [*add]
69
+
70
+ @edited = true
71
+ add
72
+ end
73
+
74
+ # :call-seq:
75
+ # remove_target(target) -> target or nil
76
+ #
77
+ # Remove a target from this annotation. An annotation must always have a
78
+ # target so this method will do nothing if it already has only one target.
79
+ #
80
+ # If the target can be removed then it is returned, otherwise nil is
81
+ # returned.
82
+ def remove_target(remove)
83
+ return if @structure[:about].length == 1
84
+
85
+ @edited = true
86
+ @structure[:about].delete(remove)
46
87
  end
47
88
 
48
89
  # :call-seq:
@@ -58,15 +99,16 @@ module ROBundle
58
99
  #
59
100
  # Set the content of this annotation.
60
101
  def content=(new_content)
102
+ @edited = true
61
103
  @structure[:content] = new_content
62
104
  end
63
105
 
64
106
  # :call-seq:
65
- # annotation_id -> String
107
+ # uri -> String in the form of a urn:uuid URI.
66
108
  #
67
109
  # Return the annotation id of this Annotation.
68
- def annotation_id
69
- @structure[:annotation]
110
+ def uri
111
+ @structure[:uri]
70
112
  end
71
113
 
72
114
  # :call-seq:
@@ -75,13 +117,9 @@ module ROBundle
75
117
  # Write this Annotation out as a json string. Takes the same options as
76
118
  # JSON#generate.
77
119
  def to_json(*a)
78
- Util.clean_json(@structure).to_json(*a)
79
- end
80
-
81
- private
82
-
83
- def structure
84
- @structure
120
+ cleaned = Util.clean_json(@structure)
121
+ cleaned[:about] = target
122
+ JSON.generate(cleaned,*a)
85
123
  end
86
124
 
87
125
  end
@@ -0,0 +1,52 @@
1
+ #------------------------------------------------------------------------------
2
+ # Copyright (c) 2014 The University of Manchester, UK.
3
+ #
4
+ # BSD Licenced. See LICENCE.rdoc for details.
5
+ #
6
+ # Author: Robert Haines
7
+ #------------------------------------------------------------------------------
8
+
9
+ #
10
+ module ROBundle
11
+
12
+ # This is the bass class of things which can appear in the manifest:
13
+ # * Aggregate
14
+ # * Annotation
15
+ # * Proxy
16
+ class ManifestEntry
17
+ include Provenance
18
+
19
+ # :call-seq:
20
+ # new
21
+ #
22
+ # Create a new ManifestEntry with an empty structure and the +edited+ flag
23
+ # set to false.
24
+ def initialize
25
+ @structure = {}
26
+ @edited = false
27
+ end
28
+
29
+ # :call-seq:
30
+ # edited? -> true or false
31
+ #
32
+ # Has this ManifestEntry been edited?
33
+ def edited?
34
+ @edited
35
+ end
36
+
37
+ # :stopdoc:
38
+ # For internal use only!
39
+ def stored
40
+ @edited = false
41
+ end
42
+ # :startdoc:
43
+
44
+ private
45
+
46
+ def structure
47
+ @structure
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -24,9 +24,19 @@ module ROBundle
24
24
  def initialize
25
25
  super(FILE_NAME, :required => true)
26
26
 
27
+ @structure = nil
28
+ @initialized = false
27
29
  @edited = false
28
30
  end
29
31
 
32
+ # :call-seq:
33
+ # initialized? -> true or false
34
+ #
35
+ # Has this manifest been initialized?
36
+ def initialized?
37
+ @initialized
38
+ end
39
+
30
40
  # :call-seq:
31
41
  # context -> List of context URIs
32
42
  #
@@ -93,7 +103,7 @@ module ROBundle
93
103
  #
94
104
  # Return a list of all the aggregated resources in this Research Object.
95
105
  def aggregates
96
- structure[:aggregates].dup
106
+ structure[:aggregates]
97
107
  end
98
108
 
99
109
  # :call-seq:
@@ -134,12 +144,9 @@ module ROBundle
134
144
 
135
145
  if object.is_a?(Aggregate)
136
146
  removed = structure[:aggregates].delete(object)
137
-
138
- unless removed.nil?
139
- removed = removed.file.nil? ? removed.uri : removed.file
140
- end
147
+ removed = removed.uri unless removed.nil?
141
148
  else
142
- removed = remove_aggregate_by_file_or_uri(object)
149
+ removed = remove_aggregate_by_uri(object)
143
150
  end
144
151
 
145
152
  unless removed.nil?
@@ -165,7 +172,7 @@ module ROBundle
165
172
  # If the supplied Annotation object is already registered then it is
166
173
  # the annotation itself we are annotating!
167
174
  if container.annotation?(object)
168
- object = Annotation.new(object.annotation_id, content)
175
+ object = Annotation.new(object.uri, content)
169
176
  end
170
177
  else
171
178
  object = Annotation.new(object, content)
@@ -200,7 +207,7 @@ module ROBundle
200
207
  end
201
208
 
202
209
  removed.each do |ann|
203
- id = ann.annotation_id
210
+ id = ann.uri
204
211
  remove_annotation(id) unless id.nil?
205
212
  end
206
213
 
@@ -212,7 +219,7 @@ module ROBundle
212
219
  #
213
220
  # Return a list of all the annotations in this Research Object.
214
221
  def annotations
215
- structure[:annotations].dup
222
+ structure[:annotations]
216
223
  end
217
224
 
218
225
  # :call-seq:
@@ -220,7 +227,11 @@ module ROBundle
220
227
  #
221
228
  # Has this manifest been altered in any way?
222
229
  def edited?
223
- @edited
230
+ if @structure.nil?
231
+ @edited
232
+ else
233
+ @edited || edited(aggregates) || edited(annotations)
234
+ end
224
235
  end
225
236
 
226
237
  # :call-seq:
@@ -229,8 +240,39 @@ module ROBundle
229
240
  # Write this Manifest out as a json string. Takes the same options as
230
241
  # JSON#generate.
231
242
  def to_json(*a)
232
- Util.clean_json(structure).to_json(*a)
243
+ JSON.generate(Util.clean_json(structure),*a)
244
+ end
245
+
246
+ # :call-seq:
247
+ # write
248
+ #
249
+ # Write this manifest into the RO Bundle, overwriting the old version.
250
+ def write
251
+ container.file.open(full_name, "w") do |m|
252
+ m.puts JSON.pretty_generate(self)
253
+ end
254
+
255
+ stored
256
+ end
257
+
258
+ # :stopdoc:
259
+ # For internal use only!
260
+ def init(struct = {})
261
+ init_default_context(struct)
262
+ init_default_id(struct)
263
+ init_provenance_defaults(struct)
264
+ struct[:history] = [*struct.fetch(:history, [])]
265
+ struct[:aggregates] = [*struct.fetch(:aggregates, [])].map do |agg|
266
+ Aggregate.new(agg)
267
+ end
268
+ struct[:annotations] = [*struct.fetch(:annotations, [])].map do |ann|
269
+ Annotation.new(ann)
270
+ end
271
+
272
+ @initialized = true
273
+ @structure = struct
233
274
  end
275
+ # :startdoc:
234
276
 
235
277
  protected
236
278
 
@@ -250,12 +292,17 @@ module ROBundle
250
292
 
251
293
  private
252
294
 
295
+ def stored
296
+ @edited = false
297
+ (aggregates + annotations).each { |a| a.stored }
298
+ end
299
+
253
300
  # The internal structure of this class cannot be setup at construction
254
301
  # time in the initializer as there is no route to its data on disk at that
255
302
  # point. Once loaded, parts of the structure are converted to local
256
303
  # objects where appropriate.
257
304
  def structure
258
- return @structure if @structure
305
+ return @structure if initialized?
259
306
 
260
307
  begin
261
308
  struct ||= JSON.parse(contents, :symbolize_names => true)
@@ -263,22 +310,7 @@ module ROBundle
263
310
  struct = {}
264
311
  end
265
312
 
266
- @structure = init_defaults(struct)
267
- end
268
-
269
- def init_defaults(struct)
270
- init_default_context(struct)
271
- init_default_id(struct)
272
- init_provenance_defaults(struct)
273
- struct[:history] = [*struct.fetch(:history, [])]
274
- struct[:aggregates] = [*struct.fetch(:aggregates, [])].map do |agg|
275
- Aggregate.new(agg)
276
- end
277
- struct[:annotations] = [*struct.fetch(:annotations, [])].map do |ann|
278
- Annotation.new(ann)
279
- end
280
-
281
- struct
313
+ init(struct)
282
314
  end
283
315
 
284
316
  def init_default_context(struct)
@@ -303,14 +335,10 @@ module ROBundle
303
335
  struct
304
336
  end
305
337
 
306
- def remove_aggregate_by_file_or_uri(object)
307
- aggregates.each do |agg|
308
- if Util.is_absolute_uri?(object)
309
- return structure[:aggregates].delete(agg).uri if object == agg.uri
310
- else
311
- if object == agg.file || object == agg.file_entry
312
- return structure[:aggregates].delete(agg).file
313
- end
338
+ def remove_aggregate_by_uri(object)
339
+ structure[:aggregates].each do |agg|
340
+ if object == agg.uri || object == agg.file_entry
341
+ return structure[:aggregates].delete(agg).uri
314
342
  end
315
343
  end
316
344
 
@@ -321,8 +349,10 @@ module ROBundle
321
349
  def remove_annotation_by_field(object)
322
350
  removed = []
323
351
 
324
- annotations.each do |ann|
325
- if ann.annotation_id == object ||
352
+ # Need to dup the list here so we don't break it when deleting things.
353
+ # We can't use delete_if because we want to know what we've deleted!
354
+ structure[:annotations].dup.each do |ann|
355
+ if ann.uri == object ||
326
356
  ann.target == object ||
327
357
  ann.content == object
328
358
 
@@ -333,6 +363,14 @@ module ROBundle
333
363
  removed
334
364
  end
335
365
 
366
+ def edited(resource)
367
+ resource.each do |res|
368
+ return true if res.edited?
369
+ end
370
+
371
+ false
372
+ end
373
+
336
374
  end
337
375
 
338
376
  end
@@ -10,8 +10,7 @@
10
10
  module ROBundle
11
11
 
12
12
  # This module is a mixin for Research Object
13
- # {provenance}[http://wf4ever.github.io/ro/bundle/draft/#provenance]
14
- # information.
13
+ # {provenance}[https://w3id.org/bundle/#provenance] information.
15
14
  #
16
15
  # To use this module simply provide an (optionally private) method named
17
16
  # 'structure' which returns the internal fields of the object as a Hash.
@@ -21,16 +20,19 @@ module ROBundle
21
20
  # * <tt>:authoredOn</tt>
22
21
  # * <tt>:createdBy</tt>
23
22
  # * <tt>:createdOn</tt>
23
+ # * <tt>:retrievedBy</tt>
24
+ # * <tt>:retrievedFrom</tt>
25
+ # * <tt>:retrievedOn</tt>
24
26
  module Provenance
25
27
 
26
28
  # :call-seq:
27
29
  # add_author(author) -> Agent
28
30
  #
29
- # Add an author to the list of authors for this Research Object. The
31
+ # Add an author to the list of authors for this resource. The
30
32
  # supplied parameter can either be an Agent or the name of an author as a
31
33
  # String.
32
34
  #
33
- # The Agent object added to the Research Object is returned.
35
+ # The Agent object that is added is returned.
34
36
  def add_author(author)
35
37
  unless author.is_a?(Agent)
36
38
  author = Agent.new(author.to_s)
@@ -44,7 +46,7 @@ module ROBundle
44
46
  # :call-seq:
45
47
  # authored_by -> Agents
46
48
  #
47
- # Return the list of Agents that authored this Research Object.
49
+ # Return the list of Agents that authored this resource.
48
50
  def authored_by
49
51
  structure.fetch(:authoredBy, []).dup
50
52
  end
@@ -52,7 +54,7 @@ module ROBundle
52
54
  # :call-seq:
53
55
  # authored_on -> Time
54
56
  #
55
- # Return the time that this RO Bundle was edited as a Time object, or
57
+ # Return the time that this resource was edited as a Time object, or
56
58
  # +nil+ if not present in the manifest.
57
59
  def authored_on
58
60
  Util.parse_time(structure[:authoredOn])
@@ -61,7 +63,7 @@ module ROBundle
61
63
  # :call-seq:
62
64
  # authored_on = new_time
63
65
  #
64
- # Set a new authoredOn time for this Manifest. Anything that Ruby can
66
+ # Set a new authoredOn time for this resource. Anything that Ruby can
65
67
  # interpret as a time is accepted and converted to ISO8601 format on
66
68
  # serialization.
67
69
  def authored_on=(new_time)
@@ -72,7 +74,7 @@ module ROBundle
72
74
  # :call-seq:
73
75
  # created_by -> Agent
74
76
  #
75
- # Return the Agent that created this Research Object.
77
+ # Return the Agent that created this resource.
76
78
  def created_by
77
79
  structure[:createdBy]
78
80
  end
@@ -80,7 +82,7 @@ module ROBundle
80
82
  # :call-seq:
81
83
  # created_by = new_creator
82
84
  #
83
- # Set the Agent that has created this RO Bundle. Anything passed to this
85
+ # Set the Agent that has created this resource. Anything passed to this
84
86
  # method that is not an Agent will be converted to an Agent before setting
85
87
  # the value.
86
88
  def created_by=(new_creator)
@@ -95,7 +97,7 @@ module ROBundle
95
97
  # :call-seq:
96
98
  # created_on -> Time
97
99
  #
98
- # Return the time that this RO Bundle was created as a Time object, or
100
+ # Return the time that this resource was created as a Time object, or
99
101
  # +nil+ if not present in the manifest.
100
102
  def created_on
101
103
  Util.parse_time(structure[:createdOn])
@@ -104,7 +106,7 @@ module ROBundle
104
106
  # :call-seq:
105
107
  # created_on = new_time
106
108
  #
107
- # Set a new createdOn time for this Manifest. Anything that Ruby can
109
+ # Set a new createdOn time for this resource. Anything that Ruby can
108
110
  # interpret as a time is accepted and converted to ISO8601 format on
109
111
  # serialization.
110
112
  def created_on=(new_time)
@@ -128,6 +130,69 @@ module ROBundle
128
130
  end
129
131
  end
130
132
 
133
+ # :call-seq:
134
+ # retrieved_by -> Agent
135
+ #
136
+ # Return the Agent that retrieved this resource.
137
+ def retrieved_by
138
+ structure[:retrievedBy]
139
+ end
140
+
141
+ # :call-seq:
142
+ # retrieved_by = new_retrievor
143
+ #
144
+ # Set the Agent that has retrieved this resource. Anything passed to this
145
+ # method that is not an Agent will be converted to an Agent before setting
146
+ # the value.
147
+ def retrieved_by=(new_retrievor)
148
+ unless new_retrievor.instance_of?(Agent)
149
+ new_retrievor = Agent.new(new_retrievor.to_s)
150
+ end
151
+
152
+ @edited = true
153
+ structure[:retrievedBy] = new_retrievor
154
+ end
155
+
156
+ # :call-seq:
157
+ # retrieved_from -> String URI
158
+ #
159
+ # Return the URI from which this resource was retrieved.
160
+ def retrieved_from
161
+ structure[:retrievedFrom]
162
+ end
163
+
164
+ # :call-seq:
165
+ # retrieved_from = uri
166
+ #
167
+ # Set the URI from which this resource was retrieved. If a URI object is
168
+ # given it is converted to a String first.
169
+ def retrieved_from=(uri)
170
+ return unless Util.is_absolute_uri?(uri)
171
+
172
+ @edited = true
173
+ structure[:retrievedFrom] = uri.to_s
174
+ end
175
+
176
+ # :call-seq:
177
+ # retrieved_on -> Time
178
+ #
179
+ # Return the time that this resource was retrieved as a Time object, or
180
+ # +nil+ if not present in the manifest.
181
+ def retrieved_on
182
+ Util.parse_time(structure[:retrievedOn])
183
+ end
184
+
185
+ # :call-seq:
186
+ # retrieved_on = new_time
187
+ #
188
+ # Set a new retrievedOn time for this resource. Anything that Ruby can
189
+ # interpret as a time is accepted and converted to ISO8601 format on
190
+ # serialization.
191
+ def retrieved_on=(new_time)
192
+ @edited = true
193
+ set_time(:retrievedOn, new_time)
194
+ end
195
+
131
196
  private
132
197
 
133
198
  def init_provenance_defaults(struct)
@@ -136,6 +201,8 @@ module ROBundle
136
201
  struct[:authoredBy] = [*struct.fetch(:authoredBy, [])].map do |agent|
137
202
  Agent.new(agent)
138
203
  end
204
+ retrievor = struct[:retrievedBy]
205
+ struct[:retrievedBy] = Agent.new(retrievor) unless retrievor.nil?
139
206
 
140
207
  struct
141
208
  end