Skip to content

Commit

Permalink
lib.options.filterOptions
Browse files Browse the repository at this point in the history
Co-authored-by: Samyak S Sarnayak <samyak201@gmail.com>
  • Loading branch information
mightyiam and Samyak2 committed Mar 16, 2024
1 parent 9a9a755 commit adc61bd
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 1 deletion.
2 changes: 1 addition & 1 deletion lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ let
scrubOptionValue literalExpression literalExample
showOption showOptionWithDefLocs showFiles
unknownModule mkOption mkPackageOption mkPackageOptionMD
mdDoc literalMD;
mdDoc literalMD filterOptions;
inherit (self.types) isType setType defaultTypeMerge defaultFunctor
isOptionType mkOptionType;
inherit (self.asserts)
Expand Down
61 changes: 61 additions & 0 deletions lib/options.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ let
;
inherit (lib.attrsets)
attrByPath
concatMapAttrs
optionalAttrs
;
inherit (lib.strings)
Expand Down Expand Up @@ -466,6 +467,66 @@ rec {
${concatMapStringsSep "\n" (defFile: " - ${defFile}") opt.files}
'';

/* Filters an option tree, such that the returned tree only contains the options for which the callback returns `true`.
Arguments passed to the callback are:
* The option path, as a list of strings representing the attribute names.
* The option declaration at that path.
Non-attrset values pass through unchanged.
It is not possible to prune entire branches of the option tree in one go, as the callback is only invoked for each option (leaf).
Type
```
filterOptions :: (List String -> Option -> Bool) -> Options -> Options
```
Example
:::{.example}
## `filterOptions` usage example
```nix
filterOptions
(path: option: option.description == "a" || elem "c" path)
{
a = mkOption { description = "a"; };
b = mkOption { description = "b"; };
c = mkOption { description = "foo"; };
}
=> {
a = mkOption { description = "a"; };
c = mkOption { description = "foo"; };
}
```
::: */
filterOptions = predicate: options: let
recurse = path: options:
concatMapAttrs
(
name: value: let
newPath = path ++ [name];
in
if isAttrs value then
if isOption value then
if predicate newPath value then {${name} = value;}
else {}
else # attrs, not option
let branch = recurse newPath value;
in if branch == {} then {} else {${name} = branch;}
else # not attrs
{${name} = value;}
)
options;
in
recurse [] options;

unknownModule = "<unknown-file>";

}
49 changes: 49 additions & 0 deletions lib/tests/misc.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2154,4 +2154,53 @@ runTests {
};
expected = "c";
};

testFilterOptionsEmpty = {
expr = filterOptions (p: o: true) {};
expected = {};
};

testFilterOptionsNoFilter = {
expr = filterOptions (p: o: true) {foo-1 = mkOption {};};
expected = {foo-1 = mkOption {};};
};

testFilterOptionsFilter = {
expr = filterOptions (p: o: hasPrefix "foo-" o.description) {
foo-1 = mkOption {description = "foo-1";};
bar-1 = mkOption {description = "bar-1";};
};
expected = {
foo-1 = mkOption {description = "foo-1";};
};
};

testFilterOptionsFilterDeep = {
expr = filterOptions (p: o: o.description == "foo") {
nested.foo = mkOption {description = "foo";};
nested.bar = mkOption {description = "bar";};
};
expected.nested.foo = mkOption {description = "foo";};
};

testFilterOptionsFilterPath = {
expr = filterOptions (p: o: elem "foo" p) {
nested.foo = mkOption {};
nested.bar = mkOption {};
};
expected.nested.foo = mkOption {};
};

testFilterOptionsNoEmptyBranches = {
expr = filterOptions (p: o: elem "foo" p) {
foo.a = mkOption {};
bar.a = mkOption {};
};
expected.foo.a = mkOption {};
};

testFilterOptionsNonAttrsetValuesPassthrough = {
expr = filterOptions (p: o: false) { a = []; };
expected.a = [];
};
}

0 comments on commit adc61bd

Please sign in to comment.