use std::time::Duration;
use shep_client::Client;
use shep_core::protocol::{ProcessInfo, Request, Response, SelectorSpec};
use shep_core::selector::ProcessSelector;
use crate::cli::Format;
use crate::commands::lifecycle::resolve::{TargetError, target_exit_code};
use crate::commands::rpc::{client_error, deadline_for, unexpected_response};
use crate::commands::selector::parse_selector_spec;
use crate::exit::ExitCode;
use crate::output::{Render, Streams, emit, emit_flock, write_outcome};
pub(crate) fn parse_selectors(
streams: &mut Streams<'_>,
raw: &[String],
) -> Result<Vec<SelectorSpec>, ExitCode> {
let mut parsed = Vec::with_capacity(raw.len());
for one in raw {
parsed.push(parse_selector_spec(streams, one)?);
}
Ok(parsed)
}
pub(crate) async fn request_each<I, B, F>(
client: &Client,
streams: &mut Streams<'_>,
selectors: &[SelectorSpec],
deadline: Option<Duration>,
body: B,
mut extract: F,
) -> (Vec<I>, Option<ExitCode>)
where
B: Fn(SelectorSpec) -> Request,
F: FnMut(Response) -> Option<Vec<I>>,
{
let mut collected = Vec::new();
let mut failure: Option<ExitCode> = None;
for selector in selectors {
let request = body(selector.clone());
let deadline = deadline.or_else(|| deadline_for(&request));
match client.request_with_deadline(request, deadline).await {
Ok(response) => match extract(response) {
Some(mut rows) => collected.append(&mut rows),
None => failure = failure.or(Some(unexpected_response(streams))),
},
Err(err) => failure = failure.or(Some(client_error(streams, &err))),
}
}
(collected, failure)
}
pub(crate) fn flock_matches(selector: &ProcessSelector, flock: &[ProcessInfo]) -> Vec<ProcessInfo> {
let sheep_only = |flock: &[ProcessInfo], keep: &dyn Fn(&ProcessInfo) -> bool| {
flock
.iter()
.filter(|info| info.dog.is_none() && info.disabled != Some(true))
.filter(|info| keep(info))
.cloned()
.collect::<Vec<ProcessInfo>>()
};
match selector {
ProcessSelector::Name(wanted) => {
let named: Vec<ProcessInfo> = flock
.iter()
.filter(|info| &info.name == wanted)
.cloned()
.collect();
if !named.is_empty() {
return named;
}
sheep_only(flock, &|info| info.fold.as_deref() == Some(wanted.as_str()))
}
exact if exact.is_exact() => flock
.iter()
.filter(|info| exact.matches(&info.name, info.id, info.fold.as_deref(), info.instance))
.cloned()
.collect(),
wildcard => sheep_only(flock, &|info| {
wildcard.matches(&info.name, info.id, info.fold.as_deref(), info.instance)
}),
}
}
pub(crate) fn selector_miss(
target: &str,
selector: &ProcessSelector,
flock: &[ProcessInfo],
) -> Option<String> {
match selector {
ProcessSelector::All if flock.iter().any(|info| info.disabled == Some(true)) => Some(
"every sheep in the flock is disabled; `shep enable all` brings them back".to_string(),
),
ProcessSelector::All if flock.iter().any(|info| info.dog.is_some()) => Some(
"no sheep in the flock; there is nothing to start. The dogs listed \
by `shep dogs` are not sheep and `all` never reaches them"
.to_string(),
),
ProcessSelector::All => Some("the flock is empty; there is nothing to start".to_string()),
ProcessSelector::Fold(fold) => Some(format!("no sheep is in a fold called {fold}")),
ProcessSelector::Regex(_) => Some(format!("no sheep matched {target}")),
ProcessSelector::Instance { name, slot } => {
Some(format!("no instance {slot} of {name} is registered"))
}
ProcessSelector::Name(_) | ProcessSelector::Id(_) => None,
}
}
pub(crate) fn is_reachable_as_a_name(selector: &ProcessSelector) -> bool {
match selector {
ProcessSelector::Name(name) => !name.contains(['/', '\\']) && name != "." && name != "..",
_ => true,
}
}
pub(crate) async fn render_outcome<T: Render>(
client: &Client,
streams: &mut Streams<'_>,
command: &str,
narrow: T,
) -> ExitCode {
if streams.fmt == Format::Json {
return write_outcome(emit(
&mut *streams.out,
streams.fmt,
command,
narrow,
streams.style,
));
}
let listing = flock_now(client).await;
write_outcome(emit_flock(
&mut *streams.out,
streams.fmt,
command,
listing,
None,
streams.style,
))
}
pub(crate) fn fail_target(streams: &mut Streams<'_>, err: &TargetError) -> ExitCode {
let code = target_exit_code(err);
streams.fail(code, &err.to_string())
}
pub(crate) async fn flock_now(client: &Client) -> Vec<shep_core::protocol::ProcessInfo> {
match client.request(Request::ListFlock).await {
Ok(Response::Flock(procs)) => procs,
_ => Vec::new(),
}
}