paneron-register 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,41 +6,179 @@ module Paneron
6
6
  module Register
7
7
  module Raw
8
8
  class DataSet
9
- attr_reader :data_set_path, :register_path, :data_set_yaml_path,
9
+ include Writeable
10
+ include Validatable
11
+ include RootFinder
12
+
13
+ attr_reader :register_path,
10
14
  :data_set_name, :extension
15
+ attr_accessor :register
16
+
17
+ def initialize(
18
+ data_set_path = nil,
19
+ extension = "yaml",
20
+ register: nil
21
+ )
22
+
23
+ unless data_set_path.nil?
24
+ register_path, new_data_set_name = Hierarchical.split_path(data_set_path)
25
+ end
11
26
 
12
- def initialize(register_path, data_set_name, extension = "yaml")
13
- data_set_path = File.join(register_path, data_set_name)
14
- self.class.validate_data_set_path(data_set_path)
27
+ # Deduce parent from self path,
28
+ # if only self path is specified.
29
+ @register = if register.nil?
30
+ Paneron::Register::Raw::Register.new(
31
+ register_path,
32
+ )
33
+ else
34
+ register
35
+ end
36
+
37
+ if register_path != @register.register_path
38
+ raise Paneron::Register::Error,
39
+ "Register path mismatch:\n" \
40
+ "Expected passed in register to align with data set's parent:\n" \
41
+ "#{@register.register_path} != #{register_path}"
42
+ end
15
43
 
16
- @register_path = register_path
17
- @data_set_name = data_set_name
18
44
  @extension = extension
19
- @data_set_path = data_set_path
20
- @data_set_yaml_path = File.join(data_set_path,
21
- DATA_SET_METADATA_FILENAME)
22
45
  @item_classes = {}
23
- @item_class_names = nil
24
- @item_uuids = nil
46
+ @items = {}
25
47
  @metadata = nil
48
+ @paneron_metadata = nil
49
+ self.data_set_name = new_data_set_name
50
+ @old_name = @data_set_name
51
+ # {
52
+ # "title" => data_set_name,
53
+ # }
26
54
  end
27
55
 
28
56
  DATA_SET_METADATA_FILENAME = "/register.yaml"
57
+ PANERON_METADATA_FILENAME = "/panerondataset.yaml"
58
+
59
+ def data_set_path
60
+ File.join(register_path, data_set_name)
61
+ end
62
+
63
+ def data_set_name=(new_data_set_name)
64
+ if new_data_set_name.nil? || new_data_set_name.empty?
65
+ raise Paneron::Register::Error, "Data set name cannot be empty"
66
+ end
67
+
68
+ unless new_data_set_name.is_a?(String)
69
+ raise Paneron::Register::Error, "Data set name must be a string"
70
+ end
71
+
72
+ @data_set_name = new_data_set_name
73
+ self.title = new_data_set_name
74
+ end
75
+
76
+ def data_set_yaml_path
77
+ File.join(data_set_path,
78
+ DATA_SET_METADATA_FILENAME)
79
+ end
80
+
81
+ def paneron_yaml_path
82
+ File.join(data_set_path,
83
+ PANERON_METADATA_FILENAME)
84
+ end
85
+
86
+ def parent
87
+ register
88
+ end
89
+
90
+ def self.name
91
+ "Data set"
92
+ end
93
+
94
+ # Important that the parent paths are existent.
95
+ def save_sequence
96
+ # Save self
97
+ require "fileutils"
98
+
99
+ # Move old data set to new path
100
+ old_path = File.join(register_path, @old_name)
101
+ if File.directory?(old_path) && @old_name != data_set_name
102
+ FileUtils.mv(old_path, self_path)
103
+ @old_name = data_set_name
104
+ else
105
+ FileUtils.mkdir_p(self_path)
106
+ end
107
+
108
+ # TODO: populate template with sensible defaults
109
+ if @metadata.nil? || @metadata.empty?
110
+ File.write(data_set_yaml_path, self.class.metadata_template.to_yaml)
111
+ else
112
+ File.write(data_set_yaml_path, metadata.to_yaml)
113
+ end
114
+
115
+ # TODO: populate template with sensible defaults
116
+ if @paneron_metadata.nil? || @paneron_metadata.empty?
117
+ File.write(paneron_yaml_path,
118
+ self.class.paneron_metadata_template(data_set_name).to_yaml)
119
+ else
120
+ File.write(paneron_yaml_path, paneron_metadata.to_yaml)
121
+ end
122
+
123
+ # Save item classes
124
+ item_class_names.each do |item_class_name|
125
+ new_thing = item_classes(item_class_name)
126
+ new_thing.data_set = self
127
+ new_thing.save
128
+ end
129
+
130
+ # # Save proposals
131
+ # proposal_uuids.each do |proposal_uuid|
132
+ # proposals(proposal_uuid).save
133
+ # end
134
+ end
135
+
136
+ def self_path
137
+ data_set_path
138
+ end
29
139
 
