mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-11-13 06:54:54 +03:00
Add module curl to parse curl command-line
This commit is contained in:
parent
d88f79c468
commit
31d4783366
215
packages/hurlfmt/src/curl/args.rs
Normal file
215
packages/hurlfmt/src/curl/args.rs
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/// Split a `str` into a vec of String params
|
||||
pub fn split(s: &str) -> Result<Vec<String>, String> {
|
||||
let mut params = vec![];
|
||||
let mut parser = Parser::new(s);
|
||||
while let Some(param) = parser.param()? {
|
||||
params.push(param);
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
struct Parser {
|
||||
pub buffer: Vec<char>,
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn new(s: &str) -> Parser {
|
||||
let buffer = s.chars().collect();
|
||||
let index = 0;
|
||||
Parser { buffer, index }
|
||||
}
|
||||
|
||||
fn skip_spaces(&mut self) {
|
||||
while self.peek() == Some(' ') {
|
||||
self.read();
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&mut self) -> Option<char> {
|
||||
match self.buffer.get(self.index) {
|
||||
None => None,
|
||||
|
||||
Some(c) => {
|
||||
self.index += 1;
|
||||
Some(*c)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn peek(&mut self) -> Option<char> {
|
||||
self.buffer.get(self.index).copied()
|
||||
}
|
||||
|
||||
fn end_of_string(&self) -> bool {
|
||||
self.index == self.buffer.len()
|
||||
}
|
||||
|
||||
fn delimiter(&mut self) -> Option<char> {
|
||||
if self.peek() == Some('\'') {
|
||||
self.read();
|
||||
Some('\'')
|
||||
} else if self.peek() == Some('$') {
|
||||
let save = self.index;
|
||||
self.read();
|
||||
if self.peek() == Some('\'') {
|
||||
self.read();
|
||||
Some('\'')
|
||||
} else {
|
||||
self.index = save;
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn param(&mut self) -> Result<Option<String>, String> {
|
||||
self.skip_spaces();
|
||||
if self.end_of_string() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut value = "".to_string();
|
||||
if let Some(delimiter) = self.delimiter() {
|
||||
while let Some(c) = self.read() {
|
||||
if c == '\\' {
|
||||
if let Some(c) = self.read() {
|
||||
value.push(c);
|
||||
} else {
|
||||
return Err(format!("Invalid escape at index {}", self.index));
|
||||
}
|
||||
} else if c == delimiter {
|
||||
return Ok(Some(value));
|
||||
} else {
|
||||
value.push(c);
|
||||
}
|
||||
}
|
||||
Err(format!(
|
||||
"Missing delimiter {delimiter} at index {}",
|
||||
self.index
|
||||
))
|
||||
} else {
|
||||
loop {
|
||||
match self.read() {
|
||||
Some('\\') => {
|
||||
if let Some(c) = self.read() {
|
||||
value.push(c);
|
||||
} else {
|
||||
return Err(format!("Invalid escape at index {}", self.index));
|
||||
}
|
||||
}
|
||||
Some(' ') => return Ok(Some(value)),
|
||||
Some(c) => {
|
||||
value.push(c);
|
||||
}
|
||||
_ => return Ok(Some(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::curl::args;
|
||||
use crate::curl::args::Parser;
|
||||
|
||||
#[test]
|
||||
fn test_split() {
|
||||
let expected = vec!["AAA".to_string(), "BBB".to_string()];
|
||||
assert_eq!(args::split(r#"AAA BBB"#).unwrap(), expected);
|
||||
assert_eq!(args::split(r#"AAA BBB"#).unwrap(), expected);
|
||||
assert_eq!(args::split(r#" AAA BBB "#).unwrap(), expected);
|
||||
assert_eq!(args::split(r#"AAA 'BBB'"#).unwrap(), expected);
|
||||
assert_eq!(args::split(r#"AAA $'BBB'"#).unwrap(), expected);
|
||||
|
||||
let expected = vec!["'".to_string()];
|
||||
assert_eq!(args::split(r#"$'\''"#).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_error() {
|
||||
assert_eq!(
|
||||
args::split(r#"AAA 'BBB"#).err().unwrap(),
|
||||
"Missing delimiter ' at index 8".to_string()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_without_quote() {
|
||||
let mut parser = Parser::new("value");
|
||||
assert_eq!(parser.param().unwrap().unwrap(), "value".to_string());
|
||||
assert_eq!(parser.index, 5);
|
||||
|
||||
let mut parser = Parser::new(" value ");
|
||||
assert_eq!(parser.param().unwrap().unwrap(), "value".to_string());
|
||||
assert_eq!(parser.index, 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_with_quote() {
|
||||
let mut parser = Parser::new("'value'");
|
||||
assert_eq!(parser.param().unwrap().unwrap(), "value".to_string());
|
||||
assert_eq!(parser.index, 7);
|
||||
|
||||
let mut parser = Parser::new(" 'value' ");
|
||||
assert_eq!(parser.param().unwrap().unwrap(), "value".to_string());
|
||||
assert_eq!(parser.index, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dollar_prefix() {
|
||||
let mut parser = Parser::new("$'Test: \\''");
|
||||
assert_eq!(parser.param().unwrap().unwrap(), "Test: '".to_string());
|
||||
assert_eq!(parser.index, 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_param_missing_closing_quote() {
|
||||
let mut parser = Parser::new("'value");
|
||||
assert_eq!(
|
||||
parser.param().err().unwrap(),
|
||||
"Missing delimiter ' at index 6".to_string()
|
||||
);
|
||||
assert_eq!(parser.index, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_more_param() {
|
||||
assert_eq!(Parser::new("").param().unwrap(), None);
|
||||
assert_eq!(Parser::new(" ").param().unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delimiter() {
|
||||
let mut parser = Parser::new("value");
|
||||
assert_eq!(parser.delimiter(), None);
|
||||
assert_eq!(parser.index, 0);
|
||||
let mut parser = Parser::new("'value'");
|
||||
assert_eq!(parser.delimiter().unwrap(), '\'');
|
||||
assert_eq!(parser.index, 1);
|
||||
let mut parser = Parser::new("$'value'");
|
||||
assert_eq!(parser.delimiter().unwrap(), '\'');
|
||||
assert_eq!(parser.index, 2);
|
||||
let mut parser = Parser::new("$value");
|
||||
assert_eq!(parser.delimiter(), None);
|
||||
assert_eq!(parser.index, 0);
|
||||
}
|
||||
}
|
42
packages/hurlfmt/src/curl/commands.rs
Normal file
42
packages/hurlfmt/src/curl/commands.rs
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 clap::ArgAction;
|
||||
|
||||
pub fn headers() -> clap::Arg {
|
||||
clap::Arg::new("headers")
|
||||
.long("header")
|
||||
.short('H')
|
||||
.value_name("NAME:VALUE")
|
||||
.action(ArgAction::Append)
|
||||
.num_args(1)
|
||||
}
|
||||
|
||||
pub fn method() -> clap::Arg {
|
||||
clap::Arg::new("method")
|
||||
.long("request")
|
||||
.short('X')
|
||||
.value_name("METHOD")
|
||||
.num_args(1)
|
||||
}
|
||||
|
||||
pub fn url() -> clap::Arg {
|
||||
clap::Arg::new("url")
|
||||
.help("Sets the url to use")
|
||||
.required(false)
|
||||
.num_args(1)
|
||||
}
|
52
packages/hurlfmt/src/curl/matches.rs
Normal file
52
packages/hurlfmt/src/curl/matches.rs
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 clap::ArgMatches;
|
||||
|
||||
pub fn method(arg_matches: &ArgMatches) -> String {
|
||||
match get_string(arg_matches, "method") {
|
||||
None => "GET".to_string(),
|
||||
Some(v) => v,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn url(arg_matches: &ArgMatches) -> String {
|
||||
let s = get_string(arg_matches, "url").unwrap();
|
||||
if !s.starts_with("http") {
|
||||
format!("https://{s}")
|
||||
} else {
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
pub fn headers(arg_matches: &ArgMatches) -> Vec<String> {
|
||||
match get_strings(arg_matches, "headers") {
|
||||
None => vec![],
|
||||
Some(v) => v,
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
78
packages/hurlfmt/src/curl/mod.rs
Normal file
78
packages/hurlfmt/src/curl/mod.rs
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
mod args;
|
||||
mod commands;
|
||||
mod matches;
|
||||
|
||||
pub fn parse(s: &str) -> Result<String, String> {
|
||||
let mut command = clap::Command::new("curl")
|
||||
.arg(commands::headers())
|
||||
.arg(commands::method())
|
||||
.arg(commands::url());
|
||||
|
||||
let params = args::split(s)?;
|
||||
let arg_matches = match command.try_get_matches_from_mut(params) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
|
||||
let method = matches::method(&arg_matches);
|
||||
let url = matches::url(&arg_matches);
|
||||
let headers = matches::headers(&arg_matches);
|
||||
let s = format(&method, &url, headers);
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn format(method: &str, url: &str, headers: Vec<String>) -> String {
|
||||
let mut s = format!("{method} {url}");
|
||||
for header in headers {
|
||||
s.push_str(format!("\n{header}").as_str());
|
||||
}
|
||||
s.push('\n');
|
||||
s
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::curl::parse;
|
||||
|
||||
#[test]
|
||||
fn test_hello() {
|
||||
let hurl_str = r#"GET http://locahost:8000/hello
|
||||
"#;
|
||||
assert_eq!(parse("curl http://locahost:8000/hello").unwrap(), hurl_str);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_headers() {
|
||||
let hurl_str = r#"GET http://localhost:8000/custom-headers
|
||||
Fruit:Raspberry
|
||||
Fruit: Banana
|
||||
Test: '
|
||||
"#;
|
||||
assert_eq!(
|
||||
parse("curl http://localhost:8000/custom-headers -H 'Fruit:Raspberry' -H 'Fruit: Banana' -H $'Test: \\''").unwrap(),
|
||||
hurl_str
|
||||
);
|
||||
assert_eq!(
|
||||
parse("curl http://localhost:8000/custom-headers --header Fruit:Raspberry -H 'Fruit: Banana' -H $'Test: \\'' ").unwrap(),
|
||||
hurl_str
|
||||
);
|
||||
}
|
||||
}
|
@ -18,5 +18,6 @@
|
||||
#![cfg_attr(feature = "strict", deny(warnings))]
|
||||
|
||||
pub mod cli;
|
||||
pub mod curl;
|
||||
pub mod format;
|
||||
pub mod linter;
|
||||
|
Loading…
Reference in New Issue
Block a user