My neovim setup for Beancount accounting
I recently started using
Beancount to implement plain
text accounting. This post describes my
approach around it and my nvim
setup.
📒 Understanding why Beancount might be useful requires making sense of double-entry bookkeping.
Rather than writing a half-baked explanation about it, I encourage you to skim through Beancount’s getting started guide if this sounds unfamiliar to you.
Go on, I’ll wait here. ✌️
# My approach to Beancount
Beancount can shine only after we bear the pain of populating it with recent transactions and then figure out a way to periodically keep track of new ones. The easiest way I found to do this is by writing a few automated importers for bank statements, exports from operations, and so on. Each importer will parse a file (e.g., a CSV from your bank, or – sigh – a PDF) and output one or more Beancount transactions.
I have spread the effort of writing the importers and processing their output over a few months, starting with more important accounts first and then slowly adding other things. Depending on how you decide to structure transactions (e.g., dividing expenses by type, payee, etc.), adding new entries will require some manual post-processing to assign things to the right accounts. This tool can make things easier after some initial manual work: it trains an SVC classifier that tries to predict the right accounts for each transaction.
After a few iterations I ended up with a small Python library1 that can scan a directory for new files, extract their statements, and then archive them. By filing them in the appropriate directory2, Beancount will automatically match documents to the accounts they relate to (e.g., an invoice to its expenses account).
Overall, the process works well-enough, modulo that parsing some PDF statements to extract balances is more art than science and will inevitably require some evolution as the document structure will change.
I keep the full ledger within a single file3, divided in sections
(e.g., one per institution, year, etc.) through nvim
folds (more
below). Being all text (plus the occasional PDF statement),
I use git
to keep track of changes (both for the library and the journal).
Backups go through the same methods I use for the rest of the system (e.g., Time
Machine on macOS).
# Setting up nvim
The community has built awesome tools to manage Beancount files. My weapon of choice for editing plain text files is nvim, and after experimenting with a few things I settled on:
- A Tree-sitter parser for indent and highlight.
- The built-in
bean-format
to consistently format the journal. I integrate it withnvim
through the efm-langserver. - An
nvim-cmp
source to auto-complete accounts. The source enumerate accounts at startup and caches the result. As a result, it refused to auto-complete new accounts created after opening the file. I hacked together alua
function to manually refresh the cache when needed. - A custom
compiler
that runsbean-check
throughpoetry
, based on this plugin’s compiler. I quickly run it withm<cr>
. -
A few file-type options to tell
nvim
how to deal with comments, formatting, and correctly split keywords:setlocal formatprg=bean-format setlocal comments=b:; setlocal commentstring=;%s setlocal iskeyword+=:
vim
’s default fold marker{{{
to split the file in sections. I write folds in comments, number them (so there’s no need to close them) and nest them, e.g.; --- Bank A {{{2
.- Last, I extended4 universal-ctags
to support Beancount. This lets me easily jump to the opening directive of an
account or to the definition of a commodity using
C-]
innvim
. The resultingbeancount.ctags
is here.
# Results
I am pretty happy with the overall setup.
After completing the costly first-time import, Beancount is useful to keep track of financial assets, expenses, and book-keep in general. At first my understanding about it was pretty limited: I used it to track monthly expense against their budgets, or forecast them for incoming periods.
But Beancount can do a lot more! For instance, it generates comprehensive financial reports and supports an SQL-like query language that will be pretty useful for the next tax-reporting period – replacing a bunch of hacky iPython notebooks I had built over the years.
Reading Financial Intelligence for Entrepreneurs then gave it the finishing touch. The authors do a great job at helping those, like me, unfamiliar with accounting making sense of balance sheets and income statements. With a little practice I found it easy to apply their teaching to my statements and found lot of value in the balance sheet and the income statement that Beancount (or fava, its web interface) automatically generates.
And you, how do you use Beancount? If you have any feedback or comment, please get in touch, I’d love to hear them.
Thank you for reading! ‘til next time! 👋
# Footnotes
-
poetry
makes is easy to manage dependencies whilepoetry2nix
ensures a reproducible environment throughnix
. ↩ -
In my case, it looks as follows:
docs/<year>/<account>
, where<account>
builds a directory tree from Beancount’s account, turningAssets:Checking
toAssets/Checking
. ↩ -
As long as things remain manageable this way. I suspect that at some point the file length will make it complicated for
nvim
to handle things efficiently. ↩ -
Adding a Beancount
ctags
parser was easier than expected, but took me longer than it should have because the manual states:The regular expression, <line_pattern>, defines an extended regular expression (roughly that used by egrep(1)), …
I tried for a while to use
\d
(supported byegrep
but not working inctags
) then went for[0-9]
. ↩