Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor commands #297

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 90 additions & 15 deletions docs/src/modules_schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Add commands to the environment.
**Type**:

```console
list of (submodule)
list of ((package or string convertible to it) or (list with two elements of types: [ string (package or string convertible to it) ]) or (flatOptions))
```

**Default value**:
Expand Down Expand Up @@ -37,15 +37,42 @@ list of (submodule)

- [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix)

### `commands.*.package`
### `commands.*`

A config for a command when the `commands` option is a list.

**Type**:

```console
(package or string convertible to it) or (list with two elements of types: [ string (package or string convertible to it) ]) or (flatOptions)
```
Comment on lines +46 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For flake-parts users, this mix of types would make it hard to merge commands coming from different places. I would rather keep either the list, or the attrset of commands, but not both.

Let's keep things simple.

Copy link
Contributor Author

@deemp deemp Jan 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

flake-parts

I had no problems with flake-parts. See repro. May require nix flake update.

type of commands

I couldn't invent a new option name so I made commands either a list or an attrset.

I prefer attrsets with attrNames as command group names.

I think it should be the first-class feature and should be documented and type checked.

So, I see these approaches:

  1. Allow commands only as an attrset
  2. Create a new option for commands as an attrset, leave commands as a list
    • What should be the name?
  3. Allow commands to be either a list or an attrset, do something about merging lists and attrsets for flake-parts users.
    • What in particular can make it hard to merge?
    • I need a concrete example of a devshell setup with flake-parts
    • I find it nice to have a simpler representation that can be used to explain the nested options.
  4. Provide just a function that converts a commands attrset to a list
    • I don't like this idea because then commands attrset won't be the first-class feature and can't naturally go into docs.


**Example value**:

```nix
[
{
category = "scripts";
package = "black";
}
[ "[package] print hello" "hello" ]
"nodePackages.yarn"
]
```

**Declared in**:

