1{ lib, pkgs }:
2let
3 inherit (lib)
4 boolToString
5 concatStringsSep
6 escape
7 filterAttrs
8 flatten
9 hasPrefix
10 id
11 isAttrs
12 isBool
13 isDerivation
14 isFloat
15 isInt
16 isList
17 isString
18 mapAttrs
19 mapAttrsToList
20 mkOption
21 optionalAttrs
22 optionalString
23 pipe
24 singleton
25 strings
26 toPretty
27 types
28 versionAtLeast
29 warn
30 ;
31
32 inherit (lib.generators)
33 mkValueStringDefault
34 toGitINI
35 toINI
36 toINIWithGlobalSection
37 toKeyValue
38 toLua
39 mkLuaInline
40 ;
41
42 inherit (lib.types)
43 attrsOf
44 atom
45 bool
46 coercedTo
47 either
48 float
49 int
50 listOf
51 luaInline
52 mkOptionType
53 nonEmptyListOf
54 nullOr
55 oneOf
56 path
57 str
58 submodule
59 ;
60
61 # Attributes added accidentally in https://github.com/NixOS/nixpkgs/pull/335232 (2024-08-18)
62 # Deprecated in https://github.com/NixOS/nixpkgs/pull/415666 (2025-06)
63 allowAliases = pkgs.config.allowAliases or false;
64 aliasWarning = name: warn "`formats.${name}` is deprecated; use `lib.types.${name}` instead.";
65 aliases = mapAttrs aliasWarning {
66 inherit
67 attrsOf
68 bool
69 coercedTo
70 either
71 float
72 int
73 listOf
74 luaInline
75 mkOptionType
76 nonEmptyListOf
77 nullOr
78 oneOf
79 path
80 str
81 ;
82 };
83in
84optionalAttrs allowAliases aliases
85// rec {
86
87 /*
88 Every following entry represents a format for program configuration files
89 used for `settings`-style options (see https://github.com/NixOS/rfcs/pull/42).
90 Each entry should look as follows:
91
92 <format> = <parameters>: {
93 # ^^ Parameters for controlling the format
94
95 # The module system type most suitable for representing such a format
96 # The description needs to be overwritten for recursive types
97 type = ...;
98
99 # Utility functions for convenience, or special interactions with the
100 # format (optional)
101 lib = {
102 exampleFunction = ...
103 # Types specific to the format (optional)
104 types = { ... };
105 ...
106 };
107
108 # generate :: Name -> Value -> Path
109 # A function for generating a file with a value of such a type
110 generate = ...;
111
112 });
113
114 Please note that `pkgs` may not always be available for use due to the split
115 options doc build introduced in fc614c37c653, so lazy evaluation of only the
116 'type' field is required.
117 */
118
119 inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; })
120 javaProperties
121 ;
122
123 libconfig = (import ./formats/libconfig/default.nix { inherit lib pkgs; }).format;
124
125 hocon = (import ./formats/hocon/default.nix { inherit lib pkgs; }).format;
126
127 php = (import ./formats/php/default.nix { inherit lib pkgs; }).format;
128
129 json =
130 { }:
131 {
132
133 type =
134 let
135 valueType =
136 nullOr (oneOf [
137 bool
138 int
139 float
140 str
141 path
142 (attrsOf valueType)
143 (listOf valueType)
144 ])
145 // {
146 description = "JSON value";
147 };
148 in
149 valueType;
150
151 generate =
152 name: value:
153 pkgs.callPackage (
154 { runCommand, jq }:
155 runCommand name
156 {
157 nativeBuildInputs = [ jq ];
158 value = builtins.toJSON value;
159 passAsFile = [ "value" ];
160 preferLocalBuild = true;
161 }
162 ''
163 jq . "$valuePath"> $out
164 ''
165 ) { };
166
167 };
168
169 yaml = yaml_1_1;
170
171 yaml_1_1 =
172 { }:
173 {
174 generate =
175 name: value:
176 pkgs.callPackage (
177 { runCommand, remarshal_0_17 }:
178 runCommand name
179 {
180 nativeBuildInputs = [ remarshal_0_17 ];
181 value = builtins.toJSON value;
182 passAsFile = [ "value" ];
183 preferLocalBuild = true;
184 }
185 ''
186 json2yaml "$valuePath" "$out"
187 ''
188 ) { };
189
190 type =
191 let
192 valueType =
193 nullOr (oneOf [
194 bool
195 int
196 float
197 str
198 path
199 (attrsOf valueType)
200 (listOf valueType)
201 ])
202 // {
203 description = "YAML 1.1 value";
204 };
205 in
206 valueType;
207
208 };
209
210 yaml_1_2 =
211 { }:
212 {
213 generate =
214 name: value:
215 pkgs.callPackage (
216 { runCommand, remarshal }:
217 runCommand name
218 {
219 nativeBuildInputs = [ remarshal ];
220 value = builtins.toJSON value;
221 passAsFile = [ "value" ];
222 preferLocalBuild = true;
223 }
224 ''
225 json2yaml "$valuePath" "$out"
226 ''
227 ) { };
228
229 type =
230 let
231 valueType =
232 nullOr (oneOf [
233 bool
234 int
235 float
236 str
237 path
238 (attrsOf valueType)
239 (listOf valueType)
240 ])
241 // {
242 description = "YAML 1.2 value";
243 };
244 in
245 valueType;
246
247 };
248
249 # the ini formats share a lot of code
250 inherit
251 (
252 let
253 singleIniAtom =
254 nullOr (oneOf [
255 bool
256 int
257 float
258 str
259 ])
260 // {
261 description = "INI atom (null, bool, int, float or string)";
262 };
263 iniAtom =
264 {
265 listsAsDuplicateKeys,
266 listToValue,
267 atomsCoercedToLists,
268 }:
269 let
270 singleIniAtomOr =
271 if atomsCoercedToLists then coercedTo singleIniAtom singleton else either singleIniAtom;
272 in
273 if listsAsDuplicateKeys then
274 singleIniAtomOr (listOf singleIniAtom)
275 // {
276 description = singleIniAtom.description + " or a list of them for duplicate keys";
277 }
278 else if listToValue != null then
279 singleIniAtomOr (nonEmptyListOf singleIniAtom)
280 // {
281 description = singleIniAtom.description + " or a non-empty list of them";
282 }
283 else
284 singleIniAtom;
285 iniSection =
286 atom:
287 attrsOf atom
288 // {
289 description = "section of an INI file (attrs of " + atom.description + ")";
290 };
291
292 maybeToList =
293 listToValue:
294 if listToValue != null then
295 mapAttrs (key: val: if isList val then listToValue val else val)
296 else
297 id;
298 in
299 {
300 ini =
301 {
302 # Represents lists as duplicate keys
303 listsAsDuplicateKeys ? false,
304 # Alternative to listsAsDuplicateKeys, converts list to non-list
305 # listToValue :: [IniAtom] -> IniAtom
306 listToValue ? null,
307 # Merge multiple instances of the same key into a list
308 atomsCoercedToLists ? null,
309 ...
310 }@args:
311 assert listsAsDuplicateKeys -> listToValue == null;
312 assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
313 let
314 atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
315 atom = iniAtom {
316 inherit listsAsDuplicateKeys listToValue;
317 atomsCoercedToLists = atomsCoercedToLists';
318 };
319 in
320 {
321
322 type = attrsOf (iniSection atom);
323 lib.types.atom = atom;
324
325 generate =
326 name: value:
327 pipe value [
328 (mapAttrs (_: maybeToList listToValue))
329 (toINI (
330 removeAttrs args [
331 "listToValue"
332 "atomsCoercedToLists"
333 ]
334 ))
335 (pkgs.writeText name)
336 ];
337 };
338
339 iniWithGlobalSection =
340 {
341 # Represents lists as duplicate keys
342 listsAsDuplicateKeys ? false,
343 # Alternative to listsAsDuplicateKeys, converts list to non-list
344 # listToValue :: [IniAtom] -> IniAtom
345 listToValue ? null,
346 # Merge multiple instances of the same key into a list
347 atomsCoercedToLists ? null,
348 ...
349 }@args:
350 assert listsAsDuplicateKeys -> listToValue == null;
351 assert atomsCoercedToLists != null -> (listsAsDuplicateKeys || listToValue != null);
352 let
353 atomsCoercedToLists' = if atomsCoercedToLists == null then false else atomsCoercedToLists;
354 atom = iniAtom {
355 inherit listsAsDuplicateKeys listToValue;
356 atomsCoercedToLists = atomsCoercedToLists';
357 };
358 in
359 {
360 type = submodule {
361 options = {
362 sections = mkOption rec {
363 type = attrsOf (iniSection atom);
364 default = { };
365 description = type.description;
366 };
367 globalSection = mkOption rec {
368 type = iniSection atom;
369 default = { };
370 description = "global " + type.description;
371 };
372 };
373 };
374 lib.types.atom = atom;
375 generate =
376 name:
377 {
378 sections ? { },
379 globalSection ? { },
380 ...
381 }:
382 pkgs.writeText name (
383 toINIWithGlobalSection
384 (removeAttrs args [
385 "listToValue"
386 "atomsCoercedToLists"
387 ])
388 {
389 globalSection = maybeToList listToValue globalSection;
390 sections = mapAttrs (_: maybeToList listToValue) sections;
391 }
392 );
393 };
394
395 gitIni =
396 {
397 listsAsDuplicateKeys ? false,
398 ...
399 }@args:
400 let
401 atom = iniAtom {
402 inherit listsAsDuplicateKeys;
403 listToValue = null;
404 atomsCoercedToLists = false;
405 };
406 in
407 {
408 type = attrsOf (attrsOf (either atom (attrsOf atom)));
409 lib.types.atom = atom;
410 generate = name: value: pkgs.writeText name (toGitINI value);
411 };
412
413 }
414 )
415 ini
416 iniWithGlobalSection
417 gitIni
418 ;
419
420 # As defined by systemd.syntax(7)
421 #
422 # null does not set any value, which allows for RFC42 modules to specify
423 # optional config options.
424 systemd =
425 let
426 mkValueString = mkValueStringDefault { };
427 mkKeyValue = k: v: if v == null then "# ${k} is unset" else "${k} = ${mkValueString v}";
428 in
429 ini {
430 listsAsDuplicateKeys = true;
431 inherit mkKeyValue;
432 };
433
434 keyValue =
435 {
436 # Represents lists as duplicate keys
437 listsAsDuplicateKeys ? false,
438 # Alternative to listsAsDuplicateKeys, converts list to non-list
439 # listToValue :: [Atom] -> Atom
440 listToValue ? null,
441 ...
442 }@args:
443 assert listsAsDuplicateKeys -> listToValue == null;
444 {
445
446 type =
447 let
448
449 singleAtom =
450 nullOr (oneOf [
451 bool
452 int
453 float
454 str
455 ])
456 // {
457 description = "atom (null, bool, int, float or string)";
458 };
459
460 atom =
461 if listsAsDuplicateKeys then
462 coercedTo singleAtom singleton (listOf singleAtom)
463 // {
464 description = singleAtom.description + " or a list of them for duplicate keys";
465 }
466 else if listToValue != null then
467 coercedTo singleAtom singleton (nonEmptyListOf singleAtom)
468 // {
469 description = singleAtom.description + " or a non-empty list of them";
470 }
471 else
472 singleAtom;
473
474 in
475 attrsOf atom;
476
477 generate =
478 name: value:
479 let
480 transformedValue =
481 if listToValue != null then
482 mapAttrs (key: val: if isList val then listToValue val else val) value
483 else
484 value;
485 in
486 pkgs.writeText name (toKeyValue (removeAttrs args [ "listToValue" ]) transformedValue);
487
488 };
489
490 toml =
491 { }:
492 json { }
493 // {
494 type =
495 let
496 valueType =
497 oneOf [
498 bool
499 int
500 float
501 str
502 path
503 (attrsOf valueType)
504 (listOf valueType)
505 ]
506 // {
507 description = "TOML value";
508 };
509 in
510 valueType;
511
512 generate =
513 name: value:
514 pkgs.callPackage (
515 { runCommand, remarshal }:
516 runCommand name
517 {
518 nativeBuildInputs = [ remarshal ];
519 value = builtins.toJSON value;
520 passAsFile = [ "value" ];
521 preferLocalBuild = true;
522 }
523 ''
524 json2toml "$valuePath" "$out"
525 ''
526 ) { };
527
528 };
529
530 /*
531 dzikoysk's CDN format, see https://github.com/dzikoysk/cdn
532
533 The result is almost identical to YAML when there are no nested properties,
534 but differs enough in the other case to warrant a separate format.
535 (see https://github.com/dzikoysk/cdn#supported-formats)
536
537 Currently used by Panda, Reposilite, and FunnyGuilds (as per the repo's readme).
538 */
539 cdn =
540 { }:
541 json { }
542 // {
543 type =
544 let
545 valueType =
546 nullOr (oneOf [
547 bool
548 int
549 float
550 str
551 path
552 (attrsOf valueType)
553 (listOf valueType)
554 ])
555 // {
556 description = "CDN value";
557 };
558 in
559 valueType;
560
561 generate =
562 name: value:
563 pkgs.callPackage (
564 { runCommand, json2cdn }:
565 runCommand name
566 {
567 nativeBuildInputs = [ json2cdn ];
568 value = builtins.toJSON value;
569 passAsFile = [ "value" ];
570 preferLocalBuild = true;
571 }
572 ''
573 json2cdn "$valuePath" > $out
574 ''
575 ) { };
576 };
577
578 /*
579 For configurations of Elixir project, like config.exs or runtime.exs
580
581 Most Elixir project are configured using the [Config] Elixir DSL
582
583 Since Elixir has more types than Nix, we need a way to map Nix types to
584 more than 1 Elixir type. To that end, this format provides its own library,
585 and its own set of types.
586
587 To be more detailed, a Nix attribute set could correspond in Elixir to a
588 [Keyword list] (the more common type), or it could correspond to a [Map].
589
590 A Nix string could correspond in Elixir to a [String] (also called
591 "binary"), an [Atom], or a list of chars (usually discouraged).
592
593 A Nix array could correspond in Elixir to a [List] or a [Tuple].
594
595 Some more types exists, like records, regexes, but since they are less used,
596 we can leave the `mkRaw` function as an escape hatch.
597
598 For more information on how to use this format in modules, please refer to
599 the Elixir section of the Nixos documentation.
600
601 TODO: special Elixir values doesn't show up nicely in the documentation
602
603 [Config]: <https://hexdocs.pm/elixir/Config.html>
604 [Keyword list]: <https://hexdocs.pm/elixir/Keyword.html>
605 [Map]: <https://hexdocs.pm/elixir/Map.html>
606 [String]: <https://hexdocs.pm/elixir/String.html>
607 [Atom]: <https://hexdocs.pm/elixir/Atom.html>
608 [List]: <https://hexdocs.pm/elixir/List.html>
609 [Tuple]: <https://hexdocs.pm/elixir/Tuple.html>
610 */
611 elixirConf =
612 {
613 elixir ? pkgs.elixir,
614 }:
615 let
616 toElixir =
617 value:
618 if value == null then
619 "nil"
620 else if value == true then
621 "true"
622 else if value == false then
623 "false"
624 else if isInt value || isFloat value then
625 toString value
626 else if isString value then
627 string value
628 else if isAttrs value then
629 attrs value
630 else if isList value then
631 list value
632 else
633 abort "formats.elixirConf: should never happen (value = ${value})";
634
635 escapeElixir = escape [
636 "\\"
637 "#"
638 "\""
639 ];
640 string = value: "\"${escapeElixir value}\"";
641
642 attrs =
643 set:
644 if set ? _elixirType then
645 specialType set
646 else
647 let
648 toKeyword = name: value: "${name}: ${toElixir value}";
649 keywordList = concatStringsSep ", " (mapAttrsToList toKeyword set);
650 in
651 "[" + keywordList + "]";
652
653 listContent = values: concatStringsSep ", " (map toElixir values);
654
655 list = values: "[" + (listContent values) + "]";
656
657 specialType =
658 { value, _elixirType }:
659 if _elixirType == "raw" then
660 value
661 else if _elixirType == "atom" then
662 value
663 else if _elixirType == "map" then
664 elixirMap value
665 else if _elixirType == "tuple" then
666 tuple value
667 else
668 abort "formats.elixirConf: should never happen (_elixirType = ${_elixirType})";
669
670 elixirMap =
671 set:
672 let
673 toEntry = name: value: "${toElixir name} => ${toElixir value}";
674 entries = concatStringsSep ", " (mapAttrsToList toEntry set);
675 in
676 "%{${entries}}";
677
678 tuple = values: "{${listContent values}}";
679
680 toConf =
681 values:
682 let
683 keyConfig =
684 rootKey: key: value:
685 "config ${rootKey}, ${key}, ${toElixir value}";
686 keyConfigs = rootKey: values: mapAttrsToList (keyConfig rootKey) values;
687 rootConfigs = flatten (mapAttrsToList keyConfigs values);
688 in
689 ''
690 import Config
691
692 ${concatStringsSep "\n" rootConfigs}
693 '';
694 in
695 {
696 type =
697 let
698 valueType =
699 nullOr (oneOf [
700 bool
701 int
702 float
703 str
704 (attrsOf valueType)
705 (listOf valueType)
706 ])
707 // {
708 description = "Elixir value";
709 };
710 in
711 attrsOf (attrsOf (valueType));
712
713 lib =
714 let
715 mkRaw = value: {
716 inherit value;
717 _elixirType = "raw";
718 };
719
720 in
721 {
722 inherit mkRaw;
723
724 # Fetch an environment variable at runtime, with optional fallback
725 mkGetEnv =
726 {
727 envVariable,
728 fallback ? null,
729 }:
730 mkRaw "System.get_env(${toElixir envVariable}, ${toElixir fallback})";
731
732 /*
733 Make an Elixir atom.
734
735 Note: lowercase atoms still need to be prefixed by ':'
736 */
737 mkAtom = value: {
738 inherit value;
739 _elixirType = "atom";
740 };
741
742 # Make an Elixir tuple out of a list.
743 mkTuple = value: {
744 inherit value;
745 _elixirType = "tuple";
746 };
747
748 # Make an Elixir map out of an attribute set.
749 mkMap = value: {
750 inherit value;
751 _elixirType = "map";
752 };
753
754 /*
755 Contains Elixir types. Every type it exports can also be replaced
756 by raw Elixir code (i.e. every type is `either type rawElixir`).
757
758 It also reexports standard types, wrapping them so that they can
759 also be raw Elixir.
760 */
761 types =
762 let
763 isElixirType = type: x: (x._elixirType or "") == type;
764
765 rawElixir = mkOptionType {
766 name = "rawElixir";
767 description = "raw elixir";
768 check = isElixirType "raw";
769 };
770
771 elixirOr = other: either other rawElixir;
772 in
773 {
774 inherit rawElixir elixirOr;
775
776 atom = elixirOr (mkOptionType {
777 name = "elixirAtom";
778 description = "elixir atom";
779 check = isElixirType "atom";
780 });
781
782 tuple = elixirOr (mkOptionType {
783 name = "elixirTuple";
784 description = "elixir tuple";
785 check = isElixirType "tuple";
786 });
787
788 map = elixirOr (mkOptionType {
789 name = "elixirMap";
790 description = "elixir map";
791 check = isElixirType "map";
792 });
793 # Wrap standard types, since anything in the Elixir configuration
794 # can be raw Elixir
795 }
796 // mapAttrs (_name: type: elixirOr type) types;
797 };
798
799 generate =
800 name: value:
801 pkgs.runCommand name
802 {
803 value = toConf value;
804 passAsFile = [ "value" ];
805 nativeBuildInputs = [ elixir ];
806 preferLocalBuild = true;
807 }
808 ''
809 cp "$valuePath" "$out"
810 mix format "$out"
811 '';
812 };
813
814 lua =
815 {
816 asBindings ? false,
817 multiline ? true,
818 columnWidth ? 100,
819 indentWidth ? 2,
820 indentUsingTabs ? false,
821 }:
822 {
823 type =
824 let
825 valueType =
826 nullOr (oneOf [
827 bool
828 float
829 int
830 path
831 str
832 luaInline
833 (attrsOf valueType)
834 (listOf valueType)
835 ])
836 // {
837 description = "lua value";
838 descriptionClass = "noun";
839 };
840 in
841 if asBindings then attrsOf valueType else valueType;
842 generate =
843 name: value:
844 pkgs.callPackage (
845 { runCommand, stylua }:
846 runCommand name
847 {
848 nativeBuildInputs = [ stylua ];
849 inherit columnWidth;
850 inherit indentWidth;
851 indentType = if indentUsingTabs then "Tabs" else "Spaces";
852 value = toLua { inherit asBindings multiline; } value;
853 passAsFile = [ "value" ];
854 preferLocalBuild = true;
855 }
856 ''
857 ${optionalString (!asBindings) ''
858 echo -n 'return ' >> $out
859 ''}
860 cat $valuePath >> $out
861 stylua \
862 --no-editorconfig \
863 --line-endings Unix \
864 --column-width $columnWidth \
865 --indent-width $indentWidth \
866 --indent-type $indentType \
867 $out
868 ''
869 ) { };
870 # Alias for mkLuaInline
871 lib.mkRaw = lib.mkLuaInline;
872 };
873
874 nixConf =
875 {
876 package,
877 version,
878 extraOptions ? "",
879 checkAllErrors ? true,
880 checkConfig ? true,
881 }:
882 let
883 isNixAtLeast = versionAtLeast version;
884 in
885 assert isNixAtLeast "2.2";
886 {
887 type =
888 let
889 atomType = nullOr (oneOf [
890 bool
891 int
892 float
893 str
894 path
895 package
896 ]);
897 in
898 attrsOf atomType;
899 generate =
900 name: value:
901 let
902
903 # note that list type has been omitted here as the separator varies, see `nix.settings.*`
904 mkValueString =
905 v:
906 if v == null then
907 ""
908 else if isInt v then
909 toString v
910 else if isBool v then
911 boolToString v
912 else if isFloat v then
913 strings.floatToString v
914 else if isDerivation v then
915 toString v
916 else if builtins.isPath v then
917 toString v
918 else if isString v then
919 v
920 else if strings.isConvertibleWithToString v then
921 toString v
922 else
923 abort "The nix conf value: ${toPretty { } v} can not be encoded";
924
925 mkKeyValue = k: v: "${escape [ "=" ] k} = ${mkValueString v}";
926
927 mkKeyValuePairs = attrs: concatStringsSep "\n" (mapAttrsToList mkKeyValue attrs);
928
929 isExtra = key: hasPrefix "extra-" key;
930
931 in
932 pkgs.writeTextFile {
933 inherit name;
934 # workaround for https://github.com/NixOS/nix/issues/9487
935 # extra-* settings must come after their non-extra counterpart
936 text = ''
937 # WARNING: this file is generated from the nix.* options in
938 # your NixOS configuration, typically
939 # /etc/nixos/configuration.nix. Do not edit it!
940 ${mkKeyValuePairs (filterAttrs (key: _: !(isExtra key)) value)}
941 ${mkKeyValuePairs (filterAttrs (key: _: isExtra key) value)}
942 ${extraOptions}
943 '';
944 checkPhase = lib.optionalString checkConfig (
945 if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then
946 ''
947 echo "Ignoring validation for cross-compilation"
948 ''
949 else
950 let
951 showCommand = if isNixAtLeast "2.20pre" then "config show" else "show-config";
952 in
953 ''
954 echo "Validating generated nix.conf"
955 ln -s $out ./nix.conf
956 set -e
957 set +o pipefail
958 NIX_CONF_DIR=$PWD \
959 ${package}/bin/nix ${showCommand} ${optionalString (isNixAtLeast "2.3pre") "--no-net"} \
960 ${optionalString (isNixAtLeast "2.4pre") "--option experimental-features nix-command"} \
961 |& sed -e 's/^warning:/error:/' \
962 | (! grep '${if checkAllErrors then "^error:" else "^error: unknown setting"}')
963 set -o pipefail
964 ''
965 );
966 };
967 };
968
969 # Outputs a succession of Python variable assignments
970 # Useful for many Django-based services
971 pythonVars =
972 { }:
973 {
974 type =
975 let
976 valueType =
977 nullOr (oneOf [
978 bool
979 float
980 int
981 path
982 str
983 (attrsOf valueType)
984 (listOf valueType)
985 ])
986 // {
987 description = "Python value";
988 };
989 in
990 attrsOf valueType;
991
992 lib = {
993 mkRaw = value: {
994 inherit value;
995 _type = "raw";
996 };
997 };
998
999 generate =
1000 name: value:
1001 pkgs.callPackage (
1002 {
1003 runCommand,
1004 python3,
1005 black,
1006 }:
1007 runCommand name
1008 {
1009 nativeBuildInputs = [
1010 python3
1011 black
1012 ];
1013 imports = builtins.toJSON (value._imports or [ ]);
1014 value = builtins.toJSON (removeAttrs value [ "_imports" ]);
1015 pythonGen = ''
1016 import json
1017 import os
1018
1019 def recursive_repr(value: any) -> str:
1020 if type(value) is list:
1021 return '\n'.join([
1022 "[",
1023 *[recursive_repr(x) + "," for x in value],
1024 "]",
1025 ])
1026 elif type(value) is dict and value.get("_type") == "raw":
1027 return value.get("value")
1028 elif type(value) is dict:
1029 return '\n'.join([
1030 "{",
1031 *[f"'{k.replace('\''', '\\\''')}': {recursive_repr(v)}," for k, v in value.items()],
1032 "}",
1033 ])
1034 else:
1035 return repr(value)
1036
1037 with open(os.environ["importsPath"], "r") as f:
1038 imports = json.load(f)
1039 if imports is not None:
1040 for i in imports:
1041 print(f"import {i}")
1042 print()
1043
1044 with open(os.environ["valuePath"], "r") as f:
1045 for key, value in json.load(f).items():
1046 print(f"{key} = {recursive_repr(value)}")
1047 '';
1048 passAsFile = [
1049 "imports"
1050 "value"
1051 "pythonGen"
1052 ];
1053 preferLocalBuild = true;
1054 }
1055 ''
1056 cat "$valuePath"
1057 python3 "$pythonGenPath" > $out
1058 black $out
1059 ''
1060 ) { };
1061 };
1062
1063 xml =
1064 {
1065 format ? "badgerfish",
1066 withHeader ? true,
1067 }:
1068 if format == "badgerfish" then
1069 {
1070 type =
1071 let
1072 valueType =
1073 nullOr (oneOf [
1074 bool
1075 int
1076 float
1077 str
1078 path
1079 (attrsOf valueType)
1080 (listOf valueType)
1081 ])
1082 // {
1083 description = "XML value";
1084 };
1085 in
1086 valueType;
1087
1088 generate =
1089 name: value:
1090 pkgs.callPackage (
1091 {
1092 runCommand,
1093 libxml2Python,
1094 python3Packages,
1095 }:
1096 runCommand name
1097 {
1098 nativeBuildInputs = [
1099 python3Packages.xmltodict
1100 libxml2Python
1101 ];
1102 value = builtins.toJSON value;
1103 pythonGen = ''
1104 import json
1105 import os
1106 import xmltodict
1107
1108 with open(os.environ["valuePath"], "r") as f:
1109 print(xmltodict.unparse(json.load(f), full_document=${
1110 if withHeader then "True" else "False"
1111 }, pretty=True, indent=" " * 2))
1112 '';
1113 passAsFile = [
1114 "value"
1115 "pythonGen"
1116 ];
1117 preferLocalBuild = true;
1118 }
1119 ''
1120 python3 "$pythonGenPath" > $out
1121 xmllint $out > /dev/null
1122 ''
1123 ) { };
1124 }
1125 else
1126 throw "pkgs.formats.xml: Unknown format: ${format}";
1127
1128}