akv-cli 0.10.1

The Azure Key Vault CLI (unofficial) can read secrets from Key Vault, securely pass secrets to other commands or inject them into configuration files, encrypt and decrypt secrets, and managed keys and secrets in Key Vault.
Documentation
// Copyright 2025 Heath Stewart.
// Licensed under the MIT License. See LICENSE.txt in the project root for license information.

use clap::{builder::TypedValueParser, ColorChoice, Parser};
use std::{
    env,
    io::{self, Write},
    time::Duration,
};
use tokio::time::sleep;
use wildcard::Wildcard;
use yansi::{Condition, Paint as _, Style};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();
    let mut vars: Vec<(String, String)> = env::vars_os()
        .filter_map(|(k, v)| {
            let key = k.as_os_str().to_str()?;
            // First determine if we should include "private" variables.
            if !args.all && key.starts_with("_") {
                return None;
            }

            // Second determine if there are any variables or patterns to match.
            if !args.is_match(key) {
                return None;
            }

            Some((key.to_owned(), v.into_string().ok()?))
        })
        .collect();

    // Sort for easy comparison to expected output.
    vars.sort_by(|a, b| a.0.cmp(&b.0));

    // Interleave stdout and stderr for testing purposes.
    let padding = vars.len().to_string().len();

    let green: Style = Style::new()
        .green()
        .whenever(args.color(Condition::STDOUT_IS_TTY));
    let red: Style = Style::new()
        .red()
        .whenever(args.color(Condition::STDERR_IS_TTY));

    let mut stdout = io::stdout().lock();
    let mut stderr = io::stderr().lock();
    for (i, (key, value)) in vars.into_iter().enumerate() {
        if let Some(delay) = args.delay {
            sleep(Duration::from_millis(delay)).await;
        }

        let line = format!("{key}={value}");
        match i % 2 {
            0 => writeln!(
                stdout,
                "{}: {line}",
                format!("stdout {:>padding$}", i + 1).paint(green)
            )?,
            _ => writeln!(
                stderr,
                "{}: {line}",
                format!("stderr {:>padding$}", i + 1).paint(red)
            )?,
        }
    }

    Ok(())
}

#[derive(Debug, Parser)]
struct Args {
    /// Include all variables including those starting with "_".
    #[arg(long)]
    all: bool,

    /// Print colors.
    #[arg(short = 'c', long, default_value_t)]
    color: ColorChoice,

    /// Number of milliseconds to delay between printing lines.
    #[arg(long)]
    delay: Option<u64>,

    /// Optional variable names to print. These may contain wildcard symbols `*` and `?`.
    #[arg(value_name = "VARIABLES", value_parser = WildcardValueParser, trailing_var_arg = true)]
    vars: Vec<Wildcard<'static>>,
}

impl Args {
    fn color(&self, is_tty: Condition) -> Condition {
        match self.color {
            ColorChoice::Always => Condition::ALWAYS,
            ColorChoice::Auto => is_tty,
            ColorChoice::Never => Condition::NEVER,
        }
    }

    fn is_match(&self, s: impl AsRef<str>) -> bool {
        if self.vars.is_empty() {
            return true;
        }

        for var in &self.vars {
            if var.is_match(s.as_ref().as_bytes()) {
                return true;
            }
        }

        false
    }
}

#[derive(Clone)]
struct WildcardValueParser;

impl TypedValueParser for WildcardValueParser {
    type Value = Wildcard<'static>;
    fn parse_ref(
        &self,
        _cmd: &clap::Command,
        _arg: Option<&clap::Arg>,
        value: &std::ffi::OsStr,
    ) -> Result<Self::Value, clap::Error> {
        let bytes = value.as_encoded_bytes().to_vec();
        Wildcard::from_owned(bytes)
            .map_err(|_| clap::Error::new(clap::error::ErrorKind::InvalidValue))
    }
}