30
- def self.validate_data_set_path(data_set_path)
31
- unless File.exist?(data_set_path)
140
+ def is_valid?
141
+ self.data_set_name = data_set_name
142
+ register.valid?
143
+ end
144
+
145
+ def set_register(new_register)
146
+ @register = new_register
147
+ end
148
+
149
+ def register_path
150
+ @register.register_path
151
+ end
152
+
153
+ def add_item_classes(*new_item_classes)
154
+ new_item_classes = [new_item_classes] unless new_item_classes.is_a?(Enumerable)
155
+ new_item_classes.each do |item_class|
156
+ item_class.set_data_set(self)
157
+ @item_classes[item_class.item_class_name] = item_class
158
+ end
159
+ end
160
+
161
+ def self.validate_path_before_saving
162
+ true
163
+ end
164
+
165
+ def self.validate_path(path)
166
+ unless File.exist?(path)
32
167
  raise Paneron::Register::Error,
33
- "Data Set path does not exist"
168
+ "#{name} path (#{path}) does not exist"
34
169
  end
35
- unless File.directory?(data_set_path)
170
+
171
+ unless File.directory?(path)
36
172
  raise Paneron::Register::Error,
37
- "Data Set path is not a directory"
173
+ "#{name} path (#{path}) is not a directory"
38
174
  end
39
- unless File.exist?(File.join(
40
- data_set_path, DATA_SET_METADATA_FILENAME
41
- ))
175
+
176
+ data_set_file = File.join(
177
+ path, DATA_SET_METADATA_FILENAME
178
+ )
179
+ unless File.exist?(data_set_file)
42
180
  raise Paneron::Register::Error,
43
- "Data Set metadata file does not exist"
181
+ "Data Set metadata file (#{data_set_file}) does not exist"
44
182
  end
45
183
  end
46
184
 
@@ -57,35 +195,212 @@ module Paneron
57
195
  end
58
196
  end
59
197
 
60
- def item_classes(item_class_name = nil)
198
+ def item_classes(item_class_name = nil, refresh: false)
61
199
  if item_class_name.nil?
62
- item_class_names.reduce({}) do |acc, item_class_name|
63
- acc[item_class_name] = item_classes(item_class_name)
64
- acc
65
- end
200
+ @item_classes = if !refresh && !@item_classes.empty?
201
+ @item_classes
202
+ else
203
+ item_class_names(refresh: refresh).reduce({}) do |acc, item_class_name|
204
+ acc[item_class_name] = item_classes(item_class_name)
205
+ acc
206
+ end
207
+ end
208
+ elsif refresh
209
+ item_classes[item_class_name]
66
210
  else
67
211
  @item_classes[item_class_name] ||=
68
212
  Paneron::Register::Raw::ItemClass.new(
69
- data_set_path, item_class_name
213
+ File.join(data_set_path, item_class_name),
214
+ data_set: self,
70
215
  )
71
216
  end
72
217
  end
73
218
 
