mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-12-26 04:23:21 +03:00
Merge pull request #245 from Orange-OpenSource/refacto/cli-options
Refacto CLI Options
This commit is contained in:
commit
7b844b6211
@ -21,11 +21,16 @@ pub use self::logger::{
|
||||
log_info, make_logger_error_message, make_logger_parser_error, make_logger_runner_error,
|
||||
make_logger_verbose,
|
||||
};
|
||||
pub use self::options::app;
|
||||
pub use self::options::output_color;
|
||||
pub use self::options::parse_options;
|
||||
pub use self::options::CliOptions;
|
||||
pub use self::variables::parse as parse_variable;
|
||||
|
||||
mod fs;
|
||||
pub mod interactive;
|
||||
mod logger;
|
||||
mod options;
|
||||
mod variables;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
417
packages/hurl/src/cli/options.rs
Normal file
417
packages/hurl/src/cli/options.rs
Normal file
@ -0,0 +1,417 @@
|
||||
/*
|
||||
* hurl (https://hurl.dev)
|
||||
* Copyright (C) 2020 Orange
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
use crate::cli;
|
||||
use crate::cli::CliError;
|
||||
use crate::http::ClientOptions;
|
||||
use crate::runner::Value;
|
||||
use atty::Stream;
|
||||
use clap::{AppSettings, ArgMatches};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CliOptions {
|
||||
pub color: bool,
|
||||
pub compressed: bool,
|
||||
pub connect_timeout: Duration,
|
||||
pub cookie_input_file: Option<String>,
|
||||
pub cookie_output_file: Option<String>,
|
||||
pub fail_fast: bool,
|
||||
pub file_root: Option<String>,
|
||||
pub follow_location: bool,
|
||||
pub html_dir: Option<PathBuf>,
|
||||
pub include: bool,
|
||||
pub insecure: bool,
|
||||
pub interactive: bool,
|
||||
pub json_file: Option<PathBuf>,
|
||||
pub max_redirect: Option<usize>,
|
||||
pub no_proxy: Option<String>,
|
||||
pub output: Option<String>,
|
||||
pub proxy: Option<String>,
|
||||
pub timeout: Duration,
|
||||
pub to_entry: Option<usize>,
|
||||
pub user: Option<String>,
|
||||
pub variables: HashMap<String, Value>,
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
pub fn app() -> clap::App<'static, 'static> {
|
||||
clap::App::new("hurl")
|
||||
.version(clap::crate_version!())
|
||||
.about("Run hurl FILE(s) or standard input")
|
||||
.setting(AppSettings::DeriveDisplayOrder)
|
||||
.setting(AppSettings::UnifiedHelpMessage)
|
||||
.arg(
|
||||
clap::Arg::with_name("INPUT")
|
||||
.help("Sets the input file to use")
|
||||
.required(false)
|
||||
.multiple(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("append")
|
||||
.long("append")
|
||||
.help("Append sessions to json output"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("color")
|
||||
.long("color")
|
||||
.conflicts_with("no-color")
|
||||
.help("Colorize Output"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("compressed")
|
||||
.long("compressed")
|
||||
.help("Request compressed response (using deflate or gzip)"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("connect_timeout")
|
||||
.long("connect-timeout")
|
||||
.value_name("SECONDS")
|
||||
.help("Maximum time allowed for connection"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("cookies_input_file")
|
||||
.short("b")
|
||||
.long("cookie")
|
||||
.value_name("FILE")
|
||||
.help("Read cookies from FILE"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("cookies_output_file")
|
||||
.short("c")
|
||||
.long("cookie-jar")
|
||||
.value_name("FILE")
|
||||
.help("Write cookies to FILE after running the session (only for one session)"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("fail_at_end")
|
||||
.long("fail-at-end")
|
||||
.help("Fail at end")
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("file_root")
|
||||
.long("file-root")
|
||||
.value_name("DIR")
|
||||
.help("set root filesystem to import file in hurl (default is current directory)")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("follow_location")
|
||||
.short("L")
|
||||
.long("location")
|
||||
.help("Follow redirects"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("html")
|
||||
.long("html")
|
||||
.value_name("DIR")
|
||||
.help("Generate html report to dir")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("include")
|
||||
.short("i")
|
||||
.long("include")
|
||||
.help("Include the HTTP headers in the output"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("insecure")
|
||||
.short("k")
|
||||
.long("insecure")
|
||||
.help("Allow insecure SSL connections"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("interactive")
|
||||
.long("interactive")
|
||||
.conflicts_with("to_entry")
|
||||
.help("Turn on interactive mode"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("json")
|
||||
.long("json")
|
||||
.value_name("FILE")
|
||||
.help("Write full session(s) to json file")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("max_time")
|
||||
.long("max-time")
|
||||
.short("m")
|
||||
.value_name("NUM")
|
||||
.allow_hyphen_values(true)
|
||||
.help("Maximum time allowed for the transfer"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("max_redirects")
|
||||
.long("max-redirs")
|
||||
.value_name("NUM")
|
||||
.allow_hyphen_values(true)
|
||||
.help("Maximum number of redirects allowed"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("no_color")
|
||||
.long("no-color")
|
||||
.conflicts_with("color")
|
||||
.help("Do not colorize Output"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("noproxy")
|
||||
.long("noproxy")
|
||||
.value_name("HOST(S)")
|
||||
.help("List of hosts which do not use proxy")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("output")
|
||||
.short("o")
|
||||
.long("output")
|
||||
.value_name("FILE")
|
||||
.help("Write to FILE instead of stdout"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("proxy")
|
||||
.short("x")
|
||||
.long("proxy")
|
||||
.value_name("[PROTOCOL://]HOST[:PORT]")
|
||||
.help("Use proxy on given protocol/host/port"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("to_entry")
|
||||
.long("to-entry")
|
||||
.value_name("ENTRY_NUMBER")
|
||||
.conflicts_with("interactive")
|
||||
.help("Execute hurl file to ENTRY_NUMBER (starting at 1)")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("user")
|
||||
.short("u")
|
||||
.long("user")
|
||||
.value_name("user:password")
|
||||
.help("Add basic Authentication header to each request.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("variable")
|
||||
.long("variable")
|
||||
.value_name("NAME=VALUE")
|
||||
.multiple(true)
|
||||
.number_of_values(1)
|
||||
.help("Define a variable")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("variables_file")
|
||||
.long("variables-file")
|
||||
.value_name("FILE")
|
||||
.help("Define a properties file in which you define your variables")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("verbose")
|
||||
.short("v")
|
||||
.long("verbose")
|
||||
.help("Turn on verbose output"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn parse_options(matches: ArgMatches) -> Result<CliOptions, CliError> {
|
||||
let color = output_color(matches.clone());
|
||||
let compressed = matches.is_present("compressed");
|
||||
let connect_timeout = match matches.value_of("connect_timeout") {
|
||||
None => ClientOptions::default().connect_timeout,
|
||||
Some(s) => match s.parse::<u64>() {
|
||||
Ok(n) => Duration::from_secs(n),
|
||||
Err(_) => {
|
||||
return Err(CliError {
|
||||
message: "connect-timeout option can not be parsed".to_string(),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
let cookie_input_file = matches
|
||||
.value_of("cookies_input_file")
|
||||
.map(|x| x.to_string());
|
||||
let cookie_output_file = matches
|
||||
.value_of("cookies_output_file")
|
||||
.map(|x| x.to_string());
|
||||
let fail_fast = !matches.is_present("fail_at_end");
|
||||
let file_root = matches.value_of("file_root").map(|value| value.to_string());
|
||||
let follow_location = matches.is_present("follow_location");
|
||||
let html_dir = if let Some(dir) = matches.value_of("html") {
|
||||
let path = Path::new(dir);
|
||||
if !path.exists() {
|
||||
match std::fs::create_dir(path) {
|
||||
Err(_) => {
|
||||
return Err(CliError {
|
||||
message: format!("Html dir {} can not be created", path.display()),
|
||||
});
|
||||
}
|
||||
Ok(_) => Some(path.to_path_buf()),
|
||||
}
|
||||
} else if path.is_dir() {
|
||||
Some(path.to_path_buf())
|
||||
} else {
|
||||
return Err(CliError {
|
||||
message: format!("{} is not a valid directory", path.display()),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let include = matches.is_present("include");
|
||||
let insecure = matches.is_present("insecure");
|
||||
let interactive = matches.is_present("interactive");
|
||||
let json_file = if let Some(filename) = matches.value_of("json") {
|
||||
let path = Path::new(filename);
|
||||
Some(path.to_path_buf())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let max_redirect = match matches.value_of("max_redirects") {
|
||||
None => Some(50),
|
||||
Some("-1") => None,
|
||||
Some(s) => match s.parse::<usize>() {
|
||||
Ok(x) => Some(x),
|
||||
Err(_) => {
|
||||
return Err(CliError {
|
||||
message: "max_redirs option can not be parsed".to_string(),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
let no_proxy = matches.value_of("proxy").map(|x| x.to_string());
|
||||
let output = matches.value_of("output").map(|x| x.to_string());
|
||||
let proxy = matches.value_of("proxy").map(|x| x.to_string());
|
||||
let timeout = match matches.value_of("max_time") {
|
||||
None => ClientOptions::default().timeout,
|
||||
Some(s) => match s.parse::<u64>() {
|
||||
Ok(n) => Duration::from_secs(n),
|
||||
Err(_) => {
|
||||
return Err(CliError {
|
||||
message: "max_time option can not be parsed".to_string(),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
let to_entry = to_entry(matches.clone())?;
|
||||
let user = matches.value_of("user").map(|x| x.to_string());
|
||||
let variables = variables(matches.clone())?;
|
||||
let verbose = matches.is_present("verbose") || matches.is_present("interactive");
|
||||
|
||||
// deprecated
|
||||
if matches.is_present("append") {
|
||||
eprintln!("The option --append is deprecated. Results are automatically appended to existing report.");
|
||||
eprintln!("It will be removed in the next version");
|
||||
}
|
||||
|
||||
Ok(CliOptions {
|
||||
color,
|
||||
compressed,
|
||||
connect_timeout,
|
||||
cookie_input_file,
|
||||
cookie_output_file,
|
||||
fail_fast,
|
||||
file_root,
|
||||
follow_location,
|
||||
html_dir,
|
||||
include,
|
||||
insecure,
|
||||
interactive,
|
||||
json_file,
|
||||
max_redirect,
|
||||
no_proxy,
|
||||
output,
|
||||
proxy,
|
||||
timeout,
|
||||
to_entry,
|
||||
user,
|
||||
variables,
|
||||
verbose,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn output_color(matches: ArgMatches) -> bool {
|
||||
if matches.is_present("color") {
|
||||
true
|
||||
} else if matches.is_present("no_color") {
|
||||
false
|
||||
} else {
|
||||
atty::is(Stream::Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_entry(matches: ArgMatches) -> Result<Option<usize>, CliError> {
|
||||
match matches.value_of("to_entry") {
|
||||
Some(value) => match value.parse() {
|
||||
Ok(v) => Ok(Some(v)),
|
||||
Err(_) => Err(CliError {
|
||||
message: "Invalid value for option --to-entry - must be a positive integer!"
|
||||
.to_string(),
|
||||
}),
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn variables(matches: ArgMatches) -> Result<HashMap<String, Value>, CliError> {
|
||||
let mut variables = HashMap::new();
|
||||
|
||||
if let Some(filename) = matches.value_of("variables_file") {
|
||||
let path = std::path::Path::new(filename);
|
||||
if !path.exists() {
|
||||
return Err(CliError {
|
||||
message: format!("Properties file {} does not exist", path.display()),
|
||||
});
|
||||
}
|
||||
|
||||
let file = File::open(path).unwrap();
|
||||
let reader = BufReader::new(file);
|
||||
for (index, line) in reader.lines().enumerate() {
|
||||
let line = match line {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return Err(CliError {
|
||||
message: format!("Can not parse line {} of {}", index + 1, path.display()),
|
||||
});
|
||||
}
|
||||
};
|
||||
let line = line.trim();
|
||||
if line.starts_with('#') || line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (name, value) = cli::parse_variable(line)?;
|
||||
variables.insert(name.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
if matches.is_present("variable") {
|
||||
let input: Vec<_> = matches.values_of("variable").unwrap().collect();
|
||||
for s in input {
|
||||
let (name, value) = cli::parse_variable(s)?;
|
||||
variables.insert(name.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(variables)
|
||||
}
|
@ -16,50 +16,21 @@
|
||||
*
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader};
|
||||
use std::io::{self};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use atty::Stream;
|
||||
use clap::{AppSettings, ArgMatches};
|
||||
|
||||
use hurl::cli;
|
||||
use hurl::cli::interactive;
|
||||
use hurl::cli::CliError;
|
||||
use hurl::cli::{CliError, CliOptions};
|
||||
use hurl::http;
|
||||
use hurl::http::ClientOptions;
|
||||
use hurl::report;
|
||||
use hurl::runner;
|
||||
use hurl::runner::{HurlResult, RunnerOptions, Value};
|
||||
use hurl::runner::{HurlResult, RunnerOptions};
|
||||
use hurl_core::ast::{Pos, SourceInfo};
|
||||
use hurl_core::error::Error;
|
||||
use hurl_core::parser;
|
||||
use std::fs::File;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct CliOptions {
|
||||
pub verbose: bool,
|
||||
pub color: bool,
|
||||
pub fail_fast: bool,
|
||||
pub insecure: bool,
|
||||
pub interactive: bool,
|
||||
pub variables: HashMap<String, Value>,
|
||||
pub to_entry: Option<usize>,
|
||||
pub follow_location: bool,
|
||||
pub max_redirect: Option<usize>,
|
||||
pub proxy: Option<String>,
|
||||
pub no_proxy: Option<String>,
|
||||
pub cookie_input_file: Option<String>,
|
||||
pub timeout: Duration,
|
||||
pub connect_timeout: Duration,
|
||||
pub compressed: bool,
|
||||
pub user: Option<String>,
|
||||
pub json_file: Option<PathBuf>,
|
||||
pub html_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
pub fn init_colored() {
|
||||
@ -83,7 +54,6 @@ fn execute(
|
||||
filename: &str,
|
||||
contents: String,
|
||||
current_dir: &Path,
|
||||
file_root: Option<String>,
|
||||
cli_options: CliOptions,
|
||||
log_verbose: &impl Fn(&str),
|
||||
log_error_message: &impl Fn(bool, &str),
|
||||
@ -154,7 +124,7 @@ fn execute(
|
||||
let connect_timeout = cli_options.connect_timeout;
|
||||
let user = cli_options.user;
|
||||
let compressed = cli_options.compressed;
|
||||
let context_dir = match file_root {
|
||||
let context_dir = match cli_options.file_root {
|
||||
None => {
|
||||
if filename == "-" {
|
||||
current_dir.to_str().unwrap().to_string()
|
||||
@ -184,12 +154,12 @@ fn execute(
|
||||
let mut client = http::Client::init(options);
|
||||
|
||||
let pre_entry = if cli_options.interactive {
|
||||
interactive::pre_entry
|
||||
cli::interactive::pre_entry
|
||||
} else {
|
||||
|| false
|
||||
};
|
||||
let post_entry = if cli_options.interactive {
|
||||
interactive::post_entry
|
||||
cli::interactive::post_entry
|
||||
} else {
|
||||
|| false
|
||||
};
|
||||
@ -214,253 +184,6 @@ fn execute(
|
||||
}
|
||||
}
|
||||
|
||||
fn output_color(matches: ArgMatches) -> bool {
|
||||
if matches.is_present("color") {
|
||||
true
|
||||
} else if matches.is_present("no_color") {
|
||||
false
|
||||
} else {
|
||||
atty::is(Stream::Stdout)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_entry(matches: ArgMatches) -> Result<Option<usize>, CliError> {
|
||||
match matches.value_of("to_entry") {
|
||||
Some(value) => match value.parse() {
|
||||
Ok(v) => Ok(Some(v)),
|
||||
Err(_) => Err(CliError {
|
||||
message: "Invalid value for option --to-entry - must be a positive integer!"
|
||||
.to_string(),
|
||||
}),
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn variables(matches: ArgMatches) -> Result<HashMap<String, Value>, CliError> {
|
||||
let mut variables = HashMap::new();
|
||||
|
||||
if let Some(filename) = matches.value_of("variables_file") {
|
||||
let path = std::path::Path::new(filename);
|
||||
if !path.exists() {
|
||||
return Err(CliError {
|
||||
message: format!("Properties file {} does not exist", path.display()),
|
||||
});
|
||||
}
|
||||
|
||||
let file = File::open(path).unwrap();
|
||||
let reader = BufReader::new(file);
|
||||
for (index, line) in reader.lines().enumerate() {
|
||||
let line = match line {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return Err(CliError {
|
||||
message: format!("Can not parse line {} of {}", index + 1, path.display()),
|
||||
});
|
||||
}
|
||||
};
|
||||
let line = line.trim();
|
||||
if line.starts_with('#') || line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (name, value) = cli::parse_variable(line)?;
|
||||
variables.insert(name.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
if matches.is_present("variable") {
|
||||
let input: Vec<_> = matches.values_of("variable").unwrap().collect();
|
||||
for s in input {
|
||||
let (name, value) = cli::parse_variable(s)?;
|
||||
variables.insert(name.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(variables)
|
||||
}
|
||||
|
||||
fn app() -> clap::App<'static, 'static> {
|
||||
clap::App::new("hurl")
|
||||
//.author(clap::crate_authors!())
|
||||
.version(clap::crate_version!())
|
||||
.about("Run hurl FILE(s) or standard input")
|
||||
.setting(AppSettings::DeriveDisplayOrder)
|
||||
.setting(AppSettings::UnifiedHelpMessage)
|
||||
.arg(
|
||||
clap::Arg::with_name("INPUT")
|
||||
.help("Sets the input file to use")
|
||||
.required(false)
|
||||
.multiple(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("append")
|
||||
.long("append")
|
||||
.help("Append sessions to json output"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("color")
|
||||
.long("color")
|
||||
.conflicts_with("no-color")
|
||||
.help("Colorize Output"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("compressed")
|
||||
.long("compressed")
|
||||
.help("Request compressed response (using deflate or gzip)"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("connect_timeout")
|
||||
.long("connect-timeout")
|
||||
.value_name("SECONDS")
|
||||
.help("Maximum time allowed for connection"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("cookies_input_file")
|
||||
.short("b")
|
||||
.long("cookie")
|
||||
.value_name("FILE")
|
||||
.help("Read cookies from FILE"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("cookies_output_file")
|
||||
.short("c")
|
||||
.long("cookie-jar")
|
||||
.value_name("FILE")
|
||||
.help("Write cookies to FILE after running the session (only for one session)"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("fail_at_end")
|
||||
.long("fail-at-end")
|
||||
.help("Fail at end")
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("file_root")
|
||||
.long("file-root")
|
||||
.value_name("DIR")
|
||||
.help("set root filesystem to import file in hurl (default is current directory)")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("html")
|
||||
.long("html")
|
||||
.value_name("DIR")
|
||||
.help("Generate html report to dir")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("include")
|
||||
.short("i")
|
||||
.long("include")
|
||||
.help("Include the HTTP headers in the output"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("insecure")
|
||||
.short("k")
|
||||
.long("insecure")
|
||||
.help("Allow insecure SSL connections"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("interactive")
|
||||
.long("interactive")
|
||||
.conflicts_with("to_entry")
|
||||
.help("Turn on interactive mode"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("json")
|
||||
.long("json")
|
||||
.value_name("FILE")
|
||||
.help("Write full session(s) to json file")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("follow_location")
|
||||
.short("L")
|
||||
.long("location")
|
||||
.help("Follow redirects"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("max_time")
|
||||
.long("max-time")
|
||||
.short("m")
|
||||
.value_name("NUM")
|
||||
.allow_hyphen_values(true)
|
||||
.help("Maximum time allowed for the transfer"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("max_redirects")
|
||||
.long("max-redirs")
|
||||
.value_name("NUM")
|
||||
.allow_hyphen_values(true)
|
||||
.help("Maximum number of redirects allowed"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("no_color")
|
||||
.long("no-color")
|
||||
.conflicts_with("color")
|
||||
.help("Do not colorize Output"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("noproxy")
|
||||
.long("noproxy")
|
||||
.value_name("HOST(S)")
|
||||
.help("List of hosts which do not use proxy")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("output")
|
||||
.short("o")
|
||||
.long("output")
|
||||
.value_name("FILE")
|
||||
.help("Write to FILE instead of stdout"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("proxy")
|
||||
.short("x")
|
||||
.long("proxy")
|
||||
.value_name("[PROTOCOL://]HOST[:PORT]")
|
||||
.help("Use proxy on given protocol/host/port"),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("to_entry")
|
||||
.long("to-entry")
|
||||
.value_name("ENTRY_NUMBER")
|
||||
.conflicts_with("interactive")
|
||||
.help("Execute hurl file to ENTRY_NUMBER (starting at 1)")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("user")
|
||||
.short("u")
|
||||
.long("user")
|
||||
.value_name("user:password")
|
||||
.help("Add basic Authentication header to each request.")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("variable")
|
||||
.long("variable")
|
||||
.value_name("NAME=VALUE")
|
||||
.multiple(true)
|
||||
.number_of_values(1)
|
||||
.help("Define a variable")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("variables_file")
|
||||
.long("variables-file")
|
||||
.value_name("FILE")
|
||||
.help("Define a properties file in which you define your variables")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
clap::Arg::with_name("verbose")
|
||||
.short("v")
|
||||
.long("verbose")
|
||||
.help("Turn on verbose output"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn unwrap_or_exit<T>(
|
||||
log_error_message: &impl Fn(bool, &str),
|
||||
result: Result<T, CliError>,
|
||||
@ -474,118 +197,8 @@ pub fn unwrap_or_exit<T>(
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_options(matches: ArgMatches) -> Result<CliOptions, CliError> {
|
||||
let verbose = matches.is_present("verbose") || matches.is_present("interactive");
|
||||
let color = output_color(matches.clone());
|
||||
let fail_fast = !matches.is_present("fail_at_end");
|
||||
let variables = variables(matches.clone())?;
|
||||
let to_entry = to_entry(matches.clone())?;
|
||||
let proxy = matches.value_of("proxy").map(|x| x.to_string());
|
||||
let no_proxy = matches.value_of("proxy").map(|x| x.to_string());
|
||||
let insecure = matches.is_present("insecure");
|
||||
let follow_location = matches.is_present("follow_location");
|
||||
let cookie_input_file = matches
|
||||
.value_of("cookies_input_file")
|
||||
.map(|x| x.to_string());
|
||||
let max_redirect = match matches.value_of("max_redirects") {
|
||||
None => Some(50),
|
||||
Some("-1") => None,
|
||||
Some(s) => match s.parse::<usize>() {
|
||||
Ok(x) => Some(x),
|
||||
Err(_) => {
|
||||
return Err(CliError {
|
||||
message: "max_redirs option can not be parsed".to_string(),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let timeout = match matches.value_of("max_time") {
|
||||
None => ClientOptions::default().timeout,
|
||||
Some(s) => match s.parse::<u64>() {
|
||||
Ok(n) => Duration::from_secs(n),
|
||||
Err(_) => {
|
||||
return Err(CliError {
|
||||
message: "max_time option can not be parsed".to_string(),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let connect_timeout = match matches.value_of("connect_timeout") {
|
||||
None => ClientOptions::default().connect_timeout,
|
||||
Some(s) => match s.parse::<u64>() {
|
||||
Ok(n) => Duration::from_secs(n),
|
||||
Err(_) => {
|
||||
return Err(CliError {
|
||||
message: "connect-timeout option can not be parsed".to_string(),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
let compressed = matches.is_present("compressed");
|
||||
let user = matches.value_of("user").map(|x| x.to_string());
|
||||
let interactive = matches.is_present("interactive");
|
||||
|
||||
let json_file = if let Some(filename) = matches.value_of("json") {
|
||||
let path = Path::new(filename);
|
||||
Some(path.to_path_buf())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let html_dir = if let Some(dir) = matches.value_of("html") {
|
||||
let path = Path::new(dir);
|
||||
if !path.exists() {
|
||||
match std::fs::create_dir(path) {
|
||||
Err(_) => {
|
||||
return Err(CliError {
|
||||
message: format!("Html dir {} can not be created", path.display()),
|
||||
})
|
||||
}
|
||||
Ok(_) => Some(path.to_path_buf()),
|
||||
}
|
||||
} else if path.is_dir() {
|
||||
Some(path.to_path_buf())
|
||||
} else {
|
||||
return Err(CliError {
|
||||
message: format!("{} is not a valid directory", path.display()),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// deprecated
|
||||
if matches.is_present("append") {
|
||||
eprintln!("The option --append is deprecated. Results are automatically appended to existing report.");
|
||||
eprintln!("It will be removed in the next version");
|
||||
}
|
||||
|
||||
Ok(CliOptions {
|
||||
verbose,
|
||||
color,
|
||||
fail_fast,
|
||||
insecure,
|
||||
interactive,
|
||||
variables,
|
||||
to_entry,
|
||||
follow_location,
|
||||
max_redirect,
|
||||
proxy,
|
||||
no_proxy,
|
||||
cookie_input_file,
|
||||
timeout,
|
||||
connect_timeout,
|
||||
compressed,
|
||||
user,
|
||||
json_file,
|
||||
html_dir,
|
||||
})
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let app = app();
|
||||
let app = cli::app();
|
||||
let matches = app.clone().get_matches();
|
||||
init_colored();
|
||||
let mut filenames = match matches.values_of("INPUT") {
|
||||
@ -606,20 +219,20 @@ fn main() {
|
||||
let current_dir_buf = std::env::current_dir().unwrap();
|
||||
let current_dir = current_dir_buf.as_path();
|
||||
|
||||
let file_root = matches.value_of("file_root").map(|value| value.to_string());
|
||||
let verbose = matches.is_present("verbose") || matches.is_present("interactive");
|
||||
let log_verbose = cli::make_logger_verbose(verbose);
|
||||
let color = output_color(matches.clone());
|
||||
let color = cli::output_color(matches.clone());
|
||||
let log_error_message = cli::make_logger_error_message(color);
|
||||
let cli_options = unwrap_or_exit(&log_error_message, parse_options(matches.clone()));
|
||||
let cli_options = unwrap_or_exit(&log_error_message, cli::parse_options(matches.clone()));
|
||||
|
||||
let mut hurl_results = vec![];
|
||||
|
||||
let cookies_output_file = match matches.value_of("cookies_output_file") {
|
||||
let cookies_output_file = match cli_options.cookie_output_file.clone() {
|
||||
None => None,
|
||||
Some(filename) => {
|
||||
let filename = unwrap_or_exit(
|
||||
&log_error_message,
|
||||
cookies_output_file(filename.to_string(), filenames.len()),
|
||||
cookies_output_file(filename, filenames.len()),
|
||||
);
|
||||
Some(filename)
|
||||
}
|
||||
@ -638,7 +251,6 @@ fn main() {
|
||||
filename,
|
||||
contents,
|
||||
current_dir,
|
||||
file_root.clone(),
|
||||
cli_options.clone(),
|
||||
&log_verbose,
|
||||
&log_error_message,
|
||||
@ -650,7 +262,7 @@ fn main() {
|
||||
if let Some(entry_result) = hurl_result.entries.last() {
|
||||
if let Some(response) = entry_result.response.clone() {
|
||||
let mut output = vec![];
|
||||
if matches.is_present("include") {
|
||||
if cli_options.include {
|
||||
let status_line = format!(
|
||||
"HTTP/{} {}\n",
|
||||
response.version.to_string(),
|
||||
@ -689,7 +301,7 @@ fn main() {
|
||||
output.append(&mut body.clone());
|
||||
unwrap_or_exit(
|
||||
&log_error_message,
|
||||
write_output(output, matches.value_of("output")),
|
||||
write_output(output, cli_options.output.clone()),
|
||||
);
|
||||
} else {
|
||||
cli::log_info("no response has been received");
|
||||
@ -794,7 +406,7 @@ fn format_html(input_file: &str, dir_path: PathBuf) -> Result<(), CliError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_output(bytes: Vec<u8>, filename: Option<&str>) -> Result<(), CliError> {
|
||||
fn write_output(bytes: Vec<u8>, filename: Option<String>) -> Result<(), CliError> {
|
||||
match filename {
|
||||
None => {
|
||||
let stdout = io::stdout();
|
||||
@ -806,7 +418,7 @@ fn write_output(bytes: Vec<u8>, filename: Option<&str>) -> Result<(), CliError>
|
||||
Ok(())
|
||||
}
|
||||
Some(filename) => {
|
||||
let path = Path::new(filename);
|
||||
let path = Path::new(filename.as_str());
|
||||
let mut file = match std::fs::File::create(&path) {
|
||||
Err(why) => {
|
||||
return Err(CliError {
|
||||
|
Loading…
Reference in New Issue
Block a user