Skip to content
Port of the Ledger accounting system (see project "ledger") to Common Lisp
Branch: master
Clone or download
Type Name Latest commit message Commit time
Failed to load latest commit information.
cambl @ 7016d1a
core Add support for the 'R' (or 'real') value expression Nov 13, 2018
doc Fixed balance reports Dec 10, 2007
ledger @ 38315cf
parsers/textual Allow hours, minutes and seconds in dates Mar 2, 2019
periods @ 3e56c2a
reports Add payees and accounts reports Oct 2, 2017
roswell Move the Roswell script to the roswell directory Mar 6, 2018
Makefile Add the ability to build a standalone cl-ledger program with ASDF Jan 11, 2018 Don't export symbols not bound to anything Oct 27, 2018
cl-ledger.el Provide more informations about CL-LEDGER system May 21, 2015
timeclock.el Provide more informations about CL-LEDGER system May 21, 2015


CL-Ledger is a Common Lisp port of the Ledger double-entry accounting system.


The easiest way to install CL-Ledger is using Quicklisp:

(quicklisp:quickload "cl-ledger")

If you want to work with the latest source code and need to install it manually, you will need the following libraries:

  • alexandria
  • cl-containers
  • cl-ppcre
  • local-time
  • series

They can be installed easily using Quicklisp:

