Finance as Code is a personal finance automation project that moves transactions from different inputs into budgeting destinations in a repeatable, code-driven way.
Goal: define your data flow once, then run it reliably to keep your tools in sync.
Sources are where transactions come from (for example bank CSV exports or online APIs). Sinks are where normalized transactions are written (for example local apps or external services).
Affiliate disclosure: links to Lunch Flow and Lunch Money may be affiliate links. If you use them, I may earn a commission at no extra cost to you.
| Source | Feature | Supported |
|---|---|---|
| mBank CSV | source_mbank |
Yes |
| Source | Feature | Supported |
|---|---|---|
| Lunch Flow | source_lunchflow |
Yes |
| Sink | Feature | Supported |
|---|---|---|
| Treeline | sink_treeline |
Yes |
| Sink | Feature | Supported |
|---|---|---|
| Lunch Money | sink_lunchmoney |
Yes |
Notes:
- For Lunch Flow, use the downloader as a setup (run before sources) to download files, then use
LocalDirectorySourceto read them. allenables all source/sink features.all_with_dbenables all source/sink features andbundled_dbfor Treeline.
This library is not published on crates.io yet. Add it from GitHub with the feature set you need:
[dependencies]
finance_as_code_budget = { git = "https://github.com/andrzejressel/finance-as-code.git", features = ["all_with_db"] }all_with_db enables all sources/sinks and bundles DuckDB for Treeline. If you have a weaker PC you can try using all instead which uses prebuilt duckdb:
[dependencies]
finance_as_code_budget = { git = "https://github.com/andrzejressel/finance-as-code.git", features = ["all"] }In that case however you must set DUCKDB_DOWNLOAD_LIB="1" in your environment variables to allow downloading the duckdb library. You can automate it by creating .cargo/config.toml with the following content:
[env]
DUCKDB_DOWNLOAD_LIB = "1"use finance_as_code_budget::{
LocalDirectorySource, Setup, Sink, Source,
lunchflow::{
LunchFlowDownloaderConfig, create_lunchflow_downloader, create_lunchflow_file_reader,
},
lunchmoney::{LunchMoneySinkConfig, create_lunchmoney_sink},
run,
};
fn main() {
let lunchflow_dir = "path/to/lunchflow/dir";
let setups: Vec<Box<dyn Setup>> = vec![
Box::new(create_lunchflow_downloader(
LunchFlowDownloaderConfig::builder()
.account_id(123_i64)
.api_key("lunchflow_api_key")
.local_directory(lunchflow_dir)
.build(),
)
.expect("Failed to create Lunch Flow downloader")),
];
let sources: Vec<Box<dyn Source>> = vec![
Box::new(LocalDirectorySource::new(
lunchflow_dir,
create_lunchflow_file_reader(),
)
.expect("Failed to create local directory source")),
];
let sinks: Vec<Box<dyn Sink>> = vec![Box::new(create_lunchmoney_sink(
LunchMoneySinkConfig::builder()
.api_key("lunchmoney_api_key")
.account_name("My Account")
.build(),
))];
run(setups, sources, vec![], sinks).expect("Pipeline run failed");
}use finance_as_code_budget::{
LocalDirectorySource, Setup, Sink, Source,
lunchflow::{
LunchFlowDownloaderConfig, create_lunchflow_downloader, create_lunchflow_file_reader,
},
treeline::{SinkTreelineOptions, create_treeline_sink},
run,
};
fn main() {
let lunchflow_dir = "path/to/lunchflow/dir";
let setups: Vec<Box<dyn Setup>> = vec![
Box::new(create_lunchflow_downloader(
LunchFlowDownloaderConfig::builder()
.account_id(123_i64)
.api_key("lunchflow_api_key")
.local_directory(lunchflow_dir)
.build(),
)
.expect("Failed to create Lunch Flow downloader")),
];
let sources: Vec<Box<dyn Source>> = vec![
Box::new(LocalDirectorySource::new(
lunchflow_dir,
create_lunchflow_file_reader(),
)
.expect("Failed to create local directory source")),
];
let sinks: Vec<Box<dyn Sink>> = vec![Box::new(create_treeline_sink(
SinkTreelineOptions::builder()
.account_name("My Treeline Account")
.build(),
))];
run(setups, sources, vec![], sinks).expect("Pipeline run failed");
}The Treeline sink performs a full replacement for the selected account (existing transactions and balances for that account are removed before import).
LocalDirectorySource is the file-based source in the pipeline. You point it to a folder and provide a matching file reader, and it imports supported transaction files from that folder.
- Use
create_mbank_file_reader()for mBank CSV exports. - Use
create_lunchflow_file_reader()for Lunch Flow JSON files. - In Lunch Flow flows, run the downloader as a setup first so it saves files locally, then use
LocalDirectorySourceto import them.