use clap::Parser;
use std::process;
use three_word_networking::{Result, ThreeWordAdaptiveEncoder};
#[derive(Parser)]
#[command(
name = "3wn",
about = "Three-Word Networking - Convert between IP addresses and memorable words",
long_about = "Automatically converts between IP addresses and three-word combinations.\n\
Features 100% perfect reconstruction for IPv4 and adaptive compression for IPv6.\n\
IPv4 uses 3 words with dots (paper.broaden.smith), IPv6 uses 6 or 9 words with dashes (Ocean-Thunder-Falcon-Star-Book-April).",
version
)]
struct Cli {
input: Vec<String>,
#[arg(short, long)]
verbose: bool,
#[arg(short, long)]
quiet: bool,
}
fn main() {
let cli = Cli::parse();
if let Err(e) = run(cli) {
eprintln!("Error: {e}");
process::exit(1);
}
}
fn run(cli: Cli) -> Result<()> {
let encoder = ThreeWordAdaptiveEncoder::new()?;
let input = if cli.input.len() == 1 {
cli.input[0].trim().to_string()
} else {
cli.input.join(" ")
};
if looks_like_words(&input) {
decode_words(&encoder, &input, cli.verbose, cli.quiet)
} else {
encode_address(&encoder, &input, cli.verbose, cli.quiet)
}
}
fn looks_like_words(input: &str) -> bool {
let segments: Vec<&str> = if input.contains(' ') && !input.contains('-') && !input.contains(':')
{
input.split_whitespace().collect()
} else if input.contains('.')
|| input.contains('-')
|| input.contains('_')
|| input.contains('+')
{
input.split(|c: char| ".-_+".contains(c)).collect()
} else {
return false;
};
if segments.len() != 3 && segments.len() != 6 && segments.len() != 9 {
return false;
}
segments
.iter()
.all(|segment| segment.len() >= 2 && segment.chars().all(|c| c.is_alphabetic()))
}
fn encode_address(
encoder: &ThreeWordAdaptiveEncoder,
address: &str,
verbose: bool,
quiet: bool,
) -> Result<()> {
let words = encoder.encode(address)?;
if quiet {
println!("{words}");
} else if verbose {
println!("Input: {address}");
println!("Words: {words}");
println!("Encoding: Perfect (100% reversible)");
if words.contains('.') && !words.contains('-') {
println!("Type: IPv4 (dot separators, lowercase)");
} else if words.contains('-') {
println!("Type: IPv6 (dash separators, title case)");
}
println!("Features:");
println!(" • Perfect IPv4 reconstruction (3 words)");
println!(" • Adaptive IPv6 compression (6 or 9 words)");
println!(" • Guaranteed perfect reconstruction");
} else {
println!("{words}");
}
Ok(())
}
fn decode_words(
encoder: &ThreeWordAdaptiveEncoder,
words: &str,
verbose: bool,
quiet: bool,
) -> Result<()> {
let address = encoder.decode(words)?;
if quiet {
println!("{address}");
} else if verbose {
println!("Input: {words}");
println!("Address: {address}");
println!("Decoding: Perfect reconstruction");
if words.contains('.') && !words.contains('-') {
println!("Type: IPv4 (detected from dot separators)");
} else if words.contains('-') {
println!("Type: IPv6 (detected from dash separators)");
}
} else {
println!("{address}");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_looks_like_words() {
assert!(looks_like_words("ocean.thunder.falcon"));
assert!(looks_like_words("Ocean-Thunder-Falcon-Star-Book-April"));
assert!(looks_like_words("ocean_thunder_falcon"));
assert!(!looks_like_words("ocean.thunder"));
assert!(!looks_like_words("a.b.c.d.e"));
assert!(!looks_like_words("ocean.thunder.123"));
assert!(!looks_like_words("192.168.1.1"));
assert!(!looks_like_words("ocean:thunder:falcon"));
}
}