forked from trinitrix/core
feat(command_interface): Add support for namespaces
This commit is contained in:
parent
29aa6c1d33
commit
27d00c564c
|
@ -1,50 +1,96 @@
|
|||
use convert_case::{Case, Casing};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{DeriveInput, Field, Type};
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{punctuated::Punctuated, Token, Type, Ident};
|
||||
|
||||
use super::{get_input_type_of_bare_fn_field, parse_derive_input_as_named_fields};
|
||||
use crate::{DataCommandEnum, Field};
|
||||
|
||||
pub fn command_enum(input: &DeriveInput) -> TokenStream2 {
|
||||
let named_fields = parse_derive_input_as_named_fields(input);
|
||||
let fields: TokenStream2 = named_fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|field| turn_struct_fieled_to_enum(field))
|
||||
.collect();
|
||||
use super::get_input_type_of_bare_fn_field;
|
||||
|
||||
pub fn command_enum(input: &DataCommandEnum) -> TokenStream2 {
|
||||
let (fields, namespace_enums): (TokenStream2, TokenStream2) =
|
||||
turn_fields_to_enum(&input.fields);
|
||||
|
||||
quote! {
|
||||
#[derive(Debug)]
|
||||
pub enum Command {
|
||||
#fields
|
||||
}
|
||||
#namespace_enums
|
||||
}
|
||||
}
|
||||
|
||||
fn turn_struct_fieled_to_enum(field: &Field) -> TokenStream2 {
|
||||
let field_name = format_ident!(
|
||||
"{}",
|
||||
field
|
||||
.ident
|
||||
.as_ref()
|
||||
.expect("These are named fields, it should be Some(<name>)")
|
||||
.to_string()
|
||||
.from_case(Case::Snake)
|
||||
.to_case(Case::Pascal)
|
||||
);
|
||||
fn turn_fields_to_enum(fields: &Punctuated<Field, Token![,]>) -> (TokenStream2, TokenStream2) {
|
||||
let output: Vec<_> = fields
|
||||
.iter()
|
||||
.map(|field| turn_struct_field_to_enum(field))
|
||||
.collect();
|
||||
|
||||
let input_type: Option<Type> = get_input_type_of_bare_fn_field(field);
|
||||
let mut fields_output: TokenStream2 = Default::default();
|
||||
let mut namespace_enums_output: TokenStream2 = Default::default();
|
||||
|
||||
match input_type {
|
||||
Some(input_type) => {
|
||||
quote! {
|
||||
#field_name(#input_type),
|
||||
for (fields, namespace_enum) in output {
|
||||
fields_output.extend(fields.to_token_stream());
|
||||
namespace_enums_output.extend(namespace_enum.to_token_stream());
|
||||
}
|
||||
|
||||
(fields_output, namespace_enums_output)
|
||||
}
|
||||
|
||||
fn turn_struct_field_to_enum(field: &Field) -> (TokenStream2, TokenStream2) {
|
||||
match field {
|
||||
Field::Function(fun_field) => {
|
||||
let field_name = format_ident!(
|
||||
"{}",
|
||||
fun_field
|
||||
.name
|
||||
.to_string()
|
||||
.from_case(Case::Snake)
|
||||
.to_case(Case::Pascal)
|
||||
);
|
||||
|
||||
let input_type: Option<Type> = get_input_type_of_bare_fn_field(fun_field);
|
||||
|
||||
match input_type {
|
||||
Some(input_type) => (
|
||||
quote! {
|
||||
#field_name(#input_type),
|
||||
},
|
||||
quote! {},
|
||||
),
|
||||
None => (
|
||||
quote! {
|
||||
#field_name,
|
||||
},
|
||||
quote! {},
|
||||
),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
quote! {
|
||||
#field_name,
|
||||
}
|
||||
Field::Namespace(namespace) => {
|
||||
let (namespace_output_fields, namespace_output_namespace_enums) =
|
||||
turn_fields_to_enum(&namespace.fields);
|
||||
let namespace_name: Ident = format_ident!(
|
||||
"{}",
|
||||
namespace.path.iter().map(|name| name.to_string()).collect::<String>()
|
||||
);
|
||||
|
||||
let new_namespace_name: Ident = format_ident!(
|
||||
"{}",
|
||||
namespace_name.to_string().from_case(Case::Snake).to_case(Case::Pascal)
|
||||
);
|
||||
|
||||
(
|
||||
quote! {
|
||||
#new_namespace_name(#new_namespace_name),
|
||||
},
|
||||
quote! {
|
||||
#[derive(Debug)]
|
||||
pub enum #new_namespace_name {
|
||||
#namespace_output_fields
|
||||
}
|
||||
#namespace_output_namespace_enums
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,35 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{DeriveInput, Field};
|
||||
use syn::{punctuated::Punctuated, Token};
|
||||
|
||||
use crate::generate::parse_derive_input_as_named_fields;
|
||||
use crate::{DataCommandEnum, Field, FunctionDeclaration, NamespacePath};
|
||||
|
||||
pub fn generate_add_lua_functions_to_globals(input: &DeriveInput) -> TokenStream2 {
|
||||
let named_fields = parse_derive_input_as_named_fields(input);
|
||||
let function_adders: TokenStream2 = named_fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|field| generate_function_adder(field))
|
||||
.collect();
|
||||
pub fn generate_add_lua_functions_to_globals(input: &DataCommandEnum) -> TokenStream2 {
|
||||
fn turn_field_to_functions(
|
||||
input: &Punctuated<Field, Token![,]>,
|
||||
namespace_path: Option<&NamespacePath>,
|
||||
) -> TokenStream2 {
|
||||
input
|
||||
.iter()
|
||||
.map(|field| match field {
|
||||
crate::Field::Function(function) => {
|
||||
generate_function_adder(function, namespace_path)
|
||||
}
|
||||
crate::Field::Namespace(namespace) => {
|
||||
let mut passed_namespace =
|
||||
namespace_path.unwrap_or(&Default::default()).clone();
|
||||
namespace
|
||||
.path
|
||||
.clone()
|
||||
.into_iter()
|
||||
.for_each(|val| passed_namespace.push(val));
|
||||
|
||||
turn_field_to_functions(&namespace.fields, Some(&passed_namespace))
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
let function_adders: TokenStream2 = turn_field_to_functions(&input.fields, None);
|
||||
|
||||
quote! {
|
||||
pub fn add_lua_functions_to_globals(
|
||||
|
@ -28,15 +47,67 @@ pub fn generate_add_lua_functions_to_globals(input: &DeriveInput) -> TokenStream
|
|||
}
|
||||
}
|
||||
|
||||
fn generate_function_adder(field: &Field) -> TokenStream2 {
|
||||
let field_ident = field
|
||||
.ident
|
||||
.as_ref()
|
||||
.expect("This is should be a named field");
|
||||
fn generate_function_adder(
|
||||
field: &FunctionDeclaration,
|
||||
namespace_path: Option<&NamespacePath>,
|
||||
) -> TokenStream2 {
|
||||
let field_ident = &field.name;
|
||||
|
||||
let function_ident = format_ident!("wrapped_lua_function_{}", field_ident);
|
||||
let function_name = field_ident.to_string();
|
||||
|
||||
let setter = if let Some(namespace_path) = namespace_path {
|
||||
// ```lua
|
||||
// local globals = {
|
||||
// ns1: {
|
||||
// ns_value,
|
||||
// ns_value2,
|
||||
// },
|
||||
// ns2: {
|
||||
// ns_value3,
|
||||
// }
|
||||
// }
|
||||
// ns1.ns_value
|
||||
// ```
|
||||
let mut counter = 0;
|
||||
let namespace_table_gen: TokenStream2 = namespace_path.iter().map(|path| {
|
||||
let path = path.to_string();
|
||||
counter += 1;
|
||||
let mut set_function: TokenStream2 = Default::default();
|
||||
if counter == namespace_path.len() {
|
||||
set_function = quote! {
|
||||
table.set(#function_name, #function_ident).expect(
|
||||
"Setting a static global value should work"
|
||||
);
|
||||
};
|
||||
}
|
||||
quote! {
|
||||
let table: mlua::Table = {
|
||||
if table.contains_key(#path).expect("This check should work") {
|
||||
let table2 = table.get(#path).expect("This was already checked");
|
||||
table2
|
||||
} else {
|
||||
table.set(#path, lua.create_table().expect("This should also always work")).expect("Setting this value should work");
|
||||
table.get(#path).expect("This was set, just above")
|
||||
}
|
||||
};
|
||||
#set_function
|
||||
}
|
||||
}).collect();
|
||||
|
||||
quote! {
|
||||
let table = &globals;
|
||||
{
|
||||
#namespace_table_gen
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
globals.set(#function_name, #function_ident).expect(
|
||||
"Setting a static global value should work"
|
||||
);
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
{
|
||||
let #function_ident = lua.create_async_function(#field_ident).expect(
|
||||
|
@ -45,10 +116,7 @@ fn generate_function_adder(field: &Field) -> TokenStream2 {
|
|||
#function_name
|
||||
)
|
||||
);
|
||||
|
||||
globals.set(#function_name, #function_ident).expect(
|
||||
"Setting a static global value should work"
|
||||
);
|
||||
#setter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use syn::DeriveInput;
|
||||
|
||||
use crate::generate::lua_wrapper::{
|
||||
lua_functions_to_globals::generate_add_lua_functions_to_globals,
|
||||
rust_wrapper_functions::generate_rust_wrapper_functions,
|
||||
use crate::{
|
||||
generate::lua_wrapper::{
|
||||
lua_functions_to_globals::generate_add_lua_functions_to_globals,
|
||||
rust_wrapper_functions::generate_rust_wrapper_functions,
|
||||
},
|
||||
DataCommandEnum,
|
||||
};
|
||||
|
||||
mod lua_functions_to_globals;
|
||||
mod rust_wrapper_functions;
|
||||
|
||||
pub fn lua_wrapper(input: &DeriveInput) -> TokenStream2 {
|
||||
pub fn lua_wrapper(input: &DataCommandEnum) -> TokenStream2 {
|
||||
let add_lua_functions_to_globals = generate_add_lua_functions_to_globals(input);
|
||||
let rust_wrapper_functions = generate_rust_wrapper_functions(input);
|
||||
let rust_wrapper_functions = generate_rust_wrapper_functions(None, input);
|
||||
quote! {
|
||||
#add_lua_functions_to_globals
|
||||
#rust_wrapper_functions
|
||||
|
|
|
@ -1,21 +1,39 @@
|
|||
use convert_case::{Case, Casing};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
punctuated::Punctuated, token::Comma, DeriveInput, Field, GenericArgument, Lifetime, Type,
|
||||
use quote::quote;
|
||||
use syn::{punctuated::Punctuated, token::Comma, GenericArgument, Lifetime, Token, Type};
|
||||
|
||||
use crate::{
|
||||
generate::{get_input_type_of_bare_fn_field, get_return_type_of_bare_fn_field},
|
||||
DataCommandEnum, Field, FunctionDeclaration, NamespacePath,
|
||||
};
|
||||
|
||||
use crate::generate::{
|
||||
get_input_type_of_bare_fn_field, get_return_type_of_bare_fn_field,
|
||||
parse_derive_input_as_named_fields,
|
||||
};
|
||||
pub fn generate_rust_wrapper_functions(
|
||||
namespace: Option<&NamespacePath>,
|
||||
input: &DataCommandEnum,
|
||||
) -> TokenStream2 {
|
||||
generate_rust_wrapper_functions_rec(namespace, &input.fields)
|
||||
}
|
||||
|
||||
pub fn generate_rust_wrapper_functions(input: &DeriveInput) -> TokenStream2 {
|
||||
let named_fields = parse_derive_input_as_named_fields(input);
|
||||
let wrapped_functions: TokenStream2 = named_fields
|
||||
.named
|
||||
pub fn generate_rust_wrapper_functions_rec(
|
||||
namespace: Option<&NamespacePath>,
|
||||
input: &Punctuated<Field, Token![,]>,
|
||||
) -> TokenStream2 {
|
||||
let wrapped_functions: TokenStream2 = input
|
||||
.iter()
|
||||
.map(|field| wrap_lua_function(field))
|
||||
.map(|field| match field {
|
||||
Field::Function(fun_field) => {
|
||||
wrap_lua_function(namespace.unwrap_or(&Default::default()), fun_field)
|
||||
}
|
||||
Field::Namespace(nasp) => {
|
||||
let mut passed_namespace = namespace.unwrap_or(&Default::default()).clone();
|
||||
nasp.path
|
||||
.clone()
|
||||
.into_iter()
|
||||
.for_each(|val| passed_namespace.push(val));
|
||||
generate_rust_wrapper_functions_rec(Some(&passed_namespace), &nasp.fields)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
|
@ -23,12 +41,12 @@ pub fn generate_rust_wrapper_functions(input: &DeriveInput) -> TokenStream2 {
|
|||
}
|
||||
}
|
||||
|
||||
fn wrap_lua_function(field: &Field) -> TokenStream2 {
|
||||
fn wrap_lua_function(namespace: &NamespacePath, field: &FunctionDeclaration) -> TokenStream2 {
|
||||
let input_type = get_input_type_of_bare_fn_field(field);
|
||||
let return_type = get_return_type_of_bare_fn_field(field);
|
||||
|
||||
let function_name = field.ident.as_ref().expect("This should be a named field");
|
||||
let function_body = get_function_body(field, input_type.is_some(), &return_type);
|
||||
let function_name = &field.name;
|
||||
let function_body = get_function_body(&namespace, field, input_type.is_some(), &return_type);
|
||||
|
||||
let lifetime_args =
|
||||
get_and_add_lifetimes_form_inputs_and_outputs(input_type.clone(), return_type);
|
||||
|
@ -98,32 +116,62 @@ fn get_and_add_lifetimes_form_inputs_and_outputs<'a>(
|
|||
output
|
||||
}
|
||||
|
||||
fn get_function_body(field: &Field, has_input: bool, output_type: &Option<Type>) -> TokenStream2 {
|
||||
fn get_function_body(
|
||||
namespace: &NamespacePath,
|
||||
field: &FunctionDeclaration,
|
||||
has_input: bool,
|
||||
output_type: &Option<Type>,
|
||||
) -> TokenStream2 {
|
||||
let command_name = field
|
||||
.ident
|
||||
.as_ref()
|
||||
.expect("These are named fields, it should be Some(<name>)")
|
||||
.name
|
||||
.to_string()
|
||||
.from_case(Case::Snake)
|
||||
.to_case(Case::Pascal);
|
||||
let command_ident = format_ident!("{}", command_name);
|
||||
|
||||
let send_output = if has_input {
|
||||
quote! {
|
||||
Event::CommandEvent(
|
||||
Command::#command_ident(input.clone()),
|
||||
Some(callback_tx),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
Event::CommandEvent(
|
||||
Command::#command_ident,
|
||||
Some(callback_tx),
|
||||
)
|
||||
let command_ident = {
|
||||
if has_input {
|
||||
format!("{}(", command_name)
|
||||
} else {
|
||||
command_name.clone()
|
||||
}
|
||||
};
|
||||
|
||||
let command_namespace: String = {
|
||||
namespace
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let path_enum_name: String = path
|
||||
.to_string()
|
||||
.from_case(Case::Snake)
|
||||
.to_case(Case::Pascal);
|
||||
|
||||
path_enum_name.clone() + "(" + &path_enum_name + "::"
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
};
|
||||
|
||||
let send_output: TokenStream2 = {
|
||||
let finishing_brackets = {
|
||||
if has_input {
|
||||
let mut output = "input.clone()".to_owned();
|
||||
output.push_str(&(0..namespace.len()).map(|_| ')').collect::<String>());
|
||||
output
|
||||
} else {
|
||||
(0..namespace.len()).map(|_| ')').collect::<String>()
|
||||
}
|
||||
};
|
||||
|
||||
("Event::CommandEvent( Command::".to_owned()
|
||||
+ &command_namespace
|
||||
+ &command_ident
|
||||
+ &finishing_brackets
|
||||
+ {if has_input {")"} else {""}} /* Needed as command_name opens one */
|
||||
+ ",Some(callback_tx))")
|
||||
.parse()
|
||||
.expect("This code should be valid")
|
||||
};
|
||||
|
||||
let function_return = if let Some(_) = output_type {
|
||||
quote! {
|
||||
return Ok(output.into_lua(lua).expect("This conversion should always work"));
|
||||
|
|
|
@ -3,18 +3,9 @@ mod lua_wrapper;
|
|||
|
||||
pub use command_enum::command_enum;
|
||||
pub use lua_wrapper::lua_wrapper;
|
||||
use syn::{DeriveInput, Field, FieldsNamed, ReturnType, Type, TypeBareFn};
|
||||
use syn::{ReturnType, Type, TypeBareFn};
|
||||
|
||||
pub fn parse_derive_input_as_named_fields(input: &DeriveInput) -> FieldsNamed {
|
||||
match &input.data {
|
||||
syn::Data::Struct(input) => match &input.fields {
|
||||
syn::Fields::Named(named_fields) => named_fields,
|
||||
_ => unimplemented!("The macro only works for named fields (e.g.: `Name: Type`)"),
|
||||
},
|
||||
_ => unimplemented!("The macro only works for structs"),
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
use crate::FunctionDeclaration;
|
||||
|
||||
pub fn get_bare_fn_input_type(function: &TypeBareFn) -> Option<Type> {
|
||||
if function.inputs.len() == 1 {
|
||||
|
@ -37,7 +28,7 @@ pub fn get_bare_fn_input_type(function: &TypeBareFn) -> Option<Type> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_input_type_of_bare_fn_field(field: &Field) -> Option<Type> {
|
||||
pub fn get_input_type_of_bare_fn_field(field: &FunctionDeclaration) -> Option<Type> {
|
||||
match &field.ty {
|
||||
syn::Type::BareFn(function) => get_bare_fn_input_type(&function),
|
||||
_ => unimplemented!(
|
||||
|
@ -46,7 +37,7 @@ pub fn get_input_type_of_bare_fn_field(field: &Field) -> Option<Type> {
|
|||
),
|
||||
}
|
||||
}
|
||||
pub fn get_return_type_of_bare_fn_field(field: &Field) -> Option<Type> {
|
||||
pub fn get_return_type_of_bare_fn_field(field: &FunctionDeclaration) -> Option<Type> {
|
||||
match &field.ty {
|
||||
syn::Type::BareFn(function) => get_bare_fn_return_type(&function),
|
||||
_ => unimplemented!(
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::quote;
|
||||
use syn::DeriveInput;
|
||||
use syn::{
|
||||
braced, parse::Parse, parse_macro_input, punctuated::Punctuated, token, Attribute, Ident,
|
||||
Token, Type,
|
||||
};
|
||||
|
||||
mod generate;
|
||||
|
||||
|
@ -14,19 +17,17 @@ mod generate;
|
|||
/// the rust wrapper functions to the lua globals.
|
||||
///
|
||||
/// The input and output values of the wrapped functions are derived from the values specified in
|
||||
/// the `Commands` struct.
|
||||
/// The returned values will be returned directly to the lua context, this allows to nest functions.
|
||||
/// the input to the `parse_command_enum` proc macro.
|
||||
///
|
||||
/// For example this rust code:
|
||||
/// ```rust
|
||||
/// #[ci_command_enum]
|
||||
/// struct Commands {
|
||||
/// ```no_run
|
||||
/// parse_command_enum! {
|
||||
/// /// Greets the user
|
||||
/// greet: fn(String) -> String,
|
||||
/// }
|
||||
/// ```
|
||||
/// results in this expanded code:
|
||||
/// ```rust
|
||||
/// ```no_run
|
||||
/// #[derive(Debug)]
|
||||
/// pub enum Command {
|
||||
/// Greet(String),
|
||||
|
@ -72,12 +73,118 @@ mod generate;
|
|||
/// };
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn ci_command_enum(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||
// Construct a representation of Rust code as a syntax tree
|
||||
// that we can manipulate
|
||||
let input: DeriveInput = syn::parse(input)
|
||||
.expect("This should always be valid rust code, as it's extracted from direct code");
|
||||
#[derive(Debug)]
|
||||
struct DataCommandEnum {
|
||||
#[allow(dead_code)]
|
||||
commands_token: kw::commands,
|
||||
|
||||
#[allow(dead_code)]
|
||||
brace_token: token::Brace,
|
||||
|
||||
fields: Punctuated<Field, Token![,]>,
|
||||
}
|
||||
|
||||
mod kw {
|
||||
syn::custom_keyword!(commands);
|
||||
syn::custom_keyword!(namespace);
|
||||
syn::custom_keyword!(declare);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Field {
|
||||
Function(FunctionDeclaration),
|
||||
Namespace(Namespace),
|
||||
}
|
||||
#[derive(Debug)]
|
||||
struct Namespace {
|
||||
#[allow(dead_code)]
|
||||
namespace_token: kw::namespace,
|
||||
|
||||
path: NamespacePath,
|
||||
|
||||
#[allow(dead_code)]
|
||||
brace_token: token::Brace,
|
||||
|
||||
fields: Punctuated<Field, Token![,]>,
|
||||
}
|
||||
type NamespacePath = Punctuated<Ident, Token![::]>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FunctionDeclaration {
|
||||
#[allow(dead_code)]
|
||||
function_token: kw::declare,
|
||||
|
||||
name: Ident,
|
||||
|
||||
#[allow(dead_code)]
|
||||
colon_token: Token![:],
|
||||
|
||||
ty: Type,
|
||||
}
|
||||
|
||||
impl Parse for DataCommandEnum {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
Ok(DataCommandEnum {
|
||||
commands_token: input.parse()?,
|
||||
brace_token: braced!(content in input),
|
||||
fields: content.parse_terminated(Field::parse, Token![,])?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Parse for Field {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if input.peek(Token![#]) {
|
||||
// FIXME(@soispha): We ignore doc comments, which should probably be replaced by adding
|
||||
// them to the output <2023-09-19>
|
||||
let _output = input.call(Attribute::parse_outer).unwrap_or(vec![]);
|
||||
let lookahead = input.lookahead1();
|
||||
|
||||
if lookahead.peek(kw::namespace) {
|
||||
input.parse().map(Field::Namespace)
|
||||
} else if lookahead.peek(kw::declare) {
|
||||
input.parse().map(Field::Function)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
} else {
|
||||
if lookahead.peek(kw::declare) {
|
||||
input.parse().map(Field::Function)
|
||||
} else if lookahead.peek(kw::namespace) {
|
||||
input.parse().map(Field::Namespace)
|
||||
} else {
|
||||
Err(lookahead.error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for FunctionDeclaration {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
Ok(FunctionDeclaration {
|
||||
function_token: input.parse()?,
|
||||
name: input.parse()?,
|
||||
colon_token: input.parse()?,
|
||||
ty: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Parse for Namespace {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
Ok(Namespace {
|
||||
namespace_token: input.parse()?,
|
||||
path: NamespacePath::parse_separated_nonempty(input)?,
|
||||
brace_token: braced!(content in input),
|
||||
fields: content.parse_terminated(Field::parse, Token![,])?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn parse_command_enum(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DataCommandEnum);
|
||||
|
||||
// Build the language wrappers
|
||||
let lua_wrapper: TokenStream2 = generate::lua_wrapper(&input);
|
||||
|
@ -85,9 +192,9 @@ pub fn ci_command_enum(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
|||
// Build the final enum
|
||||
let command_enum = generate::command_enum(&input);
|
||||
|
||||
quote! {
|
||||
let output = quote! {
|
||||
#command_enum
|
||||
#lua_wrapper
|
||||
}
|
||||
.into()
|
||||
};
|
||||
output.into()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
// Use `cargo expand app::command_interface::command_list` for an overview of the file contents
|
||||
|
||||
use language_macros::parse_command_enum;
|
||||
|
||||
// TODO(@soispha): Should these paths be moved to the proc macro?
|
||||
// As they are not static, it could be easier for other people,
|
||||
// if they stay here.
|
||||
use crate::app::command_interface::command_transfer_value::CommandTransferValue;
|
||||
use crate::app::Event;
|
||||
use mlua::IntoLua;
|
||||
|
||||
parse_command_enum! {
|
||||
commands {
|
||||
/// Prints to the output, with a newline.
|
||||
// HACK(@soispha): The stdlib Lua `print()` function has stdout as output hardcoded,
|
||||
// redirecting stdout seems too much like a hack thus we are just redefining the print function
|
||||
// to output to a controlled output. <2023-09-09>
|
||||
declare print: fn(CommandTransferValue),
|
||||
|
||||
namespace trinitrix {
|
||||
/// Debug only functions, these are effectively useless
|
||||
namespace debug {
|
||||
/// Greets the user
|
||||
declare greet: fn(String) -> String,
|
||||
|
||||
/// Returns a table of greeted users
|
||||
declare greet_multiple: fn() -> Table,
|
||||
},
|
||||
|
||||
/// General API to change stuff in Name
|
||||
namespace api {
|
||||
/// Closes the application
|
||||
declare exit: fn(),
|
||||
/// Send a message to the current room
|
||||
/// The send message is interpreted literally.
|
||||
declare room_message_send: fn(String),
|
||||
/// Open the help pages at the first occurrence of
|
||||
/// the input string if it is Some, otherwise open
|
||||
/// the help pages at the start
|
||||
declare help: fn(Option<String>),
|
||||
|
||||
/// Function that change the UI, or UI state
|
||||
namespace ui {
|
||||
/// Shows the command line
|
||||
declare command_line_show: fn(),
|
||||
|
||||
/// Hides the command line
|
||||
declare command_line_hide: fn(),
|
||||
|
||||
/// Go to the next plane
|
||||
declare cycle_planes: fn(),
|
||||
/// Go to the previous plane
|
||||
declare cycle_planes_rev: fn(),
|
||||
|
||||
/// Sets the current app mode to Normal / navigation mode
|
||||
declare set_mode_normal: fn(),
|
||||
/// Sets the current app mode to Insert / editing mode
|
||||
declare set_mode_insert: fn(),
|
||||
},
|
||||
|
||||
/// Functions only used internally within Name
|
||||
namespace raw {
|
||||
/// Send an error to the default error output
|
||||
declare raise_error: fn(String),
|
||||
/// Send output to the default output
|
||||
/// This is mainly used to display the final
|
||||
/// output of evaluated lua commands.
|
||||
declare display_output: fn(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -11,7 +11,13 @@ use tokio::{
|
|||
};
|
||||
|
||||
use crate::app::{
|
||||
command_interface::{add_lua_functions_to_globals, Command},
|
||||
command_interface::{
|
||||
add_lua_functions_to_globals,
|
||||
Api::Raw,
|
||||
Command,
|
||||
Raw::{DisplayOutput, RaiseError},
|
||||
Trinitrix::Api,
|
||||
},
|
||||
events::event_types::Event,
|
||||
};
|
||||
|
||||
|
@ -87,7 +93,10 @@ async fn exec_lua(lua_code: &str, event_call_tx: mpsc::Sender<Event>) -> Result<
|
|||
|
||||
if output != "nil" {
|
||||
event_call_tx
|
||||
.send(Event::CommandEvent(Command::DisplayOutput(output), None))
|
||||
.send(Event::CommandEvent(
|
||||
Command::Trinitrix(Api(Raw(DisplayOutput(output)))),
|
||||
None,
|
||||
))
|
||||
.await
|
||||
.context("Failed to send lua output command")?
|
||||
}
|
||||
|
@ -96,7 +105,7 @@ async fn exec_lua(lua_code: &str, event_call_tx: mpsc::Sender<Event>) -> Result<
|
|||
error!("Lua code `{}` returned error: `{}`", lua_code, err);
|
||||
event_call_tx
|
||||
.send(Event::CommandEvent(
|
||||
Command::RaiseError(err.to_string()),
|
||||
Command::Trinitrix(Api(Raw(RaiseError(err.to_string())))),
|
||||
None,
|
||||
))
|
||||
.await?;
|
||||
|
|
|
@ -1,64 +1,5 @@
|
|||
// Use `cargo expand app::command_interface` for an overview of the file contents
|
||||
|
||||
pub mod command_transfer_value;
|
||||
pub mod lua_command_manager;
|
||||
pub mod command_list;
|
||||
|
||||
use language_macros::ci_command_enum;
|
||||
|
||||
// TODO(@Soispha): Should these paths be moved to the proc macro?
|
||||
// As they are not static, it could be easier for other people,
|
||||
// if they stay here.
|
||||
use crate::app::command_interface::command_transfer_value::CommandTransferValue;
|
||||
use crate::app::Event;
|
||||
use mlua::IntoLua;
|
||||
|
||||
#[ci_command_enum]
|
||||
struct Commands {
|
||||
/// Prints to the output, with a newline.
|
||||
// HACK(@soispha): The stdlib Lua `print()` function has stdout as output hardcoded,
|
||||
// redirecting stdout seems too much like a hack thus we are just redefining the print function
|
||||
// to output to a controlled output. <2023-09-09>
|
||||
print: fn(CommandTransferValue),
|
||||
|
||||
// Begin debug functions
|
||||
/// Greets the user
|
||||
greet: fn(String) -> String,
|
||||
|
||||
/// Returns a table of greeted users
|
||||
greet_multiple: fn() -> Table,
|
||||
// End debug functions
|
||||
/// Closes the application
|
||||
exit: fn(),
|
||||
|
||||
/// Shows the command line
|
||||
command_line_show: fn(),
|
||||
|
||||
/// Hides the command line
|
||||
command_line_hide: fn(),
|
||||
|
||||
/// Go to the next plane
|
||||
cycle_planes: fn(),
|
||||
/// Go to the previous plane
|
||||
cycle_planes_rev: fn(),
|
||||
|
||||
/// Sets the current app mode to Normal / navigation mode
|
||||
set_mode_normal: fn(),
|
||||
/// Sets the current app mode to Insert / editing mode
|
||||
set_mode_insert: fn(),
|
||||
|
||||
/// Send a message to the current room
|
||||
/// The send message is interpreted literally.
|
||||
room_message_send: fn(String),
|
||||
|
||||
/// Open the help pages at the first occurrence of
|
||||
/// the input string if it is Some, otherwise open
|
||||
/// the help pages at the start
|
||||
help: fn(Option<String>),
|
||||
|
||||
/// Send an error to the default error output
|
||||
raise_error: fn(String),
|
||||
/// Send output to the default output
|
||||
/// This is mainly used to display the final
|
||||
/// output of evaluated lua commands.
|
||||
display_output: fn(String),
|
||||
}
|
||||
pub use command_list::*;
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
app::{
|
||||
command_interface::{
|
||||
command_transfer_value::{CommandTransferValue, Table},
|
||||
Command,
|
||||
Api, Command, Debug, Raw, Trinitrix, Ui,
|
||||
},
|
||||
events::event_types::EventStatus,
|
||||
status::State,
|
||||
|
@ -66,96 +66,102 @@ pub async fn handle(
|
|||
trace!("Handling command: {:#?}", command);
|
||||
|
||||
Ok(match command {
|
||||
Command::Exit => {
|
||||
send_status_output!("Terminating the application..");
|
||||
EventStatus::Terminate
|
||||
}
|
||||
|
||||
Command::DisplayOutput(output) => {
|
||||
// TODO(@Soispha): This is only used to show the Lua command output to the user.
|
||||
// Lua commands already receive the output. This should probably be communicated
|
||||
// better, should it?
|
||||
send_status_output!(output);
|
||||
EventStatus::Ok
|
||||
}
|
||||
Command::Print(output) => {
|
||||
let output_str: String = output.to_string();
|
||||
send_status_output!(output_str);
|
||||
EventStatus::Ok
|
||||
}
|
||||
|
||||
Command::CommandLineShow => {
|
||||
app.ui.cli_enable();
|
||||
app.status.set_state(State::Command);
|
||||
send_status_output!("CLI online");
|
||||
EventStatus::Ok
|
||||
}
|
||||
Command::CommandLineHide => {
|
||||
app.ui.cli_disable();
|
||||
send_status_output!("CLI offline");
|
||||
EventStatus::Ok
|
||||
}
|
||||
Command::Trinitrix(trinitrix) => match trinitrix {
|
||||
Trinitrix::Debug(debug) => match debug {
|
||||
Debug::Greet(msg) => {
|
||||
send_main_output!("Greeting, {}!", msg);
|
||||
EventStatus::Ok
|
||||
}
|
||||
Debug::GreetMultiple => {
|
||||
let mut table: Table = HashMap::new();
|
||||
table.insert("UserId".to_owned(), CommandTransferValue::Integer(2));
|
||||
table.insert(
|
||||
"UserName".to_owned(),
|
||||
CommandTransferValue::String("James".to_owned()),
|
||||
);
|
||||
|
||||
Command::CyclePlanes => {
|
||||
app.ui.cycle_main_input_position();
|
||||
send_status_output!("Switched main input position");
|
||||
EventStatus::Ok
|
||||
}
|
||||
Command::CyclePlanesRev => {
|
||||
app.ui.cycle_main_input_position_rev();
|
||||
send_status_output!("Switched main input position; reversed");
|
||||
EventStatus::Ok
|
||||
}
|
||||
|
||||
Command::SetModeNormal => {
|
||||
app.status.set_state(State::Normal);
|
||||
send_status_output!("Set input mode to Normal");
|
||||
EventStatus::Ok
|
||||
}
|
||||
Command::SetModeInsert => {
|
||||
app.status.set_state(State::Insert);
|
||||
app.ui.set_input_position(InputPosition::MessageCompose);
|
||||
send_status_output!("Set input mode to Insert");
|
||||
EventStatus::Ok
|
||||
}
|
||||
|
||||
Command::RoomMessageSend(msg) => {
|
||||
if let Some(room) = app.status.room_mut() {
|
||||
room.send(msg.clone()).await?;
|
||||
send_status_output!("Sent message: `{}`", msg);
|
||||
} else {
|
||||
// TODO(@Soispha): Should this raise a Lua error? It could be very confusing,
|
||||
// when a user doesn't read the log.
|
||||
warn!("Can't send message: `{}`, as there is no open room!", &msg);
|
||||
}
|
||||
EventStatus::Ok
|
||||
}
|
||||
Command::Greet(name) => {
|
||||
send_main_output!("Hi, {}!", name);
|
||||
EventStatus::Ok
|
||||
}
|
||||
Command::GreetMultiple => {
|
||||
let mut table: Table = HashMap::new();
|
||||
table.insert("UserId".to_owned(), CommandTransferValue::Integer(2));
|
||||
table.insert(
|
||||
"UserName".to_owned(),
|
||||
CommandTransferValue::String("James".to_owned()),
|
||||
);
|
||||
|
||||
let mut second_table: Table = HashMap::new();
|
||||
second_table.insert("interface".to_owned(), CommandTransferValue::Integer(3));
|
||||
second_table.insert("api".to_owned(), CommandTransferValue::Boolean(true));
|
||||
table.insert(
|
||||
"Versions".to_owned(),
|
||||
CommandTransferValue::Table(second_table),
|
||||
);
|
||||
send_main_output!(table);
|
||||
EventStatus::Ok
|
||||
}
|
||||
Command::Help(_) => todo!(),
|
||||
Command::RaiseError(err) => {
|
||||
send_error_output!(err);
|
||||
EventStatus::Ok
|
||||
}
|
||||
let mut second_table: Table = HashMap::new();
|
||||
second_table.insert("interface".to_owned(), CommandTransferValue::Integer(3));
|
||||
second_table.insert("api".to_owned(), CommandTransferValue::Boolean(true));
|
||||
table.insert(
|
||||
"Versions".to_owned(),
|
||||
CommandTransferValue::Table(second_table),
|
||||
);
|
||||
send_main_output!(table);
|
||||
EventStatus::Ok
|
||||
}
|
||||
},
|
||||
Trinitrix::Api(api) => match api {
|
||||
Api::Exit => {
|
||||
send_status_output!("Terminating the application..");
|
||||
EventStatus::Terminate
|
||||
}
|
||||
Api::RoomMessageSend(msg) => {
|
||||
if let Some(room) = app.status.room_mut() {
|
||||
room.send(msg.clone()).await?;
|
||||
send_status_output!("Sent message: `{}`", msg);
|
||||
} else {
|
||||
// // FIXME(@soispha): This should raise an error within lua, as it would
|
||||
// otherwise be very confusing <2023-09-20>
|
||||
warn!("Can't send message: `{}`, as there is no open room!", &msg);
|
||||
}
|
||||
EventStatus::Ok
|
||||
}
|
||||
Api::Help(_) => todo!(),
|
||||
Api::Ui(ui) => match ui {
|
||||
Ui::CommandLineShow => {
|
||||
app.ui.cli_enable();
|
||||
app.status.set_state(State::Command);
|
||||
send_status_output!("CLI online");
|
||||
EventStatus::Ok
|
||||
}
|
||||
Ui::CommandLineHide => {
|
||||
app.ui.cli_disable();
|
||||
send_status_output!("CLI offline");
|
||||
EventStatus::Ok
|
||||
}
|
||||
Ui::CyclePlanes => {
|
||||
app.ui.cycle_main_input_position();
|
||||
send_status_output!("Switched main input position");
|
||||
EventStatus::Ok
|
||||
}
|
||||
Ui::CyclePlanesRev => {
|
||||
app.ui.cycle_main_input_position_rev();
|
||||
send_status_output!("Switched main input position; reversed");
|
||||
EventStatus::Ok
|
||||
}
|
||||
Ui::SetModeNormal => {
|
||||
app.status.set_state(State::Normal);
|
||||
send_status_output!("Set input mode to Normal");
|
||||
EventStatus::Ok
|
||||
}
|
||||
Ui::SetModeInsert => {
|
||||
app.status.set_state(State::Insert);
|
||||
app.ui.set_input_position(InputPosition::MessageCompose);
|
||||
send_status_output!("Set input mode to Insert");
|
||||
EventStatus::Ok
|
||||
}
|
||||
},
|
||||
Api::Raw(raw) => match raw {
|
||||
Raw::RaiseError(err) => {
|
||||
send_error_output!(err);
|
||||
EventStatus::Ok
|
||||
}
|
||||
Raw::DisplayOutput(output) => {
|
||||
// TODO(@Soispha): This is only used to show the Lua command output to the user.
|
||||
// Lua commands already receive the output. This should probably be communicated
|
||||
// better, should it?
|
||||
send_status_output!(output);
|
||||
EventStatus::Ok
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use crossterm::event::{Event as CrosstermEvent, KeyCode, KeyEvent, KeyModifiers}
|
|||
|
||||
use crate::{
|
||||
app::{
|
||||
command_interface::Command,
|
||||
command_interface::{Api, Command, Trinitrix, Ui},
|
||||
events::event_types::{Event, EventStatus},
|
||||
App,
|
||||
},
|
||||
|
@ -20,7 +20,10 @@ pub async fn handle_command(
|
|||
code: KeyCode::Esc, ..
|
||||
}) => {
|
||||
app.tx
|
||||
.send(Event::CommandEvent(Command::SetModeNormal, None))
|
||||
.send(Event::CommandEvent(
|
||||
Command::Trinitrix(Trinitrix::Api(Api::Ui(Ui::SetModeNormal))),
|
||||
None,
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
CrosstermEvent::Key(KeyEvent {
|
||||
|
@ -62,14 +65,20 @@ pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> R
|
|||
..
|
||||
}) => {
|
||||
app.tx
|
||||
.send(Event::CommandEvent(Command::Exit, None))
|
||||
.send(Event::CommandEvent(
|
||||
Command::Trinitrix(Trinitrix::Api(Api::Exit)),
|
||||
None,
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
CrosstermEvent::Key(KeyEvent {
|
||||
code: KeyCode::Tab, ..
|
||||
}) => {
|
||||
app.tx
|
||||
.send(Event::CommandEvent(Command::CyclePlanes, None))
|
||||
.send(Event::CommandEvent(
|
||||
Command::Trinitrix(Trinitrix::Api(Api::Ui(Ui::CyclePlanes))),
|
||||
None,
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
CrosstermEvent::Key(KeyEvent {
|
||||
|
@ -77,7 +86,10 @@ pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> R
|
|||
..
|
||||
}) => {
|
||||
app.tx
|
||||
.send(Event::CommandEvent(Command::CyclePlanesRev, None))
|
||||
.send(Event::CommandEvent(
|
||||
Command::Trinitrix(Trinitrix::Api(Api::Ui(Ui::CyclePlanesRev))),
|
||||
None,
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
CrosstermEvent::Key(KeyEvent {
|
||||
|
@ -85,7 +97,10 @@ pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> R
|
|||
..
|
||||
}) => {
|
||||
app.tx
|
||||
.send(Event::CommandEvent(Command::CommandLineShow, None))
|
||||
.send(Event::CommandEvent(
|
||||
Command::Trinitrix(Trinitrix::Api(Api::Ui(Ui::CommandLineShow))),
|
||||
None,
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
CrosstermEvent::Key(KeyEvent {
|
||||
|
@ -93,7 +108,10 @@ pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> R
|
|||
..
|
||||
}) => {
|
||||
app.tx
|
||||
.send(Event::CommandEvent(Command::SetModeInsert, None))
|
||||
.send(Event::CommandEvent(
|
||||
Command::Trinitrix(Trinitrix::Api(Api::Ui(Ui::SetModeInsert))),
|
||||
None,
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
input => match app.ui.input_position() {
|
||||
|
@ -193,7 +211,10 @@ pub async fn handle_insert(app: &mut App<'_>, event: &CrosstermEvent) -> Result<
|
|||
code: KeyCode::Esc, ..
|
||||
}) => {
|
||||
app.tx
|
||||
.send(Event::CommandEvent(Command::SetModeNormal, None))
|
||||
.send(Event::CommandEvent(
|
||||
Command::Trinitrix(Trinitrix::Api(Api::Ui(Ui::SetModeNormal))),
|
||||
None,
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
CrosstermEvent::Key(KeyEvent {
|
||||
|
@ -203,7 +224,9 @@ pub async fn handle_insert(app: &mut App<'_>, event: &CrosstermEvent) -> Result<
|
|||
}) => {
|
||||
app.tx
|
||||
.send(Event::CommandEvent(
|
||||
Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")),
|
||||
Command::Trinitrix(Trinitrix::Api(Api::RoomMessageSend(
|
||||
app.ui.message_compose.lines().join("\n"),
|
||||
))),
|
||||
None,
|
||||
))
|
||||
.await?;
|
||||
|
|
Loading…
Reference in New Issue