crux_cli/
args.rs

1use std::path::PathBuf;
2
3use clap::{ArgAction, Args, Parser, Subcommand, ValueHint::DirPath};
4use heck::{ToPascalCase, ToSnakeCase};
5
6#[derive(Parser)]
7#[command(
8    name = "crux",
9    bin_name = "crux",
10    author,
11    version,
12    about,
13    long_about = None,
14    arg_required_else_help(true),
15    propagate_version = true
16)]
17pub struct Cli {
18    #[command(subcommand)]
19    pub command: Commands,
20
21    #[arg(long, short, action = ArgAction::Count)]
22    pub verbose: u8,
23}
24
25#[derive(Subcommand)]
26pub enum Commands {
27    #[command(visible_alias = "doc")]
28    Doctor(DoctorArgs),
29
30    #[command(visible_alias = "gen")]
31    Codegen(CodegenArgs),
32}
33
34#[derive(Args)]
35pub struct DoctorArgs {
36    #[arg(long, short)]
37    pub fix: Option<PathBuf>,
38
39    #[arg(long, short, default_value = "false")]
40    pub include_source_code: bool,
41
42    /// temporary
43    #[arg(long, short)]
44    pub template_dir: PathBuf,
45
46    #[arg(long, short)]
47    pub path: Option<PathBuf>,
48}
49
50#[derive(Args)]
51pub struct CodegenArgs {
52    /// name of the library containing your Crux App
53    #[arg(long, short, value_name = "STRING")]
54    pub lib: String,
55    /// Output directory for generated code
56    #[arg(
57        long,
58        short,
59        value_name = "DIR",
60        value_hint = DirPath,
61        default_value = "./shared/generated",
62    )]
63    pub output: PathBuf,
64    /// Java package name
65    #[arg(
66        long,
67        short,
68        value_name = "dotted.case",
69        value_parser = dotted_case,
70        default_value = "com.crux.example.shared.types"
71    )]
72    pub java_package: String,
73    /// Swift package name
74    #[arg(
75        long,
76        short,
77        value_name = "PascalCase",
78        value_parser = pascal_case,
79        default_value = "SharedTypes")]
80    pub swift_package: String,
81    /// TypeScript package name
82    #[arg(
83        long,
84        short,
85        value_name = "snake_case",
86        value_parser = snake_case,
87        default_value = "shared_types")]
88    pub typescript_package: String,
89}
90
91fn dotted_case(s: &str) -> Result<String, String> {
92    if s == s.to_snake_case().replace('_', ".") {
93        Ok(s.to_string())
94    } else {
95        Err(format!("Invalid dotted case: {}", s))
96    }
97}
98
99fn pascal_case(s: &str) -> Result<String, String> {
100    if s == s.to_pascal_case() {
101        Ok(s.to_string())
102    } else {
103        Err(format!("Invalid pascal case: {}", s))
104    }
105}
106
107fn snake_case(s: &str) -> Result<String, String> {
108    if s == s.to_snake_case() {
109        Ok(s.to_string())
110    } else {
111        Err(format!("Invalid snake case: {}", s))
112    }
113}
114
115#[cfg(test)]
116mod cli_tests {
117    use super::*;
118
119    #[test]
120    fn test_cli() {
121        use clap::CommandFactory;
122        Cli::command().debug_assert();
123    }
124}