org-mode and ledger cli accounting

424 views
Skip to first unread message

Ben Alexander

unread,
Dec 17, 2008, 5:29:40 PM12/17/08
to ledge...@googlegroups.com, emacs-orgmode Org-Mode
*** org-mode and ledger cli accounting - two great tastes that taste
great together
:PROPERTIES:
:COLUMNS: %16LEDGER_DATE %5LEDGER_REFERENCE %10ITEM
%25LEDGER_ACCOUNT %10AMOUNT
:END:

***** TODO Email the org-mode and ledger mailing lists

I'm starting to use 'ledger' (http://www.newartisans.com/software/ledger.html
). There is an emacs mode for editing the native plain text file
format, but I really like the org-mode UI. Date handling is great
(imho) and column mode holds some promise (although I find it awkward
to add a new headline while in column mode). And remember templates
could ease data entry, too

I think it would be great to use 'ledger' backend computation and org-
mode's UI together!

After a quick exchange of emails where I asked about the future
plans of 'ledger' I was told that a new file format for 'ledger' could
be added, if the org-mode community decided on a common format.

I'd like to suggest a format, get some feedback on it, and get
a sense of how many others are interested. I'm hoping to interest org-
mode users in ledger, and ledger users in org-mode!

As a temporary measure, I've used the org-map-entries API to
iterate over entries looking for properties of the form "LEDGER_.*" to
extract the appropriate details, format them like the current plain
text 'ledger' file.

Since there's a lot about 'ledger' I don't know (as well as
elisp, org-mode, git, programming in general, accounting) I'd expect
that my code will be ineffecient, inelegant, and dangerous. It comes
with no warrenty. If you have some programming skills, I'd appreciate
feedback on style, design, tests and documentation.

For example, I'd like to use a :LEDGER: drawer instead of
polluting the :PROPERTIES: namespace, but then I'd miss out on the
easy org-entry-get API. Any suggestions?

