summaryrefslogtreecommitdiff
path: root/src/cli.rs
diff options
context:
space:
mode:
authorDawid Rycerz <dawid@rycerz.xyz>2026-01-22 22:07:32 +0100
committerDawid Rycerz <dawid@rycerz.xyz>2026-02-10 18:44:26 +0100
commit064a1d01c5c14f5ecc032fa9b8346a4a88b893f6 (patch)
treea2023f9ccd297ed8a41a3a0cc5699c2add09244d /src/cli.rs
witryna 0.1.0 — initial releasev0.1.0
Minimalist Git-based static site deployment orchestrator. Webhook-triggered builds in Podman/Docker containers with atomic symlink publishing, SIGHUP hot-reload, and zero-downtime deploys. See README.md for usage, CHANGELOG.md for details.
Diffstat (limited to 'src/cli.rs')
-rw-r--r--src/cli.rs134
1 files changed, 134 insertions, 0 deletions
diff --git a/src/cli.rs b/src/cli.rs
new file mode 100644
index 0000000..ab191a4
--- /dev/null
+++ b/src/cli.rs
@@ -0,0 +1,134 @@
+use clap::{Parser, Subcommand};
+use std::path::PathBuf;
+
+/// Witryna - minimalist Git-based static site deployment orchestrator
+#[derive(Debug, Parser)]
+#[command(
+ name = "witryna",
+ version,
+ author,
+ about = "Minimalist Git-based static site deployment orchestrator",
+ long_about = "Minimalist Git-based static site deployment orchestrator.\n\n\
+ Witryna listens for webhook HTTP requests, pulls the corresponding Git \
+ repository (with automatic Git LFS fetch and submodule initialization), \
+ runs a user-defined build command inside an ephemeral container and \
+ publishes the resulting assets via atomic symlink switching.\n\n\
+ A health-check endpoint is available at GET /health (returns 200 OK).\n\n\
+ Witryna does not serve files, terminate TLS, or manage DNS. \
+ It is designed to sit behind a reverse proxy (Nginx, Caddy, etc.).",
+ subcommand_required = true,
+ arg_required_else_help = true
+)]
+pub struct Cli {
+ /// Path to the configuration file.
+ /// If not specified, searches: ./witryna.toml, $XDG_CONFIG_HOME/witryna/witryna.toml, /etc/witryna/witryna.toml
+ #[arg(long, global = true, value_name = "FILE")]
+ pub config: Option<PathBuf>,
+
+ #[command(subcommand)]
+ pub command: Command,
+}
+
+#[derive(Debug, Subcommand)]
+pub enum Command {
+ /// Start the deployment server (foreground)
+ Serve,
+ /// Validate configuration file and print summary
+ Validate,
+ /// Trigger a one-off build for a site (synchronous, no server)
+ Run {
+ /// Site name (as defined in witryna.toml)
+ site: String,
+ /// Stream full build output to stderr in real-time
+ #[arg(long, short)]
+ verbose: bool,
+ },
+ /// Show deployment status for configured sites
+ Status {
+ /// Show last 10 deployments for a single site
+ #[arg(long, short)]
+ site: Option<String>,
+ /// Output in JSON format
+ #[arg(long)]
+ json: bool,
+ },
+}
+
+#[cfg(test)]
+#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn run_parses_site_name() {
+ let cli = Cli::try_parse_from(["witryna", "run", "my-site"]).unwrap();
+ match cli.command {
+ Command::Run { site, verbose } => {
+ assert_eq!(site, "my-site");
+ assert!(!verbose);
+ }
+ _ => panic!("expected Run command"),
+ }
+ }
+
+ #[test]
+ fn run_parses_verbose_flag() {
+ let cli = Cli::try_parse_from(["witryna", "run", "my-site", "--verbose"]).unwrap();
+ match cli.command {
+ Command::Run { site, verbose } => {
+ assert_eq!(site, "my-site");
+ assert!(verbose);
+ }
+ _ => panic!("expected Run command"),
+ }
+ }
+
+ #[test]
+ fn status_parses_without_flags() {
+ let cli = Cli::try_parse_from(["witryna", "status"]).unwrap();
+ match cli.command {
+ Command::Status { site, json } => {
+ assert!(site.is_none());
+ assert!(!json);
+ }
+ _ => panic!("expected Status command"),
+ }
+ }
+
+ #[test]
+ fn status_parses_site_filter() {
+ let cli = Cli::try_parse_from(["witryna", "status", "--site", "my-site"]).unwrap();
+ match cli.command {
+ Command::Status { site, json } => {
+ assert_eq!(site.as_deref(), Some("my-site"));
+ assert!(!json);
+ }
+ _ => panic!("expected Status command"),
+ }
+ }
+
+ #[test]
+ fn status_parses_json_flag() {
+ let cli = Cli::try_parse_from(["witryna", "status", "--json"]).unwrap();
+ match cli.command {
+ Command::Status { site, json } => {
+ assert!(site.is_none());
+ assert!(json);
+ }
+ _ => panic!("expected Status command"),
+ }
+ }
+
+ #[test]
+ fn config_flag_is_optional() {
+ let cli = Cli::try_parse_from(["witryna", "status"]).unwrap();
+ assert!(cli.config.is_none());
+ }
+
+ #[test]
+ fn config_flag_explicit_path() {
+ let cli =
+ Cli::try_parse_from(["witryna", "--config", "/etc/witryna.toml", "status"]).unwrap();
+ assert_eq!(cli.config, Some(PathBuf::from("/etc/witryna.toml")));
+ }
+}