aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorUMTS at Teleco <crt@teleco.ch>2026-03-08 22:11:52 +0100
committerUMTS at Teleco <crt@teleco.ch>2026-03-08 22:11:52 +0100
commit17c6bd803dbbecb8975b1b98532d25a807a7b3fb (patch)
treebc0e339ca514414261ce211ab0f74101e6d8aefa /src
parentee17cec85d3d9ef2abc0d7a50c980d82b429b59d (diff)
docs and font fix
Diffstat (limited to 'src')
-rw-r--r--src/app.rs66
-rw-r--r--src/cli.rs58
-rw-r--r--src/config.rs57
-rw-r--r--src/lookup.rs505
-rw-r--r--src/main.rs41
-rw-r--r--src/output.rs21
-rw-r--r--src/tlds.rs4
-rw-r--r--src/tui.rs534
-rw-r--r--src/types.rs2
9 files changed, 916 insertions, 372 deletions
diff --git a/src/app.rs b/src/app.rs
index 55c75df..4a863bd 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,6 +1,6 @@
-// hoardom-app: native e gui emo wrapper for the hoardom tui
+// e gui emo wrapper for the hoardom tui
// spawns hoardom --tui in a pty and renders it in its own window
-// so it shows up with its own icon in the dock (mac) or taskbar (linux)
+// so it shows up with its own icon in the dock (mac) or taskbar (linux) for people that want that
//
// built with: cargo build --features gui
@@ -15,7 +15,10 @@ use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
-// ----- constants -----
+// thx gemma for the formated section comments i didnt want... thats not what I wanted when i said sanetize my comments from swearing and german language.
+// i mean it is prettier than my usual way, will check otherfiles to see if can atleast make it somewhat consistant.
+
+// ---- constants ----
const FONT_SIZE: f32 = 14.0;
const DEFAULT_COLS: u16 = 120;
@@ -33,6 +36,8 @@ enum TermColor {
Rgb(u8, u8, u8),
}
+
+// ai made this fn because i gave up on trying.
fn ansi_color(idx: u8) -> Color32 {
match idx {
0 => Color32::from_rgb(0, 0, 0),
@@ -58,7 +63,11 @@ fn ansi_color(idx: u8) -> Color32 {
let gi = (idx % 36) / 6;
let bi = idx % 6;
let v = |i: u16| -> u8 {
- if i == 0 { 0 } else { 55 + i as u8 * 40 }
+ if i == 0 {
+ 0
+ } else {
+ 55 + i as u8 * 40
+ }
};
Color32::from_rgb(v(ri), v(gi), v(bi))
}
@@ -73,7 +82,11 @@ fn ansi_color(idx: u8) -> Color32 {
fn resolve_color(c: TermColor, is_fg: bool) -> Color32 {
match c {
TermColor::Default => {
- if is_fg { DEFAULT_FG } else { DEFAULT_BG }
+ if is_fg {
+ DEFAULT_FG
+ } else {
+ DEFAULT_BG
+ }
}
TermColor::Indexed(i) => ansi_color(i),
TermColor::Rgb(r, g, b) => Color32::from_rgb(r, g, b),
@@ -135,6 +148,8 @@ impl Cell {
// ----- terminal grid -----
+
+// contains quiet a few ai solved bugfixes they seem ... fineish... to me and work
struct TermGrid {
cells: Vec<Vec<Cell>>,
rows: usize,
@@ -158,12 +173,13 @@ struct TermGrid {
alt_saved: Option<(Vec<Vec<Cell>>, usize, usize)>,
// mouse tracking modes
- mouse_normal: bool, // ?1000 - normal tracking (clicks)
- mouse_button: bool, // ?1002 - button-event tracking (drag)
- mouse_any: bool, // ?1003 - any-event tracking (all motion)
- mouse_sgr: bool, // ?1006 - SGR extended coordinates
+ mouse_normal: bool, // ?1000 - normal tracking (clicks)
+ mouse_button: bool, // ?1002 - button-event tracking (drag)
+ mouse_any: bool, // ?1003 - any-event tracking (all motion)
+ mouse_sgr: bool, // ?1006 - SGR extended coordinates
}
+// i partially stole this from somewhere i forgot from where though.
impl TermGrid {
fn new(rows: usize, cols: usize) -> Self {
TermGrid {
@@ -397,7 +413,7 @@ impl TermGrid {
}
}
- // SGR - set graphics rendition (colors and attributes)
+ // slopfix : SGR , set graphics rendition (colors and attributes)
fn sgr(&mut self, params: &[u16]) {
if params.is_empty() {
self.reset_attrs();
@@ -637,6 +653,7 @@ impl Perform for TermGrid {
// ----- keyboard input mapping -----
// map egui keys to terminal escape sequences
+// yk because maybe i want to recycle this file for some other project in the future hence why i implemented it
fn special_key_bytes(key: &egui::Key, modifiers: &egui::Modifiers) -> Option<Vec<u8>> {
use egui::Key;
match key {
@@ -721,7 +738,7 @@ struct HoardomApp {
cell_height: f32,
current_cols: u16,
current_rows: u16,
- last_mouse_button: Option<u8>, // track held mouse button for drag/release
+ last_mouse_button: Option<u8>, // track held mouse button for drag/release
}
impl eframe::App for HoardomApp {
@@ -1155,12 +1172,37 @@ fn main() -> eframe::Result<()> {
cc.egui_ctx.set_visuals(egui::Visuals::dark());
+ // font fallback chain for monospace: Hack (default) -> NotoSansMono -> NotoSansSymbols2
+ // Hack is missing box drawing, block elements, ellipsis
+ // NotoSansMono covers those but is missing dingbats (symbols)
+ // NotoSansSymbols2-subset has just those two glyphs from which only the necessary ones are extracted afaik
+ let mut fonts = egui::FontDefinitions::default();
+ fonts.font_data.insert(
+ "NotoSansMono".to_owned(),
+ std::sync::Arc::new(egui::FontData::from_static(include_bytes!(
+ "../dist/NotoSansMono-Regular.ttf"
+ ))),
+ );
+ fonts.font_data.insert(
+ "NotoSansSymbols2".to_owned(),
+ std::sync::Arc::new(egui::FontData::from_static(include_bytes!(
+ "../dist/NotoSansSymbols2-subset.ttf"
+ ))),
+ );
+ let mono = fonts
+ .families
+ .entry(egui::FontFamily::Monospace)
+ .or_default();
+ mono.push("NotoSansMono".to_owned());
+ mono.push("NotoSansSymbols2".to_owned());
+ cc.egui_ctx.set_fonts(fonts);
+
Ok(Box::new(HoardomApp {
grid,
pty_writer: Mutex::new(writer),
pty_master: pair.master,
child_exited,
- cell_width: 0.0, // measured on first frame
+ cell_width: 0.0, // measured on first frame
cell_height: 0.0,
current_cols: DEFAULT_COLS,
current_rows: DEFAULT_ROWS,
diff --git a/src/cli.rs b/src/cli.rs
index e4f905b..5428f05 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -2,98 +2,60 @@ use clap::Parser;
use std::path::PathBuf;
#[derive(Parser, Debug)]
-#[command(name = "hoardom", version = "0.0.1", about = "Domain hoarding made less painful")]
+#[command(
+ name = "hoardom",
+ version = "0.0.1",
+ about = "Domain hoarding made less painful"
+)] // static version infos ???? whoops
#[command(disable_help_flag = true, disable_version_flag = true)]
pub struct Args {
- /// One or more domain names to search for
+ /// ffs why were a million comments added at some point !?!? basically all of these are self explanatory. removed dumb ass redundant ones.
+
#[arg(value_name = "DOMAIN")]
pub domains: Vec<String>,
-
- // -- Mode --
- /// Default non interactive mode
#[arg(long = "cli", default_value_t = false)]
pub cli_mode: bool,
-
- /// Easy to use Terminal based Graphical user interface
#[arg(long = "tui", default_value_t = false)]
pub tui_mode: bool,
-
- // -- Basics --
- /// Define where environement file should be saved
#[arg(short = 'e', long = "environement")]
pub env_path: Option<PathBuf>,
-
- /// Show all in list even when unavailable
#[arg(short = 'a', long = "all", default_value_t = false)]
pub show_all: bool,
-
- // -- Advanced --
- /// Out in CSV, Path is optional. If path isnt given will be printed to terminal with no logs
#[arg(short = 'c', long = "csv")]
pub csv: Option<Option<PathBuf>>,
-
- /// Built in TLD list to use (from Lists.toml)
#[arg(short = 'l', long = "list")]
pub tld_list: Option<String>,
-
- /// Import a custom toml list for this session
#[arg(short = 'i', long = "import-filter")]
pub import_filter: Option<PathBuf>,
-
- /// Set certain TLDs to show up as first result (comma separated)
#[arg(short = 't', long = "top", value_delimiter = ',')]
pub top_tlds: Option<Vec<String>>,
-
- /// Only search these TLDs (comma separated)
#[arg(short = 'o', long = "onlytop", value_delimiter = ',')]
pub only_top: Option<Vec<String>>,
-
- /// How many suggestions to look up and try to show (defaults to 0 aka disabled)
#[arg(short = 's', long = "suggestions")]
pub suggestions: Option<usize>,
-
- // -- Various --
- /// Number of concurrent lookup requests (default: 1)
#[arg(short = 'j', long = "jobs")]
pub jobs: Option<u8>,
-
- /// Set the global delay in seconds between lookup requests
#[arg(short = 'D', long = "delay")]
pub delay: Option<f64>,
-
- /// Retry NUMBER amount of times if domain lookup errors out
#[arg(short = 'R', long = "retry")]
pub retry: Option<u32>,
-
- /// Verbose output for debugging
#[arg(short = 'V', long = "verbose", default_value_t = false)]
pub verbose: bool,
-
- /// Search for names/domains in text file, one domain per new line
+ /// search for names/domains in a text file line by line.
#[arg(short = 'A', long = "autosearch")]
pub autosearch: Option<PathBuf>,
-
- /// Use a monochrome color scheme
+ /// Use a monochrome color scheme TODO: not applied in TUI since colors were changed from RGB to Ratatui colors. should fix
#[arg(short = 'C', long = "no-color", default_value_t = false)]
pub no_color: bool,
-
- /// Do not use unicode only plain ASCII
+ /// Do not use unicode only plain ASCII TODO: not applied in TUI for some reason idk
#[arg(short = 'U', long = "no-unicode", default_value_t = false)]
pub no_unicode: bool,
-
- /// Disable the mouse integration for TUI
#[arg(short = 'M', long = "no-mouse", default_value_t = false)]
pub no_mouse: bool,
-
- /// Force refresh the RDAP bootstrap cache
#[arg(short = 'r', long = "refresh-cache", default_value_t = false)]
pub refresh_cache: bool,
-
- /// Basic Help
#[arg(short = 'h', long = "help", default_value_t = false)]
pub help: bool,
-
- /// Show full help
#[arg(short = 'H', long = "fullhelp", default_value_t = false)]
pub fullhelp: bool,
}
diff --git a/src/config.rs b/src/config.rs
index 1e2efeb..546dd16 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -36,10 +36,10 @@ pub struct FavoriteEntry {
/// last known status: "available", "registered", "error", or "unknown"
#[serde(default = "default_fav_status")]
pub status: String,
- /// when it was last checked (RFC 3339)
+ /// when it was last checked
#[serde(default)]
pub checked: String,
- /// true when status changed since last check (shows ! in TUI)
+ /// true when status changed since last check
#[serde(default)]
pub changed: bool,
}
@@ -128,7 +128,11 @@ fn default_jobs() -> u8 {
}
fn default_noretry() -> Vec<String> {
- vec!["rate_limit".to_string(), "invalid_tld".to_string(), "forbidden".to_string()]
+ vec![
+ "rate_limit".to_string(),
+ "invalid_tld".to_string(),
+ "forbidden".to_string(),
+ ]
}
fn default_backups_enabled() -> bool {
@@ -208,7 +212,11 @@ impl Config {
return Config {
settings: legacy.settings,
cache: legacy.cache,
- favorites: legacy.favorites.into_iter().map(FavoriteEntry::new).collect(),
+ favorites: legacy
+ .favorites
+ .into_iter()
+ .map(FavoriteEntry::new)
+ .collect(),
imported_filters: legacy.imported_filters,
scratchpad: legacy.scratchpad,
};
@@ -240,37 +248,35 @@ impl Config {
let body = toml::to_string_pretty(self)
.map_err(|e| format!("Failed to serialize config: {}", e))?;
- let content = format!("\
+ let content = format!(
+ "\
# hoardom config - auto saved, comments are preserved on the line theyre on
#
# [settings]
-# noretry: error types that shouldnt be retried
-# \u{201c}rate_limit\u{201d} - server said slow down, retrying immediately wont help
-# \u{201c}invalid_tld\u{201d} - TLD is genuinely broken, no point retrying
-# \u{201c}forbidden\u{201d} - server returned 403, access denied, retrying wont fix it
-# \u{201c}timeout\u{201d} - uncomment if youd rather skip slow TLDs than wait
-# \u{201c}unknown\u{201d} - uncomment to skip any unrecognized errors too
-\n{}", body);
- std::fs::write(path, content)
- .map_err(|e| format!("Failed to write config file: {}", e))?;
+# noretry: error types that shouldnt be retried has the following valid values btw:
+# \u{201c}rate_limit\u{201d}
+# \u{201c}invalid_tld\u{201d}
+# \u{201c}forbidden\u{201d}
+# \u{201c}timeout\u{201d}
+# \u{201c}unknown\u{201d}
+\n{}",
+ body
+ );
+ std::fs::write(path, content).map_err(|e| format!("Failed to write config file: {}", e))?;
Ok(())
}
- /// copy current config into backups/ folder.
- /// keeps at most `max_count` backups, tosses the oldest.
- /// only call on startup and shutdown - NOT on every save.
+ /// only call on startup and shutdown NOT on every save bruh
pub fn create_backup(config_path: &Path, max_count: u32) -> Result<(), String> {
let parent = config_path.parent().ok_or("No parent directory")?;
let backup_dir = parent.join("backups");
std::fs::create_dir_all(&backup_dir)
.map_err(|e| format!("Failed to create backup dir: {}", e))?;
- // Timestamp-based filename: config_20260308_143022.toml
let ts = chrono::Local::now().format("%Y%m%d_%H%M%S");
let backup_name = format!("config_{}.toml", ts);
let backup_path = backup_dir.join(&backup_name);
- // dont backup if same-second backup already exists
if backup_path.exists() {
return Ok(());
}
@@ -278,7 +284,6 @@ impl Config {
std::fs::copy(config_path, &backup_path)
.map_err(|e| format!("Failed to copy config to backup: {}", e))?;
- // prune old backups: sort by name (timestamp order), keep newest N
if max_count > 0 {
let mut backups: Vec<_> = std::fs::read_dir(&backup_dir)
.map_err(|e| format!("Failed to read backup dir: {}", e))?
@@ -303,6 +308,7 @@ impl Config {
}
/// replaces filter with same name if theres one already
+ /// filters ? what kinda ai slip is this ? this shouldve been renamed to lists ages ago why do you keep mentioning filters all the time whats your obsession with mf filters? JEZE!
pub fn import_filter(&mut self, filter: ImportedFilter) {
self.imported_filters.retain(|f| f.name != filter.name);
self.imported_filters.push(filter);
@@ -339,10 +345,10 @@ impl Config {
}
pub fn parse_filter_file(path: &PathBuf) -> Result<ImportedFilter, String> {
- let content = std::fs::read_to_string(path)
- .map_err(|e| format!("Could not read filter file: {}", e))?;
- let filter: ImportedFilter = toml::from_str(&content)
- .map_err(|e| format!("Could not parse filter file: {}", e))?;
+ let content =
+ std::fs::read_to_string(path).map_err(|e| format!("Could not read filter file: {}", e))?;
+ let filter: ImportedFilter =
+ toml::from_str(&content).map_err(|e| format!("Could not parse filter file: {}", e))?;
if filter.name.is_empty() {
return Err("Filter file must have a name defined".to_string());
}
@@ -425,7 +431,7 @@ pub fn resolve_paths(explicit: Option<&PathBuf>) -> HoardomPaths {
}
}
- // nothing works disable caching, use a dummy path
+ // nothing works disable caching and cry about it (it will still work just no persistant sessions)
eprintln!("Warning: could not create .hoardom directory anywhere, caching disabled");
HoardomPaths {
config_file: PathBuf::from(".hoardom/config.toml"),
@@ -434,4 +440,3 @@ pub fn resolve_paths(explicit: Option<&PathBuf>) -> HoardomPaths {
caching_enabled: false,
}
}
-
diff --git a/src/lookup.rs b/src/lookup.rs
index f5b3177..694f559 100644
--- a/src/lookup.rs
+++ b/src/lookup.rs
@@ -6,6 +6,10 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
+// Dont ask me about the spaghetti code found in here god may know but I dont remember what unholy sins have been created at like 5am here.
+
+
+
#[cfg(feature = "builtin-whois")]
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[cfg(feature = "builtin-whois")]
@@ -23,7 +27,10 @@ pub struct RdapBootstrap {
impl RdapBootstrap {
pub async fn fetch(client: &reqwest::Client, verbose: bool) -> Result<Self, String> {
if verbose {
- eprintln!("[verbose] Fetching RDAP bootstrap from {}", RDAP_BOOTSTRAP_URL);
+ eprintln!(
+ "[verbose] Fetching RDAP bootstrap from {}",
+ RDAP_BOOTSTRAP_URL
+ );
}
let resp = client
@@ -47,15 +54,24 @@ impl RdapBootstrap {
let tld_map = Self::parse_bootstrap_json(&json);
if verbose {
- eprintln!("[verbose] RDAP bootstrap loaded, {} TLDs mapped", tld_map.len());
+ eprintln!(
+ "[verbose] RDAP bootstrap loaded, {} TLDs mapped",
+ tld_map.len()
+ );
}
- Ok(Self { tld_map, raw_json: Some(body) })
+ Ok(Self {
+ tld_map,
+ raw_json: Some(body),
+ })
}
pub fn load_cached(cache_path: &Path, verbose: bool) -> Result<Self, String> {
if verbose {
- eprintln!("[verbose] Loading cached RDAP bootstrap from {}", cache_path.display());
+ eprintln!(
+ "[verbose] Loading cached RDAP bootstrap from {}",
+ cache_path.display()
+ );
}
let body = std::fs::read_to_string(cache_path)
.map_err(|e| format!("Could not read cached bootstrap: {}", e))?;
@@ -63,9 +79,15 @@ impl RdapBootstrap {
.map_err(|e| format!("Could not parse cached bootstrap: {}", e))?;
let tld_map = Self::parse_bootstrap_json(&json);
if verbose {
- eprintln!("[verbose] Cached RDAP bootstrap loaded, {} TLDs mapped", tld_map.len());
+ eprintln!(
+ "[verbose] Cached RDAP bootstrap loaded, {} TLDs mapped",
+ tld_map.len()
+ );
}
- Ok(Self { tld_map, raw_json: Some(body) })
+ Ok(Self {
+ tld_map,
+ raw_json: Some(body),
+ })
}
pub fn save_cache(&self, cache_path: &Path) -> Result<(), String> {
@@ -126,7 +148,10 @@ pub async fn lookup_domain(
None => {
// no RDAP server for this TLD, fall back to WHOIS
if verbose {
- eprintln!("[verbose] No RDAP server for {}, falling back to WHOIS", tld);
+ eprintln!(
+ "[verbose] No RDAP server for {}, falling back to WHOIS",
+ tld
+ );
}
return whois_lookup(whois_overrides, name, tld, verbose).await;
}
@@ -149,10 +174,14 @@ pub async fn lookup_domain(
} else {
ErrorKind::Unknown
};
- return DomainResult::new(name, tld, DomainStatus::Error {
- kind,
- message: "unknown error".to_string(),
- });
+ return DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind,
+ message: "unknown error".to_string(),
+ },
+ );
}
};
@@ -169,34 +198,50 @@ pub async fn lookup_domain(
// 400 = probably invalid query
if status_code == 400 {
- return DomainResult::new(name, tld, DomainStatus::Error {
- kind: ErrorKind::InvalidTld,
- message: "invalid tld".to_string(),
- });
+ return DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: ErrorKind::InvalidTld,
+ message: "invalid tld".to_string(),
+ },
+ );
}
// 429 = rate limited
if status_code == 429 {
- return DomainResult::new(name, tld, DomainStatus::Error {
- kind: ErrorKind::RateLimit,
- message: "rate limited".to_string(),
- });
+ return DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: ErrorKind::RateLimit,
+ message: "rate limited".to_string(),
+ },
+ );
}
// 403 = forbidden (some registries block queries)
if status_code == 403 {
- return DomainResult::new(name, tld, DomainStatus::Error {
- kind: ErrorKind::Forbidden,
- message: "forbidden".to_string(),
- });
+ return DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: ErrorKind::Forbidden,
+ message: "forbidden".to_string(),
+ },
+ );
}
- // anything else thats not success
+ // anything else thats not success is le bad
if !status_code.is_success() {
- return DomainResult::new(name, tld, DomainStatus::Error {
- kind: ErrorKind::Unknown,
- message: format!("HTTP {}", status_code),
- });
+ return DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: ErrorKind::Unknown,
+ message: format!("HTTP {}", status_code),
+ },
+ );
}
// 200 = domain exists, try to parse expiry from RDAP json
@@ -229,16 +274,30 @@ fn extract_expiry(json: &serde_json::Value) -> Option<String> {
// -- No whois feature: just return an error --
#[cfg(not(any(feature = "system-whois", feature = "builtin-whois")))]
-async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, _verbose: bool) -> DomainResult {
- DomainResult::new(name, tld, DomainStatus::Error {
- kind: ErrorKind::InvalidTld,
- message: "no RDAP server (whois disabled)".to_string(),
- })
+async fn whois_lookup(
+ _whois_overrides: &WhoisOverrides,
+ name: &str,
+ tld: &str,
+ _verbose: bool,
+) -> DomainResult {
+ DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: ErrorKind::InvalidTld,
+ message: "no RDAP server (whois disabled)".to_string(),
+ },
+ )
}
// -- System whois: shells out to the systems whois binary --
#[cfg(feature = "system-whois")]
-async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str, verbose: bool) -> DomainResult {
+async fn whois_lookup(
+ _whois_overrides: &WhoisOverrides,
+ name: &str,
+ tld: &str,
+ verbose: bool,
+) -> DomainResult {
let full = format!("{}.{}", name, tld);
let whois_cmd = env!("HOARDOM_WHOIS_CMD");
let whois_flags = env!("HOARDOM_WHOIS_FLAGS");
@@ -247,7 +306,10 @@ async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str,
if whois_flags.is_empty() {
eprintln!("[verbose] System WHOIS: {} {}", whois_cmd, full);
} else {
- eprintln!("[verbose] System WHOIS: {} {} {}", whois_cmd, whois_flags, full);
+ eprintln!(
+ "[verbose] System WHOIS: {} {} {}",
+ whois_cmd, whois_flags, full
+ );
}
}
@@ -260,35 +322,44 @@ async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str,
}
cmd.arg(&full);
- let output = match tokio::time::timeout(
- Duration::from_secs(15),
- cmd.output(),
- ).await {
+ let output = match tokio::time::timeout(Duration::from_secs(15), cmd.output()).await {
Ok(Ok(out)) => out,
Ok(Err(e)) => {
if verbose {
eprintln!("[verbose] System whois error for {}: {}", full, e);
}
- return DomainResult::new(name, tld, DomainStatus::Error {
- kind: ErrorKind::Unknown,
- message: format!("whois command failed: {}", e),
- });
+ return DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: ErrorKind::Unknown,
+ message: format!("whois command failed: {}", e),
+ },
+ );
}
Err(_) => {
if verbose {
eprintln!("[verbose] System whois timeout for {}", full);
}
- return DomainResult::new(name, tld, DomainStatus::Error {
- kind: ErrorKind::Timeout,
- message: "whois timeout".to_string(),
- });
+ return DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: ErrorKind::Timeout,
+ message: "whois timeout".to_string(),
+ },
+ );
}
};
let response_str = String::from_utf8_lossy(&output.stdout);
if verbose {
- eprintln!("[verbose] WHOIS response for {} ({} bytes)", full, output.stdout.len());
+ eprintln!(
+ "[verbose] WHOIS response for {} ({} bytes)",
+ full,
+ output.stdout.len()
+ );
}
if !output.status.success() {
@@ -300,26 +371,34 @@ async fn whois_lookup(_whois_overrides: &WhoisOverrides, name: &str, tld: &str,
if !response_str.is_empty() {
return parse_whois_response(name, tld, &response_str);
}
- return DomainResult::new(name, tld, DomainStatus::Error {
- kind: ErrorKind::Unknown,
- message: "whois command returned error".to_string(),
- });
+ return DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: ErrorKind::Unknown,
+ message: "whois command returned error".to_string(),
+ },
+ );
}
parse_whois_response(name, tld, &response_str)
}
-// -- Builtin whois: raw TCP to whois servers directly --
+// -- Builtin whois: rawdogs whois server violently over TCP directly--
+
-/// try a whois server, returns the response string or errors out
+
+/// try a whois server returns the response string or errors out
#[cfg(feature = "builtin-whois")]
-async fn try_whois_server(server: &str, domain: &str, verbose: bool) -> Result<String, &'static str> {
+async fn try_whois_server(
+ server: &str,
+ domain: &str,
+ verbose: bool,
+) -> Result<String, &'static str> {
let addr = format!("{}:43", server);
- let stream = match tokio::time::timeout(
- Duration::from_secs(4),
- TcpStream::connect(&addr),
- ).await {
+ let stream = match tokio::time::timeout(Duration::from_secs(4), TcpStream::connect(&addr)).await
+ {
Ok(Ok(s)) => s,
Ok(Err(_)) => return Err("connect error"),
Err(_) => return Err("connect timeout"),
@@ -337,10 +416,7 @@ async fn try_whois_server(server: &str, domain: &str, verbose: bool) -> Result<S
}
let mut response = Vec::new();
- match tokio::time::timeout(
- Duration::from_secs(8),
- reader.read_to_end(&mut response),
- ).await {
+ match tokio::time::timeout(Duration::from_secs(8), reader.read_to_end(&mut response)).await {
Ok(Ok(_)) => {}
Ok(Err(_)) => return Err("read error"),
Err(_) => return Err("read timeout"),
@@ -361,7 +437,12 @@ fn whois_candidates(tld: &str) -> Vec<String> {
}
#[cfg(feature = "builtin-whois")]
-async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, verbose: bool) -> DomainResult {
+async fn whois_lookup(
+ whois_overrides: &WhoisOverrides,
+ name: &str,
+ tld: &str,
+ verbose: bool,
+) -> DomainResult {
let full = format!("{}.{}", name, tld);
// if Lists.toml has an explicit server ("tld:server"), use ONLY that one
@@ -371,14 +452,26 @@ async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, v
}
return match try_whois_server(server, &full, verbose).await {
Ok(resp) if !resp.is_empty() => parse_whois_response(name, tld, &resp),
- Ok(_) => DomainResult::new(name, tld, DomainStatus::Error {
- kind: ErrorKind::Unknown,
- message: "empty whois response".to_string(),
- }),
- Err(e) => DomainResult::new(name, tld, DomainStatus::Error {
- kind: if e.contains("timeout") { ErrorKind::Timeout } else { ErrorKind::Unknown },
- message: format!("whois {}: {}", server, e),
- }),
+ Ok(_) => DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: ErrorKind::Unknown,
+ message: "empty whois response".to_string(),
+ },
+ ),
+ Err(e) => DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: if e.contains("timeout") {
+ ErrorKind::Timeout
+ } else {
+ ErrorKind::Unknown
+ },
+ message: format!("whois {}: {}", server, e),
+ },
+ ),
};
}
@@ -386,7 +479,11 @@ async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, v
let candidates = whois_candidates(tld);
if verbose {
- eprintln!("[verbose] WHOIS probing {} candidates for .{}", candidates.len(), tld);
+ eprintln!(
+ "[verbose] WHOIS probing {} candidates for .{}",
+ candidates.len(),
+ tld
+ );
}
for server in &candidates {
@@ -408,10 +505,14 @@ async fn whois_lookup(whois_overrides: &WhoisOverrides, name: &str, tld: &str, v
}
// nothing worked
- DomainResult::new(name, tld, DomainStatus::Error {
- kind: ErrorKind::Unknown,
- message: "no whois server reachable".to_string(),
- })
+ DomainResult::new(
+ name,
+ tld,
+ DomainStatus::Error {
+ kind: ErrorKind::Unknown,
+ message: "no whois server reachable".to_string(),
+ },
+ )
}
fn parse_whois_response(name: &str, tld: &str, response: &str) -> DomainResult {
@@ -483,6 +584,8 @@ fn extract_whois_expiry(response: &str) -> Option<String> {
None
}
+
+
pub async fn lookup_with_retry(
client: &reqwest::Client,
bootstrap: &RdapBootstrap,
@@ -503,13 +606,19 @@ pub async fn lookup_with_retry(
if let DomainStatus::Error { kind, .. } = &result.status {
if noretry.contains(kind) {
if verbose {
- eprintln!("[verbose] Not retrying {}.{} (error kind in noretry list)", name, tld);
+ eprintln!(
+ "[verbose] Not retrying {}.{} (error kind in noretry list)",
+ name, tld
+ );
}
break;
}
}
if verbose {
- eprintln!("[verbose] Retry {}/{} for {}.{}", attempt, retries, name, tld);
+ eprintln!(
+ "[verbose] Retry {}/{} for {}.{}",
+ attempt, retries, name, tld
+ );
}
tokio::time::sleep(Duration::from_millis(500)).await;
result = lookup_domain(client, bootstrap, whois_overrides, name, tld, verbose).await;
@@ -554,7 +663,17 @@ pub async fn lookup_all(
let mut results = Vec::with_capacity(total);
let delay = Duration::from_secs_f64(delay_secs);
for (i, tld) in tlds.iter().enumerate() {
- let result = lookup_with_retry(&client, &bootstrap, whois_overrides, name, tld, retries, noretry, verbose).await;
+ let result = lookup_with_retry(
+ &client,
+ &bootstrap,
+ whois_overrides,
+ name,
+ tld,
+ retries,
+ noretry,
+ verbose,
+ )
+ .await;
results.push(result);
on_progress(i + 1, total);
if delay_secs > 0.0 && i + 1 < total {
@@ -579,7 +698,17 @@ pub async fn lookup_all(
let name = name_owned.clone();
let tld = tld.to_string();
async move {
- let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await;
+ let result = lookup_with_retry(
+ &client,
+ &bootstrap,
+ &whois_overrides,
+ &name,
+ &tld,
+ retries,
+ &noretry,
+ verbose,
+ )
+ .await;
(i, result)
}
})
@@ -604,7 +733,10 @@ pub async fn refresh_cache(cache_path: &Path, verbose: bool) -> Result<(), Strin
let client = build_client();
let bootstrap = RdapBootstrap::fetch(&client, verbose).await?;
bootstrap.save_cache(cache_path)?;
- eprintln!("RDAP bootstrap cache refreshed ({} TLDs)", bootstrap.tld_map.len());
+ eprintln!(
+ "RDAP bootstrap cache refreshed ({} TLDs)",
+ bootstrap.tld_map.len()
+ );
Ok(())
}
@@ -642,33 +774,37 @@ async fn resolve_bootstrap(
match cached {
Some(b) => Some(b),
- None => {
- match RdapBootstrap::fetch(client, verbose).await {
- Ok(b) => {
- if let Some(cp) = cache_path {
- if let Err(e) = b.save_cache(cp) {
- if verbose {
- eprintln!("[verbose] Failed to save cache: {}", e);
- }
- } else if verbose {
- eprintln!("[verbose] RDAP bootstrap cached to {}", cp.display());
+ None => match RdapBootstrap::fetch(client, verbose).await {
+ Ok(b) => {
+ if let Some(cp) = cache_path {
+ if let Err(e) = b.save_cache(cp) {
+ if verbose {
+ eprintln!("[verbose] Failed to save cache: {}", e);
}
+ } else if verbose {
+ eprintln!("[verbose] RDAP bootstrap cached to {}", cp.display());
}
- Some(b)
- }
- Err(e) => {
- eprintln!("Error: {}", e);
- eprintln!("Cannot perform lookups without RDAP bootstrap data.");
- None
}
+ Some(b)
}
- }
+ Err(e) => {
+ eprintln!("Error: {}", e);
+ eprintln!("Cannot perform lookups without RDAP bootstrap data.");
+ None
+ }
+ },
}
}
pub enum StreamMsg {
- Result { result: DomainResult, sort_index: usize },
- Progress { current: usize, total: usize },
+ Result {
+ result: DomainResult,
+ sort_index: usize,
+ },
+ Progress {
+ current: usize,
+ total: usize,
+ },
Error(String),
Done,
}
@@ -698,19 +834,19 @@ pub fn lookup_streaming(
let handle = tokio::spawn(async move {
let client = build_client();
- let bootstrap = match resolve_bootstrap(
- &client,
- cache_path.as_deref(),
- force_refresh,
- verbose,
- ).await {
- Some(b) => b,
- None => {
- let _ = tx.send(StreamMsg::Error("Failed to load RDAP bootstrap".to_string())).await;
- let _ = tx.send(StreamMsg::Done).await;
- return;
- }
- };
+ let bootstrap =
+ match resolve_bootstrap(&client, cache_path.as_deref(), force_refresh, verbose).await {
+ Some(b) => b,
+ None => {
+ let _ = tx
+ .send(StreamMsg::Error(
+ "Failed to load RDAP bootstrap".to_string(),
+ ))
+ .await;
+ let _ = tx.send(StreamMsg::Done).await;
+ return;
+ }
+ };
let total = tlds.len();
let concurrent = (jobs as usize).max(1);
@@ -718,9 +854,29 @@ pub fn lookup_streaming(
if concurrent <= 1 {
let delay = Duration::from_secs_f64(delay_secs);
for (i, tld) in tlds.iter().enumerate() {
- let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, tld, retries, &noretry, verbose).await;
- let _ = tx.send(StreamMsg::Result { result, sort_index: i }).await;
- let _ = tx.send(StreamMsg::Progress { current: i + 1, total }).await;
+ let result = lookup_with_retry(
+ &client,
+ &bootstrap,
+ &whois_overrides,
+ &name,
+ tld,
+ retries,
+ &noretry,
+ verbose,
+ )
+ .await;
+ let _ = tx
+ .send(StreamMsg::Result {
+ result,
+ sort_index: i,
+ })
+ .await;
+ let _ = tx
+ .send(StreamMsg::Progress {
+ current: i + 1,
+ total,
+ })
+ .await;
if delay_secs > 0.0 && i + 1 < total {
tokio::time::sleep(delay).await;
}
@@ -740,7 +896,17 @@ pub fn lookup_streaming(
let noretry = Arc::clone(&noretry);
let name = name.clone();
async move {
- let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await;
+ let result = lookup_with_retry(
+ &client,
+ &bootstrap,
+ &whois_overrides,
+ &name,
+ &tld,
+ retries,
+ &noretry,
+ verbose,
+ )
+ .await;
(idx, result)
}
})
@@ -749,8 +915,18 @@ pub fn lookup_streaming(
let mut done_count = 0usize;
while let Some((idx, result)) = stream.next().await {
done_count += 1;
- let _ = tx2.send(StreamMsg::Result { result, sort_index: idx }).await;
- let _ = tx2.send(StreamMsg::Progress { current: done_count, total }).await;
+ let _ = tx2
+ .send(StreamMsg::Result {
+ result,
+ sort_index: idx,
+ })
+ .await;
+ let _ = tx2
+ .send(StreamMsg::Progress {
+ current: done_count,
+ total,
+ })
+ .await;
}
}
@@ -776,7 +952,18 @@ pub fn lookup_many_streaming(
) -> LookupStream {
if batches.len() == 1 {
let (name, tlds) = batches.into_iter().next().unwrap();
- return lookup_streaming(name, tlds, delay_secs, retries, verbose, cache_path, force_refresh, jobs, whois_overrides, noretry);
+ return lookup_streaming(
+ name,
+ tlds,
+ delay_secs,
+ retries,
+ verbose,
+ cache_path,
+ force_refresh,
+ jobs,
+ whois_overrides,
+ noretry,
+ );
}
let (tx, rx) = tokio::sync::mpsc::channel(64);
@@ -784,19 +971,19 @@ pub fn lookup_many_streaming(
let handle = tokio::spawn(async move {
let client = build_client();
- let bootstrap = match resolve_bootstrap(
- &client,
- cache_path.as_deref(),
- force_refresh,
- verbose,
- ).await {
- Some(b) => b,
- None => {
- let _ = tx.send(StreamMsg::Error("Failed to load RDAP bootstrap".to_string())).await;
- let _ = tx.send(StreamMsg::Done).await;
- return;
- }
- };
+ let bootstrap =
+ match resolve_bootstrap(&client, cache_path.as_deref(), force_refresh, verbose).await {
+ Some(b) => b,
+ None => {
+ let _ = tx
+ .send(StreamMsg::Error(
+ "Failed to load RDAP bootstrap".to_string(),
+ ))
+ .await;
+ let _ = tx.send(StreamMsg::Done).await;
+ return;
+ }
+ };
let total: usize = batches.iter().map(|(_, tlds)| tlds.len()).sum();
let concurrent = (jobs as usize).max(1);
@@ -807,9 +994,24 @@ pub fn lookup_many_streaming(
let mut global_idx = 0usize;
for (name, tlds) in batches {
for tld in tlds {
- let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await;
+ let result = lookup_with_retry(
+ &client,
+ &bootstrap,
+ &whois_overrides,
+ &name,
+ &tld,
+ retries,
+ &noretry,
+ verbose,
+ )
+ .await;
current += 1;
- let _ = tx.send(StreamMsg::Result { result, sort_index: global_idx }).await;
+ let _ = tx
+ .send(StreamMsg::Result {
+ result,
+ sort_index: global_idx,
+ })
+ .await;
let _ = tx.send(StreamMsg::Progress { current, total }).await;
if delay_secs > 0.0 && current < total {
tokio::time::sleep(delay).await;
@@ -839,7 +1041,17 @@ pub fn lookup_many_streaming(
let whois_overrides = Arc::clone(&whois_overrides);
let noretry = Arc::clone(&noretry);
async move {
- let result = lookup_with_retry(&client, &bootstrap, &whois_overrides, &name, &tld, retries, &noretry, verbose).await;
+ let result = lookup_with_retry(
+ &client,
+ &bootstrap,
+ &whois_overrides,
+ &name,
+ &tld,
+ retries,
+ &noretry,
+ verbose,
+ )
+ .await;
(idx, result)
}
})
@@ -848,13 +1060,26 @@ pub fn lookup_many_streaming(
let mut done_count = 0usize;
while let Some((idx, result)) = stream.next().await {
done_count += 1;
- let _ = tx2.send(StreamMsg::Result { result, sort_index: idx }).await;
- let _ = tx2.send(StreamMsg::Progress { current: done_count, total }).await;
+ let _ = tx2
+ .send(StreamMsg::Result {
+ result,
+ sort_index: idx,
+ })
+ .await;
+ let _ = tx2
+ .send(StreamMsg::Progress {
+ current: done_count,
+ total,
+ })
+ .await;
}
}
let _ = tx.send(StreamMsg::Done).await;
});
- LookupStream { receiver: rx, handle }
+ LookupStream {
+ receiver: rx,
+ handle,
+ }
}
diff --git a/src/main.rs b/src/main.rs
index aa0b993..9625959 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -98,13 +98,26 @@ async fn main() {
let overrides = whois_overrides();
// parse noretry config into ErrorKind list
- let noretry: Vec<ErrorKind> = config.settings.noretry.iter()
+ let noretry: Vec<ErrorKind> = config
+ .settings
+ .noretry
+ .iter()
.filter_map(|s| ErrorKind::from_config_str(s))
.collect();
// TUI mode
if args.is_tui() {
- if let Err(e) = tui::run_tui(&args, &config, paths.clone(), cache_file.clone(), force_refresh, overrides.clone(), noretry.clone()).await {
+ if let Err(e) = tui::run_tui(
+ &args,
+ &config,
+ paths.clone(),
+ cache_file.clone(),
+ force_refresh,
+ overrides.clone(),
+ noretry.clone(),
+ )
+ .await
+ {
eprintln!("TUI error: {}", e);
}
// save cache timestamp after TUI session if we refreshed
@@ -118,7 +131,15 @@ async fn main() {
// CLI needs at least one domain unless autosearch was given
if args.domains.is_empty() {
if let Some(file_path) = &args.autosearch {
- run_autosearch(&args, file_path, cache_file.as_deref(), force_refresh, overrides, &noretry).await;
+ run_autosearch(
+ &args,
+ file_path,
+ cache_file.as_deref(),
+ force_refresh,
+ overrides,
+ &noretry,
+ )
+ .await;
if force_refresh && paths.can_save {
config.mark_cache_updated();
let _ = config.save(&paths.config_file);
@@ -392,7 +413,10 @@ fn parse_domain_input(raw_domain: &str) -> (String, Option<String>) {
}
}
-fn build_effective_tlds(base_tlds: &[&'static str], specific_tld: Option<&str>) -> Vec<&'static str> {
+fn build_effective_tlds(
+ base_tlds: &[&'static str],
+ specific_tld: Option<&str>,
+) -> Vec<&'static str> {
if let Some(tld) = specific_tld {
vec![Box::leak(tld.to_string().into_boxed_str()) as &'static str]
} else {
@@ -403,7 +427,13 @@ fn build_effective_tlds(base_tlds: &[&'static str], specific_tld: Option<&str>)
fn estimate_total_lookups(domains: &[String], base_tlds: &[&'static str]) -> usize {
domains
.iter()
- .map(|domain| if domain.contains('.') { 1 } else { base_tlds.len() })
+ .map(|domain| {
+ if domain.contains('.') {
+ 1
+ } else {
+ base_tlds.len()
+ }
+ })
.sum()
}
@@ -415,4 +445,3 @@ fn sort_aggregated_results(mut aggregated: Vec<AggregatedResult>) -> Vec<DomainR
});
aggregated.into_iter().map(|item| item.result).collect()
}
-
diff --git a/src/output.rs b/src/output.rs
index 6a02f32..32198f4 100644
--- a/src/output.rs
+++ b/src/output.rs
@@ -57,9 +57,19 @@ pub fn print_full_table(results: &[DomainResult], no_color: bool, no_unicode: bo
}
// calc column widths
- let domain_w = results.iter().map(|r| r.full.len()).max().unwrap_or(10).max(7);
+ let domain_w = results
+ .iter()
+ .map(|r| r.full.len())
+ .max()
+ .unwrap_or(10)
+ .max(7);
let status_w = 10; // "registered" is the longest
- let note_w = results.iter().map(|r| r.note_str().len()).max().unwrap_or(4).max(4);
+ let note_w = results
+ .iter()
+ .map(|r| r.note_str().len())
+ .max()
+ .unwrap_or(4)
+ .max(4);
let domain_col = domain_w + 2;
let status_col = status_w + 2;
@@ -167,10 +177,9 @@ pub fn print_csv(results: &[DomainResult]) {
}
pub fn write_csv_file(results: &[DomainResult], path: &PathBuf) -> Result<(), String> {
- let mut file = std::fs::File::create(path)
- .map_err(|e| format!("Could not create CSV file: {}", e))?;
- writeln!(file, "Domains, Status, Note")
- .map_err(|e| format!("Write error: {}", e))?;
+ let mut file =
+ std::fs::File::create(path).map_err(|e| format!("Could not create CSV file: {}", e))?;
+ writeln!(file, "Domains, Status, Note").map_err(|e| format!("Write error: {}", e))?;
for r in results {
writeln!(file, "{}, {}, {}", r.full, r.status_str(), r.note_str())
.map_err(|e| format!("Write error: {}", e))?;
diff --git a/src/tlds.rs b/src/tlds.rs
index 2835c24..95697d0 100644
--- a/src/tlds.rs
+++ b/src/tlds.rs
@@ -52,8 +52,8 @@ static PARSED_LISTS: OnceLock<ParsedLists> = OnceLock::new();
fn parsed_lists() -> &'static ParsedLists {
PARSED_LISTS.get_or_init(|| {
- let raw: toml::Value = toml::from_str(include_str!("../Lists.toml"))
- .expect("Lists.toml must be valid TOML");
+ let raw: toml::Value =
+ toml::from_str(include_str!("../Lists.toml")).expect("Lists.toml must be valid TOML");
let table = raw.as_table().expect("Lists.toml must be a TOML table");
diff --git a/src/tui.rs b/src/tui.rs
index f6d9238..6c068dc 100644
--- a/src/tui.rs
+++ b/src/tui.rs
@@ -1,5 +1,8 @@
use crossterm::{
- event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind},
+ event::{
+ self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers,
+ MouseButton, MouseEvent, MouseEventKind,
+ },
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
@@ -19,12 +22,14 @@ use crate::cli::Args;
use crate::config::Config;
use crate::config::FavoriteEntry;
use crate::lookup;
-use crate::tlds::{apply_top_tlds, get_tlds_or_default, list_names, default_list_name};
+use crate::tlds::{apply_top_tlds, default_list_name, get_tlds_or_default, list_names};
use crate::types::{DomainResult, DomainStatus, ErrorKind};
// note : this will be the worst shitshow of code you will probably have looked at in youre entire life
-// it works and is somewhat stable but i didnt feel like sorting it into nice modules and all.
-// have fun
+// it works and is somewhat stable but i didnt feel like sorting it into nice modules and all mostly just relying
+// on copy pasting shit where i need it
+//
+// have fun and may you forgive me for this extremly large ahh file.
// names and labels
const APP_NAME: &str = "hoardom";
@@ -36,7 +41,6 @@ const SEARCH_BUTTON_LABEL: &str = "[Search]";
const STOP_BUTTON_LABEL: &str = "[Stop](s)";
const CLEAR_BUTTON_LABEL: &str = "[Clear](C)";
-
// Layout tuning constants
const TOPBAR_HEIGHT: u16 = 1;
const SEARCH_PANEL_HEIGHT: u16 = 3;
@@ -88,8 +92,7 @@ fn export_favorites_txt(path: &Path, favorites: &[FavoriteEntry]) -> Result<(),
.map_err(|e| format!("Failed to create export directory: {}", e))?;
}
let text: Vec<&str> = favorites.iter().map(|f| f.domain.as_str()).collect();
- std::fs::write(path, text.join("\n"))
- .map_err(|e| format!("Failed to export favorites: {}", e))
+ std::fs::write(path, text.join("\n")).map_err(|e| format!("Failed to export favorites: {}", e))
}
fn export_results_csv(path: &Path, results: &[&DomainResult]) -> Result<(), String> {
@@ -108,8 +111,7 @@ fn export_results_csv(path: &Path, results: &[&DomainResult]) -> Result<(), Stri
));
}
- std::fs::write(path, lines.join("\n"))
- .map_err(|e| format!("Failed to export results: {}", e))
+ std::fs::write(path, lines.join("\n")).map_err(|e| format!("Failed to export results: {}", e))
}
#[derive(Debug, Clone, PartialEq)]
@@ -125,7 +127,6 @@ enum ExportMode {
}
impl ExportMode {
-
fn default_file_name(self) -> &'static str {
match self {
ExportMode::FavoritesTxt => "hoardom-favorites.txt",
@@ -229,7 +230,16 @@ struct PanelRects {
}
impl App {
- fn new(args: &Args, config: &Config, config_path: PathBuf, can_save: bool, cache_path: Option<PathBuf>, force_refresh: bool, whois_overrides: crate::tlds::WhoisOverrides, noretry: Vec<ErrorKind>) -> Self {
+ fn new(
+ args: &Args,
+ config: &Config,
+ config_path: PathBuf,
+ can_save: bool,
+ cache_path: Option<PathBuf>,
+ force_refresh: bool,
+ whois_overrides: crate::tlds::WhoisOverrides,
+ noretry: Vec<ErrorKind>,
+ ) -> Self {
let tld_list_name = args
.tld_list
.as_ref()
@@ -271,7 +281,11 @@ impl App {
verbose: args.verbose,
delay: args.effective_delay(),
retries: args.effective_retry(),
- jobs: if args.jobs.is_some() { args.effective_jobs() } else { config.settings.jobs.max(1) },
+ jobs: if args.jobs.is_some() {
+ args.effective_jobs()
+ } else {
+ config.settings.jobs.max(1)
+ },
panel_rects: PanelRects::default(),
stream_rx: None,
stream_task: None,
@@ -421,7 +435,11 @@ impl App {
last_res_export_path: self.last_res_export_path.clone(),
top_tlds: self.top_tlds.clone().unwrap_or_default(),
jobs: self.jobs,
- noretry: self.noretry.iter().map(|k| k.to_config_str().to_string()).collect(),
+ noretry: self
+ .noretry
+ .iter()
+ .map(|k| k.to_config_str().to_string())
+ .collect(),
backups: self.backups_enabled,
backup_count: self.backup_count,
},
@@ -437,7 +455,9 @@ impl App {
let d = domain.to_lowercase();
if !self.favorites.iter().any(|f| f.domain == d) {
// check if we just looked this domain up - inherit its status
- let status = self.results.iter()
+ let status = self
+ .results
+ .iter()
.find(|(_, r)| r.full.to_lowercase() == d)
.map(|(_, r)| r.status_str().to_string())
.unwrap_or_else(|| "unknown".to_string());
@@ -477,7 +497,11 @@ impl App {
if self.show_unavailable {
self.results.iter().map(|(_, r)| r).collect()
} else {
- self.results.iter().filter(|(_, r)| r.is_available()).map(|(_, r)| r).collect()
+ self.results
+ .iter()
+ .filter(|(_, r)| r.is_available())
+ .map(|(_, r)| r)
+ .collect()
}
}
@@ -598,7 +622,16 @@ pub async fn run_tui(
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
- let mut app = App::new(args, config, paths.config_file.clone(), paths.can_save, cache_file, force_refresh, whois_overrides, noretry);
+ let mut app = App::new(
+ args,
+ config,
+ paths.config_file.clone(),
+ paths.can_save,
+ cache_file,
+ force_refresh,
+ whois_overrides,
+ noretry,
+ );
if !paths.can_save {
app.status_msg = Some("Warning: favorites and settings wont be saved".to_string());
@@ -608,10 +641,7 @@ pub async fn run_tui(
// put the terminal back to normal
if !args.no_mouse {
- execute!(
- terminal.backend_mut(),
- DisableMouseCapture
- )?;
+ execute!(terminal.backend_mut(), DisableMouseCapture)?;
while event::poll(std::time::Duration::from_millis(0))? {
let _ = event::read();
@@ -638,7 +668,10 @@ async fn run_app(
}
if let Some(popup) = app.export_popup.as_ref() {
- if popup.close_at.is_some_and(|deadline| Instant::now() >= deadline) {
+ if popup
+ .close_at
+ .is_some_and(|deadline| Instant::now() >= deadline)
+ {
app.export_popup = None;
}
}
@@ -693,7 +726,9 @@ async fn run_app(
Ok(lookup::StreamMsg::Result { result, .. }) => {
// Update the matching favorite's status
let domain_lower = result.full.to_lowercase();
- if let Some(fav) = app.favorites.iter_mut().find(|f| f.domain == domain_lower) {
+ if let Some(fav) =
+ app.favorites.iter_mut().find(|f| f.domain == domain_lower)
+ {
let new_status = result.status_str().to_string();
if fav.status != new_status && fav.status != "unknown" {
fav.changed = true;
@@ -786,7 +821,11 @@ async fn run_app(
app.dropdown = DropdownState::Closed;
app.set_focus(match app.focus {
Focus::Search => {
- if app.show_notes_panel { Focus::Scratchpad } else { Focus::Results }
+ if app.show_notes_panel {
+ Focus::Scratchpad
+ } else {
+ Focus::Results
+ }
}
Focus::Scratchpad => Focus::Results,
Focus::Results => Focus::Favorites,
@@ -800,7 +839,11 @@ async fn run_app(
Focus::Search => Focus::Settings,
Focus::Scratchpad => Focus::Search,
Focus::Results => {
- if app.show_notes_panel { Focus::Scratchpad } else { Focus::Search }
+ if app.show_notes_panel {
+ Focus::Scratchpad
+ } else {
+ Focus::Search
+ }
}
Focus::Favorites => Focus::Results,
Focus::Settings => Focus::Favorites,
@@ -901,7 +944,11 @@ fn handle_export_popup_key(app: &mut App, key: KeyCode) {
popup.selected_row = (popup.selected_row + 1) % 4;
}
KeyCode::BackTab | KeyCode::Up => {
- popup.selected_row = if popup.selected_row == 0 { 3 } else { popup.selected_row - 1 };
+ popup.selected_row = if popup.selected_row == 0 {
+ 3
+ } else {
+ popup.selected_row - 1
+ };
}
KeyCode::Left => {
if popup.selected_row == 0 {
@@ -1057,7 +1104,9 @@ async fn handle_search_key(app: &mut App, key: KeyCode) {
}
}
KeyCode::Delete => {
- if app.cursor_pos < app.search_input.len() && app.search_input.is_char_boundary(app.cursor_pos) {
+ if app.cursor_pos < app.search_input.len()
+ && app.search_input.is_char_boundary(app.cursor_pos)
+ {
app.search_input.remove(app.cursor_pos);
}
}
@@ -1100,7 +1149,11 @@ fn handle_results_key(app: &mut App, key: KeyCode) {
KeyCode::Up => {
let i = match app.results_state.selected() {
Some(i) => {
- if i > 0 { i - 1 } else { 0 }
+ if i > 0 {
+ i - 1
+ } else {
+ 0
+ }
}
None => 0,
};
@@ -1109,7 +1162,11 @@ fn handle_results_key(app: &mut App, key: KeyCode) {
KeyCode::Down => {
let i = match app.results_state.selected() {
Some(i) => {
- if i + 1 < len { i + 1 } else { i }
+ if i + 1 < len {
+ i + 1
+ } else {
+ i
+ }
}
None => 0,
};
@@ -1139,7 +1196,11 @@ fn handle_favorites_key(app: &mut App, key: KeyCode) {
KeyCode::Up => {
let i = match app.favorites_state.selected() {
Some(i) => {
- if i > 0 { i - 1 } else { 0 }
+ if i > 0 {
+ i - 1
+ } else {
+ 0
+ }
}
None => 0,
};
@@ -1148,7 +1209,11 @@ fn handle_favorites_key(app: &mut App, key: KeyCode) {
KeyCode::Down => {
let i = match app.favorites_state.selected() {
Some(i) => {
- if i + 1 < len { i + 1 } else { i }
+ if i + 1 < len {
+ i + 1
+ } else {
+ i
+ }
}
None => 0,
};
@@ -1257,26 +1322,24 @@ fn handle_settings_key(app: &mut App, key: KeyCode) {
_ => {}
}
}
- KeyCode::Char(' ') => {
- match app.settings_selected.unwrap_or(0) {
- 1 => {
- app.show_unavailable = !app.show_unavailable;
- app.save_config();
- }
- 2 => {
- app.show_notes_panel = !app.show_notes_panel;
- if !app.show_notes_panel && app.focus == Focus::Scratchpad {
- app.set_focus(Focus::Results);
- }
- app.save_config();
- }
- 3 => {
- app.clear_on_search = !app.clear_on_search;
- app.save_config();
+ KeyCode::Char(' ') => match app.settings_selected.unwrap_or(0) {
+ 1 => {
+ app.show_unavailable = !app.show_unavailable;
+ app.save_config();
+ }
+ 2 => {
+ app.show_notes_panel = !app.show_notes_panel;
+ if !app.show_notes_panel && app.focus == Focus::Scratchpad {
+ app.set_focus(Focus::Results);
}
- _ => {}
+ app.save_config();
}
- }
+ 3 => {
+ app.clear_on_search = !app.clear_on_search;
+ app.save_config();
+ }
+ _ => {}
+ },
KeyCode::Char('+') | KeyCode::Char('=') => {
if app.settings_selected == Some(4) {
app.jobs = if app.jobs >= 99 { 99 } else { app.jobs + 1 };
@@ -1502,7 +1565,11 @@ fn handle_mouse(app: &mut App, mouse: MouseEvent) {
app.set_focus(Focus::Results);
let visible_len = app.visible_results().len();
let content_start = results_rect.y + 1;
- let progress_offset = if app.searching && app.search_progress.1 > 0 { 1 } else { 0 };
+ let progress_offset = if app.searching && app.search_progress.1 > 0 {
+ 1
+ } else {
+ 0
+ };
let header_offset = if visible_len > 0 { 1 } else { 0 };
let list_start = content_start + progress_offset + header_offset;
@@ -1653,14 +1720,18 @@ fn start_fav_check(app: &mut App) {
app.checking_favorites = true;
// Build a batch: each favorite is "name.tld" -> lookup (name, [tld])
- let batches: lookup::LookupBatch = app.favorites.iter().filter_map(|fav| {
- let parts: Vec<&str> = fav.domain.splitn(2, '.').collect();
- if parts.len() == 2 {
- Some((parts[0].to_string(), vec![parts[1].to_string()]))
- } else {
- None
- }
- }).collect();
+ let batches: lookup::LookupBatch = app
+ .favorites
+ .iter()
+ .filter_map(|fav| {
+ let parts: Vec<&str> = fav.domain.splitn(2, '.').collect();
+ if parts.len() == 2 {
+ Some((parts[0].to_string(), vec![parts[1].to_string()]))
+ } else {
+ None
+ }
+ })
+ .collect();
if batches.is_empty() {
app.checking_favorites = false;
@@ -1793,7 +1864,10 @@ fn draw_ui(f: &mut Frame, app: &mut App) {
.split(size);
let content_area = main_chunks[1];
- let desired_sidebar = content_area.width.saturating_mul(SIDEBAR_TARGET_WIDTH_PERCENT) / 100;
+ let desired_sidebar = content_area
+ .width
+ .saturating_mul(SIDEBAR_TARGET_WIDTH_PERCENT)
+ / 100;
let mut sidebar_width = clamp_panel_size(desired_sidebar, SIDEBAR_MIN_WIDTH, SIDEBAR_MAX_WIDTH)
.min(content_area.width.saturating_sub(RESULTS_MIN_WIDTH));
if sidebar_width == 0 {
@@ -1809,7 +1883,10 @@ fn draw_ui(f: &mut Frame, app: &mut App) {
let (scratchpad_chunk, results_chunk) = if app.show_notes_panel {
let center_width = content_area.width.saturating_sub(sidebar_width);
- let desired_scratchpad = content_area.width.saturating_mul(SCRATCHPAD_TARGET_WIDTH_PERCENT) / 100;
+ let desired_scratchpad = content_area
+ .width
+ .saturating_mul(SCRATCHPAD_TARGET_WIDTH_PERCENT)
+ / 100;
let mut scratchpad_width = clamp_panel_size(
desired_scratchpad,
SCRATCHPAD_MIN_WIDTH,
@@ -1941,11 +2018,15 @@ fn draw_terminal_too_small(f: &mut Frame, area: Rect) {
let text = vec![
Line::from(Span::styled(
fit_cell_center("HELP ! HELP ! HELP !", content_width),
- Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
+ Style::default()
+ .fg(Color::White)
+ .add_modifier(Modifier::BOLD),
)),
Line::from(Span::styled(
fit_cell_center("I AM BEING CRUSHED!", content_width),
- Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
+ Style::default()
+ .fg(Color::White)
+ .add_modifier(Modifier::BOLD),
)),
Line::from(fit_cell_center("", content_width)),
Line::from(Span::styled(
@@ -1953,21 +2034,31 @@ fn draw_terminal_too_small(f: &mut Frame, area: Rect) {
Style::default().fg(Color::White),
)),
Line::from(Span::styled(
- fit_cell_center(&format!("Need {}x{} of space", MIN_UI_WIDTH, MIN_UI_HEIGHT), content_width),
+ fit_cell_center(
+ &format!("Need {}x{} of space", MIN_UI_WIDTH, MIN_UI_HEIGHT),
+ content_width,
+ ),
Style::default().fg(Color::White),
)),
Line::from(Span::styled(
- fit_cell_center(&format!("Current: {}x{}", area.width, area.height), content_width),
+ fit_cell_center(
+ &format!("Current: {}x{}", area.width, area.height),
+ content_width,
+ ),
Style::default().fg(Color::DarkGray),
)),
Line::from(fit_cell_center("", content_width)),
Line::from(Span::styled(
fit_cell_center("REFUSING TO WORK TILL YOU", content_width),
- Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
+ Style::default()
+ .fg(Color::White)
+ .add_modifier(Modifier::BOLD),
)),
Line::from(Span::styled(
fit_cell_center("GIVE ME BACK MY SPACE! >:(", content_width),
- Style::default().fg(Color::White).add_modifier(Modifier::BOLD),
+ Style::default()
+ .fg(Color::White)
+ .add_modifier(Modifier::BOLD),
)),
];
@@ -1981,14 +2072,49 @@ fn draw_topbar(f: &mut Frame, area: Rect) {
let right = format!("{} {}", EXPORT_BUTTON_LABEL, HELP_BUTTON_LABEL);
let gap = width.saturating_sub(left.chars().count() + right.chars().count());
let paragraph = Paragraph::new(Line::from(vec![
- Span::styled(CLOSE_BUTTON_LABEL, Style::default().fg(Color::Red).bg(Color::Gray).add_modifier(Modifier::BOLD)),
- Span::styled(format!(" {}", title), Style::default().fg(Color::White).bg(Color::Red).add_modifier(Modifier::BOLD)),
- Span::styled(" ".repeat(gap), Style::default().bg(Color::Red).add_modifier(Modifier::BOLD)),
- Span::styled(EXPORT_BUTTON_LABEL, Style::default().fg(Color::LightGreen).bg(Color::Red).add_modifier(Modifier::BOLD)),
- Span::styled(" ", Style::default().bg(Color::Red).add_modifier(Modifier::BOLD)),
- Span::styled(HELP_BUTTON_LABEL, Style::default().fg(Color::LightGreen).bg(Color::Red).add_modifier(Modifier::BOLD)),
+ Span::styled(
+ CLOSE_BUTTON_LABEL,
+ Style::default()
+ .fg(Color::Red)
+ .bg(Color::Gray)
+ .add_modifier(Modifier::BOLD),
+ ),
+ Span::styled(
+ format!(" {}", title),
+ Style::default()
+ .fg(Color::White)
+ .bg(Color::Red)
+ .add_modifier(Modifier::BOLD),
+ ),
+ Span::styled(
+ " ".repeat(gap),
+ Style::default().bg(Color::Red).add_modifier(Modifier::BOLD),
+ ),
+ Span::styled(
+ EXPORT_BUTTON_LABEL,
+ Style::default()
+ .fg(Color::LightGreen)
+ .bg(Color::Red)
+ .add_modifier(Modifier::BOLD),
+ ),
+ Span::styled(
+ " ",
+ Style::default().bg(Color::Red).add_modifier(Modifier::BOLD),
+ ),
+ Span::styled(
+ HELP_BUTTON_LABEL,
+ Style::default()
+ .fg(Color::LightGreen)
+ .bg(Color::Red)
+ .add_modifier(Modifier::BOLD),
+ ),
]))
- .style(Style::default().fg(Color::White).bg(Color::Red).add_modifier(Modifier::BOLD));
+ .style(
+ Style::default()
+ .fg(Color::White)
+ .bg(Color::Red)
+ .add_modifier(Modifier::BOLD),
+ );
f.render_widget(paragraph, area);
}
@@ -2000,22 +2126,60 @@ fn draw_help_overlay(f: &mut Frame, app: &mut App, area: Rect) {
let text = vec![
Line::from(Span::styled(" ", Style::default().fg(Color::White))),
Line::from(Span::styled("Global :", Style::default().fg(Color::White))),
- Line::from(Span::styled("F1 or Help button Toggle this help", Style::default().fg(Color::White))),
- Line::from(Span::styled("F2 or Export button Open export popup", Style::default().fg(Color::White))),
- Line::from(Span::styled("Ctrl+C Quit the app", Style::default().fg(Color::White))),
- Line::from(Span::styled("s Stop/cancel running search", Style::default().fg(Color::White))),
- Line::from(Span::styled("Esc Clear selection or close help", Style::default().fg(Color::White))),
- Line::from(Span::styled("Tab or Shift+Tab Move between panels", Style::default().fg(Color::White))),
- Line::from(Span::styled("Up and Down arrows Navigate results", Style::default().fg(Color::White))),
+ Line::from(Span::styled(
+ "F1 or Help button Toggle this help",
+ Style::default().fg(Color::White),
+ )),
+ Line::from(Span::styled(
+ "F2 or Export button Open export popup",
+ Style::default().fg(Color::White),
+ )),
+ Line::from(Span::styled(
+ "Ctrl+C Quit the app",
+ Style::default().fg(Color::White),
+ )),
+ Line::from(Span::styled(
+ "s Stop/cancel running search",
+ Style::default().fg(Color::White),
+ )),
+ Line::from(Span::styled(
+ "Esc Clear selection or close help",
+ Style::default().fg(Color::White),
+ )),
+ Line::from(Span::styled(
+ "Tab or Shift+Tab Move between panels",
+ Style::default().fg(Color::White),
+ )),
+ Line::from(Span::styled(
+ "Up and Down arrows Navigate results",
+ Style::default().fg(Color::White),
+ )),
Line::from(Span::styled(" ", Style::default().fg(Color::White))),
- Line::from(Span::styled("Mouse Click Elements duh", Style::default().fg(Color::White))),
- Line::from(Span::styled("Scrolling Scroll through elements (yea)", Style::default().fg(Color::White))),
-
+ Line::from(Span::styled(
+ "Mouse Click Elements duh",
+ Style::default().fg(Color::White),
+ )),
+ Line::from(Span::styled(
+ "Scrolling Scroll through elements (yea)",
+ Style::default().fg(Color::White),
+ )),
Line::from(Span::styled(" ", Style::default().fg(Color::White))),
- Line::from(Span::styled("In Results :", Style::default().fg(Color::White))),
- Line::from(Span::styled("Enter Add highlighted result to Favorites", Style::default().fg(Color::White))),
- Line::from(Span::styled("In Favorites :", Style::default().fg(Color::White))),
- Line::from(Span::styled("Backspace or Delete Remove focused favorite", Style::default().fg(Color::White))),
+ Line::from(Span::styled(
+ "In Results :",
+ Style::default().fg(Color::White),
+ )),
+ Line::from(Span::styled(
+ "Enter Add highlighted result to Favorites",
+ Style::default().fg(Color::White),
+ )),
+ Line::from(Span::styled(
+ "In Favorites :",
+ Style::default().fg(Color::White),
+ )),
+ Line::from(Span::styled(
+ "Backspace or Delete Remove focused favorite",
+ Style::default().fg(Color::White),
+ )),
];
let block = Block::default()
@@ -2066,7 +2230,10 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) {
style
};
- let subtitle = fit_cell_center("Choose what to export and where to save it.", chunks[0].width as usize);
+ let subtitle = fit_cell_center(
+ "Choose what to export and where to save it.",
+ chunks[0].width as usize,
+ );
f.render_widget(
Paragraph::new(subtitle).style(Style::default().fg(Color::DarkGray)),
chunks[0],
@@ -2118,9 +2285,13 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) {
);
let status_style = if popup_state.status_success {
- Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)
+ Style::default()
+ .fg(Color::Green)
+ .add_modifier(Modifier::BOLD)
} else if popup_state.confirm_overwrite {
- Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
+ Style::default()
+ .fg(Color::Yellow)
+ .add_modifier(Modifier::BOLD)
} else if popup_state.status.is_some() {
Style::default().fg(Color::Red)
} else {
@@ -2132,7 +2303,6 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) {
chunks[3],
);
-
let cancel_label = "[Cancel]";
let button_gap = " ";
let save_label = "[Save]";
@@ -2158,18 +2328,28 @@ fn draw_export_popup(f: &mut Frame, app: &mut App, area: Rect) {
Span::styled(
cancel_label,
if popup_state.selected_row == 2 {
- Style::default().fg(Color::Green).bg(Color::DarkGray).add_modifier(Modifier::BOLD)
+ Style::default()
+ .fg(Color::Green)
+ .bg(Color::DarkGray)
+ .add_modifier(Modifier::BOLD)
} else {
- Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)
+ Style::default()
+ .fg(Color::Green)
+ .add_modifier(Modifier::BOLD)
},
),
Span::raw(button_gap),
Span::styled(
save_label,
if popup_state.selected_row == 3 {
- Style::default().fg(Color::Green).bg(Color::DarkGray).add_modifier(Modifier::BOLD)
+ Style::default()
+ .fg(Color::Green)
+ .bg(Color::DarkGray)
+ .add_modifier(Modifier::BOLD)
} else {
- Style::default().fg(Color::Green).add_modifier(Modifier::BOLD)
+ Style::default()
+ .fg(Color::Green)
+ .add_modifier(Modifier::BOLD)
},
),
]);
@@ -2207,7 +2387,12 @@ fn draw_results(f: &mut Frame, app: &mut App, area: Rect) {
Some(d) => format!(" | Took: {:.1}s", d.as_secs_f64()),
None => String::new(),
};
- format!(" Results ({} available / {} total{}) ", avail, app.results.len(), duration_str)
+ format!(
+ " Results ({} available / {} total{}) ",
+ avail,
+ app.results.len(),
+ duration_str
+ )
};
let block = Block::default()
@@ -2251,13 +2436,34 @@ fn draw_results(f: &mut Frame, app: &mut App, area: Rect) {
fn draw_results_list(f: &mut Frame, app: &mut App, area: Rect) {
let show_note_column = app.show_unavailable;
let selected_idx = app.results_state.selected();
- let selected_bg = Color::Black;
+ let selected_bg = Color::Black;
// collect visible results
let visible_data: Vec<(String, String, String, DomainStatus)> = if app.show_unavailable {
- app.results.iter().map(|(_, r)| (r.full.clone(), r.status_str().to_string(), r.note_str(), r.status.clone())).collect()
+ app.results
+ .iter()
+ .map(|(_, r)| {
+ (
+ r.full.clone(),
+ r.status_str().to_string(),
+ r.note_str(),
+ r.status.clone(),
+ )
+ })
+ .collect()
} else {
- app.results.iter().filter(|(_, r)| r.is_available()).map(|(_, r)| (r.full.clone(), r.status_str().to_string(), r.note_str(), r.status.clone())).collect()
+ app.results
+ .iter()
+ .filter(|(_, r)| r.is_available())
+ .map(|(_, r)| {
+ (
+ r.full.clone(),
+ r.status_str().to_string(),
+ r.note_str(),
+ r.status.clone(),
+ )
+ })
+ .collect()
};
if visible_data.is_empty() && !app.searching {
@@ -2311,18 +2517,38 @@ fn draw_results_list(f: &mut Frame, app: &mut App, area: Rect) {
if let Some(header_area) = header_area {
let mut header_spans = vec![
- Span::styled(format!(" {}", fit_cell("Domain", domain_w)), Style::default().fg(Color::Gray).add_modifier(Modifier::BOLD)),
+ Span::styled(
+ format!(" {}", fit_cell("Domain", domain_w)),
+ Style::default()
+ .fg(Color::Gray)
+ .add_modifier(Modifier::BOLD),
+ ),
Span::styled(" │ ", Style::default().fg(Color::DarkGray)),
- Span::styled(fit_cell("Status", status_w), Style::default().fg(Color::Gray).add_modifier(Modifier::BOLD)),
+ Span::styled(
+ fit_cell("Status", status_w),
+ Style::default()
+ .fg(Color::Gray)
+ .add_modifier(Modifier::BOLD),
+ ),
];
if show_note_column {
header_spans.push(Span::styled(" │ ", Style::default().fg(Color::DarkGray)));
- header_spans.push(Span::styled(fit_cell("Details", note_w), Style::default().fg(Color::Gray).add_modifier(Modifier::BOLD)));
+ header_spans.push(Span::styled(
+ fit_cell("Details", note_w),
+ Style::default()
+ .fg(Color::Gray)
+ .add_modifier(Modifier::BOLD),
+ ));
}
header_spans.push(Span::styled(" │ ", Style::default().fg(Color::DarkGray)));
- header_spans.push(Span::styled(" ✓ ", Style::default().fg(Color::Gray).add_modifier(Modifier::BOLD)));
+ header_spans.push(Span::styled(
+ " ✓ ",
+ Style::default()
+ .fg(Color::Gray)
+ .add_modifier(Modifier::BOLD),
+ ));
f.render_widget(Paragraph::new(Line::from(header_spans)), header_area);
}
@@ -2361,22 +2587,40 @@ fn draw_results_list(f: &mut Frame, app: &mut App, area: Rect) {
};
let mut spans = vec![
- Span::styled(format!(" {}", fit_cell(full, domain_w)), apply_bg(domain_style)),
+ Span::styled(
+ format!(" {}", fit_cell(full, domain_w)),
+ apply_bg(domain_style),
+ ),
Span::styled(" \u{2502} ", apply_bg(Style::default().fg(Color::Gray))),
Span::styled(fit_cell(status_str, status_w), apply_bg(status_style)),
];
if show_note_column {
- spans.push(Span::styled(" \u{2502} ", apply_bg(Style::default().fg(Color::Gray))));
- spans.push(Span::styled(fit_cell(note, note_w), apply_bg(Style::default().fg(Color::White))));
+ spans.push(Span::styled(
+ " \u{2502} ",
+ apply_bg(Style::default().fg(Color::Gray)),
+ ));
+ spans.push(Span::styled(
+ fit_cell(note, note_w),
+ apply_bg(Style::default().fg(Color::White)),
+ ));
}
- spans.push(Span::styled(" \u{2502} ", apply_bg(Style::default().fg(Color::Gray))));
+ spans.push(Span::styled(
+ " \u{2502} ",
+ apply_bg(Style::default().fg(Color::Gray)),
+ ));
spans.push(match status {
- DomainStatus::Available => Span::styled(" ✓ ", apply_bg(Style::default().fg(Color::Green))),
- DomainStatus::Registered { .. } => Span::styled(" ✗ ", apply_bg(Style::default().fg(Color::Red))),
+ DomainStatus::Available => {
+ Span::styled(" ✓ ", apply_bg(Style::default().fg(Color::Green)))
+ }
+ DomainStatus::Registered { .. } => {
+ Span::styled(" ✗ ", apply_bg(Style::default().fg(Color::Red)))
+ }
DomainStatus::Error { kind, .. } => match kind {
- ErrorKind::InvalidTld => Span::styled(" ? ", apply_bg(Style::default().fg(Color::Yellow))),
+ ErrorKind::InvalidTld => {
+ Span::styled(" ? ", apply_bg(Style::default().fg(Color::Yellow)))
+ }
_ => Span::styled(" ! ", apply_bg(Style::default().fg(Color::Blue))),
},
});
@@ -2553,8 +2797,7 @@ fn draw_scratchpad(f: &mut Frame, app: &mut App, area: Rect) {
};
f.render_widget(block, area);
f.render_widget(
- Paragraph::new(text)
- .style(Style::default().fg(Color::White)),
+ Paragraph::new(text).style(Style::default().fg(Color::White)),
inner,
);
@@ -2623,17 +2866,17 @@ fn draw_favorites(f: &mut Frame, app: &mut App, area: Rect) {
})
.collect();
- let list = List::new(items)
- .highlight_style(
- Style::default()
- .add_modifier(Modifier::REVERSED),
- );
+ let list = List::new(items).highlight_style(Style::default().add_modifier(Modifier::REVERSED));
f.render_widget(block, area);
f.render_stateful_widget(list, list_area, &mut app.favorites_state);
// Draw the check button at the bottom
- let btn_label = if app.checking_favorites { "checking..." } else { "[c]heck all" };
+ let btn_label = if app.checking_favorites {
+ "checking..."
+ } else {
+ "[c]heck all"
+ };
let btn_style = if app.checking_favorites {
Style::default().fg(Color::DarkGray)
} else {
@@ -2688,13 +2931,17 @@ fn draw_settings(f: &mut Frame, app: &mut App, area: Rect) {
};
let tld_row_style = if selected == Some(0) {
- Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD)
+ Style::default()
+ .bg(Color::DarkGray)
+ .add_modifier(Modifier::BOLD)
} else {
Style::default()
};
let jobs_row_style = if selected == Some(4) {
- Style::default().bg(Color::DarkGray).add_modifier(Modifier::BOLD)
+ Style::default()
+ .bg(Color::DarkGray)
+ .add_modifier(Modifier::BOLD)
} else {
Style::default()
};
@@ -2801,23 +3048,44 @@ fn draw_search(f: &mut Frame, app: &mut App, area: Rect) {
let cancel_enabled = app.searching;
let search_style = if search_enabled {
- Style::default().fg(Color::Black).bg(Color::Green).add_modifier(Modifier::BOLD)
+ Style::default()
+ .fg(Color::Black)
+ .bg(Color::Green)
+ .add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::DarkGray).bg(Color::Black)
};
let stop_style = if cancel_enabled {
- Style::default().fg(Color::Black).bg(Color::Yellow).add_modifier(Modifier::BOLD)
+ Style::default()
+ .fg(Color::Black)
+ .bg(Color::Yellow)
+ .add_modifier(Modifier::BOLD)
} else {
Style::default().fg(Color::DarkGray).bg(Color::Black)
};
- let clear_style = Style::default().fg(Color::White).bg(Color::Red).add_modifier(Modifier::BOLD);
+ let clear_style = Style::default()
+ .fg(Color::White)
+ .bg(Color::Red)
+ .add_modifier(Modifier::BOLD);
- f.render_widget(Paragraph::new(SEARCH_BUTTON_LABEL).style(search_style), chunks[1]);
+ f.render_widget(
+ Paragraph::new(SEARCH_BUTTON_LABEL).style(search_style),
+ chunks[1],
+ );
if app.clear_on_search {
- f.render_widget(Paragraph::new(STOP_BUTTON_LABEL).style(stop_style), chunks[3]);
+ f.render_widget(
+ Paragraph::new(STOP_BUTTON_LABEL).style(stop_style),
+ chunks[3],
+ );
} else {
- f.render_widget(Paragraph::new(STOP_BUTTON_LABEL).style(stop_style), chunks[3]);
- f.render_widget(Paragraph::new(CLEAR_BUTTON_LABEL).style(clear_style), chunks[5]);
+ f.render_widget(
+ Paragraph::new(STOP_BUTTON_LABEL).style(stop_style),
+ chunks[3],
+ );
+ f.render_widget(
+ Paragraph::new(CLEAR_BUTTON_LABEL).style(clear_style),
+ chunks[5],
+ );
}
// show cursor in search bar when focused
@@ -2836,7 +3104,10 @@ fn draw_dropdown(f: &mut Frame, app: &mut App, settings_area: Rect, selected: us
let dropdown_full = Rect {
x: settings_area.x + 1,
y: settings_area.y + 1,
- width: settings_area.width.saturating_sub(2).min(DROPDOWN_MAX_WIDTH),
+ width: settings_area
+ .width
+ .saturating_sub(2)
+ .min(DROPDOWN_MAX_WIDTH),
height: (options.len() as u16 + 2).min(DROPDOWN_MAX_HEIGHT),
};
@@ -2861,9 +3132,12 @@ fn draw_dropdown(f: &mut Frame, app: &mut App, settings_area: Rect, selected: us
.title(" TLD List ");
f.render_widget(Clear, dropdown_full);
- let list = List::new(items)
- .block(block)
- .highlight_style(Style::default().fg(Color::White).bg(Color::Red).add_modifier(Modifier::BOLD));
+ let list = List::new(items).block(block).highlight_style(
+ Style::default()
+ .fg(Color::White)
+ .bg(Color::Red)
+ .add_modifier(Modifier::BOLD),
+ );
let mut state = ListState::default();
state.select(Some(selected));
f.render_stateful_widget(list, dropdown_full, &mut state);
diff --git a/src/types.rs b/src/types.rs
index 9a496c2..cc3950b 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -89,5 +89,3 @@ impl DomainResult {
}
}
}
-
-