baobao_codegen_typescript/adapters/
boune.rs1use baobao_codegen::{
4 adapters::{
5 CliAdapter, CliInfo, CommandMeta, Dependency, DispatchInfo, ImportSpec,
6 input_type_to_arg_type,
7 },
8 builder::CodeFragment,
9};
10use baobao_core::ArgType;
11use baobao_ir::{Input, InputKind};
12use baobao_manifest::ArgType as ManifestArgType;
13
14use crate::{
15 BOUNE_VERSION,
16 ast::{ArrowFn, JsArray, JsObject},
17};
18
19#[derive(Debug, Clone, Default)]
21pub struct BouneAdapter;
22
23impl BouneAdapter {
24 pub fn new() -> Self {
25 Self
26 }
27
28 fn convert_manifest_arg_type(arg_type: &ManifestArgType) -> ArgType {
30 match arg_type {
31 ManifestArgType::String => ArgType::String,
32 ManifestArgType::Int => ArgType::Int,
33 ManifestArgType::Float => ArgType::Float,
34 ManifestArgType::Bool => ArgType::Bool,
35 ManifestArgType::Path => ArgType::Path,
36 }
37 }
38
39 pub fn build_argument_schema_manifest(
41 &self,
42 arg_type: &ManifestArgType,
43 required: bool,
44 default: Option<&toml::Value>,
45 description: Option<&str>,
46 choices: Option<&[String]>,
47 ) -> JsObject {
48 self.build_argument_schema(
49 Self::convert_manifest_arg_type(arg_type),
50 required,
51 default,
52 description,
53 choices,
54 )
55 }
56
57 pub fn build_argument_schema(
61 &self,
62 arg_type: ArgType,
63 required: bool,
64 default: Option<&toml::Value>,
65 description: Option<&str>,
66 choices: Option<&[String]>,
67 ) -> JsObject {
68 let boune_type = self.map_arg_type(arg_type);
69
70 JsObject::new()
71 .string("type", boune_type)
72 .raw_if(required && default.is_none(), "required", "true")
73 .toml_opt("default", default)
74 .string_opt("description", description)
75 .array_opt(
76 "choices",
77 choices.map(|c| JsArray::from_strings(c).as_const()),
78 )
79 }
80
81 pub fn build_option_schema_manifest(
83 &self,
84 flag_type: &ManifestArgType,
85 short: Option<char>,
86 default: Option<&toml::Value>,
87 description: Option<&str>,
88 choices: Option<&[String]>,
89 ) -> JsObject {
90 self.build_option_schema(
91 Self::convert_manifest_arg_type(flag_type),
92 short,
93 default,
94 description,
95 choices,
96 )
97 }
98
99 pub fn build_option_schema(
103 &self,
104 flag_type: ArgType,
105 short: Option<char>,
106 default: Option<&toml::Value>,
107 description: Option<&str>,
108 choices: Option<&[String]>,
109 ) -> JsObject {
110 let boune_type = self.map_arg_type(flag_type);
111
112 JsObject::new()
113 .string("type", boune_type)
114 .string_opt("short", short.map(|c| c.to_string()))
115 .toml_opt("default", default)
116 .string_opt("description", description)
117 .array_opt(
118 "choices",
119 choices.map(|c| JsArray::from_strings(c).as_const()),
120 )
121 }
122
123 pub fn map_manifest_arg_type(&self, arg_type: &ManifestArgType) -> &'static str {
125 self.map_arg_type(Self::convert_manifest_arg_type(arg_type))
126 }
127
128 pub fn build_action_handler(&self, has_args: bool, has_options: bool) -> ArrowFn {
130 let params = match (has_args, has_options) {
132 (true, true) => "{ args, options }",
133 (true, false) => "{ args }",
134 (false, true) => "{ options }",
135 (false, false) => "{}",
136 };
137
138 let run_call = match (has_args, has_options) {
140 (true, true) => "await run(args, options);",
141 (true, false) => "await run(args);",
142 (false, true) => "await run(options);",
143 (false, false) => "await run();",
144 };
145
146 ArrowFn::new(params).async_().body_line(run_call)
147 }
148
149 pub fn build_argument_schema_ir(&self, input: &Input) -> JsObject {
155 let boune_type = self.map_arg_type(input_type_to_arg_type(input.ty));
156
157 JsObject::new()
158 .string("type", boune_type)
159 .raw_if(
160 input.required && input.default.is_none(),
161 "required",
162 "true",
163 )
164 .default_value_opt("default", input.default.as_ref())
165 .string_opt("description", input.description.as_deref())
166 .array_opt(
167 "choices",
168 input
169 .choices
170 .as_ref()
171 .map(|c| JsArray::from_strings(c).as_const()),
172 )
173 }
174
175 pub fn build_option_schema_ir(&self, input: &Input) -> JsObject {
177 let boune_type = self.map_arg_type(input_type_to_arg_type(input.ty));
178 let short = if let InputKind::Flag { short } = &input.kind {
179 *short
180 } else {
181 None
182 };
183
184 JsObject::new()
185 .string("type", boune_type)
186 .string_opt("short", short.map(|c| c.to_string()))
187 .default_value_opt("default", input.default.as_ref())
188 .string_opt("description", input.description.as_deref())
189 .array_opt(
190 "choices",
191 input
192 .choices
193 .as_ref()
194 .map(|c| JsArray::from_strings(c).as_const()),
195 )
196 }
197}
198
199impl CliAdapter for BouneAdapter {
200 fn name(&self) -> &'static str {
201 "boune"
202 }
203
204 fn dependencies(&self) -> Vec<Dependency> {
205 vec![Dependency::new("boune", BOUNE_VERSION)]
206 }
207
208 fn generate_cli(&self, info: &CliInfo) -> Vec<CodeFragment> {
209 let mut schema = JsObject::new()
211 .string("name", &info.name)
212 .string("version", info.version.to_string());
213
214 if let Some(desc) = &info.description {
215 schema = schema.string("description", desc);
216 }
217
218 let mut commands_obj = JsObject::new();
220 for cmd in &info.commands {
221 commands_obj =
222 commands_obj.raw(&cmd.pascal_name, format!("{}Command", cmd.pascal_name));
223 }
224 schema = schema.object("commands", commands_obj);
225
226 let code = format!("const app = defineCli({});", schema.build());
228 vec![CodeFragment::raw(code)]
229 }
230
231 fn generate_command(&self, info: &CommandMeta) -> Vec<CodeFragment> {
232 let action = self.build_action_handler(!info.args.is_empty(), !info.flags.is_empty());
234
235 let schema = JsObject::new()
236 .string("name", &info.name)
237 .string("description", &info.description)
238 .raw_if(!info.args.is_empty(), "arguments", "args")
239 .raw_if(!info.flags.is_empty(), "options", "options")
240 .arrow_fn("action", action);
241
242 let code = format!(
243 "export const {}Command = defineCommand({});",
244 info.pascal_name,
245 schema.build()
246 );
247
248 vec![CodeFragment::raw(code)]
249 }
250
251 fn generate_subcommands(&self, info: &CommandMeta) -> Vec<CodeFragment> {
252 let mut subcommands = JsObject::new();
254 for sub in &info.subcommands {
255 subcommands = subcommands.raw(&sub.pascal_name, format!("{}Command", sub.pascal_name));
256 }
257
258 let schema = JsObject::new()
259 .string("name", &info.name)
260 .string("description", &info.description)
261 .object("subcommands", subcommands);
262
263 let code = format!(
264 "export const {}Command = defineCommand({});",
265 info.pascal_name,
266 schema.build()
267 );
268
269 vec![CodeFragment::raw(code)]
270 }
271
272 fn generate_dispatch(&self, _info: &DispatchInfo) -> Vec<CodeFragment> {
273 Vec::new()
276 }
277
278 fn imports(&self) -> Vec<ImportSpec> {
279 vec![ImportSpec::new("boune").symbol("defineCli")]
280 }
281
282 fn command_imports(&self, info: &CommandMeta) -> Vec<ImportSpec> {
283 let mut imports = vec![ImportSpec::new("boune").symbol("defineCommand")];
284
285 if !info.args.is_empty() {
288 imports.push(ImportSpec::new("boune").symbol("InferArgs").type_only());
289 }
290
291 if !info.flags.is_empty() {
292 imports.push(ImportSpec::new("boune").symbol("InferOpts").type_only());
293 }
294
295 imports
296 }
297
298 fn map_arg_type(&self, arg_type: ArgType) -> &'static str {
299 match arg_type {
300 ArgType::String => "string",
301 ArgType::Int => "number",
302 ArgType::Float => "number",
303 ArgType::Bool => "boolean",
304 ArgType::Path => "string",
305 }
306 }
307
308 fn map_optional_type(&self, arg_type: ArgType) -> String {
309 format!("{} | undefined", self.map_arg_type(arg_type))
310 }
311}