Stub implementation of Lua plugins

This commit is contained in:
RunasSudo 2025-06-01 02:24:13 +10:00
parent 97644042a3
commit 25924d2a0a
Signed by: RunasSudo
GPG Key ID: 7234E476BF21C61A
13 changed files with 646 additions and 43 deletions

102
libdrcr/Cargo.lock generated
View File

@ -109,6 +109,16 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bstr"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.17.0"
@ -293,6 +303,16 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7"
dependencies = [
"serde",
"typeid",
]
[[package]]
name = "etcetera"
version = "0.8.0"
@ -680,12 +700,23 @@ dependencies = [
"downcast-rs",
"dyn-clone",
"indexmap",
"mlua",
"serde",
"serde_json",
"sqlx",
"tokio",
]
[[package]]
name = "libloading"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
]
[[package]]
name = "libm"
version = "0.2.15"
@ -725,6 +756,15 @@ version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "luau0-src"
version = "0.12.3+luau663"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ae337c644bbf86a8d8e9ce3ee023311833d41741baf5e51acc31b37843aba1"
dependencies = [
"cc",
]
[[package]]
name = "md-5"
version = "0.10.6"
@ -761,6 +801,37 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "mlua"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1f5f8fbebc7db5f671671134b9321c4b9aa9adeafccfd9a8c020ae45c6a35d0"
dependencies = [
"bstr",
"either",
"erased-serde",
"libloading",
"mlua-sys",
"num-traits",
"parking_lot",
"rustc-hash",
"rustversion",
"serde",
"serde-value",
]
[[package]]
name = "mlua-sys"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93"
dependencies = [
"cc",
"cfg-if",
"luau0-src",
"pkg-config",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
@ -823,6 +894,15 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "ordered-float"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
dependencies = [
"num-traits",
]
[[package]]
name = "parking"
version = "2.2.1"
@ -1007,6 +1087,12 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustversion"
version = "1.0.20"
@ -1034,6 +1120,16 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-value"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
dependencies = [
"ordered-float",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
@ -1513,6 +1609,12 @@ dependencies = [
"once_cell",
]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.18.0"

View File

@ -9,6 +9,7 @@ chrono = "0.4.41"
downcast-rs = "2.0.1"
dyn-clone = "1.0.19"
indexmap = "2.9.0"
mlua = { version = "0.10", features = ["luau", "serialize"] }
serde = "1.0.219"
serde_json = "1.0.140"
sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite" ] }

View File

@ -0,0 +1,43 @@
--!strict
-- DrCr: Web-based double-entry bookkeeping framework
-- Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
local libdrcr = require("../libdrcr")
function requires(step_name, product_kinds, args)
return {}
end
function execute(step_name, product_kinds, args)
print("Stub: Lua plugin execute")
return {}
end
local plugin: libdrcr.Plugin = {
spec = {
name = "austax",
reporting_steps = {
{
name = "CalculateIncomeTax",
product_kinds = {"DynamicReport", "Transactions"}
}
},
},
requires = requires,
execute = execute,
}
return plugin

View File

@ -0,0 +1,57 @@
--!strict
-- DrCr: Web-based double-entry bookkeeping framework
-- Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU Affero General Public License for more details.
--
-- You should have received a copy of the GNU Affero General Public License
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
------------------------
-- Plugin specific types
-- Represents a libdrcr plugin specification and implementation
export type Plugin = {
spec: PluginSpec,
requires: (string, {ReportingProductKind}, ReportingStepArgs) -> {ReportingProductId},
execute: (string, {ReportingProductKind}, ReportingStepArgs) -> {ReportingProduct},
}
-- Represents a libdrcr plugin specification
export type PluginSpec = {
name: string,
reporting_steps: {ReportingStepSpec},
}
-- Specifies a ReportingStep provided by the plugin
export type ReportingStepSpec = {
name: string,
product_kinds: {ReportingProductKind}
}
-------------------------
-- libdrcr internal types
export type ReportingProduct = any
export type ReportingProductId = {
name: string,
kind: ReportingProductKind,
args: ReportingStepArgs,
}
export type ReportingProductKind = string
-- TODO: Currently only VoidArgs is supported
export type ReportingStepArgs = string
local libdrcr = {}
return libdrcr

View File

@ -1,7 +1,8 @@
pub mod account_config;
pub mod austax;
//pub mod austax;
pub mod db;
pub mod model;
pub mod plugin;
pub mod reporting;
pub mod serde;
pub mod util;

View File

@ -38,12 +38,14 @@ async fn main() {
// Initialise ReportingContext
let mut context = ReportingContext::new(
db_connection,
"plugins".to_string(),
vec!["austax.austax".to_string()],
NaiveDate::from_ymd_opt(2025, 6, 30).unwrap(),
"$".to_string(),
);
libdrcr::plugin::register_lookup_fns(&mut context);
libdrcr::reporting::steps::register_lookup_fns(&mut context);
libdrcr::reporting::builders::register_dynamic_builders(&mut context);
libdrcr::austax::register_lookup_fns(&mut context);
let context = Arc::new(context);
@ -109,16 +111,16 @@ async fn main() {
.await
.unwrap();
let result = products
.get_or_err(&ReportingProductId {
name: "CalculateIncomeTax".to_string(),
kind: ReportingProductKind::DynamicReport,
args: ReportingStepArgs::VoidArgs,
})
.unwrap();
// let result = products
// .get_or_err(&ReportingProductId {
// name: "CalculateIncomeTax".to_string(),
// kind: ReportingProductKind::DynamicReport,
// args: ReportingStepArgs::VoidArgs,
// })
// .unwrap();
println!("Tax summary:");
println!("{:?}", result);
// println!("Tax summary:");
// println!("{:?}", result);
let result = products
.get_or_err(&ReportingProductId {

216
libdrcr/src/plugin.rs Normal file
View File

@ -0,0 +1,216 @@
/*
DrCr: Web-based double-entry bookkeeping framework
Copyright (C) 2022-2025 Lee Yingtong Li (RunasSudo)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::fmt::Display;
use async_trait::async_trait;
use mlua::{Function, Lua, LuaSerdeExt, Table, Value};
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
use crate::reporting::calculator::ReportingGraphDependencies;
use crate::reporting::executor::ReportingExecutionError;
use crate::reporting::types::{
ReportingContext, ReportingProductId, ReportingProductKind, ReportingProducts, ReportingStep,
ReportingStepArgs, ReportingStepId,
};
fn load_plugin(plugin_dir: &str, plugin_name: &str) -> (Lua, Plugin) {
let lua = Lua::new();
// Init Lua environment
let package = lua.globals().get::<Table>("package").unwrap();
package
.set("path", format!("{}/?.luau", plugin_dir))
.unwrap();
// Require and call the plugin
let require = lua.load("require").eval::<Function>().unwrap();
let plugin_table = require.call::<Table>(plugin_name).expect("Lua error");
// Convert plugin to Rust struct
let plugin = Plugin {
spec: lua
.from_value(
plugin_table
.get("spec")
.expect("Error parsing Plugin definition"),
)
.unwrap(),
requires: plugin_table
.get("requires")
.expect("Error parsing Plugin definition"),
execute: plugin_table
.get("execute")
.expect("Error parsing Plugin definition"),
};
(lua, plugin)
}
/// Call [ReportingContext::register_lookup_fn] for all steps provided by this module
pub fn register_lookup_fns(context: &mut ReportingContext) {
for plugin_path in context.plugin_names.clone().iter() {
let (_, plugin) = load_plugin(&context.plugin_dir, plugin_path);
for reporting_step in plugin.spec.reporting_steps.iter() {
context.register_lookup_fn(
reporting_step.name.clone(),
reporting_step.product_kinds.clone(),
PluginReportingStep::takes_args,
PluginReportingStep::from_args,
);
}
context
.plugin_specs
.insert(plugin_path.clone(), plugin.spec);
}
}
#[derive(Debug)]
pub struct Plugin {
spec: PluginSpec,
requires: Function,
execute: Function,
}
/// Represents a libdrcr plugin
#[derive(Debug, Deserialize, Serialize)]
pub struct PluginSpec {
name: String,
reporting_steps: Vec<ReportingStepSpec>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ReportingStepSpec {
name: String,
product_kinds: Vec<ReportingProductKind>,
}
/// Generic reporting step which is implemented by a plugin
#[derive(Debug)]
pub struct PluginReportingStep {
pub plugin_path: String,
pub step_name: String,
pub product_kinds: Vec<ReportingProductKind>,
pub args: ReportingStepArgs, // Currently only VoidArgs is supported
}
impl PluginReportingStep {
fn takes_args(_name: &str, args: &ReportingStepArgs, _context: &ReportingContext) -> bool {
*args == ReportingStepArgs::VoidArgs
}
fn from_args(
name: &str,
args: ReportingStepArgs,
context: &ReportingContext,
) -> Box<dyn ReportingStep> {
// Look up plugin
for (plugin_path, plugin_spec) in context.plugin_specs.iter() {
if let Some(reporting_step_spec) =
plugin_spec.reporting_steps.iter().find(|s| s.name == name)
{
return Box::new(Self {
plugin_path: plugin_path.to_string(),
step_name: name.to_string(),
product_kinds: reporting_step_spec.product_kinds.clone(),
args,
});
}
}
panic!("No plugin provides step {}", name);
}
}
impl Display for PluginReportingStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{} {{PluginReportingStep}}", self.id()))
}
}
#[async_trait]
impl ReportingStep for PluginReportingStep {
fn id(&self) -> ReportingStepId {
ReportingStepId {
name: self.step_name.clone(),
product_kinds: self.product_kinds.clone(),
args: self.args.clone(),
}
}
fn requires(&self, context: &ReportingContext) -> Vec<ReportingProductId> {
// Call to plugin
let (lua, plugin) = load_plugin(&context.plugin_dir, &self.plugin_path);
let result_table = plugin
.requires
.call::<Table>((
lua.to_value(&self.step_name).unwrap(),
lua.to_value(&self.product_kinds).unwrap(),
lua.to_value(&self.args).unwrap(),
))
.expect("Lua error");
// Convert result to Rust
let result = result_table
.sequence_values()
.map(|s| s.expect("Lua error"))
.map(|v| lua.from_value(v).expect("Deserialise error"))
.collect::<Vec<ReportingProductId>>();
result
}
async fn execute(
&self,
context: &ReportingContext,
_steps: &Vec<Box<dyn ReportingStep>>,
_dependencies: &ReportingGraphDependencies,
_products: &RwLock<ReportingProducts>,
) -> Result<ReportingProducts, ReportingExecutionError> {
// Call to plugin
let (lua, plugin) = load_plugin(&context.plugin_dir, &self.plugin_path);
let _result_table = plugin
.execute
.call::<Table>((
lua.to_value(&self.step_name).unwrap(),
lua.to_value(&self.product_kinds).unwrap(),
lua.to_value(&self.args).unwrap(),
))
.expect("Lua error");
eprintln!("Stub: Lua plugin execute");
Ok(ReportingProducts::new())
}
}
/// Format the [Table] as a string
fn _dbg_table(table: Table) -> String {
format!(
"{{{}}}",
table
.pairs::<Value, Value>()
.map(|p| p.expect("Lua error"))
.map(|(k, v)| format!("{:?}: {:?}", k, v))
.collect::<Vec<_>>()
.join(", ")
)
}

View File

@ -30,6 +30,7 @@ use tokio::sync::RwLock;
use crate::db::DbConnection;
use crate::model::transaction::TransactionWithPostings;
use crate::plugin::PluginSpec;
use crate::QuantityInt;
use super::calculator::ReportingGraphDependencies;
@ -42,6 +43,8 @@ use super::executor::ReportingExecutionError;
pub struct ReportingContext {
// Configuration
pub db_connection: DbConnection,
pub plugin_dir: String,
pub plugin_names: Vec<String>,
pub eofy_date: NaiveDate,
pub reporting_commodity: String,
@ -51,21 +54,27 @@ pub struct ReportingContext {
(ReportingStepTakesArgsFn, ReportingStepFromArgsFn),
>,
pub(crate) step_dynamic_builders: Vec<ReportingStepDynamicBuilder>,
pub(crate) plugin_specs: HashMap<String, PluginSpec>,
}
impl ReportingContext {
/// Initialise a new [ReportingContext]
pub fn new(
db_connection: DbConnection,
plugin_dir: String,
plugin_names: Vec<String>,
eofy_date: NaiveDate,
reporting_commodity: String,
) -> Self {
Self {
db_connection,
plugin_dir,
plugin_names,
eofy_date,
reporting_commodity,
step_lookup_fn: HashMap::new(),
step_dynamic_builders: Vec::new(),
plugin_specs: HashMap::new(),
}
}
@ -139,7 +148,7 @@ pub struct ReportingStepDynamicBuilder {
// REPORTING PRODUCTS
/// Identifies a [ReportingProduct]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct ReportingProductId {
pub name: String,
pub kind: ReportingProductKind,
@ -155,7 +164,7 @@ impl Display for ReportingProductId {
/// Identifies a type of [Box]ed [ReportingProduct]
///
/// See [Box::downcast].
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum ReportingProductKind {
/// The [Box]ed [ReportingProduct] is a [Transactions]
Transactions,
@ -345,7 +354,7 @@ downcast_rs::impl_downcast!(ReportingStep);
// REPORTING STEP ARGUMENTS
/// Represents arguments to a [ReportingStep]
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub enum ReportingStepArgs {
// This is an enum not a trait, to simply conversion to and from Lua
/// [ReportingStepArgs] implementation which takes no arguments
@ -378,8 +387,9 @@ impl Display for ReportingStepArgs {
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct DateArgs {
#[serde(with = "crate::serde::naivedate_to_js")]
pub date: NaiveDate,
}
@ -399,9 +409,11 @@ impl Into<DateArgs> for ReportingStepArgs {
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct DateStartDateEndArgs {
#[serde(with = "crate::serde::naivedate_to_js")]
pub date_start: NaiveDate,
#[serde(with = "crate::serde::naivedate_to_js")]
pub date_end: NaiveDate,
}
@ -421,7 +433,7 @@ impl Into<DateStartDateEndArgs> for ReportingStepArgs {
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct MultipleDateArgs {
pub dates: Vec<DateArgs>,
}
@ -449,7 +461,7 @@ impl Into<MultipleDateArgs> for ReportingStepArgs {
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct MultipleDateStartDateEndArgs {
pub dates: Vec<DateStartDateEndArgs>,
}

View File

@ -16,6 +16,51 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// Serialises [chrono::NaiveDate] in database format
///
/// Use as `#[serde(with = "crate::serde::naivedate_to_js")]`, etc.
pub mod naivedate_to_js {
use std::fmt;
use chrono::NaiveDate;
use serde::{
de::{self, Unexpected, Visitor},
Deserializer, Serializer,
};
pub(crate) fn serialize<S: Serializer>(
dt: &NaiveDate,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&dt.format("%Y-%m-%d").to_string())
}
struct DateVisitor;
impl<'de> Visitor<'de> for DateVisitor {
type Value = NaiveDate;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a date string")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match NaiveDate::parse_from_str(s, "%Y-%m-%d") {
Ok(dt) => Ok(dt),
Err(_) => Err(de::Error::invalid_value(Unexpected::Str(s), &self)),
}
}
}
pub(crate) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<NaiveDate, D::Error> {
deserializer.deserialize_str(DateVisitor)
}
}
/// Serialises [chrono::NaiveDateTime] in database format
///
/// Use as `#[serde(with = "crate::serde::naivedatetime_to_js")]`, etc.
@ -40,7 +85,7 @@ pub mod naivedatetime_to_js {
type Value = NaiveDateTime;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a date string")
write!(formatter, "a datetime string")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>

110
src-tauri/Cargo.lock generated
View File

@ -370,6 +370,16 @@ dependencies = [
"alloc-stdlib",
]
[[package]]
name = "bstr"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
@ -878,7 +888,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
"libloading",
"libloading 0.8.8",
]
[[package]]
@ -977,18 +987,6 @@ version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
[[package]]
name = "dyn-eq"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c2d035d21af5cde1a6f5c7b444a5bf963520a9f142e5d06931178433d7d5388"
[[package]]
name = "dyn-hash"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15401da73a9ed8c80e3b2d4dc05fe10e7b72d7243b9f614e516a44fa99986e88"
[[package]]
name = "either"
version = "1.13.0"
@ -2165,7 +2163,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
dependencies = [
"gtk-sys",
"libloading",
"libloading 0.7.4",
"once_cell",
]
@ -2183,9 +2181,8 @@ dependencies = [
"chrono",
"downcast-rs 2.0.1",
"dyn-clone",
"dyn-eq",
"dyn-hash",
"indexmap 2.9.0",
"mlua",
"serde",
"serde_json",
"sqlx",
@ -2202,6 +2199,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "libloading"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
]
[[package]]
name = "libm"
version = "0.2.11"
@ -2257,6 +2264,15 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "luau0-src"
version = "0.12.3+luau663"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ae337c644bbf86a8d8e9ce3ee023311833d41741baf5e51acc31b37843aba1"
dependencies = [
"cc",
]
[[package]]
name = "mac"
version = "0.1.1"
@ -2351,6 +2367,37 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "mlua"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1f5f8fbebc7db5f671671134b9321c4b9aa9adeafccfd9a8c020ae45c6a35d0"
dependencies = [
"bstr",
"either",
"erased-serde",
"libloading 0.8.8",
"mlua-sys",
"num-traits",
"parking_lot",
"rustc-hash",
"rustversion",
"serde",
"serde-value",
]
[[package]]
name = "mlua-sys"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93"
dependencies = [
"cc",
"cfg-if",
"luau0-src",
"pkg-config",
]
[[package]]
name = "muda"
version = "0.15.3"
@ -2769,6 +2816,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "ordered-float"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
dependencies = [
"num-traits",
]
[[package]]
name = "ordered-stream"
version = "0.2.0"
@ -3424,6 +3480,12 @@ version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustc_version"
version = "0.4.1"
@ -3446,6 +3508,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustversion"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]]
name = "ryu"
version = "1.0.18"
@ -3549,6 +3617,16 @@ dependencies = [
"typeid",
]
[[package]]
name = "serde-value"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
dependencies = [
"ordered-float",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.219"

View File

@ -18,15 +18,19 @@
use libdrcr::reporting::dynamic_report::DynamicReport;
use libdrcr::reporting::types::{ReportingProductId, ReportingProductKind, ReportingStepArgs};
use tauri::State;
use tauri::{AppHandle, State};
use tokio::sync::Mutex;
use crate::libdrcr_bridge::get_report;
use crate::AppState;
#[tauri::command]
pub(crate) async fn get_tax_summary(state: State<'_, Mutex<AppState>>) -> Result<String, ()> {
pub(crate) async fn get_tax_summary(
app: AppHandle,
state: State<'_, Mutex<AppState>>,
) -> Result<String, ()> {
Ok(get_report(
app,
state,
&ReportingProductId {
name: "CalculateIncomeTax".to_string(),

View File

@ -30,18 +30,25 @@ use libdrcr::reporting::types::{
ReportingStepArgs, Transactions,
};
use serde::{Deserialize, Serialize};
use tauri::State;
use tauri::path::BaseDirectory;
use tauri::{AppHandle, Manager, State};
use tokio::sync::Mutex;
use crate::AppState;
fn prepare_reporting_context(context: &mut ReportingContext) {
libdrcr::austax::register_lookup_fns(context);
libdrcr::reporting::steps::register_lookup_fns(context);
libdrcr::reporting::builders::register_dynamic_builders(context);
libdrcr::plugin::register_lookup_fns(context);
}
fn get_plugins() -> Vec<String> {
// FIXME: Dynamically get this
vec!["austax.austax".to_string()]
}
pub(crate) async fn get_report(
app: AppHandle,
state: State<'_, Mutex<AppState>>,
target: &ReportingProductId,
) -> Box<dyn ReportingProduct> {
@ -54,7 +61,18 @@ pub(crate) async fn get_report(
// Initialise ReportingContext
let eofy_date = db_connection.metadata().eofy_date;
let mut context = ReportingContext::new(db_connection, eofy_date, "$".to_string());
let mut context = ReportingContext::new(
db_connection,
app.path()
.resolve("plugins", BaseDirectory::Resource)
.unwrap()
.to_str()
.unwrap()
.to_string(),
get_plugins(),
eofy_date,
"$".to_string(),
);
prepare_reporting_context(&mut context);
// Get dynamic report
@ -75,9 +93,11 @@ pub(crate) async fn get_report(
#[tauri::command]
pub(crate) async fn get_all_transactions_except_earnings_to_equity(
app: AppHandle,
state: State<'_, Mutex<AppState>>,
) -> Result<String, ()> {
let transactions = get_report(
app,
state,
&ReportingProductId {
name: "AllTransactionsExceptEarningsToEquity".to_string(),
@ -97,10 +117,12 @@ pub(crate) async fn get_all_transactions_except_earnings_to_equity(
#[tauri::command]
pub(crate) async fn get_all_transactions_except_earnings_to_equity_for_account(
app: AppHandle,
state: State<'_, Mutex<AppState>>,
account: String,
) -> Result<String, ()> {
let transactions = get_report(
app,
state,
&ReportingProductId {
name: "AllTransactionsExceptEarningsToEquity".to_string(),
@ -126,6 +148,7 @@ pub(crate) async fn get_all_transactions_except_earnings_to_equity_for_account(
#[tauri::command]
pub(crate) async fn get_balance_sheet(
app: AppHandle,
state: State<'_, Mutex<AppState>>,
dates: Vec<String>,
) -> Result<String, ()> {
@ -137,6 +160,7 @@ pub(crate) async fn get_balance_sheet(
}
Ok(get_report(
app,
state,
&ReportingProductId {
name: "BalanceSheet".to_string(),
@ -154,6 +178,7 @@ pub(crate) async fn get_balance_sheet(
#[tauri::command]
pub(crate) async fn get_income_statement(
app: AppHandle,
state: State<'_, Mutex<AppState>>,
dates: Vec<(String, String)>,
) -> Result<String, ()> {
@ -166,6 +191,7 @@ pub(crate) async fn get_income_statement(
}
Ok(get_report(
app,
state,
&ReportingProductId {
name: "IncomeStatement".to_string(),
@ -183,12 +209,14 @@ pub(crate) async fn get_income_statement(
#[tauri::command]
pub(crate) async fn get_trial_balance(
app: AppHandle,
state: State<'_, Mutex<AppState>>,
date: String,
) -> Result<String, ()> {
let date = NaiveDate::parse_from_str(&date, "%Y-%m-%d").expect("Invalid date");
Ok(get_report(
app,
state,
&ReportingProductId {
name: "TrialBalance".to_string(),
@ -211,6 +239,7 @@ struct ValidatedBalanceAssertion {
#[tauri::command]
pub(crate) async fn get_validated_balance_assertions(
app: AppHandle,
state: State<'_, Mutex<AppState>>,
) -> Result<String, ()> {
let state = state.lock().await;
@ -233,8 +262,18 @@ pub(crate) async fn get_validated_balance_assertions(
// Initialise ReportingContext
let eofy_date = db_connection.metadata().eofy_date;
let mut context =
ReportingContext::new(db_connection, get_plugins(), eofy_date, "$".to_string());
let mut context = ReportingContext::new(
db_connection,
app.path()
.resolve("plugins", BaseDirectory::Resource)
.unwrap()
.to_str()
.unwrap()
.to_string(),
get_plugins(),
eofy_date,
"$".to_string(),
);
prepare_reporting_context(&mut context);
// Get report targets

View File

@ -26,6 +26,9 @@
"targets": "all",
"icon": [
"icons/icon.png"
]
],
"resources": {
"../libdrcr/plugins/": "plugins/"
}
}
}