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};
7use baobao_ir::{DefaultValue, Input, InputKind, InputType};
8
9use crate::builder::CodeFragment;
10
11/// Convert IR InputType to core ArgType.
12///
13/// This is a convenience function for adapters that need to work with both
14/// IR types and legacy ArgType-based APIs.
15pub fn input_type_to_arg_type(input_type: InputType) -> ArgType {
16    match input_type {
17        InputType::String => ArgType::String,
18        InputType::Int => ArgType::Int,
19        InputType::Float => ArgType::Float,
20        InputType::Bool => ArgType::Bool,
21        InputType::Path => ArgType::Path,
22    }
23}
24
25/// IR-based argument metadata for code generation.
26///
27/// This provides a language-agnostic representation of command arguments
28/// built from IR types.
29#[derive(Debug, Clone)]
30pub struct IRArgMeta {
31    /// Argument name
32    pub name: String,
33    /// Snake case name for field
34    pub field_name: String,
35    /// Argument type
36    pub arg_type: ArgType,
37    /// Whether this argument is required
38    pub required: bool,
39    /// Default value (if any)
40    pub default: Option<DefaultValue>,
41    /// Argument description
42    pub description: Option<String>,
43    /// Allowed choices (if any)
44    pub choices: Option<Vec<String>>,
45}
46
47impl IRArgMeta {
48    /// Create from IR Input (for positional arguments).
49    pub fn from_input(input: &Input, field_name: impl Into<String>) -> Self {
50        Self {
51            name: input.name.clone(),
52            field_name: field_name.into(),
53            arg_type: input_type_to_arg_type(input.ty),
54            required: input.required,
55            default: input.default.clone(),
56            description: input.description.clone(),
57            choices: input.choices.clone(),
58        }
59    }
60}
61
62/// IR-based flag metadata for code generation.
63///
64/// This provides a language-agnostic representation of command flags
65/// built from IR types.
66#[derive(Debug, Clone)]
67pub struct IRFlagMeta {
68    /// Flag name (long form)
69    pub name: String,
70    /// Snake case name for field
71    pub field_name: String,
72    /// Short flag character
73    pub short: Option<char>,
74    /// Flag type
75    pub flag_type: ArgType,
76    /// Default value (if any)
77    pub default: Option<DefaultValue>,
78    /// Flag description
79    pub description: Option<String>,
80    /// Allowed choices (if any)
81    pub choices: Option<Vec<String>>,
82}
83
84impl IRFlagMeta {
85    /// Create from IR Input (for flags).
86    pub fn from_input(input: &Input, field_name: impl Into<String>) -> Self {
87        let short = if let InputKind::Flag { short } = &input.kind {
88            *short
89        } else {
90            None
91        };
92
93        Self {
94            name: input.name.clone(),
95            field_name: field_name.into(),
96            short,
97            flag_type: input_type_to_arg_type(input.ty),
98            default: input.default.clone(),
99            description: input.description.clone(),
100            choices: input.choices.clone(),
101        }
102    }
103}
104
105/// Dependency specification for an adapter.
106#[derive(Debug, Clone)]
107pub struct Dependency {
108    /// Package/crate name
109    pub name: String,
110    /// Version specification (e.g., "1.0", "^0.5.0", `{ version = "4", features = ["derive"] }`)
111    pub version: String,
112    /// Whether this is a dev dependency
113    pub dev: bool,
114}
115
116impl Dependency {
117    /// Create a new runtime dependency.
118    pub fn new(name: impl Into<String>, version: impl Into<String>) -> Self {
119        Self {
120            name: name.into(),
121            version: version.into(),
122            dev: false,
123        }
124    }
125
126    /// Create a new dev dependency.
127    pub fn dev(name: impl Into<String>, version: impl Into<String>) -> Self {
128        Self {
129            name: name.into(),
130            version: version.into(),
131            dev: true,
132        }
133    }
134}
135
136/// Import specification for generated code.
137#[derive(Debug, Clone)]
138pub struct ImportSpec {
139    /// Module/package path
140    pub module: String,
141    /// Symbols to import (empty = import module itself)
142    pub symbols: Vec<String>,
143    /// Whether this is a type-only import (TypeScript)
144    pub type_only: bool,
145}
146
147impl ImportSpec {
148    /// Create a new import specification.
149    pub fn new(module: impl Into<String>) -> Self {
150        Self {
151            module: module.into(),
152            symbols: Vec::new(),
153            type_only: false,
154        }
155    }
156
157    /// Add a symbol to import.
158    pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
159        self.symbols.push(symbol.into());
160        self
161    }
162
163    /// Add multiple symbols to import.
164    pub fn symbols(mut self, symbols: impl IntoIterator<Item = impl Into<String>>) -> Self {
165        self.symbols.extend(symbols.into_iter().map(Into::into));
166        self
167    }
168
169    /// Mark as type-only import (for TypeScript).
170    pub fn type_only(mut self) -> Self {
171        self.type_only = true;
172        self
173    }
174}
175
176/// Framework-agnostic CLI application info.
177#[derive(Debug, Clone)]
178pub struct CliInfo {
179    /// Application name
180    pub name: String,
181    /// Application version
182    pub version: Version,
183    /// Application description
184    pub description: Option<String>,
185    /// Top-level commands
186    pub commands: Vec<CommandMeta>,
187    /// Whether any command uses async
188    pub is_async: bool,
189}
190
191/// Framework-agnostic command metadata.
192#[derive(Debug, Clone)]
193pub struct CommandMeta {
194    /// Command name (e.g., "hello", "db")
195    pub name: String,
196    /// Pascal case name (e.g., "Hello", "Db")
197    pub pascal_name: String,
198    /// Snake case name (e.g., "hello", "db")
199    pub snake_name: String,
200    /// Command description
201    pub description: String,
202    /// Positional arguments
203    pub args: Vec<ArgMeta>,
204    /// Optional flags
205    pub flags: Vec<FlagMeta>,
206    /// Whether this command has subcommands
207    pub has_subcommands: bool,
208    /// Subcommands (if any)
209    pub subcommands: Vec<SubcommandMeta>,
210}
211
212/// Framework-agnostic positional argument metadata.
213#[derive(Debug, Clone)]
214pub struct ArgMeta {
215    /// Argument name
216    pub name: String,
217    /// Snake case name for field
218    pub field_name: String,
219    /// Argument type
220    pub arg_type: ArgType,
221    /// Whether this argument is required
222    pub required: bool,
223    /// Default value (if any)
224    pub default: Option<String>,
225    /// Argument description
226    pub description: Option<String>,
227}
228
229/// Framework-agnostic flag metadata.
230#[derive(Debug, Clone)]
231pub struct FlagMeta {
232    /// Flag name (long form, e.g., "verbose")
233    pub name: String,
234    /// Snake case name for field
235    pub field_name: String,
236    /// Short flag character (e.g., 'v')
237    pub short: Option<char>,
238    /// Flag type
239    pub flag_type: ArgType,
240    /// Default value (if any)
241    pub default: Option<String>,
242    /// Flag description
243    pub description: Option<String>,
244}
245
246/// Framework-agnostic subcommand metadata.
247#[derive(Debug, Clone)]
248pub struct SubcommandMeta {
249    /// Subcommand name
250    pub name: String,
251    /// Pascal case name
252    pub pascal_name: String,
253    /// Snake case name
254    pub snake_name: String,
255    /// Subcommand description
256    pub description: String,
257    /// Whether this subcommand has its own subcommands
258    pub has_subcommands: bool,
259}
260
261/// Info needed to generate command dispatch logic.
262#[derive(Debug, Clone)]
263pub struct DispatchInfo {
264    /// Parent command name (pascal case)
265    pub parent_name: String,
266    /// Subcommands to dispatch to
267    pub subcommands: Vec<SubcommandMeta>,
268    /// Handler module path prefix
269    pub handler_path: String,
270    /// Whether dispatch is async
271    pub is_async: bool,
272}
273
274/// Trait for CLI framework adapters.
275///
276/// Implement this trait to support a specific CLI framework (clap, boune, etc.).
277pub trait CliAdapter {
278    /// Adapter name for identification.
279    fn name(&self) -> &'static str;
280
281    /// Dependencies required by this adapter.
282    fn dependencies(&self) -> Vec<Dependency>;
283
284    /// Generate the main CLI entry point structure/definition.
285    fn generate_cli(&self, info: &CliInfo) -> Vec<CodeFragment>;
286
287    /// Generate a command definition (args struct, command object, etc.).
288    fn generate_command(&self, info: &CommandMeta) -> Vec<CodeFragment>;
289
290    /// Generate subcommand enum/routing for a parent command.
291    fn generate_subcommands(&self, info: &CommandMeta) -> Vec<CodeFragment>;
292
293    /// Generate dispatch logic for routing to handlers.
294    fn generate_dispatch(&self, info: &DispatchInfo) -> Vec<CodeFragment>;
295
296    /// Imports needed for CLI code.
297    fn imports(&self) -> Vec<ImportSpec>;
298
299    /// Imports needed for a specific command.
300    fn command_imports(&self, info: &CommandMeta) -> Vec<ImportSpec>;
301
302    /// Map an argument type to the adapter's type string.
303    fn map_arg_type(&self, arg_type: ArgType) -> &'static str;
304
305    /// Map an optional argument type to the adapter's type string.
306    fn map_optional_type(&self, arg_type: ArgType) -> String;
307}