use shep_core::paths::ShepPaths;
use crate::cli::InstallArgs;
use crate::commands::shep_toml::ShepToml;
use crate::dog_index::AvailableDog;
use crate::exit::ExitCode;
use crate::output::Streams;
use super::consent::Input;
use super::depends;
use super::install::{Fetched, Reach, adopt_fetched, fetch_dog};
#[derive(Debug)]
pub(super) struct Catalog<'a> {
url: &'a str,
dogs: &'a [AvailableDog],
}
impl<'a> Catalog<'a> {
pub(super) fn new(url: &'a str, dogs: &'a [AvailableDog]) -> Self {
Self { url, dogs }
}
pub(super) fn url(&self) -> &'a str {
self.url
}
pub(super) fn row(&self, name: &str) -> Option<&'a AvailableDog> {
self.dogs.iter().find(|dog| dog.adopt_as == name)
}
}
pub(super) async fn gather(
streams: &mut Streams<'_>,
paths: &ShepPaths,
args: &InstallArgs,
reach: &Reach,
catalog: &Catalog<'_>,
dog: &AvailableDog,
fetched: &mut Vec<Fetched>,
) -> Result<(), ExitCode> {
let adopted = ShepToml::read_only(&paths.daemon_config)
.map(|config| config.adopted_dog_names())
.map_err(|err| crate::commands::dogs::fail_config(streams, &err))?;
let mut trail = Vec::new();
gather_below(
streams,
paths,
&(args, reach, catalog, adopted.as_slice()),
dog,
&mut trail,
fetched,
)
.await
}
type Settings<'a> = (&'a InstallArgs, &'a Reach, &'a Catalog<'a>, &'a [String]);
async fn gather_below(
streams: &mut Streams<'_>,
paths: &ShepPaths,
settings: &Settings<'_>,
dog: &AvailableDog,
trail: &mut Vec<String>,
fetched: &mut Vec<Fetched>,
) -> Result<(), ExitCode> {
let (args, reach, catalog, adopted) = *settings;
let git_ref = if trail.is_empty() {
args.git_ref.as_deref()
} else {
None
};
let here = fetch_dog(streams, paths, args, reach, dog, git_ref).await?;
trail.push(dog.adopt_as.clone());
if let Some(manifest) = &here.manifest {
let wanted = depends::missing(manifest, &dog.adopt_as, trail, adopted, catalog.dogs)
.map_err(|refusal| {
streams.fail(refusal.code(), &format!("{refusal}; nothing was adopted"))
})?;
for row in wanted {
if fetched.iter().any(|met| met.dog.adopt_as == row.adopt_as) {
continue;
}
Box::pin(gather_below(streams, paths, settings, row, trail, fetched)).await?;
}
}
trail.pop();
fetched.push(here);
Ok(())
}
pub(super) async fn adopt_in_order(
streams: &mut Streams<'_>,
paths: &ShepPaths,
args: &InstallArgs,
input: &mut Input,
catalog: &Catalog<'_>,
fetched: Vec<Fetched>,
) -> ExitCode {
let count = fetched.len();
let dog = fetched.last().map(|last| last.dog.adopt_as.clone());
for (at, one) in fetched.into_iter().enumerate() {
let name = one.dog.adopt_as.clone();
let mut said_no = false;
if at + 1 == count {
return adopt_fetched(streams, paths, args, input, one, catalog.url, &mut said_no)
.await;
}
let mut sink = std::io::sink();
let mut quiet = Streams {
out: &mut sink,
err: &mut *streams.err,
style: streams.style,
fmt: streams.fmt,
};
let code = adopt_fetched(
&mut quiet,
paths,
args,
input,
one,
catalog.url,
&mut said_no,
)
.await;
if code != ExitCode::Success {
return code;
}
let dog = dog.as_deref().unwrap_or_default();
if said_no {
return streams.fail(
ExitCode::Failure,
&format!(
"{dog} needs {name}, and you said no to it, so {dog} was not installed. \
{name} stays installed and disabled"
),
);
}
streams.aside("install", &format!("installed {name}, which {dog} needs"));
}
ExitCode::Success
}