commit 94d3322007b5cab8ec5539fabc7848fe5270b1c0
parent 7ee8f8c83df6a0eb66de6d8c4e27b16fdc4ddd32
Author: David Arnold <dgx.arnold@gmail.com>
Date: Mon, 10 Apr 2023 16:13:09 -0500
parent 7ee8f8c83df6a0eb66de6d8c4e27b16fdc4ddd32
Author: David Arnold <dgx.arnold@gmail.com>
Date: Mon, 10 Apr 2023 16:13:09 -0500
hoist list (#3) load: make transformer accept cursor as an argument load: accept a list of transformers transformers: add hoistLists Co-authored-by: figsoda <figsoda@pm.me>
11 files changed, 141 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md @@ -41,9 +41,9 @@ Arguments: `self`, `super`, and `root` are reserved names that cannot be passed as an input. To work around that, remove them using `removeAttrs`, or pass them by overriding the loader. -- (optional) `transformer` : `{ ... } -> a` +- (optional) `transformer` : `(cursor : [ String ]) -> { ... } -> a` - Module transformer, defaults to `id` (no transformation). + Module transformer, defaults to `_: id` (no transformation). This will transform each directory module in `src`, including the root. The main entry point of haumea. This is probably the function you are looking for. @@ -168,12 +168,29 @@ It is useful when the files being loaded are mostly functions that don't require ### [`transformers.liftDefault`](src/transformers/liftDefault.nix) -Type: `{ ... } -> { ... }` +Type: `[ String ]: { ... } -> { ... }` This transformer will lift the contents of `default` into the module. It will fail if `default` is not an attribute set, or has any overlapping attributes with the module. +### [`transformers.hoistLists`](src/transformers/hoistLists.nix) + +Type: `(from : String) -> (to : String) -> [ String ] -> { ... } -> { ... }` + +This transformer will hoist any attribute of type List with key +`${from}` up the chain. When the root node is reached, it will +be renamed to an attribute of type List with key `${to}` and +as such presented back to the consumer. + +Neighbouring lists are concatenated (`++`) during hoisting. +Root doesn't concat `${from}` declarations, use `${to}` at +the root. + +This can be used to declare `imports` locally at the leaves +of the configuration tree, where the module system would +not otherwise tolerate them. + ## Alternatives [std](https://github.com/divnix/std) is a more full-featured framework that also has filesystem-based auto-importing.
diff --git a/src/load.nix b/src/load.nix @@ -23,6 +23,7 @@ let pipe remove take + toList ; parsePath = suffix: path: @@ -43,7 +44,7 @@ let view = { cursor ? [ ], node, pov, transformer }: if node.isDir then - transformer + transformer cursor (flip concatMapAttrs node.children (name: node: optionalAttrs { @@ -121,8 +122,12 @@ in { src , loader ? root.loaders.default , inputs ? { } -, transformer ? id +, transformer ? _: id }: +let + transformer' = cursor: flip pipe + (map (t: t cursor) (toList transformer)); +in assert all (name: inputs ? ${name} @@ -130,15 +135,16 @@ assert all [ "self" "super" "root" ]; view { - inherit transformer; pov = "external"; + transformer = transformer'; node = fix (node: { isDir = true; children = aggregate { inherit src loader inputs; tree = { - inherit node transformer; pov = [ ]; + transformer = transformer'; + inherit node; }; }; });
diff --git a/src/transformers/_utils/concatMapAttrsWith.nix b/src/transformers/_utils/concatMapAttrsWith.nix @@ -0,0 +1,30 @@ +{ lib }: + +# map each attribute in the given set into +# a list of attributes and subsequently merge them into +# a new attribute set with the specified mergeFun. + +# Type: ({ ... } -> { ... } -> { ... }) -> (String -> a -> { ... }) -> { ... } -> { ... } + +# Example: +# concatMapAttrsWith (mergeAttrsButConcatOn "mykey") +# (name: value: { +# ${name} = value; +# ${key} = value ++ value; +# }) +# { x = "a"; y = "b"; } +# => { x = "a"; y = "b"; mykey = [ "aa" "bb"]; } + +let + inherit (builtins) + attrValues + foldl' + mapAttrs + ; + inherit (lib) + flip + pipe + ; +in + +merge: f: flip pipe [ (mapAttrs f) attrValues (foldl' merge { }) ]
diff --git a/src/transformers/hoistLists.nix b/src/transformers/hoistLists.nix @@ -0,0 +1,47 @@ +{ lib, super }: + +from: to: + +# Example from / to +# - Lifting `imports` from: _imports, to: imports +# +# Note: +# underscore used as mere convention to signalling to the user the "private" +# nature, they won't be part of the final view presented to the user + +let + inherit (builtins) + removeAttrs + ; + inherit (lib) + catAttrs + concatLists + ; + inherit (super.utils) + concatMapAttrsWith + ; + + # merge attributes shallowly, but concat values of a specific key into a list in that key + # Type: ((key : String) -> { ... } -> { ... }) -> { ${key} : [ a ], ... } + mergeAttrsButConcatOn = key: x: y: + x // y // { + ${key} = concatLists (catAttrs key [ x y ]); + }; +in + +cursor: + +if cursor == [ ] # toplevel +then + concatMapAttrsWith (mergeAttrsButConcatOn to) + (file: value: if ! value ? ${from} then { ${file} = value; } else { + ${file} = removeAttrs value [ from ]; + # top level ${from} declarations are omitted from merging + ${to} = value.${from}; + }) +else + concatMapAttrsWith (mergeAttrsButConcatOn from) + (file: value: if ! value ? ${from} then { ${file} = value; } else { + ${file} = removeAttrs value [ from ]; + ${from} = value.${from}; + })
diff --git a/src/transformers/liftDefault.nix b/src/transformers/liftDefault.nix @@ -6,7 +6,7 @@ let ; in -mod: +_: mod: unionOfDisjoint (removeAttrs mod [ "default" ])
diff --git a/tests/hoistLists/__fixture/bar.nix b/tests/hoistLists/__fixture/bar.nix @@ -0,0 +1,3 @@ +{ + _imports = [ "bar" ]; +}
diff --git a/tests/hoistLists/__fixture/baz/default.nix b/tests/hoistLists/__fixture/baz/default.nix @@ -0,0 +1,4 @@ +{ + qux = "qux"; + _imports = [ "baz" ]; +}
diff --git a/tests/hoistLists/__fixture/default.nix b/tests/hoistLists/__fixture/default.nix @@ -0,0 +1,3 @@ +{ + imports = [ "root" ]; +}
diff --git a/tests/hoistLists/__fixture/foo.nix b/tests/hoistLists/__fixture/foo.nix @@ -0,0 +1 @@ +"foo"
diff --git a/tests/hoistLists/expected.nix b/tests/hoistLists/expected.nix @@ -0,0 +1,6 @@ +{ + imports = [ "bar" "baz" "root" ]; + foo = "foo"; + bar = { }; + baz = { qux = "qux"; }; +}
diff --git a/tests/hoistLists/expr.nix b/tests/hoistLists/expr.nix @@ -0,0 +1,16 @@ +{ haumea }: + +let + inherit (haumea.transformers) + liftDefault + hoistLists + ; +in + +haumea.load { + src = ./__fixture; + transformer = [ + liftDefault + (hoistLists "_imports" "imports") + ]; +}