use clap::Parser; /// scp-like tool for copying (possibly still-growing) pcap capture files. #[derive(Parser, Debug)] #[command(name = "scpcap")] pub struct ClientCli { /// Source: local path or [user@]host:path. If the name ends in the /// partial extension, it's treated as still growing. pub source: String, /// Destination: local path or [user@]host:path. pub dest: String, /// Compress in flight; destination is written as a streaming .pcap.zst. /// Only "zstd" is supported. #[arg(long, value_name = "CODEC", num_args = 0..=1, default_missing_value = "zstd", require_equals = true)] pub compress: Option, /// Target chunk size for compression, e.g. "256k", "1m". #[arg(long, default_value = "256k")] pub chunk: String, /// zstd compression level (lower = faster; default tuned for throughput /// to stay competitive with `scp -C`). #[arg(long, default_value_t = crate::zstd_frame::DEFAULT_LEVEL)] pub level: i32, /// Suffix identifying a still-growing file. #[arg(long, default_value = "partial")] pub extension: String, } /// Remote helper mode, ssh-spawned by the client (like `rsync --server`). /// Not meant to be invoked directly by a user. #[derive(Parser, Debug)] #[command(name = "scpcap --server")] pub struct ServerCli { #[command(flatten)] pub role: ServerRoleFlags, /// Source path to read (--send mode). #[arg(long)] pub source: Option, /// Source's final (non-partial) path, for rename detection (--send mode). #[arg(long)] pub final_source: Option, /// Whether the source is a still-growing (partial) file (--send mode). #[arg(long)] pub partial: bool, /// Compress in flight (--send mode). No adjacent positional args in /// --server mode, so (unlike ClientCli's --compress) there's no /// ambiguity requiring `--compress=zstd` -- `--compress zstd` is fine. #[arg(long, value_name = "CODEC", num_args = 0..=1, default_missing_value = "zstd")] pub compress: Option, /// Target chunk size for compression (--send mode). #[arg(long, default_value = "256k")] pub chunk: String, /// zstd compression level (--send mode). #[arg(long, default_value_t = crate::zstd_frame::DEFAULT_LEVEL)] pub level: i32, /// Destination final path (--recv mode). #[arg(long)] pub dest_final: Option, /// Destination temp path, written while in flight (--recv mode). #[arg(long)] pub dest_temp: Option, } /// The clap-facing shape of the role choice: two mutually exclusive, /// individually-long-named flags, matching rsync's `--server --send`/ /// `--server --recv` convention. Clap's derive has no direct way to map two /// separate boolean flags onto one enum field, so this stays a flag pair at /// the parsing boundary and converts to `ServerRole` via `role()` below. #[derive(clap::Args, Debug)] #[group(required = true, multiple = false)] pub struct ServerRoleFlags { #[arg(long)] pub send: bool, #[arg(long)] pub recv: bool, } /// The role a `--server` process plays, for the rest of the code to match on /// instead of checking two booleans. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ServerRole { Send, Recv, } impl ServerCli { /// The `--group(required, exclusive)` on `ServerRoleFlags` guarantees /// exactly one of `send`/`recv` is set by the time parsing succeeds. pub fn role(&self) -> ServerRole { if self.role.send { ServerRole::Send } else { ServerRole::Recv } } }