1#![recursion_limit = "256"]
2
3mod tokens;
4mod errors;
5mod tools;
6
7extern crate proc_macro;
8
9use proc_macro::{ TokenStream };
10use proc_macro2::{ TokenStream as TokenStream2, Span as Span2 };
11use std::collections::HashMap;
12
13use lazy_static::lazy_static;
14use quote::quote;
15use syn::{ Ident, ItemFn, parse_macro_input };
16use tokens::{ CommandToken, OptionsToken};
17
18use crate::errors::{DON_NOT_MATCH, ENTRY_ONLY_MAIN, NO_SUB_CMD_NAMES_MAIN, OPT_DUPLICATE_DEFINITION, compile_error_info, DIRECT_ONLY_ONCE};
19use crate::tools::generate_call_fn;
20use crate::tokens::{PureArguments, check_arguments};
21use syn::spanned::Spanned;
22
23macro_rules! prefix {
24 ($($i: tt),*) => {
25 {
26 let mut prefix_str = String::new();
27
28 prefix_str.push_str("_commander_rust");
29 $(
30 prefix_str.push_str(&format!("_{}", $i));
31 )*
32 prefix_str
33 }
34 };
35
36 ($e: expr) => {
37 {
38 format!("_commander_rust_{}", $e)
39 }
40 }
41}
42
43macro_rules! import {
44 ($o: ident as $r: ident) => {
45 quote! {
46 use commander_rust::{ $o as $r };
47 }
48 };
49 ($o: ident as $r: ident from $f: path) => {
50 quote! {
51 use $f::{ $o as $r };
52 }
53 }
54}
55
56lazy_static! {
57 static ref COMMAND_OPTIONS: std::sync::Mutex<HashMap<String, Vec<String>>> = std::sync::Mutex::new(HashMap::new());
58 static ref OPTIONS: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(vec![]);
59 static ref GET_FN_NAMES: std::sync::Mutex<Vec<String>> = std::sync::Mutex::new(vec![]);
60 static ref DIRECT_NAME: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
61}
62
63
64#[proc_macro_attribute]
77pub fn command(cmd: TokenStream, method: TokenStream) -> TokenStream {
78 let method: ItemFn = parse_macro_input!(method as ItemFn);
79 let ItemFn {
80 ident,
81 decl,
82 ..
83 } = &method;
84 let name = format!("{}", ident);
85 let get_fn = Ident::new(&prefix!(name), ident.span());
86 let cmd_token = Ident::new(&prefix!("Command"), ident.span());
87 let opts = COMMAND_OPTIONS.lock().unwrap();
88 let mut get_fn_names = GET_FN_NAMES.lock().unwrap();
89 let mut get_fns = vec![];
90
91 OPTIONS.lock().unwrap().clear();
93
94 if let Some(v) = opts.get(&name) {
95 for i in v {
96 get_fns.push(Ident::new(&prefix!(name, i), ident.span()));
97 }
98 }
99
100 if !get_fn_names.contains(&name) {
101 get_fn_names.push(name.clone());
102 }
103
104 let command: CommandToken = parse_macro_input!(cmd as CommandToken);
105 let call_fn_name = Ident::new(&prefix!(name, "call"), ident.span());
107 let call_fn = generate_call_fn(&decl.inputs, &call_fn_name, &ident);
108 let mut error_info = check_arguments(&command.args);
109
110 if format!("{}", command.name) != "main" && format!("{}", command.name) != name {
111 error_info = compile_error_info(command.name.span(), DON_NOT_MATCH);
112 }
113
114 if name == "main" {
115 error_info = compile_error_info(ident.span(), NO_SUB_CMD_NAMES_MAIN)
116 }
117
118 TokenStream::from(quote! {
119 #error_info
120
121 fn #get_fn() -> #cmd_token {
122 let mut command = #command;
123 let mut fns = CALL_FNS.lock().unwrap();
124
125 if !fns.contains_key(#name) {
126 fns.insert(String::from(#name), #call_fn_name);
127 }
128
129 command.opts = {
130 let mut v = vec![];
131 #(
132 v.push(#get_fns());
133 )*
134 v
135 };
136 command
137 }
138
139 #call_fn
140
141 #method
142 })
143}
144
145
146#[proc_macro_attribute]
162pub fn option(opt: TokenStream, method: TokenStream) -> TokenStream {
163 let option: OptionsToken = parse_macro_input!(opt as OptionsToken);
164 let method: ItemFn = parse_macro_input!(method as ItemFn);
165 let ItemFn {
166 ident,
167 ..
168 } = &method;
169 let name = format!("{}", ident);
170 let opt_name = format!("{}", option.long);
171 let fn_name = prefix!(name, opt_name);
172 let get_fn = Ident::new(&fn_name, option.long.span());
173 let opt_token = Ident::new(&prefix!("Options"), ident.span());
174 let mut opts = COMMAND_OPTIONS.lock().unwrap();
175 let mut error_info = TokenStream2::new();
176 let mut all_opts = OPTIONS.lock().unwrap();
177
178
179 if all_opts.contains(&format!("{}", option.short)) {
181 error_info = compile_error_info(option.short.span(), OPT_DUPLICATE_DEFINITION);
182 } else if all_opts.contains(&format!("{}", option.long)) {
183 error_info = compile_error_info(option.long.span(), OPT_DUPLICATE_DEFINITION);
184 } else {
185 all_opts.push(format!("{}", option.short));
186 all_opts.push(format!("{}", option.long));
187
188 if opts.contains_key(&name) {
189 if let Some(v) = opts.get_mut(&name) {
190 v.push(opt_name);
191 }
192 } else {
193 opts.insert(name, vec![opt_name]);
194 }
195 }
196
197 if error_info.is_empty() {
198 TokenStream::from(quote! {
199 #error_info
200
201 fn #get_fn() -> #opt_token {
202 #option
203 }
204
205 #method
206 })
207 } else {
208 TokenStream::from(quote! {
209 #error_info
210
211 #method
212 })
213 }
214
215}
216
217#[proc_macro_attribute]
237pub fn direct(pure_args: TokenStream, func: TokenStream) -> TokenStream {
238 let func: ItemFn = parse_macro_input!(func as ItemFn);
239 let ItemFn {
240 ident,
241 decl,
242 ..
243 } = &func;
244 let name = format!("{}", ident);
245 let pure_args: PureArguments = parse_macro_input!(pure_args as PureArguments);
246 let direct_fn: &mut Option<String> = &mut (*DIRECT_NAME.lock().unwrap());
247 let direct_get_fn = Ident::new(&prefix!(name), ident.span());
248 let argument_ident = Ident::new(&prefix!("Argument"), ident.span());
249 let call_fn_name = Ident::new(&prefix!(name, "call"), ident.span());
250 let call_fn = generate_call_fn(&decl.inputs, &call_fn_name, &ident);
251 let mut error_info: TokenStream2 = check_arguments(&pure_args.0);
252
253
254 if let Some(_) = direct_fn {
255 error_info = compile_error_info(pure_args.span(), DIRECT_ONLY_ONCE);
256 } else {
257 *direct_fn = Some(format!("{}", ident));
258 }
259
260 TokenStream::from(quote! {
261 #error_info
262
263 #func
264
265 fn #direct_get_fn() -> Vec<#argument_ident> {
266 let direct_fn: &mut Option<fn(raws: &Vec<_commander_rust_Raw>, app: _commander_rust_Cli)> = &mut (*DIRECT_FN.lock().unwrap());
267
268 *direct_fn.borrow_mut() = Some(#call_fn_name);
269 #pure_args
270 }
271
272 #call_fn
273 })
274}
275
276#[proc_macro_attribute]
286pub fn entry(pure_arguments: TokenStream, main: TokenStream) -> TokenStream {
287 let pure_args: PureArguments = parse_macro_input!(pure_arguments as PureArguments);
288 let main: ItemFn = parse_macro_input!(main as ItemFn);
289 let ItemFn {
290 ident,
291 ..
292 } = &main;
293 let target = format!("{}", ident);
294 let opts = COMMAND_OPTIONS.lock().unwrap();
295 let imports = vec![
296 import!(Argument as _commander_rust_Argument),
297 import!(ArgumentType as _commander_rust_ArgumentType),
298 import!(Command as _commander_rust_Command),
299 import!(Options as _commander_rust_Options),
300 import!(Raw as _commander_rust_Raw),
301 import!(normalize as _commander_rust_normalize),
302 import!(Instance as _commander_rust_Instance),
303 import!(ls as _commander_rust_ls),
304 import!(Application as _commander_rust_Application),
305 import!(Cli as _commander_rust_Cli),
306 ];
307 let get_fn = Ident::new(&prefix!("main"), ident.span());
308 let app_token = Ident::new(&prefix!("Application"), ident.span());
309 let get_fn_names = GET_FN_NAMES.lock().unwrap();
310 let direct_fn = &(*DIRECT_NAME.lock().unwrap());
311 let mut get_cmds_fns = vec![];
312 let mut get_opts_fns = vec![];
313 let mut error_info = check_arguments(&pure_args.0);
314
315 if target != String::from("main") {
317 error_info = compile_error_info(ident.span(), ENTRY_ONLY_MAIN);
318 }
319
320 if let Some(v) = opts.get("main") {
321 for i in v {
322 get_opts_fns.push(Ident::new(&prefix!("main", i), ident.span()));
323 }
324 }
325
326 for i in get_fn_names.iter() {
327 get_cmds_fns.push(Ident::new(&prefix!(i), ident.span()));
328 }
329
330 let needed = quote! {
331 #error_info
332 mod _commander_rust_Inner {
333 use crate::_commander_rust_ls;
334 use crate::_commander_rust_Raw;
335 use crate::_commander_rust_Cli;
336
337 type Raw = _commander_rust_Raw;
338 type Map = std::collections::HashMap<String, fn(raws: &Vec<Raw>, app: _commander_rust_Cli)>;
339 type Mutex = std::sync::Mutex<Map>;
340
341 _commander_rust_ls! {
342 pub static ref CALL_FNS: Mutex = Mutex::new(Map::new());
343 pub static ref DIRECT_FN: std::sync::Mutex<Option<fn(raws: &Vec<Raw>, app: _commander_rust_Cli)>> = std::sync::Mutex::new(None);
344 }
345
346 pub const APP_NAME: &'static str = env!("CARGO_PKG_NAME");
347 pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
348 pub const DESCRIPTION: &'static str = env!("CARGO_PKG_DESCRIPTION");
349 }
350
351 use _commander_rust_Inner::{ CALL_FNS, DIRECT_FN, VERSION, DESCRIPTION, APP_NAME };
352 #(#imports)*
353
354 #main
355 };
356
357 if let Some(df) = direct_fn {
359 let direct_get_fn = Ident::new(&prefix!(df), Span2::call_site());
360
361 TokenStream::from(quote! {
362 #needed
363
364 fn #get_fn() -> #app_token {
365 let mut application = #app_token {
366 name: String::from(APP_NAME),
367 desc: String::from(DESCRIPTION),
368 opts: vec![],
369 cmds: vec![],
370 direct_args: vec![],
371 };
372
373 application.opts = {
374 let mut v = vec![];
375 #(
376 v.push(#get_opts_fns());
377 )*
378 v
379 };
380 application.cmds = {
381 let mut v = vec![];
382 #(
383 v.push(#get_cmds_fns());
384 )*
385 v
386 };
387 application.direct_args = #direct_get_fn();
389
390 application
391 }
392 })
393 } else {
394 TokenStream::from(quote!{
395 #needed
396
397 fn #get_fn() -> #app_token {
398 let mut application = #app_token {
399 name: String::from(APP_NAME),
400 desc: String::from(DESCRIPTION),
401 opts: vec![],
402 cmds: vec![],
403 direct_args: vec![],
404 };
405
406 application.opts = {
407 let mut v = vec![];
408 #(
409 v.push(#get_opts_fns());
410 )*
411 v
412 };
413
414 application.cmds = {
415 let mut v = vec![];
416 #(
417 v.push(#get_cmds_fns());
418 )*
419 v
420 };
421 application
422 }
423 })
424 }
425}
426
427#[proc_macro]
433pub fn run(_: TokenStream) -> TokenStream {
434 TokenStream::from(quote! {
435 {
436 let mut app = _commander_rust_main();
439 let ins;
440
441 app.derive();
442 ins = _commander_rust_normalize(std::env::args().into_iter().collect::<Vec<String>>(), &app);
443
444 let cli = _commander_rust_Cli::from(&ins, &app);
445 let fns = CALL_FNS.lock().unwrap();
446
447 if let Some(cli) = cli {
448 if cli.has("help") || cli.has("h") {
449 if cli.cmd.is_some() {
451 for cmd in &app.cmds {
452 if cmd.name == cli.get_name() {
453 println!("{:#?}", cmd);
454 break;
455 }
456 }
457 } else {
458 println!("{:#?}", app);
460 }
461 } else if cli.has("version") || cli.has("V") {
462 println!("version: {}", VERSION);
463 } else {
464 if let Some(callback) = fns.get(&cli.get_name()) {
465 callback(&cli.get_raws(), cli);
466 } else if !cli.direct_args.is_empty() {
467 let df = *DIRECT_FN.lock().unwrap();
468
469 if let Some(f) = &df {
470 f(&cli.direct_args.clone(), cli)
471 } else {
472 println!("ERRRRR");
473 }
474 } else {
475 eprintln!("Unknown usage. Using `{} --help` for more help information.\n", APP_NAME);
476 }
477 }
478 } else {
479 println!("Using `{} --help` for more help information.", APP_NAME);
480 }
481
482 app
483 }
484 })
485}