mirror of
https://gitlab.com/rensa-nix/devshell.git
synced 2025-12-11 22:00:08 +01:00
211 lines
5.4 KiB
Nix
211 lines
5.4 KiB
Nix
{
|
||
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
|
||
''
|
||
);
|
||
};
|
||
}
|