Add --interactive option

This commit is contained in:
Fabrice Reix 2021-01-16 14:18:21 +01:00
parent d6767f7bc1
commit da2dc6493b
9 changed files with 119 additions and 5 deletions

28
Cargo.lock generated
View File

@ -333,6 +333,7 @@ dependencies = [
"regex", "regex",
"serde", "serde",
"serde_json", "serde_json",
"termion",
"url", "url",
] ]
@ -456,6 +457,12 @@ dependencies = [
"autocfg 1.0.0", "autocfg 1.0.0",
] ]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.1.2" version = "0.1.2"
@ -687,6 +694,15 @@ version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
dependencies = [
"redox_syscall",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.3.4" version = "1.3.4"
@ -807,6 +823,18 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "termion"
version = "1.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
]
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.11.0" version = "0.11.0"

View File

@ -195,6 +195,11 @@ If you want to combine results from different Hurl executions in a unique html r
Include the HTTP headers in the output (last entry). Include the HTTP headers in the output (last entry).
### --interactive {#interactive}
Stop between requests.
This is similar to a break point, You can then continue (Press C) or quit (Press Q).
### --json <file> {#json} ### --json <file> {#json}

View File

@ -28,6 +28,7 @@ libxml = "0.2.12"
regex = "1.1.0" regex = "1.1.0"
serde = "1.0.104" serde = "1.0.104"
serde_json = "1.0.40" serde_json = "1.0.40"
termion = "1.5.5"
url = "2.1.0" url = "2.1.0"

View File

@ -0,0 +1,42 @@
extern crate termion;
use std::io::{stderr, stdin, Write};
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
pub fn pre_entry() -> bool {
let stdin = stdin();
let mut stderr = stderr().into_raw_mode().unwrap();
eprintln!("\n\rinteractive mode:");
write!(
stderr,
"\rPress Q (Quit) or C (Continue)\n\n\r{}",
termion::cursor::Hide
)
.unwrap();
stderr.flush().unwrap();
let mut exit = false;
for c in stdin.keys() {
print!("\r");
match c.unwrap() {
Key::Char('q') => {
exit = true;
break;
}
Key::Char('c') => {
break;
}
_ => {}
}
}
print!("{}\r{}", termion::clear::CurrentLine, termion::cursor::Show);
exit
}
pub fn post_entry() -> bool {
false
}

View File

@ -22,4 +22,5 @@ pub use self::logger::{
}; };
mod color; mod color;
pub mod interactive;
mod logger; mod logger;

View File

