From 61ef02df2c128cd86efcddd3fa53870bc8d575c4 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Wed, 22 Jan 2025 15:57:49 +0100 Subject: Remove the dependency on clap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three binaries (i.e. readrom, nasm and xa65) used it, and to be honest rolling our own argument parsing was actually pretty easy to achieve. This frees us from a big dependency and it also frees us from inner dependencies such as 'clap_derive' which prevented us from being able to build binaries purely statically. As a side effect, we can now display help/version messages which are more like I'm used to, and certain checks can be moved in the parsing directly instead of having to be done later. As a cherry on top, there is some type masturbation that gets removed along the way. Signed-off-by: Miquel Sabaté Solà --- crates/nasm/Cargo.toml | 1 - crates/nasm/src/main.rs | 128 ++++++++++++++++++++++++++++++++------------- crates/readrom/Cargo.toml | 1 - crates/readrom/src/main.rs | 79 ++++++++++++++++++++++------ crates/xa65/Cargo.toml | 3 -- crates/xa65/src/main.rs | 120 ++++++++++++++++++++++++++++++++---------- 6 files changed, 244 insertions(+), 88 deletions(-) (limited to 'crates') diff --git a/crates/nasm/Cargo.toml b/crates/nasm/Cargo.toml index 97d5439..f0f11db 100644 --- a/crates/nasm/Cargo.toml +++ b/crates/nasm/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" authors = ["Miquel Sabaté Solà "] [dependencies] -clap = { version = "^4", features = ["derive"] } xixanta.workspace = true diff --git a/crates/nasm/src/main.rs b/crates/nasm/src/main.rs index e757207..ef7c7d4 100644 --- a/crates/nasm/src/main.rs +++ b/crates/nasm/src/main.rs @@ -1,37 +1,104 @@ -use clap::Parser as ClapParser; use std::fs::File; use std::io::{self, Write}; use std::path::Path; use xixanta::assembler::assemble; use xixanta::SourceInfo; -/// Assembler for the 6502 microprocessor that targets the NES/Famicom. -#[derive(ClapParser, Debug)] -#[command(version, about, long_about = None)] +/// Version for this program. +const VERSION: &str = "0.1.0"; + +#[derive(Default)] struct Args { - /// Assemble the instructions given on this file. file: String, - - /// Linker configuration to be used. This configuration can be an identifier - /// for the configurations already baked in into this application, or it can - /// be a file path to a configuration of your choosing. See the - /// documentation for more information on this format. Defaults to 'nrom'. - #[arg(short = 'c', long)] config: Option, - - /// Place the output into the given file. Ignored if the `stdout` flag - /// is provided. Defaults to `out.nes`. - #[arg(short = 'o', long)] out: Option, + werror: bool, + stdout: bool, +} - /// Treat warnings as errors. - #[arg(short = 'W', value_name = "Error")] - w: Option, +// Print the help message and quit. +fn print_help() { + println!("Assembler for the 6502 microprocessor that targets the NES/Famicom.\n"); + println!("usage: nasm [OPTIONS] \n"); + println!("Options:"); + println!(" -c, --config \tLinker configuration to be used, whether an identifier or a file path."); + println!(" -o, --out \tFile path where the output should be located after execution."); + println!(" --stdout\t\tPrint the output binary to the standard output."); + println!(" -Werror\t\tWarnings should be treated as errors."); + std::process::exit(0); +} - /// Spit the output into the standard output instead. This ignores any given - /// `out` flag. Disabled by default. - #[arg(long, default_value_t = false)] - stdout: bool, +// Parse the arguments given to the program and returns an Args object with the +// given information. +fn parse_arguments() -> Args { + let mut args = std::env::args(); + let mut res = Args::default(); + + // Skip command name. + args.next(); + + while let Some(arg) = args.next() { + match arg.as_str() { + "-c" | "--config" => match res.config { + Some(_) => die("only specify the '-C/--config' flag once".to_string()), + None => match args.next() { + Some(v) => res.config = Some(v), + None => { + die("you need to provide a value for the '-C/--config' flag".to_string()) + } + }, + }, + "-h" | "--help" => print_help(), + "-o" | "--out" => match res.out { + Some(_) => die("only specify the '-o/--out' flag once".to_string()), + None => { + if res.stdout { + die("you cannot mix '-o/--out' and '--stdout'".to_string()); + } + match args.next() { + Some(v) => res.out = Some(v), + None => { + die("you need to provide a value for the '-o/--out' flag".to_string()) + } + } + } + }, + "--stdout" => match res.out { + Some(_) => die("you cannot mix '-o/--out' and '--stdout'".to_string()), + None => { + if res.stdout { + die("only specify the '--stdout' flag once".to_string()); + } + res.stdout = true; + } + }, + "-v" | "--version" => { + println!("nasm {}", VERSION); + std::process::exit(0); + } + "-Werror" => { + if res.werror { + die("only specify the '-Werror' flag once".to_string()); + } + res.werror = true; + } + _ => { + if arg.starts_with('-') { + die(format!("don't know how to handle the '{}' flag", arg)); + } + if !res.file.is_empty() { + die("cannot have multiple source files".to_string()); + } + res.file = arg; + } + } + } + + if res.file.is_empty() { + die("you need to specify a source file".to_string()); + } + + res } // Print the given `message` and exit(1). @@ -41,7 +108,7 @@ fn die(message: String) { } fn main() { - let args = Args::parse(); + let args = parse_arguments(); // Select the input stream and build the source object. let path = Path::new(&args.file); @@ -74,19 +141,6 @@ fn main() { } }; - // Check if warnings have to be treated as errors. - let warn_as_errors = match args.w { - Some(value) => { - if value.to_lowercase() != "error" { - die("the '-W' flag can only be used as '-Werror'".to_string()); - return; - } else { - true - } - } - None => false, - }; - // And assemble. let mut error_count = 0; let res = assemble( @@ -98,7 +152,7 @@ fn main() { // Print warnings and errors first, while also computing the amount of them // that exists. for warning in res.warnings { - if warn_as_errors { + if args.werror { eprintln!("error: {}", warning); error_count += 1; } else { diff --git a/crates/readrom/Cargo.toml b/crates/readrom/Cargo.toml index 93d6134..326ed92 100644 --- a/crates/readrom/Cargo.toml +++ b/crates/readrom/Cargo.toml @@ -7,5 +7,4 @@ license.workspace = true authors.workspace = true [dependencies] -clap = "^4" header.workspace = true diff --git a/crates/readrom/src/main.rs b/crates/readrom/src/main.rs index ae2a44c..e37e8c7 100644 --- a/crates/readrom/src/main.rs +++ b/crates/readrom/src/main.rs @@ -1,8 +1,65 @@ -use clap::{arg, Arg, Command}; use header::{Header, Kind}; use std::fs::File; use std::io::{ErrorKind, Read}; +/// Version for this program. +const VERSION: &str = "0.1.0"; + +#[derive(Default)] +struct Args { + file: String, + header: bool, +} + +fn print_help() { + println!("Display information about NES/Famicom ROM files.\n"); + println!("usage: readrom [OPTIONS] \n"); + println!("Options:"); + println!(" -H, --header\tJust print the ROM header and quit."); + std::process::exit(0); +} + +// Parse the arguments given to the program and returns an Args object with the +// given information. +fn parse_arguments() -> Args { + let mut args = std::env::args(); + let mut res = Args::default(); + + // Skip command name. + args.next(); + + for arg in args { + match arg.as_str() { + "-h" | "--help" => print_help(), + "-H" | "--header" => { + if res.header { + die("do not specify the '-H/--header' flag twice".to_string()); + } + res.header = true; + } + "-v" | "--version" => { + println!("readrom {}", VERSION); + std::process::exit(0); + } + _ => { + if arg.starts_with('-') { + die(format!("don't know how to handle the '{}' flag", arg)); + } + if !res.file.is_empty() { + die("cannot have multiple source files".to_string()); + } + res.file = arg; + } + } + } + + if res.file.is_empty() { + die("you need to specify the file to be read".to_string()); + } + + res +} + fn print_header(header: &Header) { println!("Header:"); @@ -47,22 +104,10 @@ fn die(message: String) { } fn main() { - let args = Command::new("readrom") - .version("0.1.0") - .about("Display information about NES/Famicom ROM files.") - .arg(Arg::new("FILE").required(true).help("ROM file to be read")) - .arg(arg!(-H --header "Just print the ROM header and quit")) - .get_matches(); - let file = match args.get_one::("FILE") { - Some(file) => file, - None => { - die("you have to provide a file".to_string()); - return; - } - }; + let args = parse_arguments(); - let Ok(mut input) = File::open(file) else { - die(format!("failed to open the given file '{}'", &file)); + let Ok(mut input) = File::open(&args.file) else { + die(format!("failed to open the given file '{}'", &args.file)); return; }; @@ -85,7 +130,7 @@ fn main() { }; print_header(&header); - if *args.get_one::("header").unwrap() { + if args.header { std::process::exit(0); } diff --git a/crates/xa65/Cargo.toml b/crates/xa65/Cargo.toml index 1ecceb0..1a2c52c 100644 --- a/crates/xa65/Cargo.toml +++ b/crates/xa65/Cargo.toml @@ -3,6 +3,3 @@ name = "xa65" version = "0.1.0" edition = "2021" authors = ["Miquel Sabaté Solà "] - -[dependencies] -clap = { version = "^4", features = ["derive"] } diff --git a/crates/xa65/src/main.rs b/crates/xa65/src/main.rs index 8a31e7b..cdfce59 100644 --- a/crates/xa65/src/main.rs +++ b/crates/xa65/src/main.rs @@ -1,30 +1,101 @@ -use clap::Parser as ClapParser; use std::path::PathBuf; use std::process::Command; -/// Bridge between 'nasm' and 'ca65'. -#[derive(ClapParser, Debug)] -#[command(version, about, long_about = None)] +/// Version for this program. +const VERSION: &str = "0.1.0"; + +// Arguments for this application. See `parse_arguments` on how it's filled. +#[derive(Default)] struct Args { - /// Assemble the instructions given on this file. file: String, - - /// Linker configuration to be used. This configuration can be an identifier - /// for the configurations already baked in into this application, or it can - /// be a file path to a configuration of your choosing. See the - /// documentation for more information on this format. Defaults to 'nrom'. - #[arg(short = 'C', long = "config")] config: Option, - - /// Used for compatibility with 'ca65'. - #[arg(long)] target: Option, - - /// Place the output into the given file. - #[arg(short = 'o', long)] out: String, } +// Print the help message and quit. +fn print_help() { + println!("Bridge between 'nasm' and 'ca65'.\n"); + println!("usage: xa65 [OPTIONS] \n"); + println!("Options:"); + println!(" -C, --config \tLinker configuration to be used, whether an identifier or a file path."); + println!(" -o, --out \tFile path where the output should be located after execution."); + println!(" --target nes\t\tUsed for compatibility with 'ca65'."); + std::process::exit(0); +} + +// Parse the arguments given to the program and returns an Args object with the +// given information. +fn parse_arguments() -> Args { + let mut args = std::env::args(); + let mut res = Args::default(); + + // Skip command name. + args.next(); + + while let Some(arg) = args.next() { + match arg.as_str() { + "-C" | "--config" => match res.config { + Some(_) => die("only specify the '-C/--config' flag once".to_string()), + None => match args.next() { + Some(v) => res.config = Some(v), + None => { + die("you need to provide a value for the '-C/--config' flag".to_string()) + } + }, + }, + "-h" | "--help" => print_help(), + "-o" | "--out" => { + if res.out.is_empty() { + match args.next() { + Some(v) => res.out = v, + None => { + die("you need to provide a value for the '-o/--out' flag".to_string()) + } + } + } else { + die("only specify the '-o/--out' flag once".to_string()); + } + } + "--target" => match res.target { + Some(_) => die("only specify the '--target' flag once".to_string()), + None => match args.next() { + Some(v) => { + let real = v.to_lowercase(); + if real != "nes" { + die("the '--target' flag only accepts 'nes' as a value".to_string()); + } + res.target = Some(real) + } + None => die("you need to provide a value for the '--target' flag".to_string()), + }, + }, + "-v" | "--version" => { + println!("xa65 {}", VERSION); + std::process::exit(0); + } + _ => { + if arg.starts_with('-') { + die(format!("don't know how to handle the '{}' flag", arg)); + } + if !res.file.is_empty() { + die("cannot have multiple source files".to_string()); + } + res.file = arg; + } + } + } + + if res.file.is_empty() { + die("you need to specify a source file".to_string()); + } + if res.out.is_empty() { + die("you need to specify an output file with '-o/--output'".to_string()); + } + + res +} + // Print the given `message` and exit(1). fn die(message: String) { eprintln!("error: {}", message); @@ -80,18 +151,9 @@ fn main() { return; } }; - let args = Args::parse(); - - // Sanity check on the target flag from 'ca65'. - if let Some(target) = args.target { - if target != "nes" { - die(format!( - "the target has to be 'nes', but '{}' was provided instead", - target - )); - return; - } - } + + // Parse arguments. + let args = parse_arguments(); // Generate a temporary directory in which both binary files will be placed // as an intermediate step. -- cgit v1.2.3