Crate argtea

Crate argtea 

Source
Expand description

Declarative macro commandline parser (inspired by argwerk).

argtea attempts to reduce abstraction and maximize flexibility.

§Comparison to argwerk

argteaargwerk
--flag=value syntax
-sw 80 <=> -s -w 80 syntax
-Wall <=> -W all syntax
OsString argument support
Customizable help message formatting✓*
Help message generation✓*

[*] At runtime

§Example project:

use argtea::{argtea_impl, simple_format};

#[derive(Debug)]
pub struct Arguments {
    output_path: String,
    files: Vec<String>,
}

fn main() -> Result<(), String> {
    let args = Arguments::parse()?;

    println!("input files: {:?}", args.files);
    println!("output file: {:?}", args.output_path);

    Ok(())
}

argtea_impl! {
    {
        /// Displays this help message.
        ("--help" | "-h") => {
            eprintln!("{}", Self::HELP);

            std::process::exit(0);
        }

        /// Sets the output file path.
        (flag @ "--output" | "-o", output_path) => {
            let Some(output_path) = output_path else {
                return Err(format!("Expected path after `{flag}`"));
            };

            output_path_ = Some(output_path);
        }

        /// Adds a file as an input.
        ///
        /// To input a file that starts with a `-`, prefix it with a `./`
        (file) => {
            if file.starts_with("-") {
                return Err(format!("invalid flag `{file}`"));
            }
             
            files.push(file);
        }
    }

    impl Arguments {
        const HELP: &'static str = simple_format!(
            "argtea_test: a demo argtea project"
            ""
            "Usage: "
            "  `argtea_test [FLAGS] [FILES]`"
            ""
            "Options:"
            docs!()
        );

        pub fn parse() -> Result<Self, String> {
            let mut files = Vec::new();
            let mut output_path_ = None;

            parse!(std::env::args().skip(1));

            Ok(Self {
                files,
                output_path: output_path_.unwrap_or_else(|| "a.out".to_owned())
            })
        }
    }
}

§Functions

Argtea functions are defined with syntax similar to regular Rust functions. Note: unlike Rust functions, generics cannot be specified.

Argtea functions can use the parse!() macro which takes in a String iterator. It will then parse it using the flags and code defined above.

NOTE: the parse!() macro cannot be used within code blocks.

§Constants

There are two types of argtea constants:

  1. Flag constants:
argtea_impl! {
    {/* ... */}
    impl Foo {
        pub const FLAGS: &'static [argtea::Flag] = docs!();
    }
}
  1. Macro constants:
argtea_impl! {
    { /* ... */ }
    impl Foo {
        pub const HELP: &'static str = simple_format!("a" docs!() "b");
    }
}

The first type of constant generates an Flag for each non-#[hidden] flag. This can be used to generate help messages and other information at run-time.

Macro constants call macros with information about the non-#[hidden] flags. These can be used for compile-time help message generation. This crate provides the simple_format macro which provides simple, compile-time help message generation. For more information about formatting macros, see the “Formatting macros” section below.

§break

break can be used within a flag’s code to immediately stop flag parsing. Additionally, the label 'stop_parsing can be used if a nested break is required.

This may be useful for implementing subcommands or --.

argtea_impl! {
    {
        ("--do_something" | "-d") => { /* do something */ }

        /// Interperets the remaining arguments as file names (even if they start with -)
        ("--") => { break }

        (file) => { files.push(file) }
    }
    impl Foo {
        fn parse() -> Foo {
            let mut files = Vec::new();

            let mut args = std::env::args().skip(1);

            parse!(args);

            // Parse remaining arguments after `--`
            for file in args {
                files.push(file);
            }

            Self { files }
        }
    }
}

§#[hidden] and #[fake]

Flags can optionally be annotated with #[hidden] or #[fake]. #[hidden] hides a flag from the documentation while #[fake] shows a flag in the documentation that doesn’t really exist.

The following is an example where #[fake] and #[hidden] come in handy:

/// Enables all warnings
#[fake]
("-Wall") => {}

// In this example, argtea interprets `-Wall` as `-W all`, so it will be matched to this flag.
// This would be the case even if the above flag wasn't annotated with `#[fake]`
//
// However, the user may still wish to display `-Wall` as a separate flag in their documentation.
("-W" | "--warning", warning) => { /* ... */ }

// This flag is just here for error handling (when the user passes in an invalid flag)
// Therefore, this shouldn't be shown in the documentation
#[hidden]
(invalid_flag) => { /* ... */ }

§Formatting macros

Formatting macros are just regular macros that take in the following pattern:

[
    $({
        doc: [
            $( $doc:literal ),*
        ],
        flags: [
            $( $flag:literal ),*
        ],
        params: [
            $( $param:ident ),*
        ]
    }),*
]

argtea itself contains the formatting macro simple_format. This macro isn’t special in any way, and the user can define their own macro that functions similarly as long as the above criterion is met.

When the following is written in the argtea_impl macro, the first docs!() parameter is replaced with the above pattern. Then, the simple_format macro is called:

argtea_impl! {
    { /* ... */ }
    impl Foo {
        const HELP: &'static str = simple_format!("a" docs!() "b");
    }
}

Macros§

argtea_impl
simple_format
Simple compile-time formatting of commandline options.

Structs§

Flag

Functions§

wrapping_format
More complicated runtime formatting of comandline options.