Skip to main content

cli

Attribute Macro cli 

Source
#[cli]
Available on crate feature cli only.
Expand description

For the Command Line Interface feature set, please refer to the cu::cli module.

This is the documentation for the #[cu::cli] macro.

By annotating the main function, this macro generates a shim that will reference the cu::cli::Flags command line arguments and initialize logging, printing, and prompting systems accordingly.

The main function can be async or sync. It should return a cu::Result

ⓘ
#[cu::cli]
fn main(flags: cu::cli::Flags) -> cu::Result<()> {
    cu::debug!("flags are {flags:?}");
    Ok(())
}

To also define your own flags using the clap crate, define a CLI struct that derives clap::Parser. Note the prelude import (cu::pre::*) automatically brings clap into scope. You don’t even need to add it to Cargo.toml!

Make sure to #[clap(flatten)] the flags into your struct.

ⓘ
use cu::pre::*;
/// My program
///
/// This is my program, it is very good.
#[derive(clap::Parser, Clone)]
struct Args {
    /// Input of the program
    #[clap(short, long)]
    input: String,
    /// Output of the program
    #[clap(short, long)]
    output: Option<String>,
    #[clap(flatten)]
    inner: cu::cli::Flags,
}

Now, to tell cu where to look for the flags, specify the name of the field with flags = "field"

ⓘ
// use the flags attribute to refer to the cu::cli::Flags field inside the Args struct
#[cu::cli(flags = "inner")]
fn main(args: Args) -> cu::Result<()> {
    cu::info!("input is {}", args.input);
    cu::info!("output is {:?}", args.output);
    Ok(())
}

Alternatively, implement AsRef<cu::cli::Flag> for your struct.

ⓘ
use cu::pre::*;
#[derive(clap::Parser, Clone)]
struct Args {
    input: String,
    #[clap(flatten)]
    inner: cu::cli::Flags,
}
impl AsRef<cu::cli::Flags> for Args {
    fn as_ref(&self) -> cu::cli::Flags {
        &self.inner
    }
}
#[cu::cli]
fn main(_: Args) -> cu::Result<()> {
    Ok(())
}

Or enable the derive feature and derive AsRef (via derive_more).

ⓘ
use cu::pre::*;
#[derive(clap::Parser, Clone, AsRef)]
struct Args {
    input: String,
    #[clap(flatten)]
    #[as_ref]
    inner: cu::cli::Flags,
}
#[cu::cli]
fn main(_: Args) -> cu::Result<()> {
    Ok(())
}

§Attributes

§preprocess

The attribute can also take a preprocess function to process flags before initializing the CLI system. This can be useful to merge multiple Flags instance in the CLI. Note that the logging/printing system will not work during the preprocess.

ⓘ
use cu::pre::*;

#[derive(clap::Parser)]
struct Args {
    #[clap(subcommand)]
    subcommand: Option<Command>,
    #[clap(flatten)]
    inner: cu::cli::Flags,
}
impl Args {
    fn preprocess(&mut self) {
        // merge subcommand flags into top level flags
        // this way, both `-v foo` and `foo -v` will work
        if let Some(Command::Foo(c)) = &self.subcommand {
            self.inner.merge(c);
        }
    }
}
impl AsRef<cu::cli::Flags> for Args {
    fn as_ref(&self) -> &cu::cli::Flags {
        &self.inner
    }
}
#[derive(clap::Subcommand)]
enum Command {
    Foo(cu::cli::Flags),
}
#[cu::cli(preprocess = Args::preprocess)]
fn main(args: Args) -> cu::Result<()> {
    Ok(())
}

§log_config

log_config takes a function that returns a LogConfig trait implementation, which can be used to alter how each LogRecord is displayed.

Below is an example that: overrides info messages in some_library to be debug messages if the print level is info, and always show module path for that library.

ⓘ
use cu::pre::*;

struct LogConfig(cu::lv::PrintLevel);
impl LogConfig {
    pub fn new(flags: &cu::cli::Flags) -> Self {
        Self(flags.print_level())
    }
}
impl cu::cli::LogConfig for LogConfig {
    fn process(&self, record: &cu::lv::LogRecord) -> (cu::lv::Lv, bool) {
        if self.0 == cu::lv::PrintLevel::Normal {
            if let Some(m) = record.module_path() {
                if m == "some_library" {
                    let level: cu::lv::Lv = record.level().into();
                    let is_info = level == cu::lv::I;
                    return (if is_info { cu::lv::D } else { level }, true);
                }
            }
        }
        cu::cli::DefaultLogConfig.process(record)
    }
}

#[cu::cli(log_config = LogConfig::new)]
fn main(_: cu::cli::Flags) -> cu::Result<()> {
    Ok(())
}