├── src ├── lib │ ├── tui │ │ ├── widgets │ │ │ ├── confirm.rs │ │ │ ├── mod.rs │ │ │ ├── popup.rs │ │ │ ├── field.rs │ │ │ ├── path_hint.rs │ │ │ └── worker_info.rs │ │ ├── mod.rs │ │ └── app.rs │ ├── logger │ │ ├── mod.rs │ │ ├── traits.rs │ │ └── file_logger.rs │ ├── worker │ │ ├── mod.rs │ │ ├── messages.rs │ │ ├── builder.rs │ │ └── unit.rs │ └── util.rs ├── lib.rs └── bin │ ├── yadb-tui.rs │ └── yadb-cli.rs ├── .gitignore ├── .github └── workflows │ ├── release.yml │ └── rust-ci.yml ├── LICENSE ├── Cargo.toml ├── README.md └── Cargo.lock /src/lib/tui/widgets/confirm.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/lib/tui/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod app; 2 | mod widgets; 3 | -------------------------------------------------------------------------------- /src/lib/logger/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod file_logger; 2 | pub mod traits; 3 | -------------------------------------------------------------------------------- /src/lib/worker/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod builder; 2 | pub mod messages; 3 | pub mod unit; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | test_server.py 3 | wordlist.txt 4 | output.txt 5 | todo 6 | .vscode -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod lib { 2 | pub mod logger; 3 | pub mod tui; 4 | pub mod util; 5 | pub mod worker; 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/tui/widgets/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod confirm; 2 | pub mod field; 3 | pub mod path_hint; 4 | pub mod popup; 5 | pub mod worker_info; 6 | -------------------------------------------------------------------------------- /src/bin/yadb-tui.rs: -------------------------------------------------------------------------------- 1 | use crossterm::cursor::SetCursorStyle; 2 | use yadb::lib::tui::app::App; 3 | 4 | fn main() -> color_eyre::Result<()> { 5 | color_eyre::install()?; 6 | let terminal = ratatui::init(); 7 | _ = crossterm::execute!(std::io::stdout(), SetCursorStyle::SteadyBar); 8 | let result = App::new().run(terminal); 9 | ratatui::restore(); 10 | _ = crossterm::execute!(std::io::stdout(), SetCursorStyle::DefaultUserShape); 11 | result 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/util.rs: -------------------------------------------------------------------------------- 1 | pub fn print_logo() { 2 | println!( 3 | " 4 | ▓██ ██▓ ▄▄▄ ▓█████▄ ▄▄▄▄ 5 | ▒██ ██▒▒████▄ ▒██▀ ██▌▓█████▄ 6 | ▒██ ██░▒██ ▀█▄ ░██ █▌▒██▒ ▄██ 7 | ░ ▐██▓░░██▄▄▄▄██ ░▓█▄ ▌▒██░█▀ 8 | ░ ██▒▓░ ▓█ ▓██▒░▒████▓ ░▓█ ▀█▓ 9 | ██▒▒▒ ▒▒ ▓▒█░ ▒▒▓ ▒ ░▒▓███▀▒ 10 | ▓██ ░▒░ ▒ ▒▒ ░ ░ ▒ ▒ ▒░▒ ░ 11 | ▒ ▒ ░░ ░ ▒ ░ ░ ░ ░ ░ 12 | ░ ░ ░ ░ ░ ░ 13 | ░ ░ ░ ░ 14 | " 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build binaries on release 2 | 3 | on: 4 | release: 5 | types: ['published'] 6 | 7 | permissions: 8 | contents: write 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | 13 | jobs: 14 | rust-cd: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: Swatinem/rust-cache@v2 19 | with: 20 | cache-on-failure: true 21 | 22 | - uses: dtolnay/rust-toolchain@stable 23 | with: 24 | components: clippy 25 | 26 | - name: Build 27 | run: cargo build --release 28 | 29 | - uses: softprops/action-gh-release@v2 30 | with: 31 | files: | 32 | ./target/release/yadb-cli 33 | ./target/release/yadb-tui -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YADB - Yet Another Directory Buster 2 | 3 | Copyright (C) 2025 Bogdan Andreev 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License 16 | along with this program. If not, see . 17 | -------------------------------------------------------------------------------- /.github/workflows/rust-ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | pull_request: 5 | branches: [ "master" ] 6 | 7 | permissions: 8 | checks: write 9 | pull-requests: write 10 | contents: read 11 | 12 | env: 13 | CARGO_TERM_COLOR: always 14 | 15 | jobs: 16 | rust-ci: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: Swatinem/rust-cache@v2 21 | with: 22 | cache-on-failure: true 23 | - uses: dtolnay/rust-toolchain@stable 24 | with: 25 | components: clippy 26 | 27 | - name: Check format 28 | run: cargo fmt -- --check 29 | 30 | - name: Run Clippy 31 | run: cargo clippy -- -D warnings 32 | 33 | - name: Build 34 | run: cargo build 35 | 36 | - name: Run tests 37 | run: cargo test --all 38 | -------------------------------------------------------------------------------- /src/lib/logger/traits.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Mutex; 2 | 3 | use crate::lib::logger::file_logger::FileLogger; 4 | 5 | pub enum LogLevel { 6 | INFO, 7 | WARN, 8 | ERROR, 9 | CRITICAL, 10 | } 11 | 12 | #[derive(Debug)] 13 | pub enum WorkerLogger { 14 | NullLogger(NullLogger), 15 | FileLogger(Mutex), 16 | } 17 | 18 | pub trait Logger: Send + Sync + 'static { 19 | fn log(&self, level: LogLevel, msg: String); 20 | } 21 | #[derive(Default, Debug)] 22 | pub struct NullLogger {} 23 | 24 | impl Logger for NullLogger { 25 | fn log(&self, _level: LogLevel, _msg: String) {} 26 | } 27 | 28 | impl WorkerLogger { 29 | pub fn log(&self, level: LogLevel, msg: String) { 30 | match self { 31 | WorkerLogger::NullLogger(logger) => logger.log(level, msg), 32 | WorkerLogger::FileLogger(logger) => logger.lock().unwrap().log(level, msg), 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "yadb" 3 | description = "Yet Another Directory Buster" 4 | version = "0.3.5" 5 | edition = "2024" 6 | readme = "README.md" 7 | repository = "https://github.com/izya4ka/yadb" 8 | license = "GPL-3.0" 9 | keywords = ["net", "scan", "web", "cli", "tui"] 10 | default-run = "yadb-cli" 11 | 12 | [dependencies] 13 | clap = { version = "4.5.39", features = ["derive"] } 14 | indicatif = "0.17.11" 15 | thiserror = "2.0.12" 16 | console = "0.15.11" 17 | url = "2.5.4" 18 | anyhow = "1.0.98" 19 | chrono = "0.4.41" 20 | ureq = "3.0.12" 21 | ratatui = "0.29.0" 22 | color-eyre = "0.6.5" 23 | crossterm = "0.29.0" 24 | tui-input = "0.14.0" 25 | 26 | [profile.dev] 27 | opt-level = 0 28 | debug = true 29 | incremental = true 30 | codegen-units = 256 31 | lto = false 32 | panic = "unwind" 33 | strip = "none" 34 | 35 | [profile.release] 36 | opt-level = 3 37 | debug = false 38 | lto = "fat" 39 | codegen-units = 1 40 | panic = "abort" 41 | strip = "symbols" 42 | incremental = false 43 | -------------------------------------------------------------------------------- /src/lib/logger/file_logger.rs: -------------------------------------------------------------------------------- 1 | use super::traits::LogLevel; 2 | use anyhow::Result; 3 | use chrono::Local; 4 | use std::{fs::File, io::Write}; 5 | 6 | use crate::lib::logger::traits::Logger; 7 | 8 | #[derive(Default, Debug)] 9 | pub struct FileLogger { 10 | file: Option, 11 | } 12 | 13 | impl FileLogger { 14 | pub fn new(path: String) -> Result { 15 | let file = File::create(path)?; 16 | Ok(FileLogger { file: Some(file) }) 17 | } 18 | } 19 | 20 | impl Logger for FileLogger { 21 | fn log(&self, level: LogLevel, msg: String) { 22 | if let Some(mut file) = self.file.as_ref() { 23 | let mut str = String::default(); 24 | 25 | str += &Local::now().format("[%H:%M:%S] ").to_string(); 26 | 27 | str += match level { 28 | LogLevel::INFO => "[INFO] ", 29 | LogLevel::WARN => "[WARN] ", 30 | LogLevel::ERROR => "[ERROR] ", 31 | LogLevel::CRITICAL => "[CRITICAL] ", 32 | }; 33 | 34 | str += &msg; 35 | str += "\n"; 36 | 37 | let _ = file.write(str.as_bytes()); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/lib/worker/messages.rs: -------------------------------------------------------------------------------- 1 | use crate::lib::logger::traits::LogLevel; 2 | 3 | pub enum WorkerMessage { 4 | Progress(ProgressMessage), 5 | Log(LogLevel, String), 6 | } 7 | pub enum ProgressMessage { 8 | Total(ProgressChangeMessage), 9 | Current(ProgressChangeMessage), 10 | } 11 | 12 | pub enum ProgressChangeMessage { 13 | SetMessage(String), 14 | SetSize(usize), 15 | Start(usize), 16 | Advance, 17 | Print(String), 18 | Finish, 19 | } 20 | 21 | impl WorkerMessage { 22 | pub fn set_total_size(size: usize) -> WorkerMessage { 23 | WorkerMessage::Progress(ProgressMessage::Total(ProgressChangeMessage::SetSize(size))) 24 | } 25 | 26 | pub fn set_current_size(size: usize) -> WorkerMessage { 27 | WorkerMessage::Progress(ProgressMessage::Current(ProgressChangeMessage::SetSize( 28 | size, 29 | ))) 30 | } 31 | 32 | pub fn finish_total() -> WorkerMessage { 33 | WorkerMessage::Progress(ProgressMessage::Total(ProgressChangeMessage::Finish)) 34 | } 35 | 36 | pub fn finish_current() -> WorkerMessage { 37 | WorkerMessage::Progress(ProgressMessage::Current(ProgressChangeMessage::Finish)) 38 | } 39 | 40 | pub fn log(level: LogLevel, str: String) -> WorkerMessage { 41 | WorkerMessage::Log(level, str) 42 | } 43 | 44 | pub fn advance_current() -> WorkerMessage { 45 | WorkerMessage::Progress(ProgressMessage::Current(ProgressChangeMessage::Advance)) 46 | } 47 | 48 | pub fn advance_total() -> WorkerMessage { 49 | WorkerMessage::Progress(ProgressMessage::Total(ProgressChangeMessage::Advance)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/lib/tui/widgets/popup.rs: -------------------------------------------------------------------------------- 1 | use ratatui::{ 2 | buffer::Buffer, 3 | layout::{self, Constraint, Flex, Layout, Rect}, 4 | style::{Style, Stylize}, 5 | text::{Line, Text}, 6 | widgets::{Block, Borders, Clear, Paragraph, Widget}, 7 | }; 8 | 9 | pub struct Popup<'a> { 10 | // Custom widget properties 11 | content: Text<'a>, 12 | title: String, 13 | } 14 | 15 | impl<'a> Widget for Popup<'a> { 16 | fn render(self, area: Rect, buf: &mut Buffer) { 17 | let area = Self::popup_area(area, 30, 15); 18 | Clear.render(area, buf); 19 | 20 | let title = Line::from(self.title) 21 | .bold() 22 | .style(Style::new().blue()) 23 | .centered(); 24 | 25 | let block = Block::default() 26 | .borders(Borders::all()) 27 | .border_type(ratatui::widgets::BorderType::Double) 28 | .title(title); 29 | 30 | let layout: [Rect; 2] = Layout::new( 31 | layout::Direction::Vertical, 32 | [Constraint::Percentage(80), Constraint::Length(1)], 33 | ) 34 | .areas(block.inner(area)); 35 | 36 | block.render(area, buf); 37 | let text = Paragraph::new(self.content).centered(); 38 | text.render(layout[0], buf); 39 | 40 | Paragraph::new("OK") 41 | .reversed() 42 | .blue() 43 | .render(layout[1], buf); 44 | } 45 | } 46 | 47 | impl<'a> Popup<'a> { 48 | pub fn new(title: String, content: Text<'a>) -> Self { 49 | Self { title, content } 50 | } 51 | 52 | fn popup_area(area: Rect, percent_x: u16, percent_y: u16) -> Rect { 53 | let vertical = Layout::vertical([Constraint::Percentage(percent_y)]).flex(Flex::Center); 54 | let horizontal = Layout::horizontal([Constraint::Percentage(percent_x)]).flex(Flex::Center); 55 | let [area] = vertical.areas(area); 56 | let [area] = horizontal.areas(area); 57 | area 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YADB - Yet Another Directory Buster 2 | ![Issues](https://img.shields.io/github/issues/izya4ka/yadb) 3 | ![Last Commit](https://img.shields.io/github/last-commit/izya4ka/yadb) 4 | ![](https://img.shields.io/crates/l/yadb) 5 | ![](https://img.shields.io/github/languages/top/izya4ka/yadb) 6 | [![Built With Ratatui](https://ratatui.rs/built-with-ratatui/badge.svg)](https://ratatui.rs/) 7 | ![WindowsTerminal_XzDicVjS7F-ezgif com-cut](https://github.com/user-attachments/assets/45368b2d-0616-40e4-9eec-5fb33ab9d9b6) 8 | ![ezgif-71158575d9683e](https://github.com/user-attachments/assets/f1fd7a50-4aa0-4c4a-a438-a22dd5b5be23) 9 | 10 | 11 | **YADB** is a directory brute-forcing tool written in **Rust**, inspired by `gobuster`. 12 | 13 | ## ✨ Features 14 | - ⚡ **High performance** with multithreading 15 | - 🖥️ **CLI and TUI interface** 16 | 17 | ## 📦 Installation 18 | ```bash 19 | cargo install yadb 20 | ``` 21 | 22 | ## 🚀 Usage 23 | 24 | ### CLI 25 | 26 | ``` 27 | Usage: yadb-cli [OPTIONS] --wordlist --uri 28 | 29 | Options: 30 | -t, --threads Number of threads [default: 50] 31 | -r, --recursive Recursivly parse directories and files (recursion depth) [default: 0] 32 | -w, --wordlist Path to wordlist 33 | -u, --uri Target URI 34 | -o, --output Output file 35 | -h, --help Print help 36 | -V, --version Print version 37 | ``` 38 | 39 | ### TUI 40 | Just simply: 41 | ``` 42 | yadb-tui 43 | ``` 44 | 45 | ## 🛠️ TODO 46 | 47 | - [x] CLI interface 48 | - [x] Recursion 49 | - [x] TUI interface 50 | - [ ] Output in HTML/JSON formats 51 | - [ ] Better TUI 52 | - [ ] Menu 53 | - [x] Proxy support 54 | - [ ] Custom request 55 | - [ ] Keyword fuzzing 56 | - [ ] In URL 57 | - [ ] In request 58 | 59 | ## 🙌 Contributions 60 | Contributions are welcome! If you have ideas for improvements, bug fixes, or new features, feel free to open an issue or submit a pull request. 61 | 62 | ## 📄 License 63 | 64 | This project is licensed under the **GNU General Public License version 3**. 65 | 66 | ## ⚠️ Disclaimer 67 | 68 | This project is provided for educational and research purposes only — use it responsibly and only on systems you own or have explicit permission to test; the author accepts no liability for any misuse or damage. 69 | -------------------------------------------------------------------------------- /src/lib/tui/widgets/field.rs: -------------------------------------------------------------------------------- 1 | use ratatui::{ 2 | layout::{self, Constraint, Layout, Rect}, 3 | style::{Style, Stylize}, 4 | widgets::{Block, Paragraph, StatefulWidget, Widget}, 5 | }; 6 | use tui_input::Input; 7 | 8 | use crate::lib::tui::widgets::path_hint::{PathHint, PathHintState}; 9 | 10 | #[derive(Debug, Default, PartialEq)] 11 | pub enum FieldType { 12 | #[default] 13 | Normal, 14 | Path(PathHintState), 15 | } 16 | 17 | #[derive(Debug, Default)] 18 | pub struct FieldState { 19 | pub input: Input, 20 | pub is_selected: bool, 21 | pub is_editing: bool, 22 | pub is_only_numbers: bool, 23 | pub field_type: FieldType, 24 | } 25 | 26 | impl FieldState { 27 | pub fn new( 28 | value: &str, 29 | is_selected: bool, 30 | is_only_numbers: bool, 31 | field_type: FieldType, 32 | ) -> Self { 33 | Self { 34 | input: Input::new(value.to_string()), 35 | is_selected, 36 | is_editing: false, 37 | is_only_numbers, 38 | field_type, 39 | } 40 | } 41 | 42 | pub fn get(&self) -> &str { 43 | self.input.value() 44 | } 45 | } 46 | 47 | pub struct Field<'a> { 48 | title: &'a str, 49 | } 50 | 51 | impl StatefulWidget for Field<'_> { 52 | type State = FieldState; 53 | 54 | fn render( 55 | self, 56 | area: ratatui::prelude::Rect, 57 | buf: &mut ratatui::prelude::Buffer, 58 | state: &mut Self::State, 59 | ) { 60 | let layout: [Rect; 1] = 61 | Layout::new(layout::Direction::Vertical, [Constraint::Length(3)]).areas(area); 62 | 63 | let scroll = state.input.visual_scroll(layout[0].width as usize); 64 | let mut input = Paragraph::new(state.input.value()) 65 | .block( 66 | Block::bordered() 67 | .title(self.title) 68 | .border_style(if state.is_editing { 69 | Style::default().red() 70 | } else if state.is_selected { 71 | Style::default().blue() 72 | } else { 73 | Style::default() 74 | }), 75 | ) 76 | .scroll((0, scroll as u16)); 77 | 78 | if state.is_editing { 79 | input = input.italic(); 80 | } 81 | 82 | input.render(layout[0], buf); 83 | 84 | if let FieldType::Path(path_hint) = &mut state.field_type 85 | && state.is_editing 86 | { 87 | let mut box_area = area; 88 | box_area.y += 2; 89 | box_area.x += 1; 90 | PathHint::new().render(box_area, buf, path_hint); 91 | } 92 | } 93 | } 94 | 95 | impl<'a> Field<'a> { 96 | pub fn new(title: &'a str) -> Field<'a> { 97 | Self { title } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/lib/tui/widgets/path_hint.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use ratatui::{ 4 | layout::{self, Constraint, Layout, Rect}, 5 | style::{Style, Stylize}, 6 | text::{Line, Text}, 7 | widgets::{Block, Paragraph, StatefulWidget, Widget}, 8 | }; 9 | 10 | const MAX_VARIANTS: usize = 5; 11 | 12 | #[derive(Debug, PartialEq)] 13 | pub struct PathHintState { 14 | pub possible_paths: Vec, 15 | selected: usize, 16 | } 17 | 18 | impl Default for PathHintState { 19 | fn default() -> Self { 20 | Self { 21 | possible_paths: Vec::with_capacity(MAX_VARIANTS), 22 | selected: 0, 23 | } 24 | } 25 | } 26 | 27 | pub struct PathHint {} 28 | 29 | impl StatefulWidget for PathHint { 30 | type State = PathHintState; 31 | 32 | fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer, state: &mut Self::State) { 33 | if !state.possible_paths.is_empty() { 34 | let layout: [Rect; 1] = Layout::new( 35 | layout::Direction::Vertical, 36 | [Constraint::Length((MAX_VARIANTS).try_into().unwrap())], 37 | ) 38 | .areas(area); 39 | let block = Block::default(); 40 | let lines: Vec> = state 41 | .possible_paths 42 | .iter() 43 | .enumerate() 44 | .map(|(i, s)| { 45 | Line::from(s.as_str()).style(if i == state.selected { 46 | Style::new().blue().reversed() 47 | } else { 48 | Style::new().white() 49 | }) 50 | }) 51 | .collect(); 52 | 53 | Paragraph::new(Text::from_iter(lines)) 54 | .block(block) 55 | .render(layout[0], buf); 56 | } 57 | } 58 | } 59 | 60 | impl PathHint { 61 | pub fn new() -> Self { 62 | Self {} 63 | } 64 | } 65 | 66 | impl PathHintState { 67 | pub fn get_hints(&mut self, current_path: &str) { 68 | self.possible_paths.clear(); 69 | self.selected = 0; 70 | 71 | let path = Path::new(current_path); 72 | if path.is_dir() 73 | && let Ok(read_dir) = path.read_dir() 74 | && current_path.ends_with('/') 75 | { 76 | for entry in read_dir 77 | .filter_map(|e| e.ok()) 78 | .filter_map(|e| e.file_name().into_string().ok()) 79 | .take(MAX_VARIANTS) 80 | { 81 | self.possible_paths.push(entry); 82 | } 83 | return; 84 | } 85 | 86 | if let Some(parent) = path.parent() 87 | && let Ok(read_dir) = parent.read_dir() 88 | { 89 | for entry in read_dir 90 | .filter_map(|e| e.ok()) 91 | .filter_map(|e| e.file_name().into_string().ok()) 92 | .filter(|e| e.starts_with(path.file_name().unwrap().to_str().unwrap())) 93 | .take(MAX_VARIANTS) 94 | { 95 | self.possible_paths.push(entry); 96 | } 97 | } 98 | } 99 | 100 | pub fn get_selected(&mut self) -> Option<&String> { 101 | self.possible_paths.get(self.selected) 102 | } 103 | 104 | pub fn next(&mut self) { 105 | if self.possible_paths.is_empty() { 106 | return; 107 | } 108 | self.selected += 1; 109 | self.selected %= self.possible_paths.len(); 110 | } 111 | 112 | pub fn previous(&mut self) { 113 | if self.possible_paths.is_empty() { 114 | return; 115 | } 116 | if self.selected == 0 { 117 | self.selected = self.possible_paths.len() - 1; 118 | return; 119 | } 120 | self.selected -= 1; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/lib/worker/builder.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | path::PathBuf, 3 | sync::{Arc, mpsc::Sender}, 4 | }; 5 | 6 | use anyhow::Result; 7 | use thiserror::Error; 8 | use url::{ParseError, Url}; 9 | 10 | use crate::lib::worker::{messages::WorkerMessage, unit::Worker}; 11 | 12 | pub const DEFAULT_THREADS_NUMBER: usize = 50; 13 | pub const DEFAULT_RECURSIVE_MODE: usize = 0; 14 | pub const DEFAULT_TIMEOUT: usize = 5; 15 | 16 | #[derive(Error, Debug, Clone)] 17 | pub enum BuilderError { 18 | #[error("Can't parse URL: {0}")] 19 | UrlParseError(#[from] ParseError), 20 | 21 | #[error("Target not specified")] 22 | TargetNotSpecified, 23 | 24 | #[error("Wordlist not specified")] 25 | WordlistNotSpecified, 26 | 27 | #[error("Non-UTF8 file path")] 28 | InvalidFilePath, 29 | 30 | #[error("File not found: {0}")] 31 | FileNotFound(String), 32 | 33 | #[error("Not a file: {0}")] 34 | NotAFile(String), 35 | 36 | #[error("Sender channel not specified")] 37 | SenderChannelNotSpecified, 38 | } 39 | 40 | #[derive(Debug, Default, Clone)] 41 | pub struct WorkerBuilder { 42 | pub threads: Option, 43 | pub recursion: Option, 44 | pub timeout: Option, 45 | pub wordlist: Option, 46 | pub uri: Option, 47 | pub proxy_uri: Option, 48 | error: Option, 49 | message_sender: Option>>, 50 | } 51 | 52 | impl WorkerBuilder { 53 | pub fn threads(mut self, threads: usize) -> Self { 54 | if self.error.is_some() { 55 | return self; 56 | } 57 | 58 | self.threads = Some(threads); 59 | self 60 | } 61 | 62 | pub fn message_sender(mut self, sender: Arc>) -> Self { 63 | self.message_sender = Some(sender); 64 | self 65 | } 66 | 67 | pub fn recursive(mut self, recursive: usize) -> Self { 68 | if self.error.is_some() { 69 | return self; 70 | } 71 | 72 | self.recursion = Some(recursive); 73 | self 74 | } 75 | 76 | pub fn timeout(mut self, timeout: usize) -> Self { 77 | if self.error.is_some() { 78 | return self; 79 | } 80 | 81 | self.timeout = Some(timeout); 82 | self 83 | } 84 | 85 | pub fn wordlist(mut self, wordlist_path: &str) -> Self { 86 | if self.error.is_some() { 87 | return self; 88 | } 89 | 90 | let path: PathBuf = PathBuf::from(wordlist_path); 91 | 92 | if !path.exists() { 93 | self.error = Some(BuilderError::FileNotFound(wordlist_path.to_string())); 94 | return self; 95 | } 96 | 97 | if !path.is_file() { 98 | self.error = Some(BuilderError::NotAFile(wordlist_path.to_string())); 99 | return self; 100 | } 101 | 102 | if path.to_str().is_none() { 103 | self.error = Some(BuilderError::InvalidFilePath); 104 | return self; 105 | } 106 | 107 | self.wordlist = Some(path); 108 | self 109 | } 110 | 111 | pub fn uri(mut self, uri: &str) -> Self { 112 | if self.error.is_some() { 113 | return self; 114 | } 115 | 116 | let parsed_uri = match Url::parse(uri) { 117 | Ok(url) => url, 118 | Err(err) => { 119 | self.error = Some(BuilderError::UrlParseError(err)); 120 | return self; 121 | } 122 | }; 123 | 124 | self.uri = Some(parsed_uri); 125 | 126 | self 127 | } 128 | 129 | pub fn proxy_url(mut self, proxy_uri: &str) -> Self { 130 | if self.error.is_some() || proxy_uri.is_empty() { 131 | return self; 132 | } 133 | 134 | let parsed_uri = match Url::parse(proxy_uri) { 135 | Ok(url) => url, 136 | Err(err) => { 137 | self.error = Some(BuilderError::UrlParseError(err)); 138 | return self; 139 | } 140 | }; 141 | 142 | self.proxy_uri = Some(parsed_uri); 143 | 144 | self 145 | } 146 | 147 | pub fn build(self) -> Result { 148 | if let Some(err) = self.error { 149 | return Err(err); 150 | } 151 | 152 | let uri = self.uri.ok_or(BuilderError::TargetNotSpecified)?; 153 | 154 | let threads = self.threads.unwrap_or(DEFAULT_THREADS_NUMBER); 155 | let recursion_depth = self.recursion.unwrap_or(DEFAULT_RECURSIVE_MODE); 156 | let timeout = self.timeout.unwrap_or(DEFAULT_TIMEOUT); 157 | 158 | let wordlist = self.wordlist.ok_or(BuilderError::WordlistNotSpecified)?; 159 | 160 | let message_sender = self 161 | .message_sender 162 | .ok_or(BuilderError::SenderChannelNotSpecified)?; 163 | 164 | let proxy_uri = self.proxy_uri; 165 | 166 | Ok(Worker::new( 167 | threads, 168 | recursion_depth, 169 | timeout, 170 | wordlist, 171 | uri, 172 | message_sender, 173 | proxy_uri, 174 | )) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/bin/yadb-cli.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Write, 3 | sync::{Mutex, mpsc}, 4 | thread, 5 | }; 6 | 7 | use clap::Parser; 8 | use console::style; 9 | use indicatif::{MultiProgress, ProgressBar, ProgressState, ProgressStyle}; 10 | use yadb::lib::{ 11 | logger::{ 12 | file_logger::FileLogger, 13 | traits::{NullLogger, WorkerLogger}, 14 | }, 15 | util, 16 | worker::{ 17 | builder::WorkerBuilder, 18 | messages::{ProgressChangeMessage, ProgressMessage, WorkerMessage}, 19 | }, 20 | }; 21 | 22 | #[derive(Parser)] 23 | #[command(name = "yadb-cli")] 24 | #[command(version)] 25 | #[command(about = "Yet Another Directory Buster")] 26 | #[command(long_about = None)] 27 | struct Args { 28 | /// Number of threads 29 | #[arg(short, long, default_value_t = 50)] 30 | threads: usize, 31 | 32 | /// Timeout of request in seconds 33 | #[arg(long, default_value_t = 5)] 34 | timeout: usize, 35 | 36 | /// Recursivly parse directories and files (recursion depth) 37 | #[arg(short, long, default_value_t = 0)] 38 | recursion: usize, 39 | 40 | /// Path to wordlist 41 | #[arg(short, long)] 42 | wordlist: String, 43 | 44 | /// Target URL 45 | #[arg(short, long)] 46 | target_url: String, 47 | 48 | /// Proxy URL 49 | #[arg(short, long)] 50 | proxy_url: Option, 51 | 52 | /// Output file 53 | #[arg(short, long)] 54 | output: Option, 55 | } 56 | fn main() { 57 | let args: Args = Args::parse(); 58 | 59 | util::print_logo(); 60 | println!("Threads: {}", style(args.threads.to_string()).cyan()); 61 | println!( 62 | "Recursion depth: {}", 63 | style(args.recursion.to_string()).cyan() 64 | ); 65 | println!( 66 | "Timeout: {} seconds", 67 | style(args.timeout.to_string()).cyan() 68 | ); 69 | println!("Wordlist path: {}", style(args.wordlist.to_string()).cyan()); 70 | println!("Target: {}", style(args.target_url.to_string()).cyan()); 71 | if let Some(proxy_url) = args.proxy_url.as_ref() { 72 | println!("Proxy: {}\n", style(proxy_url.to_string()).cyan()) 73 | } 74 | 75 | if let Some(output) = args.output.as_ref() { 76 | println!("Output: {}\n", style(output.to_string()).cyan()); 77 | } 78 | 79 | let m = MultiProgress::new(); 80 | 81 | let cpb = m.add(ProgressBar::no_length()); 82 | cpb.set_style( 83 | ProgressStyle::with_template("{spinner:.green} {prefix:.bold.dim} {wide_msg}").unwrap(), 84 | ); 85 | 86 | let tpb = m.add(ProgressBar::no_length()); 87 | tpb.set_style( 88 | ProgressStyle::with_template( 89 | "[{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos:>7}/{len:7} ({eta})", 90 | ) 91 | .unwrap() 92 | .with_key("eta", |state: &ProgressState, w: &mut dyn Write| { 93 | write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap() 94 | }) 95 | .progress_chars("#>-"), 96 | ); 97 | 98 | let logger = if let Some(output) = args.output { 99 | match FileLogger::new(output) { 100 | Ok(log) => WorkerLogger::FileLogger(Mutex::new(log)), 101 | Err(err) => { 102 | println!("Error: {err}"); 103 | return; 104 | } 105 | } 106 | } else { 107 | WorkerLogger::NullLogger(NullLogger::default()) 108 | }; 109 | 110 | let (tx, rx) = mpsc::channel::(); 111 | 112 | let mut worker = WorkerBuilder::default() 113 | .recursive(args.recursion) 114 | .threads(args.threads) 115 | .timeout(args.timeout) 116 | .uri(&args.target_url) 117 | .message_sender(tx.into()) 118 | .wordlist(&args.wordlist); 119 | 120 | if let Some(proxy_url) = args.proxy_url.as_ref() { 121 | worker = worker.proxy_url(proxy_url); 122 | } 123 | 124 | let worker = worker.build(); 125 | 126 | match worker { 127 | Ok(buster) => { 128 | thread::spawn(move || buster.run()); 129 | 130 | for msg in rx { 131 | match msg { 132 | WorkerMessage::Progress(progress_message) => match progress_message { 133 | ProgressMessage::Current(progress_change_message) => { 134 | match progress_change_message { 135 | ProgressChangeMessage::SetMessage(str) => cpb.set_message(str), 136 | ProgressChangeMessage::SetSize(size) => { 137 | cpb.set_length(size.try_into().unwrap()) 138 | } 139 | ProgressChangeMessage::Start(size) => { 140 | cpb.reset(); 141 | cpb.set_length(size.try_into().unwrap()); 142 | } 143 | ProgressChangeMessage::Advance => cpb.inc(1), 144 | ProgressChangeMessage::Print(str) => cpb.println(str), 145 | ProgressChangeMessage::Finish => cpb.finish(), 146 | } 147 | } 148 | ProgressMessage::Total(progress_change_message) => { 149 | match progress_change_message { 150 | ProgressChangeMessage::SetMessage(str) => tpb.set_message(str), 151 | ProgressChangeMessage::SetSize(size) => { 152 | tpb.set_length(size.try_into().unwrap()) 153 | } 154 | ProgressChangeMessage::Start(size) => { 155 | tpb.reset(); 156 | tpb.set_length(size.try_into().unwrap()); 157 | } 158 | ProgressChangeMessage::Advance => tpb.inc(1), 159 | ProgressChangeMessage::Print(str) => tpb.println(str), 160 | ProgressChangeMessage::Finish => tpb.finish(), 161 | } 162 | } 163 | }, 164 | WorkerMessage::Log(log_level, str) => { 165 | logger.log(log_level, str); 166 | } 167 | } 168 | } 169 | } 170 | 171 | Err(err) => println!("Error: {err}"), 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/lib/worker/unit.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::io::{BufRead, BufReader}; 3 | use std::sync::Arc; 4 | use std::sync::mpsc::Sender; 5 | use std::thread::{self, ScopedJoinHandle}; 6 | use std::time::Duration; 7 | use std::{fs::File, path::PathBuf}; 8 | use thiserror::Error; 9 | use ureq::{Agent, Proxy}; 10 | use url::Url; 11 | 12 | use crate::lib::logger::traits::LogLevel; 13 | use crate::lib::worker::messages::{ProgressChangeMessage, ProgressMessage, WorkerMessage}; 14 | 15 | #[derive(Error, Debug, Clone)] 16 | pub enum WorkerError { 17 | #[error("Request error: {0}")] 18 | RequestError(String), 19 | } 20 | 21 | #[derive(Debug, Clone)] 22 | pub struct Worker { 23 | threads: usize, 24 | recursion_depth: usize, 25 | wordlist_path: PathBuf, 26 | message_sender: Arc>, 27 | uri: Url, 28 | timeout: usize, 29 | proxy_url: Option, 30 | } 31 | 32 | impl Worker { 33 | pub fn new( 34 | threads: usize, 35 | recursion_depth: usize, 36 | timeout: usize, 37 | wordlist: PathBuf, 38 | uri: Url, 39 | message_sender: Arc>, 40 | proxy_uri: Option, 41 | ) -> Worker { 42 | Worker { 43 | threads, 44 | recursion_depth, 45 | wordlist_path: wordlist, 46 | message_sender, 47 | uri, 48 | timeout, 49 | proxy_url: proxy_uri, 50 | } 51 | } 52 | 53 | pub fn run(&self) -> Result<()> { 54 | let mut urls_vec: Vec = Vec::new(); 55 | urls_vec.push(self.uri.clone()); 56 | let file = File::open(&self.wordlist_path)?; 57 | let lines: Arc> = 58 | Arc::new(BufReader::new(file).lines().map_while(Result::ok).collect()); 59 | let lines_len = lines.len(); 60 | let mut progress_len = lines_len; 61 | let path_len_start = self.uri.path_segments().unwrap().collect::>().len(); 62 | 63 | while let Some(url) = urls_vec.pop() { 64 | if url.path_segments().unwrap().collect::>().len() - path_len_start 65 | > self.recursion_depth 66 | { 67 | continue; 68 | } 69 | 70 | let lines = lines.clone(); 71 | 72 | self.message_sender 73 | .send(WorkerMessage::set_total_size(progress_len)) 74 | .expect("SENDER ERROR"); 75 | 76 | self.message_sender 77 | .send(WorkerMessage::set_current_size(lines_len)) 78 | .expect("SENDER ERROR"); 79 | 80 | let urls_result = self.execute(url, lines)?; 81 | 82 | progress_len += urls_result.len() * lines_len; 83 | urls_vec.extend(urls_result); 84 | } 85 | 86 | self.message_sender 87 | .send(WorkerMessage::finish_total()) 88 | .expect("SENDER ERROR"); 89 | Ok(()) 90 | } 91 | 92 | pub fn execute(&self, url: Url, lines: Arc>) -> Result> { 93 | let slice_size = lines.len() / self.threads; 94 | 95 | let lines_arc = lines.clone(); 96 | 97 | let mut result: Vec = Vec::new(); 98 | 99 | let mut agent = Agent::config_builder() 100 | .timeout_global(Some(Duration::from_secs(self.timeout.try_into().unwrap()))) 101 | .http_status_as_error(false); 102 | 103 | if let Some(proxy_url) = &self.proxy_url { 104 | let proxy = Proxy::new(proxy_url.as_str()).ok(); 105 | agent = agent.proxy(proxy); 106 | } 107 | 108 | let agent: Agent = agent.build().into(); 109 | 110 | let client = Arc::new(agent); 111 | 112 | thread::scope(|s| { 113 | let mut threads: Vec, WorkerError>>> = Vec::new(); 114 | 115 | for thr in 0..self.threads { 116 | let words = lines_arc.clone(); 117 | 118 | let message_sender = self.message_sender.clone(); 119 | 120 | let client_cloned = client.clone(); 121 | let url = url.clone(); 122 | 123 | let threads_num = self.threads; 124 | 125 | threads.push(s.spawn(move || { 126 | let words = words.clone(); 127 | let words_slice = if thr != threads_num - 1 { 128 | &words[slice_size * thr..slice_size * thr + slice_size] 129 | } else { 130 | &words[slice_size * thr..] 131 | }; 132 | 133 | let mut result: Vec = Vec::new(); 134 | 135 | for word in words_slice { 136 | let url = if url.to_string().ends_with("/") { 137 | format!("{url}{word}/") 138 | } else { 139 | format!("{url}/{word}/") 140 | }; 141 | 142 | match client_cloned.get(&url).call() { 143 | Ok(res) => { 144 | let status = res.status().as_u16(); 145 | if status != 404 { 146 | // cpb.println(format!("GET {url} -> {}", style(status).cyan())); 147 | message_sender 148 | .send(WorkerMessage::Progress(ProgressMessage::Current( 149 | ProgressChangeMessage::Print(format!( 150 | "GET {url} -> {status}", 151 | )), 152 | ))) 153 | .expect("SENDER ERROR"); 154 | 155 | // logger.log(LogLevel::INFO, format!("{url} -> {status}")); 156 | message_sender 157 | .send(WorkerMessage::Log( 158 | LogLevel::INFO, 159 | format!("{url} -> {status}"), 160 | )) 161 | .expect("SENDER ERROR"); 162 | 163 | result.push(Url::parse(&url).unwrap()); 164 | } else { 165 | // cpb.set_message(format!("GET {url} -> {}", style(status).red())); 166 | message_sender 167 | .send(WorkerMessage::Progress(ProgressMessage::Current( 168 | ProgressChangeMessage::SetMessage(format!( 169 | "GET {url} -> {status}", 170 | )), 171 | ))) 172 | .expect("SENDER ERROR"); 173 | } 174 | } 175 | Err(e) => { 176 | // cpb.println(format!( 177 | // "Error while sending request to {}: {e}", 178 | // style(&url).red() 179 | // )); 180 | message_sender 181 | .send(WorkerMessage::Log( 182 | LogLevel::WARN, 183 | format!("Error while sending request to {url}: {e}",), 184 | )) 185 | .expect("SENDER ERROR") 186 | } 187 | } 188 | // cpb.advance(); 189 | // tpb.advance(); 190 | 191 | message_sender 192 | .send(WorkerMessage::advance_current()) 193 | .expect("SENDER ERROR"); 194 | 195 | message_sender 196 | .send(WorkerMessage::advance_total()) 197 | .expect("SENDER ERROR"); 198 | } 199 | 200 | Ok(result) 201 | })); 202 | } 203 | 204 | for thread in threads { 205 | match thread.join() { 206 | Ok(Ok(res)) => { 207 | result.extend(res); 208 | } 209 | 210 | Ok(Err(err)) => self 211 | .message_sender 212 | .send(WorkerMessage::log(LogLevel::ERROR, err.to_string())) 213 | .expect("SENDER ERROR"), 214 | Err(err) => self 215 | .message_sender 216 | .send(WorkerMessage::log( 217 | LogLevel::CRITICAL, 218 | format!("Panic in thread: {err:?}"), 219 | )) 220 | .expect("SENDER ERROR"), 221 | } 222 | } 223 | }); 224 | 225 | Ok(result) 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/lib/tui/widgets/worker_info.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use ratatui::{ 4 | layout::{self, Constraint, Flex, Layout, Rect}, 5 | style::{Style, Stylize}, 6 | text::{Line, Text}, 7 | widgets::{Block, Gauge, Paragraph, StatefulWidget, Widget}, 8 | }; 9 | 10 | use crate::lib::{ 11 | tui::{ 12 | app::{LOG_MAX, MESSAGES_MAX}, 13 | widgets::{ 14 | field::{Field, FieldState, FieldType}, 15 | path_hint::PathHintState, 16 | }, 17 | }, 18 | worker::builder::{DEFAULT_RECURSIVE_MODE, DEFAULT_THREADS_NUMBER, DEFAULT_TIMEOUT}, 19 | }; 20 | 21 | #[derive(Debug, Default, Clone)] 22 | pub enum WorkerVariant { 23 | Worker(bool), 24 | #[default] 25 | Builder, 26 | } 27 | 28 | #[derive(Debug, Default, Clone, Copy, PartialEq)] 29 | pub enum FieldName { 30 | #[default] 31 | Name = 0, 32 | Uri = 1, 33 | Threads = 2, 34 | Recursion = 3, 35 | Timeout = 4, 36 | WordlistPath = 5, 37 | ProxyUrl = 6, 38 | } 39 | 40 | impl FieldName { 41 | pub fn index(self) -> usize { 42 | match self { 43 | FieldName::Name => 0, 44 | FieldName::Uri => 1, 45 | FieldName::Threads => 2, 46 | FieldName::Recursion => 3, 47 | FieldName::Timeout => 4, 48 | FieldName::WordlistPath => 5, 49 | FieldName::ProxyUrl => 6, 50 | } 51 | } 52 | 53 | pub fn next(self) -> FieldName { 54 | match self { 55 | FieldName::Name => FieldName::Uri, 56 | FieldName::Uri => FieldName::Threads, 57 | FieldName::Threads => FieldName::Recursion, 58 | FieldName::Recursion => FieldName::Timeout, 59 | FieldName::Timeout => FieldName::WordlistPath, 60 | FieldName::WordlistPath => FieldName::ProxyUrl, 61 | FieldName::ProxyUrl => FieldName::Name, 62 | } 63 | } 64 | 65 | pub fn previous(self) -> FieldName { 66 | match self { 67 | FieldName::Name => FieldName::ProxyUrl, 68 | FieldName::Uri => FieldName::Name, 69 | FieldName::Threads => FieldName::Uri, 70 | FieldName::Recursion => FieldName::Threads, 71 | FieldName::Timeout => FieldName::Recursion, 72 | FieldName::WordlistPath => FieldName::Timeout, 73 | FieldName::ProxyUrl => FieldName::WordlistPath, 74 | } 75 | } 76 | 77 | pub fn is_first(self) -> bool { 78 | self == FieldName::Name 79 | } 80 | 81 | pub fn is_last(self) -> bool { 82 | self == FieldName::ProxyUrl 83 | } 84 | } 85 | 86 | const FIELDS_NUMBER: usize = 7; 87 | 88 | const NAMES: [&str; FIELDS_NUMBER] = [ 89 | " Name ", 90 | " URI ", 91 | " Threads ", 92 | " Recursion depth ", 93 | " Max timeout ", 94 | " Wordlist path ", 95 | " Proxy URL ", 96 | ]; 97 | 98 | #[derive(Debug, PartialEq)] 99 | pub enum Selection { 100 | Field(FieldName), 101 | RunButton, 102 | } 103 | 104 | impl Default for Selection { 105 | fn default() -> Self { 106 | Selection::Field(FieldName::default()) 107 | } 108 | } 109 | 110 | impl Selection { 111 | fn set_next(&mut self) { 112 | match self { 113 | Selection::Field(field) => { 114 | if field.is_last() { 115 | *self = Selection::RunButton; 116 | return; 117 | }; 118 | *self = Selection::Field(field.next()); 119 | } 120 | Selection::RunButton => *self = Selection::Field(FieldName::Name), 121 | } 122 | } 123 | 124 | fn set_previous(&mut self) { 125 | match self { 126 | Selection::Field(field) => { 127 | if field.is_first() { 128 | *self = Selection::RunButton; 129 | return; 130 | } 131 | *self = Selection::Field(field.previous()); 132 | } 133 | Selection::RunButton => *self = Selection::Field(FieldName::WordlistPath), 134 | } 135 | } 136 | } 137 | 138 | #[derive(Debug)] 139 | pub struct WorkerState { 140 | pub worker: WorkerVariant, 141 | pub selection: Selection, 142 | pub current_parsing: String, 143 | pub log: VecDeque, 144 | pub messages: VecDeque, 145 | pub progress_current_total: usize, 146 | pub progress_current_now: usize, 147 | pub progress_all_total: usize, 148 | pub progress_all_now: usize, 149 | pub do_build: bool, 150 | pub fields_states: [FieldState; FIELDS_NUMBER], 151 | cursor_position: (u16, u16), 152 | } 153 | 154 | impl Default for WorkerState { 155 | fn default() -> Self { 156 | Self { 157 | worker: Default::default(), 158 | cursor_position: Default::default(), 159 | selection: Default::default(), 160 | current_parsing: Default::default(), 161 | log: Default::default(), 162 | messages: Default::default(), 163 | do_build: Default::default(), 164 | progress_current_total: Default::default(), 165 | progress_current_now: Default::default(), 166 | progress_all_total: Default::default(), 167 | progress_all_now: Default::default(), 168 | fields_states: [ 169 | FieldState::new("Unnamed", true, false, FieldType::Normal), 170 | FieldState::new("http://localhost", false, false, FieldType::Normal), 171 | FieldState::new( 172 | DEFAULT_THREADS_NUMBER.to_string().as_str(), 173 | false, 174 | true, 175 | FieldType::Normal, 176 | ), 177 | FieldState::new( 178 | DEFAULT_RECURSIVE_MODE.to_string().as_str(), 179 | false, 180 | true, 181 | FieldType::Normal, 182 | ), 183 | FieldState::new( 184 | DEFAULT_TIMEOUT.to_string().as_str(), 185 | false, 186 | true, 187 | FieldType::Normal, 188 | ), 189 | FieldState::new( 190 | "/usr/share", 191 | false, 192 | false, 193 | FieldType::Path(PathHintState::default()), 194 | ), 195 | FieldState::new("", false, false, FieldType::Normal), 196 | ], 197 | } 198 | } 199 | } 200 | 201 | impl WorkerState { 202 | pub fn set_next_selection(&mut self) { 203 | if let Selection::Field(f) = self.selection { 204 | self.fields_states[f.index()].is_selected = false; 205 | }; 206 | self.selection.set_next(); 207 | if let Selection::Field(f) = self.selection { 208 | self.fields_states[f.index()].is_selected = true; 209 | }; 210 | } 211 | 212 | pub fn set_previous_selection(&mut self) { 213 | if let Selection::Field(f) = self.selection { 214 | self.fields_states[f.index()].is_selected = false; 215 | } 216 | self.selection.set_previous(); 217 | if let Selection::Field(f) = self.selection { 218 | self.fields_states[f.index()].is_selected = true; 219 | } 220 | } 221 | 222 | pub fn switch_field_editing(&mut self, field: FieldName) { 223 | let ind = field.index(); 224 | self.fields_states[ind].is_editing = !self.fields_states[ind].is_editing; 225 | } 226 | 227 | pub fn get_cursor_position(&self) -> (u16, u16) { 228 | self.cursor_position 229 | } 230 | } 231 | 232 | #[derive(Debug, Default)] 233 | pub struct WorkerInfo {} 234 | 235 | impl StatefulWidget for WorkerInfo { 236 | type State = WorkerState; 237 | 238 | fn render( 239 | self, 240 | area: ratatui::prelude::Rect, 241 | buf: &mut ratatui::prelude::Buffer, 242 | state: &mut Self::State, 243 | ) { 244 | match &state.worker { 245 | WorkerVariant::Worker(_) => { 246 | let layout: [Rect; 5] = Layout::new( 247 | layout::Direction::Vertical, 248 | [ 249 | Constraint::Length((LOG_MAX + 2).try_into().unwrap()), 250 | Constraint::Min((MESSAGES_MAX + 2).try_into().unwrap()), 251 | Constraint::Length(3), 252 | Constraint::Length(3), 253 | Constraint::Length(3), 254 | ], 255 | ) 256 | .areas(area); 257 | 258 | let args_and_log_layout: [Rect; 2] = Layout::new( 259 | layout::Direction::Horizontal, 260 | [Constraint::Percentage(30), Constraint::Percentage(70)], 261 | ) 262 | .areas(layout[0]); 263 | 264 | let names: [&str; 4] = [ 265 | " Logs ", 266 | " Results ", 267 | " Currently requesting ", 268 | " Arguments ", 269 | ]; 270 | 271 | Paragraph::new(Text::from_iter::<[Line; 5]>([ 272 | Line::from("URI: ") + state.fields_states[FieldName::Uri.index()].get().blue(), 273 | Line::from("Threads: ") 274 | + state.fields_states[FieldName::Threads.index()].get().blue(), 275 | Line::from("Recursion depth: ") 276 | + state.fields_states[FieldName::Recursion.index()] 277 | .get() 278 | .blue(), 279 | Line::from("Timeout: ") 280 | + state.fields_states[FieldName::Timeout.index()].get().blue(), 281 | Line::from("Wordlist: ") 282 | + state.fields_states[FieldName::WordlistPath.index()] 283 | .get() 284 | .blue(), 285 | ])) 286 | .block(Block::bordered().title(names[3])) 287 | .render(args_and_log_layout[0], buf); 288 | 289 | let log_lines = state.log.iter().map(|s| Line::from(s.as_str())); 290 | let message_lines = state.messages.iter().map(|s| Line::from(s.as_str())); 291 | 292 | Paragraph::new(Text::from_iter(log_lines)) 293 | .block(Block::bordered().title(names[0])) 294 | .render(args_and_log_layout[1], buf); 295 | 296 | Paragraph::new(Text::from_iter(message_lines)) 297 | .block(Block::bordered().title(names[1])) 298 | .render(layout[1], buf); 299 | 300 | Paragraph::new(Line::from(state.current_parsing.as_str())) 301 | .block(Block::bordered().title(names[2])) 302 | .render(layout[2], buf); 303 | 304 | if !state.fields_states[FieldName::Recursion.index()] 305 | .get() 306 | .starts_with('0') 307 | { 308 | Gauge::default() 309 | .block(Block::bordered().title(" Current recursion progress ")) 310 | .gauge_style(Style::new().white().on_black().italic()) 311 | .ratio(checked_ratio( 312 | state.progress_current_now, 313 | state.progress_current_total, 314 | )) 315 | .render(layout[3], buf); 316 | } 317 | 318 | Gauge::default() 319 | .block(Block::bordered().title(" Total progress ")) 320 | .gauge_style(Style::new().blue().on_black().italic()) 321 | .ratio(checked_ratio( 322 | state.progress_all_now, 323 | state.progress_all_total, 324 | )) 325 | .render(layout[4], buf); 326 | } 327 | WorkerVariant::Builder => { 328 | let constraints: [Constraint; FIELDS_NUMBER + 1] = std::array::from_fn(|i| { 329 | if i == FieldName::WordlistPath.index() && state.fields_states[i].is_editing { 330 | return Constraint::Length(7); 331 | } 332 | Constraint::Length(3) 333 | }); 334 | 335 | let layout: [Rect; FIELDS_NUMBER + 1] = 336 | Layout::new(layout::Direction::Vertical, constraints).areas(area); 337 | 338 | Paragraph::new("Run") 339 | .centered() 340 | .block( 341 | Block::bordered().style(if state.selection == Selection::RunButton { 342 | Style::default().green() 343 | } else { 344 | Style::default() 345 | }), 346 | ) 347 | .alignment(layout::Alignment::Center) 348 | .render( 349 | Self::center( 350 | layout[FIELDS_NUMBER], 351 | Constraint::Max(40), 352 | Constraint::Length(3), 353 | ), 354 | buf, 355 | ); 356 | 357 | for (ind, field_state) in state.fields_states.iter_mut().enumerate() { 358 | if field_state.is_editing { 359 | state.cursor_position = ( 360 | layout[ind].x + 1 + field_state.input.cursor() as u16, 361 | layout[ind].y + 1, 362 | ); 363 | } 364 | Field::new(NAMES[ind]).render(layout[ind], buf, field_state); 365 | } 366 | } 367 | } 368 | } 369 | } 370 | 371 | impl WorkerInfo { 372 | fn center(area: Rect, horizontal: Constraint, vertical: Constraint) -> Rect { 373 | let [area] = Layout::horizontal([horizontal]) 374 | .flex(Flex::Center) 375 | .areas(area); 376 | let [area] = Layout::vertical([vertical]).flex(Flex::Center).areas(area); 377 | area 378 | } 379 | } 380 | 381 | fn checked_ratio(a: usize, b: usize) -> f64 { 382 | let res = a as f64 / b as f64; 383 | if (0.0..=1.0).contains(&res) { 384 | return res; 385 | } 386 | 0.0 387 | } 388 | -------------------------------------------------------------------------------- /src/lib/tui/app.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::Result; 2 | use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; 3 | use ratatui::{ 4 | DefaultTerminal, Frame, 5 | layout::{Constraint, Direction, Layout}, 6 | style::{Style, Stylize}, 7 | text::{Line, Text}, 8 | widgets::{Block, BorderType, Borders, List, ListItem, ListState}, 9 | }; 10 | use std::{ 11 | sync::mpsc::{self, Receiver}, 12 | thread::{self}, 13 | time::Duration, 14 | }; 15 | use tui_input::InputRequest; 16 | 17 | use crate::lib::{ 18 | tui::widgets::{ 19 | field::FieldType, 20 | popup::Popup, 21 | worker_info::{FieldName, Selection, WorkerInfo, WorkerState, WorkerVariant}, 22 | }, 23 | worker::{ 24 | builder::{BuilderError, WorkerBuilder}, 25 | messages::{ProgressMessage, WorkerMessage}, 26 | }, 27 | }; 28 | 29 | pub const LOG_MAX: usize = 5; 30 | pub const MESSAGES_MAX: usize = 20; 31 | 32 | #[derive(Debug, Default, PartialEq)] 33 | enum CurrentWindow { 34 | #[default] 35 | Workers, 36 | Info, 37 | } 38 | 39 | #[derive(Debug)] 40 | enum WorkerType { 41 | Worker, 42 | Builder(Box), 43 | } 44 | 45 | #[derive(Debug)] 46 | struct WorkerRx { 47 | worker_type: WorkerType, 48 | rx: Receiver, 49 | } 50 | 51 | impl Default for WorkerRx { 52 | fn default() -> Self { 53 | let (tx, rx) = mpsc::channel::(); 54 | 55 | Self { 56 | worker_type: WorkerType::Builder(Box::new( 57 | WorkerBuilder::default().message_sender(tx.into()), 58 | )), 59 | rx, 60 | } 61 | } 62 | } 63 | 64 | #[derive(Debug, Default, PartialEq)] 65 | enum InputMode { 66 | #[default] 67 | Normal, 68 | Editing, 69 | } 70 | 71 | /// The main application which holds the state and logic of the application. 72 | #[derive(Debug, Default)] 73 | pub struct App { 74 | // Is the application running? 75 | running: bool, 76 | 77 | // Logic state 78 | current_window: CurrentWindow, 79 | workers_info_state: Vec, 80 | workers: Vec, 81 | show_help_popup: bool, 82 | worker_list_state: ListState, 83 | builder_error: Option, 84 | input_mode: InputMode, 85 | } 86 | 87 | impl App { 88 | /// Construct a new instance of [`App`]. 89 | pub fn new() -> Self { 90 | Self::default() 91 | } 92 | 93 | /// Run the application's main loop. 94 | pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { 95 | self.running = true; 96 | while self.running { 97 | self.handle_crossterm_events()?; 98 | terminal.draw(|frame| self.render(frame))?; 99 | 100 | for (sel, worker_state) in self.workers.iter_mut().enumerate() { 101 | if let Ok(msg) = worker_state.rx.try_recv() { 102 | match msg { 103 | WorkerMessage::Progress(progress_message) => { 104 | match progress_message { 105 | ProgressMessage::Total(progress_change_message) => { 106 | match progress_change_message { 107 | crate::lib::worker::messages::ProgressChangeMessage::SetMessage(_) => {}, 108 | crate::lib::worker::messages::ProgressChangeMessage::SetSize(size) => { 109 | self.workers_info_state[sel].progress_all_total = size; 110 | }, 111 | crate::lib::worker::messages::ProgressChangeMessage::Start(_) => {}, 112 | crate::lib::worker::messages::ProgressChangeMessage::Advance => { 113 | self.workers_info_state[sel].progress_all_now += 1; 114 | }, 115 | crate::lib::worker::messages::ProgressChangeMessage::Print(_) => {}, 116 | crate::lib::worker::messages::ProgressChangeMessage::Finish => { 117 | self.workers_info_state[sel].current_parsing = "Done!".to_string(); 118 | self.workers_info_state[sel].worker = WorkerVariant::Worker(true); 119 | }, 120 | } 121 | }, 122 | ProgressMessage::Current(progress_change_message) => { 123 | match progress_change_message { 124 | crate::lib::worker::messages::ProgressChangeMessage::SetMessage(str) => { 125 | self.workers_info_state[sel].current_parsing = str; 126 | }, 127 | crate::lib::worker::messages::ProgressChangeMessage::SetSize(size) => { 128 | self.workers_info_state[sel].progress_current_now = 0; 129 | self.workers_info_state[sel].progress_current_total = size; 130 | }, 131 | crate::lib::worker::messages::ProgressChangeMessage::Start(_) => {}, 132 | crate::lib::worker::messages::ProgressChangeMessage::Advance => { 133 | self.workers_info_state[sel].progress_current_now += 1; 134 | }, 135 | crate::lib::worker::messages::ProgressChangeMessage::Print(msg) => { 136 | let messages = &mut self.workers_info_state[sel].messages; 137 | messages.push_back(msg); 138 | if messages.len() > MESSAGES_MAX { 139 | messages.pop_front(); 140 | } 141 | }, 142 | crate::lib::worker::messages::ProgressChangeMessage::Finish => {}, 143 | } 144 | }, 145 | } 146 | }, 147 | WorkerMessage::Log(loglevel, str) => { 148 | let log = &mut self.workers_info_state[sel].log; 149 | match loglevel { 150 | crate::lib::logger::traits::LogLevel::WARN => log.push_front("[WARN] ".to_owned() + &str), 151 | crate::lib::logger::traits::LogLevel::ERROR => log.push_front("[ERROR] ".to_owned() + &str), 152 | crate::lib::logger::traits::LogLevel::CRITICAL => log.push_front("[CRITICAL]".to_owned() + &str), 153 | _ => {}, 154 | } 155 | if log.len() > LOG_MAX { 156 | log.pop_front(); 157 | } 158 | }, 159 | } 160 | } 161 | } 162 | } 163 | Ok(()) 164 | } 165 | 166 | /// Renders the user interface. 167 | fn render(&mut self, frame: &mut Frame) { 168 | let layout = Layout::default() 169 | .direction(Direction::Horizontal) 170 | .constraints([Constraint::Max(30), Constraint::Min(0)].as_ref()) 171 | .split(frame.area()); 172 | 173 | let rect_list = layout[0]; 174 | let rect_info = layout[1]; 175 | 176 | let workers_title = Line::from(" Workers ").centered(); 177 | 178 | let info_title = Line::from(" Info "); 179 | 180 | let mut block_list = Block::default() 181 | .border_type(BorderType::Rounded) 182 | .borders(Borders::ALL) 183 | .title(workers_title); 184 | 185 | let mut block_info = Block::default() 186 | .border_type(BorderType::Rounded) 187 | .borders(Borders::ALL) 188 | .title(info_title); 189 | 190 | let help_line = Line::from(vec![" Help - ".into(), " ".bold()]).centered(); 191 | 192 | match self.current_window { 193 | CurrentWindow::Workers => { 194 | block_list = block_list.border_style(Style::new().blue()); 195 | block_list = block_list.title_bottom(help_line); 196 | } 197 | CurrentWindow::Info => { 198 | block_info = block_info.border_style(Style::new().blue()); 199 | block_info = block_info.title_bottom(help_line); 200 | } 201 | } 202 | 203 | let block_list_inner = block_list.inner(rect_list); 204 | let block_info_inner = block_info.inner(rect_info); 205 | 206 | frame.render_widget(block_list, rect_list); 207 | frame.render_widget(block_info, rect_info); 208 | 209 | let workers_name_list = self 210 | .workers_info_state 211 | .iter() 212 | .enumerate() 213 | .map(|(i, w)| { 214 | let name = w.fields_states[0].get(); 215 | let formated_name = match self.workers_info_state[i].worker { 216 | WorkerVariant::Worker(s) if !s => format!(" {name}"), 217 | WorkerVariant::Worker(s) if s => format!(" {name}"), 218 | WorkerVariant::Builder => format!(" {name}"), 219 | _ => String::default(), 220 | }; 221 | let mut item = ListItem::new(formated_name); 222 | if let Some(selected_index) = self.worker_list_state.selected() 223 | && selected_index == i 224 | { 225 | item = item.reversed().blue(); 226 | } 227 | item 228 | }) 229 | .collect::>(); 230 | let workers_list = List::new(workers_name_list); 231 | frame.render_stateful_widget(workers_list, block_list_inner, &mut self.worker_list_state); 232 | 233 | if let Some(sel) = self.worker_list_state.selected() { 234 | let worker_info = WorkerInfo {}; 235 | let state = &mut self.workers_info_state[sel]; 236 | frame.render_stateful_widget(worker_info, block_info_inner, state); 237 | 238 | if self.input_mode == InputMode::Editing { 239 | frame.set_cursor_position(state.get_cursor_position()); 240 | } 241 | } 242 | 243 | if self.show_help_popup { 244 | self.render_help_popup(frame); 245 | } 246 | 247 | if let Some(err) = &self.builder_error { 248 | self.render_error_popup(frame, err.clone()); 249 | } 250 | } 251 | 252 | /// Reads the crossterm events and updates the state of [`App`]. 253 | fn handle_crossterm_events(&mut self) -> Result<()> { 254 | if event::poll(Duration::from_millis(40))? { 255 | match event::read()? { 256 | // it's important to check KeyEventKind::Press to avoid handling key release events 257 | Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_event(key), 258 | Event::Mouse(_) => {} 259 | Event::Resize(_, _) => {} 260 | _ => {} 261 | } 262 | } 263 | Ok(()) 264 | } 265 | 266 | /// Handles the key events and updates the state of [`App`]. 267 | fn on_key_event(&mut self, key: KeyEvent) { 268 | if (key.modifiers, key.code) == (KeyModifiers::CONTROL, KeyCode::Char('c')) { 269 | self.quit(); 270 | return; 271 | }; 272 | 273 | match self.input_mode { 274 | InputMode::Normal => self.handle_normal_input(key), 275 | InputMode::Editing => self.handle_editing_input(key), 276 | } 277 | } 278 | 279 | fn handle_normal_input(&mut self, key: KeyEvent) { 280 | match self.current_window { 281 | CurrentWindow::Workers => self.handle_workers_list_keys(key), 282 | CurrentWindow::Info => self.handle_worker_info_keys(key), 283 | } 284 | } 285 | 286 | fn handle_workers_list_keys(&mut self, key: KeyEvent) { 287 | match (key.modifiers, key.code) { 288 | (_, KeyCode::Char('a')) => { 289 | self.workers_info_state.push(WorkerState::default()); 290 | self.workers.push(WorkerRx::default()); 291 | if self.worker_list_state.selected().is_none() { 292 | self.worker_list_state.select(Some(0)); 293 | } 294 | } 295 | (_, KeyCode::Down) => { 296 | if self.workers_info_state.is_empty() { 297 | return; 298 | } 299 | if self.worker_list_state.selected() == Some(self.workers_info_state.len() - 1) { 300 | self.worker_list_state.select_first(); 301 | return; 302 | } 303 | self.worker_list_state.select_next(); 304 | } 305 | (_, KeyCode::Up) => { 306 | if self.workers_info_state.is_empty() { 307 | return; 308 | } 309 | if self.worker_list_state.selected() == Some(0) { 310 | self.worker_list_state.select_last(); 311 | return; 312 | } 313 | self.worker_list_state.select_previous(); 314 | } 315 | (_, KeyCode::Char('d')) | (_, KeyCode::Delete) => { 316 | if let Some(sel) = self.worker_list_state.selected() { 317 | self.workers_info_state.remove(sel); 318 | self.workers.remove(sel); 319 | } 320 | } 321 | (_, KeyCode::Char('h')) => { 322 | self.show_help_popup = !self.show_help_popup; 323 | } 324 | (_, KeyCode::Right | KeyCode::Enter | KeyCode::Tab) => { 325 | if !self.workers_info_state.is_empty() { 326 | self.switch_window() 327 | } 328 | } 329 | _ => {} 330 | } 331 | } 332 | 333 | fn handle_worker_info_keys(&mut self, key: KeyEvent) { 334 | if let Some(sel) = self.worker_list_state.selected() { 335 | let worker_state = &mut self.workers_info_state[sel]; 336 | match (key.modifiers, key.code) { 337 | (_, KeyCode::Char('h')) => { 338 | self.show_help_popup = !self.show_help_popup; 339 | } 340 | (_, KeyCode::Tab | KeyCode::Left) => self.switch_window(), 341 | (_, KeyCode::Down) => worker_state.set_next_selection(), 342 | (_, KeyCode::Up) => worker_state.set_previous_selection(), 343 | (_, KeyCode::Enter) => { 344 | if self.builder_error.is_some() || self.show_help_popup { 345 | self.close_all_popups(); 346 | return; 347 | }; 348 | 349 | match worker_state.selection { 350 | Selection::Field(field) => { 351 | worker_state.switch_field_editing(field); 352 | self.switch_input_mode(); 353 | } 354 | Selection::RunButton => { 355 | worker_state.do_build = true; 356 | } 357 | } 358 | } 359 | _ => {} 360 | }; 361 | 362 | if self.workers_info_state[sel].do_build 363 | && let WorkerType::Builder(builder) = &mut self.workers[sel].worker_type 364 | { 365 | let builder_clone = builder 366 | .clone() 367 | .recursive( 368 | self.workers_info_state[sel].fields_states[FieldName::Recursion.index()] 369 | .get() 370 | .parse() 371 | .unwrap(), 372 | ) 373 | .threads( 374 | self.workers_info_state[sel].fields_states[FieldName::Threads.index()] 375 | .get() 376 | .parse() 377 | .unwrap(), 378 | ) 379 | .timeout( 380 | self.workers_info_state[sel].fields_states[FieldName::Timeout.index()] 381 | .get() 382 | .parse() 383 | .unwrap(), 384 | ) 385 | .uri(self.workers_info_state[sel].fields_states[FieldName::Uri.index()].get()) 386 | .wordlist( 387 | self.workers_info_state[sel].fields_states[FieldName::WordlistPath.index()] 388 | .get(), 389 | ) 390 | .proxy_url( 391 | self.workers_info_state[sel].fields_states[FieldName::ProxyUrl.index()] 392 | .get(), 393 | ); 394 | 395 | let worker_result = builder_clone.build(); 396 | match worker_result { 397 | Ok(worker) => { 398 | self.workers[sel].worker_type = WorkerType::Worker; 399 | thread::spawn(move || worker.run()); 400 | self.workers_info_state[sel].worker = WorkerVariant::Worker(false); 401 | } 402 | Err(err) => { 403 | self.builder_error = Some(err.clone()); 404 | self.workers_info_state[sel].do_build = false; 405 | } 406 | } 407 | } 408 | } 409 | } 410 | fn handle_editing_input(&mut self, key: KeyEvent) { 411 | match self.current_window { 412 | CurrentWindow::Workers => todo!(), 413 | CurrentWindow::Info => { 414 | if let Some(sel) = self.worker_list_state.selected() { 415 | let state = &mut self.workers_info_state[sel]; 416 | if let Selection::Field(f) = state.selection { 417 | let field_state = &mut state.fields_states[f.index()]; 418 | match (key.modifiers, key.code) { 419 | (_, KeyCode::Char(c)) => { 420 | if field_state.is_only_numbers { 421 | if c.is_ascii_digit() && !field_state.get().starts_with('0') { 422 | field_state.input.handle(InputRequest::InsertChar(c)); 423 | } 424 | } else { 425 | field_state.input.handle(InputRequest::InsertChar(c)); 426 | if let FieldType::Path(hint_state) = &mut field_state.field_type 427 | { 428 | hint_state.get_hints(field_state.input.value()); 429 | } 430 | } 431 | } 432 | (KeyModifiers::CONTROL, KeyCode::Right) => { 433 | field_state.input.handle(InputRequest::GoToEnd); 434 | } 435 | (KeyModifiers::CONTROL, KeyCode::Left) => { 436 | field_state.input.handle(InputRequest::GoToStart); 437 | } 438 | (KeyModifiers::ALT, KeyCode::Backspace) => { 439 | field_state.input.handle(InputRequest::DeletePrevWord); 440 | } 441 | (_, KeyCode::Tab) => { 442 | if let FieldType::Path(hint_state) = &mut field_state.field_type { 443 | if !field_state.input.value().ends_with('/') 444 | && !hint_state.possible_paths.is_empty() 445 | { 446 | field_state.input.handle(InputRequest::DeletePrevWord); 447 | } 448 | if let Some(str) = hint_state.get_selected() { 449 | for ch in str.chars() { 450 | field_state.input.handle(InputRequest::InsertChar(ch)); 451 | } 452 | } 453 | hint_state.possible_paths.clear(); 454 | } 455 | } 456 | (_, KeyCode::Down) => { 457 | if let FieldType::Path(hint_state) = &mut field_state.field_type { 458 | hint_state.next(); 459 | } 460 | } 461 | (_, KeyCode::Up) => { 462 | if let FieldType::Path(hint_state) = &mut field_state.field_type { 463 | hint_state.previous(); 464 | } 465 | } 466 | (_, KeyCode::Backspace) => { 467 | if let FieldType::Path(hint_state) = &mut field_state.field_type { 468 | hint_state.get_hints(field_state.input.value()); 469 | } 470 | field_state.input.handle(InputRequest::DeletePrevChar); 471 | } 472 | (_, KeyCode::Delete) => { 473 | field_state.input.handle(InputRequest::DeleteNextChar); 474 | } 475 | (_, KeyCode::Left) => { 476 | field_state.input.handle(InputRequest::GoToPrevChar); 477 | } 478 | (_, KeyCode::Right) => { 479 | field_state.input.handle(InputRequest::GoToNextChar); 480 | } 481 | (_, KeyCode::Esc | KeyCode::Enter) => { 482 | state.switch_field_editing(f); 483 | self.switch_input_mode(); 484 | } 485 | _ => {} 486 | }; 487 | }; 488 | } 489 | } 490 | }; 491 | } 492 | 493 | fn switch_window(&mut self) { 494 | match self.current_window { 495 | CurrentWindow::Workers => self.current_window = CurrentWindow::Info, 496 | CurrentWindow::Info => self.current_window = CurrentWindow::Workers, 497 | } 498 | } 499 | 500 | fn render_help_popup(&mut self, frame: &mut Frame) { 501 | let help_message = match self.current_window { 502 | CurrentWindow::Workers => Text::from(vec![ 503 | " / / ".bold().blue() + " - Switch Tabs".into(), 504 | "".bold().blue() + " - Add Worker".into(), 505 | "".bold().blue() + " - Delete Worker".into(), 506 | "".bold().blue() + " - Start/Stop worker".into(), 507 | ]), 508 | CurrentWindow::Info => Text::from(vec![ 509 | " / / ".bold().blue() + " - Switch tabs".into(), 510 | " / ".bold().blue() + " - Move focus".into(), 511 | " ".bold().blue() + " - Edit property or press button".into(), 512 | ]), 513 | }; 514 | let popup = Popup::new(" Help ".to_string(), help_message); 515 | frame.render_widget(popup, frame.area()); 516 | } 517 | 518 | fn render_error_popup(&mut self, frame: &mut Frame, err: BuilderError) { 519 | let error_message = Text::from(err.to_string()); 520 | let popup = Popup::new(" Error ".to_string(), error_message); 521 | 522 | frame.render_widget(popup, frame.area()); 523 | } 524 | 525 | fn switch_input_mode(&mut self) { 526 | match self.input_mode { 527 | InputMode::Normal => self.input_mode = InputMode::Editing, 528 | InputMode::Editing => self.input_mode = InputMode::Normal, 529 | } 530 | } 531 | 532 | fn close_all_popups(&mut self) { 533 | self.builder_error = None; 534 | self.show_help_popup = false; 535 | } 536 | 537 | /// Set running to false to quit the application. 538 | fn quit(&mut self) { 539 | self.running = false; 540 | } 541 | } 542 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.25.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.1" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 19 | 20 | [[package]] 21 | name = "allocator-api2" 22 | version = "0.2.21" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 25 | 26 | [[package]] 27 | name = "android_system_properties" 28 | version = "0.1.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 31 | dependencies = [ 32 | "libc", 33 | ] 34 | 35 | [[package]] 36 | name = "anstream" 37 | version = "0.6.21" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" 40 | dependencies = [ 41 | "anstyle", 42 | "anstyle-parse", 43 | "anstyle-query", 44 | "anstyle-wincon", 45 | "colorchoice", 46 | "is_terminal_polyfill", 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle" 52 | version = "1.0.13" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" 55 | 56 | [[package]] 57 | name = "anstyle-parse" 58 | version = "0.2.7" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" 61 | dependencies = [ 62 | "utf8parse", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-query" 67 | version = "1.1.4" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" 70 | dependencies = [ 71 | "windows-sys 0.60.2", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-wincon" 76 | version = "3.0.10" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" 79 | dependencies = [ 80 | "anstyle", 81 | "once_cell_polyfill", 82 | "windows-sys 0.60.2", 83 | ] 84 | 85 | [[package]] 86 | name = "anyhow" 87 | version = "1.0.100" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 90 | 91 | [[package]] 92 | name = "autocfg" 93 | version = "1.5.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 96 | 97 | [[package]] 98 | name = "backtrace" 99 | version = "0.3.76" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" 102 | dependencies = [ 103 | "addr2line", 104 | "cfg-if", 105 | "libc", 106 | "miniz_oxide", 107 | "object", 108 | "rustc-demangle", 109 | "windows-link", 110 | ] 111 | 112 | [[package]] 113 | name = "base64" 114 | version = "0.22.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 117 | 118 | [[package]] 119 | name = "bitflags" 120 | version = "2.10.0" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 123 | 124 | [[package]] 125 | name = "bumpalo" 126 | version = "3.19.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 129 | 130 | [[package]] 131 | name = "bytes" 132 | version = "1.10.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 135 | 136 | [[package]] 137 | name = "cassowary" 138 | version = "0.3.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" 141 | 142 | [[package]] 143 | name = "castaway" 144 | version = "0.2.4" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" 147 | dependencies = [ 148 | "rustversion", 149 | ] 150 | 151 | [[package]] 152 | name = "cc" 153 | version = "1.2.44" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" 156 | dependencies = [ 157 | "find-msvc-tools", 158 | "shlex", 159 | ] 160 | 161 | [[package]] 162 | name = "cfg-if" 163 | version = "1.0.4" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 166 | 167 | [[package]] 168 | name = "chrono" 169 | version = "0.4.42" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 172 | dependencies = [ 173 | "iana-time-zone", 174 | "js-sys", 175 | "num-traits", 176 | "wasm-bindgen", 177 | "windows-link", 178 | ] 179 | 180 | [[package]] 181 | name = "clap" 182 | version = "4.5.51" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" 185 | dependencies = [ 186 | "clap_builder", 187 | "clap_derive", 188 | ] 189 | 190 | [[package]] 191 | name = "clap_builder" 192 | version = "4.5.51" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" 195 | dependencies = [ 196 | "anstream", 197 | "anstyle", 198 | "clap_lex", 199 | "strsim", 200 | ] 201 | 202 | [[package]] 203 | name = "clap_derive" 204 | version = "4.5.49" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" 207 | dependencies = [ 208 | "heck", 209 | "proc-macro2", 210 | "quote", 211 | "syn", 212 | ] 213 | 214 | [[package]] 215 | name = "clap_lex" 216 | version = "0.7.6" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" 219 | 220 | [[package]] 221 | name = "color-eyre" 222 | version = "0.6.5" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" 225 | dependencies = [ 226 | "backtrace", 227 | "color-spantrace", 228 | "eyre", 229 | "indenter", 230 | "once_cell", 231 | "owo-colors", 232 | "tracing-error", 233 | ] 234 | 235 | [[package]] 236 | name = "color-spantrace" 237 | version = "0.3.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" 240 | dependencies = [ 241 | "once_cell", 242 | "owo-colors", 243 | "tracing-core", 244 | "tracing-error", 245 | ] 246 | 247 | [[package]] 248 | name = "colorchoice" 249 | version = "1.0.4" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" 252 | 253 | [[package]] 254 | name = "compact_str" 255 | version = "0.8.1" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" 258 | dependencies = [ 259 | "castaway", 260 | "cfg-if", 261 | "itoa", 262 | "rustversion", 263 | "ryu", 264 | "static_assertions", 265 | ] 266 | 267 | [[package]] 268 | name = "console" 269 | version = "0.15.11" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 272 | dependencies = [ 273 | "encode_unicode", 274 | "libc", 275 | "once_cell", 276 | "unicode-width 0.2.0", 277 | "windows-sys 0.59.0", 278 | ] 279 | 280 | [[package]] 281 | name = "convert_case" 282 | version = "0.7.1" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" 285 | dependencies = [ 286 | "unicode-segmentation", 287 | ] 288 | 289 | [[package]] 290 | name = "core-foundation-sys" 291 | version = "0.8.7" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 294 | 295 | [[package]] 296 | name = "crc32fast" 297 | version = "1.5.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 300 | dependencies = [ 301 | "cfg-if", 302 | ] 303 | 304 | [[package]] 305 | name = "crossterm" 306 | version = "0.28.1" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" 309 | dependencies = [ 310 | "bitflags", 311 | "crossterm_winapi", 312 | "mio", 313 | "parking_lot", 314 | "rustix 0.38.44", 315 | "signal-hook", 316 | "signal-hook-mio", 317 | "winapi", 318 | ] 319 | 320 | [[package]] 321 | name = "crossterm" 322 | version = "0.29.0" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" 325 | dependencies = [ 326 | "bitflags", 327 | "crossterm_winapi", 328 | "derive_more", 329 | "document-features", 330 | "mio", 331 | "parking_lot", 332 | "rustix 1.1.2", 333 | "signal-hook", 334 | "signal-hook-mio", 335 | "winapi", 336 | ] 337 | 338 | [[package]] 339 | name = "crossterm_winapi" 340 | version = "0.9.1" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 343 | dependencies = [ 344 | "winapi", 345 | ] 346 | 347 | [[package]] 348 | name = "darling" 349 | version = "0.20.11" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 352 | dependencies = [ 353 | "darling_core", 354 | "darling_macro", 355 | ] 356 | 357 | [[package]] 358 | name = "darling_core" 359 | version = "0.20.11" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 362 | dependencies = [ 363 | "fnv", 364 | "ident_case", 365 | "proc-macro2", 366 | "quote", 367 | "strsim", 368 | "syn", 369 | ] 370 | 371 | [[package]] 372 | name = "darling_macro" 373 | version = "0.20.11" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 376 | dependencies = [ 377 | "darling_core", 378 | "quote", 379 | "syn", 380 | ] 381 | 382 | [[package]] 383 | name = "derive_more" 384 | version = "2.0.1" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" 387 | dependencies = [ 388 | "derive_more-impl", 389 | ] 390 | 391 | [[package]] 392 | name = "derive_more-impl" 393 | version = "2.0.1" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" 396 | dependencies = [ 397 | "convert_case", 398 | "proc-macro2", 399 | "quote", 400 | "syn", 401 | ] 402 | 403 | [[package]] 404 | name = "displaydoc" 405 | version = "0.2.5" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 408 | dependencies = [ 409 | "proc-macro2", 410 | "quote", 411 | "syn", 412 | ] 413 | 414 | [[package]] 415 | name = "document-features" 416 | version = "0.2.12" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" 419 | dependencies = [ 420 | "litrs", 421 | ] 422 | 423 | [[package]] 424 | name = "either" 425 | version = "1.15.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 428 | 429 | [[package]] 430 | name = "encode_unicode" 431 | version = "1.0.0" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 434 | 435 | [[package]] 436 | name = "equivalent" 437 | version = "1.0.2" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 440 | 441 | [[package]] 442 | name = "errno" 443 | version = "0.3.14" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 446 | dependencies = [ 447 | "libc", 448 | "windows-sys 0.61.2", 449 | ] 450 | 451 | [[package]] 452 | name = "eyre" 453 | version = "0.6.12" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 456 | dependencies = [ 457 | "indenter", 458 | "once_cell", 459 | ] 460 | 461 | [[package]] 462 | name = "find-msvc-tools" 463 | version = "0.1.4" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" 466 | 467 | [[package]] 468 | name = "flate2" 469 | version = "1.1.5" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" 472 | dependencies = [ 473 | "crc32fast", 474 | "miniz_oxide", 475 | ] 476 | 477 | [[package]] 478 | name = "fnv" 479 | version = "1.0.7" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 482 | 483 | [[package]] 484 | name = "foldhash" 485 | version = "0.1.5" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 488 | 489 | [[package]] 490 | name = "form_urlencoded" 491 | version = "1.2.2" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 494 | dependencies = [ 495 | "percent-encoding", 496 | ] 497 | 498 | [[package]] 499 | name = "getrandom" 500 | version = "0.2.16" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 503 | dependencies = [ 504 | "cfg-if", 505 | "libc", 506 | "wasi", 507 | ] 508 | 509 | [[package]] 510 | name = "gimli" 511 | version = "0.32.3" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" 514 | 515 | [[package]] 516 | name = "hashbrown" 517 | version = "0.15.5" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 520 | dependencies = [ 521 | "allocator-api2", 522 | "equivalent", 523 | "foldhash", 524 | ] 525 | 526 | [[package]] 527 | name = "heck" 528 | version = "0.5.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 531 | 532 | [[package]] 533 | name = "http" 534 | version = "1.3.1" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 537 | dependencies = [ 538 | "bytes", 539 | "fnv", 540 | "itoa", 541 | ] 542 | 543 | [[package]] 544 | name = "httparse" 545 | version = "1.10.1" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 548 | 549 | [[package]] 550 | name = "iana-time-zone" 551 | version = "0.1.64" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 554 | dependencies = [ 555 | "android_system_properties", 556 | "core-foundation-sys", 557 | "iana-time-zone-haiku", 558 | "js-sys", 559 | "log", 560 | "wasm-bindgen", 561 | "windows-core", 562 | ] 563 | 564 | [[package]] 565 | name = "iana-time-zone-haiku" 566 | version = "0.1.2" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 569 | dependencies = [ 570 | "cc", 571 | ] 572 | 573 | [[package]] 574 | name = "icu_collections" 575 | version = "2.1.1" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" 578 | dependencies = [ 579 | "displaydoc", 580 | "potential_utf", 581 | "yoke", 582 | "zerofrom", 583 | "zerovec", 584 | ] 585 | 586 | [[package]] 587 | name = "icu_locale_core" 588 | version = "2.1.1" 589 | source = "registry+https://github.com/rust-lang/crates.io-index" 590 | checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" 591 | dependencies = [ 592 | "displaydoc", 593 | "litemap", 594 | "tinystr", 595 | "writeable", 596 | "zerovec", 597 | ] 598 | 599 | [[package]] 600 | name = "icu_normalizer" 601 | version = "2.1.1" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" 604 | dependencies = [ 605 | "icu_collections", 606 | "icu_normalizer_data", 607 | "icu_properties", 608 | "icu_provider", 609 | "smallvec", 610 | "zerovec", 611 | ] 612 | 613 | [[package]] 614 | name = "icu_normalizer_data" 615 | version = "2.1.1" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" 618 | 619 | [[package]] 620 | name = "icu_properties" 621 | version = "2.1.1" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" 624 | dependencies = [ 625 | "icu_collections", 626 | "icu_locale_core", 627 | "icu_properties_data", 628 | "icu_provider", 629 | "zerotrie", 630 | "zerovec", 631 | ] 632 | 633 | [[package]] 634 | name = "icu_properties_data" 635 | version = "2.1.1" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" 638 | 639 | [[package]] 640 | name = "icu_provider" 641 | version = "2.1.1" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" 644 | dependencies = [ 645 | "displaydoc", 646 | "icu_locale_core", 647 | "writeable", 648 | "yoke", 649 | "zerofrom", 650 | "zerotrie", 651 | "zerovec", 652 | ] 653 | 654 | [[package]] 655 | name = "ident_case" 656 | version = "1.0.1" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 659 | 660 | [[package]] 661 | name = "idna" 662 | version = "1.1.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 665 | dependencies = [ 666 | "idna_adapter", 667 | "smallvec", 668 | "utf8_iter", 669 | ] 670 | 671 | [[package]] 672 | name = "idna_adapter" 673 | version = "1.2.1" 674 | source = "registry+https://github.com/rust-lang/crates.io-index" 675 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 676 | dependencies = [ 677 | "icu_normalizer", 678 | "icu_properties", 679 | ] 680 | 681 | [[package]] 682 | name = "indenter" 683 | version = "0.3.4" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" 686 | 687 | [[package]] 688 | name = "indicatif" 689 | version = "0.17.11" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" 692 | dependencies = [ 693 | "console", 694 | "number_prefix", 695 | "portable-atomic", 696 | "unicode-width 0.2.0", 697 | "web-time", 698 | ] 699 | 700 | [[package]] 701 | name = "indoc" 702 | version = "2.0.7" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" 705 | dependencies = [ 706 | "rustversion", 707 | ] 708 | 709 | [[package]] 710 | name = "instability" 711 | version = "0.3.9" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" 714 | dependencies = [ 715 | "darling", 716 | "indoc", 717 | "proc-macro2", 718 | "quote", 719 | "syn", 720 | ] 721 | 722 | [[package]] 723 | name = "is_terminal_polyfill" 724 | version = "1.70.2" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" 727 | 728 | [[package]] 729 | name = "itertools" 730 | version = "0.13.0" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 733 | dependencies = [ 734 | "either", 735 | ] 736 | 737 | [[package]] 738 | name = "itoa" 739 | version = "1.0.15" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 742 | 743 | [[package]] 744 | name = "js-sys" 745 | version = "0.3.82" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" 748 | dependencies = [ 749 | "once_cell", 750 | "wasm-bindgen", 751 | ] 752 | 753 | [[package]] 754 | name = "lazy_static" 755 | version = "1.5.0" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 758 | 759 | [[package]] 760 | name = "libc" 761 | version = "0.2.177" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 764 | 765 | [[package]] 766 | name = "linux-raw-sys" 767 | version = "0.4.15" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 770 | 771 | [[package]] 772 | name = "linux-raw-sys" 773 | version = "0.11.0" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 776 | 777 | [[package]] 778 | name = "litemap" 779 | version = "0.8.1" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 782 | 783 | [[package]] 784 | name = "litrs" 785 | version = "1.0.0" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" 788 | 789 | [[package]] 790 | name = "lock_api" 791 | version = "0.4.14" 792 | source = "registry+https://github.com/rust-lang/crates.io-index" 793 | checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 794 | dependencies = [ 795 | "scopeguard", 796 | ] 797 | 798 | [[package]] 799 | name = "log" 800 | version = "0.4.28" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 803 | 804 | [[package]] 805 | name = "lru" 806 | version = "0.12.5" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 809 | dependencies = [ 810 | "hashbrown", 811 | ] 812 | 813 | [[package]] 814 | name = "memchr" 815 | version = "2.7.6" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 818 | 819 | [[package]] 820 | name = "miniz_oxide" 821 | version = "0.8.9" 822 | source = "registry+https://github.com/rust-lang/crates.io-index" 823 | checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 824 | dependencies = [ 825 | "adler2", 826 | "simd-adler32", 827 | ] 828 | 829 | [[package]] 830 | name = "mio" 831 | version = "1.1.0" 832 | source = "registry+https://github.com/rust-lang/crates.io-index" 833 | checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" 834 | dependencies = [ 835 | "libc", 836 | "log", 837 | "wasi", 838 | "windows-sys 0.61.2", 839 | ] 840 | 841 | [[package]] 842 | name = "num-traits" 843 | version = "0.2.19" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 846 | dependencies = [ 847 | "autocfg", 848 | ] 849 | 850 | [[package]] 851 | name = "number_prefix" 852 | version = "0.4.0" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 855 | 856 | [[package]] 857 | name = "object" 858 | version = "0.37.3" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" 861 | dependencies = [ 862 | "memchr", 863 | ] 864 | 865 | [[package]] 866 | name = "once_cell" 867 | version = "1.21.3" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 870 | 871 | [[package]] 872 | name = "once_cell_polyfill" 873 | version = "1.70.2" 874 | source = "registry+https://github.com/rust-lang/crates.io-index" 875 | checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" 876 | 877 | [[package]] 878 | name = "owo-colors" 879 | version = "4.2.3" 880 | source = "registry+https://github.com/rust-lang/crates.io-index" 881 | checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" 882 | 883 | [[package]] 884 | name = "parking_lot" 885 | version = "0.12.5" 886 | source = "registry+https://github.com/rust-lang/crates.io-index" 887 | checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 888 | dependencies = [ 889 | "lock_api", 890 | "parking_lot_core", 891 | ] 892 | 893 | [[package]] 894 | name = "parking_lot_core" 895 | version = "0.9.12" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 898 | dependencies = [ 899 | "cfg-if", 900 | "libc", 901 | "redox_syscall", 902 | "smallvec", 903 | "windows-link", 904 | ] 905 | 906 | [[package]] 907 | name = "paste" 908 | version = "1.0.15" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 911 | 912 | [[package]] 913 | name = "percent-encoding" 914 | version = "2.3.2" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 917 | 918 | [[package]] 919 | name = "pin-project-lite" 920 | version = "0.2.16" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 923 | 924 | [[package]] 925 | name = "portable-atomic" 926 | version = "1.11.1" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" 929 | 930 | [[package]] 931 | name = "potential_utf" 932 | version = "0.1.4" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" 935 | dependencies = [ 936 | "zerovec", 937 | ] 938 | 939 | [[package]] 940 | name = "proc-macro2" 941 | version = "1.0.103" 942 | source = "registry+https://github.com/rust-lang/crates.io-index" 943 | checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 944 | dependencies = [ 945 | "unicode-ident", 946 | ] 947 | 948 | [[package]] 949 | name = "quote" 950 | version = "1.0.41" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" 953 | dependencies = [ 954 | "proc-macro2", 955 | ] 956 | 957 | [[package]] 958 | name = "ratatui" 959 | version = "0.29.0" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" 962 | dependencies = [ 963 | "bitflags", 964 | "cassowary", 965 | "compact_str", 966 | "crossterm 0.28.1", 967 | "indoc", 968 | "instability", 969 | "itertools", 970 | "lru", 971 | "paste", 972 | "strum", 973 | "unicode-segmentation", 974 | "unicode-truncate", 975 | "unicode-width 0.2.0", 976 | ] 977 | 978 | [[package]] 979 | name = "redox_syscall" 980 | version = "0.5.18" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 983 | dependencies = [ 984 | "bitflags", 985 | ] 986 | 987 | [[package]] 988 | name = "ring" 989 | version = "0.17.14" 990 | source = "registry+https://github.com/rust-lang/crates.io-index" 991 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 992 | dependencies = [ 993 | "cc", 994 | "cfg-if", 995 | "getrandom", 996 | "libc", 997 | "untrusted", 998 | "windows-sys 0.52.0", 999 | ] 1000 | 1001 | [[package]] 1002 | name = "rustc-demangle" 1003 | version = "0.1.26" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 1006 | 1007 | [[package]] 1008 | name = "rustix" 1009 | version = "0.38.44" 1010 | source = "registry+https://github.com/rust-lang/crates.io-index" 1011 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1012 | dependencies = [ 1013 | "bitflags", 1014 | "errno", 1015 | "libc", 1016 | "linux-raw-sys 0.4.15", 1017 | "windows-sys 0.59.0", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "rustix" 1022 | version = "1.1.2" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 1025 | dependencies = [ 1026 | "bitflags", 1027 | "errno", 1028 | "libc", 1029 | "linux-raw-sys 0.11.0", 1030 | "windows-sys 0.61.2", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "rustls" 1035 | version = "0.23.34" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" 1038 | dependencies = [ 1039 | "log", 1040 | "once_cell", 1041 | "ring", 1042 | "rustls-pki-types", 1043 | "rustls-webpki", 1044 | "subtle", 1045 | "zeroize", 1046 | ] 1047 | 1048 | [[package]] 1049 | name = "rustls-pemfile" 1050 | version = "2.2.0" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 1053 | dependencies = [ 1054 | "rustls-pki-types", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "rustls-pki-types" 1059 | version = "1.13.0" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" 1062 | dependencies = [ 1063 | "zeroize", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "rustls-webpki" 1068 | version = "0.103.8" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" 1071 | dependencies = [ 1072 | "ring", 1073 | "rustls-pki-types", 1074 | "untrusted", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "rustversion" 1079 | version = "1.0.22" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 1082 | 1083 | [[package]] 1084 | name = "ryu" 1085 | version = "1.0.20" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 1088 | 1089 | [[package]] 1090 | name = "scopeguard" 1091 | version = "1.2.0" 1092 | source = "registry+https://github.com/rust-lang/crates.io-index" 1093 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1094 | 1095 | [[package]] 1096 | name = "serde" 1097 | version = "1.0.228" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 1100 | dependencies = [ 1101 | "serde_core", 1102 | "serde_derive", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "serde_core" 1107 | version = "1.0.228" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 1110 | dependencies = [ 1111 | "serde_derive", 1112 | ] 1113 | 1114 | [[package]] 1115 | name = "serde_derive" 1116 | version = "1.0.228" 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" 1118 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 1119 | dependencies = [ 1120 | "proc-macro2", 1121 | "quote", 1122 | "syn", 1123 | ] 1124 | 1125 | [[package]] 1126 | name = "sharded-slab" 1127 | version = "0.1.7" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 1130 | dependencies = [ 1131 | "lazy_static", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "shlex" 1136 | version = "1.3.0" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1139 | 1140 | [[package]] 1141 | name = "signal-hook" 1142 | version = "0.3.18" 1143 | source = "registry+https://github.com/rust-lang/crates.io-index" 1144 | checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" 1145 | dependencies = [ 1146 | "libc", 1147 | "signal-hook-registry", 1148 | ] 1149 | 1150 | [[package]] 1151 | name = "signal-hook-mio" 1152 | version = "0.2.5" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" 1155 | dependencies = [ 1156 | "libc", 1157 | "mio", 1158 | "signal-hook", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "signal-hook-registry" 1163 | version = "1.4.6" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" 1166 | dependencies = [ 1167 | "libc", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "simd-adler32" 1172 | version = "0.3.7" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 1175 | 1176 | [[package]] 1177 | name = "smallvec" 1178 | version = "1.15.1" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1181 | 1182 | [[package]] 1183 | name = "stable_deref_trait" 1184 | version = "1.2.1" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 1187 | 1188 | [[package]] 1189 | name = "static_assertions" 1190 | version = "1.1.0" 1191 | source = "registry+https://github.com/rust-lang/crates.io-index" 1192 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1193 | 1194 | [[package]] 1195 | name = "strsim" 1196 | version = "0.11.1" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1199 | 1200 | [[package]] 1201 | name = "strum" 1202 | version = "0.26.3" 1203 | source = "registry+https://github.com/rust-lang/crates.io-index" 1204 | checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" 1205 | dependencies = [ 1206 | "strum_macros", 1207 | ] 1208 | 1209 | [[package]] 1210 | name = "strum_macros" 1211 | version = "0.26.4" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" 1214 | dependencies = [ 1215 | "heck", 1216 | "proc-macro2", 1217 | "quote", 1218 | "rustversion", 1219 | "syn", 1220 | ] 1221 | 1222 | [[package]] 1223 | name = "subtle" 1224 | version = "2.6.1" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1227 | 1228 | [[package]] 1229 | name = "syn" 1230 | version = "2.0.108" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" 1233 | dependencies = [ 1234 | "proc-macro2", 1235 | "quote", 1236 | "unicode-ident", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "synstructure" 1241 | version = "0.13.2" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1244 | dependencies = [ 1245 | "proc-macro2", 1246 | "quote", 1247 | "syn", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "thiserror" 1252 | version = "2.0.17" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 1255 | dependencies = [ 1256 | "thiserror-impl", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "thiserror-impl" 1261 | version = "2.0.17" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 1264 | dependencies = [ 1265 | "proc-macro2", 1266 | "quote", 1267 | "syn", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "thread_local" 1272 | version = "1.1.9" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 1275 | dependencies = [ 1276 | "cfg-if", 1277 | ] 1278 | 1279 | [[package]] 1280 | name = "tinystr" 1281 | version = "0.8.2" 1282 | source = "registry+https://github.com/rust-lang/crates.io-index" 1283 | checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" 1284 | dependencies = [ 1285 | "displaydoc", 1286 | "zerovec", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "tracing" 1291 | version = "0.1.41" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1294 | dependencies = [ 1295 | "pin-project-lite", 1296 | "tracing-core", 1297 | ] 1298 | 1299 | [[package]] 1300 | name = "tracing-core" 1301 | version = "0.1.34" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 1304 | dependencies = [ 1305 | "once_cell", 1306 | "valuable", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "tracing-error" 1311 | version = "0.2.1" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" 1314 | dependencies = [ 1315 | "tracing", 1316 | "tracing-subscriber", 1317 | ] 1318 | 1319 | [[package]] 1320 | name = "tracing-subscriber" 1321 | version = "0.3.20" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" 1324 | dependencies = [ 1325 | "sharded-slab", 1326 | "thread_local", 1327 | "tracing-core", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "tui-input" 1332 | version = "0.14.0" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "911e93158bf80bbc94bad533b2b16e3d711e1132d69a6a6980c3920a63422c19" 1335 | dependencies = [ 1336 | "ratatui", 1337 | "unicode-width 0.2.0", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "unicode-ident" 1342 | version = "1.0.22" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 1345 | 1346 | [[package]] 1347 | name = "unicode-segmentation" 1348 | version = "1.12.0" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1351 | 1352 | [[package]] 1353 | name = "unicode-truncate" 1354 | version = "1.1.0" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" 1357 | dependencies = [ 1358 | "itertools", 1359 | "unicode-segmentation", 1360 | "unicode-width 0.1.14", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "unicode-width" 1365 | version = "0.1.14" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1368 | 1369 | [[package]] 1370 | name = "unicode-width" 1371 | version = "0.2.0" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1374 | 1375 | [[package]] 1376 | name = "untrusted" 1377 | version = "0.9.0" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1380 | 1381 | [[package]] 1382 | name = "ureq" 1383 | version = "3.1.2" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "99ba1025f18a4a3fc3e9b48c868e9beb4f24f4b4b1a325bada26bd4119f46537" 1386 | dependencies = [ 1387 | "base64", 1388 | "flate2", 1389 | "log", 1390 | "percent-encoding", 1391 | "rustls", 1392 | "rustls-pemfile", 1393 | "rustls-pki-types", 1394 | "ureq-proto", 1395 | "utf-8", 1396 | "webpki-roots", 1397 | ] 1398 | 1399 | [[package]] 1400 | name = "ureq-proto" 1401 | version = "0.5.2" 1402 | source = "registry+https://github.com/rust-lang/crates.io-index" 1403 | checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2" 1404 | dependencies = [ 1405 | "base64", 1406 | "http", 1407 | "httparse", 1408 | "log", 1409 | ] 1410 | 1411 | [[package]] 1412 | name = "url" 1413 | version = "2.5.7" 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" 1415 | checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 1416 | dependencies = [ 1417 | "form_urlencoded", 1418 | "idna", 1419 | "percent-encoding", 1420 | "serde", 1421 | ] 1422 | 1423 | [[package]] 1424 | name = "utf-8" 1425 | version = "0.7.6" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1428 | 1429 | [[package]] 1430 | name = "utf8_iter" 1431 | version = "1.0.4" 1432 | source = "registry+https://github.com/rust-lang/crates.io-index" 1433 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1434 | 1435 | [[package]] 1436 | name = "utf8parse" 1437 | version = "0.2.2" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1440 | 1441 | [[package]] 1442 | name = "valuable" 1443 | version = "0.1.1" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 1446 | 1447 | [[package]] 1448 | name = "wasi" 1449 | version = "0.11.1+wasi-snapshot-preview1" 1450 | source = "registry+https://github.com/rust-lang/crates.io-index" 1451 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 1452 | 1453 | [[package]] 1454 | name = "wasm-bindgen" 1455 | version = "0.2.105" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" 1458 | dependencies = [ 1459 | "cfg-if", 1460 | "once_cell", 1461 | "rustversion", 1462 | "wasm-bindgen-macro", 1463 | "wasm-bindgen-shared", 1464 | ] 1465 | 1466 | [[package]] 1467 | name = "wasm-bindgen-macro" 1468 | version = "0.2.105" 1469 | source = "registry+https://github.com/rust-lang/crates.io-index" 1470 | checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" 1471 | dependencies = [ 1472 | "quote", 1473 | "wasm-bindgen-macro-support", 1474 | ] 1475 | 1476 | [[package]] 1477 | name = "wasm-bindgen-macro-support" 1478 | version = "0.2.105" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" 1481 | dependencies = [ 1482 | "bumpalo", 1483 | "proc-macro2", 1484 | "quote", 1485 | "syn", 1486 | "wasm-bindgen-shared", 1487 | ] 1488 | 1489 | [[package]] 1490 | name = "wasm-bindgen-shared" 1491 | version = "0.2.105" 1492 | source = "registry+https://github.com/rust-lang/crates.io-index" 1493 | checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" 1494 | dependencies = [ 1495 | "unicode-ident", 1496 | ] 1497 | 1498 | [[package]] 1499 | name = "web-time" 1500 | version = "1.1.0" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 1503 | dependencies = [ 1504 | "js-sys", 1505 | "wasm-bindgen", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "webpki-roots" 1510 | version = "1.0.4" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" 1513 | dependencies = [ 1514 | "rustls-pki-types", 1515 | ] 1516 | 1517 | [[package]] 1518 | name = "winapi" 1519 | version = "0.3.9" 1520 | source = "registry+https://github.com/rust-lang/crates.io-index" 1521 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1522 | dependencies = [ 1523 | "winapi-i686-pc-windows-gnu", 1524 | "winapi-x86_64-pc-windows-gnu", 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "winapi-i686-pc-windows-gnu" 1529 | version = "0.4.0" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1532 | 1533 | [[package]] 1534 | name = "winapi-x86_64-pc-windows-gnu" 1535 | version = "0.4.0" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1538 | 1539 | [[package]] 1540 | name = "windows-core" 1541 | version = "0.62.2" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 1544 | dependencies = [ 1545 | "windows-implement", 1546 | "windows-interface", 1547 | "windows-link", 1548 | "windows-result", 1549 | "windows-strings", 1550 | ] 1551 | 1552 | [[package]] 1553 | name = "windows-implement" 1554 | version = "0.60.2" 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" 1556 | checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 1557 | dependencies = [ 1558 | "proc-macro2", 1559 | "quote", 1560 | "syn", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "windows-interface" 1565 | version = "0.59.3" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 1568 | dependencies = [ 1569 | "proc-macro2", 1570 | "quote", 1571 | "syn", 1572 | ] 1573 | 1574 | [[package]] 1575 | name = "windows-link" 1576 | version = "0.2.1" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 1579 | 1580 | [[package]] 1581 | name = "windows-result" 1582 | version = "0.4.1" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 1585 | dependencies = [ 1586 | "windows-link", 1587 | ] 1588 | 1589 | [[package]] 1590 | name = "windows-strings" 1591 | version = "0.5.1" 1592 | source = "registry+https://github.com/rust-lang/crates.io-index" 1593 | checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 1594 | dependencies = [ 1595 | "windows-link", 1596 | ] 1597 | 1598 | [[package]] 1599 | name = "windows-sys" 1600 | version = "0.52.0" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1603 | dependencies = [ 1604 | "windows-targets 0.52.6", 1605 | ] 1606 | 1607 | [[package]] 1608 | name = "windows-sys" 1609 | version = "0.59.0" 1610 | source = "registry+https://github.com/rust-lang/crates.io-index" 1611 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1612 | dependencies = [ 1613 | "windows-targets 0.52.6", 1614 | ] 1615 | 1616 | [[package]] 1617 | name = "windows-sys" 1618 | version = "0.60.2" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 1621 | dependencies = [ 1622 | "windows-targets 0.53.5", 1623 | ] 1624 | 1625 | [[package]] 1626 | name = "windows-sys" 1627 | version = "0.61.2" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 1630 | dependencies = [ 1631 | "windows-link", 1632 | ] 1633 | 1634 | [[package]] 1635 | name = "windows-targets" 1636 | version = "0.52.6" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1639 | dependencies = [ 1640 | "windows_aarch64_gnullvm 0.52.6", 1641 | "windows_aarch64_msvc 0.52.6", 1642 | "windows_i686_gnu 0.52.6", 1643 | "windows_i686_gnullvm 0.52.6", 1644 | "windows_i686_msvc 0.52.6", 1645 | "windows_x86_64_gnu 0.52.6", 1646 | "windows_x86_64_gnullvm 0.52.6", 1647 | "windows_x86_64_msvc 0.52.6", 1648 | ] 1649 | 1650 | [[package]] 1651 | name = "windows-targets" 1652 | version = "0.53.5" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 1655 | dependencies = [ 1656 | "windows-link", 1657 | "windows_aarch64_gnullvm 0.53.1", 1658 | "windows_aarch64_msvc 0.53.1", 1659 | "windows_i686_gnu 0.53.1", 1660 | "windows_i686_gnullvm 0.53.1", 1661 | "windows_i686_msvc 0.53.1", 1662 | "windows_x86_64_gnu 0.53.1", 1663 | "windows_x86_64_gnullvm 0.53.1", 1664 | "windows_x86_64_msvc 0.53.1", 1665 | ] 1666 | 1667 | [[package]] 1668 | name = "windows_aarch64_gnullvm" 1669 | version = "0.52.6" 1670 | source = "registry+https://github.com/rust-lang/crates.io-index" 1671 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1672 | 1673 | [[package]] 1674 | name = "windows_aarch64_gnullvm" 1675 | version = "0.53.1" 1676 | source = "registry+https://github.com/rust-lang/crates.io-index" 1677 | checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 1678 | 1679 | [[package]] 1680 | name = "windows_aarch64_msvc" 1681 | version = "0.52.6" 1682 | source = "registry+https://github.com/rust-lang/crates.io-index" 1683 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1684 | 1685 | [[package]] 1686 | name = "windows_aarch64_msvc" 1687 | version = "0.53.1" 1688 | source = "registry+https://github.com/rust-lang/crates.io-index" 1689 | checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 1690 | 1691 | [[package]] 1692 | name = "windows_i686_gnu" 1693 | version = "0.52.6" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1696 | 1697 | [[package]] 1698 | name = "windows_i686_gnu" 1699 | version = "0.53.1" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 1702 | 1703 | [[package]] 1704 | name = "windows_i686_gnullvm" 1705 | version = "0.52.6" 1706 | source = "registry+https://github.com/rust-lang/crates.io-index" 1707 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1708 | 1709 | [[package]] 1710 | name = "windows_i686_gnullvm" 1711 | version = "0.53.1" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 1714 | 1715 | [[package]] 1716 | name = "windows_i686_msvc" 1717 | version = "0.52.6" 1718 | source = "registry+https://github.com/rust-lang/crates.io-index" 1719 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1720 | 1721 | [[package]] 1722 | name = "windows_i686_msvc" 1723 | version = "0.53.1" 1724 | source = "registry+https://github.com/rust-lang/crates.io-index" 1725 | checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 1726 | 1727 | [[package]] 1728 | name = "windows_x86_64_gnu" 1729 | version = "0.52.6" 1730 | source = "registry+https://github.com/rust-lang/crates.io-index" 1731 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1732 | 1733 | [[package]] 1734 | name = "windows_x86_64_gnu" 1735 | version = "0.53.1" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 1738 | 1739 | [[package]] 1740 | name = "windows_x86_64_gnullvm" 1741 | version = "0.52.6" 1742 | source = "registry+https://github.com/rust-lang/crates.io-index" 1743 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1744 | 1745 | [[package]] 1746 | name = "windows_x86_64_gnullvm" 1747 | version = "0.53.1" 1748 | source = "registry+https://github.com/rust-lang/crates.io-index" 1749 | checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 1750 | 1751 | [[package]] 1752 | name = "windows_x86_64_msvc" 1753 | version = "0.52.6" 1754 | source = "registry+https://github.com/rust-lang/crates.io-index" 1755 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1756 | 1757 | [[package]] 1758 | name = "windows_x86_64_msvc" 1759 | version = "0.53.1" 1760 | source = "registry+https://github.com/rust-lang/crates.io-index" 1761 | checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 1762 | 1763 | [[package]] 1764 | name = "writeable" 1765 | version = "0.6.2" 1766 | source = "registry+https://github.com/rust-lang/crates.io-index" 1767 | checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" 1768 | 1769 | [[package]] 1770 | name = "yadb" 1771 | version = "0.3.5" 1772 | dependencies = [ 1773 | "anyhow", 1774 | "chrono", 1775 | "clap", 1776 | "color-eyre", 1777 | "console", 1778 | "crossterm 0.29.0", 1779 | "indicatif", 1780 | "ratatui", 1781 | "thiserror", 1782 | "tui-input", 1783 | "ureq", 1784 | "url", 1785 | ] 1786 | 1787 | [[package]] 1788 | name = "yoke" 1789 | version = "0.8.1" 1790 | source = "registry+https://github.com/rust-lang/crates.io-index" 1791 | checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" 1792 | dependencies = [ 1793 | "stable_deref_trait", 1794 | "yoke-derive", 1795 | "zerofrom", 1796 | ] 1797 | 1798 | [[package]] 1799 | name = "yoke-derive" 1800 | version = "0.8.1" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" 1803 | dependencies = [ 1804 | "proc-macro2", 1805 | "quote", 1806 | "syn", 1807 | "synstructure", 1808 | ] 1809 | 1810 | [[package]] 1811 | name = "zerofrom" 1812 | version = "0.1.6" 1813 | source = "registry+https://github.com/rust-lang/crates.io-index" 1814 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 1815 | dependencies = [ 1816 | "zerofrom-derive", 1817 | ] 1818 | 1819 | [[package]] 1820 | name = "zerofrom-derive" 1821 | version = "0.1.6" 1822 | source = "registry+https://github.com/rust-lang/crates.io-index" 1823 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 1824 | dependencies = [ 1825 | "proc-macro2", 1826 | "quote", 1827 | "syn", 1828 | "synstructure", 1829 | ] 1830 | 1831 | [[package]] 1832 | name = "zeroize" 1833 | version = "1.8.2" 1834 | source = "registry+https://github.com/rust-lang/crates.io-index" 1835 | checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 1836 | 1837 | [[package]] 1838 | name = "zerotrie" 1839 | version = "0.2.3" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" 1842 | dependencies = [ 1843 | "displaydoc", 1844 | "yoke", 1845 | "zerofrom", 1846 | ] 1847 | 1848 | [[package]] 1849 | name = "zerovec" 1850 | version = "0.11.5" 1851 | source = "registry+https://github.com/rust-lang/crates.io-index" 1852 | checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" 1853 | dependencies = [ 1854 | "yoke", 1855 | "zerofrom", 1856 | "zerovec-derive", 1857 | ] 1858 | 1859 | [[package]] 1860 | name = "zerovec-derive" 1861 | version = "0.11.2" 1862 | source = "registry+https://github.com/rust-lang/crates.io-index" 1863 | checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" 1864 | dependencies = [ 1865 | "proc-macro2", 1866 | "quote", 1867 | "syn", 1868 | ] 1869 | --------------------------------------------------------------------------------