baobao_codegen/adapters/cli.rs
1//! CLI framework adapter abstraction.
2//!
3//! This module defines the [`CliAdapter`] trait for abstracting CLI framework-specific
4//! code generation (clap, argh, boune, commander, etc.).
5
6use baobao_core::{ArgType, Version};
7
8use crate::builder::CodeFragment;
9
10/// Dependency specification for an adapter.
11#[derive(Debug, Clone)]
12pub struct Dependency {
13 /// Package/crate name
14 pub name: String,
15 /// Version specification (e.g., "1.0", "^0.5.0", `{ version = "4", features = ["derive"] }`)
16 pub version: String,
17 /// Whether this is a dev dependency
18 pub dev: bool,
19}
20
21impl Dependency {
22 /// Create a new runtime dependency.
23 pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
24 Self {
25 name: name.into(),
26 version: version.into(),
27 dev: false,
28 }
29 }
30
31 /// Create a new dev dependency.
32 pub fn dev(name: impl Into<String>, version: impl Into<String>) -> Self {
33 Self {
34 name: name.into(),
35 version: version.into(),
36 dev: true,
37 }
38 }
39}
40
41/// Import specification for generated code.
42#[derive(Debug, Clone)]
43pub struct ImportSpec {
44 /// Module/package path
45 pub module: String,
46 /// Symbols to import (empty = import module itself)
47 pub symbols: Vec<String>,
48 /// Whether this is a type-only import (TypeScript)
49 pub type_only: bool,
50}
51
52impl ImportSpec {
53 /// Create a new import specification.
54 pub fn new(module: impl Into<String>) -> Self {
55 Self {
56 module: module.into(),
57 symbols: Vec::new(),
58 type_only: false,
59 }
60 }
61
62 /// Add a symbol to import.
63 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
64 self.symbols.push(symbol.into());
65 self
66 }
67
68 /// Add multiple symbols to import.
69 pub fn symbols(mut self, symbols: impl IntoIterator<Item = impl Into<String>>) -> Self {
70 self.symbols.extend(symbols.into_iter().map(Into::into));
71 self
72 }
73
74 /// Mark as type-only import (for TypeScript).
75 pub fn type_only(mut self) -> Self {
76 self.type_only = true;
77 self
78 }
79}
80
81/// Framework-agnostic CLI application info.
82#[derive(Debug, Clone)]
83pub struct CliInfo {
84 /// Application name
85 pub name: String,
86 /// Application version
87 pub version: Version,
88 /// Application description
89 pub description: Option<String>,
90 /// Top-level commands
91 pub commands: Vec<CommandMeta>,
92 /// Whether any command uses async
93 pub is_async: bool,
94}
95
96/// Framework-agnostic command metadata.
97#[derive(Debug, Clone)]
98pub struct CommandMeta {
99 /// Command name (e.g., "hello", "db")
100 pub name: String,
101 /// Pascal case name (e.g., "Hello", "Db")
102 pub pascal_name: String,
103 /// Snake case name (e.g., "hello", "db")
104 pub snake_name: String,
105 /// Command description
106 pub description: String,
107 /// Positional arguments
108 pub args: Vec<ArgMeta>,
109 /// Optional flags
110 pub flags: Vec<FlagMeta>,
111 /// Whether this command has subcommands
112 pub has_subcommands: bool,
113 /// Subcommands (if any)
114 pub subcommands: Vec<SubcommandMeta>,
115}
116
117/// Framework-agnostic positional argument metadata.
118#[derive(Debug, Clone)]
119pub struct ArgMeta {
120 /// Argument name
121 pub name: String,
122 /// Snake case name for field
123 pub field_name: String,
124 /// Argument type
125 pub arg_type: ArgType,
126 /// Whether this argument is required
127 pub required: bool,
128 /// Default value (if any)
129 pub default: Option<String>,
130 /// Argument description
131 pub description: Option<String>,
132}
133
134/// Framework-agnostic flag metadata.
135#[derive(Debug, Clone)]
136pub struct FlagMeta {
137 /// Flag name (long form, e.g., "verbose")
138 pub name: String,
139 /// Snake case name for field
140 pub field_name: String,
141 /// Short flag character (e.g., 'v')
142 pub short: Option<char>,
143 /// Flag type
144 pub flag_type: ArgType,
145 /// Default value (if any)
146 pub default: Option<String>,
147 /// Flag description
148 pub description: Option<String>,
149}
150
151/// Framework-agnostic subcommand metadata.
152#[derive(Debug, Clone)]
153pub struct SubcommandMeta {
154 /// Subcommand name
155 pub name: String,
156 /// Pascal case name
157 pub pascal_name: String,
158 /// Snake case name
159 pub snake_name: String,
160 /// Subcommand description
161 pub description: String,
162 /// Whether this subcommand has its own subcommands
163 pub has_subcommands: bool,
164}
165
166/// Info needed to generate command dispatch logic.
167#[derive(Debug, Clone)]
168pub struct DispatchInfo {
169 /// Parent command name (pascal case)
170 pub parent_name: String,
171 /// Subcommands to dispatch to
172 pub subcommands: Vec<SubcommandMeta>,
173 /// Handler module path prefix
174 pub handler_path: String,
175 /// Whether dispatch is async
176 pub is_async: bool,
177}
178
179/// Trait for CLI framework adapters.
180///
181/// Implement this trait to support a specific CLI framework (clap, boune, etc.).
182pub trait CliAdapter {
183 /// Adapter name for identification.
184 fn name(&self) -> &'static str;
185
186 /// Dependencies required by this adapter.
187 fn dependencies(&self) -> Vec<Dependency>;
188
189 /// Generate the main CLI entry point structure/definition.
190 fn generate_cli(&self, info: &CliInfo) -> Vec<CodeFragment>;
191
192 /// Generate a command definition (args struct, command object, etc.).
193 fn generate_command(&self, info: &CommandMeta) -> Vec<CodeFragment>;
194
195 /// Generate subcommand enum/routing for a parent command.
196 fn generate_subcommands(&self, info: &CommandMeta) -> Vec<CodeFragment>;
197
198 /// Generate dispatch logic for routing to handlers.
199 fn generate_dispatch(&self, info: &DispatchInfo) -> Vec<CodeFragment>;
200
201 /// Imports needed for CLI code.
202 fn imports(&self) -> Vec<ImportSpec>;
203
204 /// Imports needed for a specific command.
205 fn command_imports(&self, info: &CommandMeta) -> Vec<ImportSpec>;
206
207 /// Map an argument type to the adapter's type string.
208 fn map_arg_type(&self, arg_type: ArgType) -> &'static str;
209
210 /// Map an optional argument type to the adapter's type string.
211 fn map_optional_type(&self, arg_type: ArgType) -> String;
212}