Skip to content

Negative number arguments parsed as short options #21

@slilly-0xffff

Description

@slilly-0xffff

When a command argument is a signed integer type (i16, i32, etc.) and the user passes a negative value, the leading '-' is parsed as a short option flag rather than part of the number.

Example desktop app:

use embedded_cli::cli::CliBuilder;
use embedded_cli::codes;
use embedded_cli::Command;
use embedded_io::{ErrorType, Write};
use std::convert::Infallible;
use std::io::{stdin, stdout, Stdout, Write as _};
use termion::event::{Event, Key};
use termion::input::TermRead;
use termion::raw::{IntoRawMode, RawTerminal};

use ufmt::uwrite;

/// Simple demo to reproduce the negative number parsing bug in arguments.rs.
/// Try: `add 3 -2`  or  `add -4 5`

#[derive(Debug, Command)]
enum Base {
    /// Add two integers
    Add {
        a: i32, // First operand
        b: i32, // Second operand
    },
}

pub struct Writer {
    stdout: RawTerminal<Stdout>,
}

impl ErrorType for Writer {
    type Error = Infallible;
}

impl Write for Writer {
    fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
        self.stdout.write_all(buf).unwrap();
        Ok(buf.len())
    }

    fn flush(&mut self) -> Result<(), Self::Error> {
        self.stdout.flush().unwrap();
        Ok(())
    }
}

fn main() {
    let stdout = stdout().into_raw_mode().unwrap();

    let writer = Writer { stdout };

    const CMD_BUFFER_SIZE: usize = 256;
    const HISTORY_BUFFER_SIZE: usize = 512;

    let (command_buffer, history_buffer) = unsafe {
        static mut COMMAND_BUFFER: [u8; CMD_BUFFER_SIZE] = [0; CMD_BUFFER_SIZE];
        static mut HISTORY_BUFFER: [u8; HISTORY_BUFFER_SIZE] = [0; HISTORY_BUFFER_SIZE];
        #[allow(static_mut_refs)]
        (COMMAND_BUFFER.as_mut(), HISTORY_BUFFER.as_mut())
    };

    let mut cli = CliBuilder::default()
        .writer(writer)
        .command_buffer(command_buffer)
        .history_buffer(history_buffer)
        .build()
        .expect("Failed to build CLI");

    let _ = cli.set_prompt("calc> ");

    let stdin = stdin();
    for c in stdin.events() {
        let evt = c.unwrap();
        let bytes = match evt {
            Event::Key(Key::Esc) | Event::Key(Key::Ctrl('c')) => break,
            Event::Key(Key::Up) => vec![codes::ESCAPE, b'[', b'A'],
            Event::Key(Key::Down) => vec![codes::ESCAPE, b'[', b'B'],
            Event::Key(Key::Right) => vec![codes::ESCAPE, b'[', b'C'],
            Event::Key(Key::Left) => vec![codes::ESCAPE, b'[', b'D'],
            Event::Key(Key::BackTab) => vec![codes::TABULATION],
            Event::Key(Key::Backspace) => vec![codes::BACKSPACE],
            Event::Key(Key::Char(c)) => {
                let mut buf = [0; 4];
                c.encode_utf8(&mut buf).as_bytes().to_vec()
            }
            _ => continue,
        };
        for byte in bytes {
            cli.process_byte::<Base, _>(
                byte,
                &mut Base::processor(|cli, command| match command {
                    Base::Add { a, b } => {
                        uwrite!(cli.writer(), "{} + {} = {}", a, b, a + b)
                    }
                }),
            )
            .unwrap();
        }
    }
}

Example session:

calc> add 1 2
1 + 2 = 3
calc> add 2 -3
error: unexpected option: -3
calc> add -4 7
error: unexpected option: -4
calc>

The proposed fix:

diff --git a/embedded-cli/src/arguments.rs b/embedded-cli/src/arguments.rs
index 3c275fe..85be5be 100644
--- a/embedded-cli/src/arguments.rs
+++ b/embedded-cli/src/arguments.rs
@@ -105,6 +105,8 @@ impl<'a> Iterator for ArgsIter<'a> {
                 } else {
                     Arg::LongOption(unsafe { raw.get_unchecked(2..) })
                 }
+            } else if bytes[1].is_ascii_digit() {
+                Arg::Value(raw)
             } else {
                 let (opt, leftover) =
                     unsafe { utils::char_pop_front(raw.get_unchecked(1..)).unwrap_unchecked() };

Example session containing the proposed fix:

calc> add 1 2
1 + 2 = 3
calc> add 2 -3
2 + -3 = -1
calc> add -4 7
-4 + 7 = 3

I can submit a PR with the fix if that would be convenient for you.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions