paneron-register 0.2.0 → 0.3.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.
@@ -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