diff --git a/packages/hurl/src/cli/mod.rs b/packages/hurl/src/cli/mod.rs index 53e3464b0..6db8cd386 100644 --- a/packages/hurl/src/cli/mod.rs +++ b/packages/hurl/src/cli/mod.rs @@ -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)] diff --git a/packages/hurl/src/cli/options.rs b/packages/hurl/src/cli/options.rs new file mode 100644 index 000000000..43b27d7b7 --- /dev/null +++ b/packages/hurl/src/cli/options.rs @@ -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, + pub cookie_output_file: Option, + pub fail_fast: bool, + pub file_root: Option, + pub follow_location: bool, + pub html_dir: Option, + pub include: bool, + pub insecure: bool, + pub interactive: bool, + pub json_file: Option, + pub max_redirect: Option, + pub no_proxy: Option, + pub output: Option, + pub proxy: Option, + pub timeout: Duration, + pub to_entry: Option, + pub user: Option, + pub variables: HashMap, + 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 { + 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::() { + 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::() { + 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::() { + 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, 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, 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) +} diff --git a/packages/hurl/src/main.rs b/packages/hurl/src/main.rs index ee6239920..a9ae00035 100644 --- a/packages/hurl/src/main.rs +++ b/packages/hurl/src/main.rs @@ -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, - pub to_entry: Option, - pub follow_location: bool, - pub max_redirect: Option, - pub proxy: Option, - pub no_proxy: Option, - pub cookie_input_file: Option, - pub timeout: Duration, - pub connect_timeout: Duration, - pub compressed: bool, - pub user: Option, - pub json_file: Option, - pub html_dir: Option, -} #[cfg(target_family = "unix")] pub fn init_colored() { @@ -83,7 +54,6 @@ fn execute( filename: &str, contents: String, current_dir: &Path, - file_root: Option, 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, 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, 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( log_error_message: &impl Fn(bool, &str), result: Result, @@ -474,118 +197,8 @@ pub fn unwrap_or_exit( } } -fn parse_options(matches: ArgMatches) -> Result { - 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::() { - 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::() { - 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::() { - 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, filename: Option<&str>) -> Result<(), CliError> { +fn write_output(bytes: Vec, filename: Option) -> Result<(), CliError> { match filename { None => { let stdout = io::stdout(); @@ -806,7 +418,7 @@ fn write_output(bytes: Vec, 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 {