@ -29,6 +29,7 @@ use chrono::{DateTime, Local};
use clap::{AppSettings, ArgMatches}; use clap::{AppSettings, ArgMatches};
use hurl::cli; use hurl::cli;
use hurl::cli::interactive;
use hurl::html; use hurl::html;
use hurl::http; use hurl::http;
use hurl::runner; use hurl::runner;
@ -44,6 +45,7 @@ pub struct CLIOptions {
pub color: bool, pub color: bool,
pub fail_fast: bool, pub fail_fast: bool,
pub insecure: bool, pub insecure: bool,
pub interactive: bool,
pub variables: HashMap<String, String>, pub variables: HashMap<String, String>,
pub to_entry: Option<usize>, pub to_entry: Option<usize>,
pub follow_location: bool, pub follow_location: bool,
@ -168,11 +170,23 @@ fn execute(
} }
Some(filename) => filename, Some(filename) => filename,
}; };
let pre_entry = if cli_options.interactive {
interactive::pre_entry
} else {
|| false
};
let post_entry = if cli_options.interactive {
interactive::post_entry
} else {
|| false
};
let options = RunnerOptions { let options = RunnerOptions {
fail_fast: cli_options.fail_fast, fail_fast: cli_options.fail_fast,
variables: cli_options.variables, variables: cli_options.variables,
to_entry: cli_options.to_entry, to_entry: cli_options.to_entry,
context_dir, context_dir,
pre_entry,
post_entry,
}; };
runner::run_hurl_file( runner::run_hurl_file(
hurl_file, hurl_file,
@ -411,6 +425,12 @@ fn app() -> clap::App<'static, 'static> {
.long("insecure") .long("insecure")
.help("Allow insecure SSL connections"), .help("Allow insecure SSL connections"),
) )
.arg(
clap::Arg::with_name("interactive")
.long("interactive")
.conflicts_with("to_entry")
.help("Turn on interactive mode"),
)
.arg( .arg(
clap::Arg::with_name("json") clap::Arg::with_name("json")
.long("json") .long("json")
@ -470,6 +490,7 @@ fn app() -> clap::App<'static, 'static> {
clap::Arg::with_name("to_entry") clap::Arg::with_name("to_entry")
.long("to-entry") .long("to-entry")
.value_name("ENTRY_NUMBER") .value_name("ENTRY_NUMBER")
.conflicts_with("interactive")
.help("Execute hurl file to ENTRY_NUMBER (starting at 1)") .help("Execute hurl file to ENTRY_NUMBER (starting at 1)")
.takes_value(true), .takes_value(true),
) )
@ -519,7 +540,7 @@ pub fn unwrap_or_exit<T>(
} }
fn parse_options(matches: ArgMatches) -> Result<CLIOptions, CLIError> { fn parse_options(matches: ArgMatches) -> Result<CLIOptions, CLIError> {
let verbose = matches.is_present("verbose"); let verbose = matches.is_present("verbose") || matches.is_present("interactive");
let color = output_color(matches.clone()); let color = output_color(matches.clone());
let fail_fast = !matches.is_present("fail_at_end"); let fail_fast = !matches.is_present("fail_at_end");
let variables = variables(matches.clone())?; let variables = variables(matches.clone())?;
@ -569,7 +590,7 @@ fn parse_options(matches: ArgMatches) -> Result<CLIOptions, CLIError> {
}; };
let compressed = matches.is_present("compressed"); let compressed = matches.is_present("compressed");
let user = matches.value_of("user").map(|x| x.to_string()); let user = matches.value_of("user").map(|x| x.to_string());
let interactive = matches.is_present("interactive");
Ok(CLIOptions { Ok(CLIOptions {
verbose, verbose,
color, color,
@ -586,6 +607,7 @@ fn parse_options(matches: ArgMatches) -> Result<CLIOptions, CLIError> {
connect_timeout, connect_timeout,
compressed, compressed,
user, user,
interactive,
}) })
} }
@ -615,7 +637,7 @@ fn main() {
Some(value) => Some(value.to_string()), Some(value) => Some(value.to_string()),
_ => None, _ => None,
}; };
let verbose = matches.is_present("verbose"); let verbose = matches.is_present("verbose") || matches.is_present("interactive");
let log_verbose = cli::make_logger_verbose(verbose); let log_verbose = cli::make_logger_verbose(verbose);
let color = output_color(matches.clone()); let color = output_color(matches.clone());
let log_error_message = cli::make_logger_error_message(color); let log_error_message = cli::make_logger_error_message(color);
@ -663,7 +685,7 @@ fn main() {
&log_error_message, &log_error_message,
); );
if hurl_result.errors().is_empty() { if hurl_result.errors().is_empty() && !cli_options.interactive {
// default // default
// last entry + response + body // last entry + response + body
if let Some(entry_result) = hurl_result.entries.last() { if let Some(entry_result) = hurl_result.entries.last() {

View File

@ -28,6 +28,8 @@ pub struct RunnerOptions {
pub variables: HashMap<String, String>, pub variables: HashMap<String, String>,
pub to_entry: Option<usize>, pub to_entry: Option<usize>,
pub context_dir: String, pub context_dir: String,
pub pre_entry: fn() -> bool,
pub post_entry: fn() -> bool,
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]

View File

@ -70,6 +70,8 @@ use super::value::Value;
/// variables, /// variables,
/// to_entry: None, /// to_entry: None,
/// context_dir: "current_dir".to_string(), /// context_dir: "current_dir".to_string(),
/// pre_entry: || true,
/// post_entry: || true,
/// }; /// };
/// ///
/// // Run the hurl file /// // Run the hurl file
@ -117,6 +119,11 @@ pub fn run(
.enumerate() .enumerate()
.collect::<Vec<(usize, Entry)>>() .collect::<Vec<(usize, Entry)>>()
{ {
let exit = (options.pre_entry)();
if exit {
break;
}
let entry_result = entry::run( let entry_result = entry::run(
entry, entry,
http_client, http_client,
@ -130,10 +137,12 @@ pub fn run(
for e in entry_result.errors.clone() { for e in entry_result.errors.clone() {
log_error(&e, false); log_error(&e, false);
} }
if options.fail_fast && !entry_result.errors.is_empty() { let exit = (options.post_entry)();
if exit || (options.fail_fast && !entry_result.errors.is_empty()) {
break; break;
} }
} }
let time_in_ms = start.elapsed().as_millis(); let time_in_ms = start.elapsed().as_millis();
let success = entries let success = entries
.iter() .iter()

View File

@ -67,6 +67,8 @@ fn test_hurl_file() {
variables, variables,
to_entry: None, to_entry: None,
context_dir: "current_dir".to_string(), context_dir: "current_dir".to_string(),
pre_entry: || true,
post_entry: || true,
}; };
let log_verbose: fn(&str) = log_verbose; let log_verbose: fn(&str) = log_verbose;
@ -206,6 +208,8 @@ fn test_hello() {
variables, variables,
to_entry: None, to_entry: None,
context_dir: "current_dir".to_string(), context_dir: "current_dir".to_string(),
pre_entry: || true,
post_entry: || true,
}; };
let log_verbose: fn(&str) = log_verbose; let log_verbose: fn(&str) = log_verbose;
let log_error_message: fn(bool, &str) = log_error_message; let log_error_message: fn(bool, &str) = log_error_message;