command_rpc/
lib.rs

1//! Hey! Nice you like to write with the `cprc` module. It refers to clap, thank you for your great work there!
2//! Unfortunately, you have to import clap in your project yourself with *derive* feature enabled.
3//!
4//! # Quick setup
5//! 
6//! ```
7//! use command_rpc::crpc_main;
8//! 
9//! #[crpc_main]
10//! pub mod my_cli_backend {
11//!    use command_rpc::{cprc_mod, crpc_fn};
12//! 
13//!    #[cprc_fn]
14//!    pub fn greet(
15//!        /// The name of the person you want to greet.
16//!        name: str
17//!     ) {
18//!       eprintln!("Hello, {}!", name);
19//!   }
20//! 
21//!   #[crpc_mod]
22//!   pub mod my_cli_backend_sub {
23//!     use command_rpc::cprc_fn;
24//! 
25//!     #[crpc_fn]
26//!     pub fn friendly_greet(
27//!         /// The name of the person you want to greet.
28//!         name: str,
29//!         /// the adjective you want to use in the greeting.
30//!         adjective: str 
31//!      ) {
32//!        eprintln!("Hello, {}! You are {}!", name, adjective);
33//!      }
34//!    }
35//! }
36//!
37//! fn main() {
38//!    My_cli_mod::parse().delegate();
39//! }
40//! ```
41//!
42//! # Prettier requests to endpoint
43//!
44//! To make call to your endpoint in your Rust programs nicer, you can insert this snippet
45//! that defines a declarative macro (this is why it can´t be just exported from this crate).
46//! Calls in your program would look like this then(Same for other languages will be coming):
47//! ```
48//! callback!(my_cli_backend::greet("John"));
49//! ```
50//! 
51//! ```
52//! macro_rules! callback {
53//! ($inp:expr) => {{
54//!     let mut cmd = $inp.to_string();
55//!     cmd = cmd.replace(";", "").replace(" ", "").replace("\n", "").replace("(", "").replace(")", "").replace("::", " ").replace(",", " ");
56//!     std::thread::spawn(move || {
57//!         let output = std::process::Command::new(cmd)
58//!             .output()
59//!             .expect("Failed to execute command");
60//!         eprintln!("{}", std::string::String::from_utf8_lossy(&output.stdout));
61//!     });
62//! }};
63//! }
64//! ```
65//!
66//!
67//! ---
68///
69/// marked as not working false
70///
71/// # Integration to other languages
72///
73/// ## Python
74///
75/// To make call to your endpoint in your Rust programs nicer, you can insert this snippet
76/// ```
77/// todo!()
78/// ```
79///
80/// TODO: Integration: Javascript, Flutter, Java
81/// TODO: Clean Code conventions
82/// TODO: Docs, Error handling
83/// TODO: use doc attributes for the cli by copying them
84///
85///
86///
87///
88
89
90use proc_macro::TokenStream;
91use quote::{format_ident, quote, ToTokens};
92
93use syn::{
94    Item::{Fn, Mod, Struct},
95    __private::Span,
96    parse_macro_input,
97    token::Brace,
98};
99
100mod build;
101mod build_command;
102mod build_nested;
103mod checks;
104mod return_token;
105
106
107macro_rules! callback {
108    // For things like `callback!(my_cli_backend::...::greet("John"));`, when you want to call a function in your rust backend
109    ($inp:expr) => {{
110        let mut cmd = $inp.to_string();
111        cmd = cmd
112            .replace(";", "")
113            .replace(" ", "")
114            .replace("\n", "")
115            .replace("(", "")
116            .replace(")", "")
117            .replace("::", " ")
118            .replace(",", " ");
119        std::thread::spawn(move || {
120            let output = std::process::Command::new(cmd)
121                .output()
122                .expect("Failed to execute command");
123            eprintln!("{}", std::string::String::from_utf8_lossy(&output.stdout));
124        });
125    }};
126}
127
128/// This macro can be used to parse the input and call the right function without
129/// having to create a pipe. This shortens the code and makes it easier to understand.
130macro_rules! parse {
131    // For the main function, that automatically parses the input and calls the right function - you have to pass the path to the crpc_main module
132    ($inp:expr) => {
133        let mut path = $inp.split("::").collect();
134        let mut module = path.last();
135        module = module[0].uppercase() + module[1..].to_string();
136        path.push(&module);
137        let path = path.join("::");
138        path!($path).delegate();
139    };
140}
141
142// lazy variant! Just mark the main module with [crpc_main], then everything gets expaneded recursively! -> less control..
143
144#[proc_macro_attribute]
145pub fn crpc(attr: TokenStream, item: TokenStream) -> TokenStream {
146    let ts_item = item.clone();
147    // Parse the input tokens into a Rust syntax tree
148    // Generate the output tokens
149    let item = syn::parse_macro_input!(item as syn::Item);
150    match item {
151        Fn(item) if attr.to_string() == "fn" => {
152            // For fn
153            quote! {
154                #[crpc_fn]
155                #item
156            }
157            .into()
158        }
159        Mod(item) if attr.to_string() == "mod" => {
160            // For mod
161            quote! {
162                #[crpc_mod]
163                #item
164            }
165            .into()
166        }
167        Struct(item) if attr.to_string() == "struct" => {
168            // For param test
169            quote! {
170                #[crpc_param]
171                #item
172            }
173            .into()
174        }
175        _ => {
176            eprint!(
177                "Error in {:?}: crpc can only be used on fn, mod and struct",
178                ts_item.to_string()
179            );
180            ts_item
181        }
182    }
183}
184
185#[proc_macro_attribute]
186pub fn crpc_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
187    let item = parse_macro_input!(item as syn::Item);
188    if let Mod(mut item) = item {
189        if let syn::Visibility::Public(_) = item.vis {
190            let (name, sc_name) = build_nested::names(item.ident.to_string());
191            let subcommands = build_nested::subcommands_with_help(item.clone());
192            let sc_enum = build_nested::subcommand_enum(
193                &name.to_string()[..name.to_string().len()].to_string(),
194                subcommands.clone(),
195                sc_name.clone(),
196            );
197            let sc_match = build_nested::delegate_match_expr(subcommands.clone(), &sc_name);
198
199            let (_, mut _body) = item.content.clone().unwrap();
200            _body.insert(0, build::item_use());
201            item.content = Some((Brace(Span::call_site()), _body));
202
203            // Generate the output token
204            return_token::main_token(name, sc_name, sc_enum, sc_match, item.clone())
205        } else {
206            eprintln!(
207                "An item marked with #[crpc_main] must be public and accessible to the binary."
208            );
209            return item.to_token_stream().into();
210        }
211    } else {
212        eprintln!("An item marked with #[crpc_main] must be a module.");
213        return item.to_token_stream().into();
214    }
215}
216
217/// This attribute can be used to mark modules that should be available in the cli.
218/// This works like a subcommand with nested commands; the module name is used as
219/// the subcommand name.
220#[proc_macro_attribute]
221pub fn crpc_mod(_attr: TokenStream, item: TokenStream) -> TokenStream {
222    let item = parse_macro_input!(item as syn::Item);
223    if let Mod(mut item) = item {
224        if let syn::Visibility::Public(_) = item.vis {
225            let (name, sc_name) = build_nested::names(item.ident.to_string());
226            let subcommands = build_nested::subcommands_with_help(item.clone());
227            let sc_enum = build_nested::subcommand_enum(
228                &name.to_string()[..name.to_string().len()].to_string(),
229                subcommands.clone(),
230                sc_name.clone(),
231            );
232            let sc_match = build_nested::delegate_match_expr(subcommands.clone(), &sc_name);
233
234            let (_, mut _body) = item.content.clone().unwrap();
235            _body.insert(0, build::item_use());
236            item.content = Some((Brace(Span::call_site()), _body));
237
238            // Generate the output token
239            return_token::mod_token(name, sc_name, sc_enum, sc_match, item.clone())
240        } else {
241            eprintln!(
242                "An item marked with #[crpc_main] must be public and accessible to the binary."
243            );
244            return item.to_token_stream().into();
245        }
246    } else {
247        eprintln!("An item marked with #[crpc_main] must be a module.");
248        return item.to_token_stream().into();
249    }
250}
251
252/// This attribute can be used to mark functions that should be available in the cli.
253/// This works lika a subcommand without nested commands; function parameters are used
254/// as arguments for the cli. The expansion consists of a struct that holds the arguments
255/// and a function that calls the original function with the arguments.
256#[proc_macro_attribute]
257pub fn crpc_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
258    let item = parse_macro_input!(item as syn::Item);
259
260    // Function check
261    if let Fn(item) = &item {
262        // Public check
263        if let syn::Visibility::Public(_) = item.vis {
264            // Output type check
265            checks::output_check(&item.sig.output); //.clone().into_token_stream().to_string());
266
267            // Input type checks              TODO
268            // if item
269            //     .sig
270            //     .inputs
271            //     .iter()
272            //     .any(|arg| checks::input_check(arg).is_err())
273            // {
274            //     panic!("Your cli cannot take a function because you can´t give code to your cli at runtime.");
275            // }
276
277            // Building...
278            let name_struct = format_ident!("{}", build::bigger(&item.sig.ident.to_string()));
279            let new_function = build_command::to_impl_item(item.clone());
280
281            let fields = build_command::fields(item.clone());
282            let struct_item = build_command::to_struct(name_struct.clone(), fields);
283
284            return_token::fn_token(name_struct, struct_item, new_function)
285        } else {
286            eprintln!("An item marked with #[crpc_fn] must be public.");
287            return item.to_token_stream().into();
288        }
289    } else {
290        eprintln!("An item marked with #[crpc_fn] must be a function.");
291        return item.to_token_stream().into();
292    }
293}