[Rust] #JV1 (redirect stdout) seems to require #NI6 (single quotes)

I’m stuck on Stage #JV1. (Redirect Stdout)

Here are my logs:

[your-program] $ echo 'Hello James' 1> /tmp/rat/cow.md
[your-program] $ cat /tmp/rat/cow.md
[your-program] 'Hello James'
[tester::#JV1] ^ Line does not match expected value.
[tester::#JV1] Expected: "Hello James"
[tester::#JV1] Received: "'Hello James'"

And here’s a snippet of my code:

use std::{fs::{self, File}, io::{self, Write}, path::Path};
enum Command<'a> {
    Builtin {
        command: Builtin<'a>,
        stdout: Option<&'a Path>,
    },
    // other commands
}

enum Builtin<'a> {
    Echo(&'a str),
    // other builtins
}

trait Eval<'a> {
    fn eval(self, stdout: Option<&'a Path>) -> Result<(), io::Error>;
}

fn main() -> Result<(), io::Error> {
    let mut input = String::new();
    loop {
        // display prompt
        io::stdin().read_line(&mut input)?;
        match Command::from(input.as_str()) {
            Command::Builtin { command, stdout } => command.eval(stdout)?,
            // other commands
        }
        input.clear();
    }
    Ok(())
}

impl<'a> From<&'a str> for Command<'a> {
    fn from(input: &'a str) -> Self {
        let (cmd, args) = input.split_once(" ").unwrap_or((input, ""));
        let (args, stdout) = match args.trim().split_once("1>") {
            Some((args, stdout)) => (args, Some(Path::new(stdout.trim()))),
            None => match args.split_once(">") {
                Some((args, stdout)) => (args, Some(Path::new(stdout.trim()))),
                None => (args, None),
            },
        };
        match cmd.trim() {
            "echo" => Command::Builtin {
                command: Builtin::Echo(args),
                stdout,
            },
            // other commands
        }
    }
}

impl<'a> Eval<'a> for Builtin<'a> {
    fn eval(self, stdout: Option<&'a Path>) -> Result<(), io::Error> {
        let mut output_stdout: io::Stdout;
        let mut output_file: File;
        let stdout: &mut dyn Write = match stdout {
            Some(path) => {
                if let Some(dir_path) = path.parent() {
                    fs::create_dir_all(dir_path)?;
                }
                output_file = File::create(path)?;
                &mut output_file
            }
            None => {
                output_stdout = io::stdout();
                &mut output_stdout
            }
        };
        match self {
            Builtin::Echo(args) => writeln!(stdout, "{}", args.trim())?,
            // other builtins
        }
        Ok(())
    }
}

It seems like echo should be swallowing the quotes (according to this test), but nothing in the earlier stages has required it. If my echo implementation passed the earlier tests, including the ones for stage #IZ3 (implementing echo), I should hope it’s not the problem now. If redirecting output should strip quotes, this should be explained in #JV1.

Published to Github here.

Try running echo 'Hello James' in a Bash shell if you have access and note the output. You are reading echo 'Hello James' and split it into command and remainder as argument string based on " " to get ("echo", "'Hello James'") pair. You use the first to decide on echo built in, and then pass the unparsed trimmed argument string to writeln!.

So what you are missing is the crucial parsing steps. Are you sure you are still passing #tg6? I can’t remember, maybe --previous or something for the CLI tester; but there is a way to run the previous test stages again.

But here are some examples I think you might be handling incorrect (the expected correct output is on the right):

  • echo A BA B
  • echo 'abc'abc
  • echo "abc"abc

I am not up to #tg6 yet. Currently, I’ve completed the base tests, as well as navigation. The order of extensions for me is navigation, redirection, autocompletion, pipelines, history, history persistence, and then quoting.

Didn’t even realize quoting was in an extension (maybe it wasn’t always :thinking:). That does seem odd because I would guess quite some test cases would depend on it – well we now know for sure this one does.

But if the extensions are indeed supposed to be completed in any other, or some even skipped, then perhaps this test case should not use quoting, @andy1li ?