74
- def item_class_names
75
- @item_class_names ||=
219
+ def spawn_item_class(item_class_name, metadata: {})
220
+ new_item_class = Paneron::Register::Raw::ItemClass.new(
221
+ File.join(data_set_path, item_class_name),
222
+ data_set: self,
223
+ )
224
+
225
+ add_item_classes(new_item_class)
226
+
227
+ new_item_class
228
+ end
229
+
230
+ def item_class_names(refresh: false)
231
+ if refresh || @item_classes.empty?
76
232
  Dir.glob(File.join(data_set_path, "*/*.#{extension}"))
77
- .map { |file| File.basename(File.dirname(file)) }.uniq
233
+ .map { |file| File.basename(File.dirname(file)) }.to_set
234
+ else
235
+ @item_classes.keys
236
+ end
78
237
  end
79
238
 
80
239
  def item_uuids
81
- item_classes.values.map(&:item_uuids).flatten
240
+ item_classes.values.map(&:item_uuids).to_set.flatten
241
+ end
242
+
243
+ def items(uuid = nil, item_class_name = nil, refresh: false)
244
+ if uuid.nil?
245
+ @items = if !refresh && !@items.empty?
246
+ @items
247
+ else
248
+ item_classes.reduce({}) do |acc, (item_klass_name, item_klass)|
249
+ item_klass.item_uuids.each do |item_uuid|
250
+ acc[item_uuid] = items(item_uuid, item_klass_name)
251
+ end
252
+ acc
253
+ end
254
+ end
255
+ elsif refresh
256
+ items[uuid]
257
+ else
258
+ @items[uuid] ||=
259
+ Paneron::Register::Raw::Item.new(
260
+ uuid,
261
+ File.join(data_set_path, item_class_name),
262
+ item_class: item_classes[item_class_name],
263
+ )
264
+ end
265
+ end
266
+
267
+ # TODO: Add validation to register.yaml fields
268
+ # name: Full Name of Data Set
269
+ # stakeholders: [ { roles, name, gitServerUsername, affiliations, contacts } ]
270
+ # version: { id, timestamp }
271
+ # contentSummary:
272
+ # operatingLanguage: { name, country, language }
273
+ # organizations: { uuid..: { name, logoURL } }
274
+ # TODO: Add support to read/write panerondataset.yaml
275
+ # title: short-name
276
+ # type:
277
+ # id: 'npm-paneron-extension-name'
278
+ # version: 0.0.1-alpha1
279
+ def paneron_metadata
280
+ @paneron_metadata ||= begin
281
+ YAML.safe_load_file(
282
+ paneron_yaml_path,
283
+ permitted_classes: [Time, Date, DateTime],
284
+ )
285
+ rescue Errno::ENOENT
286
+ {}
287
+ end
288
+ end
289
+
290
+ def merge_paneron_metadata(other)
291
+ paneron_metadata.merge!(other)
292
+ end
293
+
294
+ def paneron_metadata=(new_paneron_metadata)
295
+ @paneron_metadata = new_paneron_metadata
82
296
  end
83
297
 
84
298
  def metadata
