CLI · commit a80d895

ledgerline

Plain-text double-entry accounting CLI

Download ledgerline.zip
Quickstart
unzip ledgerline.zip && cd ledgerline
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/python -m pytest -q

ledgerline

Plain-text double-entry accounting for people who want their money in version-controlled files. ledgerline reads a strict, hledger-flavoured journal, refuses to accept anything that does not balance, points a caret at exactly what is wrong when it fails, and turns the result into balance, register, income statement, balance sheet, cash flow and budget reports with multi-commodity support, price-based valuation and Indian lakh digit grouping. Everything is Decimal arithmetic; there is no database and no network access.

Features

Install

Requires Python 3.12+.

git clone <this repo> && cd ledgerline
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/ledgerline --help

(pip install -e . in any virtualenv works equally well.)

Quickstart

The repository ships a realistic year of finances in examples/ (410 transactions, 12 monthly files, prices, an import CSV with rules).

export LEDGERLINE_FILE=examples/main.journal
ledgerline check                          # parse + balance + assertions + strict accounts
ledgerline bal --depth 2                  # balances
ledgerline bal Expenses --quarterly       # spending by quarter
ledgerline is --monthly                   # income statement
ledgerline bs --value INR                 # net worth, USD holdings and stocks valued in rupees
ledgerline budget --yearly                # budget vs actual
ledgerline import examples/bank.csv --rules examples/rules.toml

Every command accepts -f/--file (or $LEDGERLINE_FILE), and -o table|csv|json.

Usage

All output below was produced by running the commands against examples/main.journal.

check

$ ledgerline check
OK: 410 transactions, 809 postings, 15 file(s)

Errors exit with status 1 and print one block per problem. Syntax errors (examples/broken-syntax.journal):

$ ledgerline check -f examples/broken-syntax.journal
error: invalid date '2026-02-30': 2026-02 has only 28 days
 --> examples/broken-syntax.journal:8:1
  |
8 | 2026-02-30 Impossible date
  | ^^^^^^^^^^

error: misplaced thousands separator in '1,00.50'
  --> examples/broken-syntax.journal:13:20
   |
13 |     Expenses:Food  1,00.50 INR
   |                    ^^^^^^^
   = hint: use 1,000 (western) or 1,00,000 (indian) grouping, or no commas

error: account name 'Expenses:Food 80 INR' contains an amount-like part
  --> examples/broken-syntax.journal:17:5
   |
17 |     Expenses:Food 80 INR
   |     ^^^^^^^^^^^^^^^^^^^^
   = hint: separate the account name from the amount with two or more spaces

3 errors found

Errors in the books themselves (examples/broken-books.journal, strict mode on):

$ ledgerline check -f examples/broken-books.journal
error: account 'Expenses:Fod' is not declared (strict mode)
  --> examples/broken-books.journal:15:5
   |
15 |     Expenses:Fod    250.00 INR
   |     ^^^^^^^^^^^^
   = hint: did you mean 'Expenses:Food'?

error: transaction does not balance: remaining 10.00 INR
  --> examples/broken-books.journal:19:1
   |
19 | 2026-01-10 Taxi
   | ^^^^^^^^^^^^^^^
   = hint: add the missing posting, or leave one amount out and it will be filled in automatically

2 errors found

A failed balance assertion (examples/broken-assertion.journal):

$ ledgerline check -f examples/broken-assertion.journal
error: balance assertion failed for Assets:Bank: expected 650.00 INR, actual 750.00 INR
  --> examples/broken-assertion.journal:17:32
   |
17 |     Assets:Bank       0.00 INR = 650.00 INR
   |                                ^^^^^^^^^^^^
   = hint: difference is -100.00 INR (assertions are checked in date order, as of 2026-01-31)

1 error found

bal

Options: QUERY..., --depth N, --flat/--tree, --empty, -b/-e DATE, --monthly/--quarterly/--yearly, -H/--historical (period columns show ending balances), --value COMMODITY, --value-at end|then, -B/--cost.

