Skip to content
 
 

Repository files navigation

argh-env

A fork of google/argh with environment variable fallback for options.

This fork exists because upstream argh only reads configuration from the command line. That works well for local CLI tools, but in Kubernetes you usually configure workloads through environment variables—env, envFrom, ConfigMaps, and Secrets in the pod spec—not long argument lists in the container command. With env fallback, the same FromArgs struct can serve both interactive use (flags on the CLI) and cluster deployment (values injected as env vars), without a separate config layer.

This crate is API-compatible with argh. By default the Rust import is argh_env, but you can keep using argh via a Cargo dependency alias (see Migrating from argh). Options can read from the environment when not passed on the command line:

#[argh(option, env = "MY_VAR")]

crates.io license docs.rs Argh

Derive-based argument parsing optimized for code size and conformance to the Fuchsia commandline tools specification.

The public API of this library consists primarily of the FromArgs derive and the from_env function, which can be used to produce a top-level FromArgs type from the current program's commandline arguments.

Migrating from argh

You can depend on argh-env directly, or keep the argh dependency name in Cargo.toml:

# Cargo.toml — keep the dependency key as `argh`
argh = { package = "argh-env", version = "0.1" }

With that alias, existing use argh::FromArgs imports continue to work unchanged.

Alternatively, depend on the crate by its published name:

# Cargo.toml
argh-env = "0.1.0"   # was: argh = "0.1.20"
use argh_env::FromArgs;  // was: use argh::FromArgs
// #[argh(...)] attributes unchanged

Basic Example

use argh_env::FromArgs;

#[derive(FromArgs)]
/// Reach new heights.
struct GoUp {
    /// whether or not to jump
    #[argh(switch, short = 'j')]
    jump: bool,

    /// how high to go
    #[argh(option)]
    height: usize,

    /// an optional nickname for the pilot
    #[argh(option)]
    pilot_nickname: Option<String>,
}

fn main() {
    let up: GoUp = argh_env::from_env();
}

./some_bin --help will then output the following:

Usage: cmdname [-j] --height <height> [--pilot-nickname <pilot-nickname>]

Reach new heights.

Options:
  -j, --jump        whether or not to jump
  --height          how high to go
  --pilot-nickname  an optional nickname for the pilot
  --help, help      display usage information

The resulting program can then be used in any of these ways:

  • ./some_bin --height 5
  • ./some_bin -j --height 5
  • ./some_bin --jump --height 5 --pilot-nickname Wes

Switches, like jump, are optional and will be set to true if provided.

Options, like height and pilot_nickname, can be either required, optional, or repeating, depending on whether they are contained in an Option or a Vec. Default values can be provided using the #[argh(default = "<your_code_here>")] attribute, and in this case an option is treated as optional.

Environment variable fallback

You can specify an environment variable fallback using the env attribute. If the argument is not provided via the command line, argh_env will look for the environment variable. Command-line values always take precedence over environment variables.

This is especially useful in Kubernetes: set options via the pod manifest and keep CLI flags for local development and debugging. For example, a Deployment can set DATABASE_URL while you still pass --database-url when running the binary on your machine.

use argh_env::FromArgs;

#[derive(FromArgs)]
/// Reach new heights.
struct GoUp {
    /// an optional nickname for the pilot
    #[argh(option, env = "GO_UP_PILOT")]
    pilot_nickname: Option<String>,

    /// an optional height
    #[argh(option, env = "GO_UP_HEIGHT")]
    height: usize,
}

fn main() {
    let up: GoUp = argh_env::from_env();
}
use argh_env::FromArgs;

fn default_height() -> usize {
    5
}

#[derive(FromArgs)]
/// Reach new heights.
struct GoUp {
    /// an optional nickname for the pilot
    #[argh(option)]
    pilot_nickname: Option<String>,

    /// an optional height
    #[argh(option, default = "default_height()")]
    height: usize,

