Hello, everyone! How often do you make mistakes while working in the terminal? I'm sure everyone has forgotten sudo
at least once or typed cs
instead of cd
. Once, I saw the alias fuck
for adding sudo
to the previous command. I got interested and started digging.
The first solution worthy of respect
In my search, I found the utility thefuck. In addition to simply adding sudo
where necessary, it can correct typos in commands, add missing flags, and much more.
However, I found two drawbacks. First, the program is written in Python, which can make it slow. Second, it has not been supported for a long time, so it does not work on newer versions of the language.
A quick alternative
We're programmers here, I thought, and decided to write an alternative in Rust called theshit. Honestly, it was my first experience with Rust, which made it even more interesting. The author of thefuck used an interesting approach to pass the last command to the program, and I borrowed it. Rather than calling our program directly, we create a special alias (function) for the shell.
An example of the theshit alias for zsh:
{name}() { # {name} - alias name
export SH_SHELL=zsh;
SH_PREV_CMD=\"$(fc -ln -1)\";
export SH_PREV_CMD;
SH_SHELL_ALIASES=$(alias);
export SH_SHELL_ALIASES;
SH_CMD=$(
{} fix $@ # {} - путь к программе
) && eval \"$SH_CMD\";
unset SH_PREV_CMD;
unset SH_SHELL;
unset SH_SHELL_ALIASES;
}
As you can see, we pass all the necessary information through environment variables and built-in shell commands. How do we correct commands? Just like in thefuck
, we have certain "rules" consisting of two functions: checking and correcting.
An example of a rule for correcting the cargo
subcommand:
pub fn is_match(command: &Command) -> bool {
command.output().stderr().contains("no such command")
&& command
.output()
.stderr()
.contains("a command with a similar name exists:")
&& command.parts()[0] == "cargo"
}
pub fn fix(command: &Command) -> String {
let broken = &command.parts()[1];
let fix = Regex::new(r"a command with a similar name exists: `([^`]*)`")
.unwrap()
.captures(command.output().stderr())
.and_then(|caps| caps.get(1))
.map(|m| m.as_str())
.unwrap();
misc::replace_argument(command.command(), broken, fix)
}
If we enter a command incorrectly, cargo
itself suggests the correct equivalent. For example:
❯ cargo builrt
error: no such command: `builrt`
help: a command with a similar name exists: `build`
help: view all installed commands with `cargo --list`
help: find a package to install `builrt` with `cargo search cargo-builrt`
In turn, we simply check for a hint and retrieve it.
Conclusion
I told you about two utilities for correcting errors and explained how they work. I hope you enjoyed it and that you will be able to work faster in the terminal. Finally, I would like to ask for your help in developing and testing theshit
. Please open issues, send pull requests, and, most importantly, don't be afraid to experiment!
Top comments (0)