$ ledgerline bal --depth 2 --value INR
Balance

 Account                  Balance
 ────────────────────────────────
 Assets             ₹29,32,033.71
   Bank             ₹16,61,575.68
   Broker            ₹7,85,994.81
   Cash                ₹47,212.10
   Investments       ₹4,37,251.12
 Equity            -₹11,58,030.00
   Opening         -₹11,58,030.00
 Expenses            ₹6,88,473.66
   Entertainment       ₹10,116.00
   Food              ₹1,59,551.17
   Health               ₹9,168.16
   Insurance           ₹21,500.00
   Rent              ₹3,00,000.00
   Shopping            ₹34,460.39
   Transport           ₹46,140.95
   Travel              ₹55,650.50
   Utilities           ₹51,886.49
 Income            -₹23,81,364.97
   Bonus              -₹60,000.00
   Dividends           -₹4,108.53
   Interest            -₹7,256.44
   Salary          -₹23,10,000.00

 Total                 ₹81,112.40

The last line is the unrealised gain on the USD stock and mutual-fund holdings: assets are marked to market while equity stays at cost (see docs/design.md).

$ ledgerline bal Expenses --quarterly --depth 2
Balance

 Account                 2026Q1         2026Q2         2026Q3         2026Q4          Total
 ──────────────────────────────────────────────────────────────────────────────────────────
 Expenses          ₹2,06,050.91   ₹1,48,674.52   ₹1,74,430.97   ₹1,59,317.26   ₹6,88,473.66
   Entertainment      ₹2,529.00      ₹2,529.00      ₹2,529.00      ₹2,529.00     ₹10,116.00
   Food              ₹32,952.52     ₹41,218.30     ₹39,486.58     ₹45,893.77   ₹1,59,551.17
   Health             ₹2,612.50        ₹669.67      ₹1,115.53      ₹4,770.46      ₹9,168.16
   Insurance                  0              0     ₹21,500.00              0     ₹21,500.00
   Rent              ₹75,000.00     ₹75,000.00     ₹75,000.00     ₹75,000.00   ₹3,00,000.00
   Shopping          ₹10,163.49      ₹8,176.52      ₹9,462.31      ₹6,658.07     ₹34,460.39
   Transport         ₹13,868.57      ₹9,684.01     ₹11,257.98     ₹11,330.39     ₹46,140.95
   Travel            ₹55,650.50              0              0              0     ₹55,650.50
   Utilities         ₹13,274.33     ₹11,397.02     ₹14,079.57     ₹13,135.57     ₹51,886.49

 Total             ₹2,06,050.91   ₹1,48,674.52   ₹1,74,430.97   ₹1,59,317.26   ₹6,88,473.66

reg

$ ledgerline reg tag:trip=goa
Register

 Date         Description                       Account                             Amount   Running total
 ─────────────────────────────────────────────────────────────────────────────────────────────────────────
 2026-03-09   IndiGo | Flights BLR-GOI return   Expenses:Travel:Flights         ₹18,400.00      ₹18,400.00
                                                Liabilities:CreditCard:ICICI   -₹18,400.00               0
 2026-03-09   Taj Holiday Village | 4 nights    Expenses:Travel:Hotels          ₹24,000.00      ₹24,000.00
                                                Liabilities:CreditCard:ICICI   -₹24,000.00               0
 2026-03-14   Fisherman's Wharf | Dining out    Expenses:Travel:Food             ₹3,150.00       ₹3,150.00
                                                Liabilities:CreditCard:ICICI    -₹3,150.00               0
 2026-03-14   Scooter rental                    Expenses:Travel:Transport        ₹2,400.00       ₹2,400.00
                                                Assets:Cash:Wallet              -₹2,400.00               0
 2026-03-15   Britto's | Dining out             Expenses:Travel:Food             ₹2,480.50       ₹2,480.50
                                                Liabilities:CreditCard:ICICI    -₹2,480.50               0
 2026-03-16   Gunpowder | Dining out            Expenses:Travel:Food             ₹2,890.00       ₹2,890.00
                                                Liabilities:CreditCard:ICICI    -₹2,890.00               0
 2026-03-16   Beach shack                       Expenses:Travel:Food             ₹1,350.00       ₹1,350.00
                                                Assets:Cash:Wallet              -₹1,350.00               0
 2026-03-17   Café Chocolatti | Dining out      Expenses:Travel:Food               ₹980.00         ₹980.00
                                                Liabilities:CreditCard:ICICI      -₹980.00               0

