Compare commits
4 Commits
2a2c173683
...
a6d176b6e9
Author | SHA1 | Date |
---|---|---|
Benedikt Peetz | a6d176b6e9 | |
Benedikt Peetz | 20c751fd7f | |
Benedikt Peetz | c243c90cab | |
Benedikt Peetz | 27e3ff228c |
|
@ -18,4 +18,4 @@ tokio-util = "0.7"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
cli-log = "2.0"
|
cli-log = "2.0"
|
||||||
indexmap = "2.0.0"
|
indexmap = "2.0.0"
|
||||||
mlua = { version = "0.8.9", features = ["lua54", "async"] }
|
mlua = { version = "0.8.9", features = ["lua54", "async", "send"] }
|
||||||
|
|
11
flake.nix
11
flake.nix
|
@ -45,10 +45,13 @@
|
||||||
overlays = [(import rust-overlay)];
|
overlays = [(import rust-overlay)];
|
||||||
};
|
};
|
||||||
|
|
||||||
#rust-nightly = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default);
|
nightly = true;
|
||||||
rust-stable = pkgs.rust-bin.stable.latest.default;
|
rust =
|
||||||
|
if nightly
|
||||||
|
then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default)
|
||||||
|
else pkgs.rust-bin.stable.latest.default;
|
||||||
|
|
||||||
craneLib = (crane.mkLib pkgs).overrideToolchain rust-stable;
|
craneLib = (crane.mkLib pkgs).overrideToolchain rust;
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
pkg-config
|
pkg-config
|
||||||
|
@ -79,7 +82,7 @@
|
||||||
statix
|
statix
|
||||||
ltex-ls
|
ltex-ls
|
||||||
|
|
||||||
rust-stable
|
rust
|
||||||
rust-analyzer
|
rust-analyzer
|
||||||
cargo-edit
|
cargo-edit
|
||||||
cargo-expand
|
cargo-expand
|
||||||
|
|
|
@ -3,61 +3,67 @@ mod struct_to_ci_enum;
|
||||||
|
|
||||||
use mark_as_ci_command::generate_final_function;
|
use mark_as_ci_command::generate_final_function;
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
use quote::quote;
|
use quote::{format_ident, quote};
|
||||||
use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function};
|
use struct_to_ci_enum::{generate_command_enum, generate_generate_ci_function, generate_help_function};
|
||||||
use syn::{self, ItemFn, Field, parse::Parser};
|
use syn::{self, parse_quote, parse_str, DeriveInput, FieldMutability, ItemFn, Token, Visibility};
|
||||||
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn turn_struct_to_ci_commands(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn turn_struct_to_ci_command_enum(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
// Construct a representation of Rust code as a syntax tree
|
// Construct a representation of Rust code as a syntax tree
|
||||||
// that we can manipulate
|
// that we can manipulate
|
||||||
let input = syn::parse(input)
|
let mut input: DeriveInput = syn::parse(input).expect("This should always be valid rust code, as it's extracted from direct code");
|
||||||
.expect("This should always be valid rust code, as it's extracted from direct code");
|
|
||||||
|
let mut named_fields = 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();
|
||||||
|
|
||||||
|
let attr_parsed = parse_quote! {
|
||||||
|
/// This is a help function
|
||||||
|
};
|
||||||
|
|
||||||
|
named_fields.named.push(syn::Field {
|
||||||
|
attrs: vec![attr_parsed],
|
||||||
|
// attrs: attr_parser
|
||||||
|
// .parse("#[doc = r\"This is a help function\"]".to_token_stream().into())
|
||||||
|
// .expect("See reason for other one"),
|
||||||
|
vis: Visibility::Inherited,
|
||||||
|
mutability: FieldMutability::None,
|
||||||
|
ident: Some(format_ident!("help")),
|
||||||
|
colon_token: Some(Token![:](Span::call_site())),
|
||||||
|
ty: parse_str("fn(Option<String>) -> String").expect("This is static and valid rust code"),
|
||||||
|
});
|
||||||
|
|
||||||
|
match &mut input.data {
|
||||||
|
syn::Data::Struct(input) => input.fields = syn::Fields::Named(named_fields.clone()),
|
||||||
|
_ => unreachable!("This was a DataStruct before"),
|
||||||
|
};
|
||||||
|
|
||||||
// Build the trait implementation
|
// Build the trait implementation
|
||||||
let generate_ci_function: TokenStream2 = generate_generate_ci_function(&input);
|
let generate_ci_function: TokenStream2 = generate_generate_ci_function(&input);
|
||||||
|
|
||||||
let command_enum = generate_command_enum(&input);
|
let command_enum = generate_command_enum(&named_fields);
|
||||||
|
|
||||||
|
let help_function = generate_help_function(&named_fields);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
#command_enum
|
#command_enum
|
||||||
|
|
||||||
#generate_ci_function
|
#generate_ci_function
|
||||||
|
|
||||||
|
//#help_function
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a default lua function implementation.
|
|
||||||
// TODO: Is this needed?
|
|
||||||
#[proc_macro_attribute]
|
|
||||||
pub fn gen_lua_function(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
|
||||||
// Construct a representation of Rust code as a syntax tree
|
|
||||||
// that we can manipulate
|
|
||||||
let parser = Field::parse_named;
|
|
||||||
let input = parser.parse(input)
|
|
||||||
.expect("This is only defined for named fileds.");
|
|
||||||
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
#input
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Turn a function into a valid ci command function
|
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn ci_command(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
pub fn ci_command(_attrs: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
// Construct a representation of Rust code as a syntax tree
|
let mut input: ItemFn = syn::parse(input).expect("This should always be valid rust code, as it's extracted from direct code");
|
||||||
// that we can manipulate
|
let output_function = generate_final_function(&mut input);
|
||||||
let mut input: ItemFn = syn::parse(input)
|
output_function.into()
|
||||||
.expect("This should always be valid rust code, as it's extracted from direct code");
|
|
||||||
|
|
||||||
// Build the trait implementation
|
|
||||||
let output_function: TokenStream2 = generate_final_function(&mut input);
|
|
||||||
|
|
||||||
//panic!("{:#?}", output_function);
|
|
||||||
quote! {
|
|
||||||
#output_function
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,18 +58,13 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn {
|
||||||
|
|
||||||
// There should only be two segments (the type is <String, rlua::Error>)
|
// There should only be two segments (the type is <String, rlua::Error>)
|
||||||
if filtered_paths.len() > 2 {
|
if filtered_paths.len() > 2 {
|
||||||
unreachable!(
|
unreachable!("There should be no more than two filtered_output, but got: {:#?}", filtered_paths)
|
||||||
"There should be no more than two filtered_output, but got: {:#?}",
|
|
||||||
filtered_paths
|
|
||||||
)
|
|
||||||
} else if filtered_paths.len() <= 0 {
|
} else if filtered_paths.len() <= 0 {
|
||||||
unreachable!(
|
unreachable!("There should be more than zero filtered_output, but got: {:#?}", filtered_paths)
|
||||||
"There should be more than zero filtered_output, but got: {:#?}",
|
|
||||||
filtered_paths
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if filtered_paths.len() == 2 {
|
if filtered_paths.len() == 2 {
|
||||||
// There is something else than rlua
|
// There is something else than mlua::Error
|
||||||
let gen_type = if let GenericArgument::Type(ret_type) =
|
let gen_type = if let GenericArgument::Type(ret_type) =
|
||||||
filtered_paths
|
filtered_paths
|
||||||
.first()
|
.first()
|
||||||
|
@ -97,36 +92,64 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn {
|
||||||
}
|
}
|
||||||
_ => unimplemented!("Only for path types"),
|
_ => unimplemented!("Only for path types"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let send_data = match return_type {
|
let send_data = match return_type {
|
||||||
ReturnType::Default => {
|
ReturnType::Default => {
|
||||||
quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
Event::CommandEvent(Command::#function_name_pascal)
|
Event::CommandEvent(Command::#function_name_pascal, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReturnType::Type(_, _) => {
|
ReturnType::Type(_, _) => {
|
||||||
quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
Event::CommandEvent(Command::#function_name_pascal(input_str.clone()))
|
Event::CommandEvent(Command::#function_name_pascal(input.clone()), Some(callback_tx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let output_return = match return_type {
|
||||||
|
ReturnType::Default => {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReturnType::Type(_, _) => {
|
||||||
|
quote! {
|
||||||
|
{
|
||||||
|
if let Some(output) = callback_rx.recv().await {
|
||||||
|
callback_rx.close();
|
||||||
|
return Ok(output);
|
||||||
|
} else {
|
||||||
|
return Err(mlua::Error::ExternalError(Arc::new(Error::new(
|
||||||
|
ErrorKind::Other,
|
||||||
|
"Callback reciever dropped",
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
{
|
{
|
||||||
|
let (callback_tx, mut callback_rx) = tokio::sync::mpsc::channel::<String>(256);
|
||||||
|
|
||||||
let tx:
|
let tx:
|
||||||
core::cell::Ref<
|
core::cell::Ref<tokio::sync::mpsc::Sender<crate::app::events::event_types::Event>> =
|
||||||
tokio::sync::mpsc::Sender<crate::app::events::event_types::Event>
|
|
||||||
> =
|
|
||||||
lua
|
lua
|
||||||
.app_data_ref()
|
.app_data_ref()
|
||||||
.expect("This exists, it was set before");
|
.expect("This exists, it was set before");
|
||||||
|
|
||||||
(*tx)
|
(*tx)
|
||||||
.send(#send_data)
|
.try_send(#send_data)
|
||||||
.await
|
|
||||||
.expect("This should work, as the reciever is not dropped");
|
.expect("This should work, as the reciever is not dropped");
|
||||||
|
|
||||||
|
#output_return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,6 +157,7 @@ fn append_tx_send_code(input: &mut syn::ItemFn) -> &mut syn::ItemFn {
|
||||||
|
|
||||||
let tx_send_block: Block =
|
let tx_send_block: Block =
|
||||||
syn::parse(tx_send.into()).expect("This is a static string, it will always parse");
|
syn::parse(tx_send.into()).expect("This is a static string, it will always parse");
|
||||||
|
|
||||||
let tx_send_expr_block = ExprBlock {
|
let tx_send_expr_block = ExprBlock {
|
||||||
attrs: vec![],
|
attrs: vec![],
|
||||||
label: None,
|
label: None,
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use syn::Type;
|
||||||
|
|
||||||
|
pub fn generate_command_enum(input: &syn::FieldsNamed) -> TokenStream2 {
|
||||||
|
let input_tokens: TokenStream2 = input
|
||||||
|
.named
|
||||||
|
.iter()
|
||||||
|
.map(|field| -> TokenStream2 {
|
||||||
|
let field_ident = field
|
||||||
|
.ident
|
||||||
|
.as_ref()
|
||||||
|
.expect("These are only the named fields, thus they should all have a ident.");
|
||||||
|
|
||||||
|
let enum_variant_type = match &field.ty {
|
||||||
|
syn::Type::BareFn(function) => {
|
||||||
|
let return_path = &function.inputs;
|
||||||
|
|
||||||
|
let input_type: Option<Type> = if return_path.len() == 1 {
|
||||||
|
Some(
|
||||||
|
return_path
|
||||||
|
.last()
|
||||||
|
.expect("The last element exists")
|
||||||
|
.ty
|
||||||
|
.clone(),
|
||||||
|
)
|
||||||
|
} else if return_path.len() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
panic!("The Function can only take on argument, or none");
|
||||||
|
};
|
||||||
|
input_type
|
||||||
|
}
|
||||||
|
_ => unimplemented!("This is only implemented for bare function types"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let enum_variant_name = format_ident!(
|
||||||
|
"{}",
|
||||||
|
field_ident
|
||||||
|
.to_string()
|
||||||
|
.from_case(Case::Snake)
|
||||||
|
.to_case(Case::Pascal)
|
||||||
|
);
|
||||||
|
if enum_variant_type.is_some() {
|
||||||
|
quote! {
|
||||||
|
#enum_variant_name (#enum_variant_type),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
#enum_variant_name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let gen = quote! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Command {
|
||||||
|
#input_tokens
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gen.into()
|
||||||
|
}
|
|
@ -1,63 +1,13 @@
|
||||||
use convert_case::{Case, Casing};
|
|
||||||
use proc_macro2::TokenStream as TokenStream2;
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
use syn::{self, ReturnType};
|
use syn::{parse_quote, ReturnType, Type};
|
||||||
|
|
||||||
pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 {
|
fn generate_ci_function_exposure(field: &syn::Field) -> TokenStream2 {
|
||||||
let mut functions_to_generate: Vec<TokenStream2> = vec![];
|
|
||||||
let input_tokens: TokenStream2 = match &input.data {
|
|
||||||
syn::Data::Struct(input) => match &input.fields {
|
|
||||||
syn::Fields::Named(named_fields) => named_fields
|
|
||||||
.named
|
|
||||||
.iter()
|
|
||||||
.map(|field| -> TokenStream2 {
|
|
||||||
if field.attrs.iter().any(|attribute| {
|
|
||||||
attribute.path()
|
|
||||||
== &syn::parse_str::<syn::Path>("gen_default_lua_function")
|
|
||||||
.expect("This is valid rust code")
|
|
||||||
}) {
|
|
||||||
let function_name = field
|
|
||||||
.ident
|
|
||||||
.as_ref()
|
|
||||||
.expect("These are only the named field, thus they all should have a name.");
|
|
||||||
functions_to_generate.push(quote! {
|
|
||||||
#[ci_command]
|
|
||||||
async fn #function_name(lua: &mlua::Lua, input_str: String) -> Result<(), mlua::Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
generate_ci_part(field)
|
|
||||||
} else {
|
|
||||||
generate_ci_part(field)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
|
|
||||||
_ => unimplemented!("Only implemented for named fileds"),
|
|
||||||
},
|
|
||||||
_ => unimplemented!("Only implemented for structs"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let functions_to_generate: TokenStream2 = functions_to_generate.into_iter().collect();
|
|
||||||
let gen = quote! {
|
|
||||||
pub fn generate_ci_functions(
|
|
||||||
lua: &mut mlua::Lua,
|
|
||||||
tx: tokio::sync::mpsc::Sender<crate::app::events::event_types::Event>)
|
|
||||||
{
|
|
||||||
lua.set_app_data(tx);
|
|
||||||
let globals = lua.globals();
|
|
||||||
#input_tokens
|
|
||||||
}
|
|
||||||
#functions_to_generate
|
|
||||||
};
|
|
||||||
gen.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_ci_part(field: &syn::Field) -> TokenStream2 {
|
|
||||||
let field_ident = field
|
let field_ident = field
|
||||||
.ident
|
.ident
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("These are only the named field, thus they all should have a name.");
|
.expect("These are only the named field, thus they all should have a name.");
|
||||||
|
|
||||||
let function_name_ident = format_ident!("fun_{}", field_ident);
|
let function_name_ident = format_ident!("fun_{}", field_ident);
|
||||||
let function_name = format!("{}", field_ident);
|
let function_name = format!("{}", field_ident);
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -79,62 +29,77 @@ fn generate_ci_part(field: &syn::Field) -> TokenStream2 {
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_command_enum(input: &syn::DeriveInput) -> TokenStream2 {
|
pub fn generate_generate_ci_function(input: &syn::DeriveInput) -> TokenStream2 {
|
||||||
let input_tokens: TokenStream2 = match &input.data {
|
let mut functions_to_generate: Vec<TokenStream2> = vec![];
|
||||||
|
|
||||||
|
let functions_to_export_in_lua: TokenStream2 = match &input.data {
|
||||||
syn::Data::Struct(input) => match &input.fields {
|
syn::Data::Struct(input) => match &input.fields {
|
||||||
syn::Fields::Named(named_fields) => named_fields
|
syn::Fields::Named(named_fields) => named_fields
|
||||||
.named
|
.named
|
||||||
.iter()
|
.iter()
|
||||||
.map(|field| -> TokenStream2 {
|
.map(|field| -> TokenStream2 {
|
||||||
let field_ident = field
|
let input_type = match &field.ty {
|
||||||
.ident
|
syn::Type::BareFn(bare_fn) => {
|
||||||
.as_ref()
|
if bare_fn.inputs.len() == 1 {
|
||||||
.expect("These are only the named field, thus they all should have a name.");
|
bare_fn.inputs.last().expect("The last element exists").ty.clone()
|
||||||
|
} else if bare_fn.inputs.len() == 0 {
|
||||||
let enum_variant_type = match &field.ty {
|
let input_type: Type = parse_quote! {()};
|
||||||
syn::Type::BareFn(function) => {
|
input_type
|
||||||
let return_path: &ReturnType = &function.output;
|
} else {
|
||||||
match return_path {
|
panic!("The Function can only take on argument, or none");
|
||||||
ReturnType::Default => None,
|
|
||||||
ReturnType::Type(_, return_type) => Some(match *(return_type.to_owned()) {
|
|
||||||
syn::Type::Path(path_type) => path_type
|
|
||||||
.path
|
|
||||||
.get_ident()
|
|
||||||
.expect("A path should either be complete, or only conain one segment")
|
|
||||||
.to_owned(),
|
|
||||||
_ => unimplemented!("This is only implemented for path types"),
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unimplemented!("This is only implemented for bare function types"),
|
_ => unimplemented!("This is only implemented for bare function types"),
|
||||||
};
|
};
|
||||||
|
let return_type = match &field.ty {
|
||||||
|
syn::Type::BareFn(function) => {
|
||||||
|
let return_path: &ReturnType = &function.output;
|
||||||
|
match return_path {
|
||||||
|
ReturnType::Default => None,
|
||||||
|
ReturnType::Type(_, return_type) => Some(return_type.to_owned()) }
|
||||||
|
}
|
||||||
|
_ => unimplemented!("This is only implemented for bare function types"),
|
||||||
|
};
|
||||||
|
|
||||||
let enum_variant_name = format_ident!(
|
let function_name = field
|
||||||
"{}",
|
.ident
|
||||||
field_ident.to_string().from_case(Case::Snake).to_case(Case::Pascal)
|
.as_ref()
|
||||||
);
|
.expect("These are only the named field, thus they all should have a name.");
|
||||||
if enum_variant_type.is_some() {
|
|
||||||
quote! {
|
if let Some(ret_type) = return_type {
|
||||||
#enum_variant_name (#enum_variant_type),
|
functions_to_generate.push(quote! {
|
||||||
|
#[ci_command]
|
||||||
|
async fn #function_name(lua: &mlua::Lua, input: #input_type) -> Result<#ret_type, mlua::Error> {
|
||||||
}
|
}
|
||||||
.into()
|
});
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
functions_to_generate.push(quote! {
|
||||||
#enum_variant_name,
|
#[ci_command]
|
||||||
|
async fn #function_name(lua: &mlua::Lua, input: #input_type) -> Result<(), mlua::Error> {
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
generate_ci_function_exposure(field)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
||||||
_ => unimplemented!("Only implemented for named fileds"),
|
_ => unimplemented!("Only implemented for named fileds"),
|
||||||
},
|
},
|
||||||
_ => unimplemented!("Only implemented for structs"),
|
_ => unimplemented!("Only implemented for structs"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let functions_to_generate: TokenStream2 = functions_to_generate.into_iter().collect();
|
||||||
let gen = quote! {
|
let gen = quote! {
|
||||||
#[derive(Debug)]
|
pub fn generate_ci_functions(
|
||||||
pub enum Command {
|
lua: &mut mlua::Lua,
|
||||||
#input_tokens
|
tx: tokio::sync::mpsc::Sender<crate::app::events::event_types::Event>
|
||||||
|
)
|
||||||
|
{
|
||||||
|
lua.set_app_data(tx);
|
||||||
|
let globals = lua.globals();
|
||||||
|
#functions_to_export_in_lua
|
||||||
}
|
}
|
||||||
|
#functions_to_generate
|
||||||
};
|
};
|
||||||
gen.into()
|
gen.into()
|
||||||
}
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
use proc_macro2::TokenStream as TokenStream2;
|
||||||
|
use quote::{quote, ToTokens};
|
||||||
|
|
||||||
|
pub fn generate_help_function(input: &syn::FieldsNamed) -> TokenStream2 {
|
||||||
|
let input: Vec<_> = input.named.iter().collect();
|
||||||
|
|
||||||
|
let combined_help_text: TokenStream2 = input
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
|
let attrs_with_doc: Vec<TokenStream2> = field
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.filter_map(|attr| {
|
||||||
|
if attr.path().is_ident("doc") {
|
||||||
|
let help_text = attr
|
||||||
|
.meta
|
||||||
|
.require_name_value()
|
||||||
|
.expect("This is a named value type, because all doc comments work this way")
|
||||||
|
.value
|
||||||
|
.clone();
|
||||||
|
Some(help_text.into_token_stream().into())
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if attrs_with_doc.len() == 0 {
|
||||||
|
// TODO there should be a better panic function, than the generic one
|
||||||
|
panic!(
|
||||||
|
"The command named: `{}`, does not provide a help message",
|
||||||
|
field.ident.as_ref().expect("These are all named")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let help_text_for_one_command_combined: TokenStream2 = attrs_with_doc.into_iter().collect();
|
||||||
|
return help_text_for_one_command_combined;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[ci_command]
|
||||||
|
async fn help(
|
||||||
|
lua: &mlua::Lua,
|
||||||
|
input_str: Option<String>
|
||||||
|
) -> Result<String, mlua::Error> {
|
||||||
|
// TODO add a way to filter the help based on the input
|
||||||
|
|
||||||
|
let output = "These functions exist:\n";
|
||||||
|
output.push_str(#combined_help_text);
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
pub mod generate_command_enum;
|
||||||
|
pub mod generate_generate_ci_function;
|
||||||
|
pub mod generate_help_function;
|
||||||
|
|
||||||
|
pub use generate_command_enum::*;
|
||||||
|
pub use generate_generate_ci_function::*;
|
||||||
|
pub use generate_help_function::*;
|
|
@ -1,9 +1,11 @@
|
||||||
// FIXME: This file needs documentation with examples of how the proc macros work.
|
// FIXME: This file needs documentation with examples of how the proc macros work.
|
||||||
// for now use `cargo expand app::command_interface` for an overview
|
// for now use `cargo expand app::command_interface` for an overview
|
||||||
use lua_macros::{ci_command, turn_struct_to_ci_commands};
|
|
||||||
|
|
||||||
use super::events::event_types::Event;
|
use std::{io::{Error, ErrorKind}, sync::Arc};
|
||||||
|
|
||||||
|
use lua_macros::{ci_command, turn_struct_to_ci_command_enum};
|
||||||
|
|
||||||
|
use crate::app::event_types::Event;
|
||||||
/// This struct is here to guarantee, that all functions actually end up in the lua context.
|
/// This struct is here to guarantee, that all functions actually end up in the lua context.
|
||||||
/// I.e. Rust should throw a compile error, when one field is added, but not a matching function.
|
/// I.e. Rust should throw a compile error, when one field is added, but not a matching function.
|
||||||
///
|
///
|
||||||
|
@ -20,41 +22,28 @@ use super::events::event_types::Event;
|
||||||
/// ```
|
/// ```
|
||||||
/// where $return_type is the type returned by the function (the only supported ones are right now
|
/// where $return_type is the type returned by the function (the only supported ones are right now
|
||||||
/// `String` and `()`).
|
/// `String` and `()`).
|
||||||
#[turn_struct_to_ci_commands]
|
|
||||||
|
#[turn_struct_to_ci_command_enum]
|
||||||
struct Commands {
|
struct Commands {
|
||||||
/// Greets the user
|
/// Greets the user
|
||||||
greet: fn(usize) -> String,
|
greet: fn(String) -> String,
|
||||||
|
|
||||||
/// Closes the application
|
/// Closes the application
|
||||||
#[gen_default_lua_function]
|
//#[expose(lua)]
|
||||||
exit: fn(),
|
exit: fn(),
|
||||||
|
|
||||||
/// Shows the command line
|
/// Shows the command line
|
||||||
#[gen_default_lua_function]
|
|
||||||
command_line_show: fn(),
|
command_line_show: fn(),
|
||||||
|
|
||||||
/// Hides the command line
|
/// Hides the command line
|
||||||
#[gen_default_lua_function]
|
|
||||||
command_line_hide: fn(),
|
command_line_hide: fn(),
|
||||||
|
|
||||||
/// Go to the next plane
|
/// Go to the next plane
|
||||||
#[gen_default_lua_function]
|
|
||||||
cycle_planes: fn(),
|
cycle_planes: fn(),
|
||||||
/// Go to the previous plane
|
/// Go to the previous plane
|
||||||
#[gen_default_lua_function]
|
|
||||||
cycle_planes_rev: fn(),
|
cycle_planes_rev: fn(),
|
||||||
|
|
||||||
/// Send a message to the current room
|
/// Send a message to the current room
|
||||||
/// The send message is interpreted literally.
|
/// The send message is interpreted literally.
|
||||||
room_message_send: fn(String) -> String,
|
room_message_send: fn(String) -> String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[ci_command]
|
|
||||||
async fn greet(lua: &mlua::Lua, input_str: String) -> Result<String, mlua::Error> {
|
|
||||||
Ok(format!("Name is {}", input_str))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ci_command]
|
|
||||||
async fn room_message_send(lua: &mlua::Lua, input_str: String) -> Result<String, mlua::Error> {
|
|
||||||
Ok(format!("Sent message: {}", input_str))
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
use anyhow::Result;
|
|
||||||
use cli_log::info;
|
|
||||||
|
|
||||||
use crate::app::{events::event_types::EventStatus, App};
|
|
||||||
|
|
||||||
pub async fn handle(app: &mut App<'_>, output: &String) -> Result<EventStatus> {
|
|
||||||
info!("Recieved command output: `{}`", output);
|
|
||||||
app.ui.set_command_output(output);
|
|
||||||
|
|
||||||
Ok(EventStatus::Ok)
|
|
||||||
}
|
|
|
@ -2,39 +2,65 @@ use crate::app::{command_interface::Command, events::event_types::EventStatus, A
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use cli_log::info;
|
use cli_log::info;
|
||||||
|
|
||||||
pub async fn handle(app: &mut App<'_>, command: &Command) -> Result<EventStatus> {
|
pub async fn handle(
|
||||||
|
app: &mut App<'_>,
|
||||||
|
command: &Command,
|
||||||
|
send_output: bool,
|
||||||
|
) -> Result<(EventStatus, String)> {
|
||||||
|
macro_rules! set_status_output {
|
||||||
|
($str:expr) => {
|
||||||
|
if send_output {
|
||||||
|
app.ui.set_command_output($str);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($str:expr, $($args:ident),+) => {
|
||||||
|
if send_output {
|
||||||
|
app.ui.set_command_output(&format!($str, $($args),+));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
info!("Handling command: {:#?}", command);
|
info!("Handling command: {:#?}", command);
|
||||||
|
|
||||||
Ok(match command {
|
Ok(match command {
|
||||||
Command::Exit => EventStatus::Terminate,
|
Command::Exit => (
|
||||||
|
EventStatus::Terminate,
|
||||||
|
"Terminated the application".to_owned(),
|
||||||
|
),
|
||||||
|
|
||||||
Command::CommandLineShow => {
|
Command::CommandLineShow => {
|
||||||
app.ui.cli_enable();
|
app.ui.cli_enable();
|
||||||
EventStatus::Ok
|
set_status_output!("CLI online");
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
Command::CommandLineHide => {
|
Command::CommandLineHide => {
|
||||||
app.ui.cli_disable();
|
app.ui.cli_disable();
|
||||||
EventStatus::Ok
|
set_status_output!("CLI offline");
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::CyclePlanes => {
|
Command::CyclePlanes => {
|
||||||
app.ui.cycle_main_input_position();
|
app.ui.cycle_main_input_position();
|
||||||
EventStatus::Ok
|
set_status_output!("Switched main input position");
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
Command::CyclePlanesRev => {
|
Command::CyclePlanesRev => {
|
||||||
app.ui.cycle_main_input_position_rev();
|
app.ui.cycle_main_input_position_rev();
|
||||||
EventStatus::Ok
|
set_status_output!("Switched main input position; reversed");
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::RoomMessageSend(msg) => {
|
Command::RoomMessageSend(msg) => {
|
||||||
if let Some(room) = app.status.room_mut() {
|
if let Some(room) = app.status.room_mut() {
|
||||||
room.send(msg.clone()).await?;
|
room.send(msg.clone()).await?;
|
||||||
}
|
}
|
||||||
EventStatus::Ok
|
set_status_output!("Send message: `{}`", msg);
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
Command::Greet(name) => {
|
Command::Greet(name) => {
|
||||||
info!("Greated {}", name);
|
info!("Greated {}", name);
|
||||||
EventStatus::Ok
|
set_status_output!("Hi, {}!", name);
|
||||||
|
(EventStatus::Ok, "".to_owned())
|
||||||
}
|
}
|
||||||
|
Command::Help(_) => todo!(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,38 @@
|
||||||
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use cli_log::info;
|
use cli_log::{debug, info};
|
||||||
|
use tokio::{task, time::timeout};
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{events::event_types::EventStatus, App};
|
||||||
events::event_types::{Event, EventStatus},
|
|
||||||
App,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn handle(app: &mut App<'_>, command: &str) -> Result<EventStatus> {
|
pub async fn handle(app: &mut App<'_>, command: String) -> Result<EventStatus> {
|
||||||
info!("Recieved ci command: `{command}`; executing..");
|
info!("Recieved ci command: `{command}`; executing..");
|
||||||
|
|
||||||
// TODO: Should the ci support more than strings?
|
let local = task::LocalSet::new();
|
||||||
let output = app
|
|
||||||
.lua
|
|
||||||
.load(command)
|
|
||||||
.eval_async::<String>()
|
|
||||||
.await
|
|
||||||
.with_context(|| format!("Failed to execute: `{command}`"))?;
|
|
||||||
info!("Function evaluated to: `{output}`");
|
|
||||||
|
|
||||||
app.tx.send(Event::CiOutput(output)).await.context("Failed to send ci output to internal event stream")?;
|
// Run the local task set.
|
||||||
|
let output = local
|
||||||
|
.run_until(async move {
|
||||||
|
let lua = Arc::clone(&app.lua);
|
||||||
|
debug!("before_handle");
|
||||||
|
let c_handle = task::spawn_local(async move {
|
||||||
|
lua.load(&command)
|
||||||
|
// FIXME this assumes string output only
|
||||||
|
.eval_async::<String>()
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to execute: `{command}`"))
|
||||||
|
});
|
||||||
|
debug!("after_handle");
|
||||||
|
c_handle
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
debug!("after_thread");
|
||||||
|
|
||||||
|
let output = timeout(Duration::from_secs(10), output)
|
||||||
|
.await
|
||||||
|
.context("Failed to join lua command executor")???;
|
||||||
|
info!("Command returned: `{}`", output);
|
||||||
|
|
||||||
Ok(EventStatus::Ok)
|
Ok(EventStatus::Ok)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,14 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
code: KeyCode::Esc, ..
|
code: KeyCode::Esc, ..
|
||||||
}) => {
|
}) => {
|
||||||
app.tx
|
app.tx
|
||||||
.send(Event::CommandEvent(Command::Exit))
|
.send(Event::CommandEvent(Command::Exit, None))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Tab, ..
|
code: KeyCode::Tab, ..
|
||||||
}) => {
|
}) => {
|
||||||
app.tx
|
app.tx
|
||||||
.send(Event::CommandEvent(Command::CyclePlanes))
|
.send(Event::CommandEvent(Command::CyclePlanes, None))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
@ -31,7 +31,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
app.tx
|
app.tx
|
||||||
.send(Event::CommandEvent(Command::CyclePlanesRev))
|
.send(Event::CommandEvent(Command::CyclePlanesRev, None))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
@ -40,7 +40,7 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
app.tx
|
app.tx
|
||||||
.send(Event::CommandEvent(Command::CommandLineShow))
|
.send(Event::CommandEvent(Command::CommandLineShow, None))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
input => match app.ui.input_position() {
|
input => match app.ui.input_position() {
|
||||||
|
@ -52,9 +52,10 @@ pub async fn handle(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<E
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
app.tx
|
app.tx
|
||||||
.send(Event::CommandEvent(Command::RoomMessageSend(
|
.send(Event::CommandEvent(
|
||||||
app.ui.message_compose.lines().join("\n"),
|
Command::RoomMessageSend(app.ui.message_compose.lines().join("\n")),
|
||||||
)))
|
None,
|
||||||
|
))
|
||||||
.await?;
|
.await?;
|
||||||
app.ui.message_compose_clear();
|
app.ui.message_compose_clear();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
mod handlers;
|
mod handlers;
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
use cli_log::{info, trace};
|
||||||
use crossterm::event::Event as CrosstermEvent;
|
use crossterm::event::Event as CrosstermEvent;
|
||||||
|
use tokio::sync::mpsc::Sender;
|
||||||
|
|
||||||
use crate::app::{command_interface::Command, status::State, App};
|
use crate::app::{command_interface::Command, status::State, App};
|
||||||
|
|
||||||
use self::handlers::{ci_output, command, lua_command, main, matrix, setup};
|
use self::handlers::{command, lua_command, main, matrix, setup};
|
||||||
|
|
||||||
use super::EventStatus;
|
use super::EventStatus;
|
||||||
|
|
||||||
|
@ -13,32 +15,39 @@ use super::EventStatus;
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
InputEvent(CrosstermEvent),
|
InputEvent(CrosstermEvent),
|
||||||
MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse),
|
MatrixEvent(matrix_sdk::deserialized_responses::SyncResponse),
|
||||||
CommandEvent(Command),
|
CommandEvent(Command, Option<Sender<String>>),
|
||||||
CiOutput(String),
|
|
||||||
LuaCommand(String),
|
LuaCommand(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
pub async fn handle(&self, app: &mut App<'_>) -> Result<EventStatus> {
|
||||||
|
trace!("Recieved event to handle: `{:#?}`", &self);
|
||||||
match &self {
|
match &self {
|
||||||
Event::MatrixEvent(event) => matrix::handle(app, event)
|
Event::MatrixEvent(event) => matrix::handle(app, event)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to handle matrix event: `{:#?}`", event)),
|
.with_context(|| format!("Failed to handle matrix event: `{:#?}`", event)),
|
||||||
|
|
||||||
Event::CommandEvent(event) => command::handle(app, event)
|
Event::CommandEvent(event, callback_tx) => {
|
||||||
.await
|
let (result, output) = command::handle(app, event, callback_tx.is_some())
|
||||||
.with_context(|| format!("Failed to handle command event: `{:#?}`", event)),
|
.await
|
||||||
Event::CiOutput(output) => ci_output::handle(app, output).await.with_context(|| {
|
.with_context(|| format!("Failed to handle command event: `{:#?}`", event))?;
|
||||||
format!("Failed to handle command interface output: `{:#?}`", output)
|
|
||||||
}),
|
if let Some(callback_tx) = callback_tx {
|
||||||
Event::LuaCommand(lua_code) => {
|
callback_tx
|
||||||
lua_command::handle(app, lua_code).await.with_context(|| {
|
.send(output.clone())
|
||||||
format!("Failed to handle lua code: `{:#?}`", lua_code)
|
.await
|
||||||
})
|
.with_context(|| format!("Failed to send command output: {}", output))?;
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
Event::LuaCommand(lua_code) => lua_command::handle(app, lua_code.to_owned())
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to handle lua code: `{:#?}`", lua_code)),
|
||||||
|
|
||||||
Event::InputEvent(event) => match app.status.state() {
|
Event::InputEvent(event) => match app.status.state() {
|
||||||
State::None => Ok(EventStatus::Ok),
|
State::None => unreachable!(
|
||||||
|
"This state should not be available, when we are in the input handling"
|
||||||
|
),
|
||||||
State::Main => main::handle(app, event)
|
State::Main => main::handle(app, event)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to handle input event: `{:#?}`", event)),
|
.with_context(|| format!("Failed to handle input event: `{:#?}`", event)),
|
||||||
|
|
|
@ -2,9 +2,9 @@ pub mod command_interface;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::{path::Path, sync::Arc};
|
||||||
|
|
||||||
use anyhow::{Error, Result, Context};
|
use anyhow::{Context, Error, Result};
|
||||||
use cli_log::info;
|
use cli_log::info;
|
||||||
use matrix_sdk::Client;
|
use matrix_sdk::Client;
|
||||||
use mlua::Lua;
|
use mlua::Lua;
|
||||||
|
@ -31,16 +31,16 @@ pub struct App<'ui> {
|
||||||
input_listener_killer: CancellationToken,
|
input_listener_killer: CancellationToken,
|
||||||
matrix_listener_killer: CancellationToken,
|
matrix_listener_killer: CancellationToken,
|
||||||
|
|
||||||
lua: Lua,
|
lua: Arc<Lua>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App<'_> {
|
impl App<'_> {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
fn set_up_lua(tx: mpsc::Sender<Event>) -> Lua {
|
fn set_up_lua(tx: mpsc::Sender<Event>) -> Arc<Lua> {
|
||||||
let mut lua = Lua::new();
|
let mut lua = Lua::new();
|
||||||
|
|
||||||
generate_ci_functions(&mut lua, tx);
|
generate_ci_functions(&mut lua, tx);
|
||||||
lua
|
Arc::new(lua)
|
||||||
}
|
}
|
||||||
|
|
||||||
let path: &std::path::Path = Path::new("userdata/accounts.json");
|
let path: &std::path::Path = Path::new("userdata/accounts.json");
|
||||||
|
|
Reference in New Issue