85
- @metadata ||= YAML.safe_load_file(
86
- data_set_yaml_path,
87
- permitted_classes: [Time],
88
- )
299
+ @metadata ||= begin
300
+ YAML.safe_load_file(
301
+ data_set_yaml_path,
302
+ permitted_classes: [Time, Date, DateTime],
303
+ )
304
+ rescue Errno::ENOENT
305
+ {}
306
+ end
307
+ end
308
+
309
+ def merge_metadata(other)
310
+ metadata.merge!(other)
311
+ end
312
+
313
+ def metadata=(metadata)
314
+ @metadata = metadata
315
+ end
316
+
317
+ def name=(new_name)
318
+ metadata["name"] = new_name.to_s
319
+ end
320
+
321
+ # This is really just data_set_name
322
+ def title=(new_title)
323
+ paneron_metadata["title"] = new_title.to_s
324
+ end
325
+ private :title=
326
+
327
+ def title
328
+ paneron_metadata["title"]
329
+ end
330
+
331
+ def stakeholders
332
+ metadata["stakeholders"] || []
333
+ end
334
+
335
+ def stakeholders=(new_stakeholders)
336
+ metadata["stakeholders"] ||= new_stakeholders
337
+ end
338
+
339
+ def content_summary
340
+ metadata["contentSummary"] || ""
341
+ end
342
+
343
+ def content_summary=(new_content_summary)
344
+ metadata["contentSummary"] = new_content_summary.to_s
345
+ end
346
+
347
+ def operating_language
348
+ metadata["operatingLanguage"] || {}
349
+ end
350
+
351
+ def operating_language=(name: "", country: "", language: "")
352
+ metadata["operatingLanguage"] = {
353
+ "name" => name.to_s,
354
+ "country" => country.to_s,
355
+ "language" => language.to_s,
356
+ }
357
+ end
358
+
359
+ def organizations
360
+ metadata["organizations"] || {}
361
+ end
362
+
363
+ def organizations=(new_organizations)
364
+ metadata["organizations"] = case new_organizations
365
+ when Hash then new_organizations
366
+ else
367
+ raise Paneron::Register::Error, "organizations must be a hash"
368
+ end
369
+ end
370
+
371
+ def self.metadata_template
372
+ {
373
+ "name" => "",
374
+ "stakeholders" => [
375
+ # {
376
+ # "roles" => [],
377
+ # "name" => "",
378
+ # "gitServerUsername" => "",
379
+ # "affiliations" => [],
380
+ # "contacts" => [],
381
+ # },
382
+ ],
383
+ "version" => nil, # { "id" => "", "timestamp" => "" },
384
+ "contentSummary" => "",
385
+ "operatingLanguage" => {
386
+ "name" => "English",
387
+ "country" => "N/A",
388
+ "language" => "eng",
389
+ },
390
+ "organizations" => {
391
+ # "uuid" => { "name" => "", "logoURL" => "" },
392
+ },
393
+ }
394
+ end
395
+
396
+ def self.paneron_metadata_template(title: "")
397
+ {
398
+ "title" => "",
399
+ "type" => {
400
+ "id" => "",
401
+ "version" => "",
402
+ },
403
+ }
89
404
  end
90
405
  end
91
406
  end
@@ -6,51 +6,175 @@ module Paneron
6
6
  module Register
7
7
  module Raw
8
8
  class Item
9
- attr_reader :item_class_path,
10
- :item_id,
11
- :item_path,
9
+ include Writeable
10
+ include Validatable
11
+ include RootFinder
12
+
13
+ attr_reader :item_uuid,
12
14
  :extension
15
+ attr_accessor :item_class
13
16
 
14
17
  def initialize(
15
- item_class_path,
16
- item_id,
17
- extension = "yaml"
18
+ item_uuid = nil,
19
+ item_class_path = nil,
20
+ extension = "yaml",
21
+ item_class: nil
18
22
  )
19
- item_path = File.join(item_class_path, "#{item_id}.#{extension}")
20
- self.class.validate_item_path(item_path)
21
- @item_class_path = item_class_path
22
- @item_id = item_id
23
- @item_path = item_path
23
+ # Deduce parent from self path,
24
+ # if only self path is specified.
25
+ @item_class = if item_class.nil?
26
+ Paneron::Register::Raw::ItemClass.new(
27
+ item_class_path,
28
+ )
29
+ else
30
+ item_class
31
+ end
32
+
33
+ require "securerandom"
34
+ require "uuid"
35
+
36
+ @item_uuid = if item_uuid.nil? || item_uuid.empty?
37
+ SecureRandom.uuid
38
+ elsif UUID.validate(item_uuid)
39
+ item_uuid
40
+ else
41
+ raise Paneron::Register::Error,
42
+ "Specified UUID not valid (#{item_uuid})"
43
+ end
44
+
24
45
  @extension = extension
25
46
  @to_h = nil