Queries combine: ledgerline reg Expenses:Rent date:2026-03 amt:">1000".

$ ledgerline reg Expenses:Rent date:2026-03 amt:">1000"
Register

 Date         Description       Account             Amount   Running total
 ─────────────────────────────────────────────────────────────────────────
 2026-03-02   Landlord | Rent   Expenses:Rent   ₹25,000.00      ₹25,000.00

is, bs, cf

$ ledgerline is --quarterly --depth 1
Income Statement

 Account                2026Q1         2026Q2         2026Q3         2026Q4           Total
 ──────────────────────────────────────────────────────────────────────────────────────────
 Revenues
 Income           ₹6,16,959.42   ₹5,86,786.25   ₹5,86,735.15   ₹5,86,775.62   ₹23,77,256.44
                        $13.52         $11.31         $11.47         $12.03          $48.33

 Total revenues   ₹6,16,959.42   ₹5,86,786.25   ₹5,86,735.15   ₹5,86,775.62   ₹23,77,256.44
                        $13.52         $11.31         $11.47         $12.03          $48.33

 Expenses
 Expenses         ₹2,06,050.91   ₹1,48,674.52   ₹1,74,430.97   ₹1,59,317.26    ₹6,88,473.66

 Total expenses   ₹2,06,050.91   ₹1,48,674.52   ₹1,74,430.97   ₹1,59,317.26    ₹6,88,473.66

 Net income       ₹4,10,908.51   ₹4,38,111.73   ₹4,12,304.18   ₹4,27,458.36   ₹16,88,782.78
                        $13.52         $11.31         $11.47         $12.03          $48.33
$ ledgerline bs --value INR --depth 2
Balance Sheet

 Account                                            total
 ────────────────────────────────────────────────────────
 Assets
 Bank                                       ₹16,61,575.68
 Broker                                      ₹7,85,994.81
 Cash                                          ₹47,212.10
 Investments                                 ₹4,37,251.12

 Total assets                               ₹29,32,033.71

 Liabilities

 Total liabilities                                      0

 Net worth                                  ₹29,32,033.71

 Equity
 Opening                                    ₹11,58,030.00
 Current earnings                           ₹16,92,891.31
 Unrealised gains / commodity differences      ₹81,112.40

 Total equity                               ₹29,32,033.71

Current earnings is the not-yet-closed profit; Unrealised gains closes the gap between market value and cost, so Net worth always equals Total equity.

$ ledgerline cf --yearly --depth 1
Cash Flow

 Account                    2026
 ───────────────────────────────
 Operating
 Expenses          -₹4,39,344.61
 Income            ₹23,77,256.44
                          $48.33

 Total operating   ₹19,37,911.83
                          $48.33

 Investing
 Assets            -₹3,80,000.00
                      -$4,975.22

 Total investing   -₹3,80,000.00
                      -$4,975.22

 Financing
 Equity             ₹9,03,000.00
                       $3,000.00
 Liabilities       -₹2,49,129.05

 Total financing    ₹6,53,870.95
                       $3,000.00

 Exchange
 Exchange          -₹5,02,995.00
                       $6,000.00

 Total exchange    -₹5,02,995.00
                       $6,000.00

 Net cash flow     ₹17,08,787.78
                       $4,073.11
 Cash at start                 0
 Cash at end       ₹17,08,787.78
                       $4,073.11

