Skip to content

Instantly share code, notes, and snippets.

@pstch
Created November 19, 2020 20:49
Show Gist options
  • Save pstch/d43e803443be167b1f642918cf94fd91 to your computer and use it in GitHub Desktop.
Save pstch/d43e803443be167b1f642918cf94fd91 to your computer and use it in GitHub Desktop.
Filtering circular definitions in nested data structures (Nix)
{ lib }:
with builtins;
with lib;
rec {
# Common predicates
nonEmpty = x: length x > 0;
nonEmptyAttrs = x: isAttrs x && nonEmpty (attrNames x);
nonEmptyList = x: isList x && nonEmpty x;
# Recursively apply predicate, exluding values that were already seen
# NOTE: adapted from standard library, adding the `seen` stack
filterAttrsCyclic = filterAttrsCyclic' [ ];
filterAttrsCyclic' = seen: pred: set:
listToAttrs (concatMap (name:
let v = set.${name};
in if (!elem v seen) && name != "_module" && pred v then
[
(nameValuePair name (let s = seen ++ [ v ];
in if isAttrs v then
filterAttrsCyclic' s pred v
else if isList v then
filterListCyclic' s pred v
else
v))
]
else
[ ]) (attrNames set));
# Recursively apply predicate, exluding elements that were already seen
filterListCyclic = filterListCyclic' [ ];
filterListCyclic' = seen: pred: list:
concatLists (map (v:
if (!elem v seen) && pred v then
[
(let s = seen ++ [ v ];
in if isAttrs v then
filterAttrsCyclic' s pred v
else if isList v then
filterListCyclic' s pred v
else
v)
]
else
[ ]) list);
# Recursively clean attributes to prepare for JSON export
# keeps only string, int, bool, non-empty attrsets and lists
# removes cyclic values (already seen) from attrsets and lists
cleanAttrs = x:
(filterAttrsCyclic (v:
isString v || isInt v || isBool v || nonEmptyAttrs v || nonEmptyList v)
x);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment