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