ax-rnd 0.1.5

AxRng is a fast, small random number generator (rng) library and CLI tool written in Rust.
Documentation
use ax_rnd::AxRng;
use std::fs;
use std::io::{self, BufRead, Write};

fn generate_uuid_v4(rnd: &mut AxRng) -> String {
    let mut bytes = [0u8; 16];
    rnd.fill_bytes(&mut bytes);
    bytes[6] = (bytes[6] & 0x0F) | 0x40;
    bytes[8] = (bytes[8] & 0x3F) | 0x80;

    format!(
        "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
        bytes[0],
        bytes[1],
        bytes[2],
        bytes[3],
        bytes[4],
        bytes[5],
        bytes[6],
        bytes[7],
        bytes[8],
        bytes[9],
        bytes[10],
        bytes[11],
        bytes[12],
        bytes[13],
        bytes[14],
        bytes[15]
    )
}

fn main() {
    let args: Vec<String> = std::env::args().collect();

    if args.len() < 2 {
        print_usage();
        return;
    }

    match args[1].as_str() {
        "bytes" => {
            let count = args
                .get(2)
                .and_then(|s| s.parse::<usize>().ok())
                .unwrap_or(32);
            let hex = args.get(3).map(|s| s == "--hex").unwrap_or(false);
            let seed = args.get(4).and_then(|s| parse_seed(s)).unwrap_or_else(|| {
                use std::time::{SystemTime, UNIX_EPOCH};
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .as_nanos() as u64
            });

            let mut rnd = AxRng::new(seed);
            let mut buf = vec![0u8; count];
            rnd.fill_bytes(&mut buf);

            if hex {
                for b in buf {
                    print!("{:02x}", b);
                }
                println!();
            } else {
                io::stdout().write_all(&buf).unwrap();
            }
        }
        "alpha" => {
            let len = args
                .get(2)
                .and_then(|s| s.parse::<usize>().ok())
                .unwrap_or(32);
            let seed = args.get(3).and_then(|s| parse_seed(s)).unwrap_or_else(|| {
                use std::time::{SystemTime, UNIX_EPOCH};
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .as_nanos() as u64
            });

            let mut rnd = AxRng::new(seed);
            println!("{}", rnd.alpha(len));
        }
        "u64" => {
            let count = args
                .get(2)
                .and_then(|s| s.parse::<usize>().ok())
                .unwrap_or(1);
            let seed = args.get(3).and_then(|s| parse_seed(s)).unwrap_or_else(|| {
                use std::time::{SystemTime, UNIX_EPOCH};
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .as_nanos() as u64
            });

            let mut rnd = AxRng::new(seed);
            for _ in 0..count {
                println!("{}", rnd.next_u64());
            }
        }
        "uuid" => {
            let count = args
                .get(2)
                .and_then(|s| s.parse::<usize>().ok())
                .unwrap_or(1);
            let seed = args.get(3).and_then(|s| parse_seed(s)).unwrap_or_else(|| {
                use std::time::{SystemTime, UNIX_EPOCH};
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .as_nanos() as u64
            });

            let mut rnd = AxRng::new(seed);
            for _ in 0..count {
                println!("{}", generate_uuid_v4(&mut rnd));
            }
        }
        "token" => {
            let len = args
                .get(2)
                .and_then(|s| s.parse::<usize>().ok())
                .unwrap_or(32);
            let seed = args.get(3).and_then(|s| parse_seed(s)).unwrap_or_else(|| {
                use std::time::{SystemTime, UNIX_EPOCH};
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap()
                    .as_nanos() as u64
            });

            let mut rnd = AxRng::new(seed);
            println!("{}", rnd.token(len));
        }
        "shuffle" => {
            let mut seed: Option<u64> = None;
            let mut file_path: Option<String> = None;

            let mut i = 2;
            while i < args.len() {
                match args[i].as_str() {
                    "-f" | "--file" => {
                        if i + 1 < args.len() {
                            file_path = Some(args[i + 1].clone());
                            i += 2;
                        } else {
                            eprintln!("Error: -f/--file requires a path");
                            std::process::exit(1);
                        }
                    }
                    "-s" | "--seed" => {
                        if i + 1 < args.len() {
                            seed = parse_seed(&args[i + 1]);
                            if seed.is_none() {
                                eprintln!("Error: invalid seed value");
                                std::process::exit(1);
                            }
                            i += 2;
                        } else {
                            eprintln!("Error: -s/--seed requires a value");
                            std::process::exit(1);
                        }
                    }
                    _ => {
                        if file_path.is_none() && args[i].parse::<u64>().is_err() {
                            file_path = Some(args[i].clone());
                        } else if seed.is_none()
                            && let Some(s) = parse_seed(&args[i])
                        {
                            seed = Some(s);
                        }
                        i += 1;
                    }
                }
            }

            let seed = seed.unwrap_or_else(|| {
                use std::time::{SystemTime, UNIX_EPOCH};
                SystemTime::now()
                    .duration_since(UNIX_EPOCH)
                    .unwrap_or_else(|_| std::time::Duration::from_nanos(0xA0761D6478BD642F))
                    .as_nanos() as u64
            });

            let mut rnd = AxRng::new(seed);

            if let Some(path) = file_path {
                let content = fs::read_to_string(&path).unwrap_or_else(|e| {
                    eprintln!("Error: Cannot read file '{}': {}", path, e);
                    std::process::exit(1);
                });
                let mut lines: Vec<&str> = content.lines().collect();
                shuffle_lines(&mut lines, &mut rnd);
                for line in lines {
                    println!("{}", line);
                }
            } else {
                let stdin = io::stdin();
                let mut lines: Vec<String> = Vec::new();
                for line in stdin.lock().lines() {
                    match line {
                        Ok(l) => lines.push(l),
                        Err(_) => break,
                    }
                }
                shuffle_lines(&mut lines, &mut rnd);
                for line in lines {
                    println!("{}", line);
                }
            }
        }
        _ => print_usage(),
    }
}

