From 7a2155b30cc09367d364abb7db2b55991e862c1f Mon Sep 17 00:00:00 2001
From: h7x4 <h7x4@nani.wtf>
Date: Tue, 25 Mar 2025 13:26:42 +0100
Subject: [PATCH] home/modules/shellAliases: move logic info format, rewrite
 text gen

---
 home/modules/shellAliases.nix | 327 +++++++++++++++++-----------------
 1 file changed, 164 insertions(+), 163 deletions(-)

diff --git a/home/modules/shellAliases.nix b/home/modules/shellAliases.nix
index 7c538d6..3cf5234 100644
--- a/home/modules/shellAliases.nix
+++ b/home/modules/shellAliases.nix
@@ -1,50 +1,161 @@
-{ pkgs, lib, extendedLib, inputs, config, ... }: let
-  inherit (lib) types mkEnableOption mkOption mdDoc;
+{ config, pkgs, lib, extendedLib, ... }: let
   cfg = config.local.shell;
 
-# NOTE:
-#       This module is an over-engineered solution to a non-problem.
-#       It is a fun experiment in using the Nix language to create a
-#       shell alias system that organizes aliases into a tree structure,
-#       with categories and subcategories and subsubcategories and so on.
-#
-#       It also has a lazy join function that will join a list of commands
-#       with a separator, but render it in a prettier way in a documentation
-#       file that you can print out and read.
-
-  isAlias = v: builtins.isAttrs v && v ? "alias" && v ? "type";
-in {
-  options.local.shell = {
-    aliases = let
-
-      coerceStrToAlias = str: {
-        type = " ";
-        alias = [ str ];
-      };
-
-      aliasType = (types.coercedTo types.str coerceStrToAlias (types.submodule {
-        options = {
-          type = mkOption {
-            type = types.enum [ "|" "&&" ";" " " ];
-            default = ";";
-            description = ''
-              If the alias is a list of commands, this is the kind of separator that will be used.
-            '';
+  shellAliasesFormat = let
+    formatLib = {
+      functors = let
+        inherit (extendedLib.termColors.front) blue;
+      in
+        {
+          "|" = {
+            apply = f: lib.concatStringsSep " | " f.alias;
+            stringify = f: lib.concatStringsSep (blue "\n| ") f.alias;
           };
-          alias = mkOption {
-            type = types.listOf types.str;
+          "&&" = {
+            apply = f: lib.concatStringsSep " && " f.alias;
+            stringify = f: lib.concatStringsSep (blue "\n&& ") f.alias;
+          };
+          ";" = {
+            apply = f: lib.concatStringsSep "; " f.alias;
+            stringify = f: lib.concatStringsSep (blue ";\n  ") f.alias;
+          };
+          " " = {
+            apply = f: lib.concatStringsSep " " f.alias;
+            stringify = f: lib.concatStringsSep " \\\n   " f.alias;
+          };
+        };
+
+      isAlias = v: builtins.isAttrs v && v ? "alias" && v ? "type";
+    };
+  in {
+    lib = formatLib;
+
+    type = let
+      rawAliasType = lib.types.submodule {
+        options = {
+          type = lib.mkOption {
+            description = "If the alias is a list of commands, this is the kind of separator that will be used.";
+            type = lib.types.enum (lib.attrNames formatLib.functors);
+            default = ";";
+            example = "&&";
+          };
+          alias = lib.mkOption {
+            description = "List of commands that will be concatenated together.";
+            type = with lib.types; listOf str;
+            example = [
+              "ls"
+              "grep nix"
+            ];
           };
         };
-      })) // {
-        # NOTE: this check is necessary, because nix will recurse on types.either,
-        #       and report that the option does not exist.
-        #       See https://discourse.nixos.org/t/problems-with-types-oneof-and-submodules/15197
-        check = v: builtins.isString v || isAlias v;
       };
 
-      recursingAliasTreeType = types.attrsOf (types.either aliasType recursingAliasTreeType);
-    in mkOption {
-      type = recursingAliasTreeType;
+      coercedAliasType = with lib.types; let
+        coerce = str: {
+          type = " ";
+          alias = [ str ];
+        };
+      in (coercedTo str coerce rawAliasType) // {
+        check = v: builtins.isString v || formatLib.isAlias v;
+      };
+
+      aliasTreeType = with lib.types; attrsOf (either coercedAliasType aliasTreeType);
+    in aliasTreeType;
+
+    # Alias Tree -> { :: Alias }
+    generateAttrs = let
+      inherit (lib) mapAttrs attrValues filterAttrs isAttrs
+                    isString concatStringsSep foldr;
+
+      applyFunctor = attrset: formatLib.functors.${attrset.type}.apply attrset;
+
+      # TODO: better naming
+      allAttrValuesAreStrings = attrset: let
+
+        # [ {String} ]
+        filteredAliases = [(filterAttrs (_: isString) attrset)];
+
+        # [ {String} ]
+        remainingFunctors = let
+          functorSet = filterAttrs (_: formatLib.isAlias) attrset;
+          appliedFunctorSet = mapAttrs (_: applyFunctor) functorSet;
+        in [ appliedFunctorSet ];
+
+        # [ {AttrSet} ]
+        remainingAliasSets = attrValues (filterAttrs (_: v: isAttrs v && !formatLib.isAlias v) attrset);
+
+        # [ {String} ]
+        recursedAliasSets = filteredAliases
+                          ++ (remainingFunctors)
+                          ++ (map allAttrValuesAreStrings remainingAliasSets);
+      in foldr (a: b: a // b) {} recursedAliasSets;
+
+    in
+      allAttrValuesAreStrings;
+
+    # TODO:
+    # generateAttrs = pipe [
+      # collect leave nodes
+      # map apply functor
+    # ]
+
+    # Alias Tree -> String
+    generateText = aliases: let
+      inherit (extendedLib.termColors.front) red green blue;
+
+      # String -> Alias -> String
+      stringifyAlias = aliasName: alias: let
+        # String -> String
+        removeNixLinks = text: let
+          maybeMatches = lib.match "(|.*[^)])(/nix/store/.*/bin/).*" text;
+          matches = lib.mapNullable (lib.remove "") maybeMatches;
+        in
+          if (maybeMatches == null)
+          then text
+          else lib.replaceStrings matches (lib.replicate (lib.length matches) "") text;
+
+        # Alias -> String
+        applyFunctor = attrset: let
+          applied = formatLib.functors.${attrset.type}.stringify attrset;
+          indent' = lib.strings.replicate (lib.stringLength "${aliasName} -> ") " ";
+        in
+          lib.replaceStrings ["\n"] [("\n" + indent')] applied;
+      in "${red aliasName} -> ${blue "\""}${removeNixLinks (applyFunctor alias)}${blue "\""}";
+
+      # String -> String
+      indent = x: lib.pipe x [
+        (x: "\n" + x)
+        (lib.replaceStrings ["\n"] [("\n" + (lib.strings.replicate 2 " "))])
+        (lib.removePrefix "\n")
+      ];
+
+      # String -> { :: Alias | Category } -> String
+      stringifyCategory = categoryName: category: lib.pipe category [
+        (category: let
+          aliases = lib.filterAttrs (_: formatLib.isAlias) category;
+        in {
+          inherit aliases;
+          subcategories = lib.removeAttrs category (lib.attrNames aliases);
+        })
+        ({ aliases, subcategories }: {
+          aliases = lib.mapAttrsToList stringifyAlias aliases;
+          subcategories = lib.mapAttrsToList stringifyCategory subcategories;
+        })
+        ({ aliases, subcategories }:
+          lib.concatStringsSep "\n" (lib.filter (x: lib.trim x != "") [
+            "[${green categoryName}]"
+            (indent (lib.concatStringsSep "\n" aliases) + "\n")
+            (indent (lib.concatStringsSep "\n" subcategories) + "\n")
+          ])
+        )
+      ];
+    in (stringifyCategory "Aliases" aliases);
+  };
+in {
+  options.local.shell = {
+    aliases = lib.mkOption {
+      # TODO: freeformType
+      type = shellAliasesFormat.type;
       description = "A tree of aliases";
       default = { };
       example = {
@@ -71,8 +182,8 @@ in {
       };
     };
 
-    variables = mkOption {
-      type = types.attrsOf types.str;
+    variables = lib.mkOption {
+      type = with lib.types; attrsOf str;
       description = "Environment variables";
       default = { };
     };
@@ -82,129 +193,19 @@ in {
 
     # };
 
-    enablePackageManagerLecture = mkEnableOption "distro reminder messages, aliased to common package manager commands";
-    enableAliasOverview = mkEnableOption "`aliases` command that prints out a list of all aliases" // {
+    enablePackageManagerLecture = lib.mkEnableOption "distro reminder messages, aliased to common package manager commands";
+
+    enableAliasOverview = lib.mkEnableOption "`aliases` command that prints out a list of all aliases" // {
       default = true;
       example = false;
     };
   };
 
-  config = let
-    sedColor =
-      color:
-      inputPattern:
-      outputPattern:
-      "-e \"s|${inputPattern}|${outputPattern.before or ""}$(tput setaf ${toString color})${outputPattern.middle}$(tput op)${outputPattern.after or ""}|g\"";
-
-    colorRed = sedColor 1;
-
-    colorSlashes = colorRed "/" {middle = "/";};
-
-    # Alias type functors
-    # These will help pretty print the commands
-    functors = let
-      inherit (lib.strings) concatStringsSep;
-      inherit (extendedLib.termColors.front) blue;
-    in
-      {
-        "|" = {
-          apply = f: concatStringsSep " | " f.alias;
-          stringify = f: concatStringsSep (blue "\n| ") f.alias;
-        };
-        "&&" = {
-          apply = f: concatStringsSep " && " f.alias;
-          stringify = f: concatStringsSep (blue "\n&& ") f.alias;
-        };
-        ";" = {
-          apply = f: concatStringsSep "; " f.alias;
-          stringify = f: concatStringsSep (blue ";\n  ") f.alias;
-        };
-        " " = {
-          apply = f: concatStringsSep " " f.alias;
-          stringify = f: concatStringsSep " \\\n   " f.alias;
-        };
-      };
-
-    aliasTextOverview = let
-      inherit (lib) stringLength length concatStringsSep replaceStrings
-                    attrValues mapAttrs isAttrs remove replicate mapNullable;
-
-      inherit (extendedLib.termColors.front) red green blue;
-
-      # String -> String -> String
-      wrap' = wrapper: str: wrapper + str + wrapper;
-
-      # [String] -> String -> String -> String
-      replaceStrings' = from: to: replaceStrings from (replicate (length from) to);
-
-      # String -> Int -> String
-      repeatString = string: times: concatStringsSep "" (replicate times string);
-
-      # int -> String -> AttrSet -> String
-      stringifyCategory = level: name: category: let
-        title = "${repeatString "  " level}[${green name}]";
-
-        commands = attrValues ((lib.flip mapAttrs) category (n: v: let
-            # String
-            indent = repeatString "  " level;
-
-            # String -> String
-            removeNixLinks = text: let
-              maybeMatches = builtins.match "(|.*[^)])(/nix/store/.*/bin/).*" text;
-              matches = mapNullable (remove "") maybeMatches;
-            in
-              if (maybeMatches == null)
-              then text
-              else replaceStrings' matches "" text;
-
-            applyFunctor = attrset: let
-              applied = functors.${attrset.type}.stringify attrset;
-              indent' = indent + (repeatString " " ((stringLength " -> \"") + (stringLength n))) + " ";
-            in
-              replaceStrings' ["\n"] ("\n" + indent') applied;
-
-            recurse = stringifyCategory (level + 1) n v;
-          in if isAlias v
-            then "${indent}  ${red n} -> ${wrap' (blue "\"") (removeNixLinks (applyFunctor v))}"
-            else recurse
-        ));
-      in concatStringsSep "\n" ([title] ++ commands) + "\n";
-    in (stringifyCategory 0 "Aliases" cfg.aliases) + "\n";
-
-    flattenedAliases = let
-      inherit (lib) mapAttrs attrValues filterAttrs isAttrs
-                    isString concatStringsSep foldr;
-
-      applyFunctor = attrset: functors.${attrset.type}.apply attrset;
-
-      # TODO: better naming
-      allAttrValuesAreStrings = attrset: let
-
-        # [ {String} ]
-        filteredAliases = [(filterAttrs (n: v: isString v) attrset)];
-
-        # [ {String} ]
-        remainingFunctors = let
-          functorSet = filterAttrs (_: v: isAlias v) attrset;
-          appliedFunctorSet = mapAttrs (n: v: applyFunctor v) functorSet;
-        in [ appliedFunctorSet ];
-
-        # [ {AttrSet} ]
-        remainingAliasSets = attrValues (filterAttrs (_: v: isAttrs v && !isAlias v) attrset);
-
-        # [ {String} ]
-        recursedAliasSets = filteredAliases
-                          ++ (remainingFunctors)
-                          ++ (map allAttrValuesAreStrings remainingAliasSets);
-      in foldr (a: b: a // b) {} recursedAliasSets;
-
-    in
-      allAttrValuesAreStrings cfg.aliases;
-
-  in {
+  config = {
     xdg.dataFile = {
-      aliases.text = aliasTextOverview;
-      packageManagerLecture = lib.mkIf cfg.enablePackageManagerLecture {
+      "aliases".text = shellAliasesFormat.generateText cfg.aliases;
+
+      "packageManagerLecture" = lib.mkIf cfg.enablePackageManagerLecture {
         target = "package-manager.lecture";
         text = let
           inherit (extendedLib.termColors.front) red blue;
@@ -242,20 +243,20 @@ in {
 
     programs = {
       zsh = {
-        shellAliases = flattenedAliases;
+        shellAliases = shellAliasesFormat.generateAttrs cfg.aliases;
         sessionVariables = cfg.variables;
       };
       bash = {
-        shellAliases = flattenedAliases;
+        shellAliases = shellAliasesFormat.generateAttrs cfg.aliases;
         sessionVariables = cfg.variables;
       };
       fish = {
-        shellAliases = flattenedAliases;
+        shellAliases = shellAliasesFormat.generateAttrs cfg.aliases;
         # TODO: fish does not support session variables?
         # localVariables = cfg.variables;
       };
       nushell = {
-        shellAliases = flattenedAliases;
+        shellAliases = shellAliasesFormat.generateAttrs cfg.aliases;
         environmentVariables = cfg.variables;
       };
     };