Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions modules/deprecated.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
40 changes: 38 additions & 2 deletions modules/hjem.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,49 @@
rumLib,
inputs,
}: {
config,
options,
...
}: let
resolvedConfig =
rumLib.modules.resolveModulesFromLazyModule {
modulesDir = ./collection;
deferredModule = config.rum;
extraModules = [
inputs.hjem.nixosModules.hjem-lib
{_module.args.rumLib = rumLib;}
(
{
config,
options,
...
}: {
options = options.rum;
config.rum = builtins.removeAttrs config ["rum"];
}
Comment on lines +18 to +25
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This exists as a workaround to the fact that we are basically declaring options at user.<user>.rum.rum atm.

)
./deprecated.nix
];
inherit rumLib options;
}
// {
_module.args.rumLib = rumLib;
};

usedConfig = builtins.removeAttrs resolvedConfig ["rum"];
in {
# Import the Hjem Rum module collection as an extraModule available under `hjem.users.<username>`
# This allows the definition of rum modules under `hjem.users.<username>.rum`

# Import the collection modules recursively so that all files
# are imported. This then gets imported into the user's
# 'hjem.extraModules' to make them available under 'hjem.users.<username>'
imports = [inputs.hjem.nixosModules.hjem-lib] ++ lib.filesystem.listFilesRecursive ./collection;
options.rum = lib.mkOption {
type = lib.types.deferredModule;
default = {};
};

_module.args.rumLib = rumLib;
config = {
files = usedConfig;
};
Comment on lines +48 to +50
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intended to be config = usedConfig;, however during debugging I suspected that to cause the infrec, and I haven't changed it back yet

}
288 changes: 288 additions & 0 deletions modules/lib/black_magic.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
{lib}: let
inherit
(builtins)
filter
isAttrs
isNull
isString
concatLists
head
readDir
mapAttrs
length
hasAttr
getAttr
concatStringsSep
attrNames
attrValues
;
inherit
(lib.attrsets)
mapAttrsToList
showAttrPath
attrByPath
mergeAttrsList
filterAttrs
recursiveUpdate
;
inherit
(lib.lists)
last
drop
dropEnd
fold
;
inherit (lib.modules) evalModules;
/**
This is effectively (builtins.tail list), however even the docs themselves state
to avoid that function due to an operation cost of O(n) instead of O(1) per call.
tail :: [ T ] -> [ T ]
*/
tail = list: (drop 1 list);
/**
A bit of terminology to prevent possible confusion of myself and others in the future:
path: An attribute path,
e.g.
{ a = { b = "c"; }; } => [ "a" "b" ]
or
{ a = { b = mkOption {...}; }; } => [ "a" "b" ]
filetree:
Just your regular directory with possibly nested directories.
filetree != path (in this context)
Some custom types used throughout the annotations:
filetree :: { ${pathComponent} :: (filetree | ${file}}) }
attrPath :: [ string ]
moduleResolutionResult :: { resolved :: [ ${filepath} ]; unresolved :: [ ${attrPath} ] }
*/

resolveFileTreeRecursive = path: let
dirItems = readDir path;
in
mapAttrs (
name: value: let
ItemPath = path + ("/" + name);
in
if value == "directory"
then resolveFileTreeRecursive ItemPath
else ItemPath
)
dirItems;

/**
pop the last element from the list
pop :: [ T ] -> [ T ]
*/
Comment on lines +75 to +79
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function vanished, will clean up later


