Make hurlfmt support several input files (like Hurl)

This commit is contained in:
Fabrice Reix 2023-06-17 13:26:03 +02:00 committed by hurl-bot
parent f7d7c6c6e7
commit cb0ce3051a
No known key found for this signature in database
GPG Key ID: 1283A2B4A0DCAF8D
8 changed files with 162 additions and 107 deletions

View File

@ -0,0 +1,38 @@
GET http://localhost:8000/hello
HTTP 200
Content-Type: text/html; charset=utf-8
Content-Length: 12
[Asserts]
header "Date" exists
`Hello World!`
GET http://localhost:8000/hello
HTTP 200
file, data.txt;
GET http://localhost:8000/hello
HTTP 200
hex, 48656c6c6f20576f726c6421;
GET http://localhost:8000/hello
HTTP 200
base64, SGVsbG8gV29ybGQh;
GET http://localhost:8000/hello
HTTP 200
Content-Type: text/html; charset=utf-8
Content-Length: 12
[Asserts]
header "Date" exists
`Hello World!`
GET http://localhost:8000/hello
HTTP 200
file, data.txt;
GET http://localhost:8000/hello
HTTP 200
hex, 48656c6c6f20576f726c6421;
GET http://localhost:8000/hello
HTTP 200
base64, SGVsbG8gV29ybGQh;

View File

@ -0,0 +1,3 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
hurlfmt tests_ok/hello.hurl tests_ok/hello.hurl

View File

@ -0,0 +1,4 @@
#!/bin/bash
set -Eeuo pipefail
hurlfmt tests_ok/hello.hurl tests_ok/hello.hurl

View File

