use std::collections::BTreeMap;
use std::time::Duration;
use anyhow::{Result, bail};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Missing {
pub(crate) flag: &'static str,
pub(crate) variable: &'static str,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Environment {
variables: BTreeMap<String, String>,
}
impl Environment {
pub(crate) fn from_process() -> Self {
Self {
variables: std::env::vars().collect(),
}
}
#[cfg(test)]
pub(crate) fn from_pairs<'pair>(
pairs: impl IntoIterator<Item = (&'pair str, &'pair str)>,
) -> Self {
Self {
variables: pairs
.into_iter()
.map(|(name, value)| (name.to_owned(), value.to_owned()))
.collect(),
}
}
pub(crate) fn read(&self, name: &str) -> Option<String> {
self.variables
.get(name)
.map(|value| value.trim().to_owned())
.filter(|value| !value.is_empty())
}
}
pub(crate) fn resolve_one(
flag_value: Option<String>,
variable: &'static str,
flag: &'static str,
environment: &Environment,
missing: &mut Vec<Missing>,
) -> Option<String> {
let resolved = optional(flag_value, variable, environment);
if resolved.is_none() {
missing.push(Missing { flag, variable });
}
resolved
}
pub(crate) fn optional(
flag_value: Option<String>,
variable: &str,
environment: &Environment,
) -> Option<String> {
flag_value
.map(|value| value.trim().to_owned())
.filter(|value| !value.is_empty())
.or_else(|| environment.read(variable))
}
pub(crate) fn refuse_if_missing(mut missing: Vec<Missing>) -> Result<()> {
if missing.is_empty() {
return Ok(());
}
missing.sort_unstable();
missing.dedup();
let listed = missing
.iter()
.map(|item| format!(" {} or ${}", item.flag, item.variable))
.collect::<Vec<_>>()
.join("\n");
bail!(
"these worker settings have no value; supply each as a flag or an environment \
variable:\n{listed}"
);
}
pub(crate) fn positive_usize(raw: &str, flag: &str, variable: &str) -> Result<usize> {
let value: usize = raw
.parse()
.map_err(|_| anyhow::anyhow!("{flag} / ${variable} must be a whole number, got `{raw}`"))?;
if value == 0 {
bail!("{flag} / ${variable} must be greater than zero, got {value}");
}
Ok(value)
}
pub(crate) fn positive_seconds(raw: &str, flag: &str, variable: &str) -> Result<Duration> {
let seconds = finite_seconds(raw, flag, variable)?;
if seconds <= 0.0 {
bail!("{flag} / ${variable} must be a finite number greater than zero, got `{raw}`");
}
duration(seconds, raw, flag, variable)
}
#[cfg(feature = "acp")]
pub(crate) fn non_negative_seconds(raw: &str, flag: &str, variable: &str) -> Result<Duration> {
let seconds = finite_seconds(raw, flag, variable)?;
if seconds < 0.0 {
bail!("{flag} / ${variable} must not be negative, got `{raw}`");
}
duration(seconds, raw, flag, variable)
}
fn finite_seconds(raw: &str, flag: &str, variable: &str) -> Result<f64> {
let seconds: f64 = raw
.parse()
.map_err(|_| anyhow::anyhow!("{flag} / ${variable} must be a number, got `{raw}`"))?;
if !seconds.is_finite() {
bail!("{flag} / ${variable} must be a finite number of seconds, got `{raw}`");
}
Ok(seconds)
}
fn duration(seconds: f64, raw: &str, flag: &str, variable: &str) -> Result<Duration> {
Duration::try_from_secs_f64(seconds).map_err(|source| {
anyhow::anyhow!("{flag} / ${variable} is not a representable duration (`{raw}`): {source}")
})
}