1
1
#include " nix/expr/primops.hh"
2
2
#include " nix/expr/eval-inline.hh"
3
3
4
+ #include " expr-config-private.hh"
5
+
4
6
#include < sstream>
5
7
6
8
#include < toml.hpp>
7
9
8
10
namespace nix {
9
11
12
+ #if HAVE_TOML11_4
13
+
14
+ /* *
15
+ * This is what toml11 < 4.0 did when choosing the subsecond precision.
16
+ * TOML 1.0.0 spec doesn't define how sub-millisecond ranges should be handled and calls it
17
+ * implementation defined behavior. For a lack of a better choice we stick with what older versions
18
+ * of toml11 did [1].
19
+ *
20
+ * [1]: https://github.com/ToruNiina/toml11/blob/dcfe39a783a94e8d52c885e5883a6fbb21529019/toml/datetime.hpp#L282
21
+ */
22
+ static size_t normalizeSubsecondPrecision (toml::local_time lt)
23
+ {
24
+ auto millis = lt.millisecond ;
25
+ auto micros = lt.microsecond ;
26
+ auto nanos = lt.nanosecond ;
27
+ if (millis != 0 || micros != 0 || nanos != 0 ) {
28
+ if (micros != 0 || nanos != 0 ) {
29
+ if (nanos != 0 )
30
+ return 9 ;
31
+ return 6 ;
32
+ }
33
+ return 3 ;
34
+ }
35
+ return 0 ;
36
+ }
37
+
38
+ /* *
39
+ * Normalize date/time formats to serialize to the same strings as versions prior to toml11 4.0.
40
+ *
41
+ * Several things to consider:
42
+ *
43
+ * 1. Sub-millisecond range is represented the same way as in toml11 versions prior to 4.0. Precisioun is rounded
44
+ * towards the next multiple of 3 or capped at 9 digits.
45
+ * 2. Seconds must be specified. This may become optional in (yet unreleased) TOML 1.1.0, but 1.0.0 defined local time
46
+ * in terms of RFC3339 [1].
47
+ * 3. date-time separator (`t`, `T` or space ` `) is canonicalized to an upper T. This is compliant with RFC3339
48
+ * [1] 5.6:
49
+ * > Applications that generate this format SHOULD use upper case letters.
50
+ *
51
+ * [1]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
52
+ */
53
+ static void normalizeDatetimeFormat (toml::value & t)
54
+ {
55
+ if (t.is_local_datetime ()) {
56
+ auto & ldt = t.as_local_datetime ();
57
+ t.as_local_datetime_fmt () = {
58
+ .delimiter = toml::datetime_delimiter_kind::upper_T,
59
+ // https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
60
+ .has_seconds = true , // Mandated by TOML 1.0.0
61
+ .subsecond_precision = normalizeSubsecondPrecision (ldt.time ),
62
+ };
63
+ return ;
64
+ }
65
+
66
+ if (t.is_offset_datetime ()) {
67
+ auto & odt = t.as_offset_datetime ();
68
+ t.as_offset_datetime_fmt () = {
69
+ .delimiter = toml::datetime_delimiter_kind::upper_T,
70
+ // https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
71
+ .has_seconds = true , // Mandated by TOML 1.0.0
72
+ .subsecond_precision = normalizeSubsecondPrecision (odt.time ),
73
+ };
74
+ return ;
75
+ }
76
+
77
+ if (t.is_local_time ()) {
78
+ auto & lt = t.as_local_time ();
79
+ t.as_local_time_fmt () = {
80
+ .has_seconds = true , // Mandated by TOML 1.0.0
81
+ .subsecond_precision = normalizeSubsecondPrecision (lt),
82
+ };
83
+ return ;
84
+ }
85
+ }
86
+
87
+ #endif
88
+
10
89
static void prim_fromTOML (EvalState & state, const PosIdx pos, Value ** args, Value & val)
11
90
{
12
91
auto toml = state.forceStringNoCtx (*args[0 ], pos, " while evaluating the argument passed to builtins.fromTOML" );
13
92
14
93
std::istringstream tomlStream (std::string{toml});
15
94
16
- std::function<void (Value &, toml::value)> visit;
17
-
18
- visit = [&](Value & v, toml::value t) {
95
+ auto visit = [&](auto & self, Value & v, toml::value t) -> void {
19
96
switch (t.type ()) {
20
97
case toml::value_t ::table: {
21
98
auto table = toml::get<toml::table>(t);
22
-
23
- size_t size = 0 ;
24
- for (auto & i : table) {
25
- (void ) i;
26
- size++;
27
- }
28
-
29
- auto attrs = state.buildBindings (size);
99
+ auto attrs = state.buildBindings (table.size ());
30
100
31
101
for (auto & elem : table) {
32
102
forceNoNullByte (elem.first );
33
- visit ( attrs.alloc (elem.first ), elem.second );
103
+ self (self, attrs.alloc (elem.first ), elem.second );
34
104
}
35
105
36
106
v.mkAttrs (attrs);
37
107
} break ;
38
- ;
39
108
case toml::value_t ::array: {
40
109
auto array = toml::get<std::vector<toml::value>>(t);
41
110
42
111
auto list = state.buildList (array.size ());
43
112
for (const auto & [n, v] : enumerate(list))
44
- visit ( *(v = state.allocValue ()), array[n]);
113
+ self (self, *(v = state.allocValue ()), array[n]);
45
114
v.mkList (list);
46
115
} break ;
47
- ;
48
116
case toml::value_t ::boolean:
49
117
v.mkBool (toml::get<bool >(t));
50
118
break ;
51
- ;
52
119
case toml::value_t ::integer:
53
120
v.mkInt (toml::get<int64_t >(t));
54
121
break ;
55
- ;
56
122
case toml::value_t ::floating:
57
123
v.mkFloat (toml::get<NixFloat>(t));
58
124
break ;
59
- ;
60
125
case toml::value_t ::string: {
61
126
auto s = toml::get<std::string_view>(t);
62
127
forceNoNullByte (s);
63
128
v.mkString (s);
64
129
} break ;
65
- ;
66
130
case toml::value_t ::local_datetime:
67
131
case toml::value_t ::offset_datetime:
68
132
case toml::value_t ::local_date:
69
133
case toml::value_t ::local_time: {
70
134
if (experimentalFeatureSettings.isEnabled (Xp::ParseTomlTimestamps)) {
135
+ #if HAVE_TOML11_4
136
+ normalizeDatetimeFormat (t);
137
+ #endif
71
138
auto attrs = state.buildBindings (2 );
72
139
attrs.alloc (" _type" ).mkString (" timestamp" );
73
140
std::ostringstream s;
@@ -80,16 +147,24 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Va
80
147
throw std::runtime_error (" Dates and times are not supported" );
81
148
}
82
149
} break ;
83
- ;
84
150
case toml::value_t ::empty:
85
151
v.mkNull ();
86
152
break ;
87
- ;
88
153
}
89
154
};
90
155
91
156
try {
92
- visit (val, toml::parse (tomlStream, " fromTOML" /* the "filename" */ ));
157
+ visit (
158
+ visit,
159
+ val,
160
+ toml::parse (
161
+ tomlStream,
162
+ " fromTOML" /* the "filename" */
163
+ #if HAVE_TOML11_4
164
+ ,
165
+ toml::spec::v (1 , 0 , 0 ) // Be explicit that we are parsing TOML 1.0.0 without extensions
166
+ #endif
167
+ ));
93
168
} catch (std::exception & e) { // TODO: toml::syntax_error
94
169
state.error <EvalError>(" while parsing TOML: %s" , e.what ()).atPos (pos).debugThrow ();
95
170
}
0 commit comments