getargs icon indicating copy to clipboard operation
getargs copied to clipboard

parsing --some-X-arg; positional with value embedded?

Open davehorner opened this issue 7 months ago • 6 comments

found you from here. https://www.reddit.com/r/rust/comments/wbblxc/getargs_050_is_finally_out_fastest_rust_argument/

looking for a command line library that supports parsing values like: [-g ROWSxCOLSmDISPLAY#] or -gROWSxCOLSmDISPLAY#

or

    for arg in &*args {
        if let Some(num) = arg
            .strip_prefix("--run-")
            .and_then(|s| s.strip_suffix("-at-a-time"))
        {
            if let Ok(n) = num.parse() {
                println!("run-at-a-time: {}", n);
                run_at_a_time = Some(n);
            }
            // Don't push this arg to filtered_args
            continue;
        }
        filtered_args.push(arg);
    }

--run-7-at-a-time

I have just begun to look; your zero-copy and speed claims are interesting as it's a common bottle neck for any parsing of command lines. I'm interested.

The reddit and repo are untouched for 3 yrs. All-Time: 256,121 Recent: 14,435 looks active and maybe stable or left for a time.

davehorner avatar May 21 '25 14:05 davehorner

Hello!! Is there something specific you need help with? The code you posted looks fine.

j-tai avatar May 21 '25 19:05 j-tai

Do also be aware of the existence of getargs-os on the platforms that support it. The repository is archived because that crate is considered fully complete.

LoganDark avatar May 21 '25 19:05 LoganDark

Do also be aware of the existence of getargs-os on the platforms that support it. The repository is archived because that crate is considered fully complete.

Just to clarify -- while the crate is pretty much feature complete, I do have vague plans to refactor it a bit. Hence why the repo is not archived.

j-tai avatar May 21 '25 19:05 j-tai

Do also be aware of the existence of getargs-os on the platforms that support it. The repository is archived because that crate is considered fully complete.

Just to clarify -- while the crate is pretty much feature complete, I do have vague plans to refactor it a bit. Hence why the repo is not archived.

In my comment, "the repository" refers to the repository of getargs-os, not this repository. That extension should never need to be updated, which is why the repository is archived.

LoganDark avatar May 21 '25 19:05 LoganDark

I'm trying to understand how to do the argument parsing using getargs and take on these faster characteristics. I was looking for a better way than what I have. If that looks good and I keep that; that's really zero dependencies.

i've been rolling my own parser, stripping out arguments before passing them to clap and ignore_errors for derive doesn't seem to exist. idk.

here's the other example;

// Helper function for parsing grid argument
fn parse_grid_arg(grid_str: &str) -> (u32, u32, i32) {
    let (rc, m) = if let Some(idx) = grid_str.find('m') {
        (&grid_str[..idx], Some(&grid_str[idx + 1..]))
    } else {
        (grid_str, None)
    };
    let parts: Vec<&str> = rc.split('x').collect();
    if parts.len() != 2 {
        panic!(
            "Grid argument must be in the form ROWSxCOLS or ROWSxCOLSmDISPLAY, got '{}'",
            grid_str
        );
    }
    let rows = parts[0]
        .parse::<u32>()
        .expect("Invalid ROWS in grid argument");
    let cols = parts[1]
        .parse::<u32>()
        .expect("Invalid COLS in grid argument");
    let monitor = m.and_then(|s| s.parse::<i32>().ok()).unwrap_or(0);
    (rows, cols, monitor)
}
  let mut grid: Option<(u32, u32, i32)> = None;
    let mut follow_children = false;
    let mut follow_forver = false;
    let mut track_top_level = false; // <-- NEW
    let mut positional_args = Vec::new();
    let mut args = env::args_os().skip(1).peekable();
    while let Some(arg) = args.next() {
        let arg_str = arg.to_string_lossy();
        if arg_str == "-f" || arg_str == "--follow" {
            follow_children = true;
        } else if arg_str == "-F" || arg_str == "--follow-forver" {
            follow_children = true;
            follow_forver = true;
        } else if arg_str == "-t" || arg_str == "--top-level" {
            track_top_level = true;
        } else if arg_str == "-g" || arg_str == "--grid" {
            let grid_arg = args
                .next()
                .expect("Expected ROWSxCOLS or ROWSxCOLSmDISPLAY# after -g/--grid");
            let grid_str = grid_arg.to_string_lossy();
            let (rows, cols, monitor) = parse_grid_arg(&grid_str);
            grid = Some((rows, cols, monitor));
            println!("Grid set to {}x{} on monitor {}", rows, cols, monitor);
        } else if arg_str.starts_with("-g") && arg_str.len() > 2 {
            // Support -g2x2 or -g2x2m1
            let grid_str = &arg_str[2..];
            let (rows, cols, monitor) = parse_grid_arg(grid_str);
            grid = Some((rows, cols, monitor));
            println!("Grid set to {}x{} on monitor {}", rows, cols, monitor);
        } else {
            positional_args.push(arg);
            // Push the rest as positional args
            positional_args.extend(args);
            break;
        }
    }

it's not a ton of code but its not using any framework to go about doing it and it's not something written:

producing a stream of options, and after each option, your code decides whether to require and retrieve the value for the option or not.

all these zero benefits sound great; its not clear to me how to rewrite what I have into a stream of options and perform this parsing via getargs. figured I'd say hello before exploring too much and congratulations on a well downloaded crate; it looks like anywhere and complete examples are the best to work from.

thank you both for your replies.

--aside I had no idea how complex argument parsing/tunneling can get until I met https://github.com/chromiumembedded/cef ; https://github.com/tauri-apps/cef-rs

I tried doing a custom rolled parser before the cefargs code and it was impossible with clap. I wonder if this parser would be more compatible. It's so complicated I'm not sure even how to describe it; but the work in interfacing with it seems to be the complexity in the argument parsing.

davehorner avatar May 22 '25 00:05 davehorner

its not clear to me how to rewrite what I have into a stream of options and perform this parsing via getargs

There is documentation that should help. You should be able to just create an Options over your args iterator and then continually call next_arg to get something you can match on. Since it looks like you are working with OS strings then it may be worth it for you to also use getargs-os. Otherwise you would have to convert the OS strings to bytes or something first just to give them to getargs which wouldn't be ideal though possible.

LoganDark avatar May 22 '25 00:05 LoganDark

looking for a command line library that supports parsing values like: [-g ROWSxCOLSmDISPLAY#] or -gROWSxCOLSmDISPLAY#

I realize I may not have pointed specifically enough last time, so here is a test case that reproduces the behavior you're describing.

#[test]
fn thing() {
    let args = ["-g", "ROWSxCOLSmDISPLAY#", "-gROWSxCOLSmDISPLAY#"];
    let mut opts = Options::new(args.into_iter());

    assert_eq!(opts.next_opt(), Ok(Some(Opt::Short('g'))));
    assert_eq!(opts.value(), Ok("ROWSxCOLSmDISPLAY#"));
    assert_eq!(opts.next_opt(), Ok(Some(Opt::Short('g'))));
    assert_eq!(opts.value(), Ok("ROWSxCOLSmDISPLAY#"));
    assert_eq!(opts.next_opt(), Ok(None));
    assert!(opts.is_empty());
}

I don't know if you're still shopping around, but I think if this solves your problem the issue can be closed.

LoganDark avatar Nov 07 '25 07:11 LoganDark

thank you!

davehorner avatar Nov 07 '25 09:11 davehorner