    /// an optional direction which is "up" by default
    #[argh(option, default = "String::from(\"only up\")")]
    direction: String,
}

fn main() {
    let up: GoUp = argh_env::from_env();
}

Custom option types can be deserialized so long as they implement the FromArgValue trait (automatically implemented for all FromStr types). If more customized parsing is required, you can supply a custom fn(&str) -> Result<T, String> using the from_str_fn attribute:

use argh_env::FromArgs;

#[derive(FromArgs)]
/// Goofy thing.
struct FiveStruct {
    /// always five
    #[argh(option, from_str_fn(always_five))]
    five: usize,
}

fn always_five(_value: &str) -> Result<usize, String> {
    Ok(5)
}

Positional arguments can be declared using #[argh(positional)]. These arguments will be parsed in order of their declaration in the structure:

use argh_env::FromArgs;

#[derive(FromArgs, PartialEq, Debug)]
/// A command with positional arguments.
struct WithPositional {
    #[argh(positional)]
    first: String,
}

The last positional argument may include a default, or be wrapped in Option or Vec to indicate an optional or repeating positional argument.

Subcommands are also supported. To use a subcommand, declare a separate FromArgs type for each subcommand as well as an enum that cases over each command:

use argh_env::FromArgs;

#[derive(FromArgs, PartialEq, Debug)]
/// Top-level command.
struct TopLevel {
    #[argh(subcommand)]
    nested: MySubCommandEnum,
}

#[derive(FromArgs, PartialEq, Debug)]
#[argh(subcommand)]
enum MySubCommandEnum {
    One(SubCommandOne),
    Two(SubCommandTwo),
}

#[derive(FromArgs, PartialEq, Debug)]
/// First subcommand.
#[argh(subcommand, name = "one")]
struct SubCommandOne {
    #[argh(option)]
    /// how many x
    x: usize,
}

#[derive(FromArgs, PartialEq, Debug)]
/// Second subcommand.
#[argh(subcommand, name = "two", short = 't')]
struct SubCommandTwo {
    #[argh(switch)]
    /// whether to fooey
    fooey: bool,
}

Advanced Description

You can define a complex help output that includes an Examples section. Use a {command_name} placeholder.

#[derive(FromArgs, Debug)]
#[argh(
    description = "{command_name} is a tool to reach new heights.\n\n\
    Start exploring new heights:\n\n\
    \u{00A0} {command_name} --height 5 jump\n\
    ",
    example = "\
    {command_name} --height 5\n\
    {command_name} --height 5 j\n\
    {command_name} --height 5 --pilot-nickname Wes jump"
)]
pub struct CliArgs {
    /// how high to go
    #[argh(option)]
    height: usize,
    /// an optional nickname for the pilot
    #[argh(option)]
    pilot_nickname: Option<String>,
    /// command to execute
    #[argh(subcommand)]
    pub command: Command,
}

Output:

Usage: goup --height <height> [--pilot-nickname <pilot-nickname>] <command> [<args>]

goup is a tool to reach new heights.

Start exploring new heights:

  goup --height 5 jump

Options:
  --height          how high to go
  --pilot-nickname  an optional nickname for the pilot
  --help, help      display usage information

Commands:
  jump  j           whether or not to jump

Examples:
  goup --height 5
  goup --height 5 j
  goup --height 5 --pilot-nickname Wes jump

How to debug the expanded derive macro

The argh_env::FromArgs derive macro can be debugged with the cargo-expand crate.

Expand the derive macro in examples/simple_example.rs

See argh/examples/simple_example.rs for the example struct we wish to expand.

First, install cargo-expand by running cargo install cargo-expand. Note this requires the nightly build of Rust.

Once installed, run cargo expand --package argh-env and you can see the expanded code.

Note

This is an independent fork of google/argh, maintained separately from Google. The original project is not an officially supported Google product. This fork is licensed under the same BSD-3-Clause license as upstream.

About

Rust derive-based argument parsing optimized for code size

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages