parse command-line arguments into a hashmap and vec of positional args

Overview

argmap

parse command-line arguments into a hashmap and vec of positional args

This library doesn't populate custom structs, format help messages, or convert types.

You provide an iterator of items that implement ToString and you get back a 2-tuple of (args,argv) where:

  • args is a Vec of positional arguments
  • argv is a HashMap> of all the values that map to a --key
let (args,argv) = argmap::parse(std::env::args());
eprintln!["args={:?}", &args];
eprintln!["argv={:?}", &argv];

Long (--file) and short (-x) options, with or without equal signs, clustered short options (example: tar -xvf file.tgz) and non-alpha short-circuiting (example: tail -n1) are all supported. You can also have numeric flags but not in short clusters.

Here's an example of the junk you can throw at this parser:

$ cargo run -q --example parse -- -z 5 -y=6 -y8 --msg cool -7 --here=there \
  -xvf file.tgz -qrs=1234 -n -555 one two three -abc+5 -c-6 -- four -z 0
args=["target/debug/examples/parse", "one", "two", "three", "four", "-z", "0"]
argv={"z": ["5"], "7": [], "y": ["6", "8"], "x": [], "v": [], "f": ["file.tgz"], "a": [], "b": [], "here": ["there"], "n": ["-555"], "qrs": ["1234"], "c": ["+5", "-6"], "msg": ["cool"]}

The values for the argv HashMap are Vec instead of String because you may have the same option specified multiple times. If you only want to deal with a single value for a given key, you can use the .first() or .last() inside an .and_then():

let (args,argv) = argmap::parse(std::env::args());
let cool = argv.get("cool").and_then(|v| v.last());

Boolean options will be stored as an empty vec![]. You can use .contains_key() to test for the presence of a boolean flag:

let (args,argv) = argmap::parse(std::env::args());
let show_help = argv.contains_key("h") || argv.contains_key("help");

HashMap has more ergonomic field access than any argument parser could hope to create and you can use the knowledge you already have for how to work with it instead of learning an argument-parser specific api.

Likewise, many of the usual features that a command-line parser has (aliasing and default values for example) can be obtained from methods on the core Option type such as .or_else(), .and_then(), or .unwrap_or().

Here is a longer example because how to string all of those together in a useful way is not necessarily obvious. This example is a word count program like wc, but overly-simplified and somewhat inaccurate for the sake of brevity.

use std::{io,fs::File};

type Error = Box<dyn std::error::Error+Send+Sync>;
type R = Box<dyn io::Read+Unpin>;

fn main() -> Result<(),Error> {
  let (args,argv) = argmap::new()
    .booleans(&[ "h", "help", "c", "bytes", "w", "words", "l", "lines" ])
    .parse(std::env::args());
  if argv.contains_key("h") || argv.contains_key("help") {
    indoc::printdoc![r#"usage: {} {{OPTIONS}} [FILE]

      Count the number of bytes, words, or lines in a file or stdin.

        -i, --infile  Count words from FILE or '-' for stdin (default).
        -c, --bytes   Show number of bytes.
        -w, --words   Show number of words.
        -l, --lines   Show number of lines.
        -h, --help    Show this message.

    "#, args.get(0).unwrap_or(&"???".to_string())];
    return Ok(());
  }

  let mut show_bytes = argv.contains_key("c") || argv.contains_key("bytes");
  let mut show_words = argv.contains_key("w") || argv.contains_key("words");
  let mut show_lines = argv.contains_key("l") || argv.contains_key("lines");
  if !show_bytes && !show_words && !show_lines {
    show_bytes = true;
    show_words = true;
    show_lines = true;
  }

  let stdin_file = "-".to_string();
  let infile = argv.get("infile").and_then(|v| v.first()) // --infile=file
    .or_else(|| argv.get("i").and_then(|v| v.first())) // -i file
    .or_else(|| args.get(1)) // first positional arg after $0
    .unwrap_or(&stdin_file) // default value: "-"
    .as_str();

  let mut stream: R = match infile {
    "-" => Box::new(io::stdin()),
    f => Box::new(File::open(f)?),
  };
  let mut buf = vec![0;4096];
  let mut byte_count = 0;
  let mut word_count = 0;
  let mut line_count = 0;
  loop {
    let len = stream.read(&mut buf)?;
    if len == 0 { break }
    byte_count += len;
    let s = std::str::from_utf8(&buf[0..len])?;
    word_count += s.split_whitespace().count();
    line_count += s.lines().count();
  }
  let mut outline = "".to_string();
  if show_lines { outline += &format!["{:>4} ", line_count] }
  if show_words { outline += &format!["{:>4} ", word_count] }
  if show_bytes { outline += &format!["{:>4} ", byte_count] }
  println!["{}", outline.trim_end()];
  Ok(())
}
Box::new(io::stdin()), f => Box::new(File::open(f)?), }; let mut buf = vec![0;4096]; let mut byte_count = 0; let mut word_count = 0; let mut line_count = 0; loop { let len = stream.read(&mut buf)?; if len == 0 { break } byte_count += len; let s = std::str::from_utf8(&buf[0..len])?; word_count += s.split_whitespace().count(); line_count += s.lines().count(); } let mut outline = "".to_string(); if show_lines { outline += &format!["{:>4} ", line_count] } if show_words { outline += &format!["{:>4} ", word_count] } if show_bytes { outline += &format!["{:>4} ", byte_count] } println!["{}", outline.trim_end()]; Ok(()) } " aria-label="Copy" class="ClipboardButton btn js-clipboard-copy m-2 p-0 tooltipped-no-delay" data-copy-feedback="Copied!" data-tooltip-direction="w">