- [nix/commands/types.nix](https://github.com/numtide/devshell/tree/main/nix/commands/types.nix)

### `commands.*.package (flatOptions)`

Used to bring in a specific package. This package will be added to the
environment.

**Type**:

```console
null or (package or string convertible to it)
null or (package or string convertible to it) or package
```

**Default value**:
Expand All @@ -56,12 +83,12 @@ null

**Declared in**:

- [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix)
- [nix/commands/flatOptions.nix](https://github.com/numtide/devshell/tree/main/nix/commands/flatOptions.nix)

### `commands.*.category`
### `commands.*.category (flatOptions)`

Set a free text category under which this command is grouped
and shown in the help menu.
Sets a free text category under which this command is grouped
and shown in the devshell menu.

**Type**:

Expand All @@ -77,9 +104,9 @@ string

**Declared in**:

- [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix)
- [nix/commands/flatOptions.nix](https://github.com/numtide/devshell/tree/main/nix/commands/flatOptions.nix)

### `commands.*.command`
### `commands.*.command (flatOptions)`

If defined, it will add a script with the name of the command, and the
content of this value.
Expand Down Expand Up @@ -110,9 +137,33 @@ null

**Declared in**:

- [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix)
- [nix/commands/flatOptions.nix](https://github.com/numtide/devshell/tree/main/nix/commands/flatOptions.nix)

### `commands.*.expose (flatOptions)`

### `commands.*.help`
When `true`, the `command (flatOptions)`
or the `package (flatOptions)` will be added to the environment.

Otherwise, they will not be added to the environment, but will be printed
in the devshell menu.

**Type**:

```console
boolean
```

**Default value**:

```nix
true
```

**Declared in**:

- [nix/commands/flatOptions.nix](https://github.com/numtide/devshell/tree/main/nix/commands/flatOptions.nix)

### `commands.*.help (flatOptions)`

Describes what the command does in one line of text.

Expand All @@ -130,11 +181,15 @@ null

**Declared in**:

- [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix)
- [nix/commands/flatOptions.nix](https://github.com/numtide/devshell/tree/main/nix/commands/flatOptions.nix)

### `commands.*.name (flatOptions)`

Name of this command.

### `commands.*.name`
Defaults to a `package (flatOptions)` name or pname if present.

Name of this command. Defaults to attribute name in commands.
The value of this option is required for a `command (flatOptions)`.

**Type**:

Expand All @@ -150,7 +205,27 @@ null

**Declared in**:

- [modules/commands.nix](https://github.com/numtide/devshell/tree/main/modules/commands.nix)
- [nix/commands/flatOptions.nix](https://github.com/numtide/devshell/tree/main/nix/commands/flatOptions.nix)

### `commands.*.prefix (flatOptions)`

Prefix of the command name in the devshell menu.

**Type**:

```console
string
```

**Default value**:

```nix
""
```

**Declared in**:

- [nix/commands/flatOptions.nix](https://github.com/numtide/devshell/tree/main/nix/commands/flatOptions.nix)

### `devshell.packages`

Expand Down
159 changes: 6 additions & 153 deletions modules/commands.nix
Original file line number Diff line number Diff line change
@@ -1,163 +1,16 @@
{ lib, config, pkgs, ... }:
with lib;
let
ansi = import ../nix/ansi.nix;

# Because we want to be able to push pure JSON-like data into the
# environment.
strOrPackage = import ../nix/strOrPackage.nix { inherit lib pkgs; };

writeDefaultShellScript = import ../nix/writeDefaultShellScript.nix {
inherit (pkgs) lib writeTextFile bash;
};

pad = str: num:
if num > 0 then
pad "${str} " (num - 1)
else
str;

# Fallback to the package pname if the name is unset
resolveName = cmd:
if cmd.name == null then
cmd.package.pname or (builtins.parseDrvName cmd.package.name).name
else
cmd.name;

# Fill in default options for a command.
commandToPackage = cmd:
assert lib.assertMsg (cmd.command == null || cmd.name != cmd.command) "[[commands]]: ${toString cmd.name} cannot be set to both the `name` and the `command` attributes. Did you mean to use the `package` attribute?";
assert lib.assertMsg (cmd.package != null || (cmd.command != null && cmd.command != "")) "[[commands]]: ${resolveName cmd} expected either a command or package attribute.";
if cmd.package == null then
writeDefaultShellScript
{
name = cmd.name;
text = cmd.command;
binPrefix = true;
}
else
cmd.package;

commandsToMenu = cmds:
let
cleanName = { name, package, ... }@cmd:
assert lib.assertMsg (cmd.name != null || cmd.package != null) "[[commands]]: some command is missing both a `name` or `package` attribute.";
let
name = resolveName cmd;

help =
if cmd.help == null then
cmd.package.meta.description or ""
else
cmd.help;
in
cmd // {
inherit name help;
};

commands = map cleanName cmds;

commandLengths =
map ({ name, ... }: builtins.stringLength name) commands;

maxCommandLength =
builtins.foldl'
(max: v: if v > max then v else max)
0
commandLengths
;

commandCategories = lib.unique (
(zipAttrsWithNames [ "category" ] (name: vs: vs) commands).category
);

commandByCategoriesSorted =
builtins.attrValues (lib.genAttrs
commandCategories
(category: lib.nameValuePair category (builtins.sort
(a: b: a.name < b.name)
(builtins.filter (x: x.category == category) commands)
))
);

opCat = kv:
let
category = kv.name;
cmd = kv.value;
opCmd = { name, help, ... }:
let
len = maxCommandLength - (builtins.stringLength name);
in
if help == null || help == "" then
" ${name}"
else
" ${pad name len} - ${help}";
in
"\n${ansi.bold}[${category}]${ansi.reset}\n\n" + builtins.concatStringsSep "\n" (map opCmd cmd);
in
builtins.concatStringsSep "\n" (map opCat commandByCategoriesSorted) + "\n";

# These are all the options available for the commands.
commandOptions = {
name = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Name of this command. Defaults to attribute name in commands.
'';
};

category = mkOption {
type = types.str;
default = "[general commands]";
description = ''
Set a free text category under which this command is grouped
and shown in the help menu.
'';
};

help = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Describes what the command does in one line of text.
'';
};

command = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
If defined, it will add a script with the name of the command, and the
content of this value.

By default it generates a bash script, unless a different shebang is
provided.
'';
example = ''
#!/usr/bin/env python
print("Hello")
'';
};

package = mkOption {
type = types.nullOr strOrPackage;
default = null;
description = ''
Used to bring in a specific package. This package will be added to the
environment.
'';
};
};
inherit (import ../nix/commands/devshell.nix { inherit pkgs; }) commandsToMenu commandToPackage devshellMenuCommandName;
inherit (import ../nix/commands/types.nix { inherit pkgs; }) commandsFlatType;
in
{
options.commands = mkOption {
type = types.listOf (types.submodule { options = commandOptions; });
options.commands = lib.mkOption {
type = commandsFlatType;
default = [ ];
description = ''
Add commands to the environment.
'';
example = literalExpression ''
example = lib.literalExpression ''
[
{
help = "print hello";
Expand All @@ -176,7 +29,7 @@ in
config.commands = [
{
help = "prints this menu";
name = "menu";
name = devshellMenuCommandName;
command = ''
cat <<'DEVSHELL_MENU'
${commandsToMenu config.commands}
Expand Down
4 changes: 3 additions & 1 deletion modules/devshell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ let
# environment.
strOrPackage = import ../nix/strOrPackage.nix { inherit lib pkgs; };

inherit (import ../nix/commands/devshell.nix { inherit pkgs; }) devshellMenuCommandName;

# Use this to define a flake app for the environment.
mkFlakeApp = bin: {
type = "app";
Expand Down Expand Up @@ -255,7 +257,7 @@ in
type = types.str;
default = ''
{202}🔨 Welcome to ${cfg.name}{reset}
$(type -p menu &>/dev/null && menu)
$(type -p ${devshellMenuCommandName} &>/dev/null && ${devshellMenuCommandName})
'';
apply = replaceStrings
(map (key: "{${key}}") (attrNames ansi))
Expand Down
2 changes: 1 addition & 1 deletion modules/modules-docs.nix
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ let

# TODO: handle opt.relatedPackages. What is it for?
optToMd = opt:
let heading = lib.showOption opt.loc; in
let heading = lib.showOption (filter isString opt.loc) + concatStrings (filter (x: !(isString x)) opt.loc); in
''
### `${heading}`

Expand Down
Loading
Loading