(quicklisp:quickload '("alexandria" "cl-containers" "cl-ppcre" "local-time" "series"))

Now you need to get the source code and pull all of the projects relating to CL-Ledger:

git clone
cd cl-ledger
git submodule init
git submodule update

The next step is to indicate your Common Lisp system where to load CL-Ledger from.

If you are using Quicklisp, you can add links to the local-projects directory:

cd /path/to/quicklisp/
cd local-projects
ln -s /path/to/cl-ledger .
ln -s /path/to/cl-ledger/cambl .
ln -s /path/to/cl-ledger/periods .

If you don’t use Quicklisp, you can add the following contents to the initialization file of your Common Lisp implementation (e.g. ~/.sbclrc for SBCL):

(push "/path/to/cl-ledger/" asdf:*central-registry*)

(dolist (project '("cambl/" "periods/"))
  (push (merge-pathnames project "/path/to/cl-ledger/")

Make sure you change /path/to/cl-ledger/ to be the directory where CL-Ledger lives. Also, be sure this pathname ends with a slash! In CL, directory names always end with /.

Now you can run CL-Ledger at the REPL like this:

(asdf:load-system "cl-ledger")

This compiles and loads the CL-Ledger core, and also the textual parser package, for parsing standard Ledger text files.

Basic commands

Once in the REPL, try out this command:

(ledger:register-report "/path/to/cl-ledger/doc/sample.dat")

You should see a register printed representing the contents of sample.dat. You can constrain this report using keyword modifiers:

(ledger:register-report "/path/to/cl-ledger/doc/sample.dat"
                        :account "books")

CL-Ledger only reads the file on the first run, and if it changes, so feel free to repeat the same command several times even for large journal files.

The following reports are supported:

(ledger:register-report "/path/to/file" [OPTIONS])
(ledger:balance-report "/path/to/file" [OPTIONS])
(ledger:print-report "/path/to/file" [OPTIONS])
(ledger:equity-report "/path/to/file" [OPTIONS])
(ledger:sexp-report "/path/to/file" [OPTIONS])
(ledger:csv-report "/path/to/file" [OPTIONS])
(ledger:derive-entry "/path/to/file" [OPTIONS])

As for OPTIONS, any of the following keyword pairs is allowed. There are some extra options allowed for derive-entry, for which please see below.

:account “REGEXP”
:not-account “REGEXP”
:payee “REGEXP”
:not-payee “REGEXP”
:note “REGEXP”
:not-note “REGEXP”
:begin “YYYY/MM/DD”
:end “YYYY/MM/DD”
:range “RANGE EXPRESSION”a range expression, like “this month”
:period “PERIOD EXPRESSION”like “every month this year”
:expr “VALUE-EXPR”most Ledger 2.x value exprs allowed
:limit “VALUE-EXPR”the same as 2.x’s –limit or -l
this is a convenience alias for :expr
:only “VALUE-EXPR”the same as 2.x’s –only
:display “VALUE-EXPR”the same as 2.x’s -d or –display
:status KEYWORDonly report transactions whose status
:sort “VALUE-EXPR”sort based on VALUE-EXPR calculation
:no-total BOOLdon’t show totals
:collapse BOOLcollapse multiline entries
:subtotal BOOLgroup all transactions by account
:invert BOOLnegate all transaction values
(same as saying :amount “-a”)
:related BOOLshow “other” transactions of each entry
:lots BOOLshow all commodity lot information
:lot-prices BOOLshow commodity lot prices
:lot-dates BOOLshow commodity lot dates
:lot-tags BOOLshow commodity lot tags
:amount “VALUE-EXPR”use EXPR to display transaction amounts
:total “VALUE-EXPR”use EXPR to display the running total
:set-amount “VALUE-EXPR”instead of :amount, actually represent
the amount using EXPR (this is rarely
something you want to do)
:set-total “VALUE-EXPR”same for the running total
:bridge-totals BOOLif the running totals are not contiguous
create revaluation entries to fill gaps
:show OUTPUT-MODEshow amounts and totals using the given mode
:show :market.. in terms of their market value
:show :basis.. in terms of their basis cost

Here’s a quick table for translating Ledger 2.6.1 options into their corresponding CL-Ledger keywords:

Short optionLong optionCL-Ledger keyword
-b ARG–begin ARG:begin ARG
-e ARG–end ARG:end ARG
-p ARG–period ARG:period ARG
-l ARG–limit ARG:limit ARG
–only ARG:only ARG
-d ARG–display ARG:display ARG
-n (for balances):no-total t
-n–collapse:collapse t
-r–related:related t
-s–subtotal:subtotal t
-S EXPR–sort ARG:sort ARG
-t EXPR–sort-entries ARG:sort-entries ARG

Options to derive-entry

The reporting command derive-entry takes some special options.

The derive-entry report uses CL-Ledger to intelligently create a new entry for you. The possible keywords arguments are:

  • :date
  • :payee
  • :account
  • :balance-account
  • :amount
  • :append

Except for :payee, all of these keyword arguments are optional. Here is what they mean:

:payee “REGEXP”Find the most recent entry whose payee matches
REGEXP, and base the new entry derivation on
its details. If no matching entry can be found,
the payee of the newly created entry will
exactly match REGEXP.
:date “DATE-STRING”The date of the new entry will be DATE-STRING,
otherwise it is today.
:account “REGEXP”Set the first account line in the new entry to
be the most recently used account which matches
REGEXP. If no such account can be found, an
account named REGEXP is used. If no account is
specified, the account “Expenses:Unknown” is
:balance-account “REGEXP”Like :ACCOUNT, except this refers to the
account used for the second transaction in the
newly derived entry. If not specified, a
calculated “balance account” is looked for in
the matching entry; if this does not apply, the
journal’s default account is used; if this does
not apply, the account “Assets:Unknown” is used.
:amount “VALUE-STRING”The amount of the first transaction. If it has
no commodity, the correlated commodity from the
discovered entry is used.
:append BOOLIf non-NIL, the new entry is written to the same
journal where the matching entry was found (for
a binder that references many journals, this is
whichever file the discovered entry was in).

Here are a few examples, using sample.dat as a reference:

(ledger:derive-entry "doc/sample.dat" :payee "book")
  2007/12/04 Book Store
      Expenses:Books                            $20.00

(ledger:derive-entry :payee "book" :amount "$125")
  2007/12/04 Book Store
      Expenses:Books                           $125.00

(ledger:derive-entry :payee "Hello World")
  2007/12/04 Hello World

(ledger:derive-entry :date "2004/01/01" :payee "Hello World")
  2004/01/01 Hello World

(ledger:derive-entry :payee "book" :account "equ")
  2007/12/04 Book Store
      Equity:Opening Balances                   $20.00

(ledger:derive-entry :payee "book" :account "Who Knows")
  2007/12/04 Book Store
      Who Knows                                 $20.00

(ledger:derive-entry :payee "book" :balance-account "bank")
  2007/12/04 Book Store
      Expenses:Books                            $20.00

(ledger:derive-entry :payee "book" :account "liab"
                     :balance-account "bank")
  2007/12/04 Book Store
      Liabilities:MasterCard                   $-20.00

(ledger:derive-entry :payee "book" :account "bank" :amount "50")
  2007/12/04 Book Store
      Assets:Bank:Checking                      $50.00

(ledger:derive-entry :payee "book" :account "bank" :amount "$125")
  2007/12/04 Book Store
      Assets:Bank:Checking                     $125.00

Date format

The date format used in your journal file can be specified either using the *input-time-format* variable (by default: “%Y/%m/%d%| %H:%M:%S”), or by writing a F command directive at the beginning of the journal:

F %Y-%m-%d

2017-09-01 * Some payee
  Some account  45,18 EUR
  Some other account

The date format used in reports can be specified using the *output-time-format* variable (by default: “%Y/%m/%d”).

Binder caching

After the call to binder, the variable *last-binder* contains the contents of what was read. From that point forward, if no binder or string is passed to the reporting function, they will assume you wish to report on the contents of *last-binder*.

Implementations status

Here is how CL-Ledger stands up against current Lisp implementations:

LispWorks5.02 PersonalWORKS
Allegro CL10.0 ExpressWORKS
Clozure CL1.11WORKS
OpenMCL2007-07-22Fails to compile LOCAL-TIME
ABCL1.5.0Fails to compile CL-LEDGER
CLISP2.49Fails to compile CL-CONTAINERS
CMUCL19d (2007-11)Fails to compile PERIODS
GCL2.6.7<unable to build so far>


For fans of the Series library, you can apply scan-transactions or scan-entries to a binder/account/journal/entry in order to produce a series of the corresponding type. Example:

(collect (ledger:scan-transactions
          (ledger:read-journal "doc/sample.dat")))
  => [a list of all transactions, in sequence, within sample.dat]

Command line

You can build a standalone cl-ledger binary using the Makefile:

make LISP=sbcl
cl-ledger -f doc/sample.dat balance

You can also use the Roswell script cl-ledger.ros in the roswell directory to use CL-Ledger from the command line:

cl-ledger.ros -f doc/sample.dat balance
You can’t perform that action at this time.