budget

~ monthly transactions in main.journal define the budget; actuals roll up from sub-accounts.

$ ledgerline budget --yearly
Budget (actual / budget, % used)

 Account                                                 2026
 ────────────────────────────────────────────────────────────
 Expenses:Entertainment         ₹10,116.00 / ₹10,800.00 (94%)
 Expenses:Food:Dining           ₹65,304.75 / ₹72,000.00 (91%)
 Expenses:Food:Groceries      ₹94,246.42 / ₹1,20,000.00 (79%)
 Expenses:Rent             ₹3,00,000.00 / ₹3,00,000.00 (100%)
 Expenses:Shopping              ₹34,460.39 / ₹48,000.00 (72%)
 Expenses:Transport             ₹46,140.95 / ₹48,000.00 (96%)
 Expenses:Utilities             ₹51,886.49 / ₹66,000.00 (79%)

 Total                      ₹6,02,155.00 / ₹6,64,800.00 (91%)

Cells over 100% are red, 80% and above yellow.

import

$ ledgerline import examples/bank.csv --rules examples/rules.toml
15 new, 3 duplicate(s) skipped, 4 uncategorized (marked !)
2027-01-01 * Acme Corp | NEFT CR-ACME CORP-SALARY JAN  ; tag:salary, import:17f07fb9a69a
    Assets:Bank:HDFC  ₹1,95,000.00
    Income:Salary

2027-01-02 * Landlord | UPI-LANDLORD-RENT JAN  ; import:15ca623493ea
    Assets:Bank:HDFC  -₹25,000.00
    Expenses:Rent

2027-01-03 * UPI-ZOMATO-ORDER-88123  ; tag:food, import:8b25f29826ca
    Assets:Bank:HDFC      -₹486.00
    Expenses:Food:Dining

2027-01-04 * BESCOM | BESCOM ELECTRICITY BILL  ; tag:utilities, import:c0c3cfb816f8
    Assets:Bank:HDFC                -₹2,284.50
...
15 new, 3 duplicate(s) skipped, 4 uncategorized (marked !)

