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à --- Cargo.lock | 229 ++++----------------------------------------- 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 ++++++++++++++++++------ 7 files changed, 260 insertions(+), 301 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab5483d..15586ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,101 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "anstream" -version = "0.6.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" - -[[package]] -name = "anstyle-parse" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" -dependencies = [ - "anstyle", - "windows-sys", -] - -[[package]] -name = "clap" -version = "4.5.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" - -[[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - [[package]] name = "equivalent" version = "1.0.1" @@ -113,28 +18,16 @@ checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" name = "header" version = "0.1.0" -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "indexmap" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - [[package]] name = "lazy_static" version = "1.5.0" @@ -151,7 +44,6 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" name = "nasm" version = "0.1.0" dependencies = [ - "clap", "xixanta", ] @@ -171,18 +63,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -191,24 +83,23 @@ dependencies = [ name = "readrom" version = "1.0.0" dependencies = [ - "clap", "header", ] [[package]] name = "serde" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -224,17 +115,11 @@ dependencies = [ "serde", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "syn" -version = "2.0.85" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -278,94 +163,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" dependencies = [ "memchr", ] @@ -373,9 +179,6 @@ dependencies = [ [[package]] name = "xa65" version = "0.1.0" -dependencies = [ - "clap", -] [[package]] name = "xixanta" 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