From 39a3a1a6f8c0a5bd387dcddf07ea686a3f2e493d Mon Sep 17 00:00:00 2001 From: Benedikt Peetz Date: Mon, 2 Nov 2026 15:19:44 +0100 Subject: treewide: Add new math and options subcategories and restructure --- by-name-overlay.nix | 149 ---------------------------------------------------- by-name.nix | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++ default.nix | 29 +++++----- flake.nix | 4 +- math.nix | 95 +++++++++++++++++++++++++++++++++ options.nix | 21 ++++++++ 6 files changed, 279 insertions(+), 168 deletions(-) delete mode 100644 by-name-overlay.nix create mode 100644 by-name.nix create mode 100644 math.nix create mode 100644 options.nix diff --git a/by-name-overlay.nix b/by-name-overlay.nix deleted file mode 100644 index 2d03e75..0000000 --- a/by-name-overlay.nix +++ /dev/null @@ -1,149 +0,0 @@ -# nixLib - A library of nix functions for vhack.eu -# -# Copyright (C) 2025 Benedikt Peetz -# SPDX-License-Identifier: LGPL-3.0-or-later -# -# This file is part of vhack.eu's nix library. -# -# You should have received a copy of the License along with this program. -# If not, see . -{warn}: -# Adapted from this: https://github.com/NixOS/nixpkgs/blob/1814b56453c91192f6d5a6276079948f9fe96c18/pkgs/top-level/by-name-overlay.nix -# This file should not depend on `pkgs` and thus not use `lib`. -{ - baseDirectory, - fileName ? null, - fileRegex ? null, - finalizeFunction ? name: value: value, - useShards ? true, - relativePaths ? false, -}: -assert fileName == null -> fileRegex != null; -assert fileRegex == null -> fileName != null; let - finalFileRegex = - if fileRegex == null - then "^${escapeRegex fileName}$" - else fileRegex; - fileDisplay = - if fileName == null - then "matching ${fileRegex}" - else fileName; - - # From nixpkgs/lib {{{ - # These functions are taken straight out of the `nixpkgs/lib`. - # We can't depended on `pkgs` (and thus on `lib`), because the `pkgs` module argument - # is only defined in the `nixpkgs` module (which is imported through this function). - mapAttrsToList = f: attrs: - builtins.map (name: f name attrs.${name}) (builtins.attrNames attrs); - - stringToCharacters = s: builtins.genList (p: builtins.substring p 1 s) (builtins.stringLength s); - escape = list: builtins.replaceStrings list (map (c: "\\${c}") list); - escapeRegex = escape (stringToCharacters "\\[{()^$?*+|."); - - mapAttrs' = f: set: builtins.listToAttrs (map (attr: f attr set.${attr}) (builtins.attrNames set)); - - nameValuePair = name: value: {inherit name value;}; - filterAttrs = pred: set: - builtins.listToAttrs (builtins.concatMap (name: let - v = set.${name}; - in - if pred name v - then [(nameValuePair name v)] - else []) (builtins.attrNames set)); - # }}} - - rawByName = { - baseDirectory, - finalFileRegex, - fileDisplay, - finalizeFunction, - useShards, - relativePaths, - }: let - # Takes a list of attrs as input and returns one merged attr set. - flattenAttrs = list: - if builtins.isList list - then - builtins.foldl' (acc: elem: - if builtins.isList elem - # Merging them with `//` is okay here, as we can be sure that the attr names are - # unique (they were separate dictionary after all) - then acc // (flattenAttrs elem) - else acc // elem) {} - list - else list; - - # Module files for a single shard - # Type: String -> String -> ListOf Path - namesForShard = shard: type: - if type != "directory" - then warn "Ignored non-directory, whilst importing by-name directory (${fileDisplay}): '${shard}'" {} - else if useShards - then namesForElementShard shard type - else namesForElementDirect shard type; - - # Type: String -> String -> String -> ListOf Path - mkPath = shard: name: toplevelType: let - rawPath = baseDirectory + "/${shard}/${name}"; - paths = filterAttrs (_: v: v != null) (mapAttrs' (name: value: - if builtins.match finalFileRegex name != null - then nameValuePair name value - else nameValuePair name null) (builtins.readDir rawPath)); - - checkPath = pathSegment: type: let - path = rawPath + "/${pathSegment}"; - in - if builtins.pathExists path - then - if toplevelType != "directory" - then - # The `namesForShard` function should have already printed a warning. - [null] - else if relativePaths - then pathSegment - else path - else warn "'${builtins.toString path}' does not exist. Skipped" null; - in - if toplevelType != "directory" - then - # The `namesForShard` function should have already printed a warning. - [null] - else if (builtins.attrValues paths) == [] - then warn "'${fileDisplay}' did not match anything in ${builtins.toString rawPath}. Skipped" [null] - else mapAttrsToList checkPath paths; - - filterNull = list: builtins.filter (value: value != null) list; - # Type: String -> String -> AttrSet - namesForElementShard = shard: _type: (builtins.mapAttrs - (name: type: filterNull (mkPath shard name type)) - (builtins.readDir (baseDirectory + "/${shard}"))); - - # Type: String -> String -> AttrSet - namesForElementDirect = name: type: {"${name}" = filterNull ((mkPath ".") name type);}; - - # A list of all paths. - output = flattenAttrs (mapAttrsToList namesForShard (builtins.readDir baseDirectory)); - in - output; - - firstPass = rawByName { - inherit - baseDirectory - finalFileRegex - fileDisplay - finalizeFunction - useShards - relativePaths - ; - }; - - secondPass = - if fileName != null - then - builtins.mapAttrs (name: value: builtins.head value) - (filterAttrs (_: value: value != []) firstPass) - else firstPass; -in - builtins.mapAttrs - finalizeFunction - secondPass diff --git a/by-name.nix b/by-name.nix new file mode 100644 index 0000000..2d03e75 --- /dev/null +++ b/by-name.nix @@ -0,0 +1,149 @@ +# nixLib - A library of nix functions for vhack.eu +# +# Copyright (C) 2025 Benedikt Peetz +# SPDX-License-Identifier: LGPL-3.0-or-later +# +# This file is part of vhack.eu's nix library. +# +# You should have received a copy of the License along with this program. +# If not, see . +{warn}: +# Adapted from this: https://github.com/NixOS/nixpkgs/blob/1814b56453c91192f6d5a6276079948f9fe96c18/pkgs/top-level/by-name-overlay.nix +# This file should not depend on `pkgs` and thus not use `lib`. +{ + baseDirectory, + fileName ? null, + fileRegex ? null, + finalizeFunction ? name: value: value, + useShards ? true, + relativePaths ? false, +}: +assert fileName == null -> fileRegex != null; +assert fileRegex == null -> fileName != null; let + finalFileRegex = + if fileRegex == null + then "^${escapeRegex fileName}$" + else fileRegex; + fileDisplay = + if fileName == null + then "matching ${fileRegex}" + else fileName; + + # From nixpkgs/lib {{{ + # These functions are taken straight out of the `nixpkgs/lib`. + # We can't depended on `pkgs` (and thus on `lib`), because the `pkgs` module argument + # is only defined in the `nixpkgs` module (which is imported through this function). + mapAttrsToList = f: attrs: + builtins.map (name: f name attrs.${name}) (builtins.attrNames attrs); + + stringToCharacters = s: builtins.genList (p: builtins.substring p 1 s) (builtins.stringLength s); + escape = list: builtins.replaceStrings list (map (c: "\\${c}") list); + escapeRegex = escape (stringToCharacters "\\[{()^$?*+|."); + + mapAttrs' = f: set: builtins.listToAttrs (map (attr: f attr set.${attr}) (builtins.attrNames set)); + + nameValuePair = name: value: {inherit name value;}; + filterAttrs = pred: set: + builtins.listToAttrs (builtins.concatMap (name: let + v = set.${name}; + in + if pred name v + then [(nameValuePair name v)] + else []) (builtins.attrNames set)); + # }}} + + rawByName = { + baseDirectory, + finalFileRegex, + fileDisplay, + finalizeFunction, + useShards, + relativePaths, + }: let + # Takes a list of attrs as input and returns one merged attr set. + flattenAttrs = list: + if builtins.isList list + then + builtins.foldl' (acc: elem: + if builtins.isList elem + # Merging them with `//` is okay here, as we can be sure that the attr names are + # unique (they were separate dictionary after all) + then acc // (flattenAttrs elem) + else acc // elem) {} + list + else list; + + # Module files for a single shard + # Type: String -> String -> ListOf Path + namesForShard = shard: type: + if type != "directory" + then warn "Ignored non-directory, whilst importing by-name directory (${fileDisplay}): '${shard}'" {} + else if useShards + then namesForElementShard shard type + else namesForElementDirect shard type; + + # Type: String -> String -> String -> ListOf Path + mkPath = shard: name: toplevelType: let + rawPath = baseDirectory + "/${shard}/${name}"; + paths = filterAttrs (_: v: v != null) (mapAttrs' (name: value: + if builtins.match finalFileRegex name != null + then nameValuePair name value + else nameValuePair name null) (builtins.readDir rawPath)); + + checkPath = pathSegment: type: let + path = rawPath + "/${pathSegment}"; + in + if builtins.pathExists path + then + if toplevelType != "directory" + then + # The `namesForShard` function should have already printed a warning. + [null] + else if relativePaths + then pathSegment + else path + else warn "'${builtins.toString path}' does not exist. Skipped" null; + in + if toplevelType != "directory" + then + # The `namesForShard` function should have already printed a warning. + [null] + else if (builtins.attrValues paths) == [] + then warn "'${fileDisplay}' did not match anything in ${builtins.toString rawPath}. Skipped" [null] + else mapAttrsToList checkPath paths; + + filterNull = list: builtins.filter (value: value != null) list; + # Type: String -> String -> AttrSet + namesForElementShard = shard: _type: (builtins.mapAttrs + (name: type: filterNull (mkPath shard name type)) + (builtins.readDir (baseDirectory + "/${shard}"))); + + # Type: String -> String -> AttrSet + namesForElementDirect = name: type: {"${name}" = filterNull ((mkPath ".") name type);}; + + # A list of all paths. + output = flattenAttrs (mapAttrsToList namesForShard (builtins.readDir baseDirectory)); + in + output; + + firstPass = rawByName { + inherit + baseDirectory + finalFileRegex + fileDisplay + finalizeFunction + useShards + relativePaths + ; + }; + + secondPass = + if fileName != null + then + builtins.mapAttrs (name: value: builtins.head value) + (filterAttrs (_: value: value != []) firstPass) + else firstPass; +in + builtins.mapAttrs + finalizeFunction + secondPass diff --git a/default.nix b/default.nix index 9098bce..26e93c3 100644 --- a/default.nix +++ b/default.nix @@ -7,27 +7,18 @@ # # You should have received a copy of the License along with this program. # If not, see . - -{}: let +{lib}: let # string -> string -> string colorize = color: string: "[${color}m${string}"; - - # Taken from `nixpkgs/lib`. - # Is here to avoid a dependency on `lib` (making this file easy to test with `nix eval`) - warn = - # Since Nix 2.23, https://github.com/NixOS/nix/pull/10592 - builtins.warn - or ( - # Do not eta reduce v, so that we have the same strictness as `builtins.warn`. - msg: v: - # `builtins.warn` requires a string message, so we enforce that in our implementation, so that callers aren't accidentally incompatible with newer Nix versions. - assert builtins.isString msg; - builtins.trace "${colorize "1;35" "evaluation warning:"} ${msg}" v - ); in { - mkByName = import ./by-name-overlay.nix {inherit warn;}; + /** + Converts a `by-name` structured directory into. + * + */ + mkByName = import ./by-name.nix; # Warn while merging two attrsets if the RHS shares names with the LHS. + # # # Example: # ```nix # let @@ -36,6 +27,7 @@ in { # in # warnMerge attr1 attr2 "attr1 core value set" # ``` + # # # Type: # {} -> {} -> string -> {} warnMerge = lhs: rhs: sourceSet: let @@ -46,7 +38,10 @@ in { in if overridden != {} then - warn "Attributes overridden while merging ${sourceSet}: ${mkWarning overridden}" + builtins.warn "Attributes overridden while merging ${sourceSet}: ${mkWarning overridden}" merged else merged; + + math = import ./math.nix {inherit lib;}; + options = import ./options.nix {inherit lib;}; } diff --git a/flake.nix b/flake.nix index e0d6353..cb3e2a5 100644 --- a/flake.nix +++ b/flake.nix @@ -12,8 +12,8 @@ inputs = {}; outputs = _: let - lib = import ./default.nix {}; + extra_lib = import ./default.nix; in { - nixLib = lib; + extraLib = extra_lib; }; } diff --git a/math.nix b/math.nix new file mode 100644 index 0000000..9860449 --- /dev/null +++ b/math.nix @@ -0,0 +1,95 @@ +{lib}: rec { + /* + source: https://github.com/NixOS/nix/issues/10387#issuecomment-2494597690 + + Raises the `base` to the power of `power`. + + Considering the small input size and the lack of a built-in power function [2], + this naive power implementation should satisfy reasonable performance + requirements. + + Due to the lack of a modulo and bitwise AND operator, it is questionable whether + the recursive exponentiation by squaring [1] implementation would even be + faster. + + [1]: https://en.wikipedia.org/wiki/Exponentiation_by_squaring + [2]: https://github.com/NixOS/nix/issues/10387 + + # Type + + pow :: Number -> Number -> Number + + # Arguments + + base + : The base number to be raised. + + power + : The exponent to raise `base` to. + + # Examples + + pow 2 5 + => 32 + */ + pow = base: power: + if power == 0 + then 1 + else if power > 0 + then (base * (pow base (power - 1))) + else builtins.throw "Negative powers are not supported"; + + /* + Converts `number` from a binary number to a decimal one. + + The input is indented to be a number composed only of `1`s and `0` (e.g., 10010.) + + # Type + + binaryToDecimal :: Int -> Int + + # Arguments + + number + : The sequence of `1`s and `0`s defining a binary number, in little endian. + + # Examples + + binaryToDecimal 1001 + => 9 + */ + binaryToDecimal = number: let + binaryList = + builtins.map lib.strings.toInt (lib.lists.reverseList (lib.strings.stringToCharacters (builtins.toString number))); + in + builtins.foldl' (acc: num: acc + num) 0 + (lib.lists.imap0 (index: elem: elem * (pow 2 index)) binaryList); + + /* + Converts `string` to a (probably) unique numerical id. + + This is achieved by hashing the string and returning the first six hex-digits + converted to a base 10 number. + + # Type + + idFromString :: String -> Int + + # Arguments + + string + : The string to use as seed for the id generation. + + # Examples + + idFromString "3d-printer" + => 10365713 + */ + idFromString = string: let + inputSeed = + builtins.concatStringsSep "" + (lib.lists.take 6 + (lib.strings.stringToCharacters (builtins.hashString "sha256" string))); + in + lib.trivial.fromHexString inputSeed; +} diff --git a/options.nix b/options.nix new file mode 100644 index 0000000..e01c10d --- /dev/null +++ b/options.nix @@ -0,0 +1,21 @@ +{lib}: { + /* + Generate an enable option definition, that defaults to true. + + This conveniently allows the composition of a “common” module set. + + # Type + + mkEnable :: String -> AttrSet + + # Arguments + + name + : The name to use in the option description. + */ + mkDefaultEnabled = name: + (lib.mkEnableOption name) + // { + default = true; + }; +} -- cgit v1.3.1