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

feat: new rule for nix-shell #1393

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

feat: new rule for nix-shell #1393

wants to merge 6 commits into from

Conversation

thenbe
Copy link

@thenbe thenbe commented Jul 28, 2023

Implementation is similar to the one explained in #912 (comment).

In a nutshell, it tries to wrap the user's failed command in a nix-shell call.

$ ponysay moo
The program 'ponysay' is not in your PATH. You can make it available in an
ephemeral shell by typing:
  nix-shell -p ponysay

$ fuck
nix-shell -p ponysay --run "ponysay moo" [enter/↑/↓/ctrl+c]

Further info on nix-shell: https://thiagowfx.github.io/2022/02/nix-shell-in-a-nutshell/#hello-world-classic

Copy link
Collaborator

@scorphus scorphus left a comment

Choose a reason for hiding this comment

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

Thanks for contributing!

Please consider my comment below.

bin = command.script_parts[0]
return (
"nix-shell" not in command.script
and "command not found" in command.output
Copy link
Collaborator

Choose a reason for hiding this comment

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

In your example:

$ ponysay moo
The program 'ponysay' is not in your PATH. You can make it available in an
ephemeral shell by typing:
  nix-shell -p ponysay

$ fuck
nix-shell -p ponysay --run "ponysay moo" [enter/↑/↓/ctrl+c]

command not found is not part of the output. Didn't you mean:

Suggested change
and "command not found" in command.output
and "command not found" not in command.output

Copy link
Author

Choose a reason for hiding this comment

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

In your example, command not found is not part of the output

For commands that could be made available through nix:

  1. Indeed, the visible output in the terminal does not contain the text "command not found".
$ spt
The program 'spt' is not in your PATH. You can make it available in an
ephemeral shell by typing:
  nix-shell -p spotify-tui
  1. But when we log the value of command to file, we do see "command not found":
Command(script=spt, output=/nix/store/p6dlr3skfhxpyphipg2bqnj52999banh-bash-5.2-p15/bin/sh: line 1: spt: command not found)

I'm not sure exactly why they're different, but it works 🤷🏼‍♂️.

Admittedly, it could be simpler if we could do:

- and "command not found" in command.output
+ and command.exitcode is 127

But I couldn't find a way to access the numeric exit code (127) from within the match function. Only the human error message (command not found) is available it seems.

Finally, I've annotated the match conditions to make it clear what my intentions are for each.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, yes. That's because the command is executed through the shell — /bin/sh. Would you please run thefuck in debug mode and look for the line that says DEBUG: Received output:? e.g.:

THEFUCK_DEBUG=true thefuck spt

Among the debug messages there will be one with the output generated by the shell. Could you then please update the output values in mocked_nixpkgs?

Also, how about writing tests for match? 😊

Copy link
Author

Choose a reason for hiding this comment

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

tests for match

done

I've cleaned up the PR a fair bit. As for the mock output, I've indicated which command I've used to get them so their purpose should be more clear now. If you notice that I've missed anything let me know. Thank you.

Copy link
Author

@thenbe thenbe left a comment

Choose a reason for hiding this comment

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

I wasn't aware we could return more than one new command. Instead of just returning one, I've updated the rule to return them all. For example, the lsof program is provided by two packages*:

  1. busybox: which bundles a bunch of common UNIX utilites
  2. lsof: standalone

* If you follow the link, you can click "Show more package details" to view the programs provided by each package.

@thenbe thenbe requested a review from scorphus August 18, 2023 10:24
@kdb424
Copy link

kdb424 commented Oct 14, 2023

Been loving this. Hope it gets reviewed and upstreamed for all. Thanks for making this!

@wesleycoder
Copy link

I've just discovered thefuck and I can't imagine how I didn't stumble into it earlier.

This PR would fit like a glove for me that just switched to nixos and haven't yet grown the muscle memory of typing the nix-shell whenever my command fails.

@KiaraGrouwstra
Copy link
Contributor

i would love to see this merged as well

@KiaraGrouwstra
Copy link
Contributor

looks like you can use this already using e.g. an overlay, altho i had a bit of trouble getting it to work out of the box.
specifically, without adding doCheck = false;, i would run into this error:

error: builder for '/nix/store/rl44gb6qd4x2myclj9i8cpkfrvw6ysqa-thefuck-3.32.drv' failed with exit code 2;
       last 10 log lines:
       > thefuck/system/unix.py:6
       >   /build/source/thefuck/system/unix.py:6: DeprecationWarning: The distutils package is deprecated and slated for removal in Python 3.12. Use setuptools or check PEP 632 for potential alternatives
       >     from distutils.spawn import find_executable
       >
       > -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
       > =========================== short test summary info ============================
       > ERROR  - ModuleNotFoundError: No module named 'pytest_docker_pexpect'
       > !!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
       > ========================= 1 warning, 1 error in 0.09s ==========================
       > /nix/store/bknngadwym46j65qs14ic2w79rpav888-stdenv-linux/setup: line 1582: pop_var_context: head of shell_variables not a function context

i had tried removing the added test, altho that appeared not to resolve the issue.

@KiaraGrouwstra
Copy link
Contributor

KiaraGrouwstra commented Feb 23, 2024

it would seem cool to similarly get an approach using nix run, i.e. go from suggesting nix-shell -p ponysay --run "ponysay moo" to nix run nixpkgs#ponysay -- moo - this might eventually help extend beyond just nixpkgs.

edit: KiaraGrouwstra@81d6786

@thenbe
Copy link
Author

thenbe commented Feb 23, 2024

I've been using a custom rule that supports the new unified CLI for a while, and was planning on opening a PR once this one has been merged (I hesitate to update this current PR as it's already tested and ready to be merged). I don't know if that will happen soon, so in the meantime I've pushed the changes to this new branch instead, which builds on this here PR. You can use the updated rule by adding it as a custom rule to your config.