47
+ @status = nil
48
+ @data = nil
49
+ @date_accepted = nil
50
+ end
51
+
52
+ def item_path
53
+ File.join(item_class_path, "#{item_uuid}.#{extension}")
54
+ end
55
+
56
+ def item_class_path
57
+ @item_class.item_class_path
58
+ end
59
+
60
+ def set(
61
+ status: nil,
62
+ data: nil,
63
+ date_accepted: nil
64
+ )
65
+ self.status = status
66
+ self.data = data
67
+ self.date_accepted = date_accepted
26
68
  end
27
69
 
28
- def self.validate_item_path(path)
70
+ def uuid
71
+ @item_uuid
72
+ end
73
+
74
+ def status
75
+ @status ||= disk_to_h["status"]
76
+ end
77
+
78
+ def status=(new_status)
79
+ @status = new_status
80
+ end
81
+
82
+ def data
83
+ @data ||= disk_to_h["data"]
84
+ end
85
+
86
+ def data=(new_data)
87
+ @data = new_data
88
+ end
89
+
90
+ # Default is now
91
+ def date_accepted
92
+ @date_accepted ||= disk_to_h["dateAccepted"] || DateTime.now
93
+ end
94
+
95
+ def date_accepted=(new_date_accepted)
96
+ @date_accepted = case new_date_accepted
97
+ when String then DateTime.parse(new_date_accepted)
98
+ when DateTime then new_date_accepted
99
+ when Time, Date then new_date_accepted.to_datetime
100
+ when NilClass then nil
101
+ else
102
+ raise Paneron::Register::Error,
103
+ "Invalid date: #{new_date_accepted}"
104
+ end
105
+ end
106
+
107
+ def parent
108
+ item_class
109
+ end
110
+
111
+ def self.name
112
+ "Item"
113
+ end
114
+
115
+ def save_sequence
116
+ # Save self
117
+ require "fileutils"
118
+ FileUtils.mkdir_p(item_class_path)
119
+
120
+ File.write(item_path, to_h.to_yaml)
121
+ end
122
+
123
+ def self_path
124
+ item_path
125
+ end
126
+
127
+ def is_valid?
128
+ item_class.valid?
129
+ end
130
+
131
+ def set_item_class(new_item_class)
132
+ @item_class = new_item_class
133
+ end
134
+
135
+ def self.validate_path_before_saving
136
+ true
137
+ end
138
+
139
+ def self.validate_path(path)
29
140
  unless File.exist?(path)
30
141
  raise Paneron::Register::Error,
31
- "Item path does not exist"
142
+ "Item path (#{path}) does not exist"
32
143
  end
33
144
  unless File.file?(path)
34
145
  raise Paneron::Register::Error,
35
- "Item path is not a file"
146
+ "Item path (#{path}) is not a file"
36
147
  end
37
148
  end
38
149
 
39
150
  def to_lutaml
40
151
  Paneron::Register::Item.new(
41
- id: item_id,
42
- data: to_h["data"],
43
- status: Paneron::Register::ItemStatus.new(state: to_h["status"]),
44
- date_accepted: to_h["dateAccepted"].to_s,
152
+ id: item_uuid,
153
+ data: data,
154
+ status: Paneron::Register::ItemStatus.new(state: status),
155
+ date_accepted: date_accepted.to_s,
45
156
  )
46
157
  end
47
158
 
48
159
  def to_h
49
- @to_h ||=
50
- YAML.safe_load_file(
51
- File.join(item_path),
52
- permitted_classes: [Time],
53
- )
160
+ {
161
+ "id" => item_uuid,
162
+ "data" => data,
163
+ "status" => status,
164
+ "dateAccepted" => date_accepted,
165
+ }
166
+ end
167
+
168
+ def disk_to_h
169
+ if File.exist?(item_path)
170
+ @disk_to_h ||=
171
+ YAML.safe_load_file(
172
+ item_path,
173
+ permitted_classes: [Time, Date, DateTime],
174
+ )
175
+ else
176
+ {}
177
+ end
54
178
  end
55
179
  end
56
180
  end