use std::collections::BTreeMap;
use std::path::Path;
use shep_client::{Client, ConnectError};
use shep_core::paths::ShepPaths;
use shep_core::permissions::Grants;
use shep_core::protocol::{DogSource, EnvValue};
use crate::commands::shep_toml::{ShepToml, ShepTomlError};
use crate::exit::ExitCode;
use crate::output::{DogActionRow, DogActionStatus, Streams, emit, write_outcome};
use crate::version_guard::{VersionGuard, refuse_version_skew};
mod adopt;
mod barks;
mod consent;
mod depends;
mod disable;
mod dog_rollback;
mod dog_upgrade;
mod enable;
mod entry_drift;
mod grant;
mod grants;
mod install;
mod install_cargo;
mod install_depends;
mod install_download;
mod install_keep;
mod install_ref;
mod receipt;
mod remove;
mod second_look;
mod session;
mod shep_upgrade;
mod upgrade;
mod vet;
#[cfg(all(test, unix))]
mod adopt_consent_tests;
#[cfg(all(test, unix))]
mod adopt_declined_tests;
#[cfg(test)]
mod consent_tests;
#[cfg(all(test, unix))]
mod defaults_adoption_tests;
#[cfg(all(test, unix))]
mod dog_upgrade_tests;
#[cfg(all(test, unix))]
mod emit_dog_action_tests;
#[cfg(test)]
mod enable_builtin_tests;
#[cfg(test)]
mod grant_builtin_tests;
#[cfg(all(test, unix))]
mod grant_record_tests;
#[cfg(all(test, unix))]
mod grant_tests;
#[cfg(all(test, unix))]
mod install_depends_tests;
#[cfg(all(test, unix))]
mod install_download_tests;
#[cfg(all(test, unix))]
mod install_ref_tests;
#[cfg(all(test, unix))]
mod install_tests;
#[cfg(all(test, unix))]
mod manifest_adoption_tests;
#[cfg(all(test, unix))]
mod remove_install_tests;
#[cfg(test)]
mod second_look_race_tests;
#[cfg(test)]
mod second_look_tests;
#[cfg(all(test, unix))]
mod session_tests;
#[cfg(test)]
mod shep_upgrade_tests;
#[cfg(test)]
mod test_support;
#[cfg(all(test, unix))]
mod upgrade_tests;
pub use adopt::adopt;
pub use barks::barks;
pub use disable::disable;
pub use dog_upgrade::upgrade;
pub use enable::enable;
#[cfg(test)]
pub(crate) use entry_drift::test_support as entry_drift_support;
pub(crate) use entry_drift::{
DOG_ENTRY_KEPT_NOTICE, kept_entries, kept_entries_among, kept_entry_report,
};
pub(crate) use grant::{Change, GrantRefusal, change, record_change};
pub use install::install;
pub(crate) use install::Reach;
pub(crate) use receipt::installed;
pub use remove::rehome;
pub(crate) use remove::{Removal, remove_dog};
#[cfg(all(test, unix))]
pub(crate) use session::stub_reach;
pub(crate) use session::{Job, Progress, Step, run};
pub(crate) use upgrade::{upgrade_grants, upgrade_notice};
pub use vet::{DogSchema, warn_of_a_dog_a_restart_would_break};
pub(crate) use adopt::collides_with_a_verb;
pub(crate) use vet::cached_manifest;
#[cfg(test)]
pub(crate) use vet::sha256_hex;
#[cfg(test)]
pub(crate) use consent::declared_review;
#[expect(unused_imports, reason = "lookout's install modal reaches them here")]
pub(crate) use consent::{Input, Prompt, Review};
pub(crate) use disable::disable_in_config;
pub(crate) use enable::{EnableRefusal, enable_in_config, join_with_and};
pub(crate) use vet::{ManifestRead, VERSION_BUDGET, ask_schema, dog_env, read_manifest};
const NO_SHEPHERD_ENABLE_STATUS: DogActionStatus =
DogActionStatus::NoShepherd("will start with the next shepherd");
const DECLINED_STATUS: DogActionStatus =
DogActionStatus::NoShepherd("not enabled; nothing was granted");
const NOT_ENABLED_STATUS: DogActionStatus =
DogActionStatus::NoShepherd("adopted, not enabled; `shep enable` starts it");
const NO_SHEPHERD_DISABLE_STATUS: DogActionStatus =
DogActionStatus::NoShepherd("not running; will not start with the next shepherd");
const DISABLED_STATUS: DogActionStatus = DogActionStatus::Acted("stopped");
fn emit_dog_action(
streams: &mut Streams<'_>,
verb: &str,
name: &str,
source: impl Into<Option<DogSource>>,
status: DogActionStatus,
) -> ExitCode {
write_outcome(emit(
&mut *streams.out,
streams.fmt,
verb,
DogActionRow::new(name, source, status),
streams.style,
))
}
pub(crate) fn fail_config(streams: &mut Streams<'_>, err: &ShepTomlError) -> ExitCode {
let code = match err {
ShepTomlError::Io { .. } => ExitCode::Failure,
ShepTomlError::Parse { .. }
| ShepTomlError::WrongShape { .. }
| ShepTomlError::NoDogEntry { .. }
| ShepTomlError::BadGrants { .. } => ExitCode::InvalidConfig,
};
streams.fail(code, &err.to_string())
}
pub(crate) fn boot_first_in_config(
path: &Path,
name: &str,
first: bool,
) -> Result<(), ShepTomlError> {
ShepToml::try_edit(path, |cfg| cfg.set_boot_first_dog(name, first))
}
fn dog_source(cfg: &ShepToml, name: &str, grants: Option<Grants>) -> DogSource {
adopted_source(cfg, name, grants.clone()).unwrap_or(DogSource::BuiltIn {
grants,
events: recorded_events(cfg, name),
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DogEntry {
pub source: DogSource,
pub env: BTreeMap<String, EnvValue>,
}
fn dog_entry(cfg: &ShepToml, name: &str, grants: Option<Grants>) -> DogEntry {
DogEntry {
source: dog_source(cfg, name, grants),
env: cfg
.adopted_dog_env(name)
.into_iter()
.map(|(key, value)| (key, value.into()))
.collect(),
}
}
fn adopted_source(cfg: &ShepToml, name: &str, grants: Option<Grants>) -> Option<DogSource> {
cfg.adopted_dog_path(name).map(|path| DogSource::Adopted {
grants,
path: path.display().to_string(),
channel: cfg.asked_for_channel(name),
events: recorded_events(cfg, name),
})
}
fn recorded_events(cfg: &ShepToml, name: &str) -> Vec<String> {
cfg.grant_record(name)
.ok()
.flatten()
.map(|record| record.events)
.unwrap_or_default()
}
pub(crate) async fn connect_or_absent(
paths: &ShepPaths,
streams: &mut Streams<'_>,
) -> Result<Option<Client>, ExitCode> {
match Client::connect(&paths.socket).await {
Ok(client) => {
refuse_version_skew(streams, &client, VersionGuard::Enforce)?;
Ok(Some(client))
}
Err(ConnectError::Connect { .. }) => Ok(None),
Err(err) => {
let code = ExitCode::from(&err);
Err(streams.fail(
code,
&format!(
"{err}; run `shep {}`",
crate::version_guard::VERSION_SKEW_REMEDY
),
))
}
}
}