2024-11-13 15:24:41 +01:00
|
|
|
{ lib }:
|
|
|
|
rec {
|
|
|
|
/**
|
|
|
|
Create a URL-safe slug from any string
|
|
|
|
*/
|
|
|
|
slug = str:
|
|
|
|
let
|
|
|
|
# Replace non-alphanumeric characters with hyphens
|
|
|
|
replaced = join ""
|
|
|
|
(
|
|
|
|
builtins.map
|
|
|
|
(c:
|
|
|
|
if (c >= "a" && c <= "z") || (c >= "0" && c <= "9")
|
|
|
|
then c
|
|
|
|
else "-"
|
|
|
|
)
|
|
|
|
(with lib; stringToCharacters (toLower str)));
|
|
|
|
|
|
|
|
# Remove leading and trailing hyphens
|
|
|
|
trimHyphens = s:
|
|
|
|
let
|
|
|
|
matched = builtins.match "(-*)([^-].*[^-]|[^-])(-*)" s;
|
|
|
|
in
|
|
|
|
with lib; optionalString (!isNull matched) (builtins.elemAt matched 1);
|
|
|
|
|
|
|
|
collapseHyphens = s:
|
|
|
|
let
|
|
|
|
result = builtins.replaceStrings [ "--" ] [ "-" ] s;
|
|
|
|
in
|
|
|
|
if result == s then s else collapseHyphens result;
|
|
|
|
in
|
|
|
|
trimHyphens (collapseHyphens replaced);
|
|
|
|
|
|
|
|
join = lib.concatStringsSep;
|
|
|
|
|
|
|
|
splitLines = s: with builtins; filter (x: !isList x) (split "\n" s);
|
|
|
|
|
|
|
|
indent = prefix: s:
|
2024-11-13 15:24:41 +01:00
|
|
|
with lib.lists;
|
|
|
|
let
|
|
|
|
lines = splitLines s;
|
|
|
|
in
|
|
|
|
join "\n" (
|
|
|
|
[ (head lines) ]
|
|
|
|
++
|
|
|
|
(map (x: if x == "" then x else "${prefix}${x}") (tail lines))
|
|
|
|
);
|
2024-11-13 15:24:41 +01:00
|
|
|
|
2024-11-13 15:24:41 +01:00
|
|
|
relativePath = path1': path2':
|
|
|
|
let
|
|
|
|
inherit (lib.path) subpath;
|
|
|
|
inherit (lib) lists;
|
|
|
|
|
|
|
|
path1 = subpath.components path1';
|
2024-11-13 15:24:41 +01:00
|
|
|
prefix1 = with lib; take (length path1 - 1) path1;
|
2024-11-13 15:24:41 +01:00
|
|
|
path2 = subpath.components path2';
|
2024-11-13 15:24:41 +01:00
|
|
|
prefix2 = with lib; take (length path1 - 1) path2;
|
2024-11-13 15:24:41 +01:00
|
|
|
|
|
|
|
commonPrefixLength = with lists;
|
|
|
|
findFirstIndex (i: i.fst != i.snd)
|
2024-11-13 15:24:41 +01:00
|
|
|
(length prefix1)
|
|
|
|
(zipLists prefix1 prefix2);
|
2024-11-13 15:24:41 +01:00
|
|
|
|
|
|
|
relativeComponents = with lists;
|
2024-11-13 15:24:41 +01:00
|
|
|
[ "." ] ++ (replicate (length prefix1 - commonPrefixLength) "..") ++ (drop commonPrefixLength path2);
|
2024-11-13 15:24:41 +01:00
|
|
|
in
|
|
|
|
join "/" relativeComponents;
|
|
|
|
|
2024-11-13 15:24:41 +01:00
|
|
|
/**
|
|
|
|
Recursively list all Nix files from a directory, except the top-level `default.nix`
|
|
|
|
|
|
|
|
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
|
|
|
types = rec {
|
|
|
|
collection = elemType:
|
|
|
|
let
|
|
|
|
unparenthesize = class: class == "noun";
|
|
|
|
desc = type:
|
|
|
|
types.optionDescriptionPhrase unparenthesize type;
|
|
|
|
desc' = type:
|
|
|
|
let
|
|
|
|
typeDesc = lib.types.optionDescriptionPhrase unparenthesize type;
|
|
|
|
in
|
|
|
|
if type.descriptionClass == "noun"
|
|
|
|
then
|
|
|
|
typeDesc + "s"
|
|
|
|
else
|
|
|
|
"many instances of ${typeDesc}";
|
|
|
|
in
|
|
|
|
lib.types.mkOptionType {
|
|
|
|
name = "collection";
|
|
|
|
description = "separately specified ${desc elemType} for a collection of ${desc' elemType}";
|
|
|
|
merge = loc: defs:
|
|
|
|
map
|
|
|
|
(def:
|
2024-11-13 15:24:41 +01:00
|
|
|
elemType.merge (loc ++ [ "[definition ${toString def.file}]" ]) [{ inherit (def) file; value = def.value; }]
|
2024-11-13 15:24:41 +01:00
|
|
|
)
|
|
|
|
defs;
|
|
|
|
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
|
|
|
}
|