cargo-dinghy 0.8.5

Cross-compilation made easier
use clap::{CommandFactory, Parser, Subcommand};
use std::collections::HashSet;

#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
pub struct DinghyGeneralArgs {
    /// Use a specific platform
    #[arg(long, short)]
    pub platform: Option<String>,

    /// Make output more verbose, can be passed multiple times
    #[arg(long, short, action = clap::ArgAction::Count)]
    pub verbose: u8,

    /// Make output less verbose, can be passed multiple times
    #[arg(long, short, action = clap::ArgAction::Count)]
    pub quiet: u8,

    /// Force the use of an overlay during project build, can be passed multiple times
    #[arg(long, short)]
    pub overlay: Vec<String>,

    /// Env variables to set on target device e.g. RUST_TRACE=trace, can be passed multiple times
    #[arg(long, short)]
    pub env: Vec<String>,

    /// Cleanup target device after completion
    #[arg(long, short)]
    pub cleanup: bool,

    /// Strip executable before running it on target
    #[arg(long, short)]
    pub strip: bool,

    /// Device hint
    #[arg(long, short)]
    pub device: Option<String>,

    /// Either a dinghy subcommand (see cargo dinghy all-dinghy-subcommands) or a
    /// cargo one (see cargo --list)
    // this one is here so that the help generated by clap makes sense
    pub subcommand: Vec<String>,
}

#[derive(Parser, Debug)]
pub struct SubCommandWrapper {
    #[command(subcommand)]
    subcommand: DinghySubcommand,
}

#[derive(Subcommand, Debug)]
pub enum DinghySubcommand {
    /// List devices that can be used with Dinghy for the selected platform
    Devices {},
    /// List all devices that can be used with Dinghy
    AllDevices {},
    /// List all platforms known to dinghy
    AllPlatforms {},
    /// List all available dinghy subcommands
    AllDinghySubcommands {},
    /// Dinghy runner, used internally to run executables on targets
    Runner { args: Vec<String> },
    /// Build an artifact and run it on a target device using the provided wrapper
    RunWith {
        /// Wrapper crate to use to run the lib
        #[arg(long, short('c'))]
        wrapper_crate: String,
        // TODO support executables / scripts as wrappers
        // /// Wrapper executable to use to run the lib
        // #[clap(long, short('e'))]
        // wrapper_executable: Option<String>,
        /// Arguments to cargo build for the artifact
        lib_build_args: Vec<String>,
    },
}

#[derive(Debug)]
pub enum DinghyMode {
    DinghySubcommand(DinghySubcommand),
    CargoSubcommand { args: Vec<String> },
    Naked,
}

#[derive(Debug)]
pub struct DinghyCli {
    pub args: DinghyGeneralArgs,
    pub mode: DinghyMode,
}

impl DinghyCli {
    pub fn parse() -> Self {
        log::debug!("args {:?}", std::env::args().collect::<Vec<_>>());

        let args = std::env::args().skip(1).skip_while(|it| it == "dinghy");

        #[derive(Debug, Default)]
        struct SplitArgs {
            general_args: Vec<String>,
            subcommand: Vec<String>,
        }

        let args_taking_value = DinghyGeneralArgs::command()
            .get_arguments()
            .filter_map(|arg| {
                if arg.get_value_names().is_some() {
                    let mut values = vec![];
                    if let Some(shorts) = arg.get_short_and_visible_aliases() {
                        values
                            .append(&mut shorts.iter().map(|short| format!("-{}", short)).collect())
                    }
                    if let Some(longs) = arg.get_long_and_visible_aliases() {
                        values.append(&mut longs.iter().map(|long| format!("--{}", long)).collect())
                    }
                    if values.is_empty() {
                        None
                    } else {
                        Some(values)
                    }
                } else {
                    None
                }
            })
            .flatten()
            .collect::<HashSet<_>>();

        let split_args = args.fold(SplitArgs::default(), |mut split_args, elem| {
            if !split_args.subcommand.is_empty() {
                // we've started putting args in the sub command, let's continue
                split_args.subcommand.push(elem)
            } else {
                if elem.starts_with("-") /* This is a new option */
                    || split_args.general_args
                    .last()
                    .map(|it| args_taking_value.contains(it))
                    .unwrap_or(false)
                /* value for the previous option */
                {
                    split_args.general_args.push(elem)
                } else {
                    // leve the start of the subcommand here so that clap can verify it is there
                    split_args.general_args.push(elem.clone());
                    // this is the start of the sub command
                    split_args.subcommand.push(elem)
                }
            }

            split_args
        });

        let cli = DinghyCli {
            args: Parser::parse_from(
                vec!["dinghy".to_string()]
                    .into_iter()
                    .chain(split_args.general_args),
            ),
            mode: split_args
                .subcommand
                .first()
                .cloned()
                .map(|subcommand| {
                    if DinghySubcommand::has_subcommand(&subcommand) {
                        DinghyMode::DinghySubcommand(
                            SubCommandWrapper::parse_from(
                                vec!["dinghy".to_string()]
                                    .into_iter()
                                    .chain(split_args.subcommand),
                            )
                            .subcommand,
                        )
                    } else {
                        DinghyMode::CargoSubcommand {
                            args: split_args.subcommand,
                        }
                    }
                })
                .unwrap_or(DinghyMode::Naked),
        };

        log::debug!("cli {:?}", cli);

        cli
    }
}