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}