11#include " nix/expr/primops.hh"
22#include " nix/expr/eval-inline.hh"
33
4+ #include " expr-config-private.hh"
5+
46#include < sstream>
57
68#include < toml.hpp>
79
810namespace nix {
911
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+
1089static void prim_fromTOML (EvalState & state, const PosIdx pos, Value ** args, Value & val)
1190{
1291 auto toml = state.forceStringNoCtx (*args[0 ], pos, " while evaluating the argument passed to builtins.fromTOML" );
1392
1493 std::istringstream tomlStream (std::string{toml});
1594
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 {
1996 switch (t.type ()) {
2097 case toml::value_t ::table: {
2198 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 ());
30100
31101 for (auto & elem : table) {
32102 forceNoNullByte (elem.first );
33- visit ( attrs.alloc (elem.first ), elem.second );
103+ self (self, attrs.alloc (elem.first ), elem.second );
34104 }
35105
36106 v.mkAttrs (attrs);
37107 } break ;
38- ;
39108 case toml::value_t ::array: {
40109 auto array = toml::get<std::vector<toml::value>>(t);
41110
42111 auto list = state.buildList (array.size ());
43112 for (const auto & [n, v] : enumerate(list))
44- visit ( *(v = state.allocValue ()), array[n]);
113+ self (self, *(v = state.allocValue ()), array[n]);
45114 v.mkList (list);
46115 } break ;
47- ;
48116 case toml::value_t ::boolean:
49117 v.mkBool (toml::get<bool >(t));
50118 break ;
51- ;
52119 case toml::value_t ::integer:
53120 v.mkInt (toml::get<int64_t >(t));
54121 break ;
55- ;
56122 case toml::value_t ::floating:
57123 v.mkFloat (toml::get<NixFloat>(t));
58124 break ;
59- ;
60125 case toml::value_t ::string: {
61126 auto s = toml::get<std::string_view>(t);
62127 forceNoNullByte (s);
63128 v.mkString (s);
64129 } break ;
65- ;
66130 case toml::value_t ::local_datetime:
67131 case toml::value_t ::offset_datetime:
68132 case toml::value_t ::local_date:
69133 case toml::value_t ::local_time: {
70134 if (experimentalFeatureSettings.isEnabled (Xp::ParseTomlTimestamps)) {
135+ #if HAVE_TOML11_4
136+ normalizeDatetimeFormat (t);
137+ #endif
71138 auto attrs = state.buildBindings (2 );
72139 attrs.alloc (" _type" ).mkString (" timestamp" );
73140 std::ostringstream s;
@@ -80,16 +147,24 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Va
80147 throw std::runtime_error (" Dates and times are not supported" );
81148 }
82149 } break ;
83- ;
84150 case toml::value_t ::empty:
85151 v.mkNull ();
86152 break ;
87- ;
88153 }
89154 };
90155
91156 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+ ));
93168 } catch (std::exception & e) { // TODO: toml::syntax_error
94169 state.error <EvalError>(" while parsing TOML: %s" , e.what ()).atPos (pos).debugThrow ();
95170 }
0 commit comments