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 #[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 #[arg(long, short, value_name = "STRING")]
54 pub lib: String,
55 #[arg(
57 long,
58 short,
59 value_name = "DIR",
60 value_hint = DirPath,
61 default_value = "./shared/generated",
62 )]
63 pub output: PathBuf,
64 #[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 #[arg(
75 long,
76 short,
77 value_name = "PascalCase",
78 value_parser = pascal_case,
79 default_value = "SharedTypes")]
80 pub swift_package: String,
81 #[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}