Here's an example tree, with a column-mode definition above, and a
single minimal entry below. Here's how to use it at the moment
(caveat, I wrote this assuming you already have ledger (the shell
program) downloaded and ledger.el in your loaded into emacs.

***** How to use the sample code and data
1. In the file bens-org-ledger.el, modify the variable bens-org-ledger-
file-name to point to a file you don't mind overwriting without
warning. (Did I mention that my code might be dangerous?)
2. Load bens-org-ledger.el
3. Run the defun (bens-org-ledger) while the current buffer contains
this org-tree
4. Now (find-file bens-org-ledger-file-name). In my setup, the
extension automaticaly loads ledger.el and the file is in ledger-mode.
5. C-c C-o C-r reg <RET> in a ledger-mode buffer (visting a native
ledger file) will run a basic register report. Try C-c C-o C-r bal
<RET> for a basic balance report.


***** Wegman's
:PROPERTIES:
:LEDGER: entry
:LEDGER_DATE: [2008-11-07 Fri]
:LEDGER_REFERENCE: chq 1001
:END:
*******
:PROPERTIES:
:LEDGER: transaction
:LEDGER_ACCOUNT: Expenses:Food and Drink:Groceries
:LEDGER_AMOUNT: -£10.00
:END:
Since all text except the headline and specific properties is
ignored, I can comment my transactions with body text!
******* Assets:Checking
:PROPERTIES:
:LEDGER: transaction
:LEDGER_DATE: [2008-11-13 Thu]
:END:

Instead of using the :LEDGER_ACCOUNT: property, I can use the
headline. Properties come with completion (big win!) but
headlines are easier to see outside of column mode.

Also, in this case, the :LEDGER_DATE: property is ignored.
Maybe it should become the 'effective date' in the future?


*** This is my lisp code, from a file named bens-org-ledger.el
It starts from the comments below to the end of the message. I'm
sorry to be wordy but I thought having something that could be used
(even awkwardly) would jump start the conversation

;; I'm naming every function I can with the bens-org-ledger prefix,
;; because these are temporary, pre-alpha attempts, intended to
;; elict comments from real programmers
;;
;; This code is copyright Ben Alexander
;; You have license to use, copy, and create derivative works
;; under the terms of the General Public License (version 2 or later).
;;
(defvar bens-org-ledger-file-name "bens-org-ledger-sample.ledger"
"This variable is the name of a file that will be overwritten during
the processing of an org-mode file. OVERWRITTEN WITHOUT WARNING!") ;;
except for this warning here


(defun bens-org-ledger-create-entry ()
"this function retreives the LEDGER_DATE, LEDGER_REFERENCE (if it
exists), and the LEDGER_DESCRIPTION properites of the current
headline and formats them as the beginning of a new ledger entry"

(concat
"\n" ;; a blank line separates ledger entries
(format-time-string "%Y/%m/%d"
(org-time-string-to-time (org-entry-get (point)
"LEDGER_DATE") ) t)
;; Wouldn't it be nice if the timestamps repeat intervals were
converted
;; to appropriate ledger syntax here? Does org-mode have some
timestamp
;; property retrievals I could use?
;;
;; Also, I need to add support for "effective dates"

(if (> (length (org-entry-get (point) "LEDGER_REFERENCE")) 0)
(concat " (" (org-entry-get (point) "LEDGER_REFERENCE") ") ")
" ")
(or (org-entry-get (point) "LEDGER_DESCRIPTION") (org-get-
heading))
"\n" ;; I assume the cursor starts on a newline -- make it so!
)
)

(defun bens-org-ledger-create-transaction ()
"this function retreives the LEDGER_ACCOUNT (or headline),
LEDGER_AMOUNT
and formats them as a continuation of the existing ledger entry"

(concat
" " ;; ledger transactions must begin with whitespace
(or (org-entry-get (point) "LEDGER_ACCOUNT")
(org-get-heading))
" " ;; two spaces separate account name from amount
(org-entry-get (point) "LEDGER_AMOUNT")
"\n" ;; I assume the cursor starts on a newline -- make it so!
)
)


(defun bens-org-ledger-map-entries-helper ()
"this function passed to org-map-entries to format headlines to
ledger style
plain text. It chooses bens-org-ledger-create-entry or
bens-org-ledger-create-transation based on the LEDGER property.
Those functions
return the actual data that org-map-entries collects"
(cond ((string= (org-entry-get (point) "LEDGER") "entry") (bens-org-
ledger-create-entry))
((string= (org-entry-get (point) "LEDGER") "transaction") (bens-
org-ledger-create-transaction))
))


(defun bens-org-ledger ()
"this function generates a properly formatted ledger file from the
org-mode headlines based on properties stored in the org-mode property
drawer. The file refered to by bens-org-ledger-file-name is
OVERWRITTEN WITHOUT WARNING. (you have been warned, again)

The properties used are of the form LEDGER_.*

:LEDGER: entry|transaction => entries (headings) contain transactions
(sub-headings)
For ledger entries, the following properties are used
:LEDGER: entry => Means this is the beginning of a new ledger
entry.
Entries (in the parlance of ledger) may
contain
multiple transactions

:LEDGER_DATE: <2009-01-01 Thu> => An org-mode timestamp.

:LEDGER_REFERENCE: chq 101 => There's a place for this in the ledger
syntax.
Any string is legal.

:LEDGER_DESCRIPTION: => if this is missing, the org-mode headline is
used

For ledger transactions, the follwoing properties are used

:LEDGER: transaction => Means this headline is a transaction.
It should be a sub-heading of some 'entry'

:LEDGER_ACCOUNT: => a colon:delimited:account:as:used:by:ledger
:LEDGER_AMOUNT: £10 => Any legal amount for ledger.
Any currency symbol or code is considered
legal, e.g.
:LEDGER_AMOUNT: 10 GBP
:LEDGER_AMOUNT: $10
:LEDGER_AMOUNT: 10 STICKS_OR_STONES
"

(interactive)
(let ((ledger-text (org-map-entries 'bens-org-ledger-map-entries-
helper "+LEDGER={transaction\\\|entry}")))
(with-temp-file bens-org-ledger-file-name (dolist (str ledger-text)
(insert str)))
))

Reply all
Reply to author
Forward
0 new messages