Lines
92.59 %
Functions
81.25 %
Branches
100 %
//! Cross-frontend form descriptors and shared validators.
pub use descriptors::{
ACCOUNT_CREATE, COMMODITY_CONVERT, COMMODITY_CREATE, FieldSpec, FormDescriptor, SelectSource,
TRANSACTION_CREATE, WidgetKind,
};
pub use payload::{build_transaction_logical_payload, build_transaction_update_payload};
pub use reverse::{EditableRow, EditableTransaction, parse_editable_transaction};
pub use validators::{
LogicalSplitInput, amount_token, now_template, parse_amount, row_complete, step_date,
validate_date,
mod descriptors;
mod payload;
pub mod reverse;
#[cfg(test)]
pub mod tests;
mod validators;
/// Serialise a [`FormDescriptor`] to a nomiscript-readable s-expression.
pub fn to_sexpr(form: &FormDescriptor) -> String {
let fields: Vec<String> = form.fields.iter().map(field_to_sexpr).collect();
format!("(:form \"{}\" :fields ({}))", form.name, fields.join(" "))
}
fn widget_to_sexpr(w: WidgetKind) -> &'static str {
match w {
WidgetKind::Text => "text",
WidgetKind::Amount => "amount",
WidgetKind::Date => "date",
WidgetKind::Splits => "splits",
WidgetKind::Note => "note",
WidgetKind::Select {
source: SelectSource::Accounts,
} => "(select :source accounts)",
source: SelectSource::Commodities,
} => "(select :source commodities)",
fn field_to_sexpr(f: &FieldSpec) -> String {
format!(
"(:key \"{}\" :label \"{}\" :widget {} :required {})",
f.key,
f.label,
widget_to_sexpr(f.widget),
if f.required { "t" } else { "nil" }
)
/// Parse a list-accounts wire reply into (id, label) account option pairs.
///
/// Returns `[]` on any parse failure — never panics.
pub fn parse_account_options(wire: &str) -> Vec<(String, String)> {
use crate::eval::plist_field;
use crate::render::{WireValue, parse_wire, reparse_list};
use nomiscript::{Value, list_to_vec};
let Ok(WireValue::Value(Value::String(ref s))) = parse_wire(wire) else {
return vec![];
let Ok(list) = reparse_list(s) else {
let Some(elements) = list_to_vec(&list) else {
elements
.iter()
.filter_map(|e| {
let id = plist_field(e, ":id")?;
let name = plist_field(e, ":name")?;
Some((id, name))
})
.collect()
/// Parse a list-commodities wire reply into (id, label) commodity option pairs.
/// The label is `"{symbol}"` (`:name` appended when non-empty: `"{symbol} – {name}"`).
pub fn parse_commodity_options(wire: &str) -> Vec<(String, String)> {
let symbol = plist_field(e, ":symbol")?;
let label = match plist_field(e, ":name").filter(|n| !n.is_empty()) {
Some(name) => format!("{symbol} \u{2013} {name}"),
None => symbol,
Some((id, label))