use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(name = "nander")]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
pub struct Args {
#[arg(short, long, global = true)]
pub verbose: bool,
#[arg(long = "speed", global = true, default_value = "5", value_parser = clap::value_parser!(u8).range(0..8))]
pub spi_speed: u8,
#[arg(long = "driver", short = 'D', global = true, default_value = "auto")]
pub driver: String,
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand, Debug)]
pub enum Command {
#[command(alias = "i")]
Info,
#[command(alias = "L")]
List,
#[command(alias = "r")]
Read {
#[arg(short, long)]
output: PathBuf,
#[arg(short, long)]
length: Option<u32>,
#[arg(short, long, default_value = "0")]
start: u32,
#[arg(short = 'd', long = "no-ecc")]
disable_ecc: bool,
#[arg(short = 'k', long = "skip-bad")]
skip_bad: bool,
#[arg(short = 'K', long = "include-bad")]
include_bad: bool,
#[arg(long = "oob")]
oob: bool,
#[arg(short = 'O', long = "oob-only")]
oob_only: bool,
#[arg(short = 'I', long = "ignore-ecc")]
ignore_ecc: bool,
#[arg(short = 'R', long = "retries", default_value = "0")]
retries: u32,
#[arg(long = "bbt")]
bbt_file: Option<PathBuf>,
},
#[command(alias = "w")]
Write {
#[arg(short, long)]
input: PathBuf,
#[arg(short, long, default_value = "0")]
start: u32,
#[arg(short = 'V', long, default_value = "true")]
verify: bool,
#[arg(short = 'd', long = "no-ecc")]
disable_ecc: bool,
#[arg(short = 'k', long = "skip-bad")]
skip_bad: bool,
#[arg(short = 'K', long = "include-bad")]
include_bad: bool,
#[arg(short = 'o', long = "oob")]
oob: bool,
#[arg(short = 'O', long = "oob-only")]
oob_only: bool,
#[arg(short = 'I', long = "ignore-ecc")]
ignore_ecc: bool,
#[arg(short = 'R', long = "retries", default_value = "0")]
retries: u32,
#[arg(long = "bbt")]
bbt_file: Option<PathBuf>,
},
#[command(alias = "e")]
Erase {
#[arg(short, long)]
length: Option<u32>,
#[arg(short, long, default_value = "0")]
start: u32,
#[arg(short = 'd', long = "no-ecc")]
disable_ecc: bool,
#[arg(short = 'k', long = "skip-bad")]
skip_bad: bool,
#[arg(short = 'K', long = "include-bad")]
include_bad: bool,
#[arg(long = "bbt")]
bbt_file: Option<PathBuf>,
},
#[command(alias = "v")]
Verify {
#[arg(short, long)]
input: PathBuf,
#[arg(short, long, default_value = "0")]
start: u32,
#[arg(short = 'd', long = "no-ecc")]
disable_ecc: bool,
#[arg(short = 'k', long = "skip-bad")]
skip_bad: bool,
#[arg(short = 'K', long = "include-bad")]
include_bad: bool,
#[arg(short = 'o', long = "oob")]
oob: bool,
#[arg(short = 'O', long = "oob-only")]
oob_only: bool,
#[arg(short = 'I', long = "ignore-ecc")]
ignore_ecc: bool,
#[arg(short = 'R', long = "retries", default_value = "0")]
retries: u32,
#[arg(long = "bbt")]
bbt_file: Option<PathBuf>,
},
Protect {
#[arg(value_name = "OPERATION", default_value = "status")]
operation: String,
},
Status {
#[arg(value_name = "VALUE")]
value: Option<String>,
},
Bbt {
#[command(subcommand)]
command: BbtCommand,
},
#[command(alias = "test")]
Diagnostic {
#[arg(short, long)]
interactive: bool,
},
#[command(alias = "b")]
Batch {
#[arg(short, long)]
script: Option<PathBuf>,
#[arg(short, long, conflicts_with = "script")]
template: Option<String>,
#[arg(
short,
long,
required_if_eq("template", "flash-update"),
required_if_eq("template", "production")
)]
firmware: Option<PathBuf>,
#[arg(long)]
save_to: Option<PathBuf>,
},
#[command(alias = "g")]
Gui,
#[command(alias = "pass")]
Passthrough {
#[arg(short, long, default_value = "spi")]
mode: String,
#[arg(short, long)]
tx: Option<String>,
#[arg(short, long, default_value = "0")]
rx: usize,
#[arg(short, long)]
addr: Option<String>,
},
}
#[derive(Subcommand, Debug)]
pub enum BbtCommand {
Scan {
#[arg(short, long)]
output: Option<PathBuf>,
},
Load {
#[arg(short, long)]
input: PathBuf,
},
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn test_parse_args_defaults() {
let args = Args::parse_from(["nander", "info"]);
assert_eq!(args.driver, "auto");
assert_eq!(args.spi_speed, 5);
match args.command {
Command::Info => (),
_ => panic!("Expected Info command"),
}
}
#[test]
fn test_parse_args_with_driver_and_speed() {
let args = Args::parse_from([
"nander", "-D", "ch347", "--speed", "2", "read", "-o", "out.bin",
]);
assert_eq!(args.driver, "ch347");
assert_eq!(args.spi_speed, 2);
match args.command {
Command::Read { output, .. } => assert_eq!(output, PathBuf::from("out.bin")),
_ => panic!("Expected Read command"),
}
}
#[test]
fn test_parse_args_with_passthrough() {
let args = Args::parse_from(["nander", "pass", "--mode", "spi", "--tx", "9F", "--rx", "3"]);
match args.command {
Command::Passthrough { mode, tx, rx, .. } => {
assert_eq!(mode, "spi");
assert_eq!(tx, Some("9F".to_string()));
assert_eq!(rx, 3);
}
_ => panic!("Expected Passthrough command"),
}
}
}