Nix now supports floats & we can pretty easily map them to Plist’s <real></real> type. Note that I am unsure how this affects older version of Nix that may or may not have builtins.isFloat available. Make sure this satisfies minver.nix’s "1.11" requirement.
229 lines
7.4 KiB
Nix
229 lines
7.4 KiB
Nix
/* Functions that generate widespread file
|
|
* formats from nix data structures.
|
|
*
|
|
* They all follow a similar interface:
|
|
* generator { config-attrs } data
|
|
*
|
|
* `config-attrs` are “holes” in the generators
|
|
* with sensible default implementations that
|
|
* can be overwritten. The default implementations
|
|
* are mostly generators themselves, called with
|
|
* their respective default values; they can be reused.
|
|
*
|
|
* Tests can be found in ./tests.nix
|
|
* Documentation in the manual, #sec-generators
|
|
*/
|
|
{ lib }:
|
|
with (lib).trivial;
|
|
let
|
|
libStr = lib.strings;
|
|
libAttr = lib.attrsets;
|
|
|
|
flipMapAttrs = flip libAttr.mapAttrs;
|
|
|
|
inherit (lib) isFunction;
|
|
in
|
|
|
|
rec {
|
|
|
|
## -- HELPER FUNCTIONS & DEFAULTS --
|
|
|
|
/* Convert a value to a sensible default string representation.
|
|
* The builtin `toString` function has some strange defaults,
|
|
* suitable for bash scripts but not much else.
|
|
*/
|
|
mkValueStringDefault = {}: v: with builtins;
|
|
let err = t: v: abort
|
|
("generators.mkValueStringDefault: " +
|
|
"${t} not supported: ${toPretty {} v}");
|
|
in if isInt v then toString v
|
|
# we default to not quoting strings
|
|
else if isString v then v
|
|
# isString returns "1", which is not a good default
|
|
else if true == v then "true"
|
|
# here it returns to "", which is even less of a good default
|
|
else if false == v then "false"
|
|
else if null == v then "null"
|
|
# if you have lists you probably want to replace this
|
|
else if isList v then err "lists" v
|
|
# same as for lists, might want to replace
|
|
else if isAttrs v then err "attrsets" v
|
|
else if isFunction v then err "functions" v
|
|
else err "this value is" (toString v);
|
|
|
|
|
|
/* Generate a line of key k and value v, separated by
|
|
* character sep. If sep appears in k, it is escaped.
|
|
* Helper for synaxes with different separators.
|
|
*
|
|
* mkValueString specifies how values should be formatted.
|
|
*
|
|
* mkKeyValueDefault {} ":" "f:oo" "bar"
|
|
* > "f\:oo:bar"
|
|
*/
|
|
mkKeyValueDefault = {
|
|
mkValueString ? mkValueStringDefault {}
|
|
}: sep: k: v:
|
|
"${libStr.escape [sep] k}${sep}${mkValueString v}";
|
|
|
|
|
|
## -- FILE FORMAT GENERATORS --
|
|
|
|
|
|
/* Generate a key-value-style config file from an attrset.
|
|
*
|
|
* mkKeyValue is the same as in toINI.
|
|
*/
|
|
toKeyValue = {
|
|
mkKeyValue ? mkKeyValueDefault {} "="
|
|
}: attrs:
|
|
let mkLine = k: v: mkKeyValue k v + "\n";
|
|
in libStr.concatStrings (libAttr.mapAttrsToList mkLine attrs);
|
|
|
|
|
|
/* Generate an INI-style config file from an
|
|
* attrset of sections to an attrset of key-value pairs.
|
|
*
|
|
* generators.toINI {} {
|
|
* foo = { hi = "${pkgs.hello}"; ciao = "bar"; };
|
|
* baz = { "also, integers" = 42; };
|
|
* }
|
|
*
|
|
*> [baz]
|
|
*> also, integers=42
|
|
*>
|
|
*> [foo]
|
|
*> ciao=bar
|
|
*> hi=/nix/store/y93qql1p5ggfnaqjjqhxcw0vqw95rlz0-hello-2.10
|
|
*
|
|
* The mk* configuration attributes can generically change
|
|
* the way sections and key-value strings are generated.
|
|
*
|
|
* For more examples see the test cases in ./tests.nix.
|
|
*/
|
|
toINI = {
|
|
# apply transformations (e.g. escapes) to section names
|
|
mkSectionName ? (name: libStr.escape [ "[" "]" ] name),
|
|
# format a setting line from key and value
|
|
mkKeyValue ? mkKeyValueDefault {} "="
|
|
}: attrsOfAttrs:
|
|
let
|
|
# map function to string for each key val
|
|
mapAttrsToStringsSep = sep: mapFn: attrs:
|
|
libStr.concatStringsSep sep
|
|
(libAttr.mapAttrsToList mapFn attrs);
|
|
mkSection = sectName: sectValues: ''
|
|
[${mkSectionName sectName}]
|
|
'' + toKeyValue { inherit mkKeyValue; } sectValues;
|
|
in
|
|
# map input to ini sections
|
|
mapAttrsToStringsSep "\n" mkSection attrsOfAttrs;
|
|
|
|
|
|
/* Generates JSON from an arbitrary (non-function) value.
|
|
* For more information see the documentation of the builtin.
|
|
*/
|
|
toJSON = {}: builtins.toJSON;
|
|
|
|
|
|
/* YAML has been a strict superset of JSON since 1.2, so we
|
|
* use toJSON. Before it only had a few differences referring
|
|
* to implicit typing rules, so it should work with older
|
|
* parsers as well.
|
|
*/
|
|
toYAML = {}@args: toJSON args;
|
|
|
|
|
|
/* Pretty print a value, akin to `builtins.trace`.
|
|
* Should probably be a builtin as well.
|
|
*/
|
|
toPretty = {
|
|
/* If this option is true, attrsets like { __pretty = fn; val = …; }
|
|
will use fn to convert val to a pretty printed representation.
|
|
(This means fn is type Val -> String.) */
|
|
allowPrettyValues ? false
|
|
}@args: v: with builtins;
|
|
let isPath = v: typeOf v == "path";
|
|
in if isInt v then toString v
|
|
else if isString v then ''"${libStr.escape [''"''] v}"''
|
|
else if true == v then "true"
|
|
else if false == v then "false"
|
|
else if null == v then "null"
|
|
else if isPath v then toString v
|
|
else if isList v then "[ "
|
|
+ libStr.concatMapStringsSep " " (toPretty args) v
|
|
+ " ]"
|
|
else if isAttrs v then
|
|
# apply pretty values if allowed
|
|
if attrNames v == [ "__pretty" "val" ] && allowPrettyValues
|
|
then v.__pretty v.val
|
|
# TODO: there is probably a better representation?
|
|
else if v ? type && v.type == "derivation" then
|
|
"<δ:${v.name}>"
|
|
# "<δ:${concatStringsSep "," (builtins.attrNames v)}>"
|
|
else "{ "
|
|
+ libStr.concatStringsSep " " (libAttr.mapAttrsToList
|
|
(name: value:
|
|
"${toPretty args name} = ${toPretty args value};") v)
|
|
+ " }"
|
|
else if isFunction v then
|
|
let fna = lib.functionArgs v;
|
|
showFnas = concatStringsSep "," (libAttr.mapAttrsToList
|
|
(name: hasDefVal: if hasDefVal then "(${name})" else name)
|
|
fna);
|
|
in if fna == {} then "<λ>"
|
|
else "<λ:{${showFnas}}>"
|
|
else abort "generators.toPretty: should never happen (v = ${v})";
|
|
|
|
# PLIST handling
|
|
toPlist = {}: v: let
|
|
isFloat = builtins.isFloat or (x: false);
|
|
expr = ind: x: with builtins;
|
|
if isNull x then "" else
|
|
if isBool x then bool ind x else
|
|
if isInt x then int ind x else
|
|
if isString x then str ind x else
|
|
if isList x then list ind x else
|
|
if isAttrs x then attrs ind x else
|
|
if isFloat x then float ind x else
|
|
abort "generators.toPlist: should never happen (v = ${v})";
|
|
|
|
literal = ind: x: ind + x;
|
|
|
|
bool = ind: x: literal ind (if x then "<true/>" else "<false/>");
|
|
int = ind: x: literal ind "<integer>${toString x}</integer>";
|
|
str = ind: x: literal ind "<string>${x}</string>";
|
|
key = ind: x: literal ind "<key>${x}</key>";
|
|
float = ind: x: literal ind "<real>${toString x}</real>";
|
|
|
|
indent = ind: expr "\t${ind}";
|
|
|
|
item = ind: libStr.concatMapStringsSep "\n" (indent ind);
|
|
|
|
list = ind: x: libStr.concatStringsSep "\n" [
|
|
(literal ind "<array>")
|
|
(item ind x)
|
|
(literal ind "</array>")
|
|
];
|
|
|
|
attrs = ind: x: libStr.concatStringsSep "\n" [
|
|
(literal ind "<dict>")
|
|
(attr ind x)
|
|
(literal ind "</dict>")
|
|
];
|
|
|
|
attr = let attrFilter = name: value: name != "_module" && value != null;
|
|
in ind: x: libStr.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList
|
|
(name: value: lib.optional (attrFilter name value) [
|
|
(key "\t${ind}" name)
|
|
(expr "\t${ind}" value)
|
|
]) x));
|
|
|
|
in ''<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
${expr "" v}
|
|
</plist>'';
|
|
|
|
}
|