diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/default.nix | 176 | ||||
-rw-r--r-- | lib/genid.nix | 3 | ||||
-rw-r--r-- | lib/haskell.nix | 25 | ||||
-rw-r--r-- | lib/impure.nix | 3 | ||||
-rw-r--r-- | lib/pure.nix | 226 | ||||
-rw-r--r-- | lib/svg-colors.json | 149 | ||||
-rw-r--r-- | lib/types.nix | 135 |
7 files changed, 507 insertions, 210 deletions
diff --git a/lib/default.nix b/lib/default.nix index 738e5218..f9f2f157 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -1,175 +1 @@ -let - nixpkgs-lib = import <nixpkgs/lib>; - lib = with lib; nixpkgs-lib // builtins // { - - evalSource = import ./eval-source.nix; - - git = import ./git.nix { inherit lib; }; - haskell = import ./haskell.nix { inherit lib; }; - krebs = import ./krebs lib; - krops = import ../submodules/krops/lib; - shell = import ./shell.nix { inherit lib; }; - systemd = { - encodeName = replaceChars ["/"] ["\\x2f"]; - }; - types = nixpkgs-lib.types // import ./types.nix { inherit lib; }; - uri = import ./uri.nix { inherit lib; }; - xml = import ./xml.nix { inherit lib; }; - - # compose a list of functions to be applied from left to right, i.e. - # compose :: [ (xm -> xn) ... (x1 -> x2) (x0 -> x1) ] -> x0 -> xn - compose = foldl' (f: g: x: f (g x)) id; - - eq = x: y: x == y; - ne = x: y: x != y; - mod = x: y: x - y * (x / y); - - genid = lib.genid_uint32; # TODO remove - genid_uint31 = x: ((lib.genid_uint32 x) + 16777216) / 2; - genid_uint32 = import ./genid.nix { inherit lib; }; - - lpad = n: c: s: - if lib.stringLength s < n - then lib.lpad n c (c + s) - else s; - - genAttrs' = names: f: listToAttrs (map f names); - - getAttrs = names: set: - listToAttrs (map (name: nameValuePair name set.${name}) - (filter (flip hasAttr set) names)); - - packageName = pkg: - pkg.pname or (parseDrvName pkg.name).name; - - test = re: x: isString x && testString re x; - - testString = re: x: match re x != null; - - toC = x: let - type = typeOf x; - reject = throw "cannot convert ${type}"; - in { - list = "{ ${concatStringsSep ", " (map toC x)} }"; - null = "NULL"; - set = if isDerivation x then toJSON x else reject; - string = toJSON x; # close enough - }.${type} or reject; - - indent = replaceChars ["\n"] ["\n "]; - - stripAttr = converge (filterAttrsRecursive (n: v: v != {} && v != null)); - - mapNixDir = f: x: { - list = foldl' mergeAttrs {} (map (mapNixDir1 f) x); - path = mapNixDir1 f x; - }.${typeOf x}; - - mapNixDir1 = f: dirPath: - let - toPackageName = name: - if test "^[0-9].*" name then "_${name}" else name; - in - listToAttrs - (map - (relPath: let - name = removeSuffix ".nix" relPath; - path = dirPath + "/${relPath}"; - in - nameValuePair (toPackageName name) (f path)) - (filter - (name: name != "default.nix" && !hasPrefix "." name) - (attrNames (readDir dirPath)))); - - # https://tools.ietf.org/html/rfc5952 - normalize-ip6-addr = - let - max-run-0 = - let - both = v: { off = v; pos = v; }; - gt = a: b: a.pos - a.off > b.pos - b.off; - - chkmax = ctx: { - cur = both (ctx.cur.pos + 1); - max = if gt ctx.cur ctx.max then ctx.cur else ctx.max; - }; - - incpos = ctx: recursiveUpdate ctx { - cur.pos = ctx.cur.pos + 1; - }; - - f = ctx: blk: (if blk == "0" then incpos else chkmax) ctx; - z = { cur = both 0; max = both 0; }; - in - blks: (chkmax (foldl' f z blks)).max; - - group-zeros = a: - let - blks = splitString ":" a; - max = max-run-0 blks; - lhs = take max.off blks; - rhs = drop max.pos blks; - in - if max.pos == 0 - then a - else let - sep = - if 8 - (length lhs + length rhs) == 1 - then ":0:" - else "::"; - in - "${concatStringsSep ":" lhs}${sep}${concatStringsSep ":" rhs}"; - - drop-leading-zeros = - let - f = block: - let - res = match "0*(.+)" block; - in - if res == null - then block # empty block - else elemAt res 0; - in - a: concatStringsSep ":" (map f (splitString ":" a)); - in - a: - toLower - (if test ".*::.*" a - then a - else group-zeros (drop-leading-zeros a)); - - hashToLength = n: s: substring 0 n (hashString "sha256" s); - - dropLast = n: xs: reverseList (drop n (reverseList xs)); - takeLast = n: xs: reverseList (take n (reverseList xs)); - - # Split string into list of chunks where each chunk is at most n chars long. - # The leftmost chunk might shorter. - # Example: stringToGroupsOf "123456" -> ["12" "3456"] - stringToGroupsOf = n: s: let - acc = - foldl' - (acc: c: if stringLength acc.chunk < n then { - chunk = acc.chunk + c; - chunks = acc.chunks; - } else { - chunk = c; - chunks = acc.chunks ++ [acc.chunk]; - }) - { - chunk = ""; - chunks = []; - } - (stringToCharacters s); - in - filter (x: x != []) ([acc.chunk] ++ acc.chunks); - - warnOldVersion = oldName: newName: - if compareVersions oldName newName != -1 then - trace "Upstream `${oldName}' gets overridden by `${newName}'." newName - else - newName; - }; -in - -lib +import ./impure.nix diff --git a/lib/genid.nix b/lib/genid.nix index 0aed1d35..bfa4a9a0 100644 --- a/lib/genid.nix +++ b/lib/genid.nix @@ -32,6 +32,5 @@ let out = genid; hexint = x: hexvals.${toLower x}; # :: attrset char uint4 - hexvals = listToAttrs (imap (i: c: { name = c; value = i - 1; }) - (stringToCharacters "0123456789abcdef")); + hexvals = listToAttrs (imap (i: c: { name = c; value = i - 1; }) hexchars); in out diff --git a/lib/haskell.nix b/lib/haskell.nix index b1889caf..f87cfa76 100644 --- a/lib/haskell.nix +++ b/lib/haskell.nix @@ -9,19 +9,14 @@ rec { # "${pkgs.bar}/bin/foo" for each {-pkg:bar-}"foo". # If a package doesn't exist, a warning gets printed. substitutePkgs = name: { callsite ? null, pkgs, path }: - pkgs.writeText name (substitutePkgs' { - inherit pkgs; + let sourceDescription = if callsite != null then "${name} in ${toString callsite}" else "${name} from ${toString path}"; - text = readFile path; - }); - substitutePkgs' = { pkgs, sourceDescription, text }: - let - f = s: + f = dependencies: s: let parse = match "(.*)([{]-pkg(:([^}]+))?-[}]\"([^\"]+)\")(.*)" s; prefix = elemAt parse 0; @@ -35,7 +30,7 @@ rec { "${pkg}/bin/${exename}" else trace (toString [ - "lib.haskell.replacePkg:" + "lib.haskell.substitutePkgs:" "warning:" "while deriving ${sourceDescription}:" "no substitute found for ${elemAt parse 1}" @@ -43,9 +38,17 @@ rec { exename; in if parse == null then - s + (pkgs.writeText name s).overrideAttrs (old: { + dependencies = + lib.uniq + (lib.sort (lib.on lib.lessThan (lib.getAttr "name")) + (filter + (lib.ne null) + (old.dependencies or [] ++ dependencies))); + }) + else - f (prefix + toJSON substitute + suffix); + f (dependencies ++ [pkg]) (prefix + toJSON substitute + suffix); in - f text; + f [] (readFile path); } diff --git a/lib/impure.nix b/lib/impure.nix new file mode 100644 index 00000000..3f95c375 --- /dev/null +++ b/lib/impure.nix @@ -0,0 +1,3 @@ +import ./pure.nix { + lib = import <nixpkgs/lib>; +} diff --git a/lib/pure.nix b/lib/pure.nix new file mode 100644 index 00000000..3fe51cd5 --- /dev/null +++ b/lib/pure.nix @@ -0,0 +1,226 @@ +{ lib, ... }: +let + nixpkgs-lib = lib; + stockholm.lib = with stockholm.lib; nixpkgs-lib // builtins // { + + evalModulesConfig = modules: let + eval = evalModules { + inherit modules; + }; + in filterAttrsRecursive (name: _: !hasPrefix "_" name) eval.config; + + evalSource = import ./eval-source.nix; + + evalSubmodule = submodule: modules: let + prefix = ["evalSubmodule"]; + in evalModulesConfig [ + { + options = removeAttrs (submodule.getSubOptions prefix) ["_module"]; + imports = modules; + } + ]; + + git = import ./git.nix { inherit (stockholm) lib; }; + haskell = import ./haskell.nix { inherit (stockholm) lib; }; + krebs = import ./krebs stockholm.lib; + shell = import ./shell.nix { inherit (stockholm) lib; }; + systemd = { + encodeName = replaceStrings ["/"] ["\\x2f"]; + }; + types = nixpkgs-lib.types // import ./types.nix { lib = stockholm.lib; }; + uri = import ./uri.nix { inherit (stockholm) lib; }; + xml = import ./xml.nix { inherit (stockholm) lib; }; + + # compose a list of functions to be applied from left to right, i.e. + # compose :: [ (xm -> xn) ... (x1 -> x2) (x0 -> x1) ] -> x0 -> xn + compose = foldl' (f: g: x: f (g x)) id; + + eq = x: y: x == y; + ne = x: y: x != y; + mod = x: y: x - y * (x / y); + + on = b: u: x: y: b (u x) (u y); + + genid = stockholm.lib.genid_uint32; # TODO remove + genid_uint31 = x: ((stockholm.lib.genid_uint32 x) + 16777216) / 2; + genid_uint32 = import ./genid.nix { lib = stockholm.lib; }; + + hexchars = stringToCharacters "0123456789abcdef"; + + lpad = n: c: s: + if lib.stringLength s < n + then stockholm.lib.lpad n c (c + s) + else s; + + genAttrs' = names: f: listToAttrs (map f names); + + getAttrs = names: set: + listToAttrs (map (name: nameValuePair name set.${name}) + (filter (flip hasAttr set) names)); + + maybeHead = x: if isList x && length x > 0 then head x else null; + + packageName = pkg: + pkg.pname or (parseDrvName pkg.name).name; + + test = re: x: isString x && testString re x; + + testString = re: x: match re x != null; + + toC = x: let + type = typeOf x; + reject = throw "cannot convert ${type}"; + in { + int = toJSON x; # close enough + list = "{ ${concatStringsSep ", " (map toC x)} }"; + null = "NULL"; + set = if isDerivation x then toJSON x else reject; + string = toJSON x; # close enough + }.${type} or reject; + + indent = replaceStrings ["\n"] ["\n "]; + + stripAttr = converge (filterAttrsRecursive (n: v: v != {} && v != null)); + + mapNixDir = f: x: { + list = foldl' mergeAttrs {} (map (mapNixDir1 f) x); + path = mapNixDir1 f x; + }.${typeOf x}; + + mapNixDir1 = f: dirPath: + let + toPackageName = name: + if test "^[0-9].*" name then "_${name}" else name; + in + listToAttrs + (map + (relPath: let + name = removeSuffix ".nix" relPath; + path = dirPath + "/${relPath}"; + in + nameValuePair (toPackageName name) (f path)) + (attrNames + (filterAttrs isNixDirEntry (readDir dirPath)))); + + isNixDirEntry = name: type: + (type == "regular" && hasSuffix ".nix" name && name != "default.nix") || + (type == "directory" && !hasPrefix "." name); + + # https://tools.ietf.org/html/rfc5952 + normalize-ip6-addr = + let + max-run-0 = + let + both = v: { off = v; pos = v; }; + gt = a: b: a.pos - a.off > b.pos - b.off; + + chkmax = ctx: { + cur = both (ctx.cur.pos + 1); + max = if gt ctx.cur ctx.max then ctx.cur else ctx.max; + }; + + incpos = ctx: recursiveUpdate ctx { + cur.pos = ctx.cur.pos + 1; + }; + + f = ctx: blk: (if blk == "0" then incpos else chkmax) ctx; + z = { cur = both 0; max = both 0; }; + in + blks: (chkmax (foldl' f z blks)).max; + + group-zeros = a: + let + blks = splitString ":" a; + max = max-run-0 blks; + lhs = take max.off blks; + rhs = drop max.pos blks; + in + if max.pos == 0 + then a + else let + sep = + if 8 - (length lhs + length rhs) == 1 + then ":0:" + else "::"; + in + "${concatStringsSep ":" lhs}${sep}${concatStringsSep ":" rhs}"; + + drop-leading-zeros = + let + f = block: + let + res = match "0*(.+)" block; + in + if res == null + then block # empty block + else elemAt res 0; + in + a: concatStringsSep ":" (map f (splitString ":" a)); + in + a: + toLower + (if test ".*::.*" a + then a + else group-zeros (drop-leading-zeros a)); + + hashToLength = n: s: substring 0 n (hashString "sha256" s); + + dropLast = n: xs: reverseList (drop n (reverseList xs)); + takeLast = n: xs: reverseList (take n (reverseList xs)); + + # Split string into list of chunks where each chunk is at most n chars long. + # The leftmost chunk might shorter. + # Example: stringToGroupsOf "123456" -> ["12" "3456"] + stringToGroupsOf = n: s: let + acc = + foldl' + (acc: c: if stringLength acc.chunk < n then { + chunk = acc.chunk + c; + chunks = acc.chunks; + } else { + chunk = c; + chunks = acc.chunks ++ [acc.chunk]; + }) + { + chunk = ""; + chunks = []; + } + (stringToCharacters s); + in + filter (x: x != []) ([acc.chunk] ++ acc.chunks); + + # Filter adjacent duplicate elements. + uniq = uniqBy eq; + + # Filter adjacent duplicate elements determined via the given function. + uniqBy = cmp: let + f = a: s: + if length s == 0 then + [] + else let + b = head s; + in + if cmp a b then + f b (tail s) + else + [b] ++ f b (tail s); + in + s: + if length s == 0 then + [] + else let + b = head s; + in + [b] ++ f b (tail s); + + warnOldVersion = oldName: newName: + if compareVersions oldName newName != -1 then + trace "Upstream `${oldName}' gets overridden by `${newName}'." newName + else + newName; + }; +in + +stockholm.lib +// { lib = stockholm.lib; } + diff --git a/lib/svg-colors.json b/lib/svg-colors.json new file mode 100644 index 00000000..834bf14f --- /dev/null +++ b/lib/svg-colors.json @@ -0,0 +1,149 @@ +[ + "aliceblue", + "antiquewhite", + "aqua", + "aquamarine", + "azure", + "beige", + "bisque", + "black", + "blanchedalmond", + "blue", + "blueviolet", + "brown", + "burlywood", + "cadetblue", + "chartreuse", + "chocolate", + "coral", + "cornflowerblue", + "cornsilk", + "crimson", + "cyan", + "darkblue", + "darkcyan", + "darkgoldenrod", + "darkgray", + "darkgreen", + "darkgrey", + "darkkhaki", + "darkmagenta", + "darkolivegreen", + "darkorange", + "darkorchid", + "darkred", + "darksalmon", + "darkseagreen", + "darkslateblue", + "darkslategray", + "darkslategrey", + "darkturquoise", + "darkviolet", + "deeppink", + "deepskyblue", + "dimgray", + "dimgrey", + "dodgerblue", + "firebrick", + "floralwhite", + "forestgreen", + "fuchsia", + "gainsboro", + "ghostwhite", + "gold", + "goldenrod", + "gray", + "green", + "greenyellow", + "grey", + "honeydew", + "hotpink", + "indianred", + "indigo", + "ivory", + "khaki", + "lavender", + "lavenderblush", + "lawngreen", + "lemonchiffon", + "lightblue", + "lightcoral", + "lightcyan", + "lightgoldenrodyellow", + "lightgray", + "lightgreen", + "lightgrey", + "lightpink", + "lightsalmon", + "lightseagreen", + "lightskyblue", + "lightslategray", + "lightslategrey", + "lightsteelblue", + "lightyellow", + "lime", + "limegreen", + "linen", + "magenta", + "maroon", + "mediumaquamarine", + "mediumblue", + "mediumorchid", + "mediumpurple", + "mediumseagreen", + "mediumslateblue", + "mediumspringgreen", + "mediumturquoise", + "mediumvioletred", + "midnightblue", + "mintcream", + "mistyrose", + "moccasin", + "navajowhite", + "navy", + "oldlace", + "olive", + "olivedrab", + "orange", + "orangered", + "orchid", + "palegoldenrod", + "palegreen", + "paleturquoise", + "palevioletred", + "papayawhip", + "peachpuff", + "peru", + "pink", + "plum", + "powderblue", + "purple", + "red", + "rosybrown", + "royalblue", + "saddlebrown", + "salmon", + "sandybrown", + "seagreen", + "seashell", + "sienna", + "silver", + "skyblue", + "slateblue", + "slategray", + "slategrey", + "snow", + "springgreen", + "steelblue", + "tan", + "teal", + "thistle", + "tomato", + "turquoise", + "violet", + "wheat", + "white", + "whitesmoke", + "yellow", + "yellowgreen" +] diff --git a/lib/types.nix b/lib/types.nix index 689a2c80..ad8421b1 100644 --- a/lib/types.nix +++ b/lib/types.nix @@ -3,11 +3,11 @@ let inherit (lib) all any attrNames concatMapStringsSep concatStringsSep const filter flip - genid_uint31 hasSuffix head isInt isString length mergeOneOption mkOption - mkOptionType optional optionalAttrs optionals range splitString + genid_uint31 hasSuffix head importJSON isInt isString length mergeOneOption + mkOption mkOptionType optional optionalAttrs optionals range splitString stringLength substring test testString typeOf; inherit (lib.types) - attrsOf bool either enum int listOf nullOr path str submodule; + addCheck attrsOf bool either enum int lines listOf nullOr path str submodule; in rec { @@ -18,9 +18,6 @@ rec { type = label; default = config._module.args.name; }; - cores = mkOption { - type = uint; - }; nets = mkOption { type = attrsOf net; default = {}; @@ -34,7 +31,7 @@ rec { ci = mkOption { description = '' If true, then the host wants to be tested by some CI system. - See <stockholm/krebs/2configs/buildbot-all.nix> + See ‹stockholm/krebs/2configs/buildbot-all.nix› ''; type = bool; default = false; @@ -43,7 +40,7 @@ rec { external = mkOption { description = '' Whether the host is defined externally (in contrast to being defined - in <stockholm>). This is useful e.g. when legacy and/or adopted + in ‹stockholm›). This is useful e.g. when legacy and/or adopted hosts should be part of retiolum or some other component. ''; type = bool; @@ -58,6 +55,14 @@ rec { default = false; }; + consul = mkOption { + description = '' + Whether the host is a member of the global consul network + ''; + type = bool; + default = false; + }; + owner = mkOption { type = user; }; @@ -102,11 +107,18 @@ rec { default = config._module.args.name; }; via = mkOption { - type = nullOr net; + type = + # XXX break infinite recursion when generating manuals + if config._module.args.name == "‹name›" then + mkOptionType { + name = "‹net›"; + } + else + nullOr net; default = null; }; addrs = mkOption { - type = listOf addr; + type = listOf (either addr str); default = optional (config.ip4 != null) config.ip4.addr ++ optional (config.ip6 != null) config.ip6.addr; @@ -121,22 +133,33 @@ rec { default = null; }; ip4 = mkOption { - type = nullOr (submodule { + type = nullOr (submodule (ip4: { options = { addr = mkOption { type = addr4; }; prefix = mkOption ({ type = cidr4; - } // optionalAttrs (config.name == "retiolum") { - default = "10.243.0.0/16"; + } // { + retiolum.default = "10.243.0.0/16"; + wiregrill.default = "10.244.0.0/16"; + }.${config._module.args.name} or { + default = "${ip4.config.addr}/32"; + }); + prefixLength = mkOption ({ + type = uint; + } // { + retiolum.default = 16; + wiregrill.default = 16; + }.${config._module.args.name} or { + default = 32; }); }; - }); + })); default = null; }; ip6 = mkOption { - type = nullOr (submodule { + type = nullOr (submodule (ip6: { options = { addr = mkOption { type = addr6; @@ -144,11 +167,22 @@ rec { }; prefix = mkOption ({ type = cidr6; - } // optionalAttrs (config.name == "retiolum") { - default = "42::/16"; + } // { + retiolum.default = "42:0::/32"; + wiregrill.default = "42:1::/32"; + }.${config._module.args.name} or { + default = "${ip6.config.addr}/128"; + }); + prefixLength = mkOption ({ + type = uint; + } // { + retiolum.default = 32; + wiregrill.default = 32; + }.${config._module.args.name} or { + default = 128; }); }; - }); + })); default = null; }; ssh = mkOption { @@ -178,7 +212,19 @@ rec { [config.extraConfig] ++ [config.pubkey] + ++ + optional (config.pubkey_ed25519 != null) '' + Ed25519PublicKey = ${config.pubkey_ed25519} + '' + ++ + optional (config.weight != null) "Weight = ${toString config.weight}" ); + defaultText = '' + Address = ‹addr› ‹port› # for each ‹net.via.addrs› + Subnet = ‹addr› # for each ‹net.addrs› + ‹extraConfig› + ‹pubkey› + ''; }; pubkey = mkOption { type = tinc-pubkey; @@ -190,7 +236,7 @@ rec { extraConfig = mkOption { description = "Extra Configuration to be appended to the hosts file"; default = ""; - type = str; + type = lines; }; port = mkOption { type = int; @@ -202,6 +248,15 @@ rec { description = "tinc subnets"; default = []; }; + weight = mkOption { + type = nullOr int; + description = '' + global tinc weight (latency in ms) of this particular node. + can be set to some high value to make it unprobable to be used as router. + if set to null, tinc will autogenerate the value based on latency. + ''; + default = if net.via != null then null else 300; + }; }; })); default = null; @@ -227,19 +282,32 @@ rec { }; }; })); + default = null; }; }; }); + boundedInt = min: max: mkOptionType { + name = "bounded integer"; + check = x: isInt x && min <= x && x <= max; + merge = mergeOneOption; + }; + + lowerBoundedInt = min: mkOptionType { + name = "lower bounded integer"; + check = x: isInt x && min <= x; + merge = mergeOneOption; + }; + positive = mkOptionType { + inherit (lowerBoundedInt 1) check; name = "positive integer"; - check = x: isInt x && x > 0; merge = mergeOneOption; }; uint = mkOptionType { + inherit (lowerBoundedInt 0) check; name = "unsigned integer"; - check = x: isInt x && x >= 0; merge = mergeOneOption; }; @@ -252,6 +320,7 @@ rec { path = mkOption { type = absolute-pathname; default = "/run/keys/${config.name}"; + defaultText = "/run/keys/‹name›"; }; mode = mkOption { type = file-mode; @@ -267,10 +336,12 @@ rec { service = mkOption { type = systemd.unit-name; default = "secret-${lib.systemd.encodeName config.name}.service"; + defaultText = "secret-‹name›.service"; }; source-path = mkOption { type = str; - default = toString <secrets> + "/${config.name}"; + default = config.name; + defaultText = "‹secrets/‹name››"; }; }; }); @@ -379,6 +450,7 @@ rec { home = mkOption { type = absolute-pathname; default = "/home/${config.name}"; + defaultText = "/home/‹name›"; }; mail = mkOption { type = nullOr str; @@ -406,6 +478,7 @@ rec { uid = mkOption { type = int; default = genid_uint31 config.name; + defaultText = "genid_uint31 ‹name›"; }; }; }); @@ -414,10 +487,12 @@ rec { name = mkOption { type = username; default = config._module.args.name; + defaultText = "genid_uint31 ‹name›"; }; gid = mkOption { type = int; default = genid_uint31 config.name; + defaultText = "genid_uint31 ‹name›"; }; }; }); @@ -520,6 +595,9 @@ rec { }; }; + flameshot.color = + either (addCheck str (test "#[0-9A-Fa-f]{6}")) svg.color-keyword; + file-mode = mkOptionType { name = "file mode"; check = test "[0-7]{4}"; @@ -538,6 +616,19 @@ rec { merge = mergeOneOption; }; + # SVG 1.1, 4.4 Recognized color keyword names + # + # svg-colors.json has been generated with: + # curl -sS https://www.w3.org/TR/SVG11/types.html#ColorKeywords | + # fq -d html '[ + # grep_by(.["@class"]=="color-keywords") | + # grep_by(.["@class"]=="prop-value"and.["#text"]!="").["#text"] + # ] | sort' + # + svg.color-keyword = enum (importJSON ./svg-colors.json) // { + name = "SVG 1.1 recognized color keyword"; + }; + systemd.unit-name = mkOptionType { name = "systemd unit name"; check = x: |