In the new rule, three variants are suggested. Assuming I run cowsay hello world, I am presented with the following:

  1. nix run nixpkgs#cowsay -- hello world: This runs my command in a non-interactive shell. Uses the nix unified CLI.
  2. nix shell nixpkgs#cowsay: This enters an interactive shell with cowsay available, but does not run any command. This is useful if you'd rather run the command yourself after entering the shell because your command requires delicate massaging (e.g. running it with sudo, prefixing it with environment variable, juggling quote variants, etc).
  3. nix-shell -p cowsay --run "cowsay hello world". This runs my command in a non-interactive shell. Uses the nix original CLI.
  4. nix shell nixpkgs#cowsay --command cowsay hello world: Very similar to the first one so I've personally disabled this one.

Thoughts on future updates:

  • It'd be nice if there was a variant that runs my command and then keeps me in the shell.
    • For the original CLI, we can add a --command "echo hello; return" to our nix-shell invocation.
    • For the unified CLI: not sure yet, we might need to do something like this example from the docs: nix shell nixpkgs#gnumake --command sh -c "cd src && make"
  • We should expose a couple of flags for users to configure this.
    • disable_unified_cli (boolean)
    • disable_original_cli (boolean)
  • As far as I can tell, the command-not-found db doesn't really play nice if you use flakes to configure your system and might return stale results (unless you update it manually?). nix-index seems to be the go-to alternative. It'd be great if we could optionally use that instead (perhaps behind a flag enable_nix_index for users who have installed nix-index (programs.nix-index.enable = true; in home manager).

@KiaraGrouwstra
Copy link
Contributor

KiaraGrouwstra commented Feb 23, 2024

@thenbe i agree integrating with nix-index's command-not-found replacement seems cool, as a flake user.
i kinda wish we could have command-not-found (and this thefuck integration) extend to flake inputs beyond nixpkgs as well, such as to packages from NUR for example. preferably this should be dynamic based on your inputs rather than hardcoded to specific ones like nixpkgs, or NUR for that matter.
i'll admit i haven't really figured out how that might work tho.

@KiaraGrouwstra
Copy link
Contributor

just tried these with a command like program_i_have | program_i_dont_have, seems that may complicate the suggestions a bit

@thenbe
Copy link
Author

thenbe commented Feb 24, 2024

I'm not sure if thefuck can handle piping.

If I make a typo git statis it will correct me to git status. But if I do echo hello | git statis it does not correct my typo. thefuck seems to work mostly on single commands AFAICT.

@KiaraGrouwstra
Copy link
Contributor

@thenbe hm, i'm not sure.

fortune | cowsay
The program 'cowsay' is not in your PATH. It is provided by several packages.
You can make it available in an ephemeral shell by typing one of the following:
  nix-shell -p cowsay
  nix-shell -p neo-cowsay
$ fuck
nix run nixpkgs#fortune | cowsay

feels like it knows about the whole command given it's reproducing it?

@KiaraGrouwstra
Copy link
Contributor

KiaraGrouwstra commented Feb 24, 2024

another common nix thing we might be able to address from thefuck would be errors about packages being unfree

edit: KiaraGrouwstra@16d838b

@KiaraGrouwstra
Copy link
Contributor

@thenbe what was the argument to favor nix run over nix shell again? i guess the latter seems a bit more generic in case of handling non-standard binaries at least

@thenbe
Copy link
Author

thenbe commented Feb 25, 2024

If I'm only looking to execute a program (and don't need to be dropped into a shell) then I prefer nix run over nix shell as the documentation suggests nix run specifically for this use case.

