use std::io::IsTerminal;
const VERSION: &str = env!("CARGO_PKG_VERSION");
const BANNER_COLOR: &str = concat!(
"\x1b[36m ███╗ ██╗███╗ ███╗ █████╗ ██████╗ ██████╗ ███████╗\x1b[0m\n",
"\x1b[36m ██╔██╗ ██║████╗ ████║██╔══██╗██╔══██╗██╔══██╗██╔════╝\x1b[0m\n",
"\x1b[35m ██║╚██╗██║██╔████╔██║███████║██████╔╝██████╔╝███████╗\x1b[0m\n",
"\x1b[35m ██║ ╚████║██║╚██╔╝██║██╔══██║██╔══██╗██╔══██╗╚════██║\x1b[0m\n",
"\x1b[31m ╚██╗ ╚██╔╝██║ ╚═╝ ██║██║ ██║██║ ██║██║ ██║███████║\x1b[0m\n",
"\x1b[31m ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝\x1b[0m\n",
);
const BANNER_PLAIN: &str = concat!(
" ███╗ ██╗███╗ ███╗ █████╗ ██████╗ ██████╗ ███████╗\n",
" ██╔██╗ ██║████╗ ████║██╔══██╗██╔══██╗██╔══██╗██╔════╝\n",
" ██║╚██╗██║██╔████╔██║███████║██████╔╝██████╔╝███████╗\n",
" ██║ ╚████║██║╚██╔╝██║██╔══██║██╔══██╗██╔══██╗╚════██║\n",
" ╚██╗ ╚██╔╝██║ ╚═╝ ██║██║ ██║██║ ██║██║ ██║███████║\n",
" ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝\n",
);
fn use_color() -> bool {
std::io::stdout().is_terminal() && std::env::var_os("NO_COLOR").is_none()
}
fn section_line(title: &str, total_width: usize, color: bool) -> String {
let prefix = format!(" ── {} ", title);
let dashes = total_width.saturating_sub(prefix.chars().count());
let body = format!("{}{}", prefix, "─".repeat(dashes));
if color {
format!("\x1b[36m{}\x1b[0m", body)
} else {
body
}
}
fn tp_line(flags: &str, comment: &str, color: bool) -> String {
const COL: usize = 48;
let mut line = String::new();
line.push_str(" ");
line.push_str(flags);
let len = line.chars().count();
if len < COL {
line.push_str(&" ".repeat(COL - len));
} else {
line.push_str(" ");
}
if color {
line.push_str(" \x1b[32m//\x1b[0m ");
} else {
line.push_str("// ");
}
line.push_str(comment);
line
}
fn print_section(title: &str, rows: &[(&str, &str)], color: bool) {
println!("{}", section_line(title, 56, color));
for &(flags, desc) in rows {
println!("{}", tp_line(flags, desc, color));
}
println!();
}
fn print_banner_and_status(color: bool) {
if color {
print!("{}", BANNER_COLOR);
println!("\x1b[36m ┌──────────────────────────────────────────────────────┐\x1b[0m");
println!(
"\x1b[36m │ STATUS: ONLINE // SIGNAL: ████████░░ // v{}\x1b[36m │\x1b[0m",
VERSION
);
println!("\x1b[36m └──────────────────────────────────────────────────────┘\x1b[0m");
println!("\x1b[35m >> NMAPRS // GRID SCANNER // FULL SPECTRUM <<\x1b[0m");
} else {
print!("{}", BANNER_PLAIN);
println!(" ┌──────────────────────────────────────────────────────┐");
println!(" │ STATUS: ONLINE // SIGNAL: ████████░░ // v{} │", VERSION);
println!(" └──────────────────────────────────────────────────────┘");
println!(" >> NMAPRS // GRID SCANNER // FULL SPECTRUM <<");
}
}
fn print_footer(color: bool) {
println!("{}", section_line("SYSTEM", 52, color));
if color {
println!(concat!(
"\x1b[35m v",
env!("CARGO_PKG_VERSION"),
" \x1b[0m// \x1b[33m(c) MenkeTechnologies\x1b[0m"
));
println!("\x1b[35m The grid is wide and infinite.\x1b[0m");
println!("\x1b[33m >>> JACK IN. MAP THE GRID. OWN YOUR PORTS. <<<\x1b[0m");
println!("\x1b[36m ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\x1b[0m");
} else {
println!(" v{VERSION} // (c) MenkeTechnologies");
println!(" The grid is wide and infinite.");
println!(" >>> JACK IN. MAP THE GRID. OWN YOUR PORTS. <<<");
println!(" ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░");
}
}
pub fn print_help(bin: &str) {
let color = use_color();
print_banner_and_status(color);
println!();
println!("Parallel network scanner — nmap-compatible CLI (see README).");
println!();
if color {
println!("\x1b[33m USAGE:\x1b[0m {} [OPTIONS] [TARGET]...", bin);
} else {
println!(" USAGE: {bin} [OPTIONS] [TARGET]...");
}
println!();
print_section(
"TARGET SPEC",
&[
("--iL <FILE>", "Input host list from FILE"),
("--iR <NUM>", "Choose NUM random targets"),
("--exclude <TARGETS>", "Exclude hosts or networks"),
("--excludefile <FILE>", "Exclude list from FILE"),
],
color,
);
print_section(
"HOST DISCOVERY",
&[
("--sL", "List scan (targets only)"),
("--sn", "Ping scan — no port scan"),
("-Pn, --no-ping", "Skip host discovery; treat all as up"),
("--ping-S [<PORTLIST>]", "TCP SYN discovery (or -PS)"),
("--ping-A [<PORTLIST>]", "TCP ACK discovery (or -PA)"),
("--ping-U [<PORTLIST>]", "UDP discovery (or -PU)"),
("--ping-Y [<PORTLIST>]", "SCTP discovery (or -PY)"),
("--ping-E / -PE", "ICMP echo discovery"),
("--ping-P / -PP", "ICMP timestamp discovery"),
("--ping-M / -PM", "ICMP netmask discovery"),
("--ping-ip-proto [<PROTOS>]", "IP protocol ping (or -PO)"),
("-n", "Never do DNS resolution"),
("-R", "Always resolve"),
("--dns-servers <SERVERS>", "Custom resolvers"),
("--system-dns", "Use OS resolver"),
("--traceroute", "Trace hop path to each host"),
],
color,
);
print_section(
"SCAN TECHNIQUES",
&[
("--scan-type <CHAR>", "Scan letter (-sT / -sS / -sU / …)"),
("--scanflags <FLAGS>", "Custom TCP flags (raw TCP scans)"),
("--sI <ZOMBIE>", "Idle scan (or -sI)"),
("--sO", "IP protocol scan"),
("-b <FTP>", "FTP bounce scan"),
(
"-sS -sT -sU …",
"Short scan types (-sN -sF -sX -sA -sW -sM -sY -sZ)",
),
],
color,
);
print_section(
"SCRIPTS / VERSION / OS",
&[
("--version-scan / -sV", "Service version detection"),
("--script-default / -sC", "Default script set"),
("--script <EXPR>", "Script expression"),
("--script-args <ARGS>", "Script arguments"),
("--script-args-file <FILE>", "Script args from file"),
("--script-trace", "Trace script I/O"),
("--script-updatedb", "Update script DB (stub)"),
("--script-help <EXPR>", "Script help (built-ins)"),
("--version-intensity <0-9>", "Version probe depth"),
("--version-light / --version-all", "Intensity presets"),
("--version-trace", "Trace version scan"),
("-O", "OS detection (heuristic)"),
("--osscan-limit", "Limit OS scan to promising targets"),
("--osscan-guess / --fuzzy", "Guess OS more aggressively"),
],
color,
);
print_section(
"PORTS",
&[
("-p <PORTS>", "Port list / ranges"),
("--exclude-ports <PORTS>", "Exclude ports from scan"),
("-F", "Fast scan (fewer ports)"),
("-r", "Sequential port order"),
("--top-ports <N>", "Top N TCP ports by frequency"),
("--port-ratio <RATIO>", "Ports more common than RATIO"),
("--allports", "Ignore --exclude-ports"),
],
color,
);
print_section(
"TIMING",
&[
("-T <0-5>, --timing <0-5>", "Timing template"),
("--min-hostgroup / --max-hostgroup <N>", "Host batch sizes"),
(
"--min-parallelism / -M, --max-parallelism <N>",
"Probe parallelism",
),
(
"--min-rtt-timeout / --max-rtt-timeout / --initial-rtt-timeout",
"RTT bounds",
),
("--max-retries <N>", "Extra probe retries after timeout"),
("--host-timeout <TIME>", "Give up on host after TIME"),
(
"--scan-delay / --max-scan-delay <TIME>",
"Inter-probe delay",
),
("--min-rate / --max-rate <RATE>", "Global probe start rate"),
("--stats-every <TIME>", "Stats interval (parsed)"),
],
color,
);
print_section(
"EVASION",
&[
("-f, --mtu <VAL>", "Fragment / MTU"),
("-D <DECOYS>", "Decoy addresses"),
("-S <ADDR>", "Spoof source address"),
("-e <IFACE>", "Network interface"),
("-g, --source-port <PORT>", "Fixed source port"),
("--proxies / --proxy <URLS>", "HTTP/SOCKS proxies"),
("--data / --data-string / --data-length", "Append payload"),
("--ip-options <OPTS>", "Raw IP options"),
("--ttl <VAL>", "IP TTL"),
("--spoof-mac <MAC>", "Spoof MAC"),
("--badsum", "Bad TCP/UDP checksum"),
],
color,
);
print_section(
"OUTPUT",
&[
("-oN -oX -oS -oG -oA -oM -oH <FILE>", "Output paths"),
("--verbosity <N> / --debug <N>", "Log levels"),
("--reason", "Show port state reason"),
("--open", "Open ports only"),
("--packet-trace", "Packet trace"),
("--iflist", "List interfaces"),
("--append-output", "Append to output files"),
("--resume <FILE>", "Resume from checkpoint"),
("--noninteractive", "No keyboard interaction"),
("--stylesheet / --webxml / --no-stylesheet", "XML styling"),
],
color,
);
print_section(
"MISC",
&[
("-6", "IPv6"),
("-A", "Aggressive (-O -sV -sC --traceroute)"),
("--datadir <DIR>", "Nmap data files directory"),
("--send-eth / --send-ip", "Link-layer send mode"),
("--privileged / --unprivileged", "Capability hints"),
("-h, --help", "Print help"),
("-V, --version", "Print version"),
],
color,
);
print_section(
"EXTENDED",
&[
("--resolve-all", "Scan every resolved address"),
("--max-os-tries <N>", "OS detection tries (1–50)"),
("--defeat-rst-ratelimit", "Defeat RST rate limit"),
("--defeat-icmp-ratelimit", "Defeat ICMP rate limit"),
("--randomize-hosts / --rH", "Shuffle host order"),
("--nsock-engine <NAME>", "Nsock engine (kqueue/poll/select)"),
("--discovery-ignore-rst", "Ignore RST in discovery"),
("--unique", "Deduplicate targets"),
("--log-errors", "Log errors"),
("--deprecated-xml-osclass", "Legacy XML osclass"),
("--adler32", "Adler32 checksums"),
("--disable-arp-ping", "Disable ARP ping"),
("--route-dst <HOST>", "Route debug"),
("--servicedb / --versiondb <FILE>", "Override data files"),
("--release-memory", "Release memory hint"),
("--nogcc", "No GCC optimizations"),
("--script-timeout <TIME>", "Built-in script timeout"),
("--thc", "THC mode"),
("--vv / --ff", "Extra verbosity / debug"),
],
color,
);
print_section(
"POSITIONAL",
&[(
"[TARGET]...",
"Hostnames, IPs, CIDR, nmap-style octet ranges",
)],
color,
);
print_footer(color);
}
pub fn print_version(bin: &str) {
if use_color() {
println!("\x1b[35m{bin} {VERSION}\x1b[0m");
} else {
println!("{bin} {VERSION}");
}
}
#[cfg(test)]
mod tests {
use super::{section_line, tp_line, VERSION};
#[test]
fn section_line_contains_title_and_fills_width_plain() {
let s = section_line("SCAN", 40, false);
assert!(s.contains("SCAN"));
assert!(s.starts_with(" ── "));
assert!(s.chars().filter(|c| *c == '─').count() >= 10);
}
#[test]
fn section_line_color_wraps_body() {
let s = section_line("NET", 30, true);
assert!(s.contains("\x1b[36m"));
assert!(s.ends_with("\x1b[0m"));
}
#[test]
fn tp_line_aligns_flags_before_comment() {
let plain = tp_line("-p <ports>", "Port selection", false);
assert!(plain.contains("//"));
assert!(plain.contains("Port selection"));
assert!(plain.contains("-p"));
let long = tp_line("--very-long-option-name <VAL>", "desc", false);
assert!(long.contains("//"));
assert!(long.contains("desc"));
}
#[test]
fn tp_line_color_includes_green_comment_delim() {
let c = tp_line("-h", "help", true);
assert!(c.contains("\x1b[32m//\x1b[0m"));
}
#[test]
fn version_constant_non_empty() {
assert!(!VERSION.is_empty());
}
#[test]
fn section_line_respects_requested_width() {
let narrow = section_line("X", 20, false);
let wide = section_line("X", 60, false);
assert!(wide.len() >= narrow.len());
}
#[test]
fn tp_line_short_flag_has_comment_separator() {
let line = tp_line("-V", "version", false);
let parts: Vec<_> = line.split("//").collect();
assert_eq!(parts.len(), 2);
assert!(parts[0].contains("-V"));
}
#[test]
fn tp_line_color_mode_wraps_flag_text() {
let c = tp_line("--help", "help", true);
assert!(c.contains("\x1b["));
}
#[test]
fn section_line_plain_has_no_escape_codes() {
let s = section_line("TARGETS", 50, false);
assert!(!s.contains("\x1b"));
}
#[test]
fn banner_plain_contains_nmaprs_art() {
use super::BANNER_PLAIN;
assert!(BANNER_PLAIN.contains('█'));
assert!(BANNER_PLAIN.lines().count() >= 6);
}
#[test]
fn tp_line_long_flag_still_has_comment() {
let line = tp_line("--very-long-flag-name-here", "desc", false);
assert!(line.contains("//"));
assert!(line.contains("desc"));
}
#[test]
fn section_line_zero_width_still_has_title() {
let s = section_line("X", 0, false);
assert!(s.contains("X"));
}
#[test]
fn tp_line_starts_with_two_spaces() {
let line = tp_line("-h", "help", false);
assert!(line.starts_with(" "));
}
#[test]
fn section_line_long_title_present() {
let s = section_line("HOST DISCOVERY", 70, false);
assert!(s.contains("HOST DISCOVERY"));
}
#[test]
fn tp_line_empty_comment_still_has_separator() {
let line = tp_line("-v", "", false);
assert!(line.contains("//"));
}
#[test]
fn tp_line_color_comment_contains_reset() {
let c = tp_line("-O", "OS detect", true);
assert!(c.contains("\x1b[0m"));
}
#[test]
fn section_line_color_wraps_title() {
let c = section_line("SCAN TECHNIQUES", 50, true);
assert!(c.contains("\x1b["));
}
#[test]
fn tp_line_plain_has_no_color_codes() {
let line = tp_line("-p", "ports", false);
assert!(!line.contains("\x1b"));
}
#[test]
fn section_line_color_includes_title_text() {
let c = section_line("OUTPUT", 40, true);
assert!(c.contains("OUTPUT"));
}
#[test]
fn tp_line_double_dash_long_option() {
let line = tp_line("--scan-type", "scan letter", false);
assert!(line.contains("--scan-type"));
assert!(line.contains("scan letter"));
}
#[test]
fn section_line_very_long_title_truncates_dashes_not_title() {
let s = section_line("HOST DISCOVERY OPTIONS", 40, false);
assert!(s.contains("HOST DISCOVERY OPTIONS"));
}
#[test]
fn tp_line_color_has_green_delimiter_only_once() {
let c = tp_line("-O", "OS", true);
assert_eq!(c.matches("\x1b[32m//\x1b[0m").count(), 1);
}
#[test]
fn banner_plain_no_escape_sequences() {
use super::BANNER_PLAIN;
assert!(!BANNER_PLAIN.contains("\x1b"));
}
#[test]
fn version_constant_matches_cargo_pkg() {
assert!(VERSION.chars().any(|c| c.is_ascii_digit()));
}
}