From 5fee6ef12a4946e50db6d40c16ae52f79b89c48c Mon Sep 17 00:00:00 2001 From: technofab Date: Mon, 15 Sep 2025 11:28:58 +0200 Subject: [PATCH] chore: format and add meta module --- lib/flake.nix | 2 +- lib/modules/default.nix | 1 + lib/modules/env.nix | 11 +- lib/modules/main.nix | 6 +- lib/modules/meta.nix | 211 +++++++++++++++++++++++++++++++++++++ lib/utils/mkNakedShell.nix | 2 +- nix/repo/devShells.nix | 13 ++- 7 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 lib/modules/meta.nix diff --git a/lib/flake.nix b/lib/flake.nix index 1bb6f53..0c81bd4 100644 --- a/lib/flake.nix +++ b/lib/flake.nix @@ -1,5 +1,5 @@ { - outputs = i: { + outputs = _i: { lib = import ./.; }; } diff --git a/lib/modules/default.nix b/lib/modules/default.nix index a0b6428..cc6fd4a 100644 --- a/lib/modules/default.nix +++ b/lib/modules/default.nix @@ -2,5 +2,6 @@ imports = [ ./main.nix ./env.nix + ./meta.nix ]; } diff --git a/lib/modules/env.nix b/lib/modules/env.nix index ac0a0c3..4332c96 100644 --- a/lib/modules/env.nix +++ b/lib/modules/env.nix @@ -13,7 +13,7 @@ prefix, ... } @ args: let - vals = filter (key: args.${key} != null && args.${key} != false) [ + vals = filter (key: args.${key} != null && args.${key}) [ "eval" "prefix" "unset" @@ -122,5 +122,14 @@ in { }; enterShellCommands."env".text = concatStringsSep "\n" (map envToBash (builtins.attrValues config.env)); # TODO: collect all env vars, then add them to the menu if they have a description and "visibile == true" + + meta = { + sections."Environment Variables".color = "red"; + entries."XDG_DATA_DIRS" = { + description = "Hello world"; + section = "Environment Variables"; + subEntries."Usage" = "hello world"; + }; + }; }; } diff --git a/lib/modules/main.nix b/lib/modules/main.nix index e620a6b..7cf0c71 100644 --- a/lib/modules/main.nix +++ b/lib/modules/main.nix @@ -124,8 +124,12 @@ in { meta.mainProgram = "devshell-${config.name}"; }; finalPackage = mkNakedShell { - name = config.name; + inherit (config) name; profile = config.shell.finalProfile; + passthru = { + metadata = config.meta.finalMetadata; + metadataJson = config.meta.finalJson; + }; }; }; } diff --git a/lib/modules/meta.nix b/lib/modules/meta.nix new file mode 100644 index 0000000..9e275de --- /dev/null +++ b/lib/modules/meta.nix @@ -0,0 +1,211 @@ +{ + pkgs, + lib, + config, + ... +}: let + inherit (lib) mkOption types optional mkIf concatMapStrings concatMapStringsSep toUpper splitString; + cfg = config.meta; + + printSubtext = subtext: + # sh + '' + printf " $FG_GRAY ↳ $RESET''${BOLD}${subtext.text}$RESET: ${subtext.desc}\n" + ''; + + printEntry = entry: + # sh + '' + printf " $FG_${entry.color}› %-*s$RESET $FG_GRAY%s$RESET\n" "${maxWidth}" "${entry.text}" "${entry.desc}" + ${concatMapStrings printSubtext entry.subtexts} + ''; + printGroup = group: + # sh + '' + printf " $FG_WHITE$RESET$BG_WHITE$FG_BLACK %s $RESET$FG_WHITE$RESET\n" "${group.title}" + ${concatMapStrings printEntry group.entries} + ''; + + calculateMaxWidth = groups: let + lengths = builtins.concatLists (builtins.map ( + group: + builtins.map (entry: builtins.stringLength entry.text) group.entries + ) + groups); + + max = a: b: + if a > b + then a + else b; + maxLength = + if lengths == [] + then 0 + else builtins.foldl' max 0 lengths; + in + maxLength; + maxWidth = builtins.toString (calculateMaxWidth result); + + printHeaderDescription = text: + concatMapStringsSep "\n" (line: + # sh + '' + printf " $FG_BLUE│$RESET $FG_WHITE%s$RESET\n" "${line}" + '') (splitString "\n" text); + + menuPackage = pkgs.writeTextFile { + name = "menu"; + destination = "/bin/menu"; + executable = true; + text = + # sh + '' + BOLD=$(tput bold) + RESET=$(tput sgr0) + FG_BLACK=$(tput setaf 0) + FG_RED=$(tput setaf 1) + FG_GREEN=$(tput setaf 2) + FG_YELLOW=$(tput setaf 3) + FG_BLUE=$(tput setaf 4) + FG_MAGENTA=$(tput setaf 5) + FG_CYAN=$(tput setaf 6) + FG_WHITE=$(tput setaf 7) + BG_WHITE=$(tput setab 7) + FG_GRAY=$(tput setaf 8) + + printf " $FG_BLUE╭─┄┄┈┈$RESET\n" + printf " $FG_BLUE│$RESET $FG_YELLOW$BOLD%s$RESET\n" "${config.name}" + ${printHeaderDescription cfg.description} + printf " $FG_BLUE╰─┄┄┈┈$RESET\n" + printf "\n" + + ${concatMapStringsSep "printf '\n'\n" printGroup result} + ''; + }; + + groupedEntries = builtins.foldl' ( + acc: entryName: let + entry = cfg.entries.${entryName}; + sectionName = entry.section; + section = cfg.sections.${sectionName} or {}; + + newEntry = { + text = entry.name; + desc = entry.description or ""; + color = section.color or "BLUE"; + subtexts = map (name: { + text = name; + desc = entry.subEntries.${name}; + }) (builtins.attrNames entry.subEntries); + }; + + updatedSection = + if acc ? ${sectionName} + then acc.${sectionName} ++ [newEntry] + else [newEntry]; + in + acc // {${sectionName} = updatedSection;} + ) {} (builtins.attrNames cfg.entries); + + result = builtins.map (sectionName: { + title = sectionName; + entries = groupedEntries.${sectionName} or []; + }) (builtins.attrNames groupedEntries); +in { + options.meta = { + enableMenu = mkOption { + type = types.bool; + default = true; + description = '' + Add a `menu` command which shows a nice overview/summary of the devshell. + ''; + }; + showMenu = mkOption { + type = types.enum ["always" "once" "never"]; + default = "once"; + description = '' + When to show the devshell menu. `once` uses a state file in `$REN_STATE` + to keep track of the first time. + ''; + }; + description = mkOption { + type = types.str; + default = "Welcome to the devshell."; + description = ''''; + }; + sections = mkOption { + type = types.attrsOf (types.submodule ({name, ...}: { + options = { + name = mkOption { + type = types.str; + default = name; + }; + color = mkOption { + type = types.nullOr types.str; + default = "blue"; + apply = toUpper; + }; + }; + })); + default = { + "general" = {}; + }; + }; + entries = mkOption { + type = types.attrsOf (types.submodule ({name, ...}: { + options = { + name = mkOption { + type = types.str; + default = name; + }; + section = mkOption { + type = types.str; + default = "general"; + }; + description = mkOption { + type = types.nullOr types.str; + default = null; + }; + subEntries = mkOption { + type = types.attrs; + default = {}; + }; + }; + })); + default = {}; + }; + finalMetadata = mkOption { + type = types.attrs; + internal = true; + }; + finalJson = mkOption { + type = types.package; + internal = true; + }; + }; + + config = { + meta = rec { + finalMetadata = { + inherit (config) name; + # inherit (cfg) sections entries; + sections = result; + }; + finalJson = builtins.toFile "meta.json" (builtins.toJSON finalMetadata); + }; + + packages = optional cfg.enableMenu menuPackage; + # shows the menu when entering the devshell + enterShellCommands."menu".text = mkIf (cfg.enableMenu && cfg.showMenu != "never") ( + if cfg.showMenu == "always" + then "menu" + else + # sh + '' + if [[ ! -f "$REN_STATE/${config.name}_menu_shown" ]]; then + touch "$REN_STATE/${config.name}_menu_shown" + menu + fi + '' + ); + }; +} diff --git a/lib/utils/mkNakedShell.nix b/lib/utils/mkNakedShell.nix index 50ce52d..486b92f 100644 --- a/lib/utils/mkNakedShell.nix +++ b/lib/utils/mkNakedShell.nix @@ -30,7 +30,7 @@ in (derivation { inherit name; - system = stdenv.hostPlatform.system; + inherit (stdenv.hostPlatform) system; # `nix develop` actually checks and uses builder. And it must be bash. builder = bashPath; diff --git a/nix/repo/devShells.nix b/nix/repo/devShells.nix index cea93ea..3d59cfc 100644 --- a/nix/repo/devShells.nix +++ b/nix/repo/devShells.nix @@ -13,10 +13,15 @@ in { }; }) ]; - env."HELLO".value = "world!"; - enterShellCommands.test = { - text = "echo Hello $HELLO"; - deps = ["env"]; + meta = { + enableMenu = true; + showMenu = "always"; + entries = { + "World" = { + section = "Hello"; + description = "meow"; + }; + }; }; }; }