use clish::help::{AppStyle, AppStyles};
use clish::prelude::*;
#[command(
param(target, help = "Target triple", placeholder = "TRIPLE", value_hint = "string"),
param(opt_level, help = "Optimization level", short = 'O',
choices = ["0", "1", "2", "3", "s", "z"], default = "2",
details = "Controls code generation optimization. \
's' optimizes for binary size, \
'z' optimizes even more aggressively for size."),
param(jobs, help = "Parallel compilation jobs", short = 'j', placeholder = "N"),
param(release, help = "Release mode", short = 'r'),
param(debug, help = "Include debug symbols", short = 'g',
conflicts_with = ["release"]),
param(verbose, help = "Verbose output", short = 'v'),
param(checksum, help = "Write SHA-256 checksum file", name = "gen-checksum"),
)]
fn build(
target: Pos<String>,
opt_level: Named<String>,
jobs: Named<Option<u32>>,
release: bool,
debug: bool,
verbose: bool,
checksum: bool,
) {
println!("Building for target: {target}");
if release {
println!(" Mode: release");
}
if debug {
println!(" Debug symbols: enabled");
}
println!(" Optimization: -O{opt_level}");
if let Some(j) = jobs {
println!(" Parallel jobs: {j}");
}
if verbose {
println!(" Verbose: on");
}
if checksum {
println!(" Writing checksum: {target}.sha256");
}
println!(" Build complete.");
}
#[command(
param(patterns, help = "Test filter patterns (repeatable)", short = 't',
placeholder = "PATTERN"),
param(format, help = "Output format", short = 'f',
choices = ["pretty", "compact", "junit", "tap"], default = "pretty",
env = "PL_TEST_FORMAT"),
param(junit_out, help = "JUnit report path", name = "junit-output",
placeholder = "PATH", value_hint = "file"),
param(coverage, help = "Collect code coverage", short = 'c'),
param(jobs, help = "Parallel test jobs", short = 'j', placeholder = "N"),
param(quiet, help = "Suppress progress output", short = 'q'),
param(verbose, help = "Verbose test output", short = 'v'),
)]
fn test(
patterns: Named<Vec<String>>,
format: Named<String>,
junit_out: Named<Option<String>>,
coverage: bool,
jobs: Named<Option<u16>>,
quiet: bool,
verbose: bool,
) {
if !patterns.is_empty() {
println!("Test patterns: {}", patterns.join(", "));
}
println!("Format: {format}");
if let Some(path) = junit_out {
println!("JUnit output: {path}");
}
if coverage {
println!("Coverage: enabled");
}
if let Some(j) = jobs {
println!("Parallel jobs: {j}");
}
if quiet {
println!("Quiet mode: on");
}
if verbose {
println!("Verbose: on");
}
println!("All tests passed.");
}
#[command(
param(targets, help = "Target environments (variadic)", placeholder = "ENV"),
param(region, help = "AWS region", default = "us-east-1",
short = 'r', placeholder = "REGION"),
param(strategy, help = "Deployment strategy",
short = 's', choices = ["rolling", "bluegreen", "recreate"],
default = "rolling"),
param(port, help = "Application port", short = 'p'),
param(tag, help = "Container image tag", name = "tag", default = "latest"),
param(canary, help = "Canary percentage", short = 'c',
placeholder = "PCT", requires = ["rollback"]),
param(rollback, help = "Rollback timeout (seconds)",
name = "rollback-timeout", short = 'b'),
param(dry_run, help = "Validate without deploying", name = "dry-run",
short = 'n'),
param(verbose, help = "Verbose output", short = 'v'),
)]
fn deploy(
targets: Pos<Vec<String>>,
region: Named<String>,
strategy: Named<String>,
port: Named<Option<u16>>,
tag: Named<String>,
canary: Named<Option<u8>>,
rollback: Named<Option<u16>>,
dry_run: bool,
verbose: bool,
) {
println!("Deploying to: {}", targets.join(", "));
println!(" Region: {region}");
println!(" Strategy: {strategy}");
println!(" Image tag: {tag}");
if let Some(p) = port {
println!(" Port: {p}");
}
if let Some(c) = canary {
println!(" Canary: {c}%");
}
if let Some(rb) = rollback {
println!(" Rollback timeout: {rb}s");
}
if verbose {
println!(" Verbose: on");
}
if dry_run {
println!(" (dry run — no changes made)");
}
if !dry_run {
println!(" Deploy complete.");
}
}
#[command(
deprecated = true,
deprecation_note = "use the 'pl config --cli' subcommands instead",
aliases = ["conf", "cfg"],
param(key, help = "Configuration key", placeholder = "KEY"),
param(value, help = "Configuration value (omit to read)", placeholder = "VALUE"),
param(file, help = "Config file path", short = 'f', hide = true),
param(port, help = "Config service port", short = 'p'),
)]
fn config(
key: Pos<String>,
value: Pos<Option<String>>,
file: Named<Option<String>>,
port: Named<Option<u16>>,
) {
if let Some(v) = value {
println!("Setting config: {key} = {v}");
} else {
println!("Config {key} = (stored value)");
}
if let Some(f) = file {
println!(" File: {f}");
}
if let Some(p) = port {
println!(" Port: {p}");
}
}
#[command(
help = "Run a command in a container",
details = "Executes the given command inside a fresh \
container using the specified image. All arguments \
after the first positional are treated as the command \
and its arguments. Use -- to separate container \
options from the command.",
param(command, help = "Command and arguments (use -- to separate)",
placeholder = "CMD"),
param(image, help = "Container image", default = "alpine:latest",
short = 'i', placeholder = "IMAGE"),
param(timeout, help = "Execution timeout (seconds)", short = 't',
placeholder = "SECS",
details = "Kills the container if execution exceeds this limit. \
No timeout is applied when omitted."),
param(label, help = "Container labels (repeatable)", short = 'l',
placeholder = "KEY=VAL"),
param(env_var, help = "Environment variables (repeatable)",
name = "env", short = 'e', placeholder = "VAR=VAL"),
param(detach, help = "Run in background", short = 'd',
conflicts_with = ["wait"]),
param(wait, help = "Wait for completion", short = 'w'),
param(tty, help = "Allocate a pseudo-TTY", short = 'T'),
param(verbose, help = "Verbose output", short = 'v'),
)]
fn exec(
command: Pos<Vec<String>>,
image: Named<String>,
timeout: Named<Option<u32>>,
label: Named<Vec<String>>,
env_var: Named<Vec<String>>,
detach: bool,
wait: bool,
tty: bool,
verbose: bool,
) {
let cmd_str = command.join(" ");
println!("Executing: {cmd_str}");
println!(" Image: {image}");
if let Some(t) = timeout {
println!(" Timeout: {t}s");
}
for l in &label {
println!(" Label: {l}");
}
for e in &env_var {
println!(" Env: {e}");
}
if detach {
println!(" Background: yes");
}
if wait {
println!(" Wait: yes");
}
if tty {
println!(" TTY: allocated");
}
if verbose {
println!(" Verbose: on");
}
println!(" Exit code: 0");
}
#[command(
hidden = true,
aliases = ["execute", "invoke"],
param(script, help = "Script to run", placeholder = "SCRIPT"),
param(timeout, help = "Execution timeout (minutes)", placeholder = "MINS"),
param(log_file, help = "Log output path", name = "log-file",
placeholder = "PATH", value_hint = "file",
details = "Writes all output to the specified file \
in addition to stdout. The file is created \
if it does not exist."),
param(retries, help = "Retry on failure count", short = 'r'),
param(verbose, help = "Verbose output", short = 'v'),
param(env_var, help = "Environment variables (repeatable)",
name = "env", short = 'e', placeholder = "VAR=VAL"),
)]
fn run(
script: Pos<String>,
timeout: Pos<Option<u8>>,
log_file: Named<Option<String>>,
retries: Named<Option<u16>>,
verbose: bool,
env_var: Named<Vec<String>>,
) {
println!("Running script: {script}");
if let Some(t) = timeout {
println!(" Timeout: {t} min");
}
if let Some(lf) = log_file {
println!(" Log file: {lf}");
}
if let Some(r) = retries {
println!(" Retries: {r}");
}
if verbose {
println!(" Verbose: on");
}
for e in &env_var {
println!(" Env: {e}");
}
}
fn main() {
app!()
.name("pl")
.version("1.0.0")
.description("Pipeline DevOps toolkit")
.details("A feature-rich CLI for building, testing, deploying, \
and managing pipeline workflows across environments.")
.styles(AppStyles {
header: AppStyle::Markup("[bold cyan underline]"),
brand: AppStyle::Markup("[bold magenta]"),
..Default::default()
})
.run();
}