Compare commits
No commits in common. "7aea23f3b650820453970a625b92626f72042a34" and "d93b00484eea27f1ff853877012e6fc31f8d5004" have entirely different histories.
7aea23f3b6
...
d93b00484e
File diff suppressed because it is too large
Load Diff
|
@ -11,15 +11,15 @@ default = ["tui"]
|
||||||
tui = ["dep:tui", "dep:tui-textarea", "dep:crossterm", "dep:tokio-util", "dep:serde", "dep:indexmap"]
|
tui = ["dep:tui", "dep:tui-textarea", "dep:crossterm", "dep:tokio-util", "dep:serde", "dep:indexmap"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.4.2", features = ["derive"] }
|
clap = { version = "4.3.19", features = ["derive"] }
|
||||||
cli-log = "2.0"
|
cli-log = "2.0"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
matrix-sdk = "0.6"
|
matrix-sdk = "0.6"
|
||||||
tokio = { version = "1.32", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.29", features = ["macros", "rt-multi-thread"] }
|
||||||
|
|
||||||
# lua stuff
|
# lua stuff
|
||||||
language_macros = { path = "./language_macros" }
|
language_macros = { path = "./language_macros" }
|
||||||
mlua = { version = "0.9.1", features = ["lua54", "async", "send", "serialize"] }
|
mlua = { version = "0.8.9", features = ["lua54", "async", "send", "serialize"] }
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
|
|
||||||
# tui feature specific parts
|
# tui feature specific parts
|
||||||
|
|
32
flake.lock
32
flake.lock
|
@ -16,11 +16,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1693762390,
|
"lastModified": 1688772518,
|
||||||
"narHash": "sha256-IzmbGqrzVUzyc1IxgH62nQ9l6Dz7hq6JWIeyZiDZqKw=",
|
"narHash": "sha256-ol7gZxwvgLnxNSZwFTDJJ49xVY5teaSvF7lzlo3YQfM=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "7b92b595c947b3c82ba04f78e58446faac20c4cb",
|
"rev": "8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -50,11 +50,11 @@
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1692799911,
|
"lastModified": 1689068808,
|
||||||
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
|
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
|
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -65,11 +65,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1693654884,
|
"lastModified": 1690327932,
|
||||||
"narHash": "sha256-EqKKEl+IOS8TSjkt+xn1qGpsjnx5/ag33YNQ1+c7OuM=",
|
"narHash": "sha256-Fv7PYZxN4eo0K6zXhHG/vOc+e2iuqQ5ywDrh0yeRjP0=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e7f35e03abd06a2faef6684d0de813370e13bda8",
|
"rev": "a9b47d85504bdd199e90846622c76aa0bfeabfac",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -100,11 +100,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1693707092,
|
"lastModified": 1690338181,
|
||||||
"narHash": "sha256-HR1EnynBSPqbt+04/yxxqsG1E3n6uXrOl7SPco/UnYo=",
|
"narHash": "sha256-Sz2oQ9aNS3MVncnCMndr0302G26UrFUfPynoH2iLjsg=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "98ccb73e6eefc481da6039ee57ad8818d1ca8d56",
|
"rev": "b7f0b7b58b3c6f14a1377ec31a3d78b23ab843ec",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -116,11 +116,11 @@
|
||||||
"rustc_cranelift_backend": {
|
"rustc_cranelift_backend": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1693573696,
|
"lastModified": 1690205271,
|
||||||
"narHash": "sha256-9V2JGtm7tiAAxaIMXzHLliS7rRyuogMxCmP8u9VRjBE=",
|
"narHash": "sha256-mMj1dSlGzM+jCgtVX/KNxdYPXohS22RyU/uWmhR7EA0=",
|
||||||
"owner": "bjorn3",
|
"owner": "bjorn3",
|
||||||
"repo": "rustc_codegen_cranelift",
|
"repo": "rustc_codegen_cranelift",
|
||||||
"rev": "0559de65672243a97d6cd6e6ee6a8e8c291ef4ce",
|
"rev": "6641b3a548a425eae518b675e43b986094daf609",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -133,7 +133,7 @@
|
||||||
"rustc_cranelift_backend_src": {
|
"rustc_cranelift_backend_src": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"narHash": "sha256-h7VLPtyiXg5KpbQKPo3RKl/+AnTWqimKqAXc1VngYEo=",
|
"narHash": "sha256-E4n1Dqne8/9XHVKFqApdp2baxXun0DMl+oHfo4+oq3Q=",
|
||||||
"type": "tarball",
|
"type": "tarball",
|
||||||
"url": "https://github.com/bjorn3/rustc_codegen_cranelift/releases/download/dev/cg_clif-x86_64-unknown-linux-gnu.tar.xz"
|
"url": "https://github.com/bjorn3/rustc_codegen_cranelift/releases/download/dev/cg_clif-x86_64-unknown-linux-gnu.tar.xz"
|
||||||
},
|
},
|
||||||
|
|
24
flake.nix
24
flake.nix
|
@ -52,28 +52,6 @@
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
flake-utils.lib.eachDefaultSystem (system: let
|
flake-utils.lib.eachDefaultSystem (system: let
|
||||||
inherit (pkgs) lib;
|
|
||||||
bintools-wrapper = "${nixpkgs}/pkgs/build-support/bintools-wrapper";
|
|
||||||
mold' = pkgs.symlinkJoin {
|
|
||||||
name = "mold";
|
|
||||||
paths = [pkgs.mold];
|
|
||||||
nativeBuildInputs = [pkgs.makeWrapper];
|
|
||||||
suffixSalt = lib.replaceStrings ["-" "."] ["_" "_"] pkgs.targetPlatform.config;
|
|
||||||
postBuild = ''
|
|
||||||
for bin in ${pkgs.mold}/bin/*; do
|
|
||||||
rm $out/bin/"$(basename "$bin")"
|
|
||||||
|
|
||||||
export prog="$bin"
|
|
||||||
substituteAll "${bintools-wrapper}/ld-wrapper.sh" $out/bin/"$(basename "$bin")"
|
|
||||||
chmod +x $out/bin/"$(basename "$bin")"
|
|
||||||
|
|
||||||
mkdir -p $out/nix-support
|
|
||||||
substituteAll "${bintools-wrapper}/add-flags.sh" $out/nix-support/add-flags.sh
|
|
||||||
substituteAll "${bintools-wrapper}/add-hardening.sh" $out/nix-support/add-hardening.sh
|
|
||||||
substituteAll "${bintools-wrapper}/../wrapper-common/utils.bash" $out/nix-support/utils.bash
|
|
||||||
done
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
c_rust = pkgs.rust-bin.fromRustupToolchainFile "${rustc_cranelift_backend}/rust-toolchain";
|
c_rust = pkgs.rust-bin.fromRustupToolchainFile "${rustc_cranelift_backend}/rust-toolchain";
|
||||||
rcb = pkgs.stdenv.mkDerivation {
|
rcb = pkgs.stdenv.mkDerivation {
|
||||||
pname = "rustc_cranelift_backend";
|
pname = "rustc_cranelift_backend";
|
||||||
|
@ -168,7 +146,7 @@
|
||||||
|
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
pkg-config
|
pkg-config
|
||||||
mold'
|
mold
|
||||||
rcb
|
rcb
|
||||||
];
|
];
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
|
|
|
@ -126,7 +126,67 @@ fn get_function_body(field: &Field, has_input: bool, output_type: &Option<Type>)
|
||||||
|
|
||||||
let function_return = if let Some(_) = output_type {
|
let function_return = if let Some(_) = output_type {
|
||||||
quote! {
|
quote! {
|
||||||
return Ok(output.into_lua(lua).expect("This conversion should always work"));
|
let converted_output = lua
|
||||||
|
.to_value(&output)
|
||||||
|
.expect("This conversion should (indirectely) be checked at compile time");
|
||||||
|
if let mlua::Value::Table(table) = converted_output {
|
||||||
|
let real_output: mlua::Value = match output {
|
||||||
|
CommandTransferValue::Nil => table
|
||||||
|
.get("Nil")
|
||||||
|
.expect("This should exist"),
|
||||||
|
CommandTransferValue::Boolean(_) => table
|
||||||
|
.get("Boolean")
|
||||||
|
.expect("This should exist"),
|
||||||
|
CommandTransferValue::Integer(_) => table
|
||||||
|
.get("Integer")
|
||||||
|
.expect("This should exist"),
|
||||||
|
CommandTransferValue::Number(_) => table
|
||||||
|
.get("Number")
|
||||||
|
.expect("This should exist"),
|
||||||
|
CommandTransferValue::String(_) => table
|
||||||
|
.get("String")
|
||||||
|
.expect("This should exist"),
|
||||||
|
CommandTransferValue::Table(_) => {
|
||||||
|
todo!()
|
||||||
|
// FIXME(@Soispha): This returns a table with the values wrapped the
|
||||||
|
// same way the values above are wrapped. That is (from the greet_multiple
|
||||||
|
// function):
|
||||||
|
// ```json
|
||||||
|
// {
|
||||||
|
// "Table": {
|
||||||
|
// "UserName1": {
|
||||||
|
// "Integer": 2
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
// whilst the output should be:
|
||||||
|
// ```json
|
||||||
|
// {
|
||||||
|
// "UserName1": 2
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
// That table would need to be unpacked, but this requires some recursive
|
||||||
|
// function, which seems not very performance oriented.
|
||||||
|
//
|
||||||
|
// My first (quick) attempt:
|
||||||
|
//let mut output_table = lua.create_table().expect("This should work?");
|
||||||
|
//let initial_table: mlua::Value = table
|
||||||
|
// .get("Table")
|
||||||
|
// .expect("This should exist");
|
||||||
|
//while let mlua::Value::Table(table) = initial_table {
|
||||||
|
// for pair in table.pairs() {
|
||||||
|
// let (key, value) = pair.expect("This should also work?");
|
||||||
|
// output_table.set(key, value);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return Ok(real_output);
|
||||||
|
} else {
|
||||||
|
unreachable!("Lua serializes these things always in a table");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
|
@ -136,7 +196,7 @@ fn get_function_body(field: &Field, has_input: bool, output_type: &Option<Type>)
|
||||||
let does_function_expect_output = if output_type.is_some() {
|
let does_function_expect_output = if output_type.is_some() {
|
||||||
quote! {
|
quote! {
|
||||||
// We didn't receive output but expected output. Raise an error to notify the lua code
|
// We didn't receive output but expected output. Raise an error to notify the lua code
|
||||||
// about it.
|
// about it
|
||||||
return Err(mlua::Error::ExternalError(std::sync::Arc::new(
|
return Err(mlua::Error::ExternalError(std::sync::Arc::new(
|
||||||
err
|
err
|
||||||
)));
|
)));
|
||||||
|
@ -150,7 +210,7 @@ fn get_function_body(field: &Field, has_input: bool, output_type: &Option<Type>)
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::<CommandTransferValue>();
|
let (callback_tx, callback_rx) = tokio::sync::oneshot::channel::<CommandTransferValue>();
|
||||||
let tx: mlua::AppDataRef<tokio::sync::mpsc::Sender<Event>> =
|
let tx: core::cell::Ref<tokio::sync::mpsc::Sender<Event>> =
|
||||||
lua.app_data_ref().expect("This should exist, it was set before");
|
lua.app_data_ref().expect("This should exist, it was set before");
|
||||||
|
|
||||||
(*tx)
|
(*tx)
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
use cli_log::{debug, info};
|
|
||||||
use mlua::{IntoLua, LuaSerdeExt, Table, Value};
|
|
||||||
|
|
||||||
use super::CommandTransferValue;
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for CommandTransferValue {
|
|
||||||
fn into_lua(self, lua: &'lua mlua::Lua) -> mlua::Result<mlua::Value<'lua>> {
|
|
||||||
let converted_output = lua.to_value(&self)?;
|
|
||||||
return unwrap(converted_output, lua);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn unwrap<'lua>(
|
|
||||||
value_to_unwrap: Value<'lua>,
|
|
||||||
lua: &'lua mlua::Lua,
|
|
||||||
) -> mlua::Result<mlua::Value<'lua>> {
|
|
||||||
fn unwrap_first_level<'lua>(table: Table<'lua>) -> mlua::Result<Value<'lua>> {
|
|
||||||
let (_, value): (Value, Value) = table
|
|
||||||
.pairs()
|
|
||||||
.next()
|
|
||||||
.expect("Exactly one item should extist")?;
|
|
||||||
Ok(value)
|
|
||||||
}
|
|
||||||
// That converted value looks somewhat like this (e.g. a String):
|
|
||||||
// ```
|
|
||||||
// {
|
|
||||||
// ["String"] = "hi",
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
// or like this (e.g. a table):
|
|
||||||
// ```
|
|
||||||
// {
|
|
||||||
// ["Table"] = {
|
|
||||||
// ["UserId"] = {
|
|
||||||
// ["Integer"] = 2,
|
|
||||||
// },
|
|
||||||
// ["UserName"] = {
|
|
||||||
// ["String"] = "James",
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
if let Value::Table(table) = value_to_unwrap {
|
|
||||||
let value = unwrap_first_level(table)?;
|
|
||||||
if let Value::Table(wrapped_table) = value {
|
|
||||||
info!("We've got a wtable! wtable: \n{:#?}", wrapped_table);
|
|
||||||
// we now have a wrapped table value for example like this:
|
|
||||||
// ```
|
|
||||||
// {
|
|
||||||
// ["UserId"] = {
|
|
||||||
// ["Integer"] = 2,
|
|
||||||
// },
|
|
||||||
// ["UserName"] = {
|
|
||||||
// ["String"] = "James",
|
|
||||||
// },
|
|
||||||
// ["Versions"] = {
|
|
||||||
// ["Table"] = {
|
|
||||||
// ["api"] = {
|
|
||||||
// ["Boolean"] = true,
|
|
||||||
// },
|
|
||||||
// ["interface"] = {
|
|
||||||
// ["Integer"] = 3,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
let output_table: Table = lua
|
|
||||||
.load("{}")
|
|
||||||
.eval()
|
|
||||||
.expect("This is static, it should always work");
|
|
||||||
|
|
||||||
// FIXME(@soispha): This still fails for nested tables (i.e. the table above), as it
|
|
||||||
// unpacks too much. While unpacking the while loop should stop, when a key is not from
|
|
||||||
// the CommandTransferValue family (i.e. ["Integer", "Boolean", "String", "Table",
|
|
||||||
// etc.]) <2023-09-09>
|
|
||||||
for pair in wrapped_table.pairs::<Value, Value>() {
|
|
||||||
let (key, mut raw_value) = pair?;
|
|
||||||
while let Value::Table(raw_table) = raw_value {
|
|
||||||
raw_value = unwrap_first_level(raw_table)?;
|
|
||||||
}
|
|
||||||
output_table.set(key, raw_value)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(output_table.into_lua(lua)?);
|
|
||||||
} else {
|
|
||||||
info!("We've got a normal output! output: {:#?}", value);
|
|
||||||
// we had a simple wrapped value, which is already unwrapped, thus it can be
|
|
||||||
// returned directly
|
|
||||||
return Ok(value);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!("The returned table should always only contain one element");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
use std::{collections::HashMap, fmt::Display};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
pub mod type_conversions;
|
|
||||||
|
|
||||||
// language support
|
|
||||||
pub mod lua;
|
|
||||||
|
|
||||||
pub type Table = HashMap<String, CommandTransferValue>;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub enum CommandTransferValue {
|
|
||||||
/// `nil` or `null` or `undefined`; anything which goes in that group of types.
|
|
||||||
Nil,
|
|
||||||
|
|
||||||
/// `true` or `false`.
|
|
||||||
Boolean(bool),
|
|
||||||
|
|
||||||
// A “light userdata” object, equivalent to a raw pointer.
|
|
||||||
// /* TODO */ LightUserData(LightUserData),
|
|
||||||
/// An integer number.
|
|
||||||
Integer(i64),
|
|
||||||
|
|
||||||
/// A floating point number.
|
|
||||||
Number(f64),
|
|
||||||
|
|
||||||
/// A string
|
|
||||||
String(String),
|
|
||||||
|
|
||||||
/// A table, dictionary or HashMap
|
|
||||||
Table(HashMap<String, CommandTransferValue>),
|
|
||||||
// Reference to a Lua function (or closure).
|
|
||||||
// /* TODO */ Function(Function),
|
|
||||||
|
|
||||||
// Reference to a Lua thread (or coroutine).
|
|
||||||
// /* TODO */ Thread(Thread<'lua>),
|
|
||||||
|
|
||||||
// Reference to an userdata object that holds a custom type which implements `UserData`.
|
|
||||||
// Special builtin userdata types will be represented as other `Value` variants.
|
|
||||||
// /* TODO */ UserData(AnyUserData),
|
|
||||||
|
|
||||||
// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
|
|
||||||
// /* TODO */ Error(Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for CommandTransferValue {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
CommandTransferValue::Nil => f.write_str("Nil"),
|
|
||||||
CommandTransferValue::Boolean(bool) => f.write_str(&format!("{}", bool)),
|
|
||||||
CommandTransferValue::Integer(int) => f.write_str(&format!("{}", int)),
|
|
||||||
CommandTransferValue::Number(num) => f.write_str(&format!("{}", num)),
|
|
||||||
CommandTransferValue::String(str) => f.write_str(&format!("{}", str)),
|
|
||||||
// TODO(@Soispha): The following line should be a real display call, but how do you
|
|
||||||
// format a HashMap?
|
|
||||||
CommandTransferValue::Table(table) => f.write_str(&format!("{:#?}", table)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use super::CommandTransferValue;
|
|
||||||
|
|
||||||
impl From<String> for CommandTransferValue {
|
|
||||||
fn from(s: String) -> Self {
|
|
||||||
Self::String(s.to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<f64> for CommandTransferValue {
|
|
||||||
fn from(s: f64) -> Self {
|
|
||||||
Self::Number(s.to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<i64> for CommandTransferValue {
|
|
||||||
fn from(s: i64) -> Self {
|
|
||||||
Self::Integer(s.to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<HashMap<String, CommandTransferValue>> for CommandTransferValue {
|
|
||||||
fn from(s: HashMap<String, CommandTransferValue>) -> Self {
|
|
||||||
Self::Table(s.to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<bool> for CommandTransferValue {
|
|
||||||
fn from(s: bool) -> Self {
|
|
||||||
Self::Boolean(s.to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<()> for CommandTransferValue {
|
|
||||||
fn from(_: ()) -> Self {
|
|
||||||
Self::Nil
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::thread;
|
use std::{collections::HashMap, fmt::Display, thread};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use cli_log::{debug, error, info};
|
use cli_log::{error, info, debug};
|
||||||
use mlua::{Function, Value};
|
use mlua::{Function, Value};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
runtime::Builder,
|
runtime::Builder,
|
||||||
sync::{mpsc, Mutex},
|
sync::{mpsc, Mutex},
|
||||||
|
@ -16,13 +17,95 @@ use crate::app::{
|
||||||
};
|
};
|
||||||
|
|
||||||
static LUA: OnceCell<Mutex<mlua::Lua>> = OnceCell::new();
|
static LUA: OnceCell<Mutex<mlua::Lua>> = OnceCell::new();
|
||||||
|
pub type Table = HashMap<String, CommandTransferValue>;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub enum CommandTransferValue {
|
||||||
|
/// `nil` or `null` or `undefined`; anything which goes in that group of types.
|
||||||
|
Nil,
|
||||||
|
|
||||||
|
/// `true` or `false`.
|
||||||
|
Boolean(bool),
|
||||||
|
|
||||||
|
// A "light userdata" object, equivalent to a raw pointer.
|
||||||
|
// /*TODO*/ LightUserData(LightUserData),
|
||||||
|
|
||||||
|
/// An integer number.
|
||||||
|
Integer(i64),
|
||||||
|
|
||||||
|
/// A floating point number.
|
||||||
|
Number(f64),
|
||||||
|
|
||||||
|
/// A string
|
||||||
|
String(String),
|
||||||
|
|
||||||
|
/// A table, directory or HashMap
|
||||||
|
Table(HashMap<String, CommandTransferValue>),
|
||||||
|
|
||||||
|
// Reference to a Lua function (or closure).
|
||||||
|
// /* TODO */ Function(Function),
|
||||||
|
|
||||||
|
// Reference to a Lua thread (or coroutine).
|
||||||
|
// /* TODO */ Thread(Thread<'lua>),
|
||||||
|
|
||||||
|
// Reference to a userdata object that holds a custom type which implements `UserData`.
|
||||||
|
// Special builtin userdata types will be represented as other `Value` variants.
|
||||||
|
// /* TODO */ UserData(AnyUserData),
|
||||||
|
|
||||||
|
// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
|
||||||
|
// /* TODO */ Error(Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for CommandTransferValue {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
CommandTransferValue::Nil => f.write_str("Nil"),
|
||||||
|
CommandTransferValue::Boolean(bool) => f.write_str(&format!("{}", bool)),
|
||||||
|
CommandTransferValue::Integer(int) => f.write_str(&format!("{}", int)),
|
||||||
|
CommandTransferValue::Number(num) => f.write_str(&format!("{}", num)),
|
||||||
|
CommandTransferValue::String(str) => f.write_str(&format!("{}", str)),
|
||||||
|
// TODO(@Soispha): The following line should be a real display call, but how do you
|
||||||
|
// format a HashMap?
|
||||||
|
CommandTransferValue::Table(table) => f.write_str(&format!("{:#?}", table)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This structure contains the necessary state for running an embedded Lua runtime (i.e.
|
|
||||||
/// the tread, the Lua memory, etc.).
|
|
||||||
pub struct LuaCommandManager {
|
pub struct LuaCommandManager {
|
||||||
lua_command_tx: mpsc::Sender<String>,
|
lua_command_tx: mpsc::Sender<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<String> for CommandTransferValue {
|
||||||
|
fn from(s: String) -> Self {
|
||||||
|
Self::String(s.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<f64> for CommandTransferValue {
|
||||||
|
fn from(s: f64) -> Self {
|
||||||
|
Self::Number(s.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<i64> for CommandTransferValue {
|
||||||
|
fn from(s: i64) -> Self {
|
||||||
|
Self::Integer(s.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<HashMap<String, CommandTransferValue>> for CommandTransferValue {
|
||||||
|
fn from(s: HashMap<String, CommandTransferValue>) -> Self {
|
||||||
|
Self::Table(s.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<bool> for CommandTransferValue {
|
||||||
|
fn from(s: bool) -> Self {
|
||||||
|
Self::Boolean(s.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<()> for CommandTransferValue {
|
||||||
|
fn from(_: ()) -> Self {
|
||||||
|
Self::Nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl LuaCommandManager {
|
impl LuaCommandManager {
|
||||||
pub async fn execute_code(&self, code: String) {
|
pub async fn execute_code(&self, code: String) {
|
||||||
self.lua_command_tx
|
self.lua_command_tx
|
||||||
|
@ -46,11 +129,13 @@ impl LuaCommandManager {
|
||||||
waiting for commands.."
|
waiting for commands.."
|
||||||
);
|
);
|
||||||
while let Some(command) = lua_command_rx.recv().await {
|
while let Some(command) = lua_command_rx.recv().await {
|
||||||
debug!("Recieved lua code (in LuaCommandHandler): {}", &command);
|
debug!("Recieved lua code: {}", &command);
|
||||||
let local_event_call_tx = event_call_tx.clone();
|
let local_event_call_tx = event_call_tx.clone();
|
||||||
|
|
||||||
task::spawn_local(async move {
|
task::spawn_local(async move {
|
||||||
exec_lua(&command, local_event_call_tx).await.expect(
|
exec_lua_command(&command, local_event_call_tx)
|
||||||
|
.await
|
||||||
|
.expect(
|
||||||
"This should return all relevent errors \
|
"This should return all relevent errors \
|
||||||
by other messages, \
|
by other messages, \
|
||||||
this should never error",
|
this should never error",
|
||||||
|
@ -65,7 +150,7 @@ impl LuaCommandManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exec_lua(lua_code: &str, event_call_tx: mpsc::Sender<Event>) -> Result<()> {
|
async fn exec_lua_command(command: &str, event_call_tx: mpsc::Sender<Event>) -> Result<()> {
|
||||||
let second_event_call_tx = event_call_tx.clone();
|
let second_event_call_tx = event_call_tx.clone();
|
||||||
let lua = LUA
|
let lua = LUA
|
||||||
.get_or_init(|| {
|
.get_or_init(|| {
|
||||||
|
@ -77,23 +162,21 @@ async fn exec_lua(lua_code: &str, event_call_tx: mpsc::Sender<Event>) -> Result<
|
||||||
.lock()
|
.lock()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
info!("Recieved code to execute: `{}`, executing...", &lua_code);
|
info!("Recieved code to execute: `{}`, executing...", &command);
|
||||||
let output = lua.load(lua_code).eval_async::<Value>().await;
|
let output = lua.load(command).eval_async::<Value>().await;
|
||||||
match output {
|
match output {
|
||||||
Ok(out) => {
|
Ok(out) => {
|
||||||
let to_string_fn: Function = lua.globals().get("tostring").expect("This always exists");
|
let to_string_fn: Function = lua.globals().get("tostring").expect("This always exists");
|
||||||
let output: String = to_string_fn.call(out).expect("tostring should not error");
|
let output: String = to_string_fn.call(out).expect("tostring should not error");
|
||||||
info!("Lua code `{}` evaluated to: `{}`", lua_code, &output);
|
info!("Function `{}` returned: `{}`", command, &output);
|
||||||
|
|
||||||
if output != "nil" {
|
|
||||||
event_call_tx
|
event_call_tx
|
||||||
.send(Event::CommandEvent(Command::DisplayOutput(output), None))
|
.send(Event::CommandEvent(Command::DisplayOutput(output), None))
|
||||||
.await
|
.await
|
||||||
.context("Failed to send lua output command")?
|
.context("Failed to send lua output command")?
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Lua code `{}` returned error: `{}`", lua_code, err);
|
error!("Function `{}` returned error: `{}`", command, err);
|
||||||
event_call_tx
|
event_call_tx
|
||||||
.send(Event::CommandEvent(
|
.send(Event::CommandEvent(
|
||||||
Command::RaiseError(err.to_string()),
|
Command::RaiseError(err.to_string()),
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
// Use `cargo expand app::command_interface` for an overview of the file contents
|
// 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 lua_command_manager;
|
||||||
|
|
||||||
use language_macros::ci_command_enum;
|
use language_macros::ci_command_enum;
|
||||||
|
|
||||||
// TODO(@Soispha): Should these paths be moved to the proc macro?
|
// TODO(@Soispha): Should these paths be moved to the proc macro?
|
||||||
// As they are not static, it could be easier for other people,
|
// As they are not static, it could be easier for other people,
|
||||||
// if they stay here.
|
// if they stay here
|
||||||
use crate::app::command_interface::command_transfer_value::CommandTransferValue;
|
use lua_command_manager::CommandTransferValue;
|
||||||
use mlua::IntoLua;
|
use mlua::LuaSerdeExt;
|
||||||
use crate::app::Event;
|
use crate::app::Event;
|
||||||
|
|
||||||
#[ci_command_enum]
|
#[ci_command_enum]
|
||||||
|
@ -26,6 +25,7 @@ struct Commands {
|
||||||
/// Returns a table of greeted users
|
/// Returns a table of greeted users
|
||||||
greet_multiple: fn() -> Table,
|
greet_multiple: fn() -> Table,
|
||||||
// End debug functions
|
// End debug functions
|
||||||
|
|
||||||
/// Closes the application
|
/// Closes the application
|
||||||
exit: fn(),
|
exit: fn(),
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ struct Commands {
|
||||||
set_mode_insert: fn(),
|
set_mode_insert: fn(),
|
||||||
|
|
||||||
/// Send a message to the current room
|
/// Send a message to the current room
|
||||||
/// The send message is interpreted literally.
|
/// The sent message is interpreted literally.
|
||||||
room_message_send: fn(String),
|
room_message_send: fn(String),
|
||||||
|
|
||||||
/// Open the help pages at the first occurrence of
|
/// Open the help pages at the first occurrence of
|
||||||
|
|
|
@ -4,18 +4,14 @@ use anyhow::{Error, Result};
|
||||||
use cli_log::{trace, warn};
|
use cli_log::{trace, warn};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use crate::{
|
use crate::{app::{
|
||||||
app::{
|
|
||||||
command_interface::{
|
command_interface::{
|
||||||
command_transfer_value::{CommandTransferValue, Table},
|
lua_command_manager::{CommandTransferValue, Table},
|
||||||
Command,
|
Command,
|
||||||
},
|
},
|
||||||
events::event_types::EventStatus,
|
events::event_types::EventStatus,
|
||||||
status::State,
|
App, status::State,
|
||||||
App,
|
}, ui::central::InputPosition};
|
||||||
},
|
|
||||||
ui::central::InputPosition,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn handle(
|
pub async fn handle(
|
||||||
app: &mut App<'_>,
|
app: &mut App<'_>,
|
||||||
|
@ -24,8 +20,8 @@ pub async fn handle(
|
||||||
) -> Result<EventStatus> {
|
) -> Result<EventStatus> {
|
||||||
// A command can both return _status output_ (what you would normally print to stderr)
|
// A command can both return _status output_ (what you would normally print to stderr)
|
||||||
// and _main output_ (the output which is normally printed to stdout).
|
// and _main output_ (the output which is normally printed to stdout).
|
||||||
// We simulate these by returning the main output to the Lua function, and printing the
|
// We simulate these by returning the main output to the lua function, and printing the
|
||||||
// status output to a status UI field.
|
// status output to a status ui field.
|
||||||
//
|
//
|
||||||
// Every function should return some status output to show the user, that something is
|
// Every function should return some status output to show the user, that something is
|
||||||
// happening, while only some functions return some value to the main output, as this
|
// happening, while only some functions return some value to the main output, as this
|
||||||
|
@ -72,7 +68,7 @@ pub async fn handle(
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::DisplayOutput(output) => {
|
Command::DisplayOutput(output) => {
|
||||||
// TODO(@Soispha): This is only used to show the Lua command output to the user.
|
// 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
|
// Lua commands already receive the output. This should probably be communicated
|
||||||
// better, should it?
|
// better, should it?
|
||||||
send_status_output!(output);
|
send_status_output!(output);
|
||||||
|
@ -81,7 +77,6 @@ pub async fn handle(
|
||||||
|
|
||||||
Command::CommandLineShow => {
|
Command::CommandLineShow => {
|
||||||
app.ui.cli_enable();
|
app.ui.cli_enable();
|
||||||
app.status.set_state(State::Command);
|
|
||||||
send_status_output!("CLI online");
|
send_status_output!("CLI online");
|
||||||
EventStatus::Ok
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
|
@ -119,7 +114,7 @@ pub async fn handle(
|
||||||
room.send(msg.clone()).await?;
|
room.send(msg.clone()).await?;
|
||||||
send_status_output!("Sent message: `{}`", msg);
|
send_status_output!("Sent message: `{}`", msg);
|
||||||
} else {
|
} else {
|
||||||
// TODO(@Soispha): Should this raise a Lua error? It could be very confusing,
|
// TODO(@Soispha): Should this raise a lua error? It could be very confusing,
|
||||||
// when a user doesn't read the log.
|
// when a user doesn't read the log.
|
||||||
warn!("Can't send message: `{}`, as there is no open room!", &msg);
|
warn!("Can't send message: `{}`, as there is no open room!", &msg);
|
||||||
}
|
}
|
||||||
|
@ -131,25 +126,13 @@ pub async fn handle(
|
||||||
}
|
}
|
||||||
Command::Print(output) => {
|
Command::Print(output) => {
|
||||||
// FIXME(@Soispha): This only works with strings, which is a clear downside to the
|
// FIXME(@Soispha): This only works with strings, which is a clear downside to the
|
||||||
// original print function. Find a way to just use the original one.
|
// original print function. Find a way to just use the original one
|
||||||
send_main_output!("{}", output);
|
send_main_output!("{}", output);
|
||||||
EventStatus::Ok
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
Command::GreetMultiple => {
|
Command::GreetMultiple => {
|
||||||
let mut table: Table = HashMap::new();
|
let mut table: Table = HashMap::new();
|
||||||
table.insert("UserId".to_owned(), CommandTransferValue::Integer(2));
|
table.insert("UserName1".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);
|
send_main_output!(table);
|
||||||
EventStatus::Ok
|
EventStatus::Ok
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,56 +10,13 @@ use crate::{
|
||||||
ui::central,
|
ui::central,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn handle_command(
|
pub async fn handle_normal(
|
||||||
app: &mut App<'_>,
|
app: &mut App<'_>,
|
||||||
input_event: &CrosstermEvent,
|
input_event: &CrosstermEvent,
|
||||||
) -> Result<EventStatus> {
|
) -> Result<EventStatus> {
|
||||||
if let Some(cli) = &app.ui.cli {
|
|
||||||
match input_event {
|
match input_event {
|
||||||
CrosstermEvent::Key(KeyEvent {
|
CrosstermEvent::Key(KeyEvent {
|
||||||
code: KeyCode::Esc, ..
|
code: KeyCode::Esc, ..
|
||||||
}) => {
|
|
||||||
app.tx
|
|
||||||
.send(Event::CommandEvent(Command::SetModeNormal, None))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
CrosstermEvent::Key(KeyEvent {
|
|
||||||
code: KeyCode::Enter,
|
|
||||||
..
|
|
||||||
}) => {
|
|
||||||
let ci_event = cli
|
|
||||||
.lines()
|
|
||||||
.get(0)
|
|
||||||
.expect(
|
|
||||||
"One line always exists,
|
|
||||||
and others can't exists
|
|
||||||
because we collect on
|
|
||||||
enter",
|
|
||||||
)
|
|
||||||
.to_owned();
|
|
||||||
app.tx
|
|
||||||
.send(Event::LuaCommand(ci_event))
|
|
||||||
.await
|
|
||||||
.context("Failed to send lua command to internal event stream")?;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
app.ui
|
|
||||||
.cli
|
|
||||||
.as_mut()
|
|
||||||
.expect("This is already checked")
|
|
||||||
.input(tui_textarea::Input::from(input_event.to_owned()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!("The cli should not be active while no cli is defined");
|
|
||||||
}
|
|
||||||
Ok(EventStatus::Ok)
|
|
||||||
}
|
|
||||||
pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> Result<EventStatus> {
|
|
||||||
match input_event {
|
|
||||||
CrosstermEvent::Key(KeyEvent {
|
|
||||||
code: KeyCode::Char('q'),
|
|
||||||
..
|
|
||||||
}) => {
|
}) => {
|
||||||
app.tx
|
app.tx
|
||||||
.send(Event::CommandEvent(Command::Exit, None))
|
.send(Event::CommandEvent(Command::Exit, None))
|
||||||
|
@ -180,7 +137,38 @@ pub async fn handle_normal(app: &mut App<'_>, input_event: &CrosstermEvent) -> R
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
central::InputPosition::CLI => {
|
||||||
|
if let Some(cli) = &app.ui.cli {
|
||||||
|
match input {
|
||||||
|
CrosstermEvent::Key(KeyEvent {
|
||||||
|
code: KeyCode::Enter,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let ci_event = cli
|
||||||
|
.lines()
|
||||||
|
.get(0)
|
||||||
|
.expect(
|
||||||
|
"One line always exists,
|
||||||
|
and others can't exists
|
||||||
|
because we collect on
|
||||||
|
enter",
|
||||||
|
)
|
||||||
|
.to_owned();
|
||||||
|
app.tx
|
||||||
|
.send(Event::LuaCommand(ci_event))
|
||||||
|
.await
|
||||||
|
.context("Failed to send lua command to internal event stream")?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
app.ui
|
||||||
|
.cli
|
||||||
|
.as_mut()
|
||||||
|
.expect("This is already checked")
|
||||||
|
.input(tui_textarea::Input::from(input.to_owned()));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crossterm::event::Event as CrosstermEvent;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
command_interface::{command_transfer_value::CommandTransferValue, Command},
|
command_interface::{lua_command_manager::CommandTransferValue, Command},
|
||||||
status::State,
|
status::State,
|
||||||
App,
|
App,
|
||||||
};
|
};
|
||||||
|
@ -45,9 +45,6 @@ impl Event {
|
||||||
State::Insert => main::handle_insert(app, &event).await.with_context(|| {
|
State::Insert => main::handle_insert(app, &event).await.with_context(|| {
|
||||||
format!("Failed to handle input (insert) event: `{:#?}`", event)
|
format!("Failed to handle input (insert) event: `{:#?}`", event)
|
||||||
}),
|
}),
|
||||||
State::Command => main::handle_command(app, &event).await.with_context(|| {
|
|
||||||
format!("Failed to handle input (command) event: `{:#?}`", event)
|
|
||||||
}),
|
|
||||||
State::Setup => setup::handle(app, &event).await.with_context(|| {
|
State::Setup => setup::handle(app, &event).await.with_context(|| {
|
||||||
format!("Failed to handle input (setup) event: `{:#?}`", event)
|
format!("Failed to handle input (setup) event: `{:#?}`", event)
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -15,7 +15,6 @@ use matrix_sdk::{
|
||||||
pub enum State {
|
pub enum State {
|
||||||
Normal,
|
Normal,
|
||||||
Insert,
|
Insert,
|
||||||
Command,
|
|
||||||
/// Temporary workaround until command based login is working
|
/// Temporary workaround until command based login is working
|
||||||
Setup,
|
Setup,
|
||||||
}
|
}
|
||||||
|
@ -58,7 +57,6 @@ impl fmt::Display for State {
|
||||||
match self {
|
match self {
|
||||||
Self::Normal => write!(f, "Normal"),
|
Self::Normal => write!(f, "Normal"),
|
||||||
Self::Insert => write!(f, "Insert"),
|
Self::Insert => write!(f, "Insert"),
|
||||||
Self::Command => write!(f, "Command"),
|
|
||||||
Self::Setup => write!(f, "Setup (!! workaround !!)"),
|
Self::Setup => write!(f, "Setup (!! workaround !!)"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue