I’ve been a fan of accounting for a long time now. My affair with bookkeeping started when I studied economics, and took a course in Financial accounting (not that I had any choice – it was a compulsory one). I was then exposed to the brilliant ideas of double-entry accounting, first codified by Luca Pacioli in the late XV century. As a mathematician, I cannot overestimate the fact that double-entry bookkeeping is in fact an abstract formalized system. What’s even more impressive is that the bookkeepers are the ones who do negative numbers right! Indeed, in bookkeeping there is no such thing as a “negative number” (well, except for some special circumstances, but never mind). Instead, each account has two sides, and its “balance” is effectively the difference between them. In other words, each “number” in accounting is in reality a pair of numbers, and if two such pairs (a,b) and (c,d) have the property that a+d=b+c, they are identical from the balance standpoint. Looks familiar? (Though to be honest, bookkeepers do not use the language of “pairs” and “equivalence”.)
When I was looking for a tool to manage my personal finance some twelve years ago, I stumbled upon GnuCash. It was free as in beer, worked under Linux (I used Mandrake Linux, later renamed Mandriva, at that time), and featured a full-blown double-entry system. What else could I ask for?
Then I got married, and I lost motivation to track my (our) spending (the income part was easy, but you really need to do both in an accounting system). I had a few attempts to track our family finance in GnuCash, but they never caught on.
Fast forward to 2014, and I discovered Ledger. Wow. Already being a user of Org-mode, and thus sold on the idea of keeping my data in plain text, I instantly fell in love. (BTW, GnuCash keeps its data in format whose name I prefer not to mention; suffice it to say that it has an ominous three-letter buzzword abbreviation and is basically s-expressions reinvented with angle brackets and more verbose notation.) When I learned that there’s a special Emacs mode for editing Ledger files, I was just squeaking with delight.
Now I’ve been using Ledger and ledger-mode for almost a year. Don’t get me wrong: I’m happy with it, but it doesn’t work as smoothly as I would wish to. For a long time I wanted to do something about it; now the time has come. I started to read ledger-mode source code, and decided to augment it with some customizations.
The first one I wanted to do was a way to quickly perform computations. In Ledger, you can put an arithmetic expression in place of an amount (though you have to put it in parentheses), but I didn’t feel like cluttering my ledger file with that. An obvious solution is to use Calc – but firing Calc, performing computations, and then yanking them back into the file is a bit awkward. I decided that I would check the programmer’s interface for Calc, and it turned out that it’s a very nice one. Basically, you have to know about one function (unless you really, really care for performance, or have other special needs) – calc-eval
. In its basic form, it takes a string with an expression and gives you back a string with the result. Writing a function which looks back, checks whether it sees an expression (using a primitive regex) and replaces it with the result was trivial.
This was already a nice thing to have, but it was not enough. I decided that I want my function to always give results to two decimal places, and append the currency name (in Poland we put the currency name or abbreviation after the amount). Since I wanted my function to remain a general-purpose device, I decided to delegate the latter behavior to a prefix argument. Here is the code:
;; Fast calculation: interactively replace simple arithmetic ;; expressions with their values in arbitrary buffers (defvar fast-calc-suffix " PLN" "The default suffix for C-u M-x fast-calc. Useful in ledger-mode.") (defun fast-calc (currency) "Replace the arithmetic expression to the left of the point with its value. The arithmetic expression is defined as a simple regex match." (interactive "P") (when (looking-back "[-+/*().0-9]\\{3,\\} *" (line-beginning-position) t) (replace-match (save-match-data (calc-eval (if currency (list (concat (match-string-no-properties 0) "+0.0") 'calc-float-format '(fix 2)) (match-string-no-properties 0)))) t t) (if currency (insert fast-calc-suffix)))) (global-set-key (kbd "C-z c") #'fast-calc)
It looks a lot more complicated than it is (well, I could simplify it a bit by using a backquote instead of list
, but I do not like to use it outside macros), but in fact is quite straightforward. The only interesting thing is the +0.0
part; this is needed to convert an int (in case the result of computation is an integer) to a float. Notice the calc-eval
invocation, where I set the format to “fixed-point” with two decimal places.