Skip to content

Conversation

@emlun
Copy link

@emlun emlun commented Oct 11, 2020

Hello and happy Hacktoberfest! Here's a stab at implementing the alias/unalias builtin, along with a necessary bug fix (see 921fbeb and 7d0511f) and some basic integration tests. I also extracted a debug_println! macro to make the assertions for those tests a bit cleaner.

What do you think?

@emlun
Copy link
Author

emlun commented Oct 25, 2020

@ashpil Have you had time to look at this?

@ashpil
Copy link
Owner

ashpil commented Oct 25, 2020

@ashpil Have you had time to look at this?

Oh wow this is amazing! I somehow completely missed this PR, thanks for pinging me again! Taking a look now.

Some(_) => match self.read_until(false, false, false, Box::new(is_token_split)) {
Ok(w) => {
println!("The words I got: {:?}", w);
debug_println!("The words I got: {:?}", w);
Copy link
Owner

Choose a reason for hiding this comment

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

Whoops, this print statement did slid past me before I pushed. Thanks for catching it.

Honestly I'm not sure whether something like this makes sense as a debug_println, or if it should just be removed.

Copy link
Author

Choose a reason for hiding this comment

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

Heheh, ok. I've left it in for now, feel free to remove it later.

@ashpil
Copy link
Owner

ashpil commented Oct 25, 2020

Currently panics if such a sequence of commands is run:

$ alias nothing=
$ nothing

If you do the same thing in dash, it just does nothing.

Edit: might be good to put a test case for this in along with the fix

@ashpil
Copy link
Owner

ashpil commented Oct 26, 2020

Thanks for the debug macro! Can you do convert this and this to debug_println, too?

@ashpil
Copy link
Owner

ashpil commented Oct 26, 2020

Looking at this, I think to me it might make more sense to do the parsing of the aliased command in the alias builtin itself, and have the alias structure be a map of names->Cmds. Then we can avoid parsing each time it is called. What are your thoughts on this?

@ashpil
Copy link
Owner

ashpil commented Oct 26, 2020

@emlun With the above comments, I think this PR should be good. Let me know if my comments make sense to you.

And, thanks again for implementing this! It looks great.

",
"$> $> alias bar='oche'
alias foo='echo'
$> $> $> $> alias bar='oche'
Copy link
Owner

Choose a reason for hiding this comment

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

Why are there multiple $> on one line in some places? How does this work?

Copy link
Author

Choose a reason for hiding this comment

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

As far as I can tell, the shell isn't printing newlines unless the command does (by using println! instead of print!, for example). In interactive mode, most newlines are entered by the user rather than printed by the shell.

@emlun
Copy link
Author

emlun commented Oct 26, 2020

Thanks for the review! I'll try to fix these things up in the next couple of days.

@ashpil
Copy link
Owner

ashpil commented Oct 26, 2020

Thanks for the review! I'll try to fix these things up in the next couple of days.

Great! Make sure you rebase against master, as I just did a small commit, and run cargo clippy before pushing - it caught a couple things.

@ashpil
Copy link
Owner

ashpil commented Oct 28, 2020

And you can check alias/unalias in the README! 🎉

@emlun
Copy link
Author

emlun commented Oct 30, 2020

Alright, I think I've addressed all your comments now.

Looking at this, I think to me it might make more sense to do the parsing of the aliased command in the alias builtin itself, and have the alias structure be a map of names->Cmds. Then we can avoid parsing each time it is called. What are your thoughts on this?

You mean parse the alias immediately when defined? Hmm... maybe? My first thought on that is that it could get weird because aliases can reference each other and include pipes/conjunctions, and you can also pass additional arguments after an alias. But maybe all you'd need to do is concatenate those additional arguments onto whatever's in the predefined Cmd in the aliases map? I'm not sure.

@ashpil
Copy link
Owner

ashpil commented Oct 30, 2020

You mean parse the alias immediately when defined? Hmm... maybe? My first thought on that is that it could get weird because aliases can reference each other and include pipes/conjunctions, and you can also pass additional arguments after an alias. But maybe all you'd need to do is concatenate those additional arguments onto whatever's in the predefined Cmd in the aliases map? I'm not sure.

Yeah, I think you could append the additional arguments into the predefined Cmd's arguments, and that might even be simpler than the current version of concating the two strings and parsing them together. Just parse the args as normal, then if the command matches an alias, append it to the alias args, and execute as normal. The pipes and redirections are all included as part of the Cmd struct, so I don't think that would be an issue. The aliases referencing each other shouldn't be an issue either. Might have to move some of it into the parser, though.

To me, it seems as if it would be a cleaner and more performant way of doing it. If you're not up to it, that's fine. We can just get this merged in, and I can take a look at it myself later.

@emlun
Copy link
Author

emlun commented Oct 31, 2020

I took a stab at it and quickly ran into a couple of nontrivial issues. 🙂 For starters you'll have to pass the whole Shell as a mutable parameter to builtins::alias since the Lexer and Parser constructors need it. That in itself isn't so bad, but then comes the issue of actually constructing a Cmd inside builtins::alias, as that would need access to some stdin/out/err handles. That probably can be done too, but it feels to me like we're piling up a few too many things to be passed into the builtins::alias function. What do you think?

@ashpil
Copy link
Owner

ashpil commented Oct 31, 2020

Passing in the Shell seems reasonable, after all, that's what builtins are for - commands that can't be run by an external program as they must affect the shell directly.
Unless I'm misunderstanding something, wouldn't builtins::alias not need io handles? It should just be able to use the parser.get interface to get the parsed version of the command, which it can then store in the aliases map?

@emlun
Copy link
Author

emlun commented Oct 31, 2020

Hm... oh yeah, you're probably right. I figured I'd need to construct a Simple instance directly at some point, which has the io handles in its fields, but yeah, you can probably get away with the instances returned by the parser. I'll take another look!

@emlun
Copy link
Author

emlun commented Oct 31, 2020

Okay, here are the changes I ended up with to make that happen. What do you think, should I pull that in here?

@ashpil
Copy link
Owner

ashpil commented Oct 31, 2020

Okay, here are the changes I ended up with to make that happen. What do you think, should I pull that in here?

I think that's better! A couple things:

  1. I think at the moment, when we propagate the env we lose the existing env. Meaning, if we first do alias test='SOME_ENV=hello ls', when the env propagation happens, the existing SOME_ENV value is lost. So instead of replacing the env, it should just add onto it.
  2. We currently do not think about redirections at all. Meaning if we use our previously defined test alias with test > file.txt, the output is not redirected.
  3. Pipelines seems to freeze if they're part of an alias. alias test='ls | cat'

These things make me think that there would be way too much code for this in the runner. I think it might be smarter to just move the current propagate_env, move_args, etc functions into the impl Cmd, so one can just call cmd.extend_env(env), cmd.extend_args(args), cmd.new_io(io), etc, which to me seems it would be a lot cleaner.

Something that's subjective and doesn't necessarily need to be implemented now: I think instead of Cmds having a to_commandline function, it makes more sense to implement the std::fmt::Display trait which will make it easier to use.

@emlun
Copy link
Author

emlun commented Nov 1, 2020

That all sounds like good points and good ideas, thanks! I'll look into that.

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.

2 participants