pass clippy
This commit is contained in:
+216
-121
@@ -1,18 +1,19 @@
|
|||||||
#![allow(clippy::struct_excessive_bools)]
|
#![allow(clippy::struct_excessive_bools)]
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor::MoveTo,
|
event::{self, Event, KeyCode, KeyEventKind, KeyModifiers},
|
||||||
event::{
|
|
||||||
self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers,
|
|
||||||
},
|
|
||||||
execute,
|
execute,
|
||||||
style::{
|
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||||
Color, Print, ResetColor, SetBackgroundColor, SetForegroundColor, Stylize,
|
|
||||||
},
|
|
||||||
terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType},
|
|
||||||
};
|
};
|
||||||
use std::io::{self, Write};
|
use ratatui::{
|
||||||
use std::time::Instant;
|
backend::CrosstermBackend,
|
||||||
|
layout::{Alignment, Constraint, Direction, Layout},
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
text::{Line, Span},
|
||||||
|
widgets::{Block, Paragraph},
|
||||||
|
Frame, Terminal,
|
||||||
|
};
|
||||||
|
use std::io;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TuiConfig {
|
pub struct TuiConfig {
|
||||||
@@ -50,35 +51,27 @@ impl TuiConfig {
|
|||||||
if !self.output_dir.is_empty() {
|
if !self.output_dir.is_empty() {
|
||||||
args.extend(["-o".to_string(), self.output_dir.clone()]);
|
args.extend(["-o".to_string(), self.output_dir.clone()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.subdomains {
|
if self.subdomains {
|
||||||
args.push("--subdomains".to_string());
|
args.push("--subdomains".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.single {
|
if self.single {
|
||||||
args.push("--single".to_string());
|
args.push("--single".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.no_scope {
|
if self.no_scope {
|
||||||
args.push("--no-scope".to_string());
|
args.push("--no-scope".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.no_doc_conversion {
|
if self.no_doc_conversion {
|
||||||
args.push("--no-doc-conversion".to_string());
|
args.push("--no-doc-conversion".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.delay_ms != 1000 {
|
if self.delay_ms != 1000 {
|
||||||
args.extend(["--delay-ms".to_string(), self.delay_ms.to_string()]);
|
args.extend(["--delay-ms".to_string(), self.delay_ms.to_string()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref t) = self.types {
|
if let Some(ref t) = self.types {
|
||||||
args.extend(["--types".to_string(), t.clone()]);
|
args.extend(["--types".to_string(), t.clone()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref t) = self.exclude_types {
|
if let Some(ref t) = self.exclude_types {
|
||||||
args.extend(["--exclude-types".to_string(), t.clone()]);
|
args.extend(["--exclude-types".to_string(), t.clone()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
args
|
args
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,24 +85,16 @@ fn validate_comma_list(input: &str) -> Result<(), String> {
|
|||||||
if trimmed.is_empty() {
|
if trimmed.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
let items: Vec<&str> = trimmed.split(',').map(str::trim).filter(|s| !s.is_empty()).collect();
|
||||||
let items: Vec<&str> = trimmed
|
|
||||||
.split(',')
|
|
||||||
.map(str::trim)
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
return Err("List cannot be empty".to_string());
|
return Err("List cannot be empty".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
for item in &items {
|
for item in &items {
|
||||||
let check = item.strip_prefix('.').unwrap_or(item);
|
let check = item.strip_prefix('.').unwrap_or(item);
|
||||||
if check.is_empty() || !check.chars().all(char::is_alphanumeric) {
|
if check.is_empty() || !check.chars().all(char::is_alphanumeric) {
|
||||||
return Err(format!("Invalid item '{item}': use extensions like 'pdf' or '.pdf'"));
|
return Err(format!("Invalid item '{item}': use extensions like 'pdf' or '.pdf'"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +193,14 @@ impl FieldIndex {
|
|||||||
| FieldIndex::NoDocConversion
|
| FieldIndex::NoDocConversion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn subtitle(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
FieldIndex::DelayMs => "[enter positive integer]",
|
||||||
|
FieldIndex::TypesInclude | FieldIndex::TypesExclude => "[comma-separated, dots optional]",
|
||||||
|
_ => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALL_FIELDS: [FieldIndex; 10] = [
|
const ALL_FIELDS: [FieldIndex; 10] = [
|
||||||
@@ -223,108 +216,191 @@ const ALL_FIELDS: [FieldIndex; 10] = [
|
|||||||
FieldIndex::Submit,
|
FieldIndex::Submit,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
enum Action {
|
||||||
|
Continue,
|
||||||
|
Cancel,
|
||||||
|
Submit,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_tui() -> io::Result<Option<TuiConfig>> {
|
pub fn run_tui() -> io::Result<Option<TuiConfig>> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let mut stdout = io::stdout();
|
let mut stdout = io::stdout();
|
||||||
execute!(stdout, EnableMouseCapture)?;
|
execute!(stdout, EnterAlternateScreen)?;
|
||||||
|
let backend = CrosstermBackend::new(stdout);
|
||||||
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
let mut config = TuiConfig::default();
|
let mut config = TuiConfig::default();
|
||||||
let mut current_field = FieldIndex::StartUrl;
|
let mut current_field = FieldIndex::StartUrl;
|
||||||
let mut text_input = String::new();
|
let mut text_input = String::new();
|
||||||
let mut errors: Vec<(FieldIndex, String)> = Vec::new();
|
let mut errors: Vec<(FieldIndex, String)> = Vec::new();
|
||||||
let mut last_render = Instant::now();
|
|
||||||
|
|
||||||
load_field_into_input(&mut text_input, &config, current_field);
|
load_field_into_input(&mut text_input, &config, current_field);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if last_render.elapsed().as_millis() > 50 {
|
terminal.draw(|frame| ui(frame, &config, current_field, &errors))?;
|
||||||
render_ui(&mut stdout, &config, current_field, &errors)?;
|
|
||||||
last_render = Instant::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
if event::poll(std::time::Duration::from_millis(50))?
|
if event::poll(std::time::Duration::from_millis(50))?
|
||||||
&& let Event::Key(key) = event::read()?
|
&& let Event::Key(key) = event::read()?
|
||||||
&& key.kind == KeyEventKind::Press
|
&& key.kind == KeyEventKind::Press
|
||||||
{
|
{
|
||||||
handle_key_event(
|
let action = handle_key_event(
|
||||||
&mut config,
|
&mut config,
|
||||||
&mut current_field,
|
&mut current_field,
|
||||||
&mut text_input,
|
&mut text_input,
|
||||||
&mut errors,
|
&mut errors,
|
||||||
key.code,
|
key.code,
|
||||||
key.modifiers,
|
key.modifiers,
|
||||||
)?;
|
);
|
||||||
|
match action {
|
||||||
|
Action::Continue => {}
|
||||||
|
Action::Cancel => {
|
||||||
|
disable_raw_mode()?;
|
||||||
|
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||||
|
terminal.show_cursor()?;
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Action::Submit => {
|
||||||
|
disable_raw_mode()?;
|
||||||
|
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
||||||
|
terminal.show_cursor()?;
|
||||||
|
return Ok(Some(config));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_ui(stdout: &mut io::Stdout, config: &TuiConfig, current_field: FieldIndex, errors: &[(FieldIndex, String)]) -> io::Result<()> {
|
#[allow(clippy::too_many_lines)]
|
||||||
execute!(stdout, Clear(ClearType::All), MoveTo(0, 0))?;
|
fn ui(frame: &mut Frame, config: &TuiConfig, current_field: FieldIndex, errors: &[(FieldIndex, String)]) {
|
||||||
|
let title_line = Line::from(Span::styled(
|
||||||
|
"Interactive Web Scraper Configuration",
|
||||||
|
Style::default().fg(Color::LightCyan).add_modifier(Modifier::BOLD),
|
||||||
|
));
|
||||||
|
|
||||||
writeln!(stdout, "{}", "Interactive Web Scraper Configuration".cyan().bold())?;
|
let controls_lines: Vec<Line> = vec![
|
||||||
writeln!(stdout, "{}", "=".repeat(60).blue())?;
|
Line::from(Span::styled("Controls:", Style::default().fg(Color::DarkGray))),
|
||||||
writeln!(stdout)?;
|
Line::from(" Enter : Next field / Toggle boolean / Submit"),
|
||||||
|
Line::from(" Tab : Next field"),
|
||||||
for &field in &ALL_FIELDS {
|
Line::from(" Shift+Tab: Previous field"),
|
||||||
let marker = if current_field == field { ">" } else { " " };
|
Line::from(" Ctrl+C : Exit without running"),
|
||||||
let value_text = get_field_display(config, field);
|
Line::from(" Esc : Exit without running"),
|
||||||
let hint = field.hint();
|
];
|
||||||
|
|
||||||
if current_field == field {
|
|
||||||
execute!(
|
|
||||||
stdout,
|
|
||||||
SetBackgroundColor(Color::DarkGrey),
|
|
||||||
Print(format!("{marker} {:30}: ", field.label())),
|
|
||||||
ResetColor
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
write!(stdout, "{marker} {:30}: ", field.label())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !value_text.is_empty() {
|
|
||||||
write!(stdout, "{value_text}")?;
|
|
||||||
} else if !hint.is_empty() {
|
|
||||||
write!(stdout, "{}", hint.dark_grey())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((_, err)) = errors.iter().find(|(f, _)| *f == field) {
|
|
||||||
execute!(
|
|
||||||
stdout,
|
|
||||||
SetForegroundColor(Color::Red),
|
|
||||||
Print(format!(" [err: {err}]")),
|
|
||||||
ResetColor
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(stdout)?;
|
|
||||||
|
|
||||||
if field.is_boolean_field() {
|
|
||||||
let enabled = get_bool_field(config, field);
|
|
||||||
writeln!(stdout, " [{}]",
|
|
||||||
if enabled { "enabled" } else { "disabled" })?;
|
|
||||||
} else if field == FieldIndex::DelayMs {
|
|
||||||
writeln!(stdout, " [enter positive integer]")?;
|
|
||||||
} else if field == FieldIndex::TypesInclude || field == FieldIndex::TypesExclude {
|
|
||||||
writeln!(stdout, " [comma-separated, dots optional e.g. .pdf,xlsx]")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(stdout)?;
|
|
||||||
writeln!(stdout, "{}", "Controls:".dark_grey())?;
|
|
||||||
writeln!(stdout, " Enter : Next field / Toggle boolean / Submit")?;
|
|
||||||
writeln!(stdout, " Tab : Next field")?;
|
|
||||||
writeln!(stdout, " Shift+Tab: Previous field")?;
|
|
||||||
writeln!(stdout, " Ctrl+C : Exit without running")?;
|
|
||||||
writeln!(stdout, " Esc : Exit without running")?;
|
|
||||||
|
|
||||||
let cmd_preview = config.build_command();
|
let cmd_preview = config.build_command();
|
||||||
writeln!(stdout)?;
|
let cmd_lines: Vec<Line> = vec![
|
||||||
writeln!(stdout, "{}", "Command preview:".yellow())?;
|
Line::from(Span::styled(
|
||||||
writeln!(stdout, " {cmd_preview}", cmd_preview = cmd_preview.green())?;
|
"Command preview:",
|
||||||
|
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
|
||||||
|
)),
|
||||||
|
Line::from(Span::styled(format!(" {cmd_preview}"), Style::default().fg(Color::Yellow))),
|
||||||
|
];
|
||||||
|
|
||||||
stdout.flush()?;
|
let status_text = if config.start_url.is_empty() {
|
||||||
|
"Waiting for Start URL..."
|
||||||
|
} else {
|
||||||
|
"Ready. Press Enter on Submit to run."
|
||||||
|
};
|
||||||
|
let status_style = if config.start_url.is_empty() {
|
||||||
|
Style::default().fg(Color::Yellow)
|
||||||
|
} else {
|
||||||
|
Style::default().fg(Color::Green)
|
||||||
|
};
|
||||||
|
let status_line = Line::from(Span::styled(status_text, status_style));
|
||||||
|
|
||||||
Ok(())
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.margin(1)
|
||||||
|
.constraints([
|
||||||
|
Constraint::Length(1),
|
||||||
|
Constraint::Min(10),
|
||||||
|
Constraint::Length(6),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Length(1),
|
||||||
|
])
|
||||||
|
.split(frame.area());
|
||||||
|
|
||||||
|
frame.render_widget(Paragraph::new(title_line), chunks[0]);
|
||||||
|
|
||||||
|
let field_chunks = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(std::iter::repeat_n(Constraint::Length(2), ALL_FIELDS.len()).collect::<Vec<_>>())
|
||||||
|
.split(chunks[1]);
|
||||||
|
|
||||||
|
for (i, &field) in ALL_FIELDS.iter().enumerate() {
|
||||||
|
render_field(frame, field_chunks[i], config, current_field, field, errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.render_widget(Paragraph::new(controls_lines), chunks[2]);
|
||||||
|
frame.render_widget(Paragraph::new(cmd_lines), chunks[3]);
|
||||||
|
|
||||||
|
let error_line = if let Some((_, err)) = errors.iter().find(|(f, _)| *f == FieldIndex::Submit) {
|
||||||
|
Line::from(Span::styled(format!("[ERR: {err}]"), Style::default().fg(Color::Red)))
|
||||||
|
} else {
|
||||||
|
status_line
|
||||||
|
};
|
||||||
|
frame.render_widget(
|
||||||
|
Paragraph::new(error_line).alignment(Alignment::Center),
|
||||||
|
chunks[4],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_field(frame: &mut Frame, area: ratatui::layout::Rect, config: &TuiConfig, current_field: FieldIndex, field: FieldIndex, errors: &[(FieldIndex, String)]) {
|
||||||
|
let value_text = get_field_display(config, field);
|
||||||
|
let hint = field.hint();
|
||||||
|
let is_active = current_field == field;
|
||||||
|
|
||||||
|
let display_value = if !value_text.is_empty() {
|
||||||
|
value_text.as_str()
|
||||||
|
} else if !hint.is_empty() {
|
||||||
|
hint
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
|
||||||
|
let bool_state = if field.is_boolean_field() {
|
||||||
|
format!(" [{}]", if get_bool_field(config, field) { "enabled" } else { "disabled" })
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let prefix = if is_active { ">" } else { " " };
|
||||||
|
let label = format!("{:30} ", format!("{}:", field.label()));
|
||||||
|
let subtitle = field.subtitle();
|
||||||
|
|
||||||
|
let value_style = if is_active {
|
||||||
|
Style::default()
|
||||||
|
} else if display_value.is_empty() || display_value == hint {
|
||||||
|
Style::default().fg(Color::DarkGray)
|
||||||
|
} else {
|
||||||
|
Style::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut spans = vec![
|
||||||
|
Span::styled(
|
||||||
|
format!("{prefix} {label}"),
|
||||||
|
if is_active {
|
||||||
|
Style::default().add_modifier(Modifier::BOLD)
|
||||||
|
} else {
|
||||||
|
Style::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Span::styled(display_value.to_string(), value_style),
|
||||||
|
Span::styled(bool_state, Style::default().fg(Color::DarkGray)),
|
||||||
|
Span::styled(format!(" {subtitle}"), Style::default().fg(Color::DarkGray)),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some((_, err)) = errors.iter().find(|(f, _)| *f == field) {
|
||||||
|
spans.push(Span::styled(format!(" [ERR: {err}]"), Style::default().fg(Color::Red)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = Line::from(spans);
|
||||||
|
let block = if is_active {
|
||||||
|
Block::default().style(Style::default().bg(Color::DarkGray))
|
||||||
|
} else {
|
||||||
|
Block::default()
|
||||||
|
};
|
||||||
|
let widget = Paragraph::new(line).block(block);
|
||||||
|
frame.render_widget(widget, area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bool_field(config: &TuiConfig, field: FieldIndex) -> bool {
|
fn get_bool_field(config: &TuiConfig, field: FieldIndex) -> bool {
|
||||||
@@ -367,8 +443,12 @@ fn load_field_into_input(text_input: &mut String, config: &TuiConfig, field: Fie
|
|||||||
|
|
||||||
fn save_input_to_field(config: &mut TuiConfig, field: FieldIndex, text_input: &str) {
|
fn save_input_to_field(config: &mut TuiConfig, field: FieldIndex, text_input: &str) {
|
||||||
match field {
|
match field {
|
||||||
FieldIndex::StartUrl => { config.start_url = text_input.to_string(); }
|
FieldIndex::StartUrl => {
|
||||||
FieldIndex::OutputDir => { config.output_dir = text_input.to_string(); }
|
config.start_url = text_input.to_string();
|
||||||
|
}
|
||||||
|
FieldIndex::OutputDir => {
|
||||||
|
config.output_dir = text_input.to_string();
|
||||||
|
}
|
||||||
FieldIndex::DelayMs => {
|
FieldIndex::DelayMs => {
|
||||||
if let Ok(val) = text_input.trim().parse::<u64>() {
|
if let Ok(val) = text_input.trim().parse::<u64>() {
|
||||||
config.delay_ms = val;
|
config.delay_ms = val;
|
||||||
@@ -376,11 +456,19 @@ fn save_input_to_field(config: &mut TuiConfig, field: FieldIndex, text_input: &s
|
|||||||
}
|
}
|
||||||
FieldIndex::TypesInclude => {
|
FieldIndex::TypesInclude => {
|
||||||
let trimmed = text_input.trim();
|
let trimmed = text_input.trim();
|
||||||
config.types = if trimmed.is_empty() { None } else { Some(trimmed.to_string()) };
|
config.types = if trimmed.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(trimmed.to_string())
|
||||||
|
};
|
||||||
}
|
}
|
||||||
FieldIndex::TypesExclude => {
|
FieldIndex::TypesExclude => {
|
||||||
let trimmed = text_input.trim();
|
let trimmed = text_input.trim();
|
||||||
config.exclude_types = if trimmed.is_empty() { None } else { Some(trimmed.to_string()) };
|
config.exclude_types = if trimmed.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(trimmed.to_string())
|
||||||
|
};
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -393,42 +481,43 @@ fn handle_key_event(
|
|||||||
errors: &mut Vec<(FieldIndex, String)>,
|
errors: &mut Vec<(FieldIndex, String)>,
|
||||||
code: KeyCode,
|
code: KeyCode,
|
||||||
modifiers: KeyModifiers,
|
modifiers: KeyModifiers,
|
||||||
) -> io::Result<bool> {
|
) -> Action {
|
||||||
match code {
|
match code {
|
||||||
KeyCode::Esc
|
KeyCode::Esc
|
||||||
| KeyCode::Char('c') if modifiers.contains(KeyModifiers::CONTROL) => {
|
| KeyCode::Char('c') if modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
disable_raw_mode()?;
|
Action::Cancel
|
||||||
execute!(io::stdout(), DisableMouseCapture)?;
|
|
||||||
return Ok(false);
|
|
||||||
}
|
}
|
||||||
KeyCode::Tab => {
|
KeyCode::Tab => {
|
||||||
save_input_to_field(config, *current_field, text_input);
|
save_input_to_field(config, *current_field, text_input);
|
||||||
*current_field = current_field.next();
|
*current_field = current_field.next();
|
||||||
load_field_into_input(text_input, config, *current_field);
|
load_field_into_input(text_input, config, *current_field);
|
||||||
errors.clear();
|
errors.clear();
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
KeyCode::BackTab => {
|
KeyCode::BackTab => {
|
||||||
save_input_to_field(config, *current_field, text_input);
|
save_input_to_field(config, *current_field, text_input);
|
||||||
*current_field = current_field.prev();
|
*current_field = current_field.prev();
|
||||||
load_field_into_input(text_input, config, *current_field);
|
load_field_into_input(text_input, config, *current_field);
|
||||||
errors.clear();
|
errors.clear();
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
process_enter(config, current_field, text_input, errors);
|
process_enter(config, current_field, text_input, errors)
|
||||||
}
|
}
|
||||||
KeyCode::Char(ch) if current_field.is_text_field() => {
|
KeyCode::Char(ch) if current_field.is_text_field() => {
|
||||||
text_input.push(ch);
|
text_input.push(ch);
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
KeyCode::Backspace if current_field.is_text_field() => {
|
KeyCode::Backspace if current_field.is_text_field() => {
|
||||||
text_input.pop();
|
text_input.pop();
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
KeyCode::Delete if current_field.is_text_field() => {
|
KeyCode::Delete if current_field.is_text_field() => {
|
||||||
text_input.clear();
|
text_input.clear();
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => Action::Continue,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_enter(
|
fn process_enter(
|
||||||
@@ -436,19 +525,23 @@ fn process_enter(
|
|||||||
current_field: &mut FieldIndex,
|
current_field: &mut FieldIndex,
|
||||||
text_input: &mut String,
|
text_input: &mut String,
|
||||||
errors: &mut Vec<(FieldIndex, String)>,
|
errors: &mut Vec<(FieldIndex, String)>,
|
||||||
) {
|
) -> Action {
|
||||||
match *current_field {
|
match *current_field {
|
||||||
FieldIndex::Subdomains => {
|
FieldIndex::Subdomains => {
|
||||||
config.subdomains = !config.subdomains;
|
config.subdomains = !config.subdomains;
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
FieldIndex::Single => {
|
FieldIndex::Single => {
|
||||||
config.single = !config.single;
|
config.single = !config.single;
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
FieldIndex::NoScope => {
|
FieldIndex::NoScope => {
|
||||||
config.no_scope = !config.no_scope;
|
config.no_scope = !config.no_scope;
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
FieldIndex::NoDocConversion => {
|
FieldIndex::NoDocConversion => {
|
||||||
config.no_doc_conversion = !config.no_doc_conversion;
|
config.no_doc_conversion = !config.no_doc_conversion;
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
FieldIndex::StartUrl => {
|
FieldIndex::StartUrl => {
|
||||||
save_input_to_field(config, *current_field, text_input);
|
save_input_to_field(config, *current_field, text_input);
|
||||||
@@ -460,12 +553,14 @@ fn process_enter(
|
|||||||
*current_field = current_field.next();
|
*current_field = current_field.next();
|
||||||
load_field_into_input(text_input, config, *current_field);
|
load_field_into_input(text_input, config, *current_field);
|
||||||
}
|
}
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
FieldIndex::OutputDir | FieldIndex::DelayMs => {
|
FieldIndex::OutputDir | FieldIndex::DelayMs => {
|
||||||
save_input_to_field(config, *current_field, text_input);
|
save_input_to_field(config, *current_field, text_input);
|
||||||
errors.clear();
|
errors.clear();
|
||||||
*current_field = current_field.next();
|
*current_field = current_field.next();
|
||||||
load_field_into_input(text_input, config, *current_field);
|
load_field_into_input(text_input, config, *current_field);
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
FieldIndex::TypesInclude => {
|
FieldIndex::TypesInclude => {
|
||||||
save_input_to_field(config, *current_field, text_input);
|
save_input_to_field(config, *current_field, text_input);
|
||||||
@@ -481,6 +576,7 @@ fn process_enter(
|
|||||||
errors.push((FieldIndex::TypesInclude, e));
|
errors.push((FieldIndex::TypesInclude, e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
FieldIndex::TypesExclude => {
|
FieldIndex::TypesExclude => {
|
||||||
save_input_to_field(config, *current_field, text_input);
|
save_input_to_field(config, *current_field, text_input);
|
||||||
@@ -496,37 +592,36 @@ fn process_enter(
|
|||||||
errors.push((FieldIndex::TypesExclude, e));
|
errors.push((FieldIndex::TypesExclude, e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Action::Continue
|
||||||
}
|
}
|
||||||
FieldIndex::Submit => {
|
FieldIndex::Submit => {
|
||||||
if config.start_url.trim().is_empty() {
|
if config.start_url.trim().is_empty() {
|
||||||
errors.retain(|(f, _)| *f != FieldIndex::StartUrl);
|
errors.retain(|(f, _)| *f != FieldIndex::StartUrl);
|
||||||
errors.push((FieldIndex::StartUrl, "Required".into()));
|
errors.push((FieldIndex::StartUrl, "Required".into()));
|
||||||
return;
|
return Action::Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref t) = config.types
|
if let Some(ref t) = config.types
|
||||||
&& let Err(e) = validate_comma_list(t)
|
&& let Err(e) = validate_comma_list(t)
|
||||||
{
|
{
|
||||||
errors.retain(|(f, _)| *f != FieldIndex::TypesInclude);
|
errors.retain(|(f, _)| *f != FieldIndex::TypesInclude);
|
||||||
errors.push((FieldIndex::TypesInclude, e));
|
errors.push((FieldIndex::TypesInclude, e));
|
||||||
return;
|
return Action::Continue;
|
||||||
}
|
}
|
||||||
if let Some(ref t) = config.exclude_types
|
if let Some(ref t) = config.exclude_types
|
||||||
&& let Err(e) = validate_comma_list(t)
|
&& let Err(e) = validate_comma_list(t)
|
||||||
{
|
{
|
||||||
errors.retain(|(f, _)| *f != FieldIndex::TypesExclude);
|
errors.retain(|(f, _)| *f != FieldIndex::TypesExclude);
|
||||||
errors.push((FieldIndex::TypesExclude, e));
|
errors.push((FieldIndex::TypesExclude, e));
|
||||||
return;
|
return Action::Continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.types.is_some() && config.exclude_types.is_some() {
|
if config.types.is_some() && config.exclude_types.is_some() {
|
||||||
errors.push((FieldIndex::Submit, "types and exclude-types are mutually exclusive".into()));
|
errors.push((
|
||||||
return;
|
FieldIndex::Submit,
|
||||||
|
"types and exclude-types are mutually exclusive".into(),
|
||||||
|
));
|
||||||
|
return Action::Continue;
|
||||||
}
|
}
|
||||||
|
Action::Submit
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
execute!(io::stdout(), DisableMouseCapture).unwrap();
|
|
||||||
std::process::exit(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user