use std::collections::HashMap;
use anyhow::{Result, anyhow};
use super::join_labels;
use super::policies::{
is_env_truthy, parse_prefer_runners, resolve_failure_policy, resolve_fallback_policy,
resolve_mismatch_policy,
};
use super::types::{
DiagnosticFlags, ExplainSource, OverrideOrigin, OverrideSources, PmOverride,
ResolutionOverrides, RunnerOverride, SourceValue,
};
use crate::config::{LoadedConfig, parse_node_pm, parse_python_pm};
use crate::types::{Ecosystem, PackageManager, TaskRunner};
impl ResolutionOverrides {
pub(crate) fn from_cli_and_env(
cli_pm: Option<&str>,
cli_runner: Option<&str>,
cli_fallback: Option<&str>,
cli_on_mismatch: Option<&str>,
diagnostics: DiagnosticFlags,
failure: crate::cli::ChainFailureFlags,
config: Option<&LoadedConfig>,
) -> Result<Self> {
let env_pm = std::env::var("RUNNER_PM").ok();
let env_runner = std::env::var("RUNNER_RUNNER").ok();
let env_fallback = std::env::var("RUNNER_FALLBACK").ok();
let env_on_mismatch = std::env::var("RUNNER_ON_MISMATCH").ok();
let env_no_warnings = std::env::var("RUNNER_NO_WARNINGS").ok();
let env_explain = std::env::var("RUNNER_EXPLAIN").ok();
let env_keep_going = std::env::var("RUNNER_KEEP_GOING").ok();
let env_kill_on_fail = std::env::var("RUNNER_KILL_ON_FAIL").ok();
Self::from_sources(OverrideSources {
pm: SourceValue {
cli: cli_pm,
env: env_pm.as_deref(),
},
runner: SourceValue {
cli: cli_runner,
env: env_runner.as_deref(),
},
fallback: SourceValue {
cli: cli_fallback,
env: env_fallback.as_deref(),
},
on_mismatch: SourceValue {
cli: cli_on_mismatch,
env: env_on_mismatch.as_deref(),
},
no_warnings: ExplainSource {
cli: diagnostics.no_warnings,
env: env_no_warnings.as_deref(),
},
explain: ExplainSource {
cli: diagnostics.explain,
env: env_explain.as_deref(),
},
keep_going: ExplainSource {
cli: failure.keep_going,
env: env_keep_going.as_deref(),
},
kill_on_fail: ExplainSource {
cli: failure.kill_on_fail,
env: env_kill_on_fail.as_deref(),
},
config,
})
}
#[allow(
clippy::needless_pass_by_value,
reason = "OverrideSources is a single-use builder; taking by value keeps the call sites moveable"
)]
pub(crate) fn from_sources(sources: OverrideSources<'_>) -> Result<Self> {
let pm = parse_override(
sources.pm.cli,
sources.pm.env,
parse_pm_label,
|pm, origin| PmOverride { pm, origin },
)?;
let runner = parse_override(
sources.runner.cli,
sources.runner.env,
parse_runner_label,
|runner, origin| RunnerOverride { runner, origin },
)?;
let fallback =
resolve_fallback_policy(sources.fallback.cli, sources.fallback.env, sources.config)?;
let on_mismatch = resolve_mismatch_policy(
sources.on_mismatch.cli,
sources.on_mismatch.env,
sources.config,
)?;
let prefer_runners = parse_prefer_runners(sources.config)?;
let no_warnings =
sources.no_warnings.cli || sources.no_warnings.env.is_some_and(is_env_truthy);
let explain = sources.explain.cli || sources.explain.env.is_some_and(is_env_truthy);
let failure_policy =
resolve_failure_policy(sources.keep_going, sources.kill_on_fail, sources.config)?;
let mut pm_by_ecosystem = HashMap::new();
if let Some(loaded) = sources.config {
if let Some(raw) = loaded.config.pm.node.as_deref() {
let pm_value = parse_node_pm(raw)?;
pm_by_ecosystem.insert(
pm_value.ecosystem(),
PmOverride {
pm: pm_value,
origin: OverrideOrigin::ConfigFile {
path: loaded.path.clone(),
},
},
);
}
if let Some(raw) = loaded.config.pm.python.as_deref() {
let pm_value = parse_python_pm(raw)?;
pm_by_ecosystem.insert(
Ecosystem::Python,
PmOverride {
pm: pm_value,
origin: OverrideOrigin::ConfigFile {
path: loaded.path.clone(),
},
},
);
}
}
Ok(Self {
pm,
pm_by_ecosystem,
runner,
prefer_runners,
fallback,
on_mismatch,
no_warnings,
explain,
failure_policy,
})
}
}
fn parse_pm_label(raw: &str) -> Result<PackageManager> {
if let Some(pm) = PackageManager::from_label(raw) {
return Ok(pm);
}
if let Some(runner) = TaskRunner::from_label(raw) {
return Err(anyhow!(
"{:?} is a task runner, not a package manager; use `--runner {}` instead",
raw,
runner.label(),
));
}
Err(anyhow!(
"unknown package manager {raw:?}; expected one of {}",
join_labels(
PackageManager::all()
.iter()
.copied()
.map(PackageManager::label)
),
))
}
fn parse_runner_label(raw: &str) -> Result<TaskRunner> {
if let Some(runner) = TaskRunner::from_label(raw) {
return Ok(runner);
}
if let Some(pm) = PackageManager::from_label(raw) {
return Err(anyhow!(
"{:?} is a package manager, not a task runner; use `--pm {}` instead",
raw,
pm.label(),
));
}
Err(anyhow!(
"unknown task runner {raw:?}; expected one of {}",
join_labels(TaskRunner::all().iter().copied().map(TaskRunner::label)),
))
}
fn parse_override<T, P, V, B>(
cli: Option<&str>,
env: Option<&str>,
parse: V,
build: B,
) -> Result<Option<T>>
where
V: Fn(&str) -> Result<P>,
B: Fn(P, OverrideOrigin) -> T,
{
if let Some(raw) = cli.map(str::trim).filter(|s| !s.is_empty()) {
let parsed = parse(raw)?;
return Ok(Some(build(parsed, OverrideOrigin::CliFlag)));
}
if let Some(raw) = env.map(str::trim).filter(|s| !s.is_empty()) {
let parsed = parse(raw)?;
return Ok(Some(build(parsed, OverrideOrigin::EnvVar)));
}
Ok(None)
}