@ -30,7 +30,19 @@ fn strip_bom(bytes: &mut Vec<u8>) {
/// Similar to the standard read_to_string()
/// But remove any existing BOM
/// Support also input stream when filename = '-'
pub fn read_to_string(filename: &str) -> Result<String, CliError> {
if filename == "-" {
let mut contents = String::new();
return if let Err(e) = std::io::stdin().read_to_string(&mut contents) {
Err(CliError {
message: format!("Input stream can not be read - {e}"),
})
} else {
return Ok(contents);
};
}
let mut f = match File::open(filename) {
Ok(f) => f,
Err(e) => {

View File

@ -54,12 +54,12 @@ pub fn in_place() -> clap::Arg {
.conflicts_with("color")
}
pub fn input_file() -> clap::Arg {
clap::Arg::new("input_file")
pub fn input_files() -> clap::Arg {
clap::Arg::new("input_files")
.help("Sets the input file to use")
.required(false)
.index(1)
.num_args(1)
.num_args(1..)
}
pub fn input_format() -> clap::Arg {

View File

@ -70,7 +70,7 @@ pub fn in_place(arg_matches: &ArgMatches) -> Result<bool, OptionsError> {
Err(OptionsError::Error(
"You can use --in-place only hurl format!".to_string(),
))
} else if get_string(arg_matches, "input_file").is_none() {
} else if get_string(arg_matches, "input_files").is_none() {
Err(OptionsError::Error(
"You can not use --in-place with standard input stream!".to_string(),
))
@ -82,21 +82,26 @@ pub fn in_place(arg_matches: &ArgMatches) -> Result<bool, OptionsError> {
}
}
pub fn input_file(arg_matches: &ArgMatches) -> Result<Option<PathBuf>, OptionsError> {
match get_string(arg_matches, "input_file") {
None => Ok(None),
Some(s) => {
let path = Path::new(&s);
/// Returns the input files from the positional arguments and input stream
pub fn input_files(arg_matches: &ArgMatches) -> Result<Vec<String>, OptionsError> {
let mut files = vec![];
if let Some(filenames) = get_strings(arg_matches, "input_files") {
for filename in filenames {
let path = Path::new(&filename);
if path.exists() {
Ok(Some(path.to_path_buf()))
files.push(filename);
} else {
Err(OptionsError::Error(format!(
"input file {} does not exist",
return Err(OptionsError::Error(format!(
"hurl: cannot access '{}': No such file or directory",
path.display()
)))
)));
}
}
}
if files.is_empty() && !atty::is(Stream::Stdin) {
files.push("-".to_string());
}
Ok(files)
}
pub fn output_file(arg_matches: &ArgMatches) -> Option<PathBuf> {
@ -124,3 +129,10 @@ fn has_flag(matches: &ArgMatches, name: &str) -> bool {
pub fn get_string(matches: &ArgMatches, name: &str) -> Option<String> {
matches.get_one::<String>(name).map(|x| x.to_string())
}
/// Returns an optional list of `String` from the command line `matches` given the option `name`.
pub fn get_strings(matches: &ArgMatches, name: &str) -> Option<Vec<String>> {
matches
.get_many::<String>(name)
.map(|v| v.map(|x| x.to_string()).collect())
}

View File

@ -29,7 +29,7 @@ pub struct Options {
pub check: bool,
pub color: bool,
pub in_place: bool,
pub input_file: Option<PathBuf>,
pub input_files: Vec<String>,
pub input_format: InputFormat,
pub output_file: Option<PathBuf>,
pub output_format: OutputFormat,
@ -74,7 +74,7 @@ pub fn parse() -> Result<Options, OptionsError> {
.arg(commands::color())
.arg(commands::format())
.arg(commands::in_place())
.arg(commands::input_file())
.arg(commands::input_files())
.arg(commands::input_format())
.arg(commands::no_color())
.arg(commands::output())
@ -84,7 +84,7 @@ pub fn parse() -> Result<Options, OptionsError> {
let arg_matches = command.try_get_matches_from_mut(env::args_os())?;
let opts = parse_matches(&arg_matches)?;
if opts.input_file.is_none() && atty::is(Stream::Stdin) {
if opts.input_files.is_empty() && atty::is(Stream::Stdin) {
let help = command.render_help().to_string();
return Err(OptionsError::Error(help));
}
@ -95,7 +95,7 @@ fn parse_matches(arg_matches: &ArgMatches) -> Result<Options, OptionsError> {
let check = matches::check(arg_matches);
let color = matches::color(arg_matches);
let in_place = matches::in_place(arg_matches)?;
let input_file = matches::input_file(arg_matches)?;
let input_files = matches::input_files(arg_matches)?;
let input_format = matches::input_format(arg_matches)?;
let output_file = matches::output_file(arg_matches);
let output_format = matches::output_format(arg_matches)?;
@ -104,7 +104,7 @@ fn parse_matches(arg_matches: &ArgMatches) -> Result<Options, OptionsError> {
check,
color,
in_place,
input_file,
input_files,
input_format,
output_file,
output_format,

View File

@ -15,8 +15,8 @@
* limitations under the License.
*
*/
use std::io::{self, Read, Write};
use std::path::PathBuf;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process;
use hurl_core::parser;
@ -51,102 +51,88 @@ fn main() {
init_colored();
let log_error_message = cli::make_logger_error_message(opts.color);
let contents = if let Some(filename) = &opts.input_file {
match cli::read_to_string(&filename.display().to_string()) {
Ok(s) => s,
let mut output_all = "".to_string();
for input_file in &opts.input_files {
match cli::read_to_string(input_file) {
Ok(contents) => {
// parse input
let input = match opts.input_format {
InputFormat::Hurl => contents.to_string(),
InputFormat::Curl => match curl::parse(&contents) {
Ok(s) => s,
Err(e) => {
eprintln!("{}", e);
process::exit(2);
}
},
};
let input_path = Path::new(input_file).to_path_buf();
let lines: Vec<&str> = regex::Regex::new(r"\n|\r\n")
.unwrap()
.split(&input)
.collect();
let lines: Vec<String> = lines.iter().map(|s| (*s).to_string()).collect();
let log_parser_error = cli::make_logger_parser_error(
lines.clone(),
opts.color,
Some(input_path.clone()),
);
let log_linter_error =
cli::make_logger_linter_error(lines, opts.color, Some(input_path));
match parser::parse_hurl_file(&input) {
Err(e) => {
log_parser_error(&e, false);
process::exit(2);
}
Ok(hurl_file) => {
if opts.check {
for e in linter::check_hurl_file(&hurl_file).iter() {
log_linter_error(e, true);
}
process::exit(1);
} else {
let output = match opts.output_format {
OutputFormat::Hurl => {
let hurl_file = linter::lint_hurl_file(&hurl_file);
format::format_text(hurl_file, opts.color)
}
OutputFormat::Json => format::format_json(&hurl_file),
OutputFormat::Html => {
hurl_core::format::format_html(&hurl_file, opts.standalone)
}
};
if opts.in_place {
let output_file = Some(Path::new(input_file).to_path_buf());
write_output(&output, output_file.clone());
} else {
output_all.push_str(&output);
}
}
}
}
}
Err(e) => {
log_error_message(
false,
format!(
"Input file {} can not be read - {}",
filename.display(),
e.message
)
.as_str(),
format!("Input file {} can not be read - {}", input_file, e.message).as_str(),
);
process::exit(2);
}
}
} else {
let mut contents = String::new();
if let Err(e) = io::stdin().read_to_string(&mut contents) {
log_error_message(
false,
format!("Input stream can not be read - {e}").as_str(),
);
process::exit(2);
}
contents
};
let input = match opts.input_format {
InputFormat::Hurl => contents,
InputFormat::Curl => match curl::parse(&contents) {
Ok(s) => s,
Err(e) => {
eprintln!("{}", e);
process::exit(2);
}
},
};
let lines: Vec<&str> = regex::Regex::new(r"\n|\r\n")
.unwrap()
.split(&input)
.collect();
let lines: Vec<String> = lines.iter().map(|s| (*s).to_string()).collect();
let log_parser_error =
cli::make_logger_parser_error(lines.clone(), opts.color, opts.input_file.clone());
let log_linter_error =
cli::make_logger_linter_error(lines, opts.color, opts.input_file.clone());
match parser::parse_hurl_file(&input) {
Err(e) => {
log_parser_error(&e, false);
process::exit(2);
}
Ok(hurl_file) => {
if opts.check {
for e in linter::check_hurl_file(&hurl_file).iter() {
log_linter_error(e, true);
}
process::exit(1);
} else {
let output = match opts.output_format {
OutputFormat::Hurl => {
let hurl_file = linter::lint_hurl_file(&hurl_file);
format::format_text(hurl_file, opts.color)
}
OutputFormat::Json => format::format_json(&hurl_file),
OutputFormat::Html => {
hurl_core::format::format_html(&hurl_file, opts.standalone)
}
};
let output = if !output.ends_with('\n') {
format!("{output}\n")
} else {
output
};
let output_file = match opts.output_file {
None => {
if opts.in_place {
Some(
opts.input_file
.expect("an input file when --in-place is set"),
)
} else {
None
}
}
v => v,
};
write_output(output.into_bytes(), output_file);
}
}
}
if !opts.in_place {
write_output(&output_all, opts.output_file);
}
}
fn write_output(bytes: Vec<u8>, filename: Option<PathBuf>) {
fn write_output(content: &str, filename: Option<PathBuf>) {
let content = if !content.ends_with('\n') {
format!("{content}\n")
} else {
content.to_string()
};
let bytes = content.into_bytes();
match filename {
None => {
let stdout = io::stdout();
@ -160,7 +146,7 @@ fn write_output(bytes: Vec<u8>, filename: Option<PathBuf>) {
let mut file = match std::fs::File::create(&path_buf) {
Err(why) => {
eprintln!("Issue writing to {}: {:?}", path_buf.display(), why);
std::process::exit(1);
process::exit(1);
}
Ok(file) => file,
};