nvbn / thefuck

Magnificent app which corrects your previous console command.
MIT License
83.76k stars 3.41k forks source link

feat: new rule for `nix-shell` #1393

Open thenbe opened 11 months ago

thenbe commented 11 months ago

Implementation is similar to the one explained in https://github.com/nvbn/thefuck/issues/912#issue-441679613.

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

kdb424 commented 9 months ago

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

wesleycoder commented 6 months ago

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 commented 5 months ago

i would love to see this merged as well

KiaraGrouwstra commented 5 months ago

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 commented 4 months ago

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: https://github.com/KiaraGrouwstra/thefuck/commit/81d6786c80b86f2cc80b3ea90adc214df8266643

thenbe commented 4 months ago

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:

KiaraGrouwstra commented 4 months ago

@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 commented 4 months ago

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

thenbe commented 4 months ago

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 commented 4 months ago

@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 commented 4 months ago

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

edit: https://github.com/KiaraGrouwstra/thefuck/commit/16d838bf6f63117b161a2f1e6572e06108b007eb

KiaraGrouwstra commented 4 months ago

@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 commented 4 months ago

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 commented 4 months ago

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 commented 4 months ago

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 commented 4 months ago

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. ``` ```bash # it wants this instead: $ NIXPKGS_ALLOW_UNFREE=1 nix shell nixpkgs#github-copilot-cli --impure ```
KiaraGrouwstra commented 4 months ago

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

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