1 use crate::{ast, update};
2
3 use std::{fmt::Write, path::Path};
4
emit(xflags: &ast::XFlags) -> String5 pub(crate) fn emit(xflags: &ast::XFlags) -> String {
6 let mut buf = String::new();
7
8 emit_cmd(&mut buf, &xflags.cmd);
9 blank_line(&mut buf);
10 emit_api(&mut buf, xflags);
11
12 if std::env::var("UPDATE_XFLAGS").is_ok() {
13 if let Some(src) = &xflags.src {
14 update::in_place(&buf, Path::new(src.as_str()))
15 } else {
16 update::stdout(&buf);
17 }
18 }
19
20 if xflags.src.is_some() {
21 buf.clear()
22 }
23
24 blank_line(&mut buf);
25 emit_impls(&mut buf, &xflags);
26 emit_help(&mut buf, xflags);
27
28 buf
29 }
30
31 macro_rules! w {
32 ($($tt:tt)*) => {
33 drop(write!($($tt)*))
34 };
35 }
36
emit_cmd(buf: &mut String, cmd: &ast::Cmd)37 fn emit_cmd(buf: &mut String, cmd: &ast::Cmd) {
38 w!(buf, "#[derive(Debug)]\n");
39 w!(buf, "pub struct {}", cmd.ident());
40 if cmd.args.is_empty() && cmd.flags.is_empty() && cmd.subcommands.is_empty() {
41 w!(buf, ";\n");
42 return;
43 }
44 w!(buf, " {{\n");
45
46 for arg in &cmd.args {
47 let ty = gen_arg_ty(arg.arity, &arg.val.ty);
48 w!(buf, " pub {}: {},\n", arg.val.ident(), ty);
49 }
50
51 if !cmd.args.is_empty() && !cmd.flags.is_empty() {
52 blank_line(buf);
53 }
54
55 for flag in &cmd.flags {
56 let ty = gen_flag_ty(flag.arity, flag.val.as_ref().map(|it| &it.ty));
57 w!(buf, " pub {}: {},\n", flag.ident(), ty);
58 }
59
60 if cmd.has_subcommands() {
61 w!(buf, " pub subcommand: {},\n", cmd.cmd_enum_ident());
62 }
63 w!(buf, "}}\n");
64
65 if cmd.has_subcommands() {
66 blank_line(buf);
67 w!(buf, "#[derive(Debug)]\n");
68 w!(buf, "pub enum {} {{\n", cmd.cmd_enum_ident());
69 for sub in &cmd.subcommands {
70 let name = camel(&sub.name);
71 w!(buf, " {}({}),\n", name, name);
72 }
73 w!(buf, "}}\n");
74
75 for sub in &cmd.subcommands {
76 blank_line(buf);
77 emit_cmd(buf, sub);
78 }
79 }
80 }
81
gen_flag_ty(arity: ast::Arity, ty: Option<&ast::Ty>) -> String82 fn gen_flag_ty(arity: ast::Arity, ty: Option<&ast::Ty>) -> String {
83 match ty {
84 None => match arity {
85 ast::Arity::Optional => "bool".to_string(),
86 ast::Arity::Required => "()".to_string(),
87 ast::Arity::Repeated => "u32".to_string(),
88 },
89 Some(ty) => gen_arg_ty(arity, ty),
90 }
91 }
92
gen_arg_ty(arity: ast::Arity, ty: &ast::Ty) -> String93 fn gen_arg_ty(arity: ast::Arity, ty: &ast::Ty) -> String {
94 let ty = match ty {
95 ast::Ty::PathBuf => "PathBuf".into(),
96 ast::Ty::OsString => "OsString".into(),
97 ast::Ty::FromStr(it) => it.clone(),
98 };
99 match arity {
100 ast::Arity::Optional => format!("Option<{}>", ty),
101 ast::Arity::Required => ty,
102 ast::Arity::Repeated => format!("Vec<{}>", ty),
103 }
104 }
105
emit_api(buf: &mut String, xflags: &ast::XFlags)106 fn emit_api(buf: &mut String, xflags: &ast::XFlags) {
107 w!(buf, "impl {} {{\n", camel(&xflags.cmd.name));
108
109 w!(buf, " pub const HELP: &'static str = Self::HELP_;\n");
110 blank_line(buf);
111
112 w!(buf, " #[allow(dead_code)]\n");
113 w!(buf, " pub fn from_env() -> xflags::Result<Self> {{\n");
114 w!(buf, " Self::from_env_()\n");
115 w!(buf, " }}\n");
116 blank_line(buf);
117
118 w!(buf, " #[allow(dead_code)]\n");
119 w!(buf, " pub fn from_vec(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {{\n");
120 w!(buf, " Self::from_vec_(args)\n");
121 w!(buf, " }}\n");
122 w!(buf, "}}\n");
123 }
124
emit_impls(buf: &mut String, xflags: &ast::XFlags) -> ()125 fn emit_impls(buf: &mut String, xflags: &ast::XFlags) -> () {
126 w!(buf, "impl {} {{\n", camel(&xflags.cmd.name));
127 w!(buf, " fn from_env_() -> xflags::Result<Self> {{\n");
128 w!(buf, " let mut p = xflags::rt::Parser::new_from_env();\n");
129 w!(buf, " Self::parse_(&mut p)\n");
130 w!(buf, " }}\n");
131 w!(buf, " fn from_vec_(args: Vec<std::ffi::OsString>) -> xflags::Result<Self> {{\n");
132 w!(buf, " let mut p = xflags::rt::Parser::new(args);\n");
133 w!(buf, " Self::parse_(&mut p)\n");
134 w!(buf, " }}\n");
135 w!(buf, "}}\n");
136 blank_line(buf);
137 emit_impls_rec(buf, &xflags.cmd)
138 }
139
emit_impls_rec(buf: &mut String, cmd: &ast::Cmd) -> ()140 fn emit_impls_rec(buf: &mut String, cmd: &ast::Cmd) -> () {
141 emit_impl(buf, cmd);
142 for sub in &cmd.subcommands {
143 blank_line(buf);
144 emit_impls_rec(buf, sub);
145 }
146 }
147
emit_impl(buf: &mut String, cmd: &ast::Cmd) -> ()148 fn emit_impl(buf: &mut String, cmd: &ast::Cmd) -> () {
149 w!(buf, "impl {} {{\n", camel(&cmd.name));
150 w!(buf, "fn parse_(p_: &mut xflags::rt::Parser) -> xflags::Result<Self> {{\n");
151
152 for flag in &cmd.flags {
153 w!(buf, "let mut {} = Vec::new();\n", flag.ident());
154 }
155 blank_line(buf);
156
157 if !cmd.args.is_empty() {
158 for arg in &cmd.args {
159 w!(buf, "let mut {} = (false, Vec::new());\n", arg.val.ident());
160 }
161 blank_line(buf);
162 }
163
164 if cmd.has_subcommands() {
165 w!(buf, "let mut sub_ = None;");
166 blank_line(buf);
167 }
168
169 w!(buf, "while let Some(arg_) = p_.pop_flag() {{\n");
170 w!(buf, "match arg_ {{\n");
171 {
172 w!(buf, "Ok(flag_) => match flag_.as_str() {{\n");
173 for flag in &cmd.flags {
174 w!(buf, "\"--{}\"", flag.name);
175 if let Some(short) = &flag.short {
176 w!(buf, "| \"-{}\"", short);
177 }
178 w!(buf, " => {}.push(", flag.ident());
179 match &flag.val {
180 Some(val) => match &val.ty {
181 ast::Ty::OsString | ast::Ty::PathBuf => {
182 w!(buf, "p_.next_value(&flag_)?.into()")
183 }
184 ast::Ty::FromStr(ty) => {
185 w!(buf, "p_.next_value_from_str::<{}>(&flag_)?", ty)
186 }
187 },
188 None => w!(buf, "()"),
189 }
190 w!(buf, "),");
191 }
192 if cmd.default_subcommand().is_some() {
193 w!(buf, "_ => {{ p_.push_back(Ok(flag_)); break; }}");
194 } else {
195 w!(buf, "_ => return Err(p_.unexpected_flag(&flag_)),");
196 }
197 w!(buf, "}}\n");
198 }
199 {
200 w!(buf, "Err(arg_) => {{\n");
201 if cmd.has_subcommands() {
202 w!(buf, "match arg_.to_str().unwrap_or(\"\") {{\n");
203 for sub in cmd.named_subcommands() {
204 w!(buf, "\"{}\" => {{\n", sub.name);
205 w!(
206 buf,
207 "sub_ = Some({}::{}({}::parse_(p_)?));",
208 cmd.cmd_enum_ident(),
209 sub.ident(),
210 sub.ident()
211 );
212 w!(buf, "break;");
213 w!(buf, "}}\n");
214 }
215 w!(buf, "_ => (),\n");
216 w!(buf, "}}\n");
217 }
218
219 for arg in &cmd.args {
220 let done = match arg.arity {
221 ast::Arity::Optional | ast::Arity::Required => "done_ @ ",
222 ast::Arity::Repeated => "",
223 };
224 w!(buf, "if let ({}false, buf_) = &mut {} {{\n", done, arg.val.ident());
225 w!(buf, "buf_.push(");
226 match &arg.val.ty {
227 ast::Ty::OsString | ast::Ty::PathBuf => {
228 w!(buf, "arg_.into()")
229 }
230 ast::Ty::FromStr(ty) => {
231 w!(buf, "p_.value_from_str::<{}>(\"{}\", arg_)?", ty, arg.val.name);
232 }
233 }
234 w!(buf, ");\n");
235 match arg.arity {
236 ast::Arity::Optional | ast::Arity::Required => {
237 w!(buf, "*done_ = true;\n");
238 }
239 ast::Arity::Repeated => (),
240 }
241 w!(buf, "continue;\n");
242 w!(buf, "}}\n");
243 }
244 if cmd.default_subcommand().is_some() {
245 w!(buf, "p_.push_back(Err(arg_)); break;");
246 } else {
247 w!(buf, "return Err(p_.unexpected_arg(arg_));");
248 }
249
250 w!(buf, "}}\n");
251 }
252 w!(buf, "}}\n");
253 w!(buf, "}}\n");
254
255 if let Some(sub) = cmd.default_subcommand() {
256 w!(buf, "if sub_.is_none() {{\n");
257 w!(
258 buf,
259 "sub_ = Some({}::{}({}::parse_(p_)?));",
260 cmd.cmd_enum_ident(),
261 sub.ident(),
262 sub.ident()
263 );
264 w!(buf, "}}\n");
265 }
266
267 w!(buf, "Ok(Self {{\n");
268 if !cmd.args.is_empty() {
269 for arg in &cmd.args {
270 let val = &arg.val;
271 w!(buf, "{}: ", val.ident());
272 match arg.arity {
273 ast::Arity::Optional => {
274 w!(buf, "p_.optional(\"{}\", {}.1)?", val.name, val.ident())
275 }
276 ast::Arity::Required => {
277 w!(buf, "p_.required(\"{}\", {}.1)?", val.name, val.ident())
278 }
279 ast::Arity::Repeated => w!(buf, "{}.1", val.ident()),
280 }
281 w!(buf, ",\n");
282 }
283 blank_line(buf);
284 }
285
286 for flag in &cmd.flags {
287 w!(buf, "{}: ", flag.ident());
288 match &flag.val {
289 Some(_val) => match flag.arity {
290 ast::Arity::Optional => {
291 w!(buf, "p_.optional(\"--{}\", {})?", flag.name, flag.ident())
292 }
293 ast::Arity::Required => {
294 w!(buf, "p_.required(\"--{}\", {})?", flag.name, flag.ident())
295 }
296 ast::Arity::Repeated => w!(buf, "{}", flag.ident()),
297 },
298 None => match flag.arity {
299 ast::Arity::Optional => {
300 w!(buf, "p_.optional(\"--{}\", {})?.is_some()", flag.name, flag.ident())
301 }
302 ast::Arity::Required => {
303 w!(buf, "p_.required(\"--{}\", {})?", flag.name, flag.ident())
304 }
305 ast::Arity::Repeated => w!(buf, "{}.len() as u32", flag.ident()),
306 },
307 }
308 w!(buf, ",\n");
309 }
310 if cmd.has_subcommands() {
311 w!(buf, "subcommand: p_.subcommand(sub_)?,\n");
312 }
313 w!(buf, "}})\n");
314
315 w!(buf, "}}\n");
316 w!(buf, "}}\n");
317 }
318
emit_help(buf: &mut String, xflags: &ast::XFlags)319 fn emit_help(buf: &mut String, xflags: &ast::XFlags) {
320 w!(buf, "impl {} {{\n", xflags.cmd.ident());
321
322 let help = {
323 let mut buf = String::new();
324 help_rec(&mut buf, "", &xflags.cmd);
325 buf
326 };
327 let help = format!("{:?}", help);
328 let help = help.replace("\\n", "\n").replacen("\"", "\"\\\n", 1);
329
330 w!(buf, "const HELP_: &'static str = {};", help);
331 w!(buf, "}}\n");
332 }
333
write_lines_indented(buf: &mut String, multiline_str: &str, indent: usize)334 fn write_lines_indented(buf: &mut String, multiline_str: &str, indent: usize) {
335 for line in multiline_str.split('\n').map(str::trim_end) {
336 if line.is_empty() {
337 w!(buf, "\n")
338 } else {
339 w!(buf, "{blank:indent$}{}\n", line, indent = indent, blank = "");
340 }
341 }
342 }
343
help_rec(buf: &mut String, prefix: &str, cmd: &ast::Cmd)344 fn help_rec(buf: &mut String, prefix: &str, cmd: &ast::Cmd) {
345 w!(buf, "{}{}\n", prefix, cmd.name);
346 if let Some(doc) = &cmd.doc {
347 write_lines_indented(buf, doc, 2);
348 }
349 let indent = if prefix.is_empty() { "" } else { " " };
350
351 let args = cmd.args_with_default();
352 if !args.is_empty() {
353 blank_line(buf);
354 w!(buf, "{}ARGS:\n", indent);
355
356 let mut blank = "";
357 for arg in &args {
358 w!(buf, "{}", blank);
359 blank = "\n";
360
361 let (l, r) = match arg.arity {
362 ast::Arity::Optional => ("[", "]"),
363 ast::Arity::Required => ("<", ">"),
364 ast::Arity::Repeated => ("<", ">..."),
365 };
366 w!(buf, " {}{}{}\n", l, arg.val.name, r);
367 if let Some(doc) = &arg.doc {
368 write_lines_indented(buf, doc, 6)
369 }
370 }
371 }
372
373 let flags = cmd.flags_with_default();
374 if !flags.is_empty() {
375 blank_line(buf);
376 w!(buf, "{}OPTIONS:\n", indent);
377
378 let mut blank = "";
379 for flag in &flags {
380 w!(buf, "{}", blank);
381 blank = "\n";
382
383 let short = flag.short.as_ref().map(|it| format!("-{}, ", it)).unwrap_or_default();
384 let value = flag.val.as_ref().map(|it| format!(" <{}>", it.name)).unwrap_or_default();
385 w!(buf, " {}--{}{}\n", short, flag.name, value);
386 if let Some(doc) = &flag.doc {
387 write_lines_indented(buf, doc, 6);
388 }
389 }
390 }
391
392 let subcommands = cmd.named_subcommands();
393 if !subcommands.is_empty() {
394 if prefix.is_empty() {
395 blank_line(buf);
396 w!(buf, "SUBCOMMANDS:");
397 }
398
399 let prefix = format!("{}{} ", prefix, cmd.name);
400 for sub in subcommands {
401 blank_line(buf);
402 blank_line(buf);
403 help_rec(buf, &prefix, sub);
404 }
405 }
406 }
407
408 impl ast::Cmd {
ident(&self) -> String409 fn ident(&self) -> String {
410 camel(&self.name)
411 }
cmd_enum_ident(&self) -> String412 fn cmd_enum_ident(&self) -> String {
413 format!("{}Cmd", self.ident())
414 }
has_subcommands(&self) -> bool415 fn has_subcommands(&self) -> bool {
416 !self.subcommands.is_empty()
417 }
named_subcommands(&self) -> &[ast::Cmd]418 fn named_subcommands(&self) -> &[ast::Cmd] {
419 let start = if self.default { 1 } else { 0 };
420 &self.subcommands[start..]
421 }
default_subcommand(&self) -> Option<&ast::Cmd>422 fn default_subcommand(&self) -> Option<&ast::Cmd> {
423 if self.default {
424 self.subcommands.first()
425 } else {
426 None
427 }
428 }
args_with_default(&self) -> Vec<&ast::Arg>429 fn args_with_default(&self) -> Vec<&ast::Arg> {
430 let mut res = self.args.iter().collect::<Vec<_>>();
431 if let Some(sub) = self.default_subcommand() {
432 res.extend(sub.args_with_default());
433 }
434 res
435 }
flags_with_default(&self) -> Vec<&ast::Flag>436 fn flags_with_default(&self) -> Vec<&ast::Flag> {
437 let mut res = self.flags.iter().collect::<Vec<_>>();
438 if let Some(sub) = self.default_subcommand() {
439 res.extend(sub.flags_with_default())
440 }
441 res
442 }
443 }
444
445 impl ast::Flag {
ident(&self) -> String446 fn ident(&self) -> String {
447 snake(&self.name)
448 }
449 }
450
451 impl ast::Val {
ident(&self) -> String452 fn ident(&self) -> String {
453 snake(&self.name)
454 }
455 }
456
blank_line(buf: &mut String)457 fn blank_line(buf: &mut String) {
458 w!(buf, "\n");
459 }
460
camel(s: &str) -> String461 fn camel(s: &str) -> String {
462 s.split('-').map(first_upper).collect()
463 }
464
first_upper(s: &str) -> String465 fn first_upper(s: &str) -> String {
466 s.chars()
467 .next()
468 .map(|it| it.to_ascii_uppercase())
469 .into_iter()
470 .chain(s.chars().skip(1))
471 .collect()
472 }
473
snake(s: &str) -> String474 fn snake(s: &str) -> String {
475 s.replace('-', "_")
476 }
477
478 #[cfg(test)]
479 mod tests {
480 use std::{
481 fs,
482 io::Write,
483 path::Path,
484 process::{Command, Stdio},
485 };
486
reformat(text: String) -> Option<String>487 fn reformat(text: String) -> Option<String> {
488 let mut rustfmt =
489 Command::new("rustfmt").stdin(Stdio::piped()).stdout(Stdio::piped()).spawn().unwrap();
490 let mut stdin = rustfmt.stdin.take().unwrap();
491 stdin.write_all(text.as_bytes()).unwrap();
492 drop(stdin);
493 let out = rustfmt.wait_with_output().unwrap();
494 let res = String::from_utf8(out.stdout).unwrap();
495 if res.is_empty() {
496 None
497 } else {
498 Some(res)
499 }
500 }
501
update_on_disk_if_different(file: &Path, new_contents: String) -> bool502 fn update_on_disk_if_different(file: &Path, new_contents: String) -> bool {
503 let old_contents = fs::read_to_string(file).unwrap_or_default();
504 if old_contents.trim() == new_contents.trim() {
505 return false;
506 }
507 fs::write(file, new_contents).unwrap();
508 true
509 }
510
511 #[test]
gen_it()512 fn gen_it() {
513 let test_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/it");
514
515 let mut did_update = false;
516 for entry in fs::read_dir(test_dir.join("src")).unwrap() {
517 let entry = entry.unwrap();
518
519 let text = fs::read_to_string(entry.path()).unwrap();
520 let mut lines = text.lines().collect::<Vec<_>>();
521 lines.pop();
522 lines.remove(0);
523 let text = lines.join("\n");
524
525 let res = crate::compile(&text);
526 let fmt = reformat(res.clone());
527
528 let code = format!(
529 "#![allow(unused)]\nuse std::{{ffi::OsString, path::PathBuf}};\n\n{}",
530 fmt.as_deref().unwrap_or(&res)
531 );
532
533 let name = entry.file_name();
534 did_update |= update_on_disk_if_different(&test_dir.join(name), code);
535
536 if fmt.is_none() {
537 panic!("syntax error");
538 }
539 }
540 if did_update {
541 panic!("generated output changed")
542 }
543 }
544 }
545