From b6f1c998fdd4f7fab87bcd8acb0b46bb2375b7ee Mon Sep 17 00:00:00 2001 From: Josh Watley Date: Thu, 25 Apr 2024 23:15:08 +0100 Subject: [PATCH 1/6] draft implementation of curl file import --- Cargo.lock | 86 +++++++++++++++++++++++ Cargo.toml | 1 + src/app/files/curl.rs | 138 +++++++++++++++++++++++++++++++++++++ src/app/files/mod.rs | 3 +- src/app/startup/args.rs | 2 +- src/app/startup/startup.rs | 13 +++- 6 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 src/app/files/curl.rs diff --git a/Cargo.lock b/Cargo.lock index 77834c8..78f301b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,6 +107,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + [[package]] name = "arboard" version = "3.3.2" @@ -126,6 +132,12 @@ dependencies = [ "x11rb", ] +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "async-compression" version = "0.4.6" @@ -155,6 +167,7 @@ dependencies = [ "lazy_static", "nestify", "parse_postman_collection", + "parser4curls", "ratatui", "regex", "reqwest", @@ -219,6 +232,18 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +[[package]] +name = "bitvec" +version = "0.19.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block" version = "0.1.6" @@ -700,6 +725,12 @@ dependencies = [ "percent-encoding 2.3.1", ] +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "futures-channel" version = "0.3.30" @@ -1029,6 +1060,19 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" +dependencies = [ + "arrayvec", + "bitflags 1.3.2", + "cfg-if", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.153" @@ -1152,6 +1196,19 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "nom" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" +dependencies = [ + "bitvec", + "funty", + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -1281,6 +1338,17 @@ dependencies = [ "url_serde", ] +[[package]] +name = "parser4curls" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786d5240eaa2d22c40fbd8e33bfa6662a0e7d7c86515dbcc92b0fc00bd1c2cb0" +dependencies = [ + "anyhow", + "nom", + "serde_json", +] + [[package]] name = "paste" version = "1.0.14" @@ -1423,6 +1491,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.8.5" @@ -1995,6 +2069,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "thiserror" version = "1.0.57" @@ -2636,6 +2716,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "x11rb" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index 4a1069e..a1958a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ jsonxf = "1.1.1" toml = "0.8.11" envfile = "0.2.1" parse_postman_collection = "0.2.3" +parser4curls = "0.1" clap = { version = "4.5.0", features = ["derive", "color"] } arboard = "3.3.2" tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/src/app/files/curl.rs b/src/app/files/curl.rs new file mode 100644 index 0000000..e1a25e8 --- /dev/null +++ b/src/app/files/curl.rs @@ -0,0 +1,138 @@ +use std::fs; +use std::path::PathBuf; +use std::sync::{Arc, RwLock}; + +use parser4curls::{parse, Curl}; +use reqwest::Url; + +use crate::app::app::App; +use crate::app::startup::args::ARGS; +use crate::panic_error; +use crate::request::auth::Auth; +use crate::request::body::ContentType; +use crate::request::collection::Collection; +use crate::request::method::Method; +use crate::request::request::{KeyValue, Request}; + +impl App<'_> { + pub fn import_curl_file(&mut self, path_buf: &PathBuf) { + println!("Parsing curl file"); + + let original_curl = match fs::read_to_string(path_buf) { + Ok(original_curl) => original_curl, + Err(e) => panic_error(format!("Could not read cURL file\n\t{e}")), + }; + + let curl = match parse(original_curl.as_str()) { + // The first element is the curl command - for now we only support one per file + Ok(curl) => curl.1, + Err(e) => panic_error(format!("Could not parse cURL\n\t{e}")), + }; + + let req_name = path_buf.file_name().unwrap().to_str().unwrap().to_string(); + + // We will check if theres an 'imported collection', if so we will append, else create + let imported_exists = self.collections.iter().any(|c| c.name == "imported"); + + if imported_exists { + let imported = self + .collections + .iter_mut() + .find(|c| c.name == "imported") + .unwrap(); + imported.requests.push(Arc::new(RwLock::new(parse_request(&curl, req_name)))); + } else { + let collection = Collection { + name: "imported".to_string(), + requests: vec![Arc::new(RwLock::new(parse_request(&curl, req_name)))], + path: ARGS.directory.join("imported.json"), + }; + + self.collections.push(collection); + } + + let imported_index = self.collections.iter().position(|c| c.name == "imported").unwrap(); + self.save_collection_to_file(imported_index); + } +} + +fn parse_request(curl: &Curl, req_name: String) -> Request { + print!("Found cURL: {:?}", curl); + + let mut request = Request::default(); + + request.name = req_name; + + // Parse the URL so we can transform it + let mut curl_url = match Url::parse(curl.url) { + Ok(url) => url, + Err(e) => panic_error(format!("Could not parse URL\n\t{e}")), + }; + + + /* QUERY PARAMS */ + + request.params = curl_url + .query_pairs() + .map(|(k, v)| KeyValue { + enabled: true, + data: (k.to_string(), v.to_string()), + }) + .collect(); + + /* URL */ + + curl_url.set_query(None); + request.url = curl_url.to_string(); + + /* METHOD */ + + request.method = get_http_method(&curl); + + /* HEADERS */ + + request.headers = curl + .options_headers_more + .iter() + .filter(|&(k, _)| !k.eq_ignore_ascii_case("Authorization")) // Exclude Authorization header, as that will be handled by the auth field + .map(|(k, v)| KeyValue { + enabled: true, + data: (k.to_string(), v.to_string()), + }) + .collect(); + + /* AUTH */ + + if let Some(auth) = curl.options_more.get("u") { + let parts: Vec<&str> = auth.splitn(2, ':').collect(); + if parts.len() == 2 { + request.auth = Auth::BasicAuth(parts[0].to_string(), parts[1].to_string()); + } + } else if let Some(auth) = curl.options_headers_more.get("Authorization") { + let parts: Vec<&str> = auth.split_whitespace().collect(); + if parts.len() > 1 && parts[0].starts_with("Bearer") { + request.auth = Auth::BearerToken(parts[1].to_string()); + } + } + + /* BODY */ + + // TODO: Handle content type + request.body = ContentType::Raw(curl.options_data_raw.to_string()); + + request +} + +fn get_http_method(curl: &Curl) -> Method { + if let Some(x) = curl.options_more.get("X") { + match x { + &"PUT" => Method::PUT, + &"DELETE" => Method::DELETE, + _ => Method::GET, + } + } else if !curl.options_data_raw.is_empty() { + Method::POST + } else { + Method::GET + } +} diff --git a/src/app/files/mod.rs b/src/app/files/mod.rs index 6e2e60e..2a27520 100644 --- a/src/app/files/mod.rs +++ b/src/app/files/mod.rs @@ -3,4 +3,5 @@ pub mod environment; pub mod log; pub mod config; pub mod key_bindings; -mod postman; \ No newline at end of file +mod postman; +mod curl; \ No newline at end of file diff --git a/src/app/startup/args.rs b/src/app/startup/args.rs index 2c48e62..077be5b 100644 --- a/src/app/startup/args.rs +++ b/src/app/startup/args.rs @@ -21,7 +21,7 @@ pub struct Args { #[derive(Debug, Subcommand, PartialEq)] pub enum Command { - /// Used to import a collection file such as Postman + /// Used to import a collection file such as Postman, or a file containing a curl Import(ImportArgs), } diff --git a/src/app/startup/startup.rs b/src/app/startup/startup.rs index 348f5bc..4bfb48e 100644 --- a/src/app/startup/startup.rs +++ b/src/app/startup/startup.rs @@ -18,11 +18,20 @@ impl App<'_> { if let Some(command) = &ARGS.command { match command { Command::Import(import_args) => { - self.import_postman_collection(&import_args.path, import_args.max_depth.unwrap_or(99)); + print!("Importing: {}", import_args.path.display()); + + let extension = import_args.path.extension().unwrap_or_default().to_str().unwrap_or_default(); + if extension == "json" { + // If the file is json, we attempt to import postman collection + self.import_postman_collection(&import_args.path, import_args.max_depth.unwrap_or(99)); + } else { + // We attempt to import a curl file + self.import_curl_file(&import_args.path); + } } } } - + self } From a01c7e289452a66aea8d488ef3788b2b509240e2 Mon Sep 17 00:00:00 2001 From: Josh Watley Date: Fri, 26 Apr 2024 17:49:13 +0100 Subject: [PATCH 2/6] adding collection prompt picker --- src/app/app.rs | 9 ++++- src/app/app_logic/change_app_state.rs | 4 +++ src/app/app_logic/collection.rs | 34 +++++++++++++++++++ src/app/app_states.rs | 16 +++++++++ src/app/events.rs | 23 +++++++++++++ src/app/files/curl.rs | 32 +++-------------- src/app/startup/startup.rs | 2 +- .../ui/popups/append_or_create_collection.rs | 31 +++++++++++++++++ src/app/ui/popups/mod.rs | 1 + src/app/ui/ui.rs | 1 + 10 files changed, 124 insertions(+), 29 deletions(-) create mode 100644 src/app/ui/popups/append_or_create_collection.rs diff --git a/src/app/app.rs b/src/app/app.rs index 207c96b..c1badcc 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -18,6 +18,7 @@ use crate::app::ui::result_tabs::RequestResultTabs; use crate::app::ui::views::RequestView; use crate::request::collection::Collection; use crate::request::environment::Environment; +use crate::request::request::Request; use crate::utils::choice_popup::ChoicePopup; use crate::utils::cookies_popup::CookiesPopup; use crate::utils::help_popup::HelpPopup; @@ -71,6 +72,8 @@ pub struct App<'a> { pub new_request_popup: NewRequestPopup, pub rename_request_input: TextInput, + pub append_or_create_collection_input: TextInput, + pub delete_collection_popup: ValidationPopup, pub delete_request_popup: ValidationPopup, @@ -100,7 +103,8 @@ pub struct App<'a> { /* Others */ - pub syntax_highlighting: SyntaxHighlighting + pub syntax_highlighting: SyntaxHighlighting, + pub tmp_request: Option, } impl App<'_> { @@ -145,6 +149,7 @@ impl App<'_> { }, new_collection_input: TextInput::default(), + append_or_create_collection_input: TextInput::default(), rename_collection_input: TextInput::default(), new_request_popup: NewRequestPopup::default(), rename_request_input: TextInput::default(), @@ -184,6 +189,8 @@ impl App<'_> { theme_set: ThemeSet::load_defaults(), last_highlighted: Arc::new(RwLock::new(None)), }, + + tmp_request: None, } } diff --git a/src/app/app_logic/change_app_state.rs b/src/app/app_logic/change_app_state.rs index df9fdda..6e4d3b4 100644 --- a/src/app/app_logic/change_app_state.rs +++ b/src/app/app_logic/change_app_state.rs @@ -52,6 +52,10 @@ impl App<'_> { self.state = AppState::CreatingNewCollection; } + pub fn append_or_create_collection_state(&mut self){ + self.state = AppState::AppendingOrCreatingCollection; + } + pub fn create_new_request_state(&mut self) { let collections_length = self.collections.len(); diff --git a/src/app/app_logic/collection.rs b/src/app/app_logic/collection.rs index d35eb4e..bd4615e 100644 --- a/src/app/app_logic/collection.rs +++ b/src/app/app_logic/collection.rs @@ -153,6 +153,40 @@ impl App<'_> { _ => {} } } + + pub fn append_or_create_collection(&mut self) { + let collection_name = &self.append_or_create_collection_input.text; + + if collection_name.trim().is_empty() { + return; + } + + // Check to see if the collection exists, if it does - append the request + let existing_collection = self.collections.iter().any(|c| c.name == *collection_name); + if existing_collection { + // Append the request to the existing collection + let collection = self + .collections + .iter_mut() + .find(|c| c.name == *collection_name) + .unwrap(); + collection.requests.push(Arc::new(RwLock::new(self.tmp_request.take().unwrap()))); + } else { + // Create a new collection and add the request + let collection = Collection { + name: collection_name.clone(), + requests: vec![Arc::new(RwLock::new(self.tmp_request.take().unwrap()))], + path: ARGS.directory.join(format!("{}.json", collection_name.clone())), + }; + self.collections.push(collection); + } + + // Now save the collection to file + let collection_index = self.collections.iter().position(|c| c.name == *collection_name).unwrap(); + self.save_collection_to_file(collection_index); + self.normal_state(); + self.tmp_request = None; + } pub fn new_collection(&mut self) { let new_collection_name = &self.new_collection_input.text; diff --git a/src/app/app_states.rs b/src/app/app_states.rs index 7679279..4f599a6 100644 --- a/src/app/app_states.rs +++ b/src/app/app_states.rs @@ -40,6 +40,9 @@ pub enum AppState { #[strum(to_string = "Creating new collection")] CreatingNewCollection, + #[strum(to_string = "Add to or create collection")] + AppendingOrCreatingCollection, + #[strum(to_string = "Creating new request")] CreatingNewRequest, @@ -98,6 +101,7 @@ pub fn next_app_state(app_state: &AppState) -> AppState { EditingCookies => ChoosingElementToCreate, ChoosingElementToCreate => CreatingNewCollection, CreatingNewCollection => CreatingNewRequest, + AppendingOrCreatingCollection => Normal, CreatingNewRequest => DeletingCollection, DeletingCollection => DeletingRequest, DeletingRequest => RenamingCollection, @@ -125,6 +129,7 @@ pub fn previous_app_state(app_state: &AppState) -> AppState { ChoosingElementToCreate => EditingCookies, CreatingNewCollection => ChoosingElementToCreate, CreatingNewRequest => CreatingNewCollection, + AppendingOrCreatingCollection => Normal, DeletingCollection => CreatingNewRequest, DeletingRequest => DeletingCollection, RenamingCollection => DeletingRequest, @@ -201,6 +206,16 @@ impl AppState { CreatingCollectionMoveCursorRight(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.move_cursor_right], "Move cursor right", Some("Right"))), CreatingCollectionCharInput(EventKeyBinding::new(vec![], "Char input", None)), ], + AppendingOrCreatingCollection => vec![ + GoBackToMainMenu(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.cancel], "Cancel", Some("Cancel"))), + AppendOrCreateCollection(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.validate], "Validate", Some("Validate"))), + + AppendingOrCreatingCollectionDeleteCharBackward(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.delete_backward], "Delete char backward", Some("Delete"))), + AppendingOrCreatingCollectionDeleteCharForward(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.delete_forward], "Delete char forward", Some("Backspace"))), + AppendingOrCreatingCollectionMoveCursorLeft(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.move_cursor_left], "Move cursor left", Some("Left"))), + AppendingOrCreatingCollectionMoveCursorRight(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.move_cursor_right], "Move cursor right", Some("Right"))), + AppendingOrCreatingCollectionCharInput(EventKeyBinding::new(vec![], "Char input", None)), + ], CreatingNewRequest => vec![ GoBackToMainMenu(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.cancel], "Cancel", Some("Cancel"))), CreateNewRequest(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.validate], "Validate", Some("Validate"))), @@ -520,6 +535,7 @@ impl App<'_> { Normal | ChoosingElementToCreate | CreatingNewCollection | CreatingNewRequest | + AppendingOrCreatingCollection | DisplayingCookies | EditingCookies => Line::from(self.state.to_string().white().on_dark_gray()), DeletingCollection | RenamingCollection => { diff --git a/src/app/events.rs b/src/app/events.rs index c4b0c6e..0ffa778 100644 --- a/src/app/events.rs +++ b/src/app/events.rs @@ -57,6 +57,13 @@ pub enum AppEvent { CreatingCollectionMoveCursorRight(EventKeyBinding), CreatingCollectionCharInput(EventKeyBinding), + AppendOrCreateCollection(EventKeyBinding), + AppendingOrCreatingCollectionDeleteCharBackward(EventKeyBinding), + AppendingOrCreatingCollectionDeleteCharForward(EventKeyBinding), + AppendingOrCreatingCollectionMoveCursorLeft(EventKeyBinding), + AppendingOrCreatingCollectionMoveCursorRight(EventKeyBinding), + AppendingOrCreatingCollectionCharInput(EventKeyBinding), + CreateNewRequest(EventKeyBinding), CreatingRequestDeleteCharBackward(EventKeyBinding), CreatingRequestDeleteCharForward(EventKeyBinding), @@ -365,6 +372,16 @@ impl App<'_> { _ => {} }, + AppendOrCreateCollection(_) => self.append_or_create_collection(), + AppendingOrCreatingCollectionDeleteCharBackward(_) => self.append_or_create_collection_input.delete_char_forward(), + AppendingOrCreatingCollectionDeleteCharForward(_) => self.append_or_create_collection_input.delete_char_backward(), + AppendingOrCreatingCollectionMoveCursorLeft(_) => self.append_or_create_collection_input.move_cursor_left(), + AppendingOrCreatingCollectionMoveCursorRight(_) => self.append_or_create_collection_input.move_cursor_right(), + AppendingOrCreatingCollectionCharInput(_) => match key { + KeyCombination { codes: One(KeyCode::Char(char)), .. } => self.append_or_create_collection_input.enter_char(char), + _ => {} + }, + CreateNewRequest(_) => self.new_request(), CreatingRequestDeleteCharBackward(_) => self.new_request_popup.text_input.delete_char_forward(), CreatingRequestDeleteCharForward(_) => self.new_request_popup.text_input.delete_char_backward(), @@ -659,6 +676,12 @@ impl AppEvent { ChooseElementToCreateMoveCursorRight(event_key_bindings) | SelectElementToCreate(event_key_bindings) | CreateNewCollection(event_key_bindings) | + AppendOrCreateCollection(event_key_bindings) | + AppendingOrCreatingCollectionDeleteCharBackward(event_key_bindings) | + AppendingOrCreatingCollectionDeleteCharForward(event_key_bindings) | + AppendingOrCreatingCollectionMoveCursorLeft(event_key_bindings) | + AppendingOrCreatingCollectionMoveCursorRight(event_key_bindings) | + AppendingOrCreatingCollectionCharInput(event_key_bindings) | CreatingCollectionDeleteCharBackward(event_key_bindings) | CreatingCollectionDeleteCharForward(event_key_bindings) | CreatingCollectionMoveCursorLeft(event_key_bindings) | diff --git a/src/app/files/curl.rs b/src/app/files/curl.rs index e1a25e8..da3cf8d 100644 --- a/src/app/files/curl.rs +++ b/src/app/files/curl.rs @@ -1,16 +1,12 @@ use std::fs; use std::path::PathBuf; -use std::sync::{Arc, RwLock}; - use parser4curls::{parse, Curl}; use reqwest::Url; use crate::app::app::App; -use crate::app::startup::args::ARGS; use crate::panic_error; use crate::request::auth::Auth; use crate::request::body::ContentType; -use crate::request::collection::Collection; use crate::request::method::Method; use crate::request::request::{KeyValue, Request}; @@ -31,33 +27,15 @@ impl App<'_> { let req_name = path_buf.file_name().unwrap().to_str().unwrap().to_string(); - // We will check if theres an 'imported collection', if so we will append, else create - let imported_exists = self.collections.iter().any(|c| c.name == "imported"); - - if imported_exists { - let imported = self - .collections - .iter_mut() - .find(|c| c.name == "imported") - .unwrap(); - imported.requests.push(Arc::new(RwLock::new(parse_request(&curl, req_name)))); - } else { - let collection = Collection { - name: "imported".to_string(), - requests: vec![Arc::new(RwLock::new(parse_request(&curl, req_name)))], - path: ARGS.directory.join("imported.json"), - }; - - self.collections.push(collection); - } - - let imported_index = self.collections.iter().position(|c| c.name == "imported").unwrap(); - self.save_collection_to_file(imported_index); + // We store the request in a temporary variable so we can add it to the collection + self.tmp_request = Some(parse_request(&curl, req_name)); + // Defined by the input, we can either add the request to an existing collection or create a new one + self.append_or_create_collection_state(); } } fn parse_request(curl: &Curl, req_name: String) -> Request { - print!("Found cURL: {:?}", curl); + println!("Found cURL: {:#?}", curl); let mut request = Request::default(); diff --git a/src/app/startup/startup.rs b/src/app/startup/startup.rs index 4bfb48e..69a11c6 100644 --- a/src/app/startup/startup.rs +++ b/src/app/startup/startup.rs @@ -18,7 +18,7 @@ impl App<'_> { if let Some(command) = &ARGS.command { match command { Command::Import(import_args) => { - print!("Importing: {}", import_args.path.display()); + println!("Importing file: {}", import_args.path.display()); let extension = import_args.path.extension().unwrap_or_default().to_str().unwrap_or_default(); if extension == "json" { diff --git a/src/app/ui/popups/append_or_create_collection.rs b/src/app/ui/popups/append_or_create_collection.rs new file mode 100644 index 0000000..b8f8fec --- /dev/null +++ b/src/app/ui/popups/append_or_create_collection.rs @@ -0,0 +1,31 @@ +use ratatui::Frame; +use ratatui::prelude::{Color, Style}; +use ratatui::widgets::{Block, Borders, Clear, Paragraph}; +use crate::app::app::App; +use crate::utils::centered_rect::centered_rect; + +impl App<'_> { + pub fn render_append_or_create_collection_popup(&mut self, frame: &mut Frame) { + let popup_block = Block::default() + .title("What collection to import to?") + .borders(Borders::ALL) + .style(Style::default().bg(Color::DarkGray)); + + let area = centered_rect(50, 3, frame.size()); + let new_request_area = popup_block.inner(area); + + let adjusted_input_length = new_request_area.width as usize; + let (padded_text, input_cursor_position) = self.append_or_create_collection_input.get_padded_text_and_cursor(adjusted_input_length); + + let new_request_paragraph = Paragraph::new(padded_text); + + frame.render_widget(Clear, area); + frame.render_widget(popup_block, area); + frame.render_widget(new_request_paragraph, new_request_area); + + frame.set_cursor( + new_request_area.x + input_cursor_position as u16, + new_request_area.y + ) + } +} \ No newline at end of file diff --git a/src/app/ui/popups/mod.rs b/src/app/ui/popups/mod.rs index 19ffb3b..563e5ef 100644 --- a/src/app/ui/popups/mod.rs +++ b/src/app/ui/popups/mod.rs @@ -2,6 +2,7 @@ pub mod help; pub mod cookies; pub mod creating_new_collection; pub mod creating_new_request; +pub mod append_or_create_collection; pub mod deleting_collection; pub mod deleting_request; pub mod request_settings; diff --git a/src/app/ui/ui.rs b/src/app/ui/ui.rs index 7175c2b..a0220d5 100644 --- a/src/app/ui/ui.rs +++ b/src/app/ui/ui.rs @@ -116,6 +116,7 @@ impl App<'_> { DisplayingCookies | EditingCookies => self.render_cookies_popup(frame), ChoosingElementToCreate => self.render_creating_element_popup(frame), CreatingNewCollection => self.render_creating_new_collection_popup(frame), + AppendingOrCreatingCollection => self.render_append_or_create_collection_popup(frame), CreatingNewRequest => self.render_creating_new_request_popup(frame), DeletingCollection => self.render_deleting_collection_popup(frame), DeletingRequest => self.render_deleting_request_popup(frame), From fed391c15d62d9e3ede75cee13c4c0981feaa0e4 Mon Sep 17 00:00:00 2001 From: Josh Watley Date: Fri, 26 Apr 2024 18:10:44 +0100 Subject: [PATCH 3/6] better error handling of curl import --- src/app/app_logic/collection.rs | 10 ++++++++-- src/app/files/curl.rs | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/app/app_logic/collection.rs b/src/app/app_logic/collection.rs index bd4615e..dd307ee 100644 --- a/src/app/app_logic/collection.rs +++ b/src/app/app_logic/collection.rs @@ -161,6 +161,11 @@ impl App<'_> { return; } + let pending_saved_req = match &self.tmp_request { + Some(req) => req, + None => return + }; + // Check to see if the collection exists, if it does - append the request let existing_collection = self.collections.iter().any(|c| c.name == *collection_name); if existing_collection { @@ -170,12 +175,13 @@ impl App<'_> { .iter_mut() .find(|c| c.name == *collection_name) .unwrap(); - collection.requests.push(Arc::new(RwLock::new(self.tmp_request.take().unwrap()))); + + collection.requests.push(Arc::new(RwLock::new(pending_saved_req.clone()))); } else { // Create a new collection and add the request let collection = Collection { name: collection_name.clone(), - requests: vec![Arc::new(RwLock::new(self.tmp_request.take().unwrap()))], + requests: vec![Arc::new(RwLock::new(pending_saved_req.clone()))], path: ARGS.directory.join(format!("{}.json", collection_name.clone())), }; self.collections.push(collection); diff --git a/src/app/files/curl.rs b/src/app/files/curl.rs index da3cf8d..53e8cee 100644 --- a/src/app/files/curl.rs +++ b/src/app/files/curl.rs @@ -25,15 +25,26 @@ impl App<'_> { Err(e) => panic_error(format!("Could not parse cURL\n\t{e}")), }; - let req_name = path_buf.file_name().unwrap().to_str().unwrap().to_string(); + // For now, the stem of the file is the name of the request + let req_name = match extract_file_name(path_buf){ + Ok(name) => name, + Err(e) => panic_error(format!("Could not extract file name\n\t{e}")) + }; - // We store the request in a temporary variable so we can add it to the collection + // This way we can parse the curl file before application loads, handling any errors. But only apply it once + // the application starts self.tmp_request = Some(parse_request(&curl, req_name)); - // Defined by the input, we can either add the request to an existing collection or create a new one self.append_or_create_collection_state(); } } +fn extract_file_name(path_buf: &PathBuf) -> Result { + path_buf.file_stem() + .ok_or_else(|| "Filename not found".to_string()) + .and_then(|name| name.to_str().ok_or_else(|| "Filename is not valid UTF-8".to_string())) + .map(|name| name.to_string()) +} + fn parse_request(curl: &Curl, req_name: String) -> Request { println!("Found cURL: {:#?}", curl); @@ -47,7 +58,6 @@ fn parse_request(curl: &Curl, req_name: String) -> Request { Err(e) => panic_error(format!("Could not parse URL\n\t{e}")), }; - /* QUERY PARAMS */ request.params = curl_url From 7189aa3dc5c93040c827cb667b334fad8709810e Mon Sep 17 00:00:00 2001 From: Josh Watley Date: Thu, 2 May 2024 19:35:50 +0100 Subject: [PATCH 4/6] Revert "adding collection prompt picker" This reverts commit a01c7e289452a66aea8d488ef3788b2b509240e2. --- src/app/app.rs | 9 +- src/app/app_logic/change_app_state.rs | 4 - src/app/app_logic/collection.rs | 118 ++++++++---------- src/app/app_states.rs | 16 --- src/app/events.rs | 23 ---- src/app/files/curl.rs | 32 ++++- src/app/startup/startup.rs | 2 +- .../ui/popups/append_or_create_collection.rs | 31 ----- src/app/ui/popups/mod.rs | 1 - src/app/ui/ui.rs | 1 - 10 files changed, 80 insertions(+), 157 deletions(-) delete mode 100644 src/app/ui/popups/append_or_create_collection.rs diff --git a/src/app/app.rs b/src/app/app.rs index c1badcc..207c96b 100644 --- a/src/app/app.rs +++ b/src/app/app.rs @@ -18,7 +18,6 @@ use crate::app::ui::result_tabs::RequestResultTabs; use crate::app::ui::views::RequestView; use crate::request::collection::Collection; use crate::request::environment::Environment; -use crate::request::request::Request; use crate::utils::choice_popup::ChoicePopup; use crate::utils::cookies_popup::CookiesPopup; use crate::utils::help_popup::HelpPopup; @@ -72,8 +71,6 @@ pub struct App<'a> { pub new_request_popup: NewRequestPopup, pub rename_request_input: TextInput, - pub append_or_create_collection_input: TextInput, - pub delete_collection_popup: ValidationPopup, pub delete_request_popup: ValidationPopup, @@ -103,8 +100,7 @@ pub struct App<'a> { /* Others */ - pub syntax_highlighting: SyntaxHighlighting, - pub tmp_request: Option, + pub syntax_highlighting: SyntaxHighlighting } impl App<'_> { @@ -149,7 +145,6 @@ impl App<'_> { }, new_collection_input: TextInput::default(), - append_or_create_collection_input: TextInput::default(), rename_collection_input: TextInput::default(), new_request_popup: NewRequestPopup::default(), rename_request_input: TextInput::default(), @@ -189,8 +184,6 @@ impl App<'_> { theme_set: ThemeSet::load_defaults(), last_highlighted: Arc::new(RwLock::new(None)), }, - - tmp_request: None, } } diff --git a/src/app/app_logic/change_app_state.rs b/src/app/app_logic/change_app_state.rs index 6e4d3b4..df9fdda 100644 --- a/src/app/app_logic/change_app_state.rs +++ b/src/app/app_logic/change_app_state.rs @@ -52,10 +52,6 @@ impl App<'_> { self.state = AppState::CreatingNewCollection; } - pub fn append_or_create_collection_state(&mut self){ - self.state = AppState::AppendingOrCreatingCollection; - } - pub fn create_new_request_state(&mut self) { let collections_length = self.collections.len(); diff --git a/src/app/app_logic/collection.rs b/src/app/app_logic/collection.rs index dd307ee..6bdc0fd 100644 --- a/src/app/app_logic/collection.rs +++ b/src/app/app_logic/collection.rs @@ -1,11 +1,11 @@ -use std::sync::{Arc, RwLock}; use crate::app::app::App; use crate::app::startup::args::ARGS; use crate::request::auth::Auth; use crate::request::body::ContentType; use crate::request::collection::Collection; -use crate::request::request::{DEFAULT_HEADERS, Request}; +use crate::request::request::{Request, DEFAULT_HEADERS}; use crate::request::settings::RequestSettings; +use std::sync::{Arc, RwLock}; impl App<'_> { pub fn reset_inputs(&mut self) { @@ -25,7 +25,8 @@ impl App<'_> { let local_selected_request = self.get_selected_request_as_local(); let selected_request = local_selected_request.read().unwrap(); - self.url_text_input.enter_str(&selected_request.url_with_params_to_string()); + self.url_text_input + .enter_str(&selected_request.url_with_params_to_string()); self.query_params_table.rows = selected_request.params.clone(); self.headers_table.rows = selected_request.headers.clone(); @@ -35,10 +36,12 @@ impl App<'_> { let param_text = match selection { (x, 0) => selected_request.params[x].data.0.clone(), (x, 1) => selected_request.params[x].data.1.clone(), - _ => String::new() // Should not happen + _ => String::new(), // Should not happen }; - self.query_params_table.selection_text_input.enter_str(¶m_text); + self.query_params_table + .selection_text_input + .enter_str(¶m_text); } match &selected_request.auth { @@ -67,10 +70,12 @@ impl App<'_> { let header_text = match selection { (x, 0) => selected_request.headers[x].data.0.clone(), (x, 1) => selected_request.headers[x].data.1.clone(), - _ => String::new() // Should not happen + _ => String::new(), // Should not happen }; - self.headers_table.selection_text_input.enter_str(&header_text); + self.headers_table + .selection_text_input + .enter_str(&header_text); } match &selected_request.body { @@ -87,24 +92,30 @@ impl App<'_> { let form_text = match selection { (x, 0) => form[x].data.0.clone(), (x, 1) => form[x].data.1.clone(), - _ => String::new() // Should not happen + _ => String::new(), // Should not happen }; - self.body_form_table.selection_text_input.enter_str(&form_text); + self.body_form_table + .selection_text_input + .enter_str(&form_text); } self.refresh_body_textarea(&String::new()); } - ContentType::File(file_path) => { + ContentType::File(file_path) => { self.body_file_text_input.enter_str(file_path); - }, - ContentType::Raw(body) | ContentType::Json(body) | ContentType::Xml(body) | ContentType::Html(body) | ContentType::Javascript(body) => { + } + ContentType::Raw(body) + | ContentType::Json(body) + | ContentType::Xml(body) + | ContentType::Html(body) + | ContentType::Javascript(body) => { self.body_form_table.rows = Vec::new(); self.refresh_body_textarea(body); } } } - + pub fn reset_cursors(&mut self) { self.url_text_input.reset_cursor(); self.query_params_table.selection_text_input.reset_cursor(); @@ -123,7 +134,7 @@ impl App<'_> { self.update_headers_selection(); self.update_body_table_selection(); self.refresh_result_scrollbars(); - + self.select_request_state(); } } @@ -138,14 +149,14 @@ impl App<'_> { match self.collections_tree.state.selected().len() { 1 => { self.collections_tree.state.toggle_selected(); - }, + } 2 => { self.select_request(); - }, + } _ => {} } } - + pub fn new_element(&mut self) { match self.creation_popup.selection { 0 => self.create_new_collection_state(), @@ -154,46 +165,6 @@ impl App<'_> { } } - pub fn append_or_create_collection(&mut self) { - let collection_name = &self.append_or_create_collection_input.text; - - if collection_name.trim().is_empty() { - return; - } - - let pending_saved_req = match &self.tmp_request { - Some(req) => req, - None => return - }; - - // Check to see if the collection exists, if it does - append the request - let existing_collection = self.collections.iter().any(|c| c.name == *collection_name); - if existing_collection { - // Append the request to the existing collection - let collection = self - .collections - .iter_mut() - .find(|c| c.name == *collection_name) - .unwrap(); - - collection.requests.push(Arc::new(RwLock::new(pending_saved_req.clone()))); - } else { - // Create a new collection and add the request - let collection = Collection { - name: collection_name.clone(), - requests: vec![Arc::new(RwLock::new(pending_saved_req.clone()))], - path: ARGS.directory.join(format!("{}.json", collection_name.clone())), - }; - self.collections.push(collection); - } - - // Now save the collection to file - let collection_index = self.collections.iter().position(|c| c.name == *collection_name).unwrap(); - self.save_collection_to_file(collection_index); - self.normal_state(); - self.tmp_request = None; - } - pub fn new_collection(&mut self) { let new_collection_name = &self.new_collection_input.text; @@ -211,12 +182,14 @@ impl App<'_> { let new_collection = Collection { name: new_collection_name.clone(), requests: vec![], - path: ARGS.directory.join(format!("{}.json", new_collection_name.clone())) + path: ARGS + .directory + .join(format!("{}.json", new_collection_name.clone())), }; self.collections.push(new_collection); - let collection_index= self.collections.len() - 1; + let collection_index = self.collections.len() - 1; self.save_collection_to_file(collection_index); self.normal_state(); @@ -238,7 +211,9 @@ impl App<'_> { let selected_collection = self.new_request_popup.selected_collection; - self.collections[selected_collection].requests.push(Arc::new(RwLock::new(new_request))); + self.collections[selected_collection] + .requests + .push(Arc::new(RwLock::new(new_request))); self.save_collection_to_file(selected_collection); self.normal_state(); @@ -268,7 +243,9 @@ impl App<'_> { pub fn delete_request(&mut self) { let selected_request_index = self.collections_tree.state.selected(); - self.collections[selected_request_index[0]].requests.remove(selected_request_index[1]); + self.collections[selected_request_index[0]] + .requests + .remove(selected_request_index[1]); self.collections_tree.state.select(Vec::new()); self.collections_tree.selected = None; @@ -310,14 +287,17 @@ impl App<'_> { } let selected_request_index = self.collections_tree.state.selected(); - let local_selected_request = self.get_request_as_local_from_indexes(&(selected_request_index[0], selected_request_index[1])); + let local_selected_request = self.get_request_as_local_from_indexes(&( + selected_request_index[0], + selected_request_index[1], + )); { let mut selected_request = local_selected_request.write().unwrap(); - + selected_request.name = new_request_name.to_string(); } - + self.save_collection_to_file(selected_request_index[0]); self.normal_state(); } @@ -341,7 +321,9 @@ impl App<'_> { selection[1] -= 1; // Insert the request at its new index - self.collections[selection[0]].requests.insert(selection[1], request); + self.collections[selection[0]] + .requests + .insert(selection[1], request); // Update the selection in order to move with the element self.collections_tree.state.select(selection.clone()); @@ -368,11 +350,13 @@ impl App<'_> { selection[1] += 1; // Insert the request at its new index - self.collections[selection[0]].requests.insert(selection[1], request); + self.collections[selection[0]] + .requests + .insert(selection[1], request); // Update the selection in order to move with the element self.collections_tree.state.select(selection.clone()); self.save_collection_to_file(selection[0]); } -} \ No newline at end of file +} diff --git a/src/app/app_states.rs b/src/app/app_states.rs index 4f599a6..7679279 100644 --- a/src/app/app_states.rs +++ b/src/app/app_states.rs @@ -40,9 +40,6 @@ pub enum AppState { #[strum(to_string = "Creating new collection")] CreatingNewCollection, - #[strum(to_string = "Add to or create collection")] - AppendingOrCreatingCollection, - #[strum(to_string = "Creating new request")] CreatingNewRequest, @@ -101,7 +98,6 @@ pub fn next_app_state(app_state: &AppState) -> AppState { EditingCookies => ChoosingElementToCreate, ChoosingElementToCreate => CreatingNewCollection, CreatingNewCollection => CreatingNewRequest, - AppendingOrCreatingCollection => Normal, CreatingNewRequest => DeletingCollection, DeletingCollection => DeletingRequest, DeletingRequest => RenamingCollection, @@ -129,7 +125,6 @@ pub fn previous_app_state(app_state: &AppState) -> AppState { ChoosingElementToCreate => EditingCookies, CreatingNewCollection => ChoosingElementToCreate, CreatingNewRequest => CreatingNewCollection, - AppendingOrCreatingCollection => Normal, DeletingCollection => CreatingNewRequest, DeletingRequest => DeletingCollection, RenamingCollection => DeletingRequest, @@ -206,16 +201,6 @@ impl AppState { CreatingCollectionMoveCursorRight(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.move_cursor_right], "Move cursor right", Some("Right"))), CreatingCollectionCharInput(EventKeyBinding::new(vec![], "Char input", None)), ], - AppendingOrCreatingCollection => vec![ - GoBackToMainMenu(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.cancel], "Cancel", Some("Cancel"))), - AppendOrCreateCollection(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.validate], "Validate", Some("Validate"))), - - AppendingOrCreatingCollectionDeleteCharBackward(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.delete_backward], "Delete char backward", Some("Delete"))), - AppendingOrCreatingCollectionDeleteCharForward(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.delete_forward], "Delete char forward", Some("Backspace"))), - AppendingOrCreatingCollectionMoveCursorLeft(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.move_cursor_left], "Move cursor left", Some("Left"))), - AppendingOrCreatingCollectionMoveCursorRight(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.move_cursor_right], "Move cursor right", Some("Right"))), - AppendingOrCreatingCollectionCharInput(EventKeyBinding::new(vec![], "Char input", None)), - ], CreatingNewRequest => vec![ GoBackToMainMenu(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.cancel], "Cancel", Some("Cancel"))), CreateNewRequest(EventKeyBinding::new(vec![key_bindings.generic.text_inputs.text_input.validate], "Validate", Some("Validate"))), @@ -535,7 +520,6 @@ impl App<'_> { Normal | ChoosingElementToCreate | CreatingNewCollection | CreatingNewRequest | - AppendingOrCreatingCollection | DisplayingCookies | EditingCookies => Line::from(self.state.to_string().white().on_dark_gray()), DeletingCollection | RenamingCollection => { diff --git a/src/app/events.rs b/src/app/events.rs index 0ffa778..c4b0c6e 100644 --- a/src/app/events.rs +++ b/src/app/events.rs @@ -57,13 +57,6 @@ pub enum AppEvent { CreatingCollectionMoveCursorRight(EventKeyBinding), CreatingCollectionCharInput(EventKeyBinding), - AppendOrCreateCollection(EventKeyBinding), - AppendingOrCreatingCollectionDeleteCharBackward(EventKeyBinding), - AppendingOrCreatingCollectionDeleteCharForward(EventKeyBinding), - AppendingOrCreatingCollectionMoveCursorLeft(EventKeyBinding), - AppendingOrCreatingCollectionMoveCursorRight(EventKeyBinding), - AppendingOrCreatingCollectionCharInput(EventKeyBinding), - CreateNewRequest(EventKeyBinding), CreatingRequestDeleteCharBackward(EventKeyBinding), CreatingRequestDeleteCharForward(EventKeyBinding), @@ -372,16 +365,6 @@ impl App<'_> { _ => {} }, - AppendOrCreateCollection(_) => self.append_or_create_collection(), - AppendingOrCreatingCollectionDeleteCharBackward(_) => self.append_or_create_collection_input.delete_char_forward(), - AppendingOrCreatingCollectionDeleteCharForward(_) => self.append_or_create_collection_input.delete_char_backward(), - AppendingOrCreatingCollectionMoveCursorLeft(_) => self.append_or_create_collection_input.move_cursor_left(), - AppendingOrCreatingCollectionMoveCursorRight(_) => self.append_or_create_collection_input.move_cursor_right(), - AppendingOrCreatingCollectionCharInput(_) => match key { - KeyCombination { codes: One(KeyCode::Char(char)), .. } => self.append_or_create_collection_input.enter_char(char), - _ => {} - }, - CreateNewRequest(_) => self.new_request(), CreatingRequestDeleteCharBackward(_) => self.new_request_popup.text_input.delete_char_forward(), CreatingRequestDeleteCharForward(_) => self.new_request_popup.text_input.delete_char_backward(), @@ -676,12 +659,6 @@ impl AppEvent { ChooseElementToCreateMoveCursorRight(event_key_bindings) | SelectElementToCreate(event_key_bindings) | CreateNewCollection(event_key_bindings) | - AppendOrCreateCollection(event_key_bindings) | - AppendingOrCreatingCollectionDeleteCharBackward(event_key_bindings) | - AppendingOrCreatingCollectionDeleteCharForward(event_key_bindings) | - AppendingOrCreatingCollectionMoveCursorLeft(event_key_bindings) | - AppendingOrCreatingCollectionMoveCursorRight(event_key_bindings) | - AppendingOrCreatingCollectionCharInput(event_key_bindings) | CreatingCollectionDeleteCharBackward(event_key_bindings) | CreatingCollectionDeleteCharForward(event_key_bindings) | CreatingCollectionMoveCursorLeft(event_key_bindings) | diff --git a/src/app/files/curl.rs b/src/app/files/curl.rs index 53e8cee..b08be26 100644 --- a/src/app/files/curl.rs +++ b/src/app/files/curl.rs @@ -1,12 +1,16 @@ use std::fs; use std::path::PathBuf; +use std::sync::{Arc, RwLock}; + use parser4curls::{parse, Curl}; use reqwest::Url; use crate::app::app::App; +use crate::app::startup::args::ARGS; use crate::panic_error; use crate::request::auth::Auth; use crate::request::body::ContentType; +use crate::request::collection::Collection; use crate::request::method::Method; use crate::request::request::{KeyValue, Request}; @@ -31,10 +35,28 @@ impl App<'_> { Err(e) => panic_error(format!("Could not extract file name\n\t{e}")) }; - // This way we can parse the curl file before application loads, handling any errors. But only apply it once - // the application starts - self.tmp_request = Some(parse_request(&curl, req_name)); - self.append_or_create_collection_state(); + // We will check if theres an 'imported collection', if so we will append, else create + let imported_exists = self.collections.iter().any(|c| c.name == "imported"); + + if imported_exists { + let imported = self + .collections + .iter_mut() + .find(|c| c.name == "imported") + .unwrap(); + imported.requests.push(Arc::new(RwLock::new(parse_request(&curl, req_name)))); + } else { + let collection = Collection { + name: "imported".to_string(), + requests: vec![Arc::new(RwLock::new(parse_request(&curl, req_name)))], + path: ARGS.directory.join("imported.json"), + }; + + self.collections.push(collection); + } + + let imported_index = self.collections.iter().position(|c| c.name == "imported").unwrap(); + self.save_collection_to_file(imported_index); } } @@ -46,7 +68,7 @@ fn extract_file_name(path_buf: &PathBuf) -> Result { } fn parse_request(curl: &Curl, req_name: String) -> Request { - println!("Found cURL: {:#?}", curl); + print!("Found cURL: {:?}", curl); let mut request = Request::default(); diff --git a/src/app/startup/startup.rs b/src/app/startup/startup.rs index 69a11c6..4bfb48e 100644 --- a/src/app/startup/startup.rs +++ b/src/app/startup/startup.rs @@ -18,7 +18,7 @@ impl App<'_> { if let Some(command) = &ARGS.command { match command { Command::Import(import_args) => { - println!("Importing file: {}", import_args.path.display()); + print!("Importing: {}", import_args.path.display()); let extension = import_args.path.extension().unwrap_or_default().to_str().unwrap_or_default(); if extension == "json" { diff --git a/src/app/ui/popups/append_or_create_collection.rs b/src/app/ui/popups/append_or_create_collection.rs deleted file mode 100644 index b8f8fec..0000000 --- a/src/app/ui/popups/append_or_create_collection.rs +++ /dev/null @@ -1,31 +0,0 @@ -use ratatui::Frame; -use ratatui::prelude::{Color, Style}; -use ratatui::widgets::{Block, Borders, Clear, Paragraph}; -use crate::app::app::App; -use crate::utils::centered_rect::centered_rect; - -impl App<'_> { - pub fn render_append_or_create_collection_popup(&mut self, frame: &mut Frame) { - let popup_block = Block::default() - .title("What collection to import to?") - .borders(Borders::ALL) - .style(Style::default().bg(Color::DarkGray)); - - let area = centered_rect(50, 3, frame.size()); - let new_request_area = popup_block.inner(area); - - let adjusted_input_length = new_request_area.width as usize; - let (padded_text, input_cursor_position) = self.append_or_create_collection_input.get_padded_text_and_cursor(adjusted_input_length); - - let new_request_paragraph = Paragraph::new(padded_text); - - frame.render_widget(Clear, area); - frame.render_widget(popup_block, area); - frame.render_widget(new_request_paragraph, new_request_area); - - frame.set_cursor( - new_request_area.x + input_cursor_position as u16, - new_request_area.y - ) - } -} \ No newline at end of file diff --git a/src/app/ui/popups/mod.rs b/src/app/ui/popups/mod.rs index 563e5ef..19ffb3b 100644 --- a/src/app/ui/popups/mod.rs +++ b/src/app/ui/popups/mod.rs @@ -2,7 +2,6 @@ pub mod help; pub mod cookies; pub mod creating_new_collection; pub mod creating_new_request; -pub mod append_or_create_collection; pub mod deleting_collection; pub mod deleting_request; pub mod request_settings; diff --git a/src/app/ui/ui.rs b/src/app/ui/ui.rs index a0220d5..7175c2b 100644 --- a/src/app/ui/ui.rs +++ b/src/app/ui/ui.rs @@ -116,7 +116,6 @@ impl App<'_> { DisplayingCookies | EditingCookies => self.render_cookies_popup(frame), ChoosingElementToCreate => self.render_creating_element_popup(frame), CreatingNewCollection => self.render_creating_new_collection_popup(frame), - AppendingOrCreatingCollection => self.render_append_or_create_collection_popup(frame), CreatingNewRequest => self.render_creating_new_request_popup(frame), DeletingCollection => self.render_deleting_collection_popup(frame), DeletingRequest => self.render_deleting_request_popup(frame), From ae71028803cdfcfd4ff7e1a43dbdc3f95ecb4bf3 Mon Sep 17 00:00:00 2001 From: Josh Watley Date: Thu, 2 May 2024 20:46:56 +0100 Subject: [PATCH 5/6] support curl import location from command line --- src/app/files/curl.rs | 45 +++++++++++++++++++------------------- src/app/startup/args.rs | 10 +++++++-- src/app/startup/startup.rs | 18 +++++++-------- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/app/files/curl.rs b/src/app/files/curl.rs index b08be26..96d03a8 100644 --- a/src/app/files/curl.rs +++ b/src/app/files/curl.rs @@ -15,8 +15,8 @@ use crate::request::method::Method; use crate::request::request::{KeyValue, Request}; impl App<'_> { - pub fn import_curl_file(&mut self, path_buf: &PathBuf) { - println!("Parsing curl file"); + pub fn import_curl_file(&mut self, path_buf: &PathBuf, save_to: &str) { + println!("Importing curl file from {:?} to {:?}", path_buf, save_to); let original_curl = match fs::read_to_string(path_buf) { Ok(original_curl) => original_curl, @@ -29,46 +29,37 @@ impl App<'_> { Err(e) => panic_error(format!("Could not parse cURL\n\t{e}")), }; - // For now, the stem of the file is the name of the request - let req_name = match extract_file_name(path_buf){ - Ok(name) => name, - Err(e) => panic_error(format!("Could not extract file name\n\t{e}")) + let (collection_name, req_name) = match validate_save_to(save_to) { + Ok((collection_name, req_name)) => (collection_name, req_name), + Err(e) => panic_error(format!("Could not validate chosen collection/request\n\t{e}")), }; - // We will check if theres an 'imported collection', if so we will append, else create - let imported_exists = self.collections.iter().any(|c| c.name == "imported"); + let collection_exists = self.collections.iter().any(|c| c.name == collection_name); - if imported_exists { + if collection_exists { let imported = self .collections .iter_mut() - .find(|c| c.name == "imported") + .find(|c| c.name == collection_name.clone()) .unwrap(); imported.requests.push(Arc::new(RwLock::new(parse_request(&curl, req_name)))); } else { let collection = Collection { - name: "imported".to_string(), + name: collection_name.clone(), requests: vec![Arc::new(RwLock::new(parse_request(&curl, req_name)))], - path: ARGS.directory.join("imported.json"), + path: ARGS.directory.join(collection_name.clone() + ".json"), }; self.collections.push(collection); } - let imported_index = self.collections.iter().position(|c| c.name == "imported").unwrap(); + let imported_index = self.collections.iter().position(|c| c.name == collection_name).unwrap(); self.save_collection_to_file(imported_index); } } -fn extract_file_name(path_buf: &PathBuf) -> Result { - path_buf.file_stem() - .ok_or_else(|| "Filename not found".to_string()) - .and_then(|name| name.to_str().ok_or_else(|| "Filename is not valid UTF-8".to_string())) - .map(|name| name.to_string()) -} - fn parse_request(curl: &Curl, req_name: String) -> Request { - print!("Found cURL: {:?}", curl); + println!("Found cURL: {:?}", curl); let mut request = Request::default(); @@ -127,7 +118,7 @@ fn parse_request(curl: &Curl, req_name: String) -> Request { /* BODY */ - // TODO: Handle content type + // TODO: Handle content type, for now we just assume raw request.body = ContentType::Raw(curl.options_data_raw.to_string()); request @@ -146,3 +137,13 @@ fn get_http_method(curl: &Curl) -> Method { Method::GET } } + +fn validate_save_to(save_to: &str) -> Result<(String, String), String> { + let parts: Vec<&str> = save_to.split('/').collect(); + + if parts.len() != 2 { + return Err("Path is not in the format collection/request name".to_string()); + } + + Ok((parts[0].to_string(), parts[1].to_string())) +} diff --git a/src/app/startup/args.rs b/src/app/startup/args.rs index 077be5b..7633692 100644 --- a/src/app/startup/args.rs +++ b/src/app/startup/args.rs @@ -2,7 +2,7 @@ use std::env; use std::path::PathBuf; use clap::{Parser, Subcommand}; use lazy_static::lazy_static; -use crate::{panic_error}; +use crate::panic_error; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -27,7 +27,13 @@ pub enum Command { #[derive(Debug, clap::Args, PartialEq)] pub struct ImportArgs { - /// A file to import, only Postman v2.1 JSON collection for now + /// The type of file to import, Postman v2.1 JSON collection (postman), or a curl file (curl) + pub import_type: String, + + /// The path to save the imported collection including the request name (collection/request_name) + pub save_to: String, + + /// A file to import, only Postman v2.1 JSON and curl files are supported pub path: PathBuf, /// Max depth at which import should stop creating nested collections and only get the deeper requests diff --git a/src/app/startup/startup.rs b/src/app/startup/startup.rs index 4bfb48e..bc683bc 100644 --- a/src/app/startup/startup.rs +++ b/src/app/startup/startup.rs @@ -18,15 +18,15 @@ impl App<'_> { if let Some(command) = &ARGS.command { match command { Command::Import(import_args) => { - print!("Importing: {}", import_args.path.display()); - - let extension = import_args.path.extension().unwrap_or_default().to_str().unwrap_or_default(); - if extension == "json" { - // If the file is json, we attempt to import postman collection - self.import_postman_collection(&import_args.path, import_args.max_depth.unwrap_or(99)); - } else { - // We attempt to import a curl file - self.import_curl_file(&import_args.path); + println!("Importing: {}", import_args.path.display()); + + if import_args.import_type == "postman" { + self.import_postman_collection( + &import_args.path, + import_args.max_depth.unwrap_or(99), + ); + } else if import_args.import_type == "curl" { + self.import_curl_file(&import_args.path, &import_args.save_to); } } } From b308b7d9a58c18c8fa804803fd0484a4c1576d1f Mon Sep 17 00:00:00 2001 From: julien-cpsn Date: Sun, 5 May 2024 22:16:14 +0200 Subject: [PATCH 6/6] Added import from curl file(s) --- Cargo.lock | 214 +++++++++++++++++- Cargo.toml | 5 +- README.md | 2 + .../depth_2/depth_3/test_curl_3 | 18 ++ .../depth_2/depth_3/test_curl_4 | 4 + .../depth_2/depth_3/test_curl_6 | 2 + .../depth_2/depth_3/test_curl_7 | 3 + .../recursive_curls/depth_2/test_curl_2 | 16 ++ .../recursive_curls/depth_2/test_curl_5 | 2 + import_tests/recursive_curls/test_curl_1 | 23 ++ import_tests/test_curl | 23 ++ src/app/files/curl.rs | 149 ------------ src/app/files/import/curl.rs | 202 +++++++++++++++++ src/app/files/import/mod.rs | 2 + src/app/files/{ => import}/postman.rs | 0 src/app/files/mod.rs | 3 +- src/app/startup/args.rs | 57 +++-- src/app/startup/startup.rs | 26 ++- src/request/body.rs | 14 ++ 19 files changed, 575 insertions(+), 190 deletions(-) create mode 100644 import_tests/recursive_curls/depth_2/depth_3/test_curl_3 create mode 100644 import_tests/recursive_curls/depth_2/depth_3/test_curl_4 create mode 100644 import_tests/recursive_curls/depth_2/depth_3/test_curl_6 create mode 100644 import_tests/recursive_curls/depth_2/depth_3/test_curl_7 create mode 100644 import_tests/recursive_curls/depth_2/test_curl_2 create mode 100644 import_tests/recursive_curls/depth_2/test_curl_5 create mode 100644 import_tests/recursive_curls/test_curl_1 create mode 100644 import_tests/test_curl delete mode 100644 src/app/files/curl.rs create mode 100644 src/app/files/import/curl.rs create mode 100644 src/app/files/import/mod.rs rename src/app/files/{ => import}/postman.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index be4f57c..8fcf9fa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,6 +157,7 @@ dependencies = [ "cookie_store", "crokey", "crossterm", + "curl-parser", "image", "indexmap", "jsonxf", @@ -181,6 +182,7 @@ dependencies = [ "tui-big-text", "tui-textarea", "tui-tree-widget", + "walkdir", ] [[package]] @@ -249,6 +251,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "boa_ast" version = "0.18.0" @@ -464,9 +475,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -474,9 +485,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.0" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -486,11 +497,11 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.58", @@ -604,6 +615,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -695,6 +715,32 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curl-parser" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9ac8e8203aa44a586de649b10fd57c51df4d8a6c7d2cb83a1c87f162529e9e" +dependencies = [ + "base64 0.21.7", + "form_urlencoded", + "http", + "minijinja", + "pest", + "pest_derive", + "serde", + "snafu", +] + [[package]] name = "darling" version = "0.20.8" @@ -783,6 +829,16 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.4" @@ -794,6 +850,12 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dyn-clone" version = "1.0.17" @@ -1006,6 +1068,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -1097,6 +1169,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.4" @@ -1521,6 +1599,12 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "memo-map" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374c335b2df19e62d4cb323103473cbc6510980253119180de862d89184f6a83" + [[package]] name = "memoffset" version = "0.9.1" @@ -1546,6 +1630,18 @@ dependencies = [ "unicase", ] +[[package]] +name = "minijinja" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e877d961d4f96ce13615862322df7c0b6d169d40cab71a7ef3f9b9e594451e" +dependencies = [ + "memo-map", + "self_cell", + "serde", + "v_htmlescape", +] + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -1784,6 +1880,51 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "pest_meta" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "phf" version = "0.11.2" @@ -2336,6 +2477,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + [[package]] name = "semver" version = "1.0.22" @@ -2407,6 +2554,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "signal-hook" version = "0.3.17" @@ -2464,6 +2622,28 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "snailquote" version = "0.3.1" @@ -2554,7 +2734,7 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "rustversion", @@ -2916,6 +3096,18 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "unicase" version = "2.7.0" @@ -3026,6 +3218,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "v_htmlescape" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index d4fdfb0..75bc3fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,14 +38,15 @@ jsonxf = "1.1.1" toml = "0.8.11" boa_engine = { version = "0.18.0", default-features = false } parse_postman_collection = "0.2.3" -parser4curls = "0.1" -clap = { version = "4.5.0", features = ["derive", "color"] } +curl-parser = { version = "0.3.1", default-features = false } +clap = { version = "4.5.4", features = ["derive", "color", "suggestions"] } arboard = "3.3.2" tokio = { version = "1.36.0", features = ["rt", "rt-multi-thread", "macros"] } parking_lot = { version = "0.12.2", features = ["serde"] } strum = "0.26.2" lazy_static = "1.4.0" nestify = "0.3.3" +walkdir = "2.5.0" snailquote = "0.3.1" indexmap = { version = "2.2.6", features = ["serde"] } base64 = "0.22.0" diff --git a/README.md b/README.md index 683ec92..eaf2f07 100644 --- a/README.md +++ b/README.md @@ -276,6 +276,7 @@ https://github.com/NachoNievaG/atac.nvim | [toml](https://github.com/toml-rs/toml) | 0.8.11 | Serialize & Deserialize application config files | | [boa_engine](https://github.com/boa-dev/boa) | 0.18.0 | Create Javascript runtimes. Used for pre and post request scripts | | [My fork](https://github.com/Julien-cpsn/postman-collection-rs) of [postman_collection](https://github.com/mandrean/postman-collection-rs) | 0.2.1 | Deserialize Postman collection files | +| [curl-parser](https://github.com/tyrchen/curl-parser) | 0.3.1 | Parse cURL request files | | [clap](https://github.com/clap-rs/clap) | 4.5.0 | Command Line Argument Parser | | [arboard](https://github.com/1Password/arboard) | 3.3.2 | Copy response body to clipboard | | [tokio](https://github.com/tokio-rs/tokio) | 1.0.0 | Handle asynchronous requests | @@ -283,6 +284,7 @@ https://github.com/NachoNievaG/atac.nvim | [strum](https://github.com/Peternator7/strum) | 0.26.2 | Enum facilities | | [lazy_static](https://github.com/rust-lang-nursery/lazy-static.rs) | 1.4.0 | Allows for more flexible constants. Mainly used for accessing CLI arguments everywhere | | [nestify](https://github.com/snowfoxsh/nestify) | 0.3.3 | Used to nest struct definitions | +| [walkdir](https://github.com/BurntSushi/walkdir) | 2.5.0 | Recursively retrieve files | | [snailquote](https://github.com/euank/snailquote) | 0.3.1 | Unescape string | | [indexmap](https://github.com/indexmap-rs/indexmap) | 2.2.6 | Ordered hashmap. Used in environments to preserve files' values order | | [base64](https://github.com/marshallpierce/rust-base64) | 0.22.0 | Encode auth. | diff --git a/import_tests/recursive_curls/depth_2/depth_3/test_curl_3 b/import_tests/recursive_curls/depth_2/depth_3/test_curl_3 new file mode 100644 index 0000000..b1c2835 --- /dev/null +++ b/import_tests/recursive_curls/depth_2/depth_3/test_curl_3 @@ -0,0 +1,18 @@ +curl 'https://github.com/Julien-cpsn/ATAC' \ + -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \ + -H 'accept-language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7' \ + -H 'cache-control: max-age=0' \ + -H 'cookie: _octo=GH1.1.1959269116.1687182163; _device_id=2811f3b85ff75158a08c56ca3ddbe2df; user_session=LLeRFVtOylajDHngA0_zzjNV2-T0N1S22N0YkkOqFMbJ4djZ; __Host-user_session_same_site=LLeRFVtOylajDHngA0_zzjNV2-T0N1S22N0YkkOqFMbJ4djZ; logged_in=yes; dotcom_user=Julien-cpsn; color_mode=%7B%22color_mode%22%3A%22dark%22%2C%22light_theme%22%3A%7B%22name%22%3A%22light%22%2C%22color_mode%22%3A%22light%22%7D%2C%22dark_theme%22%3A%7B%22name%22%3A%22dark_dimmed%22%2C%22color_mode%22%3A%22dark%22%7D%7D; preferred_color_mode=dark; tz=Europe%2FParis; has_recent_activity=1; _gh_sess=Jt6ZOdffY%2FjmplKDbN0QjKwWSAJJ1DmL5yLrH%2BA7Rjr9R%2B8TvXv7Q8so51Cr5ls27Hh3H6A7pDkZaAqaS0Q5HvEA4E6ia4srs8Z9a4bxJciA%2BkpgSZ7%2FIqaiMY40Fdoi7ewczp3qxwQvBmVNpQXexBPnPxpIMsivDlJQvxVX%2F%2Ff5UpK%2BVtEsDXwe1yPxc3j9nqDxnqHspncdM9PzX7zWizSIveAi0sHyFxU3yaogHmCACKZgVMFXmBnrMaIj80jQMFZo9w4LHdu5aOZATGiZKqP61kXXxT0POepF4I%2F4WWrUftneLW4ju4QEGDGOc6dMXMCHaFPVjiT9BMn8QcLJ6Vg%2BOFPB5RVSc0aBe%2B29WCho0iIvt3A64uIxf687A3JZOzmIjiWIJZpSq9nleumJTVZTi%2FJZaq2njqU4Gxjp4uPoIHqzDBg9YpEbWFmFXVoo4WuAYb28ZAD2qNOHM%2FoUUyePjtMmSpi8V7hPRqf3Ci0QDlBvX5bpnfDePbsCPpJhSHqys9oFZBqkxEYJq%2FthAuf3ZXu2euhO46NlOdcVb%2Bl2kzSbCuRHNyrkXTtt1rdlFpcp5x6ZXAvaJKGFAQuHU7jV%2B7FiAotgnz%2BIvEAEfAqLhr6dbCmdNUrKQcG%2BZk9dZhORKA4hG0sLDQ2eZUngq2dbwsLRQp3XWlZiPg%3D%3D--cq4qh6N4eKXeyUzf--fmdJG1EtntyfVOgFbk8NHA%3D%3D' \ + -H 'dnt: 1' \ + -H 'if-none-match: W/"2c8e9a97779f0715b9a44638c8ddabef"' \ + -H 'priority: u=0, i' \ + -H 'referer: https://github.com/' \ + -H 'sec-ch-ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"' \ + -H 'sec-ch-ua-mobile: ?0' \ + -H 'sec-ch-ua-platform: "Windows"' \ + -H 'sec-fetch-dest: document' \ + -H 'sec-fetch-mode: navigate' \ + -H 'sec-fetch-site: same-origin' \ + -H 'sec-fetch-user: ?1' \ + -H 'upgrade-insecure-requests: 1' \ + -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' \ No newline at end of file diff --git a/import_tests/recursive_curls/depth_2/depth_3/test_curl_4 b/import_tests/recursive_curls/depth_2/depth_3/test_curl_4 new file mode 100644 index 0000000..3b058f5 --- /dev/null +++ b/import_tests/recursive_curls/depth_2/depth_3/test_curl_4 @@ -0,0 +1,4 @@ +curl 'https://httpbin.org/put' \ + -X PUT \ + -H 'Content-Type: application/json' \ + -d 'test' \ No newline at end of file diff --git a/import_tests/recursive_curls/depth_2/depth_3/test_curl_6 b/import_tests/recursive_curls/depth_2/depth_3/test_curl_6 new file mode 100644 index 0000000..f7d6f66 --- /dev/null +++ b/import_tests/recursive_curls/depth_2/depth_3/test_curl_6 @@ -0,0 +1,2 @@ +curl --location 'https://httpbin.org/bearer' \ +--header 'Authorization: Bearer my_bearer=' \ No newline at end of file diff --git a/import_tests/recursive_curls/depth_2/depth_3/test_curl_7 b/import_tests/recursive_curls/depth_2/depth_3/test_curl_7 new file mode 100644 index 0000000..c8476fe --- /dev/null +++ b/import_tests/recursive_curls/depth_2/depth_3/test_curl_7 @@ -0,0 +1,3 @@ +curl --location 'https://httpbin.org/post' \ +--header 'Content-Type: application/octet-stream' \ +--data '@/C:/Users/u248244/Documents/Rust/ATAC/import_tests/swagger-petstore-v2.1.0.json' \ No newline at end of file diff --git a/import_tests/recursive_curls/depth_2/test_curl_2 b/import_tests/recursive_curls/depth_2/test_curl_2 new file mode 100644 index 0000000..f443ff9 --- /dev/null +++ b/import_tests/recursive_curls/depth_2/test_curl_2 @@ -0,0 +1,16 @@ +curl 'https://docs.rs/clap/latest/clap/' \ + -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \ + -H 'accept-language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7' \ + -H 'cache-control: max-age=0' \ + -H 'dnt: 1' \ + -H 'priority: u=0, i' \ + -H 'referer: https://rust-lang-nursery.github.io/' \ + -H 'sec-ch-ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"' \ + -H 'sec-ch-ua-mobile: ?0' \ + -H 'sec-ch-ua-platform: "Windows"' \ + -H 'sec-fetch-dest: document' \ + -H 'sec-fetch-mode: navigate' \ + -H 'sec-fetch-site: cross-site' \ + -H 'sec-fetch-user: ?1' \ + -H 'upgrade-insecure-requests: 1' \ + -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' \ No newline at end of file diff --git a/import_tests/recursive_curls/depth_2/test_curl_5 b/import_tests/recursive_curls/depth_2/test_curl_5 new file mode 100644 index 0000000..898c8ac --- /dev/null +++ b/import_tests/recursive_curls/depth_2/test_curl_5 @@ -0,0 +1,2 @@ +curl https://reqbin.com/echo \ + -u "login:password" \ No newline at end of file diff --git a/import_tests/recursive_curls/test_curl_1 b/import_tests/recursive_curls/test_curl_1 new file mode 100644 index 0000000..3e2052c --- /dev/null +++ b/import_tests/recursive_curls/test_curl_1 @@ -0,0 +1,23 @@ +curl 'https://www.google.com/search?q=some+search&rlz=1C1ONGR_frFR1063FR1063&oq=some+search&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIGCAEQRRg80gEINzU2OGowajeoAgCwAgA&sourceid=chrome&ie=UTF-8' \ + -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \ + -H 'accept-language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7' \ + -H 'cookie: HSID=Av_zeISP2IoC5ffne; SSID=AKgvP_7KP4Q2hAa7J; APISID=BHGY7ZbmZbJ_sm1O/ACh2Z88aE94LAktBN; SAPISID=8Pljvq5JQ81l6i-W/AE7yudhCMIhSx07aA; __Secure-1PAPISID=8Pljvq5JQ81l6i-W/AE7yudhCMIhSx07aA; __Secure-3PAPISID=8Pljvq5JQ81l6i-W/AE7yudhCMIhSx07aA; SID=g.a000iQi34M-7qEaVOkMFrycXCJM8EqqhOMd2x_eD98hvFBtmDGiYVrLlfIjHEiQDF9UR7JMTcwACgYKAW0SAQASFQHGX2Mi67FwpyVk-XamQ4h1n4-YzhoVAUF8yKpmHMVXUV6LSLVgLdR39TtR0076; __Secure-1PSID=g.a000iQi34M-7qEaVOkMFrycXCJM8EqqhOMd2x_eD98hvFBtmDGiYeQ0eDf3PWlHwVAxIQCQZSQACgYKAQ4SAQASFQHGX2Mi8cOFgeIIFTgntS5oW6LNRhoVAUF8yKqTtznLmD3NAcbAgrp4pPgX0076; __Secure-3PSID=g.a000iQi34M-7qEaVOkMFrycXCJM8EqqhOMd2x_eD98hvFBtmDGiYymEt7XyxPJc9XnYV2-s3kwACgYKAagSAQASFQHGX2Mi0MZRs5Ln80PaVraKG-YuwxoVAUF8yKrm83-kkzD22gxazewDzerG0076; OTZ=7515057_48_52_123900_48_436380; NID=513=UWSHg2m07oid_oICg5QPNg42NEXDwmQ-1Re8H5-JeTIKQN1fIIyJmwfPw38LxWLmaGKJLV8e6qrxX_8LEOaL6TRw6LwYyIsdCj-UjQ7yhHF3yApwcWTm9hjTE4cjq4bvD4PO2--Ghy8TYtl_dU3W2HG4TILc6HLhEsyScvpc_P0pWNgKGI2rIOqJyuj9ZhRaYDl1yMeX5jvHTTh5V3cGDaGw9P3iebXTaL23iauLhPI0oTM6zjdyLDpGJrxX2b3qRvosGS2MgOOF1V3cKx_Wn-7MJWj8bKvlvvw2EEJ6w_MkQMNJ7npEhLNjw8Jwp-PZPaVEJ7Pv1tqyWBU0aw1M5aP2KDdFSXrbNNM6o2Yn3fWIzboPmrxufXlIh6a2CVh4uR6CTgXKjrQaUjpbWKZtRg-rqtDuKW14B83lzZjvQR79fUrkGQYjmlmQ1FkU21QXk_ln780-gVTYULlYAUsHlyNFnMq4ynqdaT5LO7ZJW_T36Wv-URtZamHha-9sX1MmRbqqusYMxh3Nr8Uo7y9IA241D2WIKGi-OJj8z65np9nvOCAlzJkIS9WGpV27NDQqokwxJD87aPw4dibeVp2nQLwm1qRTAnwdp4t6ZB3AaAGiVzHuAIvYIfpgHYWUMTSMSIabQhSYGTmvhf5jHA; SEARCH_SAMESITE=CgQIhJsB; AEC=AQTF6HyWxVlqhwtKFurLp8MJDzz-RmIqYYq9Yp2YChWpu2seU7RYeOBnMm0; __Secure-ENID=19.SE=M390Zgw8_lsFMHxTOQYX4mBtx9KVzOdYUw7PL5JfrPlkgJ7YK1NQdieYWRmFd5g7E_PFfugqNnx6oAo7zQ5AxPT0CXQmoI-vnJAQTLR0RNxMP63vrZz7cFqkAEviEJpvhRGf4vUbrgUB_26PLDSAxejYEt6kZGc3ryi9sXgYPXFdJCK06gecG_JC2HF7o5tm0J79_C6rxle9ahzi9UXDN9QhbZ9POmaWKHefIIA3TjcYseTo-mWGLorvQZsVQUHi3hyf0zMq1QoK-2bGY9G1nC8NaFb2fNR3mxF7bu7aAhun1Hl4gHZDtX3CHghXfBGwz6Gx7JPWkSptAdu7z-9Qm1ykttSIQg66r8gysaDwEAfAvwSzSW_-fLlvE-cmjeKk_px3anvx1XiNRGxhNHXQriaQsQjqxES43FFiYGp7wLwCJsenMEB-nTI79JZ9Vh05_qknchOP7QMEXtARm6S7rPnosjBbv0e84VvzarbLpMegmkzWaId32X83ON_2h0y_yzV78urk6qzCf_5CTyD98B6opbWfExK9UGDGql7VMimj8KtqeMCXB9yNe9yKRVhx5uPnfOEIKoBY1e1SKS6UQvtzXv2n-tNqd1iy1OSejS86-J8Lnke8yJKjrfor; DV=Q2LJvyNVsmxWACJ-bZgLX549pDGc9NiJV5getGLf6QAAABAYEUSZHNXSUQAAAAgH7s-UDNE6IAAAANfCf_a5KYAFDgAAAA; __Secure-1PSIDTS=sidts-CjEBLwcBXPYch52QnGCI_H7mp8ew3-QWF1TdVapd-Ot8DKeRG0SCr6TvhFo5NyKRdO8NEAA; __Secure-3PSIDTS=sidts-CjEBLwcBXPYch52QnGCI_H7mp8ew3-QWF1TdVapd-Ot8DKeRG0SCr6TvhFo5NyKRdO8NEAA; SIDCC=AKEyXzVA8OiZZvrJ2RzBTteJTcwb4UfdUZY9dHNEz0DWkEXOI3wWe1fXDKDB8gtDkhxKeWqnYXM; __Secure-1PSIDCC=AKEyXzVfXxqD0IqDytZWO1FbpXVvv0mG_MXUbaQCBjNaX7Tf835kifjhpjNv3fIxS2RqjRiSMMur; __Secure-3PSIDCC=AKEyXzWy7jyQPWXTZICPbhtJllfzaZdxEp9bTurivJihHm0-8k59CDLsTX2_gWp8SOBAgVYiS3lM' \ + -H 'dnt: 1' \ + -H 'priority: u=0, i' \ + -H 'sec-ch-ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"' \ + -H 'sec-ch-ua-arch: "x86"' \ + -H 'sec-ch-ua-bitness: "64"' \ + -H 'sec-ch-ua-full-version: "124.0.6367.119"' \ + -H 'sec-ch-ua-full-version-list: "Chromium";v="124.0.6367.119", "Google Chrome";v="124.0.6367.119", "Not-A.Brand";v="99.0.0.0"' \ + -H 'sec-ch-ua-mobile: ?0' \ + -H 'sec-ch-ua-model: ""' \ + -H 'sec-ch-ua-platform: "Windows"' \ + -H 'sec-ch-ua-platform-version: "15.0.0"' \ + -H 'sec-ch-ua-wow64: ?0' \ + -H 'sec-fetch-dest: document' \ + -H 'sec-fetch-mode: navigate' \ + -H 'sec-fetch-site: none' \ + -H 'sec-fetch-user: ?1' \ + -H 'upgrade-insecure-requests: 1' \ + -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' \ + -H 'x-client-data: CIe2yQEIo7bJAQipncoBCJmDywEIkqHLAQia/swBCIWgzQEIk+DNAQiRh84BCPSJzgEY9MnNAQ==' \ No newline at end of file diff --git a/import_tests/test_curl b/import_tests/test_curl new file mode 100644 index 0000000..3e2052c --- /dev/null +++ b/import_tests/test_curl @@ -0,0 +1,23 @@ +curl 'https://www.google.com/search?q=some+search&rlz=1C1ONGR_frFR1063FR1063&oq=some+search&gs_lcrp=EgZjaHJvbWUyBggAEEUYOTIGCAEQRRg80gEINzU2OGowajeoAgCwAgA&sourceid=chrome&ie=UTF-8' \ + -H 'accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' \ + -H 'accept-language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7' \ + -H 'cookie: HSID=Av_zeISP2IoC5ffne; SSID=AKgvP_7KP4Q2hAa7J; APISID=BHGY7ZbmZbJ_sm1O/ACh2Z88aE94LAktBN; SAPISID=8Pljvq5JQ81l6i-W/AE7yudhCMIhSx07aA; __Secure-1PAPISID=8Pljvq5JQ81l6i-W/AE7yudhCMIhSx07aA; __Secure-3PAPISID=8Pljvq5JQ81l6i-W/AE7yudhCMIhSx07aA; SID=g.a000iQi34M-7qEaVOkMFrycXCJM8EqqhOMd2x_eD98hvFBtmDGiYVrLlfIjHEiQDF9UR7JMTcwACgYKAW0SAQASFQHGX2Mi67FwpyVk-XamQ4h1n4-YzhoVAUF8yKpmHMVXUV6LSLVgLdR39TtR0076; __Secure-1PSID=g.a000iQi34M-7qEaVOkMFrycXCJM8EqqhOMd2x_eD98hvFBtmDGiYeQ0eDf3PWlHwVAxIQCQZSQACgYKAQ4SAQASFQHGX2Mi8cOFgeIIFTgntS5oW6LNRhoVAUF8yKqTtznLmD3NAcbAgrp4pPgX0076; __Secure-3PSID=g.a000iQi34M-7qEaVOkMFrycXCJM8EqqhOMd2x_eD98hvFBtmDGiYymEt7XyxPJc9XnYV2-s3kwACgYKAagSAQASFQHGX2Mi0MZRs5Ln80PaVraKG-YuwxoVAUF8yKrm83-kkzD22gxazewDzerG0076; OTZ=7515057_48_52_123900_48_436380; NID=513=UWSHg2m07oid_oICg5QPNg42NEXDwmQ-1Re8H5-JeTIKQN1fIIyJmwfPw38LxWLmaGKJLV8e6qrxX_8LEOaL6TRw6LwYyIsdCj-UjQ7yhHF3yApwcWTm9hjTE4cjq4bvD4PO2--Ghy8TYtl_dU3W2HG4TILc6HLhEsyScvpc_P0pWNgKGI2rIOqJyuj9ZhRaYDl1yMeX5jvHTTh5V3cGDaGw9P3iebXTaL23iauLhPI0oTM6zjdyLDpGJrxX2b3qRvosGS2MgOOF1V3cKx_Wn-7MJWj8bKvlvvw2EEJ6w_MkQMNJ7npEhLNjw8Jwp-PZPaVEJ7Pv1tqyWBU0aw1M5aP2KDdFSXrbNNM6o2Yn3fWIzboPmrxufXlIh6a2CVh4uR6CTgXKjrQaUjpbWKZtRg-rqtDuKW14B83lzZjvQR79fUrkGQYjmlmQ1FkU21QXk_ln780-gVTYULlYAUsHlyNFnMq4ynqdaT5LO7ZJW_T36Wv-URtZamHha-9sX1MmRbqqusYMxh3Nr8Uo7y9IA241D2WIKGi-OJj8z65np9nvOCAlzJkIS9WGpV27NDQqokwxJD87aPw4dibeVp2nQLwm1qRTAnwdp4t6ZB3AaAGiVzHuAIvYIfpgHYWUMTSMSIabQhSYGTmvhf5jHA; SEARCH_SAMESITE=CgQIhJsB; AEC=AQTF6HyWxVlqhwtKFurLp8MJDzz-RmIqYYq9Yp2YChWpu2seU7RYeOBnMm0; __Secure-ENID=19.SE=M390Zgw8_lsFMHxTOQYX4mBtx9KVzOdYUw7PL5JfrPlkgJ7YK1NQdieYWRmFd5g7E_PFfugqNnx6oAo7zQ5AxPT0CXQmoI-vnJAQTLR0RNxMP63vrZz7cFqkAEviEJpvhRGf4vUbrgUB_26PLDSAxejYEt6kZGc3ryi9sXgYPXFdJCK06gecG_JC2HF7o5tm0J79_C6rxle9ahzi9UXDN9QhbZ9POmaWKHefIIA3TjcYseTo-mWGLorvQZsVQUHi3hyf0zMq1QoK-2bGY9G1nC8NaFb2fNR3mxF7bu7aAhun1Hl4gHZDtX3CHghXfBGwz6Gx7JPWkSptAdu7z-9Qm1ykttSIQg66r8gysaDwEAfAvwSzSW_-fLlvE-cmjeKk_px3anvx1XiNRGxhNHXQriaQsQjqxES43FFiYGp7wLwCJsenMEB-nTI79JZ9Vh05_qknchOP7QMEXtARm6S7rPnosjBbv0e84VvzarbLpMegmkzWaId32X83ON_2h0y_yzV78urk6qzCf_5CTyD98B6opbWfExK9UGDGql7VMimj8KtqeMCXB9yNe9yKRVhx5uPnfOEIKoBY1e1SKS6UQvtzXv2n-tNqd1iy1OSejS86-J8Lnke8yJKjrfor; DV=Q2LJvyNVsmxWACJ-bZgLX549pDGc9NiJV5getGLf6QAAABAYEUSZHNXSUQAAAAgH7s-UDNE6IAAAANfCf_a5KYAFDgAAAA; __Secure-1PSIDTS=sidts-CjEBLwcBXPYch52QnGCI_H7mp8ew3-QWF1TdVapd-Ot8DKeRG0SCr6TvhFo5NyKRdO8NEAA; __Secure-3PSIDTS=sidts-CjEBLwcBXPYch52QnGCI_H7mp8ew3-QWF1TdVapd-Ot8DKeRG0SCr6TvhFo5NyKRdO8NEAA; SIDCC=AKEyXzVA8OiZZvrJ2RzBTteJTcwb4UfdUZY9dHNEz0DWkEXOI3wWe1fXDKDB8gtDkhxKeWqnYXM; __Secure-1PSIDCC=AKEyXzVfXxqD0IqDytZWO1FbpXVvv0mG_MXUbaQCBjNaX7Tf835kifjhpjNv3fIxS2RqjRiSMMur; __Secure-3PSIDCC=AKEyXzWy7jyQPWXTZICPbhtJllfzaZdxEp9bTurivJihHm0-8k59CDLsTX2_gWp8SOBAgVYiS3lM' \ + -H 'dnt: 1' \ + -H 'priority: u=0, i' \ + -H 'sec-ch-ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"' \ + -H 'sec-ch-ua-arch: "x86"' \ + -H 'sec-ch-ua-bitness: "64"' \ + -H 'sec-ch-ua-full-version: "124.0.6367.119"' \ + -H 'sec-ch-ua-full-version-list: "Chromium";v="124.0.6367.119", "Google Chrome";v="124.0.6367.119", "Not-A.Brand";v="99.0.0.0"' \ + -H 'sec-ch-ua-mobile: ?0' \ + -H 'sec-ch-ua-model: ""' \ + -H 'sec-ch-ua-platform: "Windows"' \ + -H 'sec-ch-ua-platform-version: "15.0.0"' \ + -H 'sec-ch-ua-wow64: ?0' \ + -H 'sec-fetch-dest: document' \ + -H 'sec-fetch-mode: navigate' \ + -H 'sec-fetch-site: none' \ + -H 'sec-fetch-user: ?1' \ + -H 'upgrade-insecure-requests: 1' \ + -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36' \ + -H 'x-client-data: CIe2yQEIo7bJAQipncoBCJmDywEIkqHLAQia/swBCIWgzQEIk+DNAQiRh84BCPSJzgEY9MnNAQ==' \ No newline at end of file diff --git a/src/app/files/curl.rs b/src/app/files/curl.rs deleted file mode 100644 index 96d03a8..0000000 --- a/src/app/files/curl.rs +++ /dev/null @@ -1,149 +0,0 @@ -use std::fs; -use std::path::PathBuf; -use std::sync::{Arc, RwLock}; - -use parser4curls::{parse, Curl}; -use reqwest::Url; - -use crate::app::app::App; -use crate::app::startup::args::ARGS; -use crate::panic_error; -use crate::request::auth::Auth; -use crate::request::body::ContentType; -use crate::request::collection::Collection; -use crate::request::method::Method; -use crate::request::request::{KeyValue, Request}; - -impl App<'_> { - pub fn import_curl_file(&mut self, path_buf: &PathBuf, save_to: &str) { - println!("Importing curl file from {:?} to {:?}", path_buf, save_to); - - let original_curl = match fs::read_to_string(path_buf) { - Ok(original_curl) => original_curl, - Err(e) => panic_error(format!("Could not read cURL file\n\t{e}")), - }; - - let curl = match parse(original_curl.as_str()) { - // The first element is the curl command - for now we only support one per file - Ok(curl) => curl.1, - Err(e) => panic_error(format!("Could not parse cURL\n\t{e}")), - }; - - let (collection_name, req_name) = match validate_save_to(save_to) { - Ok((collection_name, req_name)) => (collection_name, req_name), - Err(e) => panic_error(format!("Could not validate chosen collection/request\n\t{e}")), - }; - - let collection_exists = self.collections.iter().any(|c| c.name == collection_name); - - if collection_exists { - let imported = self - .collections - .iter_mut() - .find(|c| c.name == collection_name.clone()) - .unwrap(); - imported.requests.push(Arc::new(RwLock::new(parse_request(&curl, req_name)))); - } else { - let collection = Collection { - name: collection_name.clone(), - requests: vec![Arc::new(RwLock::new(parse_request(&curl, req_name)))], - path: ARGS.directory.join(collection_name.clone() + ".json"), - }; - - self.collections.push(collection); - } - - let imported_index = self.collections.iter().position(|c| c.name == collection_name).unwrap(); - self.save_collection_to_file(imported_index); - } -} - -fn parse_request(curl: &Curl, req_name: String) -> Request { - println!("Found cURL: {:?}", curl); - - let mut request = Request::default(); - - request.name = req_name; - - // Parse the URL so we can transform it - let mut curl_url = match Url::parse(curl.url) { - Ok(url) => url, - Err(e) => panic_error(format!("Could not parse URL\n\t{e}")), - }; - - /* QUERY PARAMS */ - - request.params = curl_url - .query_pairs() - .map(|(k, v)| KeyValue { - enabled: true, - data: (k.to_string(), v.to_string()), - }) - .collect(); - - /* URL */ - - curl_url.set_query(None); - request.url = curl_url.to_string(); - - /* METHOD */ - - request.method = get_http_method(&curl); - - /* HEADERS */ - - request.headers = curl - .options_headers_more - .iter() - .filter(|&(k, _)| !k.eq_ignore_ascii_case("Authorization")) // Exclude Authorization header, as that will be handled by the auth field - .map(|(k, v)| KeyValue { - enabled: true, - data: (k.to_string(), v.to_string()), - }) - .collect(); - - /* AUTH */ - - if let Some(auth) = curl.options_more.get("u") { - let parts: Vec<&str> = auth.splitn(2, ':').collect(); - if parts.len() == 2 { - request.auth = Auth::BasicAuth(parts[0].to_string(), parts[1].to_string()); - } - } else if let Some(auth) = curl.options_headers_more.get("Authorization") { - let parts: Vec<&str> = auth.split_whitespace().collect(); - if parts.len() > 1 && parts[0].starts_with("Bearer") { - request.auth = Auth::BearerToken(parts[1].to_string()); - } - } - - /* BODY */ - - // TODO: Handle content type, for now we just assume raw - request.body = ContentType::Raw(curl.options_data_raw.to_string()); - - request -} - -fn get_http_method(curl: &Curl) -> Method { - if let Some(x) = curl.options_more.get("X") { - match x { - &"PUT" => Method::PUT, - &"DELETE" => Method::DELETE, - _ => Method::GET, - } - } else if !curl.options_data_raw.is_empty() { - Method::POST - } else { - Method::GET - } -} - -fn validate_save_to(save_to: &str) -> Result<(String, String), String> { - let parts: Vec<&str> = save_to.split('/').collect(); - - if parts.len() != 2 { - return Err("Path is not in the format collection/request name".to_string()); - } - - Ok((parts[0].to_string(), parts[1].to_string())) -} diff --git a/src/app/files/import/curl.rs b/src/app/files/import/curl.rs new file mode 100644 index 0000000..b044686 --- /dev/null +++ b/src/app/files/import/curl.rs @@ -0,0 +1,202 @@ +use std::fs; +use std::path::PathBuf; +use std::str::FromStr; +use std::sync::Arc; + +use parking_lot::RwLock; +use regex::Regex; +use reqwest::header::CONTENT_TYPE; +use reqwest::Url; +use walkdir::WalkDir; + +use crate::app::app::App; +use crate::app::startup::args::ARGS; +use crate::panic_error; +use crate::request::auth::Auth; +use crate::request::body::ContentType; +use crate::request::body::ContentType::NoBody; +use crate::request::collection::Collection; +use crate::request::method::Method; +use crate::request::request::{KeyValue, Request}; + +impl App<'_> { + pub fn import_curl_file(&mut self, path_buf: &PathBuf, collection_name: &String, request_name: &Option, recursive: &bool, max_depth: u16) { + println!("Parsing cURL request"); + + println!("Collection name: {}", collection_name); + + let (collection_index, collection) = match self.collections.iter_mut().enumerate().find(|(_, collection)| collection.name == collection_name.as_str()) { + Some((index, collection)) => (index, collection), + None => { + println!("Collection does not exist. Creating it..."); + + let file_format = self.config.get_preferred_collection_file_format(); + + let collection = Collection { + name: collection_name.clone(), + requests: vec![], + path: ARGS.directory.join(format!("{}.{}", collection_name.clone(), file_format.to_string())), + file_format, + }; + + self.collections.push(collection); + + (self.collections.len()-1, self.collections.last_mut().unwrap()) + } + }; + + let request_name = match request_name { + None => path_buf.file_stem().unwrap().to_str().unwrap().to_string(), + Some(request_name) => request_name.clone() + }; + + let requests = match path_buf.is_file() { + true => vec![ + parse_request(path_buf, request_name) + ], + false => parse_requests_recursively(path_buf, *recursive, max_depth), + }; + + // Add the parsed request to the collection + collection.requests.extend(requests); + + self.save_collection_to_file(collection_index); + } +} + +fn parse_requests_recursively(path: &PathBuf, recursive: bool, max_depth: u16) -> Vec>> { + let max_depth: usize = match recursive { + true => max_depth as usize, + false => 1 + }; + + let mut requests: Vec>> = vec![]; + let walker = WalkDir::new(path) + .max_depth(max_depth) + .into_iter() + .filter_map(|e| e.ok()); + + for entry in walker { + if !entry.file_type().is_file() { + continue; + } + + // Will use the file name as the request name + let file_name = entry.file_name().to_str().unwrap().to_string(); + let request = parse_request(&entry.path().to_path_buf(), file_name); + + requests.push(request); + } + + return requests; +} + +/// TODO: parse everything with regexes in order to handle everything +fn parse_request(path: &PathBuf, request_name: String) -> Arc> { + let curl_stringed = match fs::read_to_string(path) { + Ok(original_curl) => original_curl, + Err(e) => panic_error(format!("Could not read cURL file\n\t{e}")), + }; + + println!("\tRequest name: {}", request_name); + + let parsed_curl = match curl_parser::ParsedRequest::load(&curl_stringed, None::) { + Ok(parsed_curl) => parsed_curl, + Err(e) => panic_error(format!("Could not parse cURL\n\t{e}")), + }; + + /* URL */ + + // Parse the URL so we can transform it + let mut curl_url = match Url::parse(&parsed_curl.url.to_string()) { + Ok(url) => url, + Err(e) => panic_error(format!("Could not parse URL\n\t{e}")), + }; + + curl_url.set_query(None); + let url = curl_url.to_string(); + + /* QUERY PARAMS */ + + let params = curl_url + .query_pairs() + .map(|(k, v)| KeyValue { + enabled: true, + data: (k.to_string(), v.to_string()), + }) + .collect(); + + /* METHOD */ + + let method = match Method::from_str(parsed_curl.method.as_str()) { + Ok(method) => method, + Err(e) => panic_error(format!("Unknown method\n\t{e}")), + }; + + /* HEADERS */ + + let headers: Vec = parsed_curl.headers + .iter() + .filter(|(header_name, _)| header_name.as_str() != "authorization") // Exclude Authorization header, as that will be handled by the auth field + .map(|(k, v)| KeyValue { + enabled: true, + data: (k.to_string(), v.to_str().unwrap().to_string()), + }) + .collect(); + + /* AUTH */ + + let basic_auth_regex = Regex::new(r#"(-u|--user) ["'](?.*):(?.*)["']"#).unwrap(); + + let auth = match basic_auth_regex.captures(&curl_stringed) { + None => { + let bearer_token_header = parsed_curl.headers + .iter() + .find(|(header_name, value)| header_name.as_str() == "authorization" && value.to_str().unwrap().starts_with("Bearer ")); + + if let Some((_, bearer_token)) = bearer_token_header { + let bearer_token = &bearer_token.to_str().unwrap()[7..]; + + Auth::BearerToken(bearer_token.to_string()) + } + else { + Auth::NoAuth + } + } + Some(capture) => Auth::BasicAuth(capture["username"].to_string(), capture["password"].to_string()) + }; + + /* BODY */ + + let body; + + // TODO: does not support forms yet + if !parsed_curl.body.is_empty() { + let content_type_header = headers.iter().find(|header| header.data.0 == CONTENT_TYPE.as_str()); + let body_stringed = parsed_curl.body.join("\n"); + + + if let Some(content_type) = content_type_header { + body = ContentType::from_content_type(&content_type.data.1, body_stringed); + } + else { + body = NoBody; + } + } + else { + body = NoBody; + } + + let request = Request { + name: request_name, + url, + method, + params, + headers, + body, + auth, + ..Default::default() + }; + + return Arc::new(RwLock::new(request)); +} \ No newline at end of file diff --git a/src/app/files/import/mod.rs b/src/app/files/import/mod.rs new file mode 100644 index 0000000..d3306ee --- /dev/null +++ b/src/app/files/import/mod.rs @@ -0,0 +1,2 @@ +pub(super) mod postman; +pub(super) mod curl; \ No newline at end of file diff --git a/src/app/files/postman.rs b/src/app/files/import/postman.rs similarity index 100% rename from src/app/files/postman.rs rename to src/app/files/import/postman.rs diff --git a/src/app/files/mod.rs b/src/app/files/mod.rs index 2a27520..3d22bb0 100644 --- a/src/app/files/mod.rs +++ b/src/app/files/mod.rs @@ -3,5 +3,4 @@ pub mod environment; pub mod log; pub mod config; pub mod key_bindings; -mod postman; -mod curl; \ No newline at end of file +pub mod import; \ No newline at end of file diff --git a/src/app/startup/args.rs b/src/app/startup/args.rs index 7633692..a00359d 100644 --- a/src/app/startup/args.rs +++ b/src/app/startup/args.rs @@ -1,11 +1,12 @@ use std::env; use std::path::PathBuf; use clap::{Parser, Subcommand}; +use clap::builder::Styles; use lazy_static::lazy_static; use crate::panic_error; #[derive(Parser, Debug)] -#[command(version, about, long_about = None)] +#[command(version, about, long_about = None, styles = Styles::styled())] pub struct Args { /// Main application directory, containing JSON collections files, the atac.toml config file and the atac.log file #[arg(short, long)] @@ -15,32 +16,54 @@ pub struct Args { pub command: Option, /// Avoid saving data to the collection file - #[arg(long, default_value_t = false)] + #[arg(global = true, long, default_value_t = false)] pub dry_run: bool, } -#[derive(Debug, Subcommand, PartialEq)] +#[derive(Subcommand, Debug, PartialEq)] pub enum Command { - /// Used to import a collection file such as Postman, or a file containing a curl - Import(ImportArgs), + /// Import a collection or request from other file formats (Postman v2.1.0, cURL) + Import { + /// The type of file to import + #[command(subcommand)] + import_type: ImportType, + }, } -#[derive(Debug, clap::Args, PartialEq)] -pub struct ImportArgs { - /// The type of file to import, Postman v2.1 JSON collection (postman), or a curl file (curl) - pub import_type: String, +#[derive(Subcommand, Debug, PartialEq)] +pub enum ImportType { + /// Import a Postman v2.1.0 file + Postman { + /// Path to the file to import + import_path: PathBuf, - /// The path to save the imported collection including the request name (collection/request_name) - pub save_to: String, - - /// A file to import, only Postman v2.1 JSON and curl files are supported - pub path: PathBuf, + /// Max depth at which import should stop creating nested collections and only get the deeper requests + #[arg(long)] + max_depth: Option, + }, - /// Max depth at which import should stop creating nested collections and only get the deeper requests - #[arg(long)] - pub max_depth: Option, + /// Import a curl file + Curl { + /// Path to the file to import + import_path: PathBuf, + + /// Collection name to save the request to + collection_name: String, + + /// Request name (will use the file name if none is provided) + request_name: Option, + + /// Search for deeper files + #[arg(short, long, conflicts_with = "request_name")] + recursive: bool, + + /// Max depth at which import should stop creating nested collections and only get the deeper requests + #[arg(long, requires = "recursive", conflicts_with = "request_name")] + max_depth: Option, + }, } + pub struct ParsedArgs { pub directory: PathBuf, pub is_directory_from_env: bool, diff --git a/src/app/startup/startup.rs b/src/app/startup/startup.rs index 8b411a0..fa80643 100644 --- a/src/app/startup/startup.rs +++ b/src/app/startup/startup.rs @@ -1,7 +1,7 @@ use std::fs::OpenOptions; use crate::app::app::App; -use crate::app::startup::args::{ARGS, Command}; +use crate::app::startup::args::{ARGS, Command, ImportType}; use crate::panic_error; use crate::request::collection::CollectionFileFormat; @@ -18,17 +18,19 @@ impl App<'_> { if let Some(command) = &ARGS.command { match command { - Command::Import(import_args) => { - println!("Importing: {}", import_args.path.display()); - - if import_args.import_type == "postman" { - self.import_postman_collection( - &import_args.path, - import_args.max_depth.unwrap_or(99), - ); - } else if import_args.import_type == "curl" { - self.import_curl_file(&import_args.path, &import_args.save_to); - } + Command::Import { import_type} => match import_type { + ImportType::Postman { + import_path, + max_depth + } => self.import_postman_collection(&import_path, max_depth.unwrap_or(99)), + + ImportType::Curl { + import_path, + collection_name, + request_name, + recursive, + max_depth + } => self.import_curl_file(&import_path, collection_name, request_name, recursive, max_depth.unwrap_or(99)) } } } diff --git a/src/request/body.rs b/src/request/body.rs index fc059ee..6fe0965 100644 --- a/src/request/body.rs +++ b/src/request/body.rs @@ -41,6 +41,20 @@ impl ContentType { } } + pub fn from_content_type(content_type: &str, body: String) -> ContentType { + match content_type { + //"multipart/form-data" => Multipart(body), + //"application/x-www-form-urlencoded" => Form(body), + "application/octet-stream" => File(body), + "text/plain" => Raw(body), + "application/json" => Json(body), + "application/xml" => Json(body), + "application/html" => Json(body), + "application/javascript" => Json(body), + _ => NoBody + } + } + pub fn get_form(&self) -> Option<&Vec> { match self { Multipart(form) | Form(form) => Some(form),