mod dogs;
#[cfg(test)]
mod dogs_grant_tests;
mod flock;
mod help;
mod hosts;
mod import;
mod kv;
mod logs;
mod settings;
mod sheep;
mod store;
mod system;
#[cfg(test)]
mod unset_tests;
mod verbs;
use std::path::PathBuf;
use shep_core::destination::Destination;
use help::version_text;
pub(crate) use help::{HELP_TEMPLATE, dress_help_template};
pub use dogs::{AdoptArgs, DisableArgs, DogArgs, DogsArgs, DogsCommand, EnableArgs};
pub use flock::{FlockArgs, FoldArgs, LookoutArgs};
pub use hosts::{HostArgs, HostCommand};
pub use import::{ImportArgs, ImportCommand, ImportEnvArgs, ImportPm2Args};
pub use kv::{KvArgs, KvCommand, KvGetArgs, KvSetArgs, KvUnsetArgs};
pub use logs::{BarksArgs, BleatsArgs, FlushArgs, ReopenArgs};
#[allow(unused_imports)]
pub(crate) use flock::FOLLOW_INTERVAL_FLOOR_SECONDS;
#[allow(unused_imports)]
pub use logs::DEFAULT_BLEAT_LINES;
pub use settings::{GetArgs, SetArgs, UnsetArgs};
pub use sheep::{
CopyArgs, SelectorArgs, ServeArgs, SignalArgs, StartArgs, StockArgs, TriggerArgs, WhisperArgs,
};
#[allow(unused_imports)]
pub use sheep::ResetMode;
pub use store::{SecretArgs, SecretCommand};
pub use system::{
CompletionArgs, DaemonArgs, DaemonCmd, DevArgs, DialStdioArgs, InitArgs, RuntimeArgs,
StartupArgs, StyleArgs,
};
pub use verbs::Commands;
#[derive(Debug, clap::Parser)]
#[command(
name = "shep",
bin_name = "shep",
version,
long_version = version_text(),
about = "A process manager for your flock",
propagate_version = true,
help_template = HELP_TEMPLATE,
after_help = "Run `shep help <command>` for one command, or `shep welcome` for the tour."
)]
pub struct Cli {
#[command(flatten)]
pub global: GlobalArgs,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Debug, clap::Args)]
pub struct GlobalArgs {
#[arg(long, global = true, value_enum, default_value_t = Format::Table)]
pub format: Format,
#[arg(short, long, global = true)]
pub quiet: bool,
#[arg(long, global = true, value_enum)]
pub style: Option<crate::style::StyleLevel>,
#[arg(long, global = true, value_name = "DEST", value_parser = parse_destination)]
pub host: Option<String>,
#[arg(long, global = true, env = "SHEP_HOME")]
pub home: Option<PathBuf>,
}
fn parse_destination(raw: &str) -> Result<String, String> {
Destination::parse(raw)
.map(|dest| dest.as_str().to_owned())
.map_err(|err| err.to_string())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum Format {
Table,
Json,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
#[clap(rename_all = "kebab-case")]
pub enum Init {
Systemd,
Openrc,
Launchd,
FreebsdRc,
OpenbsdRc,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn format_defaults_to_table_and_accepts_json() {
use clap::Parser;
let cli = Cli::try_parse_from(["shep", "flock"]).unwrap();
assert_eq!(cli.global.format, Format::Table);
let cli = Cli::try_parse_from(["shep", "--format", "json", "flock"]).unwrap();
assert_eq!(cli.global.format, Format::Json);
}
#[test]
fn style_flag_defaults_to_unset_and_accepts_the_three_levels() {
use crate::style::StyleLevel;
use clap::Parser;
let cli = Cli::try_parse_from(["shep", "flock"]).unwrap();
assert_eq!(cli.global.style, None);
for (raw, expected) in [
("full", StyleLevel::Full),
("plain", StyleLevel::Plain),
("bare", StyleLevel::Bare),
] {
let cli = Cli::try_parse_from(["shep", "--style", raw, "flock"]).unwrap();
assert_eq!(cli.global.style, Some(expected), "--style {raw}");
}
assert!(Cli::try_parse_from(["shep", "--style", "loud", "flock"]).is_err());
}
#[test]
fn host_flag_defaults_to_unset_and_takes_a_destination() {
use clap::Parser;
let cli = Cli::try_parse_from(["shep", "flock"]).unwrap();
assert_eq!(cli.global.host, None);
for argv in [
vec!["shep", "--host", "amp2", "flock"],
vec!["shep", "--host=amp2", "flock"],
vec!["shep", "flock", "--host", "amp2"],
vec!["shep", "flock", "--host=amp2"],
] {
let cli = Cli::try_parse_from(&argv).unwrap();
assert_eq!(cli.global.host.as_deref(), Some("amp2"), "{argv:?}");
}
let cli = Cli::try_parse_from(["shep", "--host", "deploy@amp2", "flock"]).unwrap();
assert_eq!(cli.global.host.as_deref(), Some("deploy@amp2"));
}
#[test]
fn host_flag_refuses_a_destination_ssh_would_read_as_an_option() {
use clap::Parser;
for argv in [
vec!["shep", "--host=-oProxyCommand=x", "flock"],
vec!["shep", "--host", "", "flock"],
vec!["shep", "--host", "a b", "flock"],
] {
let err = Cli::try_parse_from(&argv).expect_err(&format!("{argv:?}"));
assert_eq!(
err.kind(),
clap::error::ErrorKind::ValueValidation,
"{argv:?}"
);
}
}
#[test]
fn home_flag_is_wired_to_the_shep_home_env_var() {
use clap::CommandFactory;
let cmd = Cli::command();
let home_arg = cmd
.get_arguments()
.find(|a| a.get_id().as_str() == "home")
.expect("GlobalArgs::home must still be a flattened argument named `home`");
assert_eq!(home_arg.get_env(), Some(std::ffi::OsStr::new("SHEP_HOME")));
}
}