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.
- checksums.yaml +4 -4
- data/.irbrc +1 -0
- data/.pryrc +1 -0
- data/README.adoc +163 -19
- data/docs/examples.adoc +91 -0
- data/lib/paneron/register/hierarchical.rb +24 -0
- data/lib/paneron/register/raw/data_set.rb +349 -34
- data/lib/paneron/register/raw/item.rb +147 -23
- data/lib/paneron/register/raw/item_class.rb +140 -23
- data/lib/paneron/register/raw/register.rb +452 -63
- data/lib/paneron/register/register.rb +0 -2
- data/lib/paneron/register/root_finder.rb +24 -0
- data/lib/paneron/register/validatable.rb +40 -0
- data/lib/paneron/register/version.rb +1 -1
- data/lib/paneron/register/writeable.rb +96 -0
- data/lib/paneron/register.rb +5 -0
- data/paneron-register.gemspec +1 -0
- metadata +21 -2
@@ -6,41 +6,179 @@ module Paneron
|
|
6
6
|
module Register
|
7
7
|
module Raw
|
8
8
|
class DataSet
|
9
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
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
|
-
@
|
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
|
31
|
-
|
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
|
-
"
|
168
|
+
"#{name} path (#{path}) does not exist"
|
34
169
|
end
|
35
|
-
|
170
|
+
|
171
|
+
unless File.directory?(path)
|
36
172
|
raise Paneron::Register::Error,
|
37
|
-
"
|
173
|
+
"#{name} path (#{path}) is not a directory"
|
38
174
|
end
|
39
|
-
|
40
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
75
|
-
|
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)) }.
|
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 ||=
|
86
|
-
|
87
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
16
|
-
|
17
|
-
extension = "yaml"
|
18
|
+
item_uuid = nil,
|
19
|
+
item_class_path = nil,
|
20
|
+
extension = "yaml",
|
21
|
+
item_class: nil
|
18
22
|
)
|
19
|
-
|
20
|
-
self.
|
21
|
-
@
|
22
|
-
|
23
|
-
|
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
|
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:
|
42
|
-
data:
|
43
|
-
status: Paneron::Register::ItemStatus.new(state:
|
44
|
-
date_accepted:
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|