doppio
A typed compiler pipeline for Ledger plain-text accounting -- built to be embedded.
This is the library crate. For the dop command-line interface,
install doppio-cli instead;
it depends on this crate and ships the binary.
What it does
doppio parses .ledger (and .hledger, .journal) source files
through four sequential stages:
source text -> parse -> resolution -> elaboration -> .dop
It enforces double-entry balance and balance-assertion semantics at
elaboration time, then exposes the result as a strongly-typed
elaboration::Journal (a prost-generated Protocol Buffers message)
ready for queries, reporting, or serialisation to the .dop binary
format.
Use cases
- Bank import pipelines. Build
resolution::Transactionvalues programmatically from imported records (Mercury, Plaid, SimpleFIN, etc.) and run them througheval_transactionto validate balance before writing back to source. - Reporting tools. Compile a journal once with
compile, then serialise to a.dopfile withwrite_dopand load it in milliseconds withread_dopfor repeated queries. - Cross-language consumers. The
.dopbinary format is defined byproto/doppio.protoin the source repo. Any language with a Protocol Buffers binding can read.dopfiles directly without re-parsing the source.
Quick example
use ;
use NaiveDate;
use Decimal;
let txn = new
.with_posting
.with_posting;
// Validate and elaborate -- the null posting on Assets:Checking is inferred as -$50.
let journal_txn = eval_transaction?;
Public API surface
The intended construction layer is resolution::Transaction /
resolution::Posting (builder API). The intended read layer is
elaboration::* (the prost-generated proto types) plus their
inherent ergonomic methods (Decimal::to_decimal(),
Amount::iter(), Posting::amount_in(commodity), etc.).
The Frontend trait pluralises the parser dispatch -- LedgerFrontend
and HledgerFrontend are shipped; new formats (e.g. Beancount) can
implement the trait without modifying the library.
Using doppio in tests
When writing tests that consume elaboration::Journal directly, use the
doppio::testing module to build fixtures without wrestling with proto3 quirks
(Option<Amount>, epoch-days dates, state: i32 enum casts). Enable it via
the testing feature in your dev-dependencies:
[]
= { = "...", = ["testing"] }
Then construct journals with the fluent builder:
use ;
use NaiveDate;
let j = journal
.with_txn
.build;
assert_eq!;
assert_eq!;
The testing feature is intentionally excluded from default and production
builds so it does not bloat binary consumers.
Companion crates
doppio-cli-- thedopcommand-line interface (compile, balance, register, print, stats, accounts, commodities, with--exchange,--cleared,--depth,--format text|json|csv, etc.).doppio-categorize-- counter-account suggestions for new transactions, given an existing journal as the training corpus.
Documentation
- Repository -- README, CLI reference, supported feature matrix.
docs/-- proto evolution policy, exchange-rate semantics, supported features.proto/doppio.proto-- the canonical wire format with multilingual reassembly recipes.
License
ISC. See LICENSE.