Fediversity/website/lib.nix

227 lines
6.1 KiB
Nix
Raw Normal View History

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
base' =
2025-02-19 18:34:19 +01:00
if lib.isFunction new then
lib.recursiveUpdate base (new base' base)
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
/**
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:
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':
let
inherit (lib.path) subpath;
2025-02-19 18:34:19 +01:00
inherit (lib)
lists
length
take
drop
min
max
;
path1 = subpath.components path1';
2024-11-27 12:51:38 +01:00
prefix1 = take (length path1 - 1) path1;
path2 = subpath.components path2';
2024-11-27 12:51:38 +01:00
prefix2 = take (length path2 - 1) path2;
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-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);
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 {
# 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;
# 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
}