2024-11-13 15:24:41 +01:00
|
|
|
{ lib }:
|
|
|
|
rec {
|
2025-02-19 18:34:19 +01:00
|
|
|
template =
|
|
|
|
g: f: x:
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
|
|
|
base = f x;
|
|
|
|
result = g base;
|
|
|
|
in
|
2025-02-19 18:34:19 +01:00
|
|
|
result
|
|
|
|
// {
|
|
|
|
override =
|
|
|
|
new:
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
2024-11-13 15:24:41 +01:00
|
|
|
base' =
|
2025-02-19 18:34:19 +01:00
|
|
|
if lib.isFunction new then
|
|
|
|
lib.recursiveUpdate base (new base' base)
|
2024-11-13 15:24:41 +01:00
|
|
|
else
|
|
|
|
lib.recursiveUpdate base new;
|
2024-11-13 15:24:41 +01:00
|
|
|
result' = g base';
|
|
|
|
in
|
2025-02-19 18:34:19 +01:00
|
|
|
result'
|
|
|
|
// {
|
2024-11-13 15:24:41 +01:00
|
|
|
override = new: (template g (x': base') x).override new;
|
|
|
|
};
|
|
|
|
};
|
2024-11-13 15:24:41 +01:00
|
|
|
|
2024-11-13 15:24:41 +01:00
|
|
|
/**
|
|
|
|
Recursively replace occurrences of `from` with `to` within `string`
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
|
|
replaceStringRec "--" "-" "hello-----world"
|
|
|
|
=> "hello-world"
|
|
|
|
*/
|
2025-02-19 18:34:19 +01:00
|
|
|
replaceStringsRec =
|
|
|
|
from: to: string:
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
|
|
|
replaced = lib.replaceStrings [ from ] [ to ] string;
|
|
|
|
in
|
|
|
|
if replaced == string then string else replaceStringsRec from to replaced;
|
|
|
|
|
2024-11-13 15:24:41 +01:00
|
|
|
/**
|
|
|
|
Create a URL-safe slug from any string
|
|
|
|
*/
|
2025-02-19 18:34:19 +01:00
|
|
|
slug =
|
|
|
|
str:
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
|
|
|
# Replace non-alphanumeric characters with hyphens
|
2025-02-19 18:34:19 +01:00
|
|
|
replaced = join "" (
|
|
|
|
builtins.map (c: if (c >= "a" && c <= "z") || (c >= "0" && c <= "9") then c else "-") (
|
|
|
|
with lib; stringToCharacters (toLower str)
|
|
|
|
)
|
|
|
|
);
|
2024-11-13 15:24:41 +01:00
|
|
|
|
|
|
|
# Remove leading and trailing hyphens
|
2025-02-19 18:34:19 +01:00
|
|
|
trimHyphens =
|
|
|
|
s:
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
|
|
|
matched = builtins.match "(-*)([^-].*[^-]|[^-])(-*)" s;
|
|
|
|
in
|
2025-02-19 18:34:19 +01:00
|
|
|
with lib;
|
|
|
|
optionalString (!isNull matched) (builtins.elemAt matched 1);
|
2024-11-13 15:24:41 +01:00
|
|
|
in
|
|
|
|
trimHyphens (replaceStringsRec "--" "-" replaced);
|
|
|
|
|
|
|
|
squash = replaceStringsRec "\n\n" "\n";
|
2024-11-13 15:24:41 +01:00
|
|
|
|
2024-11-13 15:24:41 +01:00
|
|
|
/**
|
|
|
|
Trim trailing spaces and squash non-leading spaces
|
|
|
|
*/
|
2025-02-19 18:34:19 +01:00
|
|
|
trim =
|
|
|
|
string:
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
2025-02-19 18:34:19 +01:00
|
|
|
trimLine =
|
|
|
|
line:
|
2024-11-13 15:24:41 +01:00
|
|
|
with lib;
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
2024-11-13 15:24:41 +01:00
|
|
|
# separate leading spaces from the rest
|
|
|
|
parts = split "(^ *)" line;
|
|
|
|
spaces = head (elemAt parts 1);
|
|
|
|
rest = elemAt parts 2;
|
|
|
|
# drop trailing spaces
|
|
|
|
body = head (split " *$" rest);
|
2024-11-13 15:24:41 +01:00
|
|
|
in
|
2025-02-19 18:34:19 +01:00
|
|
|
if body == "" then "" else spaces + replaceStringsRec " " " " body;
|
2024-11-13 15:24:41 +01:00
|
|
|
in
|
2024-11-13 15:24:41 +01:00
|
|
|
join "\n" (map trimLine (splitLines string));
|
2024-11-13 15:24:41 +01:00
|
|
|
|
|
|
|
join = lib.concatStringsSep;
|
|
|
|
|
|
|
|
splitLines = s: with builtins; filter (x: !isList x) (split "\n" s);
|
|
|
|
|
2025-02-19 18:34:19 +01:00
|
|
|
indent =
|
|
|
|
prefix: s:
|
2024-11-13 15:24:41 +01:00
|
|
|
with lib.lists;
|
|
|
|
let
|
|
|
|
lines = splitLines s;
|
|
|
|
in
|
2025-02-19 18:34:19 +01:00
|
|
|
join "\n" ([ (head lines) ] ++ (map (x: if x == "" then x else "${prefix}${x}") (tail lines)));
|
2024-11-13 15:24:41 +01:00
|
|
|
|
2025-02-19 18:34:19 +01:00
|
|
|
relativePath =
|
|
|
|
path1': path2':
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
|
|
|
inherit (lib.path) subpath;
|
2025-02-19 18:34:19 +01:00
|
|
|
inherit (lib)
|
|
|
|
lists
|
|
|
|
length
|
|
|
|
take
|
|
|
|
drop
|
|
|
|
min
|
|
|
|
max
|
|
|
|
;
|
2024-11-13 15:24:41 +01:00
|
|
|
|
|
|
|
path1 = subpath.components path1';
|
2024-11-27 12:51:38 +01:00
|
|
|
prefix1 = take (length path1 - 1) path1;
|
2024-11-13 15:24:41 +01:00
|
|
|
path2 = subpath.components path2';
|
2024-11-27 12:51:38 +01:00
|
|
|
prefix2 = take (length path2 - 1) path2;
|
2024-11-13 15:24:41 +01:00
|
|
|
|
2025-02-19 18:34:19 +01:00
|
|
|
commonPrefixLength =
|
|
|
|
with lists;
|
|
|
|
findFirstIndex (i: i.fst != i.snd) (min (length prefix1) (length prefix2)) (
|
|
|
|
zipLists prefix1 prefix2
|
|
|
|
);
|
2024-11-13 15:24:41 +01:00
|
|
|
|
2024-11-27 12:51:38 +01:00
|
|
|
depth = max 0 (length prefix1 - commonPrefixLength);
|
|
|
|
|
2025-02-19 18:34:19 +01:00
|
|
|
relativeComponents =
|
|
|
|
with lists;
|
2024-11-27 12:51:38 +01:00
|
|
|
[ "." ] ++ (replicate depth "..") ++ (drop commonPrefixLength path2);
|
2024-11-13 15:24:41 +01:00
|
|
|
in
|
|
|
|
join "/" relativeComponents;
|
|
|
|
|
2024-11-13 15:24:41 +01:00
|
|
|
/**
|
2025-02-19 18:34:19 +01:00
|
|
|
Recursively list all Nix files from a directory, except the top-level `default.nix`
|
2024-11-13 15:24:41 +01:00
|
|
|
|
2025-02-19 18:34:19 +01:00
|
|
|
Useful for module system `imports` from a top-level module.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
nixFiles =
|
|
|
|
dir:
|
|
|
|
with lib.fileset;
|
|
|
|
toList (difference (fileFilter ({ hasExt, ... }: hasExt "nix") dir) (dir + "/default.nix"));
|
2024-11-13 15:24:41 +01:00
|
|
|
|
2024-11-13 15:24:41 +01:00
|
|
|
types = rec {
|
2024-11-13 15:24:41 +01:00
|
|
|
# arbitrarily nested attribute set where the leaves are of type `type`
|
|
|
|
# NOTE: this works for anything but attribute sets!
|
2025-02-19 18:34:19 +01:00
|
|
|
recursiveAttrs =
|
|
|
|
type:
|
|
|
|
with lib.types;
|
2024-11-13 15:24:41 +01:00
|
|
|
# NOTE: due to how `either` works, the first match is significant,
|
|
|
|
# so if `type` happens to be an attrset, the typecheck will consider
|
|
|
|
# `type`, not `attrsOf`
|
|
|
|
attrsOf (either type (recursiveAttrs type));
|
|
|
|
|
|
|
|
# collection of unnamed items that can be added to item-wise, i.e. without wrapping the item in a list
|
2025-02-19 18:34:19 +01:00
|
|
|
collection =
|
|
|
|
elemType:
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
|
|
|
unparenthesize = class: class == "noun";
|
2025-02-19 18:34:19 +01:00
|
|
|
desc = type: types.optionDescriptionPhrase unparenthesize type;
|
|
|
|
desc' =
|
|
|
|
type:
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
|
|
|
typeDesc = lib.types.optionDescriptionPhrase unparenthesize type;
|
|
|
|
in
|
2025-02-19 18:34:19 +01:00
|
|
|
if type.descriptionClass == "noun" then typeDesc + "s" else "many instances of ${typeDesc}";
|
2024-11-13 15:24:41 +01:00
|
|
|
in
|
|
|
|
lib.types.mkOptionType {
|
|
|
|
name = "collection";
|
|
|
|
description = "separately specified ${desc elemType} for a collection of ${desc' elemType}";
|
2025-02-19 18:34:19 +01:00
|
|
|
merge =
|
|
|
|
loc: defs:
|
|
|
|
map (
|
|
|
|
def:
|
|
|
|
elemType.merge (loc ++ [ "[definition ${toString def.file}]" ]) [
|
|
|
|
{
|
|
|
|
inherit (def) file;
|
|
|
|
value = def.value;
|
|
|
|
}
|
|
|
|
]
|
|
|
|
) defs;
|
2024-11-13 15:24:41 +01:00
|
|
|
check = elemType.check;
|
|
|
|
getSubOptions = elemType.getSubOptions;
|
|
|
|
getSubModules = elemType.getSubModules;
|
|
|
|
substSubModules = m: collection (elemType.substSubModules m);
|
|
|
|
functor = (lib.defaultFunctor "collection") // {
|
|
|
|
type = collection;
|
|
|
|
wrapped = elemType;
|
|
|
|
payload = { };
|
|
|
|
};
|
|
|
|
nestedTypes.elemType = elemType;
|
|
|
|
};
|
2024-11-13 15:24:41 +01:00
|
|
|
|
2025-02-19 18:34:19 +01:00
|
|
|
listOfUnique =
|
|
|
|
elemType:
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
|
|
|
baseType = lib.types.listOf elemType;
|
|
|
|
in
|
2025-02-19 18:34:19 +01:00
|
|
|
baseType
|
|
|
|
// {
|
|
|
|
merge =
|
|
|
|
loc: defs:
|
2024-11-13 15:24:41 +01:00
|
|
|
let
|
|
|
|
# Keep track of which definition each value came from
|
2025-02-19 18:34:19 +01:00
|
|
|
defsWithValues = map (
|
|
|
|
def:
|
|
|
|
map (v: {
|
|
|
|
inherit (def) file;
|
|
|
|
value = v;
|
|
|
|
}) def.value
|
|
|
|
) defs;
|
2024-11-13 15:24:41 +01:00
|
|
|
flatDefs = lib.flatten defsWithValues;
|
|
|
|
|
|
|
|
# Check for duplicates while preserving source info
|
2025-02-19 18:34:19 +01:00
|
|
|
seen = builtins.foldl' (
|
|
|
|
acc: def:
|
|
|
|
if lib.lists.any (v: v.value == def.value) acc then
|
|
|
|
throw "The option `${lib.options.showOption loc}` has duplicate values (${toString def.value}) defined in ${def.file}"
|
|
|
|
else
|
|
|
|
acc ++ [ def ]
|
|
|
|
) [ ] flatDefs;
|
2024-11-13 15:24:41 +01:00
|
|
|
in
|
|
|
|
map (def: def.value) seen;
|
|
|
|
};
|
2024-11-13 15:24:41 +01:00
|
|
|
};
|
2024-11-13 15:24:41 +01:00
|
|
|
}
|