/**
If the current attrset is a final value, return an empty path as there are no child paths.
Else, recurse into each child value, get their paths, add the child name to the path,
and combine all child paths into one list
getAttrPaths' :: { ... :: ?; _type ? :: string } -> [ attrPath | [ string | 1 ] ]
*/
getAttrPaths' = attrset:
if !(isAttrs attrset) || (attrset ? _type && isString attrset._type)
then
if isAttrs attrset && attrset._type == "if"
then [[1]] # No fucking idea what do do here instead of throwing, I dont think this can happen though.
else [[]]
else
concatLists (
mapAttrsToList (name: value: map (path: [name] ++ path) (getAttrPaths' value)) attrset
);

/**
getAttrPaths' but with checking
getAttrPaths :: { ... :: ?; _type ? :: string } -> [ attrPath ]
*/
getAttrPaths = set:
map (
path:
if (last path) == 1
then throw "Encountered mkIf value at ${showAttrPath (dropEnd 1 path)}"
else path
) (getAttrPaths' set);

pathToAttr = path: value:
if (length path) > 0
then {"${head path}" = pathToAttr (tail path) value;}
else value;
in {
/**
Take a deferredModule, and use it to determine what modules need loading
resolveModulesFromLazyModule :: { modulesDir :: Path; deferredModule :: deferredModule; rumLib :: rumLib; extraModules ? :: [ module ] } -> [ module ]
*/
resolveModulesFromLazyModule = {
modulesDir,
deferredModule,
rumLib,
extraModules ? [],
options,
}: let
moduleFileTree = resolveFileTreeRecursive modulesDir;
/**
To collect the paths of files to import we need to do a couple things:
- ~~Figure out the attrPaths to the bottommost option declarations~~ 6 months later; what does this even mean??? ( 2(?) months after, I still dont know what I was yapping about )
- Consider mkIf's
- Make sure to filter (Filter *what* ??????)
*/

/**
Attempt to resolve all used config values without a matching option into filepaths pointing to modules
resolveLazyModules :: { config :: ?; options :: ? } -> moduleResolutionResult
*/
resolveLazyModules = {
config,
options,
...
}: let
/**
All config values that dont have a matching option (?)
freeformAttrPaths :: [ attrPath ]
*/
freeformAttrPaths = filter (
path: let
maybeOption = attrByPath path null options;
in
!(isAttrs maybeOption && maybeOption ? _type && maybeOption._type == "option")
) (getAttrPaths config);

/**
Recurse into each part of a path and try to resolve it to a file, returning null when unsucessful
resolvePathToModule' :: attrPath -> { ...: string } -> string | null
*/
resolvePathToModule' = path: filetree: let
headElem = head path;
in
if hasAttr headElem filetree
then let
subtree = getAttr headElem filetree;
in
assert (isAttrs subtree);
resolvePathToModule' (tail path) subtree
else let
fileHeadElem = headElem + ".nix";
in
if hasAttr fileHeadElem filetree
then let
file = getAttr fileHeadElem filetree;
in
assert isString file; file
else if filetree ? "default.nix"
then filetree."default.nix"
else null;

/**
Resolve an attrPath to a module file.
Return format: { "attr.path.seperated.with.dots" = "file/or/null/if/file/doesn't/exist.nix"}
resolvePathToModule :: attrPath -> { ${attrPath} :: string | null }
*/
resolvePathToModule = path: {
"${concatStringsSep "." path}" = resolvePathToModule' path moduleFileTree;
};

/**
All resolved and unresolved module files
allModules :: { ${attrPath} :: string | null }
*/
allModules = mergeAttrsList (map resolvePathToModule freeformAttrPaths);

/**
Get it? because you filter for all items "where Value is [what]". e.g. all items where Value is String
whereValue :: (? -> bool) -> { ... :: ? } -> { ... :: ? }
*/
whereValue = isWhat: filterAttrs (_: isWhat);

out = {
resolved = attrValues (whereValue isString allModules);
unresolved = attrNames (whereValue isNull allModules);
};
in
out;

/**
iterate :: [ module ] -> { config :: { ... :: ? }; options :: { ... :: ? } }
*/
iterate = resolvedModules:
evalModules {
modules =
resolvedModules
++ [
deferredModule
(
let
opts = removeAttrs options ["_module"];
in {
_file = "${__curPos.file}:${builtins.toString __curPos.line}";
options = opts;

config = {
_module = {
freeformType = lib.types.attrs;
args = {inherit rumLib;};
};
};
}
)
];
};
/**
converge :: [ module ] -> moduleResolutionResult -> Int -> { config :: { ... :: ? }; unresolved :: [ string ] }
*/
converge = resolvedModules: prevAllModules: limit:
if limit == 0
then throw "Module evaluation did not converge after iteration limit"
else let
current = iterate prevAllModules.resolved;
currentAllModules = resolveLazyModules current;
in
if (length currentAllModules.resolved) == 0
then {
config = current.config;
inherit (currentAllModules) unresolved;
} # Converged
else converge (resolvedModules ++ currentAllModules.resolved) currentAllModules (limit - 1);

/**
converged :: { config :: { ... :: ?}; unresolved :: [ string ] }
*/
converged = converge extraModules {resolved = [];} 20;

/**
config :: { ... :: ? }
*/
config =
if (length converged.unresolved) > 0
then throw "Hjem-Rum: Couldn't find module(s) matching the following configuration value(s) \n ${converged.unresolved}"
else converged.config;

/**
`config`, filtered to only include values that match non-rum options. (i.e. hjem options)
Yes, this basically is the same logic as for `freeformAtrrPaths`, except the filter inverted
finalConfigAttrPaths :: { ... :: ? }
*/
finalConfigAttrPaths = filter (
path: let
maybeOption = attrByPath path null options;
in (isAttrs maybeOption && maybeOption ? _type && maybeOption._type == "option")
) (getAttrPaths config);

finalConfig = fold recursiveUpdate {} (
map (path: pathToAttr path (attrByPath path null config)) finalConfigAttrPaths
);
in
finalConfig;
}
1 change: 1 addition & 0 deletions modules/lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
attrsets = import ./attrsets {inherit lib;};
generators = import ./generators {inherit lib;};
types = import ./types {inherit lib;};
modules = import ./black_magic.nix {inherit lib;};
}
Loading