use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;
use clap::ArgMatches;
use clap::builder::Styles;
use clap::builder::styling::{AnsiColor, Effects};
use hurl::pretty::PrettyMode;
use hurl::runner::Value;
use hurl_core::input::Input;
use hurl_core::types::{BytesPerSec, Count, DurationUnit};
use super::context::RunContext;
use super::variables::TypeKind;
use super::variables_file::VariablesFile;
use super::{CliOptions, commands, duration, get_version, secret};
use super::{CliOptionsError, ErrorFormat, HttpVersion, IpResolve, Output, variables};
use super::{OutputType, Verbosity};
pub fn parse_cli_args(
context: &RunContext,
default_options: CliOptions,
) -> Result<CliOptions, CliOptionsError> {
let styles = Styles::styled()
.header(AnsiColor::Green.on_default() | Effects::BOLD)
.usage(AnsiColor::Green.on_default() | Effects::BOLD)
.literal(AnsiColor::Cyan.on_default() | Effects::BOLD)
.placeholder(AnsiColor::Cyan.on_default());
let mut command = clap::Command::new("hurl")
.version(get_version())
.disable_colored_help(true)
.styles(styles)
.about("Hurl, run and test HTTP requests with plain text")
.arg(commands::aws_sigv4())
.arg(commands::cacert_file())
.arg(commands::client_cert_file())
.arg(commands::compressed())
.arg(commands::connect_timeout())
.arg(commands::connect_to())
.arg(commands::digest())
.arg(commands::header())
.arg(commands::http10())
.arg(commands::http11())
.arg(commands::http2())
.arg(commands::http3())
.arg(commands::input_files())
.arg(commands::insecure())
.arg(commands::ipv4())
.arg(commands::ipv6())
.arg(commands::client_key_file())
.arg(commands::limit_rate())
.arg(commands::follow_location())
.arg(commands::follow_location_trusted())
.arg(commands::max_filesize())
.arg(commands::max_redirects())
.arg(commands::max_time())
.arg(commands::negotiate())
.arg(commands::no_cookie_store())
.arg(commands::no_proxy())
.arg(commands::ntlm())
.arg(commands::path_as_is())
.arg(commands::pinned_pub_key())
.arg(commands::proxy())
.arg(commands::resolve())
.arg(commands::ssl_no_revoke())
.arg(commands::unix_socket())
.arg(commands::user())
.arg(commands::user_agent())
.arg(commands::color())
.arg(commands::curl())
.arg(commands::error_format())
.arg(commands::include())
.arg(commands::json())
.arg(commands::no_color())
.arg(commands::no_output())
.arg(commands::no_pretty())
.arg(commands::output())
.arg(commands::pretty())
.arg(commands::progress_bar())
.arg(commands::verbose())
.arg(commands::very_verbose())
.arg(commands::verbosity())
.arg(commands::continue_on_error())
.arg(commands::delay())
.arg(commands::from_entry())
.arg(commands::jobs())
.arg(commands::no_assert())
.arg(commands::parallel())
.arg(commands::repeat())
.arg(commands::retry())
.arg(commands::retry_interval())
.arg(commands::secret())
.arg(commands::secrets_file())
.arg(commands::test())
.arg(commands::to_entry())
.arg(commands::variable())
.arg(commands::variables_file())
.arg(commands::report_html())
.arg(commands::report_json())
.arg(commands::report_junit())
.arg(commands::report_tap())
.arg(commands::cookies_input_file())
.arg(commands::cookies_output_file())
.arg(commands::file_root())
.arg(commands::glob())
.arg(commands::netrc())
.arg(commands::netrc_file())
.arg(commands::netrc_optional());
let arg_matches = command.try_get_matches_from_mut(std::env::args_os());
let arg_matches = match arg_matches {
Ok(args) => args,
Err(error) => {
return Err(CliOptionsError::from_clap(
error,
default_options.color_stdout,
));
}
};
if !has_input_files(&arg_matches, context) {
let help = if default_options.color_stdout {
command.render_help().ansi().to_string()
} else {
command.render_help().to_string()
};
return Err(CliOptionsError::NoInput(help));
}
let options = parse_arg_matches(&arg_matches, context, default_options)?;
if options.input_files.is_empty() {
return Err(CliOptionsError::Error(
"No input files provided".to_string(),
));
}
Ok(options)
}
fn parse_arg_matches(
arg_matches: &ArgMatches,
context: &RunContext,
default_options: CliOptions,
) -> Result<CliOptions, CliOptionsError> {
let aws_sigv4 = aws_sigv4(arg_matches, default_options.aws_sigv4);
let cacert_file = cacert_file(arg_matches, default_options.cacert_file)?;
let client_cert_file = client_cert_file(arg_matches, default_options.client_cert_file)?;
let client_key_file = client_key_file(arg_matches, default_options.client_key_file)?;
let color_stdout = color(arg_matches, default_options.color_stdout);
let color_stderr = color(arg_matches, default_options.color_stderr);
let compressed = compressed(arg_matches, default_options.compressed);
let connect_timeout = connect_timeout(arg_matches, default_options.connect_timeout)?;
let connects_to = connects_to(arg_matches, default_options.connects_to);
let continue_on_error = continue_on_error(arg_matches, default_options.continue_on_error);
let cookie_input_file = cookie_input_file(arg_matches, default_options.cookie_input_file);
let cookie_output_file = cookie_output_file(arg_matches, default_options.cookie_output_file);
let curl_file = curl_file(arg_matches, default_options.curl_file);
let delay = delay(arg_matches, default_options.delay)?;
let digest = digest(arg_matches, default_options.digest);
let error_format = error_format(arg_matches, default_options.error_format)?;
let file_root = file_root(arg_matches, default_options.file_root);
let follow_location = follow_location(arg_matches, default_options.follow_location);
let follow_location_trusted =
follow_location_trusted(arg_matches, default_options.follow_location_trusted);
let from_entry = from_entry(arg_matches, default_options.from_entry);
let headers = headers(arg_matches, default_options.headers);
let html_dir = html_dir(arg_matches, default_options.html_dir)?;
let http_version = http_version(arg_matches, default_options.http_version);
let no_assert = no_assert(arg_matches, default_options.no_assert);
let include = include(arg_matches, default_options.include);
let input_files = input_files(arg_matches, context)?;
let insecure = insecure(arg_matches, default_options.insecure);
let ip_resolve = ip_resolve(arg_matches, default_options.ip_resolve);
let jobs = jobs(arg_matches, default_options.jobs);
let json_report_dir = json_report_dir(arg_matches, default_options.json_report_dir)?;
let junit_file = junit_file(arg_matches, default_options.junit_file);
let limit_rate = limit_rate(arg_matches, default_options.limit_rate);
let max_filesize = max_filesize(arg_matches, default_options.max_filesize);
let max_redirect = max_redirect(arg_matches, default_options.max_redirect)?;
let negotiate = negotiate(arg_matches, default_options.negotiate);
let netrc = netrc(arg_matches, default_options.netrc);
let netrc_file = netrc_file(arg_matches, default_options.netrc_file)?;
let netrc_optional = netrc_optional(arg_matches, default_options.netrc_optional);
let no_cookie_store = no_cookie_store(arg_matches, default_options.no_cookie_store);
let no_proxy = no_proxy(arg_matches, default_options.no_proxy);
let ntlm = ntlm(arg_matches, default_options.ntlm);
let parallel = parallel(arg_matches, default_options.parallel);
let path_as_is = path_as_is(arg_matches, default_options.path_as_is);
let pinned_pub_key = pinned_pub_key(arg_matches, default_options.pinned_pub_key);
let progress_bar = progress_bar(arg_matches, context, default_options.progress_bar);
let pretty = pretty(arg_matches, default_options.pretty);
let proxy = proxy(arg_matches, default_options.proxy);
let output = output(arg_matches, default_options.output);
let output_type = output_type(arg_matches, default_options.output_type);
let repeat = repeat(arg_matches, default_options.repeat)?;
let resolves = resolves(arg_matches, default_options.resolves);
let retry = retry(arg_matches, default_options.retry)?;
let retry_interval = retry_interval(arg_matches, default_options.retry_interval)?;
let secrets = secret(arg_matches, default_options.secrets)?;
let ssl_no_revoke = ssl_no_revoke(arg_matches, default_options.ssl_no_revoke);
let tap_file = tap_file(arg_matches, default_options.tap_file);
let test = test(arg_matches, default_options.test);
let timeout = timeout(arg_matches, default_options.timeout)?;
let to_entry = to_entry(arg_matches, default_options.to_entry);
let unix_socket = unix_socket(arg_matches, default_options.unix_socket);
let user = user(arg_matches, default_options.user);
let user_agent = user_agent(arg_matches, default_options.user_agent);
let variables = variables(arg_matches, default_options.variables)?;
let verbosity = verbosity(arg_matches, default_options.verbosity);
Ok(CliOptions {
aws_sigv4,
cacert_file,
client_cert_file,
client_key_file,
color_stdout,
color_stderr,
compressed,
connect_timeout,
connects_to,
continue_on_error,
cookie_input_file,
cookie_output_file,
curl_file,
delay,
digest,
error_format,
file_root,
follow_location,
follow_location_trusted,
from_entry,
headers,
html_dir,
http_version,
no_assert,
include,
input_files,
insecure,
ip_resolve,
json_report_dir,
junit_file,
limit_rate,
max_filesize,
max_redirect,
negotiate,
netrc,
netrc_file,
netrc_optional,
no_cookie_store,
no_proxy,
ntlm,
path_as_is,
pinned_pub_key,
parallel,
pretty,
progress_bar,
proxy,
output,
output_type,
repeat,
resolves,
retry,
retry_interval,
secrets,
ssl_no_revoke,
tap_file,
test,
timeout,
to_entry,
unix_socket,
user,
user_agent,
variables,
verbosity,
jobs,
})
}
fn cacert_file(
arg_matches: &ArgMatches,
default_value: Option<String>,
) -> Result<Option<String>, CliOptionsError> {
match get_string(arg_matches, "cacert_file") {
None => Ok(default_value),
Some(filename) => {
let path = Path::new(&filename);
if path.exists() {
Ok(Some(filename))
} else {
Err(CliOptionsError::Error(format!(
"Input file {} does not exist",
path.display()
)))
}
}
}
}
fn aws_sigv4(arg_matches: &ArgMatches, default_value: Option<String>) -> Option<String> {
get::<String>(arg_matches, "aws_sigv4").or(default_value)
}
fn client_cert_file(
arg_matches: &ArgMatches,
default_value: Option<String>,
) -> Result<Option<String>, CliOptionsError> {
match get::<String>(arg_matches, "client_cert_file") {
None => Ok(default_value),
Some(filename) => {
if !Path::new(&filename).is_file() {
let message = format!("File {filename} does not exist");
Err(CliOptionsError::Error(message))
} else {
Ok(Some(filename))
}
}
}
}
fn client_key_file(
arg_matches: &ArgMatches,
default_value: Option<String>,
) -> Result<Option<String>, CliOptionsError> {
match get::<String>(arg_matches, "client_key_file") {
None => Ok(default_value),
Some(filename) => {
if !Path::new(&filename).is_file() {
let message = format!("File {filename} does not exist");
Err(CliOptionsError::Error(message))
} else {
Ok(Some(filename))
}
}
}
}
fn color(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "no_color") {
return false;
}
if has_flag(arg_matches, "color") {
return true;
}
default_value
}
fn compressed(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "compressed") {
true
} else {
default_value
}
}
fn connect_timeout(
arg_matches: &ArgMatches,
default_value: Duration,
) -> Result<Duration, CliOptionsError> {
match get::<String>(arg_matches, "connect_timeout") {
Some(s) => duration::duration_from_str(&s, DurationUnit::Second),
None => Ok(default_value),
}
}
fn connects_to(arg_matches: &ArgMatches, default_value: Vec<String>) -> Vec<String> {
get_strings(arg_matches, "connect_to").unwrap_or(default_value)
}
fn continue_on_error(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "continue_on_error") {
true
} else {
default_value
}
}
fn cookie_input_file(arg_matches: &ArgMatches, default_value: Option<String>) -> Option<String> {
get::<String>(arg_matches, "cookies_input_file").or(default_value)
}
fn cookie_output_file(arg_matches: &ArgMatches, default_value: Option<PathBuf>) -> Option<PathBuf> {
get::<String>(arg_matches, "cookies_output_file")
.map(PathBuf::from)
.or(default_value)
}
fn curl_file(arg_matches: &ArgMatches, default_value: Option<PathBuf>) -> Option<PathBuf> {
get::<String>(arg_matches, "curl")
.map(PathBuf::from)
.or(default_value)
}
fn delay(arg_matches: &ArgMatches, default_value: Duration) -> Result<Duration, CliOptionsError> {
match get::<String>(arg_matches, "delay") {
Some(s) => duration::duration_from_str(&s, DurationUnit::MilliSecond),
None => Ok(default_value),
}
}
fn digest(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "digest") {
true
} else {
default_value
}
}
fn error_format(
arg_matches: &ArgMatches,
default_value: ErrorFormat,
) -> Result<ErrorFormat, CliOptionsError> {
match get::<String>(arg_matches, "error_format") {
Some(value) => ErrorFormat::from_str(&value),
None => Ok(default_value),
}
}
fn file_root(arg_matches: &ArgMatches, default_value: Option<String>) -> Option<String> {
get::<String>(arg_matches, "file_root").or(default_value)
}
fn follow_location(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "follow_location") || has_flag(arg_matches, "follow_location_trusted")
{
true
} else {
default_value
}
}
fn follow_location_trusted(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "follow_location_trusted") {
true
} else {
default_value
}
}
fn from_entry(arg_matches: &ArgMatches, default_value: Option<usize>) -> Option<usize> {
get::<u32>(arg_matches, "from_entry")
.map(|x| x as usize)
.or(default_value)
}
fn headers(arg_matches: &ArgMatches, default_value: Vec<String>) -> Vec<String> {
let mut all_headers = default_value;
if let Some(cli_headers) = get_strings(arg_matches, "header") {
all_headers.extend(cli_headers);
}
all_headers
}
fn html_dir(
arg_matches: &ArgMatches,
default_value: Option<PathBuf>,
) -> Result<Option<PathBuf>, CliOptionsError> {
if let Some(dir) = get::<String>(arg_matches, "report_html") {
let path = Path::new(&dir);
if !path.exists() {
match fs::create_dir_all(path) {
Err(_) => Err(CliOptionsError::Error(format!(
"HTML dir {} can not be created",
path.display()
))),
Ok(_) => Ok(Some(path.to_path_buf())),
}
} else if path.is_dir() {
Ok(Some(path.to_path_buf()))
} else {
Err(CliOptionsError::Error(format!(
"{} is not a valid directory",
path.display()
)))
}
} else {
Ok(default_value)
}
}
fn http_version(
arg_matches: &ArgMatches,
default_value: Option<HttpVersion>,
) -> Option<HttpVersion> {
if has_flag(arg_matches, "http3") {
Some(HttpVersion::V3)
} else if has_flag(arg_matches, "http2") {
Some(HttpVersion::V2)
} else if has_flag(arg_matches, "http11") {
Some(HttpVersion::V11)
} else if has_flag(arg_matches, "http10") {
Some(HttpVersion::V10)
} else {
default_value
}
}
fn no_assert(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "no_assert") {
true
} else {
default_value
}
}
fn include(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "include") {
true
} else {
default_value
}
}
fn has_input_files(arg_matches: &ArgMatches, context: &RunContext) -> bool {
get_strings(arg_matches, "input_files").is_some()
|| get_strings(arg_matches, "glob").is_some()
|| !context.is_stdin_term()
}
fn input_files(
arg_matches: &ArgMatches,
context: &RunContext,
) -> Result<Vec<Input>, CliOptionsError> {
let mut files = vec![];
if let Some(filenames) = get_strings(arg_matches, "input_files") {
for filename in &filenames {
let filename = Path::new(filename);
if !filename.exists() {
return Err(CliOptionsError::InvalidInputFile(filename.to_path_buf()));
}
if filename.is_file() {
let file = Input::from(filename);
files.push(file);
} else if filename.is_dir() {
walks_hurl_files(filename, &mut files)?;
}
}
}
for filename in glob_files(arg_matches)? {
files.push(filename);
}
if files.is_empty() && !context.is_stdin_term() {
let input = match Input::from_stdin() {
Ok(input) => input,
Err(err) => return Err(CliOptionsError::Error(err.to_string())),
};
files.push(input);
}
Ok(files)
}
fn walks_hurl_files(dir: &Path, files: &mut Vec<Input>) -> Result<(), CliOptionsError> {
let Ok(entries) = fs::read_dir(dir) else {
return Err(CliOptionsError::InvalidInputFile(dir.to_path_buf()));
};
for entry in entries {
let Ok(entry) = entry else {
return Err(CliOptionsError::InvalidInputFile(dir.to_path_buf()));
};
let path = entry.path();
if path.is_dir() {
walks_hurl_files(&path, files)?;
} else if entry.path().extension() == Some("hurl".as_ref()) {
files.push(Input::from(entry.path()));
}
}
Ok(())
}
fn insecure(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "insecure") {
true
} else {
default_value
}
}
fn ip_resolve(arg_matches: &ArgMatches, default_value: Option<IpResolve>) -> Option<IpResolve> {
if has_flag(arg_matches, "ipv6") {
Some(IpResolve::IpV6)
} else if has_flag(arg_matches, "ipv4") {
Some(IpResolve::IpV4)
} else {
default_value
}
}
fn jobs(arg_matches: &ArgMatches, default_value: Option<usize>) -> Option<usize> {
get::<u32>(arg_matches, "jobs")
.map(|m| m as usize)
.or(default_value)
}
fn json_report_dir(
arg_matches: &ArgMatches,
default_value: Option<PathBuf>,
) -> Result<Option<PathBuf>, CliOptionsError> {
if let Some(dir) = get::<String>(arg_matches, "report_json") {
let path = Path::new(&dir);
if !path.exists() {
match fs::create_dir_all(path) {
Err(_) => Err(CliOptionsError::Error(format!(
"JSON dir {} can not be created",
path.display()
))),
Ok(_) => Ok(Some(path.to_path_buf())),
}
} else if path.is_dir() {
Ok(Some(path.to_path_buf()))
} else {
Err(CliOptionsError::Error(format!(
"{} is not a valid directory",
path.display()
)))
}
} else {
Ok(default_value)
}
}
fn junit_file(arg_matches: &ArgMatches, default_value: Option<PathBuf>) -> Option<PathBuf> {
get::<String>(arg_matches, "report_junit")
.map(PathBuf::from)
.or(default_value)
}
fn limit_rate(arg_matches: &ArgMatches, default_value: Option<BytesPerSec>) -> Option<BytesPerSec> {
get::<u64>(arg_matches, "limit_rate")
.map(BytesPerSec)
.or(default_value)
}
fn max_filesize(arg_matches: &ArgMatches, default_value: Option<u64>) -> Option<u64> {
get::<u64>(arg_matches, "max_filesize").or(default_value)
}
fn max_redirect(arg_matches: &ArgMatches, default_value: Count) -> Result<Count, CliOptionsError> {
match get::<i32>(arg_matches, "max_redirects") {
Some(m) => Count::try_from(m).map_err(|error| {
let message = format!("invalid value '{m}' for '--max-redirs <NUM>' ({error})");
CliOptionsError::Error(message)
}),
None => Ok(default_value),
}
}
fn negotiate(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "negotiate") {
true
} else {
default_value
}
}
fn netrc(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "netrc") {
true
} else {
default_value
}
}
fn netrc_file(
arg_matches: &ArgMatches,
default_value: Option<String>,
) -> Result<Option<String>, CliOptionsError> {
match get::<String>(arg_matches, "netrc_file") {
None => Ok(default_value),
Some(filename) => {
if !Path::new(&filename).is_file() {
let message = format!("File {filename} does not exist");
Err(CliOptionsError::Error(message))
} else {
Ok(Some(filename))
}
}
}
}
fn netrc_optional(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "netrc_optional") {
true
} else {
default_value
}
}
fn no_cookie_store(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "no_cookie_store") {
true
} else {
default_value
}
}
fn no_proxy(arg_matches: &ArgMatches, default_value: Option<String>) -> Option<String> {
get::<String>(arg_matches, "no_proxy").or(default_value)
}
fn ntlm(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "ntlm") {
true
} else {
default_value
}
}
fn output(arg_matches: &ArgMatches, default_value: Option<Output>) -> Option<Output> {
get::<String>(arg_matches, "output")
.map(|filename| Output::new(&filename))
.or(default_value)
}
fn output_type(arg_matches: &ArgMatches, default_value: OutputType) -> OutputType {
if has_flag(arg_matches, "json") {
OutputType::Json
} else if has_flag(arg_matches, "no_output") || has_flag(arg_matches, "test") {
OutputType::NoOutput
} else {
default_value
}
}
fn parallel(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "parallel") || has_flag(arg_matches, "test") {
true
} else {
default_value
}
}
fn path_as_is(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "path_as_is") {
true
} else {
default_value
}
}
fn pinned_pub_key(arg_matches: &ArgMatches, default_value: Option<String>) -> Option<String> {
get::<String>(arg_matches, "pinned_pub_key").or(default_value)
}
fn pretty(arg_matches: &ArgMatches, default_value: PrettyMode) -> PrettyMode {
if has_flag(arg_matches, "pretty") {
return PrettyMode::Force;
}
if has_flag(arg_matches, "no_pretty") {
return PrettyMode::None;
}
default_value
}
fn progress_bar(arg_matches: &ArgMatches, context: &RunContext, default_value: bool) -> bool {
if has_flag(arg_matches, "progress_bar") {
return true;
}
if has_flag(arg_matches, "test") && context.is_stderr_term() && !context.is_ci_env_var() {
return true;
}
default_value
}
fn proxy(arg_matches: &ArgMatches, default_value: Option<String>) -> Option<String> {
get::<String>(arg_matches, "proxy").or(default_value)
}
fn repeat(
arg_matches: &ArgMatches,
default_value: Option<Count>,
) -> Result<Option<Count>, CliOptionsError> {
match get::<i32>(arg_matches, "repeat") {
Some(n) => match Count::try_from(n) {
Ok(n) => Ok(Some(n)),
Err(error) => {
let message = format!("invalid value '{n}' for '--repeat <NUM>' ({error})");
Err(CliOptionsError::Error(message))
}
},
None => Ok(default_value),
}
}
fn resolves(arg_matches: &ArgMatches, default_value: Vec<String>) -> Vec<String> {
get_strings(arg_matches, "resolve").unwrap_or(default_value)
}
fn retry(
arg_matches: &ArgMatches,
default_value: Option<Count>,
) -> Result<Option<Count>, CliOptionsError> {
match get::<i32>(arg_matches, "retry") {
Some(r) => match Count::try_from(r) {
Ok(r) => Ok(Some(r)),
Err(error) => {
let message = format!("invalid value '{r}' for '--retry <NUM>' ({error})");
Err(CliOptionsError::Error(message))
}
},
None => Ok(default_value),
}
}
fn retry_interval(
arg_matches: &ArgMatches,
default_value: Duration,
) -> Result<Duration, CliOptionsError> {
match get::<String>(arg_matches, "retry_interval") {
Some(s) => duration::duration_from_str(&s, DurationUnit::MilliSecond),
None => Ok(default_value),
}
}
fn secret(
matches: &ArgMatches,
default_value: HashMap<String, String>,
) -> Result<HashMap<String, String>, CliOptionsError> {
let mut all_secrets = default_value;
let type_kind = TypeKind::String;
if let Some(filenames) = get_strings(matches, "secrets_file") {
for f in &filenames {
let filename = Path::new(f);
let vars = VariablesFile::open(filename, type_kind)?;
for var in vars {
let (name, value) = var?;
secret::add_secret(&mut all_secrets, name, value)?;
}
}
}
if let Some(secrets) = get_strings(matches, "secret") {
for s in secrets {
let (name, value) = variables::parse(&s, type_kind)?;
secret::add_secret(&mut all_secrets, name, value)?;
}
}
Ok(all_secrets)
}
fn ssl_no_revoke(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "ssl_no_revoke") {
true
} else {
default_value
}
}
fn tap_file(arg_matches: &ArgMatches, default_value: Option<PathBuf>) -> Option<PathBuf> {
get::<String>(arg_matches, "report_tap")
.map(PathBuf::from)
.or(default_value)
}
fn test(arg_matches: &ArgMatches, default_value: bool) -> bool {
if has_flag(arg_matches, "test") {
true
} else {
default_value
}
}
fn timeout(arg_matches: &ArgMatches, default_value: Duration) -> Result<Duration, CliOptionsError> {
match get::<String>(arg_matches, "max_time") {
Some(s) => duration::duration_from_str(&s, DurationUnit::Second),
None => Ok(default_value),
}
}
fn to_entry(arg_matches: &ArgMatches, default_value: Option<usize>) -> Option<usize> {
get::<u32>(arg_matches, "to_entry")
.map(|x| x as usize)
.or(default_value)
}
fn unix_socket(arg_matches: &ArgMatches, default_value: Option<String>) -> Option<String> {
get::<String>(arg_matches, "unix_socket").or(default_value)
}
fn user(arg_matches: &ArgMatches, default_value: Option<String>) -> Option<String> {
get::<String>(arg_matches, "user").or(default_value)
}
fn user_agent(arg_matches: &ArgMatches, default_value: Option<String>) -> Option<String> {
get::<String>(arg_matches, "user_agent").or(default_value)
}
fn variables(
matches: &ArgMatches,
default_value: HashMap<String, Value>,
) -> Result<HashMap<String, Value>, CliOptionsError> {
let mut variables = default_value;
let type_kind = TypeKind::Inferred;
if let Some(filenames) = get_strings(matches, "variables_file") {
for f in &filenames {
let filename = Path::new(f);
let vars = VariablesFile::open(filename, type_kind)?;
for var in vars {
let (name, value) = var?;
variables.insert(name.to_string(), value);
}
}
}
if let Some(input) = get_strings(matches, "variable") {
for s in input {
let (name, value) = variables::parse(&s, type_kind)?;
variables.insert(name.to_string(), value);
}
}
Ok(variables)
}
fn verbosity(arg_matches: &ArgMatches, default_value: Option<Verbosity>) -> Option<Verbosity> {
if has_flag(arg_matches, "verbose") {
Some(Verbosity::Verbose)
} else if has_flag(arg_matches, "very_verbose") {
Some(Verbosity::Debug)
} else {
match get::<String>(arg_matches, "verbosity") {
Some(value) => Verbosity::from_str(&value).ok(),
None => default_value,
}
}
}
fn glob_files(matches: &ArgMatches) -> Result<Vec<Input>, CliOptionsError> {
let mut all_files = vec![];
if let Some(exprs) = get_strings(matches, "glob") {
for expr in exprs {
let paths = match glob::glob(&expr) {
Ok(paths) => paths,
Err(_) => {
return Err(CliOptionsError::Error(
"Failed to read glob pattern".to_string(),
));
}
};
let mut files = vec![];
for entry in paths {
match entry {
Ok(path) => files.push(Input::from(path)),
Err(_) => {
return Err(CliOptionsError::Error(
"Failed to read glob pattern".to_string(),
));
}
}
}
if files.is_empty() {
return Err(CliOptionsError::InvalidInputFile(PathBuf::from(&expr)));
}
all_files.extend(files);
}
}
Ok(all_files)
}
fn get<T: Clone + Send + Sync + 'static>(matches: &ArgMatches, name: &str) -> Option<T> {
matches.get_one::<T>(name).cloned()
}
fn has_flag(matches: &ArgMatches, name: &str) -> bool {
matches.get_one::<bool>(name) == Some(&true)
}
fn get_string(matches: &ArgMatches, name: &str) -> Option<String> {
matches.get_one::<String>(name).map(|x| x.to_string())
}
fn get_strings(matches: &ArgMatches, name: &str) -> Option<Vec<String>> {
matches
.get_many::<String>(name)
.map(|v| v.map(|x| x.to_string()).collect())
}