This example also demonstrates how to tell the parser that certain fields are to be interpreted as boolean values. Right now that is the only configuration available.

Many libraries that do parsing also provide help messages, but I much prefer to write them out by hand as in the example above. This way, I have more control over how the help info is presented and formatted to be maximally helpful. For example, some flags might only make sense in combination with certain other flags, but that is hard to show with formatting options presented by an automated tool. And if the help message gets too long you can always split it out into a separate file.

You might also like...
ripsecrets is a command-line tool to prevent committing secret keys into your source code.

ripsecrets is a command-line tool to prevent committing secret keys into your source code. ripsecrets has a few features that distinguish it from other secret scanning tools:

a command-line tool that transforms a Git repository into a minimal format for ChatGPT queries
a command-line tool that transforms a Git repository into a minimal format for ChatGPT queries

gprepo /dʒiːpiːˈɹi:pi:oʊ/ a command-line tool that transforms a Git repository into a minimal format for ChatGPT queries. Features Excludes LICENSE an

Command-line HTTP client for sending a POST request to specified URI on each stdin line.

line2httppost Simple tool to read lines from stdin and post each line as separate POST request to a specified URL (TCP connection is reused though). G

Pink is a command-line tool inspired by the Unix man command.

Pink is a command-line tool inspired by the Unix man command. It displays custom-formatted text pages in the terminal using a subset of HTML-like tags.

Download pdbs from symbol servers and cache locally, parse symbol paths from env vars

symsrv This crate lets you download and cache pdb files from symbol servers, according to the rules from the _NT_SYMBOL_PATH environment variable. It

languagetool-code-comments integrates the LanguageTool API to parse, spell check, and correct the grammar of your code comments!
languagetool-code-comments integrates the LanguageTool API to parse, spell check, and correct the grammar of your code comments!

languagetool-code-comments integrates the LanguageTool API to parse, spell check, and correct the grammar of your code comments! Overview Install MacO

Parse hex colors to tui::style::Color

Color - Tui Parse hex colors to tui rgb colors #c3f111 - Color::Rgb(195,241,17) Note that the indexed colors are NOT HEX #142 - Color::Indexed(142)

Irx-config - The library provides convenient way to represent/parse configuration from different sources

The irx-config library provides convenient way to represent/parse configuration from different sources. The main goals is to be very easy to use and t

REC2 (Rusty External Command and Control) is client and server tool allowing auditor to execute command from VirusTotal and Mastodon APIs written in Rust. 🦀
REC2 (Rusty External Command and Control) is client and server tool allowing auditor to execute command from VirusTotal and Mastodon APIs written in Rust. 🦀

Information: REC2 is an old personal project (early 2023) that I didn't continue development on. It's part of a list of projects that helped me to lea

Owner
James Halliday
James Halliday
Anglosaxon is a command line tool to parse XML files using SAX

anglosaxon - Convert large XML files to other formats anglosaxon is a command line tool to parse XML files using SAX. You can do simple transformation

Amanda 8 Oct 7, 2022
Python/Rust implementations and notes from Proofs Arguments and Zero Knowledge study group

What is this? This is where I'll be collecting resources related to the Study Group on Dr. Justin Thaler's Proofs Arguments And Zero Knowledge Book. T

Thor 65 Dec 16, 2022
Vim plugin to quickly parse strings into arrays.

butcher Vim plugin to quickly parse strings into arrays. It is painful to write arrays in any programming language, so butcher makes it easy for you.

null 5 Dec 31, 2021
Small command-line tool to switch monitor inputs from command line

swmon Small command-line tool to switch monitor inputs from command line Installation git clone https://github.com/cr1901/swmon cargo install --path .

William D. Jones 5 Aug 20, 2022
ddi is a wrapper for dd. It takes all the same arguments, and all it really does is call dd in the background

ddi A safer dd Introduction If you ever used dd, the GNU coreutil that lets you copy data from one file to another, then you may have encountered a ty

Tomás Ralph 80 Sep 8, 2022
A CLI command to parse kustomize build result and notify it to GitLab

ksnotify A CLI command to parse kustomize build result and notify it to GitLab Caution This repository is under development status. What ksnotify does

null 7 Jan 2, 2023
zero runtime cost default arguments in rust

Default Arguments in Rust Enables default arguments in rust by macro in zero cost. Just wrap function with default_args! and macro with name of functi

Jaeyong Sung 73 Sep 6, 2022
Fallible allocation support for Rust's Vec

Fallible allocation functions for Vec Fallible allocation functions for the Rust standard library's alloc::vec::Vec type. These functions are designed

Microsoft 4 Mar 14, 2023
hj is a command line tool to convert HTTP/1-style text into JSON

hj hj is a command line tool to convert HTTP/1-style text into JSON. This command is inspired by yusukebe/rj, which is a standalone HTTP client that s

FUJI Goro 10 Aug 21, 2022