mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-09-21 02:38:20 +03:00
Add --output option per request
This commit is contained in:
parent
8de89c16f9
commit
53d174588c
@ -341,29 +341,9 @@ impl Options {
|
||||
let cacert_file = self.cacert_file.clone();
|
||||
let client_cert_file = self.client_cert_file.clone();
|
||||
let client_key_file = self.client_key_file.clone();
|
||||
let connects_to = self.connects_to.clone();
|
||||
let follow_location = self.follow_location;
|
||||
let http_version = match self.http_version {
|
||||
Some(version) => version.into(),
|
||||
None => RequestedHttpVersion::default(),
|
||||
};
|
||||
let ip_resolve = match self.ip_resolve {
|
||||
Some(ip) => ip.into(),
|
||||
None => http::IpResolve::default(),
|
||||
};
|
||||
let insecure = self.insecure;
|
||||
let max_redirect = self.max_redirect;
|
||||
let path_as_is = self.path_as_is;
|
||||
let proxy = self.proxy.clone();
|
||||
let no_proxy = self.no_proxy.clone();
|
||||
let cookie_input_file = self.cookie_input_file.clone();
|
||||
let timeout = self.timeout;
|
||||
let connect_timeout = self.connect_timeout;
|
||||
let user = self.user.clone();
|
||||
let user_agent = self.user_agent.clone();
|
||||
let compressed = self.compressed;
|
||||
let continue_on_error = self.continue_on_error;
|
||||
let delay = self.delay;
|
||||
let connect_timeout = self.connect_timeout;
|
||||
let connects_to = self.connects_to.clone();
|
||||
let file_root = match self.file_root {
|
||||
Some(ref filename) => Path::new(filename),
|
||||
None => {
|
||||
@ -376,22 +356,73 @@ impl Options {
|
||||
}
|
||||
};
|
||||
let context_dir = ContextDir::new(current_dir, file_root);
|
||||
let pre_entry = if self.interactive {
|
||||
Some(cli::interactive::pre_entry as fn(Entry) -> bool)
|
||||
} else {
|
||||
None
|
||||
let continue_on_error = self.continue_on_error;
|
||||
let cookie_input_file = self.cookie_input_file.clone();
|
||||
let delay = self.delay;
|
||||
let follow_location = self.follow_location;
|
||||
let http_version = match self.http_version {
|
||||
Some(version) => version.into(),
|
||||
None => RequestedHttpVersion::default(),
|
||||
};
|
||||
let ignore_asserts = self.ignore_asserts;
|
||||
let insecure = self.insecure;
|
||||
let ip_resolve = match self.ip_resolve {
|
||||
Some(ip) => ip.into(),
|
||||
None => http::IpResolve::default(),
|
||||
};
|
||||
let max_redirect = self.max_redirect;
|
||||
let no_proxy = self.no_proxy.clone();
|
||||
// FIXME:
|
||||
// When used globally (on the command line), `--output` writes the last successful request
|
||||
// to `output` file. We don't want to output every entry's response, so we initialise
|
||||
// output to `None`.
|
||||
// The straightforward code should have been `let output = self.output;` but if we do this
|
||||
// every entry's response would have been dumped to stdout.
|
||||
//
|
||||
// If we compare with `--compressed`:
|
||||
//
|
||||
// ```
|
||||
// cli.compressed = true =>
|
||||
// entry_1.compressed = true
|
||||
// entry_2.compressed = true
|
||||
// entry_2.overridden.compressed = false
|
||||
// entry_3.compressed = true
|
||||
// etc...
|
||||
// entry_last.compressed = true
|
||||
// ```
|
||||
//
|
||||
// whereas
|
||||
//
|
||||
// ```
|
||||
// cli.output = /tmp/out.bin =>
|
||||
// entry_1.output = None
|
||||
// entry_2.output = None
|
||||
// entry_2.overridden.output = /tmp/bar.bin
|
||||
// entry_3.output = None
|
||||
// etc...
|
||||
// entry_last.output = /tmp/out.bin
|
||||
// ```
|
||||
let output = None;
|
||||
let path_as_is = self.path_as_is;
|
||||
let post_entry = if self.interactive {
|
||||
Some(cli::interactive::post_entry as fn() -> bool)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let to_entry = self.to_entry;
|
||||
let pre_entry = if self.interactive {
|
||||
Some(cli::interactive::pre_entry as fn(Entry) -> bool)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let proxy = self.proxy.clone();
|
||||
let resolves = self.resolves.clone();
|
||||
let retry = self.retry;
|
||||
let retry_interval = self.retry_interval;
|
||||
let ignore_asserts = self.ignore_asserts;
|
||||
let ssl_no_revoke = self.ssl_no_revoke;
|
||||
let timeout = self.timeout;
|
||||
let to_entry = self.to_entry;
|
||||
let user = self.user.clone();
|
||||
let user_agent = self.user_agent.clone();
|
||||
|
||||
RunnerOptionsBuilder::new()
|
||||
.aws_sigv4(aws_sigv4)
|
||||
@ -412,6 +443,7 @@ impl Options {
|
||||
.ip_resolve(ip_resolve)
|
||||
.max_redirect(max_redirect)
|
||||
.no_proxy(no_proxy)
|
||||
.output(output)
|
||||
.path_as_is(path_as_is)
|
||||
.post_entry(post_entry)
|
||||
.pre_entry(pre_entry)
|
||||
|
@ -75,7 +75,7 @@ pub fn eval_file(filename: &Filename, context_dir: &ContextDir) -> Result<Vec<u8
|
||||
return Err(Error::new(filename.source_info, inner, false));
|
||||
}
|
||||
let resolved_file = context_dir.get_path(&file);
|
||||
let inner = RunnerError::FileReadAccess { value: file };
|
||||
let inner = RunnerError::FileReadAccess { file };
|
||||
match std::fs::read(resolved_file) {
|
||||
Ok(value) => Ok(value),
|
||||
Err(_) => Err(Error::new(filename.source_info, inner, false)),
|
||||
@ -143,7 +143,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
error.inner,
|
||||
RunnerError::FileReadAccess {
|
||||
value: "data.bin".to_string()
|
||||
file: "data.bin".to_string()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
|
@ -91,10 +91,9 @@ pub fn run(
|
||||
let calls = match http_client.execute_with_redirect(&http_request, &client_options, logger) {
|
||||
Ok(calls) => calls,
|
||||
Err(http_error) => {
|
||||
let source_info = SourceInfo {
|
||||
start: entry.request.url.source_info.start,
|
||||
end: entry.request.url.source_info.end,
|
||||
};
|
||||
let start = entry.request.url.source_info.start;
|
||||
let end = entry.request.url.source_info.end;
|
||||
let source_info = SourceInfo::new(start, end);
|
||||
let error = Error::new(source_info, http_error.into(), false);
|
||||
return EntryResult {
|
||||
entry_index,
|
||||
|
@ -65,7 +65,12 @@ pub enum RunnerError {
|
||||
CouldNotParseResponse,
|
||||
CouldNotUncompressResponse(String),
|
||||
FileReadAccess {
|
||||
value: String,
|
||||
file: String,
|
||||
},
|
||||
// I/O write error on a path
|
||||
FileWriteAccess {
|
||||
file: String,
|
||||
error: String,
|
||||
},
|
||||
FilterDecode(String),
|
||||
FilterInvalidEncoding(String),
|
||||
@ -128,6 +133,7 @@ impl hurl_core::error::Error for Error {
|
||||
RunnerError::CouldNotParseResponse => "HTTP connection".to_string(),
|
||||
RunnerError::CouldNotUncompressResponse(..) => "Decompression error".to_string(),
|
||||
RunnerError::FileReadAccess { .. } => "File read access".to_string(),
|
||||
RunnerError::FileWriteAccess { .. } => "File write access".to_string(),
|
||||
RunnerError::FilterDecode { .. } => "Filter Error".to_string(),
|
||||
RunnerError::FilterInvalidEncoding { .. } => "Filter Error".to_string(),
|
||||
RunnerError::FilterInvalidInput { .. } => "Filter Error".to_string(),
|
||||
@ -182,7 +188,10 @@ impl hurl_core::error::Error for Error {
|
||||
RunnerError::CouldNotUncompressResponse(algorithm) => {
|
||||
format!("could not uncompress response with {algorithm}")
|
||||
}
|
||||
RunnerError::FileReadAccess { value } => format!("file {value} can not be read"),
|
||||
RunnerError::FileReadAccess { file } => format!("file {file} can not be read"),
|
||||
RunnerError::FileWriteAccess { file, error } => {
|
||||
format!("{file} can not be write ({error})")
|
||||
}
|
||||
RunnerError::FilterDecode(encoding) => {
|
||||
format!("value can not be decoded with <{encoding}> encoding")
|
||||
}
|
||||
|
@ -21,14 +21,13 @@ use std::time::Instant;
|
||||
|
||||
use chrono::Utc;
|
||||
use hurl_core::ast::VersionValue::VersionAnyLegacy;
|
||||
use hurl_core::ast::*;
|
||||
use hurl_core::ast::{Body, Bytes, Entry, HurlFile, MultilineString, Request, Response, Retry};
|
||||
use hurl_core::error::Error;
|
||||
use hurl_core::parser;
|
||||
|
||||
use crate::http::Call;
|
||||
use crate::runner::result::*;
|
||||
use crate::runner::runner_options::RunnerOptions;
|
||||
use crate::runner::{entry, options, Value};
|
||||
use crate::runner::{entry, options, EntryResult, HurlResult, Value};
|
||||
use crate::util::logger::{ErrorFormat, Logger, LoggerOptions, LoggerOptionsBuilder};
|
||||
use crate::{http, runner};
|
||||
|
||||
@ -104,15 +103,19 @@ pub fn run(
|
||||
let start = Instant::now();
|
||||
let timestamp = Utc::now().timestamp();
|
||||
|
||||
// Main loop processing each entry.
|
||||
// The `entry_index` is not always incremented of each loop tick: an entry can be retried upon
|
||||
// errors for instance. Each entry is executed with options that are computed from the global
|
||||
// runner options and the "overridden" request options.
|
||||
loop {
|
||||
if entry_index > n {
|
||||
break;
|
||||
}
|
||||
let entry = &hurl_file.entries[entry_index - 1];
|
||||
|
||||
// We compute the new logger for this entry, before entering into the `run`
|
||||
// function because entry options can modify the logger and we want the preamble
|
||||
// "Executing entry..." to be displayed based on the entry level verbosity.
|
||||
// We compute the new logger for this entry, before entering into the `run` function because
|
||||
// entry options can modify the logger and we want the preamble "Executing entry..." to be
|
||||
// displayed based on the entry level verbosity.
|
||||
let logger =
|
||||
get_entry_logger(entry, logger_options, &variables).map_err(|e| e.description())?;
|
||||
if let Some(pre_entry) = runner_options.pre_entry {
|
||||
@ -186,8 +189,8 @@ pub fn run(
|
||||
} else {
|
||||
false
|
||||
};
|
||||
// If `retry_max_reached` is true, we print now a warning, before displaying
|
||||
// any assert error so any potential error is the last thing displayed to the user.
|
||||
// If `retry_max_reached` is true, we print now a warning, before displaying any assert
|
||||
// error so any potential error is the last thing displayed to the user.
|
||||
// If `retry_max_reached` is not true (for instance `retry`is true, or there is no error
|
||||
// we first log the error and a potential warning about retrying.
|
||||
logger.test_erase_line();
|
||||
@ -196,10 +199,30 @@ pub fn run(
|
||||
logger.debug("");
|
||||
}
|
||||
|
||||
// We logs eventual errors, only if we're not retrying the current entry...
|
||||
let retry = !matches!(retry_opts, Retry::None) && !retry_max_reached && has_error;
|
||||
if has_error {
|
||||
log_errors(&entry_result, content, retry, &logger);
|
||||
}
|
||||
|
||||
// When --output is overriden on a request level, we output the HTTP response only if the
|
||||
// call has succeeded.
|
||||
if let Ok(RunnerOptions {
|
||||
output: Some(output),
|
||||
..
|
||||
}) = options
|
||||
{
|
||||
if !has_error {
|
||||
// TODO: make output write error as part of entry result errors.
|
||||
// For the moment, we deal the --output request failure as a simple warning and not
|
||||
// an error. If we want to treat it as an error, we've to add it to the current
|
||||
// `entry_result` errors, and optionally deals with retry if we can't write to the
|
||||
// specified path.
|
||||
if let Err(e) = entry_result.write_response(output) {
|
||||
logger.warning(&e.fixme());
|
||||
}
|
||||
}
|
||||
}
|
||||
entries.push(entry_result);
|
||||
|
||||
if retry {
|
||||
@ -209,9 +232,9 @@ pub fn run(
|
||||
format!("Retry entry {entry_index} (x{retry_count} pause {delay} ms)").as_str(),
|
||||
);
|
||||
retry_count += 1;
|
||||
// If we retry the entry, we do not want to display a 'blank' progress bar
|
||||
// during the sleep delay. During the pause, we artificially show the previously
|
||||
// erased progress line.
|
||||
// If we retry the entry, we do not want to display a 'blank' progress bar during the
|
||||
// sleep delay. During the pause, we artificially show the previously erased progress
|
||||
// line.
|
||||
logger.test_progress(entry_index, n);
|
||||
thread::sleep(retry_interval);
|
||||
logger.test_erase_line();
|
||||
|
@ -48,4 +48,5 @@ mod result;
|
||||
mod runner_options;
|
||||
mod template;
|
||||
mod value;
|
||||
mod write;
|
||||
mod xpath;
|
||||
|
@ -15,7 +15,6 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
@ -157,6 +156,9 @@ pub fn get_entry_options(
|
||||
let value = eval_natural_option(value, variables)?;
|
||||
runner_options.max_redirect = Some(value as usize)
|
||||
}
|
||||
OptionKind::Output(filename) => {
|
||||
runner_options.output = Some(filename.value.clone())
|
||||
}
|
||||
OptionKind::PathAsIs(value) => {
|
||||
let value = eval_boolean_option(value, variables)?;
|
||||
runner_options.path_as_is = value
|
||||
|
@ -15,11 +15,12 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
use hurl_core::ast::SourceInfo;
|
||||
use hurl_core::ast::{Pos, SourceInfo};
|
||||
|
||||
use crate::http::{Call, Cookie};
|
||||
use crate::runner::error::Error;
|
||||
use crate::runner::value::Value;
|
||||
use crate::runner::write::write_file;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct HurlResult {
|
||||
@ -60,7 +61,10 @@ pub struct EntryResult {
|
||||
pub asserts: Vec<AssertResult>,
|
||||
pub errors: Vec<Error>,
|
||||
pub time_in_ms: u128,
|
||||
pub compressed: bool, // The entry has been executed with `--compressed` option
|
||||
// The entry has been executed with `--compressed` option:
|
||||
// server is requested to send compressed response, and the response should be uncompressed
|
||||
// when outputted on stdout.
|
||||
pub compressed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@ -99,3 +103,32 @@ pub struct CaptureResult {
|
||||
}
|
||||
|
||||
pub type PredicateResult = Result<(), Error>;
|
||||
|
||||
impl EntryResult {
|
||||
/// Writes the last HTTP response of this entry result to the file `filename`.
|
||||
/// The HTTP response can be decompressed if the entry's `compressed` option has been set.
|
||||
pub fn write_response(&self, filename: String) -> Result<(), Error> {
|
||||
match self.calls.last() {
|
||||
Some(call) => {
|
||||
let response = &call.response;
|
||||
if self.compressed {
|
||||
let bytes = match response.uncompress_body() {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => {
|
||||
// TODO: pass a [`SourceInfo`] in case of error
|
||||
// We may pass a [`SourceInfo`] as a parameter of this method to make
|
||||
// a more accurate error (for instance a [`SourceInfo`] pointing at
|
||||
// `output: foo.bin`
|
||||
let source_info = SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0));
|
||||
return Err(Error::new(source_info, e.into(), false));
|
||||
}
|
||||
};
|
||||
write_file(&bytes, &filename)
|
||||
} else {
|
||||
write_file(&response.body, &filename)
|
||||
}
|
||||
}
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ pub struct RunnerOptionsBuilder {
|
||||
ip_resolve: IpResolve,
|
||||
max_redirect: Option<usize>,
|
||||
no_proxy: Option<String>,
|
||||
output: Option<String>,
|
||||
path_as_is: bool,
|
||||
post_entry: Option<fn() -> bool>,
|
||||
pre_entry: Option<fn(Entry) -> bool>,
|
||||
@ -77,6 +78,7 @@ impl Default for RunnerOptionsBuilder {
|
||||
ip_resolve: IpResolve::default(),
|
||||
max_redirect: Some(50),
|
||||
no_proxy: None,
|
||||
output: None,
|
||||
path_as_is: false,
|
||||
post_entry: None,
|
||||
pre_entry: None,
|
||||
@ -241,6 +243,12 @@ impl RunnerOptionsBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Specifies the file to output the HTTP response instead of stdout.
|
||||
pub fn output(&mut self, output: Option<String>) -> &mut Self {
|
||||
self.output = output;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets function to be executed after each entry execution.
|
||||
///
|
||||
/// If the function returns true, the run is stopped.
|
||||
@ -337,6 +345,7 @@ impl RunnerOptionsBuilder {
|
||||
ip_resolve: self.ip_resolve,
|
||||
max_redirect: self.max_redirect,
|
||||
no_proxy: self.no_proxy.clone(),
|
||||
output: self.output.clone(),
|
||||
path_as_is: self.path_as_is,
|
||||
post_entry: self.post_entry,
|
||||
pre_entry: self.pre_entry,
|
||||
@ -374,6 +383,7 @@ pub struct RunnerOptions {
|
||||
pub(crate) insecure: bool,
|
||||
pub(crate) max_redirect: Option<usize>,
|
||||
pub(crate) no_proxy: Option<String>,
|
||||
pub(crate) output: Option<String>,
|
||||
pub(crate) path_as_is: bool,
|
||||
pub(crate) post_entry: Option<fn() -> bool>,
|
||||
pub(crate) pre_entry: Option<fn(Entry) -> bool>,
|
||||
|
51
packages/hurl/src/runner/write.rs
Normal file
51
packages/hurl/src/runner/write.rs
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Hurl (https://hurl.dev)
|
||||
* Copyright (C) 2023 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::runner::{Error, RunnerError};
|
||||
use hurl_core::ast::{Pos, SourceInfo};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
// TODO: make functions for sdtout
|
||||
|
||||
/// Writes these `bytes` to the file `filename`.
|
||||
pub fn write_file(bytes: &[u8], filename: &str) -> Result<(), Error> {
|
||||
let mut file = match File::create(filename) {
|
||||
Ok(file) => file,
|
||||
Err(e) => return Err(Error::new_file_write_access(filename, &e.to_string())),
|
||||
};
|
||||
match file.write_all(bytes) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(Error::new_file_write_access(filename, &e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Creates a new file write access error.
|
||||
fn new_file_write_access(filename: &str, error: &str) -> Error {
|
||||
// TODO: improve the error with a [`SourcInfo`] passed in parameter.
|
||||
let source_info = SourceInfo::new(Pos::new(0, 0), Pos::new(0, 0));
|
||||
Error::new(
|
||||
source_info,
|
||||
RunnerError::FileWriteAccess {
|
||||
file: filename.to_string(),
|
||||
error: error.to_string(),
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
@ -725,6 +725,7 @@ pub enum OptionKind {
|
||||
IpV6(BooleanOption),
|
||||
FollowLocation(BooleanOption),
|
||||
MaxRedirect(NaturalOption),
|
||||
Output(Filename),
|
||||
PathAsIs(BooleanOption),
|
||||
Proxy(Template),
|
||||
Resolve(Template),
|
||||
@ -755,6 +756,7 @@ impl OptionKind {
|
||||
OptionKind::IpV4(_) => "ipv4",
|
||||
OptionKind::IpV6(_) => "ipv6",
|
||||
OptionKind::MaxRedirect(_) => "max-redirs",
|
||||
OptionKind::Output(_) => "output",
|
||||
OptionKind::PathAsIs(_) => "path-as-is",
|
||||
OptionKind::Proxy(_) => "proxy",
|
||||
OptionKind::Resolve(_) => "resolve",
|
||||
@ -785,6 +787,7 @@ impl OptionKind {
|
||||
OptionKind::IpV4(value) => value.to_string(),
|
||||
OptionKind::IpV6(value) => value.to_string(),
|
||||
OptionKind::MaxRedirect(value) => value.to_string(),
|
||||
OptionKind::Output(filename) => filename.value.to_string(),
|
||||
OptionKind::PathAsIs(value) => value.to_string(),
|
||||
OptionKind::Proxy(value) => value.to_string(),
|
||||
OptionKind::Resolve(value) => value.to_string(),
|
||||
|
@ -230,6 +230,7 @@ impl HtmlFormatter {
|
||||
OptionKind::IpV4(value) => self.fmt_bool_option(value),
|
||||
OptionKind::IpV6(value) => self.fmt_bool_option(value),
|
||||
OptionKind::MaxRedirect(value) => self.fmt_natural_option(value),
|
||||
OptionKind::Output(filename) => self.fmt_filename(filename),
|
||||
OptionKind::PathAsIs(value) => self.fmt_bool_option(value),
|
||||
OptionKind::Proxy(value) => self.fmt_template(value),
|
||||
OptionKind::Resolve(value) => self.fmt_template(value),
|
||||
|
@ -69,6 +69,16 @@ pub enum JsonErrorVariant {
|
||||
}
|
||||
|
||||
impl Error {
|
||||
/// Creates a new error for the position `pos`, of type `inner`.
|
||||
pub fn new(pos: Pos, recoverable: bool, inner: ParseError) -> Error {
|
||||
Error {
|
||||
pos,
|
||||
recoverable,
|
||||
inner,
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a recoverable error.
|
||||
pub fn recoverable(&self) -> Error {
|
||||
Error {
|
||||
pos: self.pos,
|
||||
@ -76,6 +86,8 @@ impl Error {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes a non recoverable error.
|
||||
pub fn non_recoverable(&self) -> Error {
|
||||
Error {
|
||||
pos: self.pos,
|
||||
|
@ -25,10 +25,11 @@ use crate::parser::reader::Reader;
|
||||
use crate::parser::string::*;
|
||||
use crate::parser::{expr, filename, ParseResult};
|
||||
|
||||
/// Parse an option in an `[Options]` section.
|
||||
pub fn parse(reader: &mut Reader) -> ParseResult<EntryOption> {
|
||||
let line_terminators = optional_line_terminators(reader)?;
|
||||
let space0 = zero_or_more_spaces(reader)?;
|
||||
let pos = reader.state.pos;
|
||||
let start = reader.state.pos;
|
||||
let option = reader.read_while(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '.');
|
||||
let space1 = zero_or_more_spaces(reader)?;
|
||||
try_literal(":", reader)?;
|
||||
@ -50,6 +51,7 @@ pub fn parse(reader: &mut Reader) -> ParseResult<EntryOption> {
|
||||
"key" => option_key(reader)?,
|
||||
"location" => option_follow_location(reader)?,
|
||||
"max-redirs" => option_max_redirect(reader)?,
|
||||
"output" => option_output(reader)?,
|
||||
"path-as-is" => option_path_as_is(reader)?,
|
||||
"proxy" => option_proxy(reader)?,
|
||||
"resolve" => option_resolve(reader)?,
|
||||
@ -59,16 +61,10 @@ pub fn parse(reader: &mut Reader) -> ParseResult<EntryOption> {
|
||||
"variable" => option_variable(reader)?,
|
||||
"verbose" => option_verbose(reader)?,
|
||||
"very-verbose" => option_very_verbose(reader)?,
|
||||
_ => {
|
||||
return Err(Error {
|
||||
pos,
|
||||
recoverable: true,
|
||||
inner: ParseError::InvalidOption,
|
||||
});
|
||||
}
|
||||
_ => return Err(Error::new(start, true, ParseError::InvalidOption)),
|
||||
};
|
||||
let line_terminator0 = line_terminator(reader)?;
|
||||
|
||||
let line_terminator0 = line_terminator(reader)?;
|
||||
Ok(EntryOption {
|
||||
line_terminators,
|
||||
space0,
|
||||
@ -159,6 +155,11 @@ fn option_max_redirect(reader: &mut Reader) -> ParseResult<OptionKind> {
|
||||
Ok(OptionKind::MaxRedirect(value))
|
||||
}
|
||||
|
||||
fn option_output(reader: &mut Reader) -> ParseResult<OptionKind> {
|
||||
let value = filename::parse(reader)?;
|
||||
Ok(OptionKind::Output(value))
|
||||
}
|
||||
|
||||
fn option_path_as_is(reader: &mut Reader) -> ParseResult<OptionKind> {
|
||||
let value = nonrecover(boolean_option, reader)?;
|
||||
Ok(OptionKind::PathAsIs(value))
|
||||
|
@ -296,6 +296,7 @@ impl ToJson for EntryOption {
|
||||
OptionKind::IpV4(value) => value.to_json(),
|
||||
OptionKind::IpV6(value) => value.to_json(),
|
||||
OptionKind::MaxRedirect(value) => JValue::Number(value.to_string()),
|
||||
OptionKind::Output(filename) => JValue::String(filename.value.clone()),
|
||||
OptionKind::PathAsIs(value) => value.to_json(),
|
||||
OptionKind::Proxy(value) => JValue::String(value.to_string()),
|
||||
OptionKind::Resolve(value) => JValue::String(value.to_string()),
|
||||
|
@ -891,6 +891,7 @@ impl Tokenizable for OptionKind {
|
||||
OptionKind::IpV4(value) => value.tokenize(),
|
||||
OptionKind::IpV6(value) => value.tokenize(),
|
||||
OptionKind::MaxRedirect(value) => value.tokenize(),
|
||||
OptionKind::Output(filename) => filename.tokenize(),
|
||||
OptionKind::PathAsIs(value) => value.tokenize(),
|
||||
OptionKind::Proxy(value) => value.tokenize(),
|
||||
OptionKind::Resolve(value) => value.tokenize(),
|
||||
|
Loading…
Reference in New Issue
Block a user