use std::path::PathBuf;
use clap::Parser;
mod commands;
mod parse;
mod types;
pub(crate) use commands::Commands;
pub(crate) use types::{
AnsiMode, AutostartCommands, AutostartMode, ConfigFormat, EventsFormat, GenerateCommands,
OutputFormat, RmiScope,
};
const HELP_STYLES: clap::builder::Styles = clap::builder::Styles::plain()
.header(clap::builder::styling::Style::new().bold().underline())
.usage(clap::builder::styling::Style::new().bold())
.literal(clap::builder::styling::Style::new().bold())
.placeholder(clap::builder::styling::Style::new().dimmed())
.error(clap::builder::styling::AnsiColor::Red.on_default().bold())
.valid(clap::builder::styling::Style::new().bold())
.invalid(clap::builder::styling::AnsiColor::Red.on_default())
.context(clap::builder::styling::Style::new());
pub(crate) fn ansi_from_argv<I: Iterator<Item = String>>(args: I) -> Option<AnsiMode> {
let mut args = args.peekable();
while let Some(arg) = args.next() {
if arg == "--" {
return None;
}
let value = if let Some(v) = arg.strip_prefix("--ansi=") {
v.to_string()
} else if arg == "--ansi" {
args.next()?
} else {
continue;
};
return match value.as_str() {
"auto" => Some(AnsiMode::Auto),
"always" => Some(AnsiMode::Always),
"never" => Some(AnsiMode::Never),
_ => None,
};
}
None
}
#[derive(Parser)]
#[command(
name = "podup",
// clap renders `--version` as `{name} {version}`, so the derived default gave
// `podup 3.3.0` while the `version` subcommand gave `podup version v3.3.0` —
// two answers to the same question, and neither caller can tell which one it
// is going to get. Measured on docker-compose v5.1.3: `version` and
// `--version` are byte-identical (`Docker Compose version v5.1.3`) and only
// `--short` drops the `v`. Folding the word and the prefix into the version
// string is what makes clap emit the same line the subcommand does; the
// subcommand still owns `--short` and `--format json`.
version = concat!("version v", env!("CARGO_PKG_VERSION")),
about = "Run Compose projects on Podman.",
styles = HELP_STYLES,
// No subcommand prints help and exits non-zero (like docker compose), and the
// built-in `help` is replaced by an explicit `Help` variant that tolerates
// extra tokens, `-h`/`--help`, and a leading `--`.
arg_required_else_help = true,
disable_help_subcommand = true
)]
pub(crate) struct Cli {
#[arg(short, long)]
pub(crate) file: Vec<PathBuf>,
#[arg(
short,
long,
visible_alias = "project-name",
env = "COMPOSE_PROJECT_NAME"
)]
pub(crate) project: Option<String>,
#[arg(long, env = "PODMAN_SOCKET", global = true)]
pub(crate) socket: Option<String>,
#[arg(long, value_delimiter = ',', env = "COMPOSE_PROFILES", global = true)]
pub(crate) profile: Vec<String>,
#[arg(long, global = true)]
pub(crate) project_directory: Option<PathBuf>,
#[arg(long = "env-file", global = true)]
pub(crate) env_file: Vec<String>,
#[arg(long, value_enum, default_value_t = AnsiMode::Auto, global = true)]
pub(crate) ansi: AnsiMode,
#[command(subcommand)]
pub(crate) command: Commands,
}
#[cfg(test)]
mod tests {
use super::{ansi_from_argv, AnsiMode};
fn argv(args: &[&str]) -> impl Iterator<Item = String> + use<> {
args.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.into_iter()
}
#[test]
fn ansi_is_found_before_the_subcommand() {
assert_eq!(
ansi_from_argv(argv(&["podup", "--ansi", "never", "--help"])),
Some(AnsiMode::Never)
);
assert_eq!(
ansi_from_argv(argv(&["podup", "--ansi=always", "up"])),
Some(AnsiMode::Always)
);
}
#[test]
fn ansi_is_found_after_the_subcommand() {
assert_eq!(
ansi_from_argv(argv(&["podup", "up", "--ansi", "never"])),
Some(AnsiMode::Never)
);
}
#[test]
fn absent_ansi_yields_none() {
assert_eq!(ansi_from_argv(argv(&["podup", "up", "-d"])), None);
assert_eq!(ansi_from_argv(argv(&["podup", "--ansi"])), None);
assert_eq!(ansi_from_argv(argv(&["podup", "--ansi", "sideways"])), None);
}
#[test]
fn ansi_after_a_double_dash_is_not_ours() {
assert_eq!(
ansi_from_argv(argv(&["podup", "exec", "svc", "--", "--ansi", "always"])),
None
);
assert_eq!(
ansi_from_argv(argv(&[
"podup",
"exec",
"svc",
"--",
"sh",
"-c",
"--ansi=never"
])),
None
);
}
#[test]
fn ansi_before_a_double_dash_is_ours() {
assert_eq!(
ansi_from_argv(argv(&[
"podup", "--ansi", "never", "exec", "svc", "--", "true"
])),
Some(AnsiMode::Never)
);
}
}