{ 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 '' ); }; }