Skip to content
Merged
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
14 changes: 14 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
help:
just -l

docs:
cd docs && pnpm run dev

ci test="":
nix-unit --override-input target . --flake github:vic/checkmate#.tests.systems.x86_64-linux.system-agnostic.{{test}}

check:
nix flake check --override-input target . github:vic/checkmate

fmt:
nix run github:vic/checkmate#fmt --override-input target .
5 changes: 4 additions & 1 deletion checkmate/modules/formatter.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
perSystem.treefmt.programs.nixf-diagnose.enable = false;
perSystem.treefmt.settings.global.excludes = [ "docs/*" ];
perSystem.treefmt.settings.global.excludes = [
"docs/*"
"Justfile"
];
}
5 changes: 3 additions & 2 deletions nix/forward.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
lib:
# An utility for creating new aspect configuration classes that
# help with separation of concerns.
#
Expand Down Expand Up @@ -39,6 +38,7 @@ lib:
#
# See checkmate/modules/tests/forward.nix for working example.
#
lib:
{
each,
fromClass,
Expand All @@ -47,14 +47,15 @@ lib:
fromAspect,
}:
let
resolve = import ./resolve.nix lib;
include =
item:
let
from = fromClass item;
into = intoClass item;
path = intoPath item;
aspect = fromAspect item;
module = aspect.resolve { class = from; };
module = resolve from [ ] aspect;
config = lib.setAttrByPath path (
{ ... }:
{
Expand Down
15 changes: 2 additions & 13 deletions nix/lib.nix
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
# Public API entry point for flake-aspects library
# Exports: types, transpose, aspects, new, new-scope
lib:
let
# Type system: aspectsType, aspectSubmodule, providerType
types = import ./types.nix lib;

# Generic transposition utility: parameterized by emit function
resolve = import ./resolve.nix lib;
transpose =
{
emit ? lib.singleton,
}:
import ./default.nix { inherit lib emit; };

# Aspect transposition with resolution
aspects = import ./aspects.nix lib;

# Dynamic class forwarding into submodules
forward = import ./forward.nix lib;

# Low-level scope factory: parameterized by callback
new = import ./new.nix lib;

# High-level named scope factory
new-scope = import ./new-scope.nix new;
in
{
Expand All @@ -32,5 +20,6 @@ in
new
new-scope
forward
resolve
;
}
24 changes: 16 additions & 8 deletions nix/resolve.nix
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
# Core aspect resolution algorithm
# Resolves aspect definitions into nixpkgs modules with dependency resolution

lib:
let
# Process a single provider: invoke with context and resolve
include =
class: aspect-chain: provider:
let
provided = provider { inherit aspect-chain class; };
provided = if lib.isFunction provider then provider { inherit aspect-chain class; } else provider;
in
resolve class aspect-chain provided;
inner class aspect-chain provided;

# Main resolution: extract class config and recursively resolve includes
resolve = class: aspect-chain: provided: {
inner = class: aspect-chain: provided: {
imports =
let
config = provided.${class} or { };
Expand All @@ -24,5 +19,18 @@ let
];
};

resolve =
class: aspect-chain: aspect:
let
provided =
if lib.isFunction aspect then
aspect {
inherit class;
aspect-chain = aspect-chain;
}
else
aspect;
in
inner class aspect-chain provided;
in
resolve
34 changes: 11 additions & 23 deletions nix/types.nix
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
# Core type system for aspect-oriented configuration

lib:
let
resolve = import ./resolve.nix lib;

# Type for computed values that only exist during evaluation
ignoredType = lib.types.mkOptionType {
name = "ignored type";
description = "ignored values";
Expand All @@ -24,48 +21,39 @@ let
apply = fn;
};

# Like lib.types.functionTo, but it does not merges all definitions, and keeps
# just the last one.
functorType = lib.mkOptionType {
functorType = lib.types.mkOptionType {
name = "aspectFunctor";
description = "aspect functor function";
check = lib.isFunction;
merge =
loc: defs:
_loc: defs:
let
# Use only the last definition to avoid duplication from
# functionTo merging all definitions with the same args.
# All definitions receive the same merged `self`, so they
# produce equivalent results - picking one is correct.
lastDef = lib.last defs;
innerType = providerType;
in
{
__functionArgs = lib.functionArgs lastDef.value;
__functor =
_: callerArgs:
(lib.modules.mergeDefinitions (loc ++ [ "<function body>" ]) innerType [
{
inherit (lastDef) file;
value = lastDef.value callerArgs;
}
]).mergedValue;
let
result = lastDef.value callerArgs;
in
if builtins.isFunction result then result else _: result;
};
};

# Check if function has submodule-style arguments
isSubmoduleFn =
m:
lib.length (
lib.intersectLists [ "lib" "config" "options" "aspect" ] (lib.attrNames (lib.functionArgs m))
) > 0;
let
args = lib.functionArgs m;
in
args ? lib || args ? config || args ? options || args ? aspect;

# Check if function accepts { class } and/or { aspect-chain }
isProviderFn =
f:
let
args = lib.functionArgs f;
n = lib.length (lib.attrNames args);
n = builtins.length (builtins.attrNames args);
in
(args ? class && n == 1)
|| (args ? aspect-chain && n == 1)
Expand Down