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()?;
if !args.all && key.starts_with("_") {
return None;
}
if !args.is_match(key) {
return None;
}
Some((key.to_owned(), v.into_string().ok()?))
})
.collect();
vars.sort_by(|a, b| a.0.cmp(&b.0));
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 {
#[arg(long)]
all: bool,
#[arg(short = 'c', long, default_value_t)]
color: ColorChoice,
#[arg(long)]
delay: Option<u64>,
#[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))
}
}