diff options
Diffstat (limited to 'nixpkgs/pkgs/pkgs-lib')
-rw-r--r-- | nixpkgs/pkgs/pkgs-lib/default.nix | 11 | ||||
-rw-r--r-- | nixpkgs/pkgs/pkgs-lib/formats.nix | 455 | ||||
-rw-r--r-- | nixpkgs/pkgs/pkgs-lib/formats/java-properties/default.nix | 132 | ||||
-rw-r--r-- | nixpkgs/pkgs/pkgs-lib/formats/java-properties/test/Main.java | 27 | ||||
-rw-r--r-- | nixpkgs/pkgs/pkgs-lib/formats/java-properties/test/default.nix | 92 | ||||
-rw-r--r-- | nixpkgs/pkgs/pkgs-lib/tests/default.nix | 45 | ||||
-rw-r--r-- | nixpkgs/pkgs/pkgs-lib/tests/formats.nix | 254 |
7 files changed, 1016 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/pkgs-lib/default.nix b/nixpkgs/pkgs/pkgs-lib/default.nix new file mode 100644 index 000000000000..113dcebf8c68 --- /dev/null +++ b/nixpkgs/pkgs/pkgs-lib/default.nix @@ -0,0 +1,11 @@ +# pkgs-lib is for functions and values that can't be in lib because +# they depend on some packages. This notably is *not* for supporting package +# building, instead pkgs/build-support is the place for that. +{ lib, pkgs }: { + # setting format types and generators. These do not fit in lib/types.nix, + # because they depend on pkgs for rendering some formats + formats = import ./formats.nix { + inherit lib pkgs; + }; +} + diff --git a/nixpkgs/pkgs/pkgs-lib/formats.nix b/nixpkgs/pkgs/pkgs-lib/formats.nix new file mode 100644 index 000000000000..3a47d3dc849c --- /dev/null +++ b/nixpkgs/pkgs/pkgs-lib/formats.nix @@ -0,0 +1,455 @@ +{ lib, pkgs }: +rec { + + /* + + Every following entry represents a format for program configuration files + used for `settings`-style options (see https://github.com/NixOS/rfcs/pull/42). + Each entry should look as follows: + + <format> = <parameters>: { + # ^^ Parameters for controlling the format + + # The module system type most suitable for representing such a format + # The description needs to be overwritten for recursive types + type = ...; + + # Utility functions for convenience, or special interactions with the + # format (optional) + lib = { + exampleFunction = ... + # Types specific to the format (optional) + types = { ... }; + ... + }; + + # generate :: Name -> Value -> Path + # A function for generating a file with a value of such a type + generate = ...; + + }); + */ + + + inherit (import ./formats/java-properties/default.nix { inherit lib pkgs; }) + javaProperties; + + json = {}: { + + type = with lib.types; let + valueType = nullOr (oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ]) // { + description = "JSON value"; + }; + in valueType; + + generate = name: value: pkgs.callPackage ({ runCommand, jq }: runCommand name { + nativeBuildInputs = [ jq ]; + value = builtins.toJSON value; + passAsFile = [ "value" ]; + } '' + jq . "$valuePath"> $out + '') {}; + + }; + + yaml = {}: { + + generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name { + nativeBuildInputs = [ remarshal ]; + value = builtins.toJSON value; + passAsFile = [ "value" ]; + } '' + json2yaml "$valuePath" "$out" + '') {}; + + type = with lib.types; let + valueType = nullOr (oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ]) // { + description = "YAML value"; + }; + in valueType; + + }; + + ini = { + # Represents lists as duplicate keys + listsAsDuplicateKeys ? false, + # Alternative to listsAsDuplicateKeys, converts list to non-list + # listToValue :: [IniAtom] -> IniAtom + listToValue ? null, + ... + }@args: + assert !listsAsDuplicateKeys || listToValue == null; + { + + type = with lib.types; let + + singleIniAtom = nullOr (oneOf [ + bool + int + float + str + ]) // { + description = "INI atom (null, bool, int, float or string)"; + }; + + iniAtom = + if listsAsDuplicateKeys then + coercedTo singleIniAtom lib.singleton (listOf singleIniAtom) // { + description = singleIniAtom.description + " or a list of them for duplicate keys"; + } + else if listToValue != null then + coercedTo singleIniAtom lib.singleton (nonEmptyListOf singleIniAtom) // { + description = singleIniAtom.description + " or a non-empty list of them"; + } + else + singleIniAtom; + + in attrsOf (attrsOf iniAtom); + + generate = name: value: + let + transformedValue = + if listToValue != null + then + lib.mapAttrs (section: lib.mapAttrs (key: val: + if lib.isList val then listToValue val else val + )) value + else value; + in pkgs.writeText name (lib.generators.toINI (removeAttrs args ["listToValue"]) transformedValue); + + }; + + keyValue = { + # Represents lists as duplicate keys + listsAsDuplicateKeys ? false, + # Alternative to listsAsDuplicateKeys, converts list to non-list + # listToValue :: [Atom] -> Atom + listToValue ? null, + ... + }@args: + assert !listsAsDuplicateKeys || listToValue == null; + { + + type = with lib.types; let + + singleAtom = nullOr (oneOf [ + bool + int + float + str + ]) // { + description = "atom (null, bool, int, float or string)"; + }; + + atom = + if listsAsDuplicateKeys then + coercedTo singleAtom lib.singleton (listOf singleAtom) // { + description = singleAtom.description + " or a list of them for duplicate keys"; + } + else if listToValue != null then + coercedTo singleAtom lib.singleton (nonEmptyListOf singleAtom) // { + description = singleAtom.description + " or a non-empty list of them"; + } + else + singleAtom; + + in attrsOf atom; + + generate = name: value: + let + transformedValue = + if listToValue != null + then + lib.mapAttrs (key: val: + if lib.isList val then listToValue val else val + ) value + else value; + in pkgs.writeText name (lib.generators.toKeyValue (removeAttrs args ["listToValue"]) transformedValue); + + }; + + gitIni = { listsAsDuplicateKeys ? false, ... }@args: { + + type = with lib.types; let + + iniAtom = (ini args).type/*attrsOf*/.functor.wrapped/*attrsOf*/.functor.wrapped; + + in attrsOf (attrsOf (either iniAtom (attrsOf iniAtom))); + + generate = name: value: pkgs.writeText name (lib.generators.toGitINI value); + }; + + toml = {}: json {} // { + type = with lib.types; let + valueType = oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ] // { + description = "TOML value"; + }; + in valueType; + + generate = name: value: pkgs.callPackage ({ runCommand, remarshal }: runCommand name { + nativeBuildInputs = [ remarshal ]; + value = builtins.toJSON value; + passAsFile = [ "value" ]; + } '' + json2toml "$valuePath" "$out" + '') {}; + + }; + + /* For configurations of Elixir project, like config.exs or runtime.exs + + Most Elixir project are configured using the [Config] Elixir DSL + + Since Elixir has more types than Nix, we need a way to map Nix types to + more than 1 Elixir type. To that end, this format provides its own library, + and its own set of types. + + To be more detailed, a Nix attribute set could correspond in Elixir to a + [Keyword list] (the more common type), or it could correspond to a [Map]. + + A Nix string could correspond in Elixir to a [String] (also called + "binary"), an [Atom], or a list of chars (usually discouraged). + + A Nix array could correspond in Elixir to a [List] or a [Tuple]. + + Some more types exists, like records, regexes, but since they are less used, + we can leave the `mkRaw` function as an escape hatch. + + For more information on how to use this format in modules, please refer to + the Elixir section of the Nixos documentation. + + TODO: special Elixir values doesn't show up nicely in the documentation + + [Config]: <https://hexdocs.pm/elixir/Config.html> + [Keyword list]: <https://hexdocs.pm/elixir/Keyword.html> + [Map]: <https://hexdocs.pm/elixir/Map.html> + [String]: <https://hexdocs.pm/elixir/String.html> + [Atom]: <https://hexdocs.pm/elixir/Atom.html> + [List]: <https://hexdocs.pm/elixir/List.html> + [Tuple]: <https://hexdocs.pm/elixir/Tuple.html> + */ + elixirConf = { elixir ? pkgs.elixir }: + with lib; let + toElixir = value: with builtins; + if value == null then "nil" else + if value == true then "true" else + if value == false then "false" else + if isInt value || isFloat value then toString value else + if isString value then string value else + if isAttrs value then attrs value else + if isList value then list value else + abort "formats.elixirConf: should never happen (value = ${value})"; + + escapeElixir = escape [ "\\" "#" "\"" ]; + string = value: "\"${escapeElixir value}\""; + + attrs = set: + if set ? _elixirType then specialType set + else + let + toKeyword = name: value: "${name}: ${toElixir value}"; + keywordList = concatStringsSep ", " (mapAttrsToList toKeyword set); + in + "[" + keywordList + "]"; + + listContent = values: concatStringsSep ", " (map toElixir values); + + list = values: "[" + (listContent values) + "]"; + + specialType = { value, _elixirType }: + if _elixirType == "raw" then value else + if _elixirType == "atom" then value else + if _elixirType == "map" then elixirMap value else + if _elixirType == "tuple" then tuple value else + abort "formats.elixirConf: should never happen (_elixirType = ${_elixirType})"; + + elixirMap = set: + let + toEntry = name: value: "${toElixir name} => ${toElixir value}"; + entries = concatStringsSep ", " (mapAttrsToList toEntry set); + in + "%{${entries}}"; + + tuple = values: "{${listContent values}}"; + + toConf = values: + let + keyConfig = rootKey: key: value: + "config ${rootKey}, ${key}, ${toElixir value}"; + keyConfigs = rootKey: values: mapAttrsToList (keyConfig rootKey) values; + rootConfigs = flatten (mapAttrsToList keyConfigs values); + in + '' + import Config + + ${concatStringsSep "\n" rootConfigs} + ''; + in + { + type = with lib.types; let + valueType = nullOr + (oneOf [ + bool + int + float + str + (attrsOf valueType) + (listOf valueType) + ]) // { + description = "Elixir value"; + }; + in + attrsOf (attrsOf (valueType)); + + lib = + let + mkRaw = value: { + inherit value; + _elixirType = "raw"; + }; + + in + { + inherit mkRaw; + + /* Fetch an environment variable at runtime, with optional fallback + */ + mkGetEnv = { envVariable, fallback ? null }: + mkRaw "System.get_env(${toElixir envVariable}, ${toElixir fallback})"; + + /* Make an Elixir atom. + + Note: lowercase atoms still need to be prefixed by ':' + */ + mkAtom = value: { + inherit value; + _elixirType = "atom"; + }; + + /* Make an Elixir tuple out of a list. + */ + mkTuple = value: { + inherit value; + _elixirType = "tuple"; + }; + + /* Make an Elixir map out of an attribute set. + */ + mkMap = value: { + inherit value; + _elixirType = "map"; + }; + + /* Contains Elixir types. Every type it exports can also be replaced + by raw Elixir code (i.e. every type is `either type rawElixir`). + + It also reexports standard types, wrapping them so that they can + also be raw Elixir. + */ + types = with lib.types; let + isElixirType = type: x: (x._elixirType or "") == type; + + rawElixir = mkOptionType { + name = "rawElixir"; + description = "raw elixir"; + check = isElixirType "raw"; + }; + + elixirOr = other: either other rawElixir; + in + { + inherit rawElixir elixirOr; + + atom = elixirOr (mkOptionType { + name = "elixirAtom"; + description = "elixir atom"; + check = isElixirType "atom"; + }); + + tuple = elixirOr (mkOptionType { + name = "elixirTuple"; + description = "elixir tuple"; + check = isElixirType "tuple"; + }); + + map = elixirOr (mkOptionType { + name = "elixirMap"; + description = "elixir map"; + check = isElixirType "map"; + }); + # Wrap standard types, since anything in the Elixir configuration + # can be raw Elixir + } // lib.mapAttrs (_name: type: elixirOr type) lib.types; + }; + + generate = name: value: pkgs.runCommand name + { + value = toConf value; + passAsFile = [ "value" ]; + nativeBuildInputs = [ elixir ]; + } '' + cp "$valuePath" "$out" + mix format "$out" + ''; + }; + + # Outputs a succession of Python variable assignments + # Useful for many Django-based services + pythonVars = {}: { + type = with lib.types; let + valueType = nullOr(oneOf [ + bool + float + int + path + str + (attrsOf valueType) + (listOf valueType) + ]) // { + description = "Python value"; + }; + in attrsOf valueType; + generate = name: value: pkgs.callPackage ({ runCommand, python3, black }: runCommand name { + nativeBuildInputs = [ python3 black ]; + value = builtins.toJSON value; + pythonGen = '' + import json + import os + + with open(os.environ["valuePath"], "r") as f: + for key, value in json.load(f).items(): + print(f"{key} = {repr(value)}") + ''; + passAsFile = [ "value" "pythonGen" ]; + } '' + cat "$valuePath" + python3 "$pythonGenPath" > $out + black $out + '') {}; + }; + +} diff --git a/nixpkgs/pkgs/pkgs-lib/formats/java-properties/default.nix b/nixpkgs/pkgs/pkgs-lib/formats/java-properties/default.nix new file mode 100644 index 000000000000..d3a4761f0f80 --- /dev/null +++ b/nixpkgs/pkgs/pkgs-lib/formats/java-properties/default.nix @@ -0,0 +1,132 @@ +{ lib, pkgs }: +let + inherit (lib) types; + inherit (types) attrsOf oneOf coercedTo str bool int float package; +in +{ + javaProperties = { comment ? "Generated with Nix", boolToString ? lib.boolToString }: { + + # Design note: + # A nested representation of inevitably leads to bad UX: + # 1. keys like "a.b" must be disallowed, or + # the addition of options in a freeformType module + # become breaking changes + # 2. adding a value for "a" after "a"."b" was already + # defined leads to a somewhat hard to understand + # Nix error, because that's not something you can + # do with attrset syntax. Workaround: "a"."", but + # that's too little too late. Another workaround: + # mkMerge [ { a = ...; } { a.b = ...; } ]. + # + # Choosing a non-nested representation does mean that + # we sacrifice the ability to override at the (conceptual) + # hierarchical levels, _if_ an application exhibits those. + # + # Some apps just use periods instead of spaces in an odd + # mix of attempted categorization and natural language, + # with no meaningful hierarchy. + # + # We _can_ choose to support hierarchical config files + # via nested attrsets, but the module author should + # make sure that problem (2) does not occur. + type = let + elemType = + oneOf ([ + # `package` isn't generalized to `path` because path values + # are ambiguous. Are they host path strings (toString /foo/bar) + # or should they be added to the store? ("${/foo/bar}") + # The user must decide. + (coercedTo package toString str) + + (coercedTo bool boolToString str) + (coercedTo int toString str) + (coercedTo float toString str) + ]) + // { description = "string, package, bool, int or float"; }; + in attrsOf elemType; + + generate = name: value: + pkgs.runCommandLocal name + { + # Requirements + # ============ + # + # 1. Strings in Nix carry over to the same + # strings in Java => need proper escapes + # 2. Generate files quickly + # - A JVM would have to match the app's + # JVM to avoid build closure bloat + # - Even then, JVM startup would slow + # down config generation. + # + # + # Implementation + # ============== + # + # Escaping has two steps + # + # 1. jq + # Escape known separators, in order not + # to break up the keys and values. + # This handles typical whitespace correctly, + # but may produce garbage for other control + # characters. + # + # 2. iconv + # Escape >ascii code points to java escapes, + # as .properties files are supposed to be + # encoded in ISO 8859-1. It's an old format. + # UTF-8 behavior may exist in some apps and + # libraries, but we can't rely on this in + # general. + + passAsFile = [ "value" ]; + value = builtins.toJSON value; + nativeBuildInputs = [ + pkgs.jq + pkgs.libiconvReal + ]; + + jqCode = + let + main = '' + to_entries + | .[] + | "\( + .key + | ${commonEscapes} + | gsub(" "; "\\ ") + | gsub("="; "\\=") + ) = \( + .value + | ${commonEscapes} + | gsub("^ "; "\\ ") + | gsub("\\n "; "\n\\ ") + )" + ''; + # Most escapes are equal for both keys and values. + commonEscapes = '' + gsub("\\\\"; "\\\\") + | gsub("\\n"; "\\n\\\n") + | gsub("#"; "\\#") + | gsub("!"; "\\!") + | gsub("\\t"; "\\t") + | gsub("\r"; "\\r") + ''; + in + main; + + inputEncoding = "UTF-8"; + + inherit comment; + + } '' + ( + echo "$comment" | while read -r ln; do echo "# $ln"; done + echo + jq -r --arg hash '#' "$jqCode" "$valuePath" \ + | iconv --from-code "$inputEncoding" --to-code JAVA \ + ) > "$out" + ''; + }; +} diff --git a/nixpkgs/pkgs/pkgs-lib/formats/java-properties/test/Main.java b/nixpkgs/pkgs/pkgs-lib/formats/java-properties/test/Main.java new file mode 100644 index 000000000000..dc83944f24b0 --- /dev/null +++ b/nixpkgs/pkgs/pkgs-lib/formats/java-properties/test/Main.java @@ -0,0 +1,27 @@ +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.Properties; +import java.util.SortedSet; +import java.util.TreeSet; + +class Main { + public static void main (String args[]) { + try { + InputStream input = new FileInputStream(args[0]); + Properties prop = new Properties(); + prop.load(input); + SortedSet<String> keySet = new TreeSet(prop.keySet()); + for (String key : keySet) { + System.out.println("KEY"); + System.out.println(key); + System.out.println("VALUE"); + System.out.println(prop.get(key)); + System.out.println(""); + } + } catch (Exception e) { + e.printStackTrace(); + System.err.println(e.toString()); + System.exit(1); + } + } +} diff --git a/nixpkgs/pkgs/pkgs-lib/formats/java-properties/test/default.nix b/nixpkgs/pkgs/pkgs-lib/formats/java-properties/test/default.nix new file mode 100644 index 000000000000..4a51179d1c86 --- /dev/null +++ b/nixpkgs/pkgs/pkgs-lib/formats/java-properties/test/default.nix @@ -0,0 +1,92 @@ +{ fetchurl +, formats +, glibcLocales +, jdk +, lib +, stdenv +}: + +# This test primarily tests correct escaping. +# See also testJavaProperties in +# pkgs/pkgs-lib/tests/formats.nix, which tests +# type coercions and is a bit easier to read. + +let + inherit (lib) concatStrings attrValues mapAttrs; + + javaProperties = formats.javaProperties { }; + + input = { + foo = "bar"; + "empty value" = ""; + "typical.dot.syntax" = "com.sun.awt"; + "" = "empty key's value"; + "1" = "2 3"; + "#" = "not a comment # still not"; + "!" = "not a comment!"; + "!a" = "still not! a comment"; + "!b" = "still not ! a comment"; + "dos paths" = "C:\\Program Files\\Nix For Windows\\nix.exe"; + "a \t\nb" = " c"; + "angry \t\nkey" = '' + multi + ${"\tline\r"} + space- + indented + trailing-space${" "} + trailing-space${" "} + value + ''; + "this=not" = "bad"; + "nor = this" = "bad"; + "all stuff" = "foo = bar"; + "unicode big brain" = "e = mc□"; + "ütf-8" = "dûh"; + # NB: Some editors (vscode) show this _whole_ line in right-to-left order + "الجبر" = "أكثر من مجرد أرقام"; + }; + +in +stdenv.mkDerivation { + name = "pkgs.formats.javaProperties-test-${jdk.name}"; + nativeBuildInputs = [ + jdk + glibcLocales + ]; + + # technically should go through the type.merge first, but that's tested + # in tests/formats.nix. + properties = javaProperties.generate "example.properties" input; + + # Expected output as printed by Main.java + passAsFile = [ "expected" ]; + expected = concatStrings (attrValues ( + mapAttrs + (key: value: + '' + KEY + ${key} + VALUE + ${value} + + '' + ) + input + )); + + src = lib.sourceByRegex ./. [ + ".*\.java" + ]; + # On Linux, this can be C.UTF-8, but darwin + zulu requires en_US.UTF-8 + LANG = "en_US.UTF-8"; + buildPhase = '' + javac Main.java + ''; + doCheck = true; + checkPhase = '' + cat -v $properties + java Main $properties >actual + diff -U3 $expectedPath actual + ''; + installPhase = "touch $out"; +} diff --git a/nixpkgs/pkgs/pkgs-lib/tests/default.nix b/nixpkgs/pkgs/pkgs-lib/tests/default.nix new file mode 100644 index 000000000000..ae91e15aa9ef --- /dev/null +++ b/nixpkgs/pkgs/pkgs-lib/tests/default.nix @@ -0,0 +1,45 @@ +# Call nix-build on this file to run all tests in this directory + +# This produces a link farm derivation with the original attrs +# merged on top of it. +# You can run parts of the "hierarchy" with for example: +# nix-build -A java-properties +# See `structured` below. + +{ pkgs ? import ../../.. { } }: +let + inherit (pkgs.lib) mapAttrs mapAttrsToList isDerivation mergeAttrs foldl' attrValues recurseIntoAttrs; + + structured = { + formats = import ./formats.nix { inherit pkgs; }; + java-properties = recurseIntoAttrs { + jdk8 = pkgs.callPackage ../formats/java-properties/test { jdk = pkgs.jdk8; }; + jdk11 = pkgs.callPackage ../formats/java-properties/test { jdk = pkgs.jdk11_headless; }; + jdk17 = pkgs.callPackage ../formats/java-properties/test { jdk = pkgs.jdk17_headless; }; + }; + }; + + flatten = prefix: as: + foldl' + mergeAttrs + { } + (attrValues + (mapAttrs + (k: v: + if isDerivation v + then { "${prefix}${k}" = v; } + else if v?recurseForDerivations + then flatten "${prefix}${k}-" (removeAttrs v [ "recurseForDerivations" ]) + else builtins.trace v throw "expected derivation or recurseIntoAttrs") + as + ) + ); +in + +# It has to be a link farm for inclusion in the hydra unstable jobset. +pkgs.linkFarm "pkgs-lib-formats-tests" + (mapAttrsToList + (k: v: { name = k; path = v; }) + (flatten "" structured) + ) +// structured diff --git a/nixpkgs/pkgs/pkgs-lib/tests/formats.nix b/nixpkgs/pkgs/pkgs-lib/tests/formats.nix new file mode 100644 index 000000000000..80df247f7b6a --- /dev/null +++ b/nixpkgs/pkgs/pkgs-lib/tests/formats.nix @@ -0,0 +1,254 @@ +{ pkgs }: +let + inherit (pkgs) lib formats; +in +with lib; +let + + evalFormat = format: args: def: + let + formatSet = format args; + config = formatSet.type.merge [] (imap1 (n: def: { + # We check the input values, so that + # - we don't write nonsensical tests that will impede progress + # - the test author has a slightly more realistic view of the + # final format during development. + value = lib.throwIfNot (formatSet.type.check def) (builtins.trace def "definition does not pass the type's check function") def; + file = "def${toString n}"; + }) [ def ]); + in formatSet.generate "test-format-file" config; + + runBuildTest = name: { drv, expected }: pkgs.runCommand name { + passAsFile = ["expected"]; + inherit expected drv; + } '' + if diff -u "$expectedPath" "$drv"; then + touch "$out" + else + echo + echo "Got different values than expected; diff above." + exit 1 + fi + ''; + + runBuildTests = tests: pkgs.linkFarm "nixpkgs-pkgs-lib-format-tests" (mapAttrsToList (name: value: { inherit name; path = runBuildTest name value; }) (filterAttrs (name: value: value != null) tests)); + +in runBuildTests { + + testJsonAtoms = { + drv = evalFormat formats.json {} { + null = null; + false = false; + true = true; + int = 10; + float = 3.141; + str = "foo"; + attrs.foo = null; + list = [ null null ]; + path = ./formats.nix; + }; + expected = '' + { + "attrs": { + "foo": null + }, + "false": false, + "float": 3.141, + "int": 10, + "list": [ + null, + null + ], + "null": null, + "path": "${./formats.nix}", + "str": "foo", + "true": true + } + ''; + }; + + testYamlAtoms = { + drv = evalFormat formats.yaml {} { + null = null; + false = false; + true = true; + float = 3.141; + str = "foo"; + attrs.foo = null; + list = [ null null ]; + path = ./formats.nix; + }; + expected = '' + attrs: + foo: null + 'false': false + float: 3.141 + list: + - null + - null + 'null': null + path: ${./formats.nix} + str: foo + 'true': true + ''; + }; + + testIniAtoms = { + drv = evalFormat formats.ini {} { + foo = { + bool = true; + int = 10; + float = 3.141; + str = "string"; + }; + }; + expected = '' + [foo] + bool=true + float=3.141000 + int=10 + str=string + ''; + }; + + testIniDuplicateKeys = { + drv = evalFormat formats.ini { listsAsDuplicateKeys = true; } { + foo = { + bar = [ null true "test" 1.2 10 ]; + baz = false; + qux = "qux"; + }; + }; + expected = '' + [foo] + bar=null + bar=true + bar=test + bar=1.200000 + bar=10 + baz=false + qux=qux + ''; + }; + + testIniListToValue = { + drv = evalFormat formats.ini { listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault {}); } { + foo = { + bar = [ null true "test" 1.2 10 ]; + baz = false; + qux = "qux"; + }; + }; + expected = '' + [foo] + bar=null, true, test, 1.200000, 10 + baz=false + qux=qux + ''; + }; + + testKeyValueAtoms = { + drv = evalFormat formats.keyValue {} { + bool = true; + int = 10; + float = 3.141; + str = "string"; + }; + expected = '' + bool=true + float=3.141000 + int=10 + str=string + ''; + }; + + testKeyValueDuplicateKeys = { + drv = evalFormat formats.keyValue { listsAsDuplicateKeys = true; } { + bar = [ null true "test" 1.2 10 ]; + baz = false; + qux = "qux"; + }; + expected = '' + bar=null + bar=true + bar=test + bar=1.200000 + bar=10 + baz=false + qux=qux + ''; + }; + + testKeyValueListToValue = { + drv = evalFormat formats.keyValue { listToValue = concatMapStringsSep ", " (generators.mkValueStringDefault {}); } { + bar = [ null true "test" 1.2 10 ]; + baz = false; + qux = "qux"; + }; + expected = '' + bar=null, true, test, 1.200000, 10 + baz=false + qux=qux + ''; + }; + + testTomlAtoms = { + drv = evalFormat formats.toml {} { + false = false; + true = true; + int = 10; + float = 3.141; + str = "foo"; + attrs.foo = "foo"; + list = [ 1 2 ]; + level1.level2.level3.level4 = "deep"; + }; + expected = '' + false = false + float = 3.141 + int = 10 + list = [1, 2] + str = "foo" + true = true + + [attrs] + foo = "foo" + + [level1.level2.level3] + level4 = "deep" + ''; + }; + + # This test is responsible for + # 1. testing type coercions + # 2. providing a more readable example test + # Whereas java-properties/default.nix tests the low level escaping, etc. + testJavaProperties = { + drv = evalFormat formats.javaProperties {} { + floaty = 3.1415; + tautologies = true; + contradictions = false; + foo = "bar"; + # # Disallowed at eval time, because it's ambiguous: + # # add to store or convert to string? + # root = /root; + "1" = 2; + package = pkgs.hello; + "ütf 8" = "dûh"; + # NB: Some editors (vscode) show this _whole_ line in right-to-left order + "الجبر" = "أكثر من مجرد أرقام"; + }; + expected = '' + # Generated with Nix + + 1 = 2 + contradictions = false + floaty = 3.141500 + foo = bar + package = ${pkgs.hello} + tautologies = true + \u00fctf\ 8 = d\u00fbh + \u0627\u0644\u062c\u0628\u0631 = \u0623\u0643\u062b\u0631 \u0645\u0646 \u0645\u062c\u0631\u062f \u0623\u0631\u0642\u0627\u0645 + ''; + }; +} |