mod formats;
mod multiline;
mod quick;
mod regex;
mod rhai;
mod time;
pub use formats::print_formats_help;
pub use multiline::print_multiline_help;
pub use quick::print_quick_help;
pub use regex::print_regex_help;
pub use rhai::print_rhai_help;
pub use time::print_time_format_help;
use crate::cli::Cli;
use crate::rhai_functions;
use clap::CommandFactory;
pub fn print_functions_help(filter: Option<&str>) {
match filter {
None => {
let help_text = rhai_functions::docs::generate_help_text();
println!("{}", help_text);
}
Some(keyword) => {
let filtered = rhai_functions::docs::filter_help_text(keyword);
if filtered.trim().is_empty() {
println!(
"No functions matching \"{keyword}\". Run --help-functions for the full catalogue."
);
} else {
println!("Functions matching \"{keyword}\":\n{filtered}");
}
}
}
}
pub fn print_cli_help_filtered(keyword: &str) {
let full = Cli::command().render_long_help().to_string();
let filtered = filter_cli_help(&full, keyword);
if filtered.trim().is_empty() {
println!(
"No options matching \"{keyword}\". Run --help for the full reference \
(search a flag like '-j' or '--since', or a bare word like 'time')."
);
} else {
println!("Options matching \"{keyword}\":\n{filtered}");
}
}
fn filter_cli_help(full: &str, keyword: &str) -> String {
let flag_query = keyword.starts_with('-');
let case_sensitive = keyword.chars().any(|c| c.is_uppercase());
let needle = if case_sensitive {
keyword.to_string()
} else {
keyword.to_lowercase()
};
let contains = |haystack: &str| -> bool {
if case_sensitive {
haystack.contains(&needle)
} else {
haystack.to_lowercase().contains(&needle)
}
};
let lines: Vec<&str> = full.lines().collect();
let indent = |line: &str| line.len() - line.trim_start().len();
let is_header =
|line: &str| !line.is_empty() && indent(line) == 0 && line.trim_end().ends_with(':');
let is_entry_start = |line: &str| {
let trimmed = line.trim_start();
indent(line) < 8 && (trimmed.starts_with('-') || trimmed.starts_with('['))
};
let mut out = String::new();
let mut current_section: Option<&str> = None;
let mut section_printed = false;
let mut i = 0;
while i < lines.len() {
let line = lines[i];
if is_header(line) {
current_section = Some(line);
section_printed = false;
i += 1;
continue;
}
if !is_entry_start(line) {
i += 1;
continue;
}
let entry_start = i;
i += 1;
while i < lines.len() && !is_header(lines[i]) && !is_entry_start(lines[i]) {
i += 1;
}
let entry = &lines[entry_start..i];
let (header_matches, entry_matches) = if flag_query {
(false, flag_token_match(entry[0], keyword))
} else {
let header_matches = matches!(current_section, Some(h) if contains(h));
(header_matches, entry.iter().any(|l| contains(l)))
};
if header_matches || entry_matches {
if !section_printed {
if let Some(sec) = current_section {
out.push('\n');
out.push_str(sec);
out.push('\n');
}
section_printed = true;
}
for l in entry {
out.push_str(l);
out.push('\n');
}
}
}
out
}
fn flag_token_match(line: &str, flag: &str) -> bool {
let bytes = line.as_bytes();
let mut from = 0;
while let Some(rel) = line[from..].find(flag) {
let start = from + rel;
let end = start + flag.len();
let left_ok = start == 0 || bytes[start - 1] == b' ';
let right_ok = end == bytes.len() || matches!(bytes[end], b' ' | b',');
if left_ok && right_ok {
return true;
}
from = start + 1;
}
false
}
pub fn print_examples_help() {
let help_text = rhai_functions::docs::generate_examples_text();
println!("{}", help_text);
}