I also recall nix run being more performant (perhaps because we forego the overhead of launching a shell?). This last point is not derived from benchmarks, only anecdotal evidence.

i guess the latter seems a bit more generic in case of handling non-standard binaries at least

I've added this variant (the 4th one in my previous post), but disabled it after a while when I realized that I never reach for it. Do you find that you still need it over nix run (the 1st variant in my previous post)?

@thenbe
Copy link
Author

thenbe commented Feb 25, 2024

another common nix thing we might be able to address from thefuck would be errors about packages being unfree

edit: KiaraGrouwstra@16d838b

This would be useful. Does it still complain about the --impure flag? Or do you use a workaround for that?

@KiaraGrouwstra
Copy link
Contributor

i've been using thefuck mostly thru its zsh plugin, which just gets you the top suggestion. i found that failed for me for e.g. poppler_utils, which bundles multiple binaries.
to be fair tho, i'm not sure that accounts for a large portion of its invocations, so maybe it could make sense to just actually type out fuck in those cases.

what was the --impure error? i'm not sure i'd run into that.

by the way, had you managed to also package your branch for nix? considering i seemed to need that doCheck = false; to get our branches to build thru nix.

@thenbe
Copy link
Author

thenbe commented Feb 25, 2024

I just have it aliased to f for extra convenience.

I opted not to package it for nix separately since fuck already exposes a method for easily adding custom rules. Instead, I placed the rule in ~/mydotfiles/thefuck/rules/nix-shell.py then told home-manager to symlink it to the appropriate place in .config:

# home.nix
home.file.".config/thefuck/rules/nix-shell.py".source = config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/mydotfiles/thefuck/rules/nix-shell.py";

This way I don't need to rebuild every time I tweak the rule.

what was the --impure error?

The unified CLI commands (nix shell, nix run, etc) will not acknowledge environment variables unless the --impure flag is used.

output
$ NIXPKGS_ALLOW_UNFREE=1 nix shell nixpkgs#github-copilot-cli

error:
       … in the condition of the assert statement

         at /nix/store/xwc3zfc544jg6zhr0wi6k8253s7mwlhi-source/lib/customisation.nix:267:17:

          266|     in commonAttrs // {
          267|       drvPath = assert condition; drv.drvPath;
             |                 ^
          268|       outPath = assert condition; drv.outPath;

       … while evaluating the attribute 'handled'

         at /nix/store/xwc3zfc544jg6zhr0wi6k8253s7mwlhi-source/pkgs/stdenv/generic/check-meta.nix:490:7:

          489|       # or, alternatively, just output a warning message.
          490|       handled =
             |       ^
          491|         (

       (stack trace truncated; use '--show-trace' to show the full trace)

       error: Package ‘github-copilot-cli-0.1.36’ in /nix/store/xwc3zfc544jg6zhr0wi6k8253s7mwlhi-source/pkgs/tools/misc/github-copilot-cli/default.nix:21 has
 an unfree license (‘unfree’), refusing to evaluate.

       a) To temporarily allow unfree packages, you can use an environment variable
          for a single invocation of the nix tools.

            $ export NIXPKGS_ALLOW_UNFREE=1

          Note: When using `nix shell`, `nix build`, `nix develop`, etc with a flake,
                then pass `--impure` in order to allow use of environment variables.

       b) For `nixos-rebuild` you can set
         { nixpkgs.config.allowUnfree = true; }
       in configuration.nix to override this.

       Alternatively you can configure a predicate to allow specific packages:
         { nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
             "github-copilot-cli-0.1.36"
           ];
         }

       c) For `nix-env`, `nix-build`, `nix-shell` or any other Nix command you can add
         { allowUnfree = true; }
       to ~/.config/nixpkgs/config.nix.


# it wants this instead:
$ NIXPKGS_ALLOW_UNFREE=1 nix shell nixpkgs#github-copilot-cli --impure

@KiaraGrouwstra
Copy link
Contributor

aah i see! i'd yet to take that into account. 🙈

specifying the rules rather than doing overlays makes sense - thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants