From 6dba1aaca8586ea164c87b8c2708573d6fcf6376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9ana=20=E6=B1=9F?= Date: Mon, 23 Jun 2025 17:23:04 +0200 Subject: [PATCH] better clustering --- default.nix | 102 ++++++++++++++++++++++++++++++++++++++++++---------- shell.nix | 23 ++---------- toDot.py | 41 +++++++++++++++++++++ 3 files changed, 128 insertions(+), 38 deletions(-) create mode 100755 toDot.py diff --git a/default.nix b/default.nix index 59207568..55e925d8 100644 --- a/default.nix +++ b/default.nix @@ -16,6 +16,14 @@ }; }; + packages = import ./nix/packages {inherit sources;}; + + # + # + # Module graph generation experiment below + # + # + inherit (nixosConfigurations.vanadium) options; /* @@ -27,21 +35,28 @@ We could draw out file -> option, and cluster each option in a file rectangle */ + traceShowId = x: builtins.trace x x; + # Drop those that fail to eval - filtered = let - drop = path: { - inherit path; - update = _: {}; - }; - in - lib.updateManyAttrsByPath - (map drop [ - ["_module"] - ["services" "immich"] # caused by option referencing a config value - ["services" "nagios"] - ["hardware" "nvidia"] - ]) - options; + filtered = + let + drop = path: { + inherit path; + update = _: {}; + }; + in + lib.updateManyAttrsByPath + (map drop [ + ["_module"] + ["services" "immich"] # caused by option referencing a config value + ["services" "nagios"] + ["hardware" "nvidia"] + ]) + # options + # Use this for a smaller example + # {inherit (options.programs) vim less;} + { inherit (options) programs; } + ; # Transform each option to its path and call site mapped = @@ -55,7 +70,9 @@ _type = "result"; optionPath = v.loc; definedBy = definedBy.value; - declaredAt = map (x: x.file) v.declarationPositions; + declaredAt = + # There can only be one + (builtins.head v.declarationPositions).file; } ) filtered; @@ -70,13 +87,62 @@ # Run `nix eval --json -f ./. 'asList' | jq >asList.json` to generate asList = let - untag = map (lib.flip lib.removeAttrs ["_type"]); + untag = lib.flip lib.removeAttrs ["_type"]; in - untag ( + map untag ( attrValuesRecursiveCond (as: !(as ? _type && as._type == "result")) mapped ); - packages = import ./nix/packages {inherit sources;}; + # TODO: Improve the granularity by checking the prefix + # + # Each attribute is a list of nodes in the same file + clusterized = + builtins.foldl' ( + acc: elem: + acc + // { + ${elem.declaredAt} = + acc.${elem.declaredAt} or [] + ++ [elem]; + } + ) + {} + asList; + + subgraphs = + lib.mapAttrsToList ( + declaredAt: elems: let + entries = + lib.concatMapStrings ( + elem: + lib.concatMapStrings ( + def: + lib.optionalString ( + def != elem.declaredAt # remove loops + ) + '' + "${def}" -> "${builtins.concatStringsSep "." elem.optionPath}"; + '' + ) + elem.definedBy + ) + elems; + in '' + subgraph "cluster_${declaredAt}" { + ${entries} + label = "${declaredAt}"; + } + '' + ) + clusterized; + + graphvizOutput = '' + digraph { + // compound=true; + rankdir="TB"; + ${builtins.concatStringsSep "\n" subgraphs} + } + ''; } diff --git a/shell.nix b/shell.nix index 5cea379e..32de333c 100644 --- a/shell.nix +++ b/shell.nix @@ -11,25 +11,8 @@ pkgs.mkShell { name = "dotfiles"; packages = with pkgs; [ - # - # Just scripts - # - just - jq - - # - # XMonad - # - (haskellPackages.ghcWithPackages (self: [ - self.xmonad-contrib - self.xmonad-extras - ])) - haskell-language-server - cabal-install - - # - # Installer testing - # - disko + python3 + pyright + graphviz ]; } diff --git a/toDot.py b/toDot.py new file mode 100755 index 00000000..a9e45315 --- /dev/null +++ b/toDot.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +import json + + +def group_by_prefix(x, y): + + + +# sections = [] +# with open("asList.json", "r") as generatedFile: +# data = json.load(generatedFile) +# +# # list of all groups of entries that are in the same file +# +# by_file = {} +# for entry in data: +# by_file[entry["declaredAt"][0]] = entry +# +# for file, entry in by_file.items(): +# def as_cluster(content): +# return f""" +# subgraph \"{file}\" {{ +# {content} +# }} +# """ +# +# buffer = [] +# for arc in entry["definedBy"]: +# for defsite in entry["declaredAt"]: +# if arc != defsite: +# +# buffer += [ +# f"\"{arc}\" -> \"{".".join(entry["optionPath"])}\";" +# ] +# +# if len(buffer) > 0: +# sections += [ as_cluster("\n".join(buffer)) ] +# +# content = "\n".join(sections) +# print("digraph {\n compound=true;\n" + content + "\n}")