Browse Source

Update docstrings and arg parsing

Also add the most basic `debug_assert` test.
pull/26/head
Ian Chamberlain 2 years ago
parent
commit
583fc67fc6
No known key found for this signature in database
GPG Key ID: AE5484D09405AA60
  1. 110
      src/command.rs
  2. 83
      src/lib.rs
  3. 21
      src/main.rs

110
src/command.rs

@ -1,10 +1,7 @@
// TODO: docstrings for everything!!!
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(name = "cargo")] #[command(name = "cargo", bin_name = "cargo")]
#[command(bin_name = "cargo")]
pub enum Cargo { pub enum Cargo {
#[command(name = "3ds")] #[command(name = "3ds")]
Input(Input), Input(Input),
@ -13,20 +10,44 @@ pub enum Cargo {
#[derive(Args, Debug)] #[derive(Args, Debug)]
#[command(version, about)] #[command(version, about)]
pub struct Input { pub struct Input {
#[command(subcommand)]
pub cmd: CargoCmd,
}
/// The cargo command to run. This command will be forwarded to the real /// The cargo command to run. This command will be forwarded to the real
/// `cargo` with the appropriate arguments for a 3DS executable. /// `cargo` with the appropriate arguments for a 3DS executable.
#[command(subcommand)] ///
pub cmd: CargoCommand, /// If another command is passed which is not recognized, it will be passed
/// through unmodified to `cargo` with RUSTFLAGS set for the 3DS.
#[derive(Subcommand, Debug)]
#[command(allow_external_subcommands = true)]
pub enum CargoCmd {
/// Builds an executable suitable to run on a 3DS (3dsx).
Build(CargoArgs),
/// Don't actually run any commands, just echo them to the console. /// Builds an executable and sends it to a device with `3dslink`.
/// This is mostly intended for testing. Run(Run),
#[arg(long, hide = true)]
pub dry_run: bool,
/// Builds a test executable and sends it to a device with `3dslink`.
///
/// This can be used with `--test` for integration tests, or `--lib` for
/// unit tests (which require a custom test runner).
Test(Test),
// NOTE: it seems docstring + name for external subcommands are not rendered
// in help, but we might as well set them here in case a future version of clap
// does include them in help text.
/// Run any other `cargo` command with RUSTFLAGS set for the 3DS.
#[command(external_subcommand, name = "COMMAND")]
Passthrough(Vec<String>),
}
#[derive(Args, Debug)]
pub struct CargoArgs {
/// Pass additional options through to the `cargo` command. /// Pass additional options through to the `cargo` command.
/// ///
/// To pass flags that start with `-`, you must use `--` to separate `cargo 3ds` /// To pass flags that start with `-`, you must use `--` to separate `cargo 3ds`
/// options from cargo options. All args after `--` will be passed through /// options from cargo options. Any argument after `--` will be passed through
/// to cargo unmodified. /// to cargo unmodified.
/// ///
/// If one of the arguments is itself `--`, the args following that will be /// If one of the arguments is itself `--`, the args following that will be
@ -36,50 +57,31 @@ pub struct Input {
/// ///
/// > cargo 3ds run -- -- arg1 arg2 /// > cargo 3ds run -- -- arg1 arg2
#[arg(trailing_var_arg = true)] #[arg(trailing_var_arg = true)]
#[arg(allow_hyphen_values = true)]
#[arg(global = true)] #[arg(global = true)]
cargo_options: Vec<String>, #[arg(name = "CARGO_ARGS")]
} args: Vec<String>,
#[derive(Subcommand, Debug)]
pub enum CargoCommand {
/// Builds an executable suitable to run on a 3DS (3dsx).
Build,
/// Equivalent to `cargo check`.
Check,
/// Equivalent to `cargo clippy`.
Clippy,
/// Equivalent to `cargo doc`.
Doc,
/// Builds an executable and sends it to a device with `3dslink`.
Run(Run),
/// Builds a test executable and sends it to a device with `3dslink`.
///
/// This can be used with `--test` for integration tests, or `--lib` for
/// unit tests (which require a custom test runner).
Test(Test),
//
// TODO: this doesn't seem to work for some reason...
// #[command(external_subcommand)]
// Other(Vec<String>),
} }
#[derive(Args, Debug)] #[derive(Parser, Debug)]
pub struct Test { pub struct Test {
/// If set, the built executable will not be sent to the device to run it. /// If set, the built executable will not be sent to the device to run it.
#[arg(long)] #[arg(long)]
pub no_run: bool, pub no_run: bool,
// The test command uses a superset of the same arguments as Run.
#[command(flatten)] #[command(flatten)]
pub run_args: Run, pub run_args: Run,
} }
#[derive(Args, Debug)] #[derive(Parser, Debug)]
pub struct Run { pub struct Run {
/// Specify the IP address of the device to send the executable to. /// Specify the IP address of the device to send the executable to.
/// ///
/// Corresponds to 3dslink's `--address` arg, which defaults to automatically /// Corresponds to 3dslink's `--address` arg, which defaults to automatically
/// finding the device. /// finding the device.
#[arg(long, short = 'a')] #[arg(long, short = 'a')]
pub address: Option<String>, pub address: Option<std::net::Ipv4Addr>,
/// Set the 0th argument of the executable when running it. Corresponds to /// Set the 0th argument of the executable when running it. Corresponds to
/// 3dslink's `--argv0` argument. /// 3dslink's `--argv0` argument.
@ -88,7 +90,7 @@ pub struct Run {
/// Start the 3dslink server after sending the executable. Corresponds to /// Start the 3dslink server after sending the executable. Corresponds to
/// 3dslink's `--server` argument. /// 3dslink's `--server` argument.
#[arg(long, short = 's')] #[arg(long, short = 's', default_value_t = false)]
pub server: bool, pub server: bool,
/// Set the number of tries when connecting to the device to send the executable. /// Set the number of tries when connecting to the device to send the executable.
@ -96,9 +98,13 @@ pub struct Run {
// Can't use `short = 'r'` because that would conflict with cargo's `--release/-r` // Can't use `short = 'r'` because that would conflict with cargo's `--release/-r`
#[arg(long)] #[arg(long)]
pub retries: Option<usize>, pub retries: Option<usize>,
// Passthrough cargo options.
#[command(flatten)]
pub cargo_args: CargoArgs,
} }
impl Input { impl CargoArgs {
/// Get the args to be passed to the executable itself (not `cargo`). /// Get the args to be passed to the executable itself (not `cargo`).
pub fn cargo_opts(&self) -> &[String] { pub fn cargo_opts(&self) -> &[String] {
self.split_args().0 self.split_args().0
@ -110,15 +116,31 @@ impl Input {
} }
fn split_args(&self) -> (&[String], &[String]) { fn split_args(&self) -> (&[String], &[String]) {
if let Some(split) = self.cargo_options.iter().position(|arg| arg == "--") { if let Some(split) = self
let split = if &self.cargo_options[split] == "--" { .args
.iter()
.position(|s| s == "--" || !s.starts_with('-'))
{
let split = if self.args[split] == "--" {
split + 1 split + 1
} else { } else {
split split
}; };
self.cargo_options.split_at(split) self.args.split_at(split)
} else { } else {
(&self.cargo_options[..], &[]) (&self.args[..], &[])
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_app() {
Cargo::command().debug_assert();
}
}

83
src/lib.rs

@ -1,8 +1,6 @@
extern crate core;
pub mod command; pub mod command;
use crate::command::{CargoCommand, Input}; use crate::command::{CargoCmd, Input};
use cargo_metadata::{Message, MetadataCommand}; use cargo_metadata::{Message, MetadataCommand};
use core::fmt; use core::fmt;
use rustc_version::Channel; use rustc_version::Channel;
@ -22,11 +20,11 @@ pub fn get_should_link(input: &mut Input) -> bool {
// When running compile only commands, don't link the executable to the 3ds. // When running compile only commands, don't link the executable to the 3ds.
// Otherwise, link and run on the 3ds but do not run locally. // Otherwise, link and run on the 3ds but do not run locally.
match input.cmd { match input.cmd {
CargoCommand::Run(_) => true, CargoCmd::Run(_) => true,
CargoCommand::Test(_) if !input.cargo_opts().contains(&"--no-run".to_string()) => { // CargoCmd::Test(_) if !input.cargo_opts().contains(&"--no-run".to_string()) => {
// input.cargo_opts().push("--no-run".to_string()); // // input.cargo_opts().push("--no-run".to_string());
true // true
} // }
_ => false, _ => false,
} }
} }
@ -35,41 +33,42 @@ pub fn get_should_link(input: &mut Input) -> bool {
/// default to `json-render-diagnostics`. /// default to `json-render-diagnostics`.
pub fn get_message_format(input: &mut Input) -> String { pub fn get_message_format(input: &mut Input) -> String {
// Checks for a position within the args where '--message-format' is located // Checks for a position within the args where '--message-format' is located
if let Some(pos) = input todo!();
.cargo_opts() // if let Some(pos) = input
.iter() // .cargo_opts()
.position(|s| s.starts_with("--message-format")) // .iter()
{ // .position(|s| s.starts_with("--message-format"))
// Remove the arg from list // {
let arg = input.cargo_opts()[pos].clone(); // TODO // // Remove the arg from list
// let arg = input.cargo_opts()[pos].clone(); // TODO
// Allows for usage of '--message-format=<format>' and also using space separation.
// Check for a '=' delimiter and use the second half of the split as the format, // // Allows for usage of '--message-format=<format>' and also using space separation.
// otherwise remove next arg which is now at the same position as the original flag. // // Check for a '=' delimiter and use the second half of the split as the format,
let format = if let Some((_, format)) = arg.split_once('=') { // // otherwise remove next arg which is now at the same position as the original flag.
format.to_string() // let format = if let Some((_, format)) = arg.split_once('=') {
} else { // format.to_string()
input.cargo_opts()[pos].clone() // TODO // } else {
}; // input.cargo_opts()[pos].clone() // TODO
// };
// Non-json formats are not supported so the executable exits.
if format.starts_with("json") { // // Non-json formats are not supported so the executable exits.
format // if format.starts_with("json") {
} else { // format
eprintln!("error: non-JSON `message-format` is not supported"); // } else {
process::exit(1); // eprintln!("error: non-JSON `message-format` is not supported");
} // process::exit(1);
} else { // }
// Default to 'json-render-diagnostics' // } else {
DEFAULT_MESSAGE_FORMAT.to_string() // // Default to 'json-render-diagnostics'
} // DEFAULT_MESSAGE_FORMAT.to_string()
// }
} }
/// Build the elf that will be used to create other 3ds files. /// Build the elf that will be used to create other 3ds files.
/// The command built from [`make_cargo_build_command`] is executed /// The command built from [`make_cargo_build_command`] is executed
/// and the messages from the spawned process are parsed and returned. /// and the messages from the spawned process are parsed and returned.
pub fn build_elf( pub fn build_elf(
cmd: CargoCommand, cmd: CargoCmd,
message_format: &str, message_format: &str,
args: &Vec<String>, args: &Vec<String>,
) -> (ExitStatus, Vec<Message>) { ) -> (ExitStatus, Vec<Message>) {
@ -100,7 +99,7 @@ pub fn build_elf(
/// Create the cargo build command, but don't execute it. /// Create the cargo build command, but don't execute it.
/// If there is no pre-built std detected in the sysroot, `build-std` is used. /// If there is no pre-built std detected in the sysroot, `build-std` is used.
pub fn make_cargo_build_command( pub fn make_cargo_build_command(
cmd: CargoCommand, cmd: CargoCmd,
message_format: &str, message_format: &str,
args: &Vec<String>, args: &Vec<String>,
) -> Command { ) -> Command {
@ -114,11 +113,9 @@ pub fn make_cargo_build_command(
let mut command = Command::new(cargo); let mut command = Command::new(cargo);
let cmd = match cmd { let cmd = match cmd {
CargoCommand::Build | CargoCommand::Run(_) => "build", CargoCmd::Build(_) | CargoCmd::Run(_) => "build",
CargoCommand::Test(_) => "test", CargoCmd::Test(_) => "test",
CargoCommand::Check => "check", CargoCmd::Passthrough(_) => todo!(),
CargoCommand::Clippy => "clippy",
CargoCommand::Doc => "doc",
}; };
command command

21
src/main.rs

@ -1,9 +1,9 @@
use cargo_3ds::command::Cargo; use cargo_3ds::command::{Cargo, CargoCmd, Input, Run, Test};
use cargo_3ds::{ use cargo_3ds::{
build_3dsx, build_elf, build_smdh, check_rust_version, get_message_format, get_metadata, build_3dsx, build_elf, build_smdh, check_rust_version, get_message_format, get_metadata,
get_should_link, link, get_should_link, link,
}; };
use clap::Parser; use clap::{CommandFactory, FromArgMatches, Parser};
use std::process; use std::process;
fn main() { fn main() {
@ -12,10 +12,21 @@ fn main() {
let Cargo::Input(mut input) = Cargo::parse(); let Cargo::Input(mut input) = Cargo::parse();
dbg!(&input); dbg!(&input);
dbg!(input.cargo_opts());
dbg!(input.exe_args());
// let should_link = get_should_link(&mut input); let cargo_args = match &input.cmd {
CargoCmd::Build(cargo_args)
| CargoCmd::Run(Run { cargo_args, .. })
| CargoCmd::Test(Test {
run_args: Run { cargo_args, .. },
..
}) => cargo_args,
CargoCmd::Passthrough(other) => todo!(),
};
dbg!(cargo_args.cargo_opts());
dbg!(cargo_args.exe_args());
// let
// let message_format = get_message_format(&mut input); // let message_format = get_message_format(&mut input);
// let (status, messages) = build_elf(input.cmd, &message_format, &input.cargo_opts); // let (status, messages) = build_elf(input.cmd, &message_format, &input.cargo_opts);

Loading…
Cancel
Save