nixpkgs/pkgs/by-name/ni/nixos-option/nixos-option.nix
2025-01-01 13:18:03 +00:00

168 lines
5.3 KiB
Nix
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
nixos,
# list representing a nixos option path (e.g. ['console' 'enable']), or a
# prefix of such a path (e.g. ['console']), or a string representing the same
# (e.g. 'console.enable')
path,
# whether to recurse down the config attrset and show each set value instead
recursive,
}:
let
inherit (nixos.pkgs) lib;
path' = if lib.isString path then (if path == "" then [ ] else readOption path) else path;
# helper that maps `f` on subslices starting when `predStart x` and
# ending when `predEnd x` (no support for nested occurrences)
flatMapSlices =
predStart: predEnd: f: list:
let
empty = {
result = [ ];
active = [ ];
};
op =
{ result, active }:
x:
if predStart x && predEnd x then
{
result = result ++ active ++ f [ x ];
active = [ ];
}
else if predStart x then
{
result = result ++ active;
active = [ x ];
}
else if predEnd x then
{
result = result ++ f (active ++ [ x ]);
active = [ ];
}
else
{
inherit result;
active = active ++ [ x ];
};
in
(x: x.result ++ x.active) (lib.foldl op empty list);
# tries to invert showOption, taking a written-out option name and splitting
# it into its parts
readOption =
str:
let
unescape = list: lib.replaceStrings (map (c: "\\${c}") list) list;
unescapeNixString = lib.flip lib.pipe [
(lib.concatStringsSep ".")
(unescape [ "$" ])
builtins.fromJSON
];
in
flatMapSlices (lib.hasPrefix "\"") (lib.hasSuffix "\"") (x: [ (unescapeNixString x) ]) (
lib.splitString "." str
);
# like 'mapAttrsRecursiveCond' but handling errors in the attrset tree as leaf
# nodes (which means `f` is expected to handle shallow errors)
safeMapAttrsRecursiveCond =
cond: f: set:
let
recurse =
path:
lib.mapAttrs (
name: value:
let
e = builtins.tryEval value;
path' = path ++ [ name ];
in
if e.success && lib.isAttrs value && cond value then recurse path' value else f path' value
);
in
recurse [ ] set;
# traverse the option tree along `path` from `root`, returning the option or
# attrset at the given location
optionByPath =
path: root:
let
into =
opt: part:
if lib.isOption opt && opt.type.descriptionClass == "composite" then
opt.type.getSubOptions [ ]
else if lib.isOption opt then
throw "Trying to access '${part}' inside ${opt.type.name} option while traversing option path '${lib.showOption path}'"
else if lib.isAttrs opt && lib.hasAttr part opt then
opt.${part}
else
throw "Found neither an attrset nor supported option type near '${part}' while traversing option path '${lib.showOption path}'";
in
lib.foldl into root path;
toPretty = lib.generators.toPretty { multiline = true; };
safeToPretty =
x:
let
e = builtins.tryEval (toPretty x);
in
if e.success then e.value else "«error»";
indent = str: lib.concatStringsSep "\n" (map (x: " " + x) (lib.splitString "\n" str));
optionAttrNames = attrs: lib.filter (x: x != "_module") (lib.attrNames attrs);
## full, non-recursive mode: print an option from `options`
renderAttrs =
attrs: "This attribute set contains:\n${lib.concatStringsSep "\n" (optionAttrNames attrs)}";
renderOption =
option: value:
let
entry =
cond: heading: value:
lib.optional cond "${heading}:\n${indent value}";
in
lib.concatStringsSep "\n\n" (
lib.concatLists [
(entry true "Value" (toPretty value))
(entry (option ? default) "Default" (toPretty option.default))
(entry (option ? type) "Type" (option.type.description))
(entry (option ? description) "Description" (lib.removeSuffix "\n" option.description))
(entry (option ? example) "Example" (toPretty option.example))
(entry (option ? declarations) "Declared by" (lib.concatStringsSep "\n" option.declarations))
(entry (option ? files) "Defined by" (lib.concatStringsSep "\n" option.files))
]
);
renderFull =
entry: configEntry:
if lib.isOption entry then
renderOption entry configEntry
else if lib.isAttrs entry then
renderAttrs entry
else
throw "Found neither an attrset nor option at option path '${lib.showOption path'}'";
## recursive mode: print paths and values from `config`
renderRecursive =
config:
let
renderShort = n: v: "${lib.showOption (path' ++ n)} = ${safeToPretty v};";
mapAttrsRecursive' = safeMapAttrsRecursiveCond (x: !lib.isDerivation x);
in
if lib.isAttrs config then
lib.concatStringsSep "\n" (lib.collect lib.isString (mapAttrsRecursive' renderShort config))
else
renderShort [ ] config;
in
if !lib.hasAttrByPath path' nixos.config then
throw "Couldn't resolve config path '${lib.showOption path'}'"
else
let
optionEntry = optionByPath path' nixos.options;
configEntry = lib.attrByPath path' null nixos.config;
in
if recursive then renderRecursive configEntry else renderFull optionEntry configEntry