use clap::{Parser, Subcommand, ValueEnum, Args};
use clap::{arg, command};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum Method {
Get,
Post,
Put,
Delete,
Patch,
Options,
Head,
Trace,
Connect,
}
#[derive(Args, Debug, Clone)]
pub struct GlobalOptions {
#[arg(long, value_name = "seconds", default_value_t = 30)]
pub timeout: u64,
#[arg(long, help = "Disable automatic response decompression (gzip, deflate, brotli)")]
pub no_decompress: bool,
#[arg(long, help = "Exit with non-zero code if response is not 2xx")]
pub fail: bool,
#[arg(long = "show-headers", global = true, help = "Always print response headers in output (global, use --show-headers)")]
pub show_headers: bool,
#[arg(long, help = "Show detailed error reports (same as ISKRA_ERROR_VERBOSE=1)")]
pub verbose_errors: bool,
}
#[derive(Parser, Debug)]
#[command(
name = "iskra",
version,
author = "Blake Park <blake@example.com>",
about = "\x1b[1;31mIskra\x1b[0m: \x1b[1;31mSecure, modern, Rust-native data transfer tool\x1b[0m",
long_about = "Iskra is a modern, secure, and flexible CLI for HTTP(S) data transfer.\n\n\
Global options can be placed before or after the subcommand and URL.\n\
\n\
Examples:\n\
iskra get https://example.com --timeout 10\n\
iskra --timeout 10 get https://example.com\n\
iskra post https://httpbin.org/post 'hello world' --fail --no-decompress\n\
iskra custom PATCH https://httpbin.org/patch --timeout 5 --verbose-errors\n\
iskra delete https://example.com/resource --fail\n\
\n\
For more details, see: https://github.com/ParkBlake/iskra\n",
help_template = "{about}\n\nUSAGE:\n {usage}\n\nCOMMANDS:\n{commands}\n\nGLOBAL OPTIONS:\n{options}\n"
)]
pub struct Cli {
#[command(flatten)]
pub global: GlobalOptions,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
#[command(trailing_var_arg = true, about = "HTTP request with arbitrary method (e.g. PATCH, OPTIONS, etc.)")]
Custom {
#[arg(value_parser = crate::cli_parser::validate_method)]
method: String,
#[arg(value_parser = crate::cli_parser::validate_url)]
url: String,
#[arg(short = 'H', long, value_parser = crate::cli_parser::parse_header, num_args = 0.., value_name = "key:value", help = "Custom headers to include (per-request, use --header/-H)")]
header: Vec<(String, String)>,
#[arg(short = 'q', long, value_parser = crate::cli_parser::parse_query, num_args = 0.., value_name = "key=value", help = "Query parameters to include")]
query: Vec<(String, String)>,
#[arg(short, long, value_name = "file", value_hint = clap::ValueHint::FilePath, help = "Write response body to file")]
output: Option<String>,
#[arg(long, value_name = "start-end", value_parser = crate::cli_parser::parse_range, help = "Download a byte range")]
range: Option<(u64, Option<u64>)>,
#[arg(long, help = "Resume download if file exists")]
resume: bool,
#[arg(long, value_name = "body", group = "body_input", help = "Request body (alternative to trailing argument)")]
body: Option<String>,
#[arg(value_name = "body", group = "body_input", required = false, help = "Request body as trailing argument")]
trailing_body: Vec<String>,
},
Get {
url: String,
#[arg(short = 'H', long, value_parser = crate::cli_parser::parse_header, num_args = 0.., value_name = "key:value", help = "Custom headers to include (per-request, use --header/-H)")]
header: Vec<(String, String)>,
#[arg(short = 'q', long, value_parser = crate::cli_parser::parse_query, num_args = 0.., value_name = "key=value")]
query: Vec<(String, String)>,
#[arg(short, long, value_name = "file")]
output: Option<String>,
#[arg(long, value_name = "start-end", value_parser = crate::cli_parser::parse_range)]
range: Option<(u64, Option<u64>)>,
#[arg(long, help = "Resume download if file exists")]
resume: bool,
},
Post {
url: String,
body: String,
#[arg(short = 'H', long, value_parser = crate::cli_parser::parse_header, num_args = 0.., value_name = "key:value", help = "Custom headers to include (per-request, use --header/-H)")]
header: Vec<(String, String)>,
#[arg(short = 'q', long, value_parser = crate::cli_parser::parse_query, num_args = 0.., value_name = "key=value")]
query: Vec<(String, String)>,
#[arg(short, long, value_name = "file")]
output: Option<String>,
#[arg(long, value_name = "start-end", value_parser = crate::cli_parser::parse_range)]
range: Option<(u64, Option<u64>)>,
#[arg(long, help = "Resume download if file exists")]
resume: bool,
},
Put {
url: String,
body: String,
#[arg(short = 'H', long, value_parser = crate::cli_parser::parse_header, num_args = 0.., value_name = "key:value", help = "Custom headers to include (per-request, use --header/-H)")]
header: Vec<(String, String)>,
#[arg(short = 'q', long, value_parser = crate::cli_parser::parse_query, num_args = 0.., value_name = "key=value")]
query: Vec<(String, String)>,
#[arg(short, long, value_name = "file")]
output: Option<String>,
#[arg(long, value_name = "start-end", value_parser = crate::cli_parser::parse_range)]
range: Option<(u64, Option<u64>)>,
#[arg(long, help = "Resume download if file exists")]
resume: bool,
},
Delete {
url: String,
#[arg(short = 'H', long, value_parser = crate::cli_parser::parse_header, num_args = 0.., value_name = "key:value")]
header: Vec<(String, String)>,
#[arg(short = 'q', long, value_parser = crate::cli_parser::parse_query, num_args = 0.., value_name = "key=value")]
query: Vec<(String, String)>,
#[arg(short, long, value_name = "file")]
output: Option<String>,
#[arg(long, value_name = "start-end", value_parser = crate::cli_parser::parse_range)]
range: Option<(u64, Option<u64>)>,
#[arg(long, help = "Resume download if file exists")]
resume: bool,
}
}