The three December rows that already exist in the journal (typed by hand with the bank's narration) were skipped. Add --append FILE to write into a journal; re-running the same import afterwards reports 0 new. Rows that no rule matched go to Expenses:Uncategorized and are marked ! for review. Rules format:

[csv]
account = "Assets:Bank:HDFC"
currency = "INR"
date_column = "Date"
date_format = "%d/%m/%y"
description_column = "Narration"
debit_column = "Withdrawal Amt."      # or: amount_column = "Amount" (+ invert = true)
credit_column = "Deposit Amt."
default_account = "Expenses:Uncategorized"

[[rule]]
match = "SWIGGY|ZOMATO"              # regex, case-insensitive, first match wins
account = "Expenses:Food:Dining"
payee = "Swiggy"                     # optional
status = "*"                         # optional ("!" to flag for review)
tags = "tag:food"                    # optional

fmt, stats, accounts, payees

$ ledgerline fmt --check && echo canonical
canonical

ledgerline fmt prints the formatted root file; -w rewrites it and every included file; --check exits 1 if anything would change.

$ ledgerline stats
Journal statistics

 Metric                                                           Value
 ──────────────────────────────────────────────────────────────────────
 Files                                                               15
 Transactions                                                       410
 Postings                                                           809
 First transaction                                           2026-01-01
 Last transaction                                            2026-12-31
 Span (days)                                                        365
 Accounts used                                                       30
 Accounts declared                                                   31
 Commodities                    AAPL, FLEXICAP, INR, MSFT, NIFTY50, USD
 Payees                                                              54
 Price directives                                                    84
 Periodic transactions                                                1
 Balance assertions                                                  16
 Pending (!) transactions                                             0
 Transactions per month (avg)                                      34.2
 Busiest month                                             2026-03 (43)
$ ledgerline accounts --tree --depth 2
Accounts

 Account           Postings   Declared
 ─────────────────────────────────────
 Assets                 312        yes
   Bank                 183        yes
   Broker                29        yes
   Cash                  75        yes
   Investments           25        yes
 Equity                   2        yes
   Opening                2        yes
 Expenses               300        yes
   Entertainment         36        yes
   Food                 103        yes
$ ledgerline payees
Payees

 Payee                   Transactions        First         Last
 ──────────────────────────────────────────────────────────────
 SIP                               24   2026-01-05   2026-12-05
 Uber                              24   2026-01-15   2026-12-27
 Chai Point                        23   2026-01-10   2026-12-22
 Local vegetables                  19   2026-02-04   2026-12-09
 Auto rickshaw                     18   2026-01-09   2026-12-09

JSON and CSV output

Reports are emitted as tidy records (one row per account, period and commodity):

$ ledgerline bal Expenses --yearly --depth 2 -o csv
account,period,commodity,quantity
Expenses,2026,INR,688473.66
Expenses:Entertainment,2026,INR,10116.00
Expenses:Food,2026,INR,159551.17
Expenses:Health,2026,INR,9168.16
Expenses:Insurance,2026,INR,21500.00
Expenses:Rent,2026,INR,300000.00
Expenses:Shopping,2026,INR,34460.39
Expenses:Transport,2026,INR,46140.95
Expenses:Travel,2026,INR,55650.50
Expenses:Utilities,2026,INR,51886.49
Total,2026,INR,688473.66

Query language

Expenses:Food, payee:Uber, tag:trip=goa, amt:>1000, date:2026-03, status:!, not:Rent, all ANDed. Full reference: docs/query-language.md.

Configuration

Setting Where
Journal file -f/--file, or $LEDGERLINE_FILE
Strict accounts option strict in the journal, or --strict
Commodity display commodity directives (symbol, precision, grouping)
Import mapping and rules rules.toml (see above)
Cash accounts for cf --cash REGEX

Dates on the command line are YYYY, YYYY-MM or YYYY-MM-DD; -b is inclusive and -e exclusive. Exit codes: 0 ok, 1 journal/format errors (or fmt --check differences), 2 usage errors (bad query, missing -f).

The file syntax is documented in docs/journal-format.md, the algorithms (balancing, assertion ordering, price lookup, valuation modes, cash flow classification, import de-duplication) in docs/design.md.

Architecture

src/ledgerline/
  cli.py        click commands, option wiring, error -> exit code mapping
  parser.py     line-oriented parser -> Journal AST; collects errors with line/col
  models.py     Amount, Posting, Transaction, PeriodicTransaction, PriceDirective, Journal
  numfmt.py     commodity formats, lakh/western grouping, Decimal rendering
  booking.py    balancing, elision, strict accounts, date-ordered assertions
  prices.py     PriceDB: P directives + implicit prices, direct/inverse/one-hop lookup
  query.py      query language -> predicates over (transaction, posting)
  periods.py    partial dates, month/quarter/year columns, recurrence expansion
  reports.py    entries, valuation, aggregation, bal/reg/is/bs/cf/budget
  output.py     Report container; rich table, csv and json emitters
  importer.py   rules.toml, CSV reading, de-duplication, transaction building
  formatter.py  canonical transaction rendering and file formatting
  info.py       stats, accounts, payees
  errors.py     LedgerError (caret rendering), JournalError, UsageError
scripts/gen_examples.py   deterministic generator for examples/

Tests

uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/pytest

130 tests cover every directive and parser error with line/column, balancing (multi-commodity, costs, virtual postings), assertion ordering, price chains, the query language, each report's numbers on a small fixture, import de-duplication, formatter idempotency, and end-to-end CLI runs against examples/.

Limitations

License

MIT, see LICENSE.