{ lib, pkgs, config, ... }: let inherit (lib) filter assertMsg head length escapeShellArg types mkOption concatStringsSep; envToBash = { name, value, eval, prefix, ... } @ args: let vals = filter (key: args.${key} != null && args.${key} != false) [ "eval" "prefix" "unset" "value" ]; valType = head vals; in assert assertMsg ( (length vals) > 0 ) "[devshell/env]: ${name} expected one of (value|eval|prefix|unset) to be set."; assert assertMsg ((length vals) < 2) "[devshell/env]: ${name} expected only one of (value|eval|prefix|unset) to be set. Not ${toString vals}"; assert assertMsg (!(name == "PATH" && valType == "value")) "[devshell/env]: ${name} should not override the value. Use 'prefix' instead."; if valType == "value" then "export ${name}=${escapeShellArg (toString value)}" else if valType == "eval" then "export ${name}=${eval}" else if valType == "prefix" then ''export ${name}=$(${pkgs.coreutils}/bin/realpath --canonicalize-missing "${prefix}")''${${name}+:''${${name}}}'' else if valType == "unset" then ''unset ${name}'' else throw "[devshell] BUG in the env module. This should never be reached."; envType = types.submodule ({name, ...}: { options = { name = mkOption { type = types.str; default = name; description = '' Name of the environment variable, usually already set to ``. ''; }; desc = mkOption { type = types.nullOr types.str; default = null; description = '' Description of the env var, can be shown in the menu. ''; }; visible = mkOption { type = types.bool; default = true; description = '' Whether to include this env var and it's description in the menu. ''; }; value = mkOption { type = with types; nullOr (oneOf [ str int bool package ]); default = null; example = "hello"; description = '' Any value to convert to a string and set the env variable to. There is no evaluation of the value, for that see [eval](#envnameeval). ''; }; eval = mkOption { type = types.nullOr types.str; default = null; description = '' Like value but evaluated by Bash. This allows to inject other variable names or even commands using the `$()` notation. ''; example = "$OTHER_VAR"; }; prefix = mkOption { type = types.nullOr types.str; default = null; description = '' Prepend to PATH-like environment variables. For example `name = "PATH"; prefix = "bin";` will expand the path of `./bin` and prepend it to the `PATH`, separated by `:`. ''; example = "bin"; }; unset = mkOption { type = types.bool; default = false; description = '' Unset the env variable. ''; }; }; }); in { options.env = mkOption { type = types.attrsOf envType; default = {}; description = '' Manage environment variables. ''; }; config = { env = { "XDG_DATA_DIRS".eval = "$DEVSHELL_DIR/share:\${XDG_DATA_DIRS-}"; }; 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" }; }