├── src ├── view │ ├── views │ │ ├── logs │ │ │ ├── log_view.rs │ │ │ └── mod.rs │ │ ├── tabs_request │ │ │ ├── TabsView.rs │ │ │ └── mod.rs │ │ ├── request │ │ │ ├── url_and_method_view.rs │ │ │ ├── store.rs │ │ │ ├── request_edition_view.rs │ │ │ └── mod.rs │ │ ├── response │ │ │ ├── response_status_view.rs │ │ │ ├── store.rs │ │ │ ├── response_content_view.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── environment │ │ │ ├── store.rs │ │ │ └── mod.rs │ ├── mod.rs │ ├── components │ │ ├── mod.rs │ │ ├── tab_list.rs │ │ ├── input_insert_mode.rs │ │ ├── doc_reader.rs │ │ ├── block_text.rs │ │ ├── counter_response_time.rs │ │ ├── tab_blocked_text.rs │ │ └── welcome_doc.rs │ ├── renderer │ │ └── mod.rs │ ├── ui.rs │ └── style │ │ └── mod.rs ├── utils │ ├── custom_types │ │ ├── mod.rs │ │ └── uuid.rs │ ├── mod.rs │ └── files.rs ├── config │ ├── configurations │ │ ├── mod.rs │ │ └── view.rs │ ├── mod.rs │ └── manager.rs ├── base │ ├── states │ │ ├── mod.rs │ │ ├── names.rs │ │ ├── states │ │ │ ├── empty.rs │ │ │ ├── default_help_mode.rs │ │ │ ├── active_logs.rs │ │ │ ├── active_response_body.rs │ │ │ ├── active_response_headers.rs │ │ │ ├── active_request_headers.rs │ │ │ ├── active_request_body.rs │ │ │ ├── active_request_url.rs │ │ │ ├── active_tablist.rs │ │ │ ├── editing_global_env.rs │ │ │ ├── editing_session_env.rs │ │ │ ├── default_insert_mode.rs │ │ │ └── default.rs │ │ ├── manager.rs │ │ └── states.rs │ ├── web │ │ ├── mod.rs │ │ ├── response.rs │ │ ├── repository.rs │ │ ├── client.rs │ │ ├── request.rs │ │ └── repository │ │ │ └── reqwest.rs │ ├── os │ │ ├── mod.rs │ │ ├── os_commands │ │ │ ├── factory.rs │ │ │ ├── mod.rs │ │ │ └── external_editor.rs │ │ ├── file_facades │ │ │ ├── requests.rs │ │ │ ├── variables.rs │ │ │ └── temp_edition.rs │ │ ├── file_facades.rs │ │ └── file_factory.rs │ ├── mod.rs │ ├── commands │ │ ├── commands │ │ │ ├── mod.rs │ │ │ ├── ui.rs │ │ │ ├── utils.rs │ │ │ ├── response.rs │ │ │ ├── docs.rs │ │ │ ├── tabs.rs │ │ │ ├── jumps.rs │ │ │ ├── external_editor.rs │ │ │ └── submit.rs │ │ ├── mod.rs │ │ └── handler.rs │ ├── actions │ │ ├── manager.rs │ │ └── mod.rs │ ├── doc │ │ ├── mod.rs │ │ ├── views │ │ │ ├── help.json │ │ │ └── welcome.json │ │ └── handler.rs │ ├── logs.rs │ ├── stores │ │ ├── view.rs │ │ ├── environment.rs │ │ ├── mod.rs │ │ └── requests.rs │ └── validators │ │ ├── response.rs │ │ ├── mod.rs │ │ └── request.rs ├── input │ ├── mod.rs │ ├── keymaps │ │ ├── docs_mode.rs │ │ ├── mod.rs │ │ └── normal_mode.rs │ ├── listener.rs │ └── input_handler.rs ├── cli.rs ├── lib.rs └── logger.rs ├── dino.png ├── tests ├── integration │ ├── mod.rs │ └── network.rs ├── e2e │ ├── mod.rs │ ├── requests.rs │ └── tabs.rs ├── mocks │ ├── mod.rs │ ├── web_client.rs │ ├── file_facade.rs │ ├── file_factory.rs │ └── mock_app.rs ├── mod.rs ├── build_tests.sh ├── Dockerfile ├── run_tests.sh └── utils │ └── mod.rs ├── Dockerfile ├── .gitignore ├── .github ├── workflows │ ├── ci.yml │ └── cd.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── CONTRIBUTING.md ├── Cargo.toml └── README.md /src/view/views/logs/log_view.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/view/views/tabs_request/TabsView.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/custom_types/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod uuid; 2 | -------------------------------------------------------------------------------- /src/view/views/request/url_and_method_view.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/config/configurations/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod view; 2 | -------------------------------------------------------------------------------- /src/view/views/response/response_status_view.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod custom_types; 2 | pub mod files; 3 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod configurations; 2 | pub mod manager; 3 | -------------------------------------------------------------------------------- /dino.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/talis-fb/legacy_TReq/HEAD/dino.png -------------------------------------------------------------------------------- /tests/integration/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod files_handler; 2 | pub mod network; 3 | -------------------------------------------------------------------------------- /src/base/states/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod manager; 2 | pub mod names; 3 | pub mod states; 4 | -------------------------------------------------------------------------------- /tests/e2e/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod requests; 2 | pub mod tabs; 3 | pub mod variables; 4 | -------------------------------------------------------------------------------- /tests/mocks/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod file_facade; 2 | pub mod file_factory; 3 | pub mod mock_app; 4 | -------------------------------------------------------------------------------- /tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod e2e; 2 | pub mod integration; 3 | pub mod mocks; 4 | pub mod utils; 5 | -------------------------------------------------------------------------------- /src/base/web/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod repository; 3 | pub mod request; 4 | pub mod response; 5 | -------------------------------------------------------------------------------- /src/input/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod buffer; 2 | pub mod input_handler; 3 | pub mod keymaps; 4 | pub mod listener; 5 | -------------------------------------------------------------------------------- /src/base/os/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod file_facades; 2 | pub mod file_factory; 3 | pub mod handler; 4 | pub mod os_commands; 5 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | #[derive(Parser, Debug)] 4 | #[command(author, version, about, long_about = None)] 5 | pub struct Args; 6 | -------------------------------------------------------------------------------- /tests/integration/network.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod network { 3 | #[test] 4 | #[ignore] 5 | fn should_request() { 6 | assert_eq!(1, 1) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/mocks/web_client.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::hash::Hash; 3 | use std::path::PathBuf; 4 | use treq::base::os::file_facades::FileFacade; 5 | -------------------------------------------------------------------------------- /tests/build_tests.sh: -------------------------------------------------------------------------------- 1 | echo " ========================" 2 | echo " ===== CREATE IMAGE =====" 3 | echo " ========================" 4 | docker build -t treq -f tests/Dockerfile . 5 | 6 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod app; 2 | pub mod base; 3 | pub mod cli; 4 | pub mod config; 5 | pub mod input; 6 | pub mod logger; 7 | pub mod runner; 8 | pub mod utils; 9 | pub mod view; 10 | -------------------------------------------------------------------------------- /src/base/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod actions; 2 | pub mod commands; 3 | pub mod doc; 4 | pub mod logs; 5 | pub mod os; 6 | pub mod states; 7 | pub mod stores; 8 | pub mod validators; 9 | pub mod web; 10 | -------------------------------------------------------------------------------- /src/view/views/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | pub mod app; 4 | pub mod environment; 5 | pub mod logs; 6 | pub mod request; 7 | pub mod response; 8 | pub mod tabs_request; 9 | 10 | pub type ViewStates = HashMap; 11 | -------------------------------------------------------------------------------- /src/base/commands/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod docs; 2 | pub mod edit_mode; 3 | pub mod environment; 4 | pub mod external_editor; 5 | pub mod jumps; 6 | pub mod request; 7 | pub mod response; 8 | pub mod submit; 9 | pub mod tabs; 10 | pub mod ui; 11 | pub mod utils; 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.68-bullseye 2 | 3 | RUN apt-get update && apt-get install -y vim 4 | 5 | WORKDIR /app 6 | COPY . . 7 | 8 | RUN cargo build --release 9 | 10 | RUN useradd -ms /bin/bash appuser 11 | USER appuser 12 | 13 | ENV EDITOR=vim 14 | 15 | CMD ["./target/release/treq"] 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | app.log 13 | -------------------------------------------------------------------------------- /src/view/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::base::stores::MainStore; 2 | 3 | pub mod components; 4 | pub mod help; 5 | pub mod renderer; 6 | pub mod style; 7 | pub mod ui; 8 | pub mod views; 9 | 10 | #[mockall::automock] 11 | pub trait UiTrait { 12 | fn restart(&mut self); 13 | fn close(&mut self); 14 | 15 | fn render(&mut self, data_store: &MainStore); 16 | } 17 | -------------------------------------------------------------------------------- /src/base/states/names.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] 2 | pub enum StatesNames { 3 | Default, 4 | DefaultEditMode, 5 | DefaultHelpMode, 6 | TabList, 7 | Url, 8 | RequestHeaders, 9 | RequestBody, 10 | ResponseHeader, 11 | ResponseBody, 12 | Log, 13 | Empty, 14 | EditingGlobalEnv, 15 | EditingSessionEnv, 16 | } 17 | -------------------------------------------------------------------------------- /src/base/states/states/empty.rs: -------------------------------------------------------------------------------- 1 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 2 | use std::collections::HashMap; 3 | 4 | pub struct EmptyState { 5 | pub maps: CommandsMap, 6 | } 7 | impl State for EmptyState { 8 | fn get_state_name(&self) -> StatesNames { 9 | StatesNames::Empty 10 | } 11 | fn get_map(&self) -> &CommandsMap { 12 | &self.maps 13 | } 14 | fn init() -> Self { 15 | Self { 16 | maps: HashMap::from([]), 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI - Testing Rust Code 2 | 3 | on: 4 | push: 5 | branches: [ "master", "develop" ] 6 | pull_request: 7 | branches: [ "master", "develop" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | - name: Format 24 | run: cargo fmt --verbose 25 | -------------------------------------------------------------------------------- /tests/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.68-bullseye 2 | 3 | RUN apt-get update && apt-get install -y vim 4 | 5 | WORKDIR /app 6 | 7 | RUN cargo init --verbose 8 | COPY Cargo.toml . 9 | COPY Cargo.lock . 10 | RUN cargo build --release 11 | 12 | COPY src/ /app/src 13 | COPY tests/ /app/tests 14 | RUN cargo build --release 15 | 16 | RUN useradd -ms /bin/bash appuser 17 | RUN chown -R appuser:appuser /app 18 | USER appuser 19 | 20 | ENV EDITOR=vim 21 | 22 | # CMD ["cargo", "test", "integration ","--release", "--", "--test-threads=1", "--ignored"] 23 | CMD ["/bin/bash"] 24 | -------------------------------------------------------------------------------- /src/config/manager.rs: -------------------------------------------------------------------------------- 1 | use super::configurations::view::ViewConfig; 2 | 3 | use crate::base::os::handler::FileHandler; 4 | use std::rc::Rc; 5 | use std::sync::Mutex; 6 | 7 | #[derive(Clone)] 8 | pub struct ConfigManager { 9 | pub view: Rc>, 10 | pub files: Rc>, 11 | } 12 | impl ConfigManager { 13 | pub fn init(file_handler: FileHandler, view: ViewConfig) -> Self { 14 | Self { 15 | view: Rc::new(Mutex::new(view)), 16 | files: Rc::new(Mutex::new(file_handler)), 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/view/components/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::view::renderer::Backend; 2 | 3 | pub trait Component { 4 | type Backend: Backend; 5 | fn render(&self, f: &mut Self::Backend); 6 | } 7 | 8 | pub trait StatedComponents { 9 | fn render(&self, state: State, f: &mut impl Backend); 10 | } 11 | 12 | // ------------------------------------------------ 13 | // Components Implementations 14 | // ------------------------------------------------ 15 | pub mod block_text; 16 | pub mod counter_response_time; 17 | pub mod doc_reader; 18 | pub mod input_insert_mode; 19 | pub mod tab_blocked_text; 20 | pub mod tab_list; 21 | pub mod welcome_doc; 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/base/actions/manager.rs: -------------------------------------------------------------------------------- 1 | use super::Actions; 2 | use crate::{base::commands::Command, base::states::manager::StateManager}; 3 | 4 | #[derive(Clone)] 5 | pub struct ActionsManager { 6 | last_command: Option, 7 | } 8 | impl ActionsManager { 9 | pub fn init() -> Self { 10 | Self { last_command: None } 11 | } 12 | 13 | pub fn get_command_of_action( 14 | &mut self, 15 | action: Actions, 16 | states: &StateManager, 17 | ) -> Option { 18 | let commands_map = states.get_command_map(); 19 | let command = commands_map.get(&action)?; 20 | self.last_command = Some(command.clone()); 21 | Some(command.clone()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/base/os/os_commands/factory.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use super::{external_editor::ExternalEditor, OsCommandTrait}; 4 | use crate::base::commands::Command; 5 | 6 | #[mockall::automock] 7 | pub trait OsCommandFactory { 8 | fn external_editor( 9 | &self, 10 | path: PathBuf, 11 | command: Command, 12 | ) -> Result, String>; 13 | } 14 | 15 | pub struct OsCommandDefaultFactory; 16 | impl OsCommandFactory for OsCommandDefaultFactory { 17 | fn external_editor( 18 | &self, 19 | path: PathBuf, 20 | command: Command, 21 | ) -> Result, String> { 22 | Ok(Box::new(ExternalEditor::init(path, command)?)) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/view/components/tab_list.rs: -------------------------------------------------------------------------------- 1 | use super::Component; 2 | use crate::view::renderer::tui_rs::BackendTuiRs; 3 | use crate::view::renderer::Tui; 4 | use crate::view::style::Texts; 5 | use tui::layout::Rect; 6 | 7 | pub struct Tabslist { 8 | pub area: Rect, 9 | pub tabs: Vec, 10 | pub current: usize, 11 | pub marked: bool, 12 | } 13 | impl Component for Tabslist { 14 | type Backend = BackendTuiRs; 15 | fn render(&self, f: &mut Self::Backend) { 16 | let tabs_str = self.tabs.iter().map(|f| Texts::from_str(f)).collect(); 17 | 18 | if self.marked { 19 | f.render_tablist_marked(tabs_str, self.current, self.area) 20 | } else { 21 | f.render_tablist(tabs_str, self.current, self.area) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/logger.rs: -------------------------------------------------------------------------------- 1 | #[cfg(debug_assertions)] 2 | pub fn init_logger() { 3 | use log::LevelFilter; 4 | use log4rs::config::{Appender, Config, Root}; 5 | 6 | let file_appender = Appender::builder().build( 7 | "file", 8 | Box::new( 9 | log4rs::append::file::FileAppender::builder() 10 | .encoder(Box::new(log4rs::encode::pattern::PatternEncoder::new( 11 | "{d} [{l}] {m}{n}", 12 | ))) 13 | .build("app.log") 14 | .unwrap(), 15 | ), 16 | ); 17 | 18 | let config = Config::builder() 19 | .appender(file_appender) 20 | .build(Root::builder().appender("file").build(LevelFilter::Debug)) 21 | .unwrap(); 22 | 23 | log4rs::init_config(config).unwrap(); 24 | } 25 | -------------------------------------------------------------------------------- /src/base/os/file_facades/requests.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | base::web::request::Request, 3 | utils::{custom_types::uuid::UUID, files::FileUtils}, 4 | }; 5 | 6 | use super::FileFacade; 7 | use std::path::PathBuf; 8 | 9 | #[derive(Clone, Debug)] 10 | pub struct RequestFile { 11 | pub path: PathBuf, 12 | } 13 | impl FileFacade for RequestFile { 14 | fn get_path(&self) -> PathBuf { 15 | self.path.clone() 16 | } 17 | 18 | fn get_root_path() -> PathBuf { 19 | FileUtils::get_data_dir().unwrap().join("requests") 20 | } 21 | 22 | fn create(id: UUID, value: Request) -> Result { 23 | let path = Self::get_root_path().join(id.value); 24 | let mut file = Self { path }; 25 | file.save_content(value)?; 26 | Ok(file) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/base/doc/mod.rs: -------------------------------------------------------------------------------- 1 | use self::handler::DocReaderHandler; 2 | use crate::view::help::DocView; 3 | // use std::path::Path; 4 | pub mod handler; 5 | 6 | pub struct DocsFactory; 7 | impl DocsFactory { 8 | pub fn help_reader() -> DocReaderHandler { 9 | let content = include_str!("views/help.json").to_string(); 10 | DocReaderHandler::init(DocView::from_string(content)) 11 | } 12 | 13 | pub fn welcome_reader() -> DocReaderHandler { 14 | let content = include_str!("views/welcome.json").to_string(); 15 | DocReaderHandler::init(DocView::from_string(content)) 16 | } 17 | } 18 | 19 | #[cfg(test)] 20 | mod test { 21 | use super::*; 22 | 23 | #[test] 24 | fn test_if_views_files_is_valid() { 25 | DocsFactory::help_reader(); 26 | DocsFactory::welcome_reader(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/base/states/states/default_help_mode.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct DefaultHelpMode { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for DefaultHelpMode { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::DefaultHelpMode 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | let maps = HashMap::from([ 18 | // General Move 19 | (Actions::Up, Commands::doc_up()), 20 | (Actions::Down, Commands::doc_down()), 21 | (Actions::DocExit, Commands::doc_exit()), 22 | ]); 23 | 24 | Self { maps } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/base/web/response.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Default, Copy, Clone, Debug)] 4 | pub enum ResponseStage { 5 | #[default] 6 | Empty, 7 | 8 | Waiting, 9 | Finished, 10 | 11 | Cancelled, 12 | InternalError, 13 | } 14 | 15 | #[derive(Default, Clone, Debug)] 16 | pub struct Response { 17 | pub status: i32, 18 | pub response_time: f64, 19 | pub headers: HashMap, 20 | pub body: String, 21 | pub stage: ResponseStage, 22 | } 23 | 24 | impl Response { 25 | pub fn default_internal_error(err: String) -> Self { 26 | Self { 27 | status: 77, // A STATUS CODE INTERNAL TO INTERNAL ERROR 28 | response_time: 0.0, 29 | headers: HashMap::new(), 30 | body: err, 31 | stage: ResponseStage::InternalError, 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/base/states/states/active_logs.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct LogsState { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for LogsState { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::Log 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | Self { 18 | maps: HashMap::from([ 19 | (Actions::Edit, Commands::do_nothing()), 20 | (Actions::Switch, Commands::do_nothing()), 21 | (Actions::Up, Commands::go_to_request_body_section()), 22 | (Actions::Down, Commands::do_nothing()), 23 | ]), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cleanup() { 4 | echo " ====================================" 5 | echo " ==== Removing opened containers ====" 6 | echo " ====================================" 7 | docker rm -f treq_container_app 8 | docker rm -f treq_container_app2 9 | } 10 | 11 | trap cleanup EXIT 12 | 13 | echo " ========================" 14 | echo " ===== FILE HANDLER =====" 15 | echo " ========================" 16 | docker run -d -it --name treq_container_app treq 17 | docker exec -it treq_container_app cargo test integration --release -- --test-threads=1 --ignored || exit 1 18 | 19 | echo " ========================" 20 | echo " ====== e2e tests =======" 21 | echo " ========================" 22 | docker run -d -it --name treq_container_app2 treq 23 | docker exec -it treq_container_app2 cargo test integration --release -- --test-threads=1 --ignored || exit 1 24 | 25 | -------------------------------------------------------------------------------- /src/base/os/file_facades/variables.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::files::FileUtils; 2 | 3 | use super::FileFacade; 4 | use std::{collections::HashMap, path::PathBuf}; 5 | 6 | #[derive(Clone, Debug)] 7 | pub struct VariablesFile { 8 | pub path: PathBuf, 9 | } 10 | impl FileFacade> for VariablesFile { 11 | fn get_path(&self) -> PathBuf { 12 | self.path.clone() 13 | } 14 | 15 | fn get_root_path() -> PathBuf { 16 | FileUtils::get_data_dir().unwrap().join("data") 17 | } 18 | 19 | fn create(id: String, value: HashMap) -> Result { 20 | let path = Self::get_root_path().join(id); 21 | let mut file = Self { path }; 22 | file.save_content(value)?; 23 | Ok(file) 24 | } 25 | } 26 | 27 | // impl VariablesFile { 28 | // pub fn factory_saved_files() -> HashMap { 29 | // todo!() 30 | // } 31 | // } 32 | -------------------------------------------------------------------------------- /src/base/logs.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] 2 | pub enum LogType { 3 | Error, 4 | Warning, 5 | Help, 6 | 7 | Empty, 8 | InputMode, 9 | } 10 | impl Default for LogType { 11 | fn default() -> Self { 12 | Self::Empty 13 | } 14 | } 15 | 16 | #[derive(Default, Clone)] 17 | pub struct Log { 18 | pub log_type: LogType, 19 | pub title: String, 20 | pub detail: Option, 21 | } 22 | 23 | impl Log { 24 | pub fn with_type(&self, t: LogType) -> Self { 25 | let mut log = self.clone(); 26 | log.log_type = t; 27 | log 28 | } 29 | pub fn with_title(&self, t: String) -> Self { 30 | let mut log = self.clone(); 31 | log.title = t; 32 | log 33 | } 34 | pub fn with_detail(&self, t: String) -> Self { 35 | let mut log = self.clone(); 36 | log.detail = Some(t); 37 | log 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/base/os/os_commands/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use tokio::sync::mpsc::Sender; 3 | 4 | use crate::base::commands::Command; 5 | 6 | pub mod external_editor; 7 | pub mod factory; 8 | 9 | #[mockall::automock] 10 | pub trait OsCommandTrait { 11 | fn exec(&self, sender: Sender) -> Result<(), String>; 12 | } 13 | 14 | #[derive(Clone)] 15 | pub enum OsCommand { 16 | Sync(Arc>), 17 | Async(Arc>), 18 | } 19 | 20 | impl OsCommand { 21 | pub fn create_sync_from(value: T) -> Self 22 | where 23 | T: OsCommandTrait + 'static + Send + Sync, 24 | { 25 | OsCommand::Sync(Arc::new(Box::new(value))) 26 | } 27 | 28 | pub fn create_async_from(value: T) -> Self 29 | where 30 | T: OsCommandTrait + 'static + Send + Sync, 31 | { 32 | OsCommand::Async(Arc::new(Box::new(value))) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/base/states/states/active_response_body.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct ResponseBodyActiveState { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for ResponseBodyActiveState { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::ResponseBody 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | Self { 18 | maps: HashMap::from([ 19 | (Actions::Edit, Commands::edit_response_body_vim()), 20 | (Actions::Switch, Commands::go_to_response_headers_section()), 21 | (Actions::Left, Commands::go_to_request_body_section()), 22 | (Actions::Up, Commands::go_to_tab_section()), 23 | ]), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/base/states/states/active_response_headers.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct ResponseHeadersState { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for ResponseHeadersState { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::ResponseHeader 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | Self { 18 | maps: HashMap::from([ 19 | (Actions::Edit, Commands::edit_response_headers_vim()), 20 | (Actions::Switch, Commands::go_to_response_body_section()), 21 | (Actions::Left, Commands::go_to_request_body_section()), 22 | (Actions::Up, Commands::go_to_tab_section()), 23 | ]), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/base/states/states/active_request_headers.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct RequestHeaderActiveState { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for RequestHeaderActiveState { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::RequestHeaders 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | Self { 18 | maps: HashMap::from([ 19 | (Actions::Edit, Commands::edit_request_headers_vim()), 20 | (Actions::Switch, Commands::go_to_request_body_section()), 21 | (Actions::Up, Commands::go_to_url_section()), 22 | (Actions::ReloadBody, Commands::restart_headers_of_file()), 23 | ]), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/view/components/input_insert_mode.rs: -------------------------------------------------------------------------------- 1 | use super::Component; 2 | use crate::view::renderer::tui_rs::BackendTuiRs; 3 | use crate::view::renderer::Tui; 4 | use crate::view::style::Texts; 5 | use tui::layout::{Constraint, Layout, Rect}; 6 | 7 | pub struct InputTextBlock<'a> { 8 | pub area: Rect, 9 | pub text: &'a str, 10 | pub cursor: usize, 11 | } 12 | impl Component for InputTextBlock<'_> { 13 | type Backend = BackendTuiRs; 14 | fn render(&self, f: &mut Self::Backend) { 15 | f.render_clear_area(self.area); 16 | f.render_block_with_title_left( 17 | Texts::from_str("[ESC] - QUIT [ENTER] - FINISH"), 18 | self.area, 19 | ); 20 | 21 | let layout_content = Layout::default() 22 | .margin(1) 23 | .constraints([Constraint::Percentage(100)]) 24 | .split(self.area)[0]; 25 | f.render_text_raw_with_cursor_at(self.text, self.cursor, layout_content); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to TReq 2 | 3 | 🦖 First of all! Thanks for you help to grow the dinosaur 🦖 4 | 5 | ## How do I contribute : 6 | 7 | - Create or choose an issue for bug or feature request 8 | - Comment the issue to say that you are tackling it 9 | - Code this out! 10 | 11 | ## How setup project 12 | 13 | - You'll only need Rust on latest version. Make a fork of branch `develop` and rename it following the [Git Flow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) convention. 14 | - Examples 15 | - `fix/name-of-task` 16 | - `feature/name-of-task` 17 | - `docs/name-of-task` 18 | 19 | - Make sure to name your commits messages followin [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) 20 | 21 | ## What should I do before Pull Request 22 | 23 | - If you're fixing a bug make sure you made unit tests about it. (This way you guarantee the bug never's gonna happen again) 24 | - Run `cargo test` and `cargo fmt` 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/base/states/states/active_request_body.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct RequestActiveState { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for RequestActiveState { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::RequestBody 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | Self { 18 | maps: HashMap::from([ 19 | (Actions::Edit, Commands::edit_request_body_vim()), 20 | (Actions::Switch, Commands::go_to_request_header_section()), 21 | (Actions::Up, Commands::go_to_url_section()), 22 | (Actions::Right, Commands::go_to_response_body_section()), 23 | (Actions::ReloadBody, Commands::restart_body_of_file()), 24 | ]), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/view/views/response/store.rs: -------------------------------------------------------------------------------- 1 | // use crate::view::style::Color; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | // Manage the State of view 5 | #[derive(Deserialize, Serialize)] 6 | pub struct State { 7 | // pub focus: bool, 8 | pub opened: StatesResEditionView, 9 | // pub status_color: Color, 10 | // pub status_text: String, 11 | } 12 | 13 | impl State { 14 | pub fn switch_opened(&mut self) { 15 | self.opened = match self.opened { 16 | StatesResEditionView::BodyOpened => StatesResEditionView::HeadersOpened, 17 | StatesResEditionView::HeadersOpened => StatesResEditionView::BodyOpened, 18 | } 19 | } 20 | 21 | pub fn open_body_view(&mut self) { 22 | self.opened = StatesResEditionView::BodyOpened; 23 | } 24 | 25 | pub fn open_headers_view(&mut self) { 26 | self.opened = StatesResEditionView::HeadersOpened; 27 | } 28 | } 29 | 30 | #[derive(Deserialize, Serialize)] 31 | pub enum StatesResEditionView { 32 | BodyOpened, 33 | HeadersOpened, 34 | } 35 | -------------------------------------------------------------------------------- /src/view/components/doc_reader.rs: -------------------------------------------------------------------------------- 1 | use super::Component; 2 | use crate::base::doc::handler::DocReaderHandler; 3 | use crate::view::renderer::tui_rs::BackendTuiRs; 4 | use crate::view::renderer::Tui; 5 | use crate::view::style::Texts; 6 | use tui::layout::{Constraint, Layout, Rect}; 7 | 8 | pub struct DocReader<'a> { 9 | pub area: Rect, 10 | pub doc_handler: &'a DocReaderHandler, 11 | } 12 | impl Component for DocReader<'_> { 13 | type Backend = BackendTuiRs; 14 | fn render(&self, f: &mut Self::Backend) { 15 | let texts = self.doc_handler.get_texts(); 16 | 17 | let layout = Layout::default() 18 | .margin(1) 19 | .constraints([Constraint::Percentage(100)]) 20 | .split(self.area)[0]; 21 | 22 | f.render_clear_area(self.area); 23 | 24 | f.render_block_with_title_center( 25 | Texts::from_str("Navigate -> [UP] and [DOWN] / Press any other key to close"), 26 | self.area, 27 | ); 28 | 29 | f.render_rows_texts(texts, layout); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/base/states/states/active_request_url.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct RequestUrlActiveState { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for RequestUrlActiveState { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::Url 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | Self { 18 | maps: HashMap::from([ 19 | (Actions::Up, Commands::go_to_tab_section()), 20 | (Actions::Down, Commands::go_to_request_body_section()), 21 | (Actions::Right, Commands::go_to_response_body_section()), 22 | (Actions::Edit, Commands::edit_request_url()), 23 | (Actions::New, Commands::add_new_tab()), 24 | (Actions::Switch, Commands::switch_request_method()), 25 | ]), 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/input/keymaps/docs_mode.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crossterm::event::KeyCode; 3 | use std::collections::HashMap; 4 | 5 | use super::utils::{create_keymap, create_keymap_char}; 6 | use super::KeyMap; 7 | 8 | pub fn keymap_factory() -> KeyMap { 9 | HashMap::from([ 10 | create_keymap(KeyCode::Up, Actions::Up), 11 | create_keymap(KeyCode::Down, Actions::Down), 12 | create_keymap_char('k', Actions::Up), 13 | create_keymap_char('j', Actions::Down), 14 | // All rest 15 | create_keymap_char('l', Actions::DocExit), 16 | create_keymap_char('h', Actions::DocExit), 17 | create_keymap_char('?', Actions::DocExit), 18 | create_keymap_char('q', Actions::DocExit), 19 | create_keymap_char('e', Actions::DocExit), 20 | create_keymap_char('d', Actions::DocExit), 21 | create_keymap_char('n', Actions::DocExit), 22 | create_keymap_char('r', Actions::DocExit), 23 | create_keymap_char('s', Actions::DocExit), 24 | create_keymap_char('G', Actions::DocExit), 25 | ]) 26 | } 27 | -------------------------------------------------------------------------------- /src/base/states/states/active_tablist.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct TabActiveState { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for TabActiveState { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::TabList 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | Self { 18 | maps: HashMap::from([ 19 | (Actions::Edit, Commands::rename_tab()), 20 | (Actions::Switch, Commands::go_to_next_tab()), 21 | (Actions::InverseSwitch, Commands::go_to_previous_tab()), 22 | (Actions::New, Commands::add_new_tab()), 23 | (Actions::Up, Commands::do_nothing()), 24 | (Actions::Down, Commands::go_to_url_section()), 25 | (Actions::Delete, Commands::delete_tab()), 26 | ]), 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/files.rs: -------------------------------------------------------------------------------- 1 | use directories::ProjectDirs; 2 | use std::{ 3 | fs::{File, OpenOptions}, 4 | path::PathBuf, 5 | }; 6 | 7 | pub struct FileUtils; 8 | impl FileUtils { 9 | pub fn create_path_if_it_does_not_exist(path: &PathBuf) -> Result<(), String> { 10 | if std::fs::metadata(path).is_err() { 11 | std::fs::create_dir_all(path).map_err(|e| e.to_string())?; 12 | } 13 | 14 | Ok(()) 15 | } 16 | 17 | pub fn open_or_create_file(path: PathBuf) -> Result { 18 | let file = OpenOptions::new() 19 | .create(true) 20 | .write(true) 21 | .open(path) 22 | .map_err(|e| e.to_string())?; 23 | Ok(file) 24 | } 25 | 26 | pub fn get_data_dir() -> Option { 27 | let project = ProjectDirs::from("", "", "TReq")?; 28 | Some(project.data_dir().to_path_buf()) 29 | } 30 | 31 | pub fn get_config_dir() -> Option { 32 | let project = ProjectDirs::from("", "", "TReq")?; 33 | Some(project.config_dir().to_path_buf()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/view/views/request/store.rs: -------------------------------------------------------------------------------- 1 | // use crate::base::web::request::METHODS; 2 | // use crate::view::style::Color; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Clone, Copy, Deserialize, Serialize)] 6 | pub struct State { 7 | pub opened: StatesReqEditionView, 8 | // pub method: METHODS, 9 | // pub color: Color, 10 | // pub url_block_focus: bool, 11 | // pub body_block_focus: bool, 12 | } 13 | 14 | impl State { 15 | pub fn switch_opened(&mut self) { 16 | self.opened = match self.opened { 17 | StatesReqEditionView::BodyOpened => StatesReqEditionView::HeadersOpened, 18 | StatesReqEditionView::HeadersOpened => StatesReqEditionView::BodyOpened, 19 | } 20 | } 21 | 22 | pub fn open_body_view(&mut self) { 23 | self.opened = StatesReqEditionView::BodyOpened; 24 | } 25 | 26 | pub fn open_headers_view(&mut self) { 27 | self.opened = StatesReqEditionView::HeadersOpened; 28 | } 29 | } 30 | 31 | #[derive(Clone, Copy, Deserialize, Serialize)] 32 | pub enum StatesReqEditionView { 33 | BodyOpened, 34 | HeadersOpened, 35 | } 36 | -------------------------------------------------------------------------------- /src/base/commands/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::app::App; 4 | 5 | #[derive(Clone, Copy)] 6 | pub enum CommandType { 7 | Sync, 8 | Async, 9 | 10 | CancelAsync, 11 | } 12 | 13 | pub trait CommandTrait { 14 | fn execute(&self, app: &mut App) -> Result<(), String>; 15 | 16 | fn type_running(&self) -> CommandType { 17 | CommandType::Sync 18 | } 19 | 20 | // --- ONLY TO ASYNC Commands --- 21 | fn get_id(&self) -> String { 22 | String::new() 23 | } 24 | fn take_task(&self) -> Option> { 25 | None 26 | } 27 | fn is_task_begin(&self) -> bool { 28 | false 29 | } 30 | } 31 | 32 | // It needs to be a Box, a Struct which implements 'CommandTrait' 33 | // but, it need to be cloned, to do so... it needs to be a Rc 34 | pub type Command = Arc>; 35 | 36 | mod commands; 37 | pub struct Commands; 38 | impl Commands { 39 | pub fn from(command: T) -> Command { 40 | Arc::new(Box::new(command)) 41 | } 42 | } 43 | 44 | pub mod handler; 45 | -------------------------------------------------------------------------------- /src/base/commands/commands/ui.rs: -------------------------------------------------------------------------------- 1 | use crate::app::App; 2 | use crate::base::commands::CommandTrait; 3 | use crate::base::commands::{Command, Commands}; 4 | 5 | impl Commands { 6 | pub fn grow_right_ui() -> Command { 7 | struct S; 8 | impl CommandTrait for S { 9 | fn execute(&self, app: &mut App) -> Result<(), String> { 10 | app.get_data_store_mut() 11 | .config 12 | .view 13 | .lock() 14 | .unwrap() 15 | .grow_right_block(); 16 | Ok(()) 17 | } 18 | } 19 | 20 | Commands::from(S {}) 21 | } 22 | 23 | pub fn grow_left_ui() -> Command { 24 | struct S; 25 | impl CommandTrait for S { 26 | fn execute(&self, app: &mut App) -> Result<(), String> { 27 | app.get_data_store_mut() 28 | .config 29 | .view 30 | .lock() 31 | .unwrap() 32 | .grow_left_block(); 33 | Ok(()) 34 | } 35 | } 36 | 37 | Commands::from(S {}) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "treq" 3 | authors = [ "Talis-Fb" ] 4 | version = "0.4.0" 5 | edition = "2021" 6 | license = "GPL-3.0" 7 | description = "A Client to make HTTP requests for Vim/Terminal Users" 8 | homepage = "https://github.com/talis-fb/TReq" 9 | repository = "https://github.com/talis-fb/TReq" 10 | documentation = "https://github.com/talis-fb/TReq/wiki" 11 | categories = ["command-line-interface"] 12 | keywords = [ 13 | "cli", 14 | "tui", 15 | "http", 16 | "client", 17 | "terminal", 18 | "restful" 19 | ] 20 | 21 | [dependencies] 22 | tui = "0.19" 23 | crossterm = "0.25" 24 | reqwest = "0.11.13" 25 | tokio = { version = "1.23.0", features = ["full"] } 26 | serde = { version = "1.0.152", features = ["derive"] } 27 | serde_json = "1.0" 28 | async-trait = "0.1.60" 29 | tempfile = "3.3.0" 30 | toml = "0.5.10" 31 | uuid = { version = "1.2.2", features = [ "v4", "fast-rng", "macro-diagnostics" ]} 32 | directories = "4.0.1" 33 | regex = "1.7.1" 34 | log = "0.4.17" 35 | log4rs = "1.2.0" 36 | tera = "1.18.1" 37 | mockall = "0.11.4" 38 | clap = { version = "4.2.7", features = ["derive"] } 39 | 40 | [[bin]] 41 | name = "treq" 42 | 43 | [package.metadata] 44 | optdepends = ["vim"] 45 | 46 | -------------------------------------------------------------------------------- /tests/mocks/file_facade.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::hash::Hash; 3 | use std::path::PathBuf; 4 | use treq::base::os::file_facades::FileFacade; 5 | 6 | pub struct MockFile { 7 | _id: FileID, 8 | value: FileEntity, 9 | } 10 | 11 | impl FileFacade for MockFile 12 | where 13 | FileID: PartialEq + Eq + Hash + 'static, 14 | FileEntity: for<'a> Deserialize<'a> + Serialize + Clone + 'static, 15 | { 16 | fn get_root_path() -> std::path::PathBuf { 17 | PathBuf::new() 18 | } 19 | 20 | fn get_path(&self) -> std::path::PathBuf { 21 | PathBuf::new() 22 | } 23 | 24 | fn get_content(&self) -> Result { 25 | Ok(self.value.clone()) 26 | } 27 | fn remove(&mut self) -> Result<(), String> { 28 | Ok(()) 29 | } 30 | 31 | fn save_content(&mut self, value: FileEntity) -> Result<(), String> { 32 | self.value = value; 33 | Ok(()) 34 | } 35 | 36 | fn setup() -> Result<(), String> { 37 | Ok(()) 38 | } 39 | fn create(_id: FileID, value: FileEntity) -> Result { 40 | Ok(Self { _id, value }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/view/components/block_text.rs: -------------------------------------------------------------------------------- 1 | use super::Component; 2 | use crate::view::renderer::tui_rs::BackendTuiRs; 3 | use crate::view::renderer::Tui; 4 | use crate::view::style::Texts; 5 | use tui::layout::{Constraint, Layout, Rect}; 6 | 7 | pub struct BlockText<'a> { 8 | pub area: Rect, 9 | pub title: Texts<'a>, 10 | pub content: Texts<'a>, 11 | pub marked: bool, 12 | } 13 | impl Component for BlockText<'_> { 14 | type Backend = BackendTuiRs; 15 | fn render(&self, f: &mut Self::Backend) { 16 | let content_text_layout = Layout::default() 17 | .margin(1) 18 | .constraints([Constraint::Percentage(100)]) 19 | .split(self.area); 20 | 21 | // TODO: 22 | // Find a better way to not clone here 23 | // It's not so bad because Texts only store references and enums 24 | // Thus, the clone it not so heavy. But it's still a Vec 25 | 26 | if self.marked { 27 | f.render_block_with_title_left_marked(self.title.clone(), self.area); 28 | } else { 29 | f.render_block_with_title_left(self.title.clone(), self.area); 30 | } 31 | 32 | f.render_text_raw(&self.content.to_string(), content_text_layout[0]); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/base/states/states/editing_global_env.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct EditingGlobalEnvState { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for EditingGlobalEnvState { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::EditingGlobalEnv 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | Self { 18 | maps: HashMap::from([ 19 | (Actions::Quit, Commands::exit_environment_view()), 20 | (Actions::Submit, Commands::exit_environment_view()), 21 | (Actions::Switch, Commands::switch_opened_env_vars()), 22 | (Actions::Edit, Commands::edit_current_global_env_var()), 23 | (Actions::Up, Commands::go_to_prev_global_env_var()), 24 | (Actions::Down, Commands::go_to_next_global_env_var()), 25 | (Actions::New, Commands::add_global_env_var()), 26 | (Actions::Delete, Commands::remove_current_global_env_var()), 27 | ]), 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/base/states/states/editing_session_env.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct EditingSessionEnvState { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for EditingSessionEnvState { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::EditingSessionEnv 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | Self { 18 | maps: HashMap::from([ 19 | (Actions::Quit, Commands::exit_environment_view()), 20 | (Actions::Submit, Commands::exit_environment_view()), 21 | (Actions::Switch, Commands::switch_opened_env_vars()), 22 | (Actions::Edit, Commands::edit_current_session_env_var()), 23 | (Actions::Up, Commands::go_to_prev_session_env_var()), 24 | (Actions::Down, Commands::go_to_next_session_env_var()), 25 | (Actions::New, Commands::add_session_env_var()), 26 | (Actions::Delete, Commands::remove_current_session_env_var()), 27 | ]), 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/view/views/request/request_edition_view.rs: -------------------------------------------------------------------------------- 1 | use super::store::StatesReqEditionView; 2 | use crate::view::components::tab_blocked_text::TabBlockText; 3 | use crate::view::components::Component; 4 | use crate::view::renderer::tui_rs::BackendTuiRs; 5 | use tui::layout::Rect; 6 | 7 | pub struct RequestEditionView<'a> { 8 | pub area: Rect, 9 | 10 | pub body: &'a str, 11 | pub headers: &'a str, 12 | pub opened: StatesReqEditionView, 13 | pub marked: bool, 14 | } 15 | impl Component for RequestEditionView<'_> { 16 | type Backend = BackendTuiRs; 17 | fn render(&self, f: &mut Self::Backend) { 18 | let mut block = TabBlockText { 19 | area: self.area, 20 | texts: vec![("Body", self.body), ("Headers", self.headers)], 21 | current: 0, 22 | marked: self.marked, 23 | }; 24 | 25 | match self.opened { 26 | StatesReqEditionView::BodyOpened => { 27 | block.texts[0].0 = "BODY"; 28 | block.current = 0; 29 | } 30 | StatesReqEditionView::HeadersOpened => { 31 | let _title_text = block.texts[1].0; 32 | block.texts[1].0 = "HEADERS"; 33 | block.current = 1; 34 | } 35 | } 36 | 37 | block.render(f); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/view/views/response/response_content_view.rs: -------------------------------------------------------------------------------- 1 | use crate::view::components::tab_blocked_text::TabBlockText; 2 | use crate::view::components::Component; 3 | use crate::view::renderer::tui_rs::BackendTuiRs; 4 | use tui::layout::Rect; 5 | 6 | use crate::view::views::response::store::StatesResEditionView; 7 | 8 | pub struct ResposeEditionView<'a> { 9 | pub area: Rect, 10 | 11 | pub body: &'a str, 12 | pub headers: &'a str, 13 | pub opened: StatesResEditionView, 14 | pub marked: bool, 15 | } 16 | impl Component for ResposeEditionView<'_> { 17 | type Backend = BackendTuiRs; 18 | fn render(&self, f: &mut Self::Backend) { 19 | let mut block = TabBlockText { 20 | area: self.area, 21 | texts: vec![("Body", self.body), ("Headers", self.headers)], 22 | current: 0, 23 | marked: self.marked, 24 | }; 25 | 26 | match self.opened { 27 | StatesResEditionView::BodyOpened => { 28 | block.texts[0].0 = "BODY"; 29 | block.current = 0; 30 | } 31 | StatesResEditionView::HeadersOpened => { 32 | let _title_text = block.texts[1].0; 33 | block.texts[1].0 = "HEADERS"; 34 | block.current = 1; 35 | } 36 | } 37 | 38 | block.render(f); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/base/web/repository.rs: -------------------------------------------------------------------------------- 1 | pub mod reqwest; 2 | use super::response::Response; 3 | use async_trait::async_trait; 4 | use std::collections::HashMap; 5 | 6 | // #[cfg_attr(test, mockall::automock)] 7 | #[mockall::automock] 8 | #[async_trait] 9 | pub trait HttpClientRepository: Send + Sync { 10 | async fn call_get( 11 | &self, 12 | url: String, 13 | headers: HashMap, 14 | ) -> Result; 15 | async fn call_post( 16 | &self, 17 | url: String, 18 | headers: HashMap, 19 | body: String, 20 | ) -> Result; 21 | async fn call_delete( 22 | &self, 23 | url: String, 24 | headers: HashMap, 25 | body: String, 26 | ) -> Result; 27 | async fn call_patch( 28 | &self, 29 | url: String, 30 | headers: HashMap, 31 | body: String, 32 | ) -> Result; 33 | async fn call_put( 34 | &self, 35 | url: String, 36 | headers: HashMap, 37 | body: String, 38 | ) -> Result; 39 | async fn call_head( 40 | &self, 41 | url: String, 42 | headers: HashMap, 43 | body: String, 44 | ) -> Result; 45 | } 46 | -------------------------------------------------------------------------------- /src/base/stores/view.rs: -------------------------------------------------------------------------------- 1 | use crate::view::views::environment::store::EnvironmentVars; 2 | use crate::view::views::environment::store::OpenedVars; 3 | use crate::view::views::environment::store::State as StateEnvironmentView; 4 | use crate::view::views::request::store::State as StateRequestView; 5 | use crate::view::views::request::store::StatesReqEditionView; 6 | use crate::view::views::response::store::State as StateResponseView; 7 | use crate::view::views::response::store::StatesResEditionView; 8 | 9 | pub struct ViewStore { 10 | pub environment: StateEnvironmentView, 11 | pub request: StateRequestView, 12 | pub response: StateResponseView, 13 | } 14 | 15 | impl ViewStore { 16 | pub fn init() -> Self { 17 | Self { 18 | environment: StateEnvironmentView { 19 | opened_section: OpenedVars::Session, 20 | vars_keys: EnvironmentVars { 21 | global: vec![], 22 | session: vec![], 23 | }, 24 | current_global_var: 0, 25 | current_session_var: 0, 26 | }, 27 | request: StateRequestView { 28 | opened: StatesReqEditionView::BodyOpened, 29 | }, 30 | response: StateResponseView { 31 | opened: StatesResEditionView::BodyOpened, 32 | }, 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/custom_types/uuid.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{File, OpenOptions}; 2 | use std::io::Write; 3 | use std::path::PathBuf; 4 | 5 | #[derive(Default, Clone, Hash, PartialEq, Eq, Debug)] 6 | pub struct UUID { 7 | pub value: String, 8 | } 9 | impl UUID { 10 | pub fn new() -> Self { 11 | UUID { 12 | value: uuid::Uuid::new_v4().to_string(), 13 | } 14 | } 15 | pub fn from(value: String) -> Self { 16 | UUID { value } 17 | } 18 | } 19 | 20 | #[derive(Clone, Debug)] 21 | pub struct AppFile { 22 | path: PathBuf, 23 | } 24 | impl AppFile { 25 | pub fn init(path: PathBuf) -> Self { 26 | Self { path } 27 | } 28 | 29 | pub fn open_or_create_file(&mut self) -> Result { 30 | let file = OpenOptions::new() 31 | .create(true) 32 | .write(true) 33 | .open(&self.path) 34 | .map_err(|e| e.to_string())?; 35 | Ok(file) 36 | } 37 | 38 | pub fn get_content(&self) -> Result { 39 | std::fs::read_to_string(&self.path).map_err(|e| e.to_string()) 40 | } 41 | 42 | pub fn save_content(&mut self, content: String) -> Result<(), String> { 43 | let mut file = self.open_or_create_file()?; 44 | file.set_len(0).map_err(|e| e.to_string())?; 45 | file.write_all(content.as_bytes()) 46 | .map_err(|e| e.to_string())?; 47 | Ok(()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/view/views/tabs_request/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::base::states::names::StatesNames; 2 | use crate::view::components::tab_list::Tabslist; 3 | use crate::view::renderer::tui_rs::BackendTuiRs; 4 | use crate::view::views::ViewStates; 5 | use crate::{base::stores::MainStore, view::components::Component}; 6 | use tui::layout::Rect; 7 | 8 | pub struct TabRequestView<'a> { 9 | pub area: Rect, 10 | pub store: &'a MainStore, 11 | pub states: &'a ViewStates, 12 | } 13 | 14 | impl TabRequestView<'_> { 15 | pub fn prepare_render<'b>(_states: &mut ViewStates, _store: &'b MainStore) { 16 | // Does nothing for while 17 | } 18 | } 19 | 20 | impl Component for TabRequestView<'_> { 21 | type Backend = BackendTuiRs; 22 | fn render(&self, f: &mut Self::Backend) { 23 | let request = self.store.get_requests(); 24 | 25 | let component = Tabslist { 26 | area: self.area, 27 | tabs: request 28 | .iter() 29 | .map(|f| { 30 | let mut name = f.name.clone(); 31 | 32 | if f.has_changed { 33 | name.push('*') 34 | } 35 | 36 | name 37 | }) 38 | .collect(), 39 | current: self.store.request_ind(), 40 | marked: self.store.current_state == StatesNames::TabList, 41 | }; 42 | 43 | component.render(f) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/base/validators/response.rs: -------------------------------------------------------------------------------- 1 | use super::{Validator, Validators}; 2 | use crate::base::web::response::Response; 3 | use serde_json::Value; 4 | 5 | impl Validators { 6 | pub fn set_pretty_json_response() -> Validator { 7 | let f = |res: &mut Response| { 8 | let json_obj: Value = serde_json::from_str(&res.body).map_err(|e| e.to_string())?; 9 | let prety_body = serde_json::to_string_pretty(&json_obj).map_err(|e| e.to_string())?; 10 | res.body = prety_body; 11 | Ok(()) 12 | }; 13 | 14 | Box::new(f) 15 | } 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use super::super::ValidatorsHandler; 21 | use super::*; 22 | 23 | #[test] 24 | fn should_format_string_of_response_body() { 25 | let mut response = Response::default(); 26 | response.body = r#"{ "StatusCode": "200", "SomeMessenger": "Here's something interesting", "Notes": ["Here live anothers interesting things"] }"#.to_string(); 27 | 28 | let response_final = ValidatorsHandler::from(&response) 29 | .execute(vec![Validators::set_pretty_json_response()]) 30 | .unwrap(); 31 | 32 | assert_eq!( 33 | response_final.body, 34 | r#"{ 35 | "Notes": [ 36 | "Here live anothers interesting things" 37 | ], 38 | "SomeMessenger": "Here's something interesting", 39 | "StatusCode": "200" 40 | }"# 41 | .to_string() 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/view/components/counter_response_time.rs: -------------------------------------------------------------------------------- 1 | use super::Component; 2 | use crate::view::renderer::tui_rs::BackendTuiRs; 3 | use crate::view::renderer::Tui; 4 | use crate::view::style::{Size, Texts}; 5 | use tui::layout::{Constraint, Layout, Rect}; 6 | 7 | pub struct CounterResponseTime { 8 | pub area: Rect, 9 | pub marked: bool, 10 | pub time: f64, 11 | } 12 | impl Component for CounterResponseTime { 13 | type Backend = BackendTuiRs; 14 | fn render(&self, f: &mut Self::Backend) { 15 | let time_string = format!("{:.1}s", self.time); 16 | 17 | f.render_clear_area(self.area); 18 | 19 | if self.marked { 20 | f.render_block_with_title_center_marked(Texts::from_str("Requesting..."), self.area); 21 | } else { 22 | f.render_block_with_title_center(Texts::from_str("Requesting..."), self.area); 23 | } 24 | 25 | let area_total = Layout::default() 26 | .margin(1) 27 | .constraints([Constraint::Percentage(100)]) 28 | .split(self.area); 29 | 30 | let centered_area = BackendTuiRs::create_absolute_centered_area( 31 | Size::Percentage(100), 32 | Size::Fixed(2), 33 | area_total[0], 34 | ); 35 | 36 | let content_area = Layout::default() 37 | .constraints([Constraint::Length(1), Constraint::Length(1)]) 38 | .split(centered_area); 39 | 40 | f.render_text_raw_align_center(&time_string, content_area[0]); 41 | f.render_text_raw_align_center("Press [ESC] to cancel", content_area[1]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/base/states/states/default_insert_mode.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use crate::input::keymaps::insert_mode::keymap_factory; 5 | use std::collections::HashMap; 6 | 7 | pub struct DefaultEditMode { 8 | pub maps: CommandsMap, 9 | } 10 | impl State for DefaultEditMode { 11 | fn get_state_name(&self) -> StatesNames { 12 | StatesNames::DefaultEditMode 13 | } 14 | fn get_map(&self) -> &CommandsMap { 15 | &self.maps 16 | } 17 | fn init() -> Self { 18 | let mut maps = HashMap::from([ 19 | // General Move 20 | (Actions::Right, Commands::edit_mode_go_to_next_char()), 21 | (Actions::Left, Commands::edit_mode_go_to_prev_char()), 22 | (Actions::TypingErase, Commands::edit_mode_delete_prev_char()), 23 | (Actions::TypingBegingLine, Commands::edit_mode_go_to_start()), 24 | (Actions::TypingEndLine, Commands::edit_mode_go_to_end()), 25 | (Actions::TypingClose, Commands::process_edit_mode()), 26 | (Actions::TypingCancel, Commands::cancel_edit_mode()), 27 | (Actions::TypingClearAll, Commands::edit_mode_delete_all()), 28 | ]); 29 | 30 | keymap_factory().values().for_each(|f| { 31 | let action = f.action; 32 | if let Actions::TypingChar(ch) = action { 33 | maps.insert(action, Commands::edit_mode_insert_char(ch)); 34 | }; 35 | }); 36 | 37 | Self { maps } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/view/renderer/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::view::components::Component; 2 | use tui::layout::Rect; 3 | 4 | use super::style::{Color, Texts}; 5 | 6 | pub mod tui_rs; 7 | 8 | pub trait Tui { 9 | fn render_block_with_title_left(&mut self, title: Texts, area: T); 10 | fn render_block_with_title_center(&mut self, title: Texts, area: T); 11 | fn render_block_with_tab(&mut self, tabs: Vec, current: usize, area: T); 12 | 13 | fn render_block_with_title_left_marked(&mut self, title: Texts, area: T); 14 | fn render_block_with_title_center_marked(&mut self, title: Texts, area: T); 15 | 16 | fn render_text<'a>(&mut self, text: Texts, area: T); 17 | fn render_text_raw<'a>(&mut self, text: &str, area: T); 18 | fn render_text_raw_align_center<'a>(&mut self, text: &str, area: T); 19 | fn render_text_raw_with_cursor_at<'a>(&mut self, text: &str, cursor: usize, area: Rect); 20 | fn render_rows_texts<'a>(&mut self, text: Vec, area: T); 21 | fn render_text_with_bg<'a>(&mut self, text: Texts, color: Color, area: T); 22 | 23 | fn render_bg_color<'a>(&mut self, color: Color, area: T); 24 | 25 | fn render_tablist(&mut self, tabs: Vec, current: usize, area: T); 26 | fn render_tablist_marked(&mut self, tabs: Vec, current: usize, area: T); 27 | 28 | fn render_divider_with_text(&mut self, text: Texts, area: T); 29 | 30 | fn render_clear_area(&mut self, area: T); 31 | } 32 | 33 | // TODO 34 | pub trait Backend: Tui { 35 | fn draw(&mut self, view: &dyn Component) 36 | where 37 | Self: Sized, 38 | { 39 | view.render(self) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/base/actions/mod.rs: -------------------------------------------------------------------------------- 1 | pub type EventListenerFn = fn(); 2 | 3 | pub mod manager; 4 | 5 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 6 | pub enum Actions { 7 | Null, 8 | SubCommand, // When user press a key that has subcommands from it 9 | Quit, 10 | AskForHelp, 11 | 12 | // Main actions 13 | Edit, 14 | Switch, 15 | InverseSwitch, 16 | Submit, 17 | CancelSubmit, 18 | Undo, 19 | New, 20 | Delete, 21 | 22 | // General Moves 23 | Up, 24 | Down, 25 | Left, 26 | Right, 27 | 28 | // Jumps to sections 29 | GoToTabList, 30 | GoToRequest, 31 | GoToResponse, 32 | GoToLogs, 33 | 34 | // Moves Tabs 35 | GoToNextTab, 36 | GoToPreviousTab, 37 | 38 | // Moves Request 39 | GoToRequestBody, 40 | GoToUrl, 41 | 42 | // Moves Response 43 | GoToResponseBody, 44 | GoToResponseHeaders, 45 | 46 | // Edit Tabs 47 | RenameTab, 48 | DeleteTab, 49 | 50 | // Edit Request 51 | Save, 52 | RequestBodyEdit, 53 | RequestHeadersEdit, 54 | UrlEdit, 55 | MethodEdit, 56 | ReloadBody, 57 | 58 | // 59 | GrowHorizontalUiRight, 60 | GrowHorizontalUiLeft, 61 | 62 | // INPUT Mode -------------------- 63 | TypingChar(char), 64 | TypingErase, 65 | TypingBreakLine, 66 | TypingClearAll, 67 | TypingClose, 68 | TypingCancel, 69 | TypingUp, 70 | TypingDown, 71 | TypingLeft, 72 | TypingRight, 73 | TypingBegingLine, 74 | TypingEndLine, 75 | 76 | // HELP Mode -------------------- 77 | DocExit, 78 | 79 | // Environment view ------------- 80 | GoToEnvironment, 81 | } 82 | -------------------------------------------------------------------------------- /tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::mocks::mock_app::MockApp; 2 | use treq::base::{ 3 | actions::Actions, 4 | commands::Commands, 5 | os::os_commands::{factory::MockOsCommandFactory, MockOsCommandTrait}, 6 | }; 7 | 8 | pub async fn set_input_mode_value(mock_app: &mut MockApp, value: &str) { 9 | mock_app.exec(Actions::TypingClearAll).await; 10 | 11 | for c in value.chars() { 12 | mock_app.exec(Actions::TypingChar(c)).await; 13 | } 14 | 15 | mock_app.exec(Actions::TypingClose).await; 16 | } 17 | 18 | pub async fn set_external_editor_output(mock_app: &mut MockApp, value: String) { 19 | let mut os_command_factory = MockOsCommandFactory::default(); 20 | os_command_factory 21 | .expect_external_editor() 22 | .return_once(move |_, command| { 23 | let mut editor_to_edit_body = MockOsCommandTrait::default(); 24 | editor_to_edit_body 25 | .expect_exec() 26 | .times(1) 27 | .returning(move |sender| { 28 | let set_input_buffer = Commands::set_input_buffer(value.clone()); 29 | let exec_input_buffer = command.clone(); 30 | 31 | tokio::task::spawn(async move { 32 | sender.send(set_input_buffer).await.ok(); 33 | sender.send(exec_input_buffer).await.ok(); 34 | }); 35 | 36 | Ok(()) 37 | }); 38 | 39 | Ok(Box::new(editor_to_edit_body)) 40 | }); 41 | 42 | // Set mocks on app 43 | mock_app 44 | .runner 45 | .app 46 | .set_os_commands_factory(os_command_factory); 47 | } 48 | -------------------------------------------------------------------------------- /src/base/commands/commands/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::app::App; 2 | use crate::app::InputMode; 3 | use crate::base::commands::CommandTrait; 4 | use crate::base::commands::{Command, Commands}; 5 | 6 | impl Commands { 7 | pub fn do_nothing() -> Command { 8 | struct S; 9 | impl CommandTrait for S { 10 | fn execute(&self, _app: &mut App) -> Result<(), String> { 11 | Ok(()) 12 | } 13 | } 14 | 15 | Commands::from(S {}) 16 | } 17 | 18 | pub fn err() -> Command { 19 | struct S; 20 | impl CommandTrait for S { 21 | fn execute(&self, _app: &mut App) -> Result<(), String> { 22 | Err("Ai".to_string()) 23 | } 24 | } 25 | 26 | Commands::from(S {}) 27 | } 28 | 29 | pub fn undo_state() -> Command { 30 | struct S; 31 | impl CommandTrait for S { 32 | fn execute(&self, app: &mut App) -> Result<(), String> { 33 | app.reset_to_last_state(); 34 | Ok(()) 35 | } 36 | } 37 | 38 | Commands::from(S {}) 39 | } 40 | 41 | pub fn show_help() -> Command { 42 | struct S; 43 | impl CommandTrait for S { 44 | fn execute(&self, app: &mut App) -> Result<(), String> { 45 | app.clear_log(); 46 | app.set_mode(InputMode::Help); 47 | Ok(()) 48 | } 49 | } 50 | 51 | Commands::from(S {}) 52 | } 53 | 54 | pub fn quit() -> Command { 55 | struct S; 56 | impl CommandTrait for S { 57 | fn execute(&self, app: &mut App) -> Result<(), String> { 58 | app.is_finished = true; 59 | Ok(()) 60 | } 61 | } 62 | 63 | Commands::from(S {}) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/base/web/client.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::base::validators::{Validators, ValidatorsHandler}; 4 | 5 | use super::repository::HttpClientRepository; 6 | use super::request::METHODS; 7 | use super::{request::Request, response::Response}; 8 | 9 | pub struct WebClient { 10 | pub http_client: Box, 11 | } 12 | 13 | impl WebClient { 14 | pub fn init(repository: T) -> Self { 15 | Self { 16 | http_client: Box::new(repository), 17 | } 18 | } 19 | 20 | pub async fn submit( 21 | &self, 22 | request: Request, 23 | variables: &HashMap, 24 | ) -> Result { 25 | let request_to_do = ValidatorsHandler::from(&request).execute([ 26 | Validators::url_protocol_request(), 27 | Validators::url_and_body_template_engine(variables), 28 | Validators::headers_template_engine(variables), 29 | ])?; 30 | 31 | let Request { 32 | url, headers, body, .. 33 | } = request_to_do; 34 | 35 | let response = match request_to_do.method { 36 | METHODS::GET => self.http_client.call_get(url, headers).await, 37 | METHODS::POST => self.http_client.call_post(url, headers, body).await, 38 | METHODS::PUT => self.http_client.call_put(url, headers, body).await, 39 | METHODS::PATCH => self.http_client.call_patch(url, headers, body).await, 40 | METHODS::HEAD => self.http_client.call_head(url, headers, body).await, 41 | METHODS::DELETE => self.http_client.call_delete(url, headers, body).await, 42 | }?; 43 | 44 | let response = ValidatorsHandler::from(&response) 45 | .execute_ignoring_errors([Validators::set_pretty_json_response()]); 46 | 47 | Ok(response) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/base/commands/commands/response.rs: -------------------------------------------------------------------------------- 1 | use crate::app::App; 2 | use crate::base::commands::CommandTrait; 3 | use crate::base::commands::{Command, Commands}; 4 | 5 | impl Commands { 6 | pub fn edit_response_body_vim() -> Command { 7 | struct S; 8 | impl CommandTrait for S { 9 | fn execute(&self, app: &mut App) -> Result<(), String> { 10 | let response = { 11 | app.get_data_store() 12 | .get_response() 13 | .lock() 14 | .unwrap() 15 | .body 16 | .clone() 17 | }; 18 | 19 | let command = 20 | Commands::open_editor_to_buffer(Commands::do_nothing(), None, Some(response)); 21 | 22 | command.execute(app)?; 23 | 24 | Ok(()) 25 | } 26 | } 27 | 28 | Commands::from(S {}) 29 | } 30 | 31 | pub fn edit_response_headers_vim() -> Command { 32 | struct S; 33 | impl CommandTrait for S { 34 | fn execute(&self, app: &mut App) -> Result<(), String> { 35 | let initial_headers_as_str = { 36 | let headers = app 37 | .get_data_store() 38 | .get_response() 39 | .lock() 40 | .unwrap() 41 | .headers 42 | .clone(); 43 | serde_json::to_string_pretty(&headers).unwrap_or_default() 44 | }; 45 | 46 | let command = Commands::open_editor_to_buffer( 47 | Commands::do_nothing(), 48 | None, 49 | Some(initial_headers_as_str), 50 | ); 51 | 52 | command.execute(app)?; 53 | 54 | Ok(()) 55 | } 56 | } 57 | 58 | Commands::from(S {}) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/input/keymaps/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crossterm::event::KeyCode; 3 | use std::collections::HashMap; 4 | 5 | pub mod docs_mode; 6 | pub mod insert_mode; 7 | pub mod normal_mode; 8 | 9 | #[derive(PartialEq, Eq, Debug, Clone)] 10 | pub struct Actionable { 11 | pub action: Actions, 12 | 13 | // This is used only if the key has other commands if other keys is pressed 14 | // if this box is NOT None, then the command above is ignored 15 | pub sub_action: Option, 16 | } 17 | 18 | pub type KeyMap = HashMap; 19 | 20 | // 21 | // What to do... 22 | // * When go to input/docs/vim mode it should change Keymap in InputHandler 23 | // and also go to a State to handle Actions of typing, ONLY this 24 | // (this state can be optional, possible) 25 | // 26 | // The new keymap should overwrite evething to only actions specials of that mode 27 | // 28 | // The app will receive all them in the same way. The only change will be in InputHandler 29 | // with Keymap used. With this, it will send Actions in the same way to App 30 | 31 | mod utils { 32 | use super::*; 33 | 34 | pub fn create_keymap_char(key: char, action: Actions) -> (KeyCode, Actionable) { 35 | ( 36 | KeyCode::Char(key), 37 | Actionable { 38 | action, 39 | sub_action: None, 40 | }, 41 | ) 42 | } 43 | 44 | pub fn create_sub_keymap_char(key: char, subcommands: KeyMap) -> (KeyCode, Actionable) { 45 | ( 46 | KeyCode::Char(key), 47 | Actionable { 48 | action: Actions::SubCommand, 49 | sub_action: Some(subcommands), 50 | }, 51 | ) 52 | } 53 | 54 | pub fn create_keymap(key_code: KeyCode, action: Actions) -> (KeyCode, Actionable) { 55 | ( 56 | key_code, 57 | Actionable { 58 | action, 59 | sub_action: None, 60 | }, 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/input/keymaps/normal_mode.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crossterm::event::KeyCode; 3 | use std::collections::HashMap; 4 | 5 | use super::utils::{create_keymap, create_keymap_char, create_sub_keymap_char}; 6 | use super::KeyMap; 7 | 8 | pub fn keymap_factory() -> KeyMap { 9 | HashMap::from([ 10 | create_keymap_char('?', Actions::AskForHelp), 11 | create_keymap_char('q', Actions::Quit), 12 | create_keymap_char('e', Actions::Edit), 13 | create_keymap_char('d', Actions::Delete), 14 | create_keymap(KeyCode::Enter, Actions::Submit), 15 | create_keymap(KeyCode::Esc, Actions::CancelSubmit), 16 | create_keymap(KeyCode::Tab, Actions::Switch), 17 | create_keymap(KeyCode::BackTab, Actions::InverseSwitch), 18 | create_keymap_char('j', Actions::Down), 19 | create_keymap_char('k', Actions::Up), 20 | create_keymap_char('l', Actions::Right), 21 | create_keymap_char('h', Actions::Left), 22 | create_keymap(KeyCode::Up, Actions::Up), 23 | create_keymap(KeyCode::Down, Actions::Down), 24 | create_keymap(KeyCode::Left, Actions::Left), 25 | create_keymap(KeyCode::Right, Actions::Right), 26 | create_keymap_char('n', Actions::New), 27 | create_keymap_char('r', Actions::ReloadBody), 28 | create_keymap_char('u', Actions::Undo), 29 | create_keymap_char('s', Actions::Save), 30 | create_keymap_char('G', Actions::GoToLogs), 31 | create_sub_keymap_char( 32 | 'g', 33 | HashMap::from([ 34 | create_keymap_char('g', Actions::GoToTabList), 35 | create_keymap_char('t', Actions::GoToNextTab), 36 | create_keymap_char('T', Actions::GoToPreviousTab), 37 | create_keymap_char('l', Actions::GrowHorizontalUiLeft), 38 | create_keymap_char('h', Actions::GrowHorizontalUiRight), 39 | create_keymap_char('v', Actions::GoToEnvironment), 40 | ]), 41 | ), 42 | ]) 43 | } 44 | -------------------------------------------------------------------------------- /src/base/os/file_facades.rs: -------------------------------------------------------------------------------- 1 | use serde::{self, Deserialize, Serialize}; 2 | 3 | use crate::utils::files::FileUtils; 4 | use std::fs::OpenOptions; 5 | use std::hash::Hash; 6 | use std::io::Write; 7 | use std::path::PathBuf; 8 | 9 | // #[cfg(test)] 10 | // use mockall::{automock, mock, predicate::*}; 11 | 12 | pub mod requests; 13 | pub mod temp_edition; 14 | pub mod variables; 15 | 16 | #[cfg_attr(test, mockall::automock)] 17 | pub trait FileFacade 18 | where 19 | FileID: PartialEq + Eq + Hash, 20 | FileEntity: for<'a> Deserialize<'a> + Serialize + Clone, 21 | { 22 | /// Setup the parent folders of the Struct 23 | fn setup() -> Result<(), String> 24 | where 25 | Self: Sized, 26 | { 27 | let path = Self::get_root_path(); 28 | FileUtils::create_path_if_it_does_not_exist(&path) 29 | } 30 | 31 | fn get_content(&self) -> Result { 32 | let content = std::fs::read_to_string(self.get_path()).map_err(|e| e.to_string())?; 33 | serde_json::from_str(&content).map_err(|e| e.to_string()) 34 | } 35 | 36 | fn save_content(&mut self, value: FileEntity) -> Result<(), String> { 37 | let mut file = OpenOptions::new() 38 | .create(true) 39 | .write(true) 40 | .open(self.get_path()) 41 | .map_err(|e| e.to_string())?; 42 | 43 | file.set_len(0).map_err(|e| e.to_string())?; 44 | 45 | let content: String = serde_json::to_string_pretty(&value).map_err(|e| e.to_string())?; 46 | 47 | file.write_all(content.as_bytes()) 48 | .map_err(|e| e.to_string())?; 49 | Ok(()) 50 | } 51 | 52 | fn remove(&mut self) -> Result<(), String> { 53 | std::fs::remove_file(self.get_path()).map_err(|e| e.to_string()) 54 | } 55 | 56 | // Must defines 57 | fn get_root_path() -> PathBuf 58 | where 59 | Self: Sized; 60 | fn get_path(&self) -> PathBuf; 61 | fn create(id: FileID, value: FileEntity) -> Result 62 | where 63 | Self: Sized; 64 | } 65 | -------------------------------------------------------------------------------- /src/base/stores/environment.rs: -------------------------------------------------------------------------------- 1 | use crate::base::os::handler::FileHandler; 2 | use crate::utils::custom_types::uuid::UUID; 3 | use std::{collections::HashMap, rc::Rc, sync::Mutex}; 4 | 5 | pub struct EnvironmentStore { 6 | pub session: HashMap, 7 | 8 | // Persistent 9 | pub global: HashMap, 10 | id_file: UUID, 11 | file_handler: Rc>, 12 | } 13 | 14 | impl EnvironmentStore { 15 | pub fn init(file_handler: Rc>) -> Result { 16 | let file_handler_cc = file_handler.lock().unwrap(); 17 | let map_files = file_handler_cc.get_map_files_variables(); 18 | 19 | // 'map_files' should contains only ONE element 20 | let (id, file) = map_files.iter().next().unwrap(); 21 | let content = file.get_content()?; 22 | 23 | let s = Self { 24 | session: HashMap::from([ 25 | ("lorem".to_string(), "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.".to_string()), 26 | ("random_uuid".to_string(), uuid::Uuid::new_v4().to_string()), 27 | ]), 28 | global: content, 29 | id_file: id.clone(), 30 | file_handler: file_handler.clone(), 31 | }; 32 | Ok(s) 33 | } 34 | 35 | pub fn get_map(&self) -> HashMap { 36 | let mut map = self.global.clone(); 37 | map.extend(self.session.clone()); 38 | map 39 | } 40 | 41 | pub fn save_globals(&self) -> Result<(), String> { 42 | let mut file_handler = self.file_handler.lock().unwrap(); 43 | file_handler.save_content_variable_file(&self.id_file, self.global.clone())?; 44 | Ok(()) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/base/os/file_facades/temp_edition.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::custom_types::uuid::UUID; 2 | use tempfile::Builder; 3 | 4 | use super::FileFacade; 5 | use std::fs::OpenOptions; 6 | use std::io::Write; 7 | use std::path::Path; 8 | use std::path::PathBuf; 9 | 10 | #[derive(Clone, Debug)] 11 | pub struct TempEditionfile { 12 | pub path: PathBuf, 13 | } 14 | 15 | impl Drop for TempEditionfile { 16 | fn drop(&mut self) { 17 | self.remove().ok(); 18 | } 19 | } 20 | 21 | impl FileFacade for TempEditionfile { 22 | fn setup() -> Result<(), String> { 23 | Ok(()) 24 | } 25 | 26 | fn get_path(&self) -> PathBuf { 27 | self.path.clone() 28 | } 29 | 30 | fn get_root_path() -> PathBuf { 31 | Path::new("/tmp").to_path_buf() 32 | } 33 | 34 | fn create(id: UUID, value: String) -> Result { 35 | let mut temp_file = Builder::new() 36 | .prefix(&id.value) 37 | .suffix(".json") 38 | .rand_bytes(10) 39 | .tempfile() 40 | .map_err(|e| e.to_string())?; 41 | 42 | temp_file 43 | .write_all(value.as_bytes()) 44 | .map_err(|e| e.to_string())?; 45 | 46 | let path = temp_file.path().to_path_buf(); 47 | let temp_file_facade = Self { path }; 48 | 49 | // Create file in '/tmp' folder to be used then 50 | temp_file 51 | .into_temp_path() 52 | .keep() 53 | .map_err(|e| e.to_string())?; 54 | 55 | Ok(temp_file_facade) 56 | } 57 | 58 | fn get_content(&self) -> Result { 59 | std::fs::read_to_string(self.get_path()).map_err(|e| e.to_string()) 60 | } 61 | 62 | fn save_content(&mut self, value: String) -> Result<(), String> { 63 | let mut file = OpenOptions::new() 64 | .create(true) 65 | .write(true) 66 | .open(self.get_path()) 67 | .map_err(|e| e.to_string())?; 68 | 69 | file.set_len(0).map_err(|e| e.to_string())?; 70 | 71 | file.write_all(value.as_bytes()) 72 | .map_err(|e| e.to_string())?; 73 | Ok(()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/view/views/logs/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::base::logs::LogType; 2 | use crate::view::renderer::tui_rs::BackendTuiRs; 3 | use crate::view::renderer::Tui; 4 | use crate::view::style::{Color, Style, Text, Texts}; 5 | use crate::view::views::ViewStates; 6 | use crate::{base::stores::MainStore, view::components::Component}; 7 | use tui::layout::{Constraint, Direction, Layout, Rect}; 8 | 9 | pub mod log_view; 10 | 11 | pub struct LogView<'a> { 12 | pub area: Rect, 13 | pub store: &'a MainStore, 14 | pub states: &'a ViewStates, 15 | } 16 | 17 | impl LogView<'_> { 18 | pub fn prepare_render<'b>(_states: &mut ViewStates, _store: &'b MainStore) { 19 | // Does nothing for while 20 | } 21 | } 22 | 23 | impl Component for LogView<'_> { 24 | type Backend = BackendTuiRs; 25 | fn render(&self, f: &mut Self::Backend) { 26 | let layout = Layout::default() 27 | .direction(Direction::Vertical) 28 | .constraints([Constraint::Length(1), Constraint::Min(1)].as_ref()) 29 | .split(self.area); 30 | 31 | let empty_string = String::new(); 32 | 33 | let title = &self.store.log.title; 34 | let detail = self.store.log.detail.as_ref().unwrap_or(&empty_string); 35 | let log_type = &self.store.log.log_type; 36 | 37 | let color_title = match log_type { 38 | LogType::Error => Color::Red, 39 | LogType::Help => Color::Blue, 40 | LogType::Empty => Color::Black, 41 | LogType::Warning => Color::Yellow, 42 | LogType::InputMode => Color::Cyan, 43 | }; 44 | 45 | let content = Texts { 46 | body: vec![ 47 | Text { 48 | body: title, 49 | style: Some(Style { 50 | color: color_title, 51 | property: None, 52 | }), 53 | }, 54 | Text { 55 | body: detail, 56 | style: None, 57 | }, 58 | ], 59 | }; 60 | 61 | f.render_divider_with_text(Texts::from_str("Logs"), layout[0]); 62 | f.render_text(content, layout[1]); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/base/web/request.rs: -------------------------------------------------------------------------------- 1 | use serde::{self, Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 5 | pub enum METHODS { 6 | DELETE, 7 | GET, 8 | HEAD, 9 | PATCH, 10 | POST, 11 | PUT, 12 | } 13 | impl ToString for METHODS { 14 | fn to_string(&self) -> String { 15 | match self { 16 | Self::GET => "GET".to_string(), 17 | Self::POST => "POST".to_string(), 18 | Self::HEAD => "HEAD".to_string(), 19 | Self::PATCH => "PATCH".to_string(), 20 | Self::PUT => "PUT".to_string(), 21 | Self::DELETE => "DELETE".to_string(), 22 | } 23 | } 24 | } 25 | 26 | pub struct HeadersRequest; 27 | impl HeadersRequest { 28 | pub fn default() -> HashMap { 29 | HashMap::from( 30 | [("Content-Type", "application/json")].map(|(k, v)| (String::from(k), String::from(v))), 31 | ) 32 | } 33 | } 34 | 35 | #[derive(Clone, Debug, Serialize, Deserialize)] 36 | pub struct Request { 37 | pub name: String, 38 | pub url: String, 39 | pub method: METHODS, 40 | pub headers: HashMap, 41 | pub body: String, 42 | 43 | #[serde(skip)] 44 | pub has_changed: bool, 45 | } 46 | 47 | impl Default for Request { 48 | fn default() -> Self { 49 | Self { 50 | method: METHODS::GET, 51 | name: String::from("New Request"), 52 | url: String::new(), 53 | headers: HeadersRequest::default(), 54 | body: String::from("{}"), 55 | has_changed: false, 56 | } 57 | } 58 | } 59 | 60 | impl Request { 61 | pub fn set_name(&mut self, name: String) { 62 | self.name = name; 63 | } 64 | 65 | pub fn set_url(&mut self, url: String) { 66 | self.url = url; 67 | } 68 | 69 | pub fn set_method(&mut self, method: METHODS) { 70 | self.method = method; 71 | } 72 | 73 | pub fn set_headers(&mut self, headers: HashMap) { 74 | self.headers = headers; 75 | } 76 | 77 | pub fn set_body(&mut self, body: String) { 78 | self.body = body; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/base/os/os_commands/external_editor.rs: -------------------------------------------------------------------------------- 1 | use crate::base::commands::{Command, Commands}; 2 | 3 | use super::OsCommandTrait; 4 | use std::{ 5 | path::PathBuf, 6 | process::{Command as OSCommand, Stdio}, 7 | }; 8 | use tokio::sync::mpsc::Sender; 9 | 10 | pub type OsCommandEditor = Box; 11 | 12 | pub struct ExternalEditor { 13 | pub command_editor: String, 14 | pub path: PathBuf, 15 | pub command: Command, 16 | } 17 | 18 | impl ExternalEditor { 19 | pub fn is_valid() -> bool { 20 | let treq_editor = std::env::var("TREQ_EDITOR").is_ok(); 21 | let default_editor = std::env::var("EDITOR").is_ok(); 22 | treq_editor || default_editor 23 | } 24 | 25 | pub fn init(path: PathBuf, command: Command) -> Result { 26 | let treq_editor = std::env::var("TREQ_EDITOR").map_err(|e| e.to_string()); 27 | let default_editor = std::env::var("EDITOR").map_err(|e| e.to_string()); 28 | 29 | let command_editor = treq_editor.unwrap_or(default_editor.unwrap_or("nano".to_string())); 30 | Ok(Self { 31 | path, 32 | command, 33 | command_editor, 34 | }) 35 | } 36 | } 37 | 38 | impl OsCommandTrait for ExternalEditor { 39 | fn exec(&self, sender: Sender) -> Result<(), String> { 40 | let mut child = OSCommand::new(&self.command_editor) 41 | .arg(self.path.clone()) 42 | .stdin(Stdio::inherit()) 43 | .stdout(Stdio::inherit()) 44 | .stderr(Stdio::inherit()) 45 | .spawn() 46 | .expect("failed to execute Command"); 47 | 48 | let _status = child.wait().expect("failed to wait Command"); 49 | 50 | let content = std::fs::read_to_string(self.path.clone()).map_err(|e| e.to_string())?; 51 | 52 | let set_input_buffer = Commands::set_input_buffer(content); 53 | let exec_input_buffer = self.command.clone(); 54 | 55 | tokio::task::spawn(async move { 56 | log::info!("DENTRO DO SPAWN DO EXTERNAL"); 57 | sender.send(set_input_buffer).await.ok(); 58 | sender.send(exec_input_buffer).await.ok(); 59 | log::info!("FIM DO SPAWN DO EXTERNAL"); 60 | }); 61 | 62 | Ok(()) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.github/workflows/cd.yml: -------------------------------------------------------------------------------- 1 | name: CD - Crates Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | 21 | publish_cargo: 22 | name: Publish in Crates.io 23 | runs-on: ubuntu-latest 24 | needs: 25 | - test 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Login in Crates.io 29 | run: cargo login ${{ secrets.CARGO_TOKEN }} 30 | - name: Publish 31 | run: cargo publish --verbose 32 | 33 | create_deb: 34 | name: Create .deb File in Release 35 | runs-on: ubuntu-latest 36 | needs: 37 | - test 38 | steps: 39 | - uses: actions/checkout@v3 40 | - name: Install and Run Cargo Deb to generate the .deb File 41 | run: cargo install cargo-deb && cargo deb --output . && ls -la 42 | 43 | - name: Upload binaries to release 44 | uses: svenstaro/upload-release-action@v2 45 | with: 46 | tag: ${{ github.ref }} 47 | overwrite: true 48 | file: '*.deb' 49 | file_glob: true 50 | 51 | publish_aur: 52 | name: Publish in AUR Package 53 | runs-on: ubuntu-latest 54 | needs: 55 | - test 56 | steps: 57 | - uses: actions/checkout@v3 58 | - name: Install and Run Cargo AUR to generate PKGBUILD 59 | run: cargo install cargo-aur && cargo aur && cat ./PKGBUILD 60 | 61 | - name: Upload binaries to release 62 | uses: svenstaro/upload-release-action@v2 63 | with: 64 | tag: ${{ github.ref }} 65 | overwrite: true 66 | file: '*.tar.gz' 67 | file_glob: true 68 | 69 | - name: Publish AUR package 70 | uses: KSXGitHub/github-actions-deploy-aur@v2.6.0 71 | with: 72 | pkgname: treq-bin 73 | pkgbuild: ./PKGBUILD 74 | commit_username: ${{ secrets.AUR_USERNAME }} 75 | commit_email: ${{ secrets.AUR_EMAIL }} 76 | ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} 77 | commit_message: Update AUR package 78 | -------------------------------------------------------------------------------- /src/view/components/tab_blocked_text.rs: -------------------------------------------------------------------------------- 1 | use super::block_text::BlockText; 2 | use super::Component; 3 | use crate::view::renderer::tui_rs::BackendTuiRs; 4 | 5 | use crate::view::style::{Color, Style, Text, Texts}; 6 | use tui::layout::Rect; 7 | 8 | pub struct TabBlockText<'a> { 9 | pub area: Rect, 10 | pub texts: Vec<(&'a str, &'a str)>, 11 | pub current: usize, 12 | pub marked: bool, 13 | } 14 | impl Component for TabBlockText<'_> { 15 | type Backend = BackendTuiRs; 16 | fn render(&self, f: &mut Self::Backend) { 17 | let (_, current_content) = self.texts.get(self.current).unwrap(); 18 | 19 | let titles_vec: Vec<&str> = self.texts.iter().map(|f| f.0).collect(); 20 | let titles_len = titles_vec.len(); 21 | let mut titles_texts_with_style: Vec<(String, bool)> = vec![]; 22 | 23 | for (i, content) in titles_vec.into_iter().enumerate() { 24 | if i == self.current { 25 | titles_texts_with_style.push((content.to_uppercase(), true)); 26 | } else { 27 | titles_texts_with_style.push((content.to_string(), false)); 28 | } 29 | 30 | let last_index = titles_len - 1; 31 | if i < last_index { 32 | titles_texts_with_style.push((" / ".to_string(), false)); 33 | } 34 | } 35 | 36 | let title = Texts { 37 | body: titles_texts_with_style 38 | .iter() 39 | .map(|(body, is_styled)| Text { 40 | body, 41 | style: if *is_styled { 42 | Some(Style { 43 | color: Color::Yellow, 44 | property: None, 45 | }) 46 | } else { 47 | None 48 | }, 49 | }) 50 | .collect(), 51 | }; 52 | 53 | let current_content = Texts::from_str(current_content); 54 | 55 | let component = BlockText { 56 | area: self.area, 57 | title, 58 | content: current_content, 59 | marked: self.marked, 60 | }; 61 | 62 | component.render(f); 63 | 64 | // f.render_block_with_title_left(title, self.area); 65 | // f.render_text(current_content, self.area); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/view/ui.rs: -------------------------------------------------------------------------------- 1 | use crate::{base::stores::MainStore, config::configurations::view::ViewConfig}; 2 | 3 | use crossterm::{ 4 | event::DisableMouseCapture, 5 | execute, 6 | terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, 7 | }; 8 | use std::{collections::HashMap, io}; 9 | use tui::{backend::CrosstermBackend, Terminal}; 10 | 11 | use crate::view::components::Component; 12 | use crate::view::views::app::AppView; 13 | use crate::view::views::ViewStates; 14 | 15 | use super::{renderer::tui_rs::BackendTuiRs, UiTrait}; 16 | 17 | pub struct UI { 18 | backend: BackendTuiRs, 19 | view_states: ViewStates, 20 | } 21 | 22 | impl UI { 23 | pub fn init() -> Self { 24 | enable_raw_mode().unwrap(); 25 | let mut stdout = io::stdout(); 26 | execute!(stdout, EnterAlternateScreen).unwrap_or(()); 27 | let stdout = io::stdout(); 28 | let backend = CrosstermBackend::new(stdout); 29 | let terminal = Terminal::new(backend).unwrap(); 30 | 31 | let backend = BackendTuiRs { 32 | terminal, 33 | configs: ViewConfig::init(), 34 | queue_render: vec![], 35 | }; 36 | 37 | UI { 38 | backend, 39 | view_states: HashMap::new(), 40 | } 41 | } 42 | } 43 | impl UiTrait for UI { 44 | fn restart(&mut self) { 45 | let new_ui = Self::init(); 46 | self.backend = new_ui.backend; 47 | self.view_states = new_ui.view_states; 48 | } 49 | 50 | fn close(&mut self) { 51 | disable_raw_mode().unwrap(); 52 | execute!( 53 | self.backend.terminal.backend_mut(), 54 | LeaveAlternateScreen, 55 | DisableMouseCapture 56 | ) 57 | .unwrap(); 58 | self.backend.terminal.show_cursor().unwrap(); 59 | } 60 | 61 | fn render(&mut self, data_store: &MainStore) { 62 | self.backend.terminal.autoresize().unwrap(); 63 | let screen_area = self.backend.terminal.get_frame().size(); 64 | 65 | AppView::prepare_render(&mut self.view_states, data_store); 66 | 67 | let app_view = AppView { 68 | area: screen_area, 69 | store: data_store, 70 | states: &self.view_states, 71 | }; 72 | 73 | app_view.render(&mut self.backend); 74 | 75 | self.backend.draw_all(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/base/states/states/default.rs: -------------------------------------------------------------------------------- 1 | use crate::base::actions::Actions; 2 | use crate::base::commands::Commands; 3 | use crate::base::states::states::{CommandsMap, State, StatesNames}; 4 | use std::collections::HashMap; 5 | 6 | pub struct DefaultState { 7 | pub maps: CommandsMap, 8 | } 9 | impl State for DefaultState { 10 | fn get_state_name(&self) -> StatesNames { 11 | StatesNames::Default 12 | } 13 | fn get_map(&self) -> &CommandsMap { 14 | &self.maps 15 | } 16 | fn init() -> Self { 17 | Self { 18 | maps: HashMap::from([ 19 | // General Move 20 | (Actions::Up, Commands::go_to_tab_section()), 21 | (Actions::Down, Commands::go_to_tab_section()), 22 | (Actions::Right, Commands::go_to_response_body_section()), 23 | (Actions::Left, Commands::go_to_request_body_section()), 24 | // Jumps 25 | (Actions::GoToNextTab, Commands::go_to_next_tab()), 26 | (Actions::GoToPreviousTab, Commands::go_to_previous_tab()), 27 | (Actions::GoToTabList, Commands::go_to_tab_section()), 28 | ( 29 | Actions::GoToRequestBody, 30 | Commands::go_to_request_body_section(), 31 | ), 32 | ( 33 | Actions::GoToResponseBody, 34 | Commands::go_to_response_body_section(), 35 | ), 36 | (Actions::GoToUrl, Commands::go_to_url_section()), 37 | (Actions::GoToLogs, Commands::go_to_log_section()), 38 | (Actions::GoToEnvironment, Commands::open_environment_view()), 39 | (Actions::RenameTab, Commands::rename_tab()), 40 | (Actions::DeleteTab, Commands::delete_tab()), 41 | (Actions::Submit, Commands::async_submit()), 42 | (Actions::CancelSubmit, Commands::cancel_async_submit()), 43 | (Actions::Quit, Commands::quit()), 44 | (Actions::Undo, Commands::undo_state()), 45 | (Actions::AskForHelp, Commands::open_help_screen()), 46 | (Actions::Save, Commands::save_request()), 47 | (Actions::GrowHorizontalUiLeft, Commands::grow_left_ui()), 48 | (Actions::GrowHorizontalUiRight, Commands::grow_right_ui()), 49 | (Actions::ReloadBody, Commands::restart_body_of_file()), 50 | ]), 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/mocks/file_factory.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::path::PathBuf; 3 | 4 | use super::file_facade::MockFile; 5 | use treq::base::os::file_facades::FileFacade; 6 | use treq::base::os::file_factory::FileFactory; 7 | use treq::base::web::request::Request; 8 | use treq::utils::custom_types::uuid::UUID; 9 | 10 | type BoxRequestFile = Box>; 11 | type BoxTempEditionfile = Box>; 12 | type BoxVariablesFile = Box>>; 13 | 14 | #[derive(Default)] 15 | pub struct MockFileFactory { 16 | requests: HashSet, 17 | variables: HashSet, 18 | temp: HashSet, 19 | } 20 | 21 | impl FileFactory for MockFileFactory { 22 | fn create_request_file( 23 | &mut self, 24 | id: UUID, 25 | request: Request, 26 | ) -> Result { 27 | let does_not_contains_before = self.requests.insert(id.clone()); 28 | 29 | if !does_not_contains_before { 30 | panic!("Creating a file already created"); 31 | } 32 | 33 | Ok(Box::new(MockFile::create(id, request)?)) 34 | } 35 | 36 | fn create_variables_file( 37 | &mut self, 38 | id: String, 39 | variables: HashMap, 40 | ) -> Result { 41 | let does_not_contains_before = self.variables.insert(id.clone()); 42 | 43 | if !does_not_contains_before { 44 | panic!("Creating a file already created"); 45 | } 46 | 47 | Ok(Box::new(MockFile::create(id, variables)?)) 48 | } 49 | 50 | fn create_temp_file( 51 | &mut self, 52 | id: UUID, 53 | content: String, 54 | ) -> Result { 55 | let does_not_contains_before = self.temp.insert(id.clone()); 56 | 57 | if !does_not_contains_before { 58 | panic!("Creating a file already created"); 59 | } 60 | 61 | Ok(Box::new(MockFile::create(id, content)?)) 62 | } 63 | 64 | fn get_saved_request_file(&self, _path: PathBuf) -> Result { 65 | todo!() 66 | } 67 | fn get_saved_variables_file(&self, _path: PathBuf) -> Result { 68 | todo!() 69 | } 70 | fn get_saved_temp_file(&self, _path: PathBuf) -> Result { 71 | todo!() 72 | } 73 | 74 | // Utils 75 | fn get_saved_files_request(&self) -> Result, String> { 76 | todo!() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/base/commands/commands/docs.rs: -------------------------------------------------------------------------------- 1 | use crate::app::App; 2 | use crate::app::InputMode; 3 | use crate::base::commands::CommandTrait; 4 | use crate::base::commands::{Command, Commands}; 5 | use crate::base::doc::DocsFactory; 6 | 7 | impl Commands { 8 | pub fn open_help_screen() -> Command { 9 | struct S; 10 | impl CommandTrait for S { 11 | fn execute(&self, app: &mut App) -> Result<(), String> { 12 | app.get_data_store_mut().doc_reader = Some(DocsFactory::help_reader()); 13 | app.set_mode(InputMode::Help); 14 | Ok(()) 15 | } 16 | } 17 | 18 | Commands::from(S {}) 19 | } 20 | 21 | pub fn open_welcome_screen() -> Command { 22 | struct S; 23 | impl CommandTrait for S { 24 | fn execute(&self, app: &mut App) -> Result<(), String> { 25 | app.get_data_store_mut().doc_reader = Some(DocsFactory::welcome_reader()); 26 | app.set_mode(InputMode::Help); 27 | Ok(()) 28 | } 29 | } 30 | 31 | Commands::from(S {}) 32 | } 33 | 34 | pub fn doc_up() -> Command { 35 | struct S; 36 | impl CommandTrait for S { 37 | fn execute(&self, app: &mut App) -> Result<(), String> { 38 | let doc_reader = app.get_data_store_mut().doc_reader.as_mut(); 39 | 40 | if doc_reader.is_none() { 41 | return Err("There is not doc to read".to_string()); 42 | } 43 | 44 | doc_reader.unwrap().go_to_prev_row(); 45 | 46 | Ok(()) 47 | } 48 | } 49 | 50 | Commands::from(S {}) 51 | } 52 | 53 | pub fn doc_down() -> Command { 54 | struct S; 55 | impl CommandTrait for S { 56 | fn execute(&self, app: &mut App) -> Result<(), String> { 57 | let doc_reader = app.get_data_store_mut().doc_reader.as_mut(); 58 | 59 | if doc_reader.is_none() { 60 | return Err("There is not doc to read".to_string()); 61 | } 62 | 63 | doc_reader.unwrap().go_to_next_row(); 64 | 65 | Ok(()) 66 | } 67 | } 68 | 69 | Commands::from(S {}) 70 | } 71 | 72 | pub fn doc_exit() -> Command { 73 | struct S; 74 | impl CommandTrait for S { 75 | fn execute(&self, app: &mut App) -> Result<(), String> { 76 | app.set_mode(InputMode::Normal); 77 | Ok(()) 78 | } 79 | } 80 | 81 | Commands::from(S {}) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/view/views/environment/store.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize)] 6 | pub struct Var { 7 | pub key: String, 8 | } 9 | 10 | #[derive(Clone, Copy, Deserialize, Serialize)] 11 | pub enum OpenedVars { 12 | Global, 13 | Session, 14 | } 15 | 16 | #[derive(Deserialize, Serialize)] 17 | pub struct EnvironmentVars { 18 | pub global: Vec, 19 | pub session: Vec, 20 | } 21 | 22 | // Manage the State of view 23 | #[derive(Deserialize, Serialize)] 24 | pub struct State { 25 | pub opened_section: OpenedVars, 26 | pub current_global_var: usize, 27 | pub current_session_var: usize, 28 | pub vars_keys: EnvironmentVars, 29 | } 30 | 31 | impl State { 32 | pub fn get_current_var_key(&self) -> Option { 33 | match self.opened_section { 34 | OpenedVars::Global => { 35 | let index = self.current_global_var; 36 | Some(self.vars_keys.global.get(index)?.clone()) 37 | } 38 | 39 | OpenedVars::Session => { 40 | let index = self.current_session_var; 41 | Some(self.vars_keys.session.get(index)?.clone()) 42 | } 43 | } 44 | } 45 | 46 | pub fn sync( 47 | &mut self, 48 | global_variables: &HashMap, 49 | session_variables: &HashMap, 50 | ) { 51 | // Update SESSION variables 52 | self.vars_keys 53 | .session 54 | .retain(|key| session_variables.contains_key(key)); 55 | 56 | let new_keys: Vec = session_variables 57 | .keys() 58 | .filter(|key| !self.vars_keys.session.contains(key)) 59 | .cloned() 60 | .collect(); 61 | 62 | self.vars_keys.session.extend(new_keys); 63 | 64 | // Update GLOBAL variables 65 | self.vars_keys 66 | .global 67 | .retain(|key| global_variables.contains_key(key)); 68 | 69 | let new_keys: Vec = global_variables 70 | .keys() 71 | .filter(|key| !self.vars_keys.global.contains(key)) 72 | .cloned() 73 | .collect(); 74 | 75 | self.vars_keys.global.extend(new_keys); 76 | 77 | // Update INDEXES 78 | if self 79 | .vars_keys 80 | .session 81 | .get(self.current_session_var) 82 | .is_none() 83 | { 84 | self.current_session_var = 0; 85 | } 86 | if self.vars_keys.global.get(self.current_global_var).is_none() { 87 | self.current_global_var = 0; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/view/style/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use tui::style::Color as ColorTuiRs; 3 | 4 | pub enum Size { 5 | Percentage(u16), 6 | Fixed(u16), 7 | } 8 | 9 | #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 10 | pub enum Color { 11 | Red, 12 | Blue, 13 | Green, 14 | Yellow, 15 | Orange, 16 | Black, 17 | White, 18 | Gray, 19 | Brown, 20 | Cyan, 21 | Pink, 22 | Magenta, 23 | Rgb(i32, i32, i32), 24 | } 25 | 26 | impl Color { 27 | pub fn to_tuirs(&self) -> ColorTuiRs { 28 | match self { 29 | Color::Red => ColorTuiRs::Red, 30 | Color::Blue => ColorTuiRs::Blue, 31 | Color::Green => ColorTuiRs::Green, 32 | Color::Yellow => ColorTuiRs::Yellow, 33 | Color::Black => ColorTuiRs::Black, 34 | Color::White => ColorTuiRs::White, 35 | Color::Gray => ColorTuiRs::Gray, 36 | Color::Cyan => ColorTuiRs::Cyan, 37 | Color::Magenta => ColorTuiRs::Magenta, 38 | Color::Brown => ColorTuiRs::White, 39 | Color::Orange => ColorTuiRs::White, 40 | Color::Pink => ColorTuiRs::White, 41 | Color::Rgb(r, g, b) => ColorTuiRs::Rgb(*r as u8, *g as u8, *b as u8), 42 | } 43 | } 44 | } 45 | 46 | #[derive(Clone, Debug, PartialEq, Eq)] 47 | pub enum Property { 48 | Marked, 49 | } 50 | 51 | #[derive(Clone, Debug, PartialEq, Eq)] 52 | pub struct Style { 53 | pub color: Color, 54 | pub property: Option, 55 | } 56 | impl Style { 57 | pub fn from_color(color: Color) -> Self { 58 | Self { 59 | color, 60 | property: None, 61 | } 62 | } 63 | } 64 | 65 | #[derive(Clone, Debug, PartialEq, Eq)] 66 | pub struct Text<'a> { 67 | pub body: &'a str, 68 | pub style: Option