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}