fn parse_seed(s: &str) -> Option<u64> {
    if s == "time" {
        use std::time::{SystemTime, UNIX_EPOCH};
        Some(
            SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_nanos() as u64,
        )
    } else {
        s.parse::<u64>().ok()
    }
}

fn shuffle_lines<T>(lines: &mut [T], rnd: &mut AxRng) {
    let len = lines.len();
    if len <= 1 {
        return;
    }
    for i in (1..len).rev() {
        let j = rnd.bounded_u64(i as u64) as usize;
        lines.swap(i, j);
    }
}

fn print_usage() {
    println!("AXRND - CLI tool for random number generation");
    println!();
    println!("USAGE:");
    println!("  ax_rnd <command> [args]");
    println!();
    println!("COMMANDS:");
    println!("  bytes [count] [--hex] [seed]  Generate random bytes");
    println!("  alpha [len]           [seed]  Generate alphanumeric (base62)");
    println!("  token [len]           [seed]  Generate URL-safe token (base64url)");
    println!("  shuffle [-f FILE] [-s SEED]   Shuffle lines from stdin or file");
    println!("  u64   [count]         [seed]  Generate random u64 numbers");
    println!("  uuid  [count]         [seed]  Generate UUID v4");
    println!();
    println!("OPTIONS:");
    println!("  count/len   Number of bytes/chars/numbers (default: 32)");
    println!("  --hex       Output bytes as hex string");
    println!("  seed        rng seed (default: time)");
    println!("              Use 'time' for current timestamp");
    println!();
    println!("EXAMPLES:");
    println!("  ax_rnd bytes 64");
    println!("  ax_rnd bytes 32 --hex");
    println!("  ax_rnd alpha 16");
    println!("  ax_rnd token 32");
    println!("  ax_rnd shuffle -f file.txt");
    println!("  ax_rnd shuffle -f file.txt -s 12345");
    println!("  echo 'a b c' | ax_rnd shuffle");
    println!("  echo 'a b c' | ax_rnd shuffle -s 42");
    println!("  ax_rnd u64 5");
    println!("  ax_rnd uuid 3");
    println!("  ax_rnd bytes 32 12345");
}