typeshare_engine/
args.rs

1use std::path::{Path, PathBuf};
2
3use clap::builder::PossibleValuesParser;
4
5use crate::serde::args::{ArgType, CliArgsSet};
6
7#[derive(Debug, Clone, Copy)]
8pub enum OutputLocation<'a> {
9    File(&'a Path),
10    Folder(&'a Path),
11}
12
13#[derive(clap::Args, Debug)]
14#[group(multiple = false, required = true)]
15pub struct Output {
16    /// File to write output to. mtime will be preserved if the file contents
17    /// don't change
18    #[arg(short = 'o', long = "output-file")]
19    pub file: Option<PathBuf>,
20
21    /// Folder to write output to. mtime will be preserved if the file contents
22    /// don't change
23    #[arg(short = 'd', long = "output-folder")]
24    pub directory: Option<PathBuf>,
25}
26
27impl Output {
28    pub fn location(&self) -> OutputLocation<'_> {
29        match (&self.directory, &self.file) {
30            (Some(dir), None) => OutputLocation::Folder(dir),
31            (None, Some(file)) => OutputLocation::File(file),
32            (None, None) => panic!("got neither a file nor a directory; clap should prevent this"),
33            (Some(dir), Some(file)) => {
34                panic!("got both file '{file:?}' and directory '{dir:?}'; clap should prevent this")
35            }
36        }
37    }
38}
39
40#[derive(clap::Parser, Debug)]
41#[command(args_conflicts_with_subcommands = true, subcommand_negates_reqs = true)]
42pub struct StandardArgs {
43    #[command(subcommand)]
44    pub subcommand: Option<Command>,
45
46    /// Path to the config file for this typeshare
47    #[arg(short, long, visible_alias("config-file"))]
48    pub config: Option<PathBuf>,
49
50    /// The directories within which to recursively find and process rust files
51    #[arg(num_args(1..), required=true)]
52    pub directories: Vec<PathBuf>,
53
54    #[arg(long, exclusive(true))]
55    pub completions: Option<String>,
56
57    #[command(flatten)]
58    pub output: Output,
59}
60
61#[derive(Debug, Clone, Copy, clap::Subcommand)]
62pub enum Command {
63    /// Generate shell completions
64    Completions {
65        /// The shell to generate the completions for
66        shell: clap_complete::Shell,
67    },
68}
69
70/// Add a `--lang` argument to the command. This argument will be optional if
71/// there is only one language
72pub fn add_lang_argument(command: clap::Command, languages: &[&'static str]) -> clap::Command {
73    let arg = clap::Arg::new("language")
74        .short('l')
75        .long("lang")
76        .value_name("LANGUAGE")
77        .value_parser(PossibleValuesParser::new(languages))
78        .action(clap::ArgAction::Set)
79        .help("the output language of generated types");
80
81    command.arg(match languages {
82        [] => panic!("need at least one language"),
83        [lang] => arg.required(false).default_value(lang),
84        _ => arg.required(true),
85    })
86}
87
88/// Given a CliArgsSet for a language, use the name of the language and
89/// information about its configuration to populate a clap command with
90/// args specific to that language
91pub fn add_language_params_to_clap(command: clap::Command, args: &CliArgsSet) -> clap::Command {
92    if let Some(arg) = command
93        .get_arguments()
94        .find(|arg| arg.get_id().as_str().starts_with(args.language()))
95    {
96        panic!(
97            "existing argument {id:?} conflicts with language {language}",
98            id = arg.get_id().as_str(),
99            language = args.language()
100        )
101    }
102
103    args.iter().fold(command, |command, spec| {
104        let arg = clap::Arg::new(spec.full_key.to_owned())
105            .long(spec.full_key.to_owned())
106            .required(false);
107
108        command.arg(match spec.arg_type {
109            ArgType::Bool => arg.action(clap::ArgAction::SetTrue),
110            ArgType::Value => arg.action(clap::ArgAction::Set).value_name(spec.key),
111        })
112    })
113}