ro-bundle 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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