odin-foundation 1.0.4 → 1.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.
- checksums.yaml +4 -4
- data/lib/odin/export.rb +1 -1
- data/lib/odin/forms/accessibility.rb +95 -0
- data/lib/odin/forms/css.rb +42 -0
- data/lib/odin/forms/parser.rb +719 -0
- data/lib/odin/forms/renderer.rb +534 -0
- data/lib/odin/forms/types.rb +102 -0
- data/lib/odin/forms/units.rb +41 -0
- data/lib/odin/forms.rb +55 -0
- data/lib/odin/parsing/parser.rb +25 -1
- data/lib/odin/parsing/tokenizer.rb +38 -20
- data/lib/odin/parsing/value_parser.rb +65 -7
- data/lib/odin/resolver/import_resolver.rb +40 -12
- data/lib/odin/resolver/type_registry.rb +54 -0
- data/lib/odin/transform/format_exporters.rb +88 -48
- data/lib/odin/transform/source_parsers.rb +2 -2
- data/lib/odin/transform/transform_engine.rb +1388 -246
- data/lib/odin/transform/transform_expr.rb +222 -0
- data/lib/odin/transform/transform_parser.rb +377 -19
- data/lib/odin/transform/transform_types.rb +23 -7
- data/lib/odin/transform/verb_context.rb +19 -1
- data/lib/odin/transform/verbs/aggregation_verbs.rb +2 -1
- data/lib/odin/transform/verbs/collection_verbs.rb +164 -89
- data/lib/odin/transform/verbs/datetime_verbs.rb +86 -15
- data/lib/odin/transform/verbs/extra_verbs.rb +613 -0
- data/lib/odin/transform/verbs/financial_verbs.rb +116 -27
- data/lib/odin/transform/verbs/geo_verbs.rb +7 -0
- data/lib/odin/transform/verbs/numeric_verbs.rb +85 -64
- data/lib/odin/transform/verbs/object_verbs.rb +31 -26
- data/lib/odin/types/errors.rb +9 -1
- data/lib/odin/types/schema.rb +20 -3
- data/lib/odin/utils/format_utils.rb +31 -15
- data/lib/odin/validation/format_validators.rb +7 -9
- data/lib/odin/validation/invariant_evaluator.rb +410 -0
- data/lib/odin/validation/schema_definition_validator.rb +357 -0
- data/lib/odin/validation/schema_parser.rb +234 -21
- data/lib/odin/validation/validator.rb +281 -123
- data/lib/odin/version.rb +1 -1
- data/lib/odin.rb +100 -4
- metadata +14 -2
|
@@ -21,6 +21,19 @@ module Odin
|
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
# Strict yyyy-MM-dd parse; returns nil for any other shape or invalid date.
|
|
25
|
+
def parse_iso_date(s)
|
|
26
|
+
s = s.to_string if s.is_a?(Types::DynValue)
|
|
27
|
+
return nil unless s.is_a?(String)
|
|
28
|
+
m = s.match(/\A(\d{4})-(\d{2})-(\d{2})\z/)
|
|
29
|
+
return nil unless m
|
|
30
|
+
begin
|
|
31
|
+
Date.new(m[1].to_i, m[2].to_i, m[3].to_i)
|
|
32
|
+
rescue ArgumentError
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
24
37
|
def parse_timestamp(s)
|
|
25
38
|
return nil if s.nil?
|
|
26
39
|
s = s.to_string if s.is_a?(Types::DynValue)
|
|
@@ -69,6 +82,34 @@ module Odin
|
|
|
69
82
|
end
|
|
70
83
|
end
|
|
71
84
|
|
|
85
|
+
# Extract date and time components positionally from a pattern, returning
|
|
86
|
+
# a UTC Time. Recognizes YYYY/YY/MM/DD/HH/mm/ss tokens.
|
|
87
|
+
def parse_timestamp_with_pattern(value, pattern)
|
|
88
|
+
pos = ->(pat) { pattern.index(pat) }
|
|
89
|
+
slice = ->(p, len) { p ? value[p, len]&.to_i : nil }
|
|
90
|
+
|
|
91
|
+
yyyy = pos.call("YYYY")
|
|
92
|
+
yy = pos.call("YY")
|
|
93
|
+
year = if yyyy
|
|
94
|
+
slice.call(yyyy, 4)
|
|
95
|
+
elsif yy
|
|
96
|
+
2000 + (slice.call(yy, 2) || 0)
|
|
97
|
+
end
|
|
98
|
+
return nil unless year
|
|
99
|
+
|
|
100
|
+
month = slice.call(pos.call("MM"), 2) || 1
|
|
101
|
+
day = slice.call(pos.call("DD"), 2) || 1
|
|
102
|
+
hour = slice.call(pos.call("HH"), 2) || 0
|
|
103
|
+
minute = slice.call(pos.call("mm"), 2) || 0
|
|
104
|
+
second = slice.call(pos.call("ss"), 2) || 0
|
|
105
|
+
|
|
106
|
+
begin
|
|
107
|
+
Time.utc(year, month, day, hour, minute, second)
|
|
108
|
+
rescue ArgumentError
|
|
109
|
+
nil
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
72
113
|
def apply_date_pattern(dt, pattern)
|
|
73
114
|
result = pattern.dup
|
|
74
115
|
if dt.is_a?(Date) && !dt.is_a?(Time)
|
|
@@ -168,11 +209,16 @@ module Odin
|
|
|
168
209
|
}
|
|
169
210
|
|
|
170
211
|
registry["parseTimestamp"] = ->(args, _ctx) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
212
|
+
return dv.of_null if args.length < 2
|
|
213
|
+
|
|
214
|
+
value = args[0]&.to_string
|
|
215
|
+
pattern = args[1]&.to_string
|
|
216
|
+
return dv.of_null if value.nil? || value.empty? || pattern.nil?
|
|
217
|
+
|
|
218
|
+
t = DateTimeVerbs.parse_timestamp_with_pattern(value, pattern)
|
|
174
219
|
return dv.of_null unless t
|
|
175
|
-
dv.
|
|
220
|
+
dv.of_string(format("%04d-%02d-%02dT%02d:%02d:%02d",
|
|
221
|
+
t.year, t.month, t.day, t.hour, t.min, t.sec))
|
|
176
222
|
}
|
|
177
223
|
|
|
178
224
|
registry["addDays"] = ->(args, _ctx) {
|
|
@@ -342,7 +388,7 @@ module Odin
|
|
|
342
388
|
return dv.of_null if v.nil? || v.null?
|
|
343
389
|
d = DateTimeVerbs.parse_date(v)
|
|
344
390
|
return dv.of_null unless d
|
|
345
|
-
dv.
|
|
391
|
+
dv.of_string(DateTimeVerbs.format_date_str(Date.new(d.year, d.month, 1)))
|
|
346
392
|
}
|
|
347
393
|
|
|
348
394
|
registry["endOfMonth"] = ->(args, _ctx) {
|
|
@@ -351,7 +397,7 @@ module Odin
|
|
|
351
397
|
d = DateTimeVerbs.parse_date(v)
|
|
352
398
|
return dv.of_null unless d
|
|
353
399
|
last = Date.new(d.year, d.month, -1)
|
|
354
|
-
dv.
|
|
400
|
+
dv.of_string(DateTimeVerbs.format_date_str(last))
|
|
355
401
|
}
|
|
356
402
|
|
|
357
403
|
registry["startOfYear"] = ->(args, _ctx) {
|
|
@@ -359,7 +405,7 @@ module Odin
|
|
|
359
405
|
return dv.of_null if v.nil? || v.null?
|
|
360
406
|
d = DateTimeVerbs.parse_date(v)
|
|
361
407
|
return dv.of_null unless d
|
|
362
|
-
dv.
|
|
408
|
+
dv.of_string(DateTimeVerbs.format_date_str(Date.new(d.year, 1, 1)))
|
|
363
409
|
}
|
|
364
410
|
|
|
365
411
|
registry["endOfYear"] = ->(args, _ctx) {
|
|
@@ -367,7 +413,7 @@ module Odin
|
|
|
367
413
|
return dv.of_null if v.nil? || v.null?
|
|
368
414
|
d = DateTimeVerbs.parse_date(v)
|
|
369
415
|
return dv.of_null unless d
|
|
370
|
-
dv.
|
|
416
|
+
dv.of_string(DateTimeVerbs.format_date_str(Date.new(d.year, 12, 31)))
|
|
371
417
|
}
|
|
372
418
|
|
|
373
419
|
registry["dayOfWeek"] = ->(args, _ctx) {
|
|
@@ -478,7 +524,7 @@ module Odin
|
|
|
478
524
|
d1 = DateTimeVerbs.parse_date(v1)
|
|
479
525
|
d2 = DateTimeVerbs.parse_date(v2)
|
|
480
526
|
return dv.of_null unless d1 && d2
|
|
481
|
-
dv.of_integer((d2 - d1).to_i
|
|
527
|
+
dv.of_integer((d2 - d1).to_i)
|
|
482
528
|
}
|
|
483
529
|
|
|
484
530
|
registry["ageFromDate"] = ->(args, _ctx) {
|
|
@@ -499,8 +545,12 @@ module Odin
|
|
|
499
545
|
registry["isValidDate"] = ->(args, _ctx) {
|
|
500
546
|
s = args[0]&.to_string
|
|
501
547
|
return dv.of_bool(false) if s.nil? || s.empty?
|
|
502
|
-
|
|
503
|
-
|
|
548
|
+
return dv.of_bool(true) unless DateTimeVerbs.parse_iso_date(s).nil?
|
|
549
|
+
if args.length >= 2
|
|
550
|
+
pattern = args[1]&.to_string || ""
|
|
551
|
+
return dv.of_bool(true) unless DateTimeVerbs.parse_date_with_pattern(s, pattern).nil?
|
|
552
|
+
end
|
|
553
|
+
dv.of_bool(false)
|
|
504
554
|
}
|
|
505
555
|
|
|
506
556
|
registry["formatLocaleDate"] = ->(args, _ctx) {
|
|
@@ -544,17 +594,38 @@ module Odin
|
|
|
544
594
|
return dv.of_null if v.nil? || v.null?
|
|
545
595
|
d = DateTimeVerbs.parse_date(v)
|
|
546
596
|
return dv.of_null unless d
|
|
547
|
-
|
|
548
|
-
d += 2
|
|
549
|
-
elsif d.sunday?
|
|
597
|
+
loop do
|
|
550
598
|
d += 1
|
|
599
|
+
break unless d.saturday? || d.sunday?
|
|
551
600
|
end
|
|
552
601
|
dv.of_string(DateTimeVerbs.format_date_str(d))
|
|
553
602
|
}
|
|
554
603
|
|
|
555
604
|
registry["formatDuration"] = ->(args, _ctx) {
|
|
556
605
|
return dv.of_null if args.empty?
|
|
557
|
-
|
|
606
|
+
arg = args[0]
|
|
607
|
+
return dv.of_null if arg.nil? || arg.null?
|
|
608
|
+
|
|
609
|
+
iso = arg.to_string
|
|
610
|
+
|
|
611
|
+
# Numeric seconds (typed or numeric string): expand into d/h/m/s.
|
|
612
|
+
if arg.numeric? || iso.match?(/\A\d+(?:\.\d+)?\z/)
|
|
613
|
+
total = arg.numeric? ? arg.to_number.to_f : iso.to_f
|
|
614
|
+
return dv.of_null if total < 0
|
|
615
|
+
days = (total / 86400).floor
|
|
616
|
+
total -= days * 86400
|
|
617
|
+
hours = (total / 3600).floor
|
|
618
|
+
total -= hours * 3600
|
|
619
|
+
minutes = (total / 60).floor
|
|
620
|
+
seconds = total - minutes * 60
|
|
621
|
+
parts = []
|
|
622
|
+
parts << DateTimeVerbs.duration_part(days, "day") unless days == 0
|
|
623
|
+
parts << DateTimeVerbs.duration_part(hours, "hour") unless hours == 0
|
|
624
|
+
parts << DateTimeVerbs.duration_part(minutes, "minute") unless minutes == 0
|
|
625
|
+
parts << DateTimeVerbs.duration_part(seconds, "second") unless seconds == 0
|
|
626
|
+
return dv.of_string(parts.empty? ? "0 seconds" : parts.join(", "))
|
|
627
|
+
end
|
|
628
|
+
|
|
558
629
|
return dv.of_null if iso.nil? || !iso.start_with?("P")
|
|
559
630
|
parts = []
|
|
560
631
|
in_time = false
|