2013-10-20 12:13:59 +02:00
|
|
|
;;; dklrt.el --- Ledger Recurring Transactions.
|
2013-10-20 12:09:44 +02:00
|
|
|
|
|
|
|
;; Copyright: (c) David Keegan 2011-2013.
|
|
|
|
;; Licence: FSF GPLv3.
|
|
|
|
;; Author: David Keegan <dksw@eircom.net>
|
2013-10-28 11:25:41 +01:00
|
|
|
;; Version: 1.0
|
|
|
|
;; Package-Requires: ((dkmisc "0.5") (ldg-mode "20130908.1357") (emacs "24.1"))
|
2013-10-20 12:09:44 +02:00
|
|
|
;; Keywords: ledger ledger-cli recurring periodic automatic
|
2013-10-20 12:13:59 +02:00
|
|
|
;; URL: https://github.com/davidkeegan/dklrt
|
2013-10-20 12:09:44 +02:00
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
2013-10-28 11:25:41 +01:00
|
|
|
;; An add-on to ledger-mode which appends recurring transactions to
|
2013-10-28 12:29:34 +01:00
|
|
|
;; the current ledger file, usually on entry to ledger-mode. Recurring
|
|
|
|
;; transactions are configured in a separate file which conforms to
|
|
|
|
;; ledger file format and resides in the same directory as the ledger
|
|
|
|
;; file.
|
2013-10-20 12:09:44 +02:00
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
2013-10-28 10:54:43 +01:00
|
|
|
(require 'dkmisc)
|
2013-10-28 11:25:41 +01:00
|
|
|
(require 'ledger "ledger-mode")
|
2013-10-28 10:54:43 +01:00
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defgroup dklrt nil
|
2013-10-28 12:29:34 +01:00
|
|
|
"Package dklrt (Ledger Recurring Transactions)."
|
2013-10-28 10:54:43 +01:00
|
|
|
:tag "dklrt"
|
|
|
|
:group 'dk)
|
|
|
|
|
|
|
|
(defcustom dklrt-SortAfterAppend nil
|
2013-10-28 11:25:41 +01:00
|
|
|
"Controls positioning of appended recurring transactions.
|
2013-10-28 10:54:43 +01:00
|
|
|
If non-nil, sort the ledger buffer after recurring transactions
|
|
|
|
have been appended. This ensures the recurring transactions are
|
|
|
|
positioned by date. Note: the positions of non-recurring
|
|
|
|
transactions will probably be affected."
|
|
|
|
:tag "dklrt-SortAfterAppend"
|
|
|
|
:type '(boolean))
|
|
|
|
|
|
|
|
(defcustom dklrt-PythonProgram "python"
|
2013-10-28 11:25:41 +01:00
|
|
|
"The Python interpreter to be run.
|
2013-10-28 10:54:43 +01:00
|
|
|
The default assumes python is on the PATH."
|
|
|
|
:tag "dklrt-PythonProgram"
|
|
|
|
:type '(string))
|
|
|
|
|
2013-10-28 20:42:33 +01:00
|
|
|
(defcustom dklrt-AppendBefore "1d"
|
2013-10-28 11:25:41 +01:00
|
|
|
"Controls when a recurring transaction is actually appended.
|
2013-10-28 10:54:43 +01:00
|
|
|
The value is a period do list format: <integer><y|m|d|w|h>. A
|
|
|
|
recurring transaction is appended when the current date/time is
|
|
|
|
greater than or equal to the configured transaction date minus
|
|
|
|
the specified period. If nil or empty, the recurring transaction
|
|
|
|
is appended without anticipation on or after the configured
|
|
|
|
transaction date."
|
|
|
|
:tag "dklrt-AppendBefore"
|
|
|
|
:type '(string))
|
|
|
|
|
|
|
|
(defcustom dklrt-RecurringConfigFileSuffix "rec"
|
2013-10-28 11:25:41 +01:00
|
|
|
"Suffix of Recurring Transactions Config File (excluding period)."
|
2013-10-28 10:54:43 +01:00
|
|
|
:tag "dklrt-RecurringConfigFileSuffix"
|
|
|
|
:type '(string))
|
2013-10-20 22:38:10 +02:00
|
|
|
|
2013-10-20 12:55:48 +02:00
|
|
|
(defconst dklrt-PackageDirectory
|
|
|
|
(if load-file-name
|
|
|
|
(file-name-directory load-file-name)
|
|
|
|
nil))
|
|
|
|
|
2013-10-28 10:54:43 +01:00
|
|
|
; Hard-coded alternative value for debug only.
|
2013-10-28 09:21:51 +01:00
|
|
|
(or dklrt-PackageDirectory
|
2013-10-28 20:42:33 +01:00
|
|
|
(setq dklrt-PackageDirectory (concat (getenv "rel"))))
|
2013-10-28 09:21:51 +01:00
|
|
|
|
2013-10-28 10:54:43 +01:00
|
|
|
;;;###autoload
|
2013-10-28 09:38:39 +01:00
|
|
|
(defun dklrt-SetCcKeys()
|
2013-10-28 11:25:41 +01:00
|
|
|
"Bind \C-cr to `dklrt-AppendRecurring'.
|
|
|
|
To invoke, add this function to `ledger-mode-hook'."
|
2013-10-28 09:38:39 +01:00
|
|
|
(define-key (current-local-map) "\C-cr" 'dklrt-AppendRecurring))
|
2013-10-28 09:21:51 +01:00
|
|
|
|
2013-10-28 10:54:43 +01:00
|
|
|
;;;###autoload
|
2013-10-28 09:21:51 +01:00
|
|
|
(defun dklrt-AppendRecurringMaybe()
|
2013-10-28 22:33:32 +01:00
|
|
|
"Call `dklrt-AppendRecurring', but only if appropriate."
|
2013-10-28 09:21:51 +01:00
|
|
|
(interactive)
|
|
|
|
(if (dklrt-AppendRecurringOk) (dklrt-AppendRecurring)))
|
2013-10-21 23:44:46 +02:00
|
|
|
|
2013-10-28 10:54:43 +01:00
|
|
|
;;;###autoload
|
2013-10-20 12:55:48 +02:00
|
|
|
(defun dklrt-AppendRecurring()
|
2013-10-28 11:25:41 +01:00
|
|
|
"Append recurring transactions to the current ledger buffer/file."
|
2013-10-20 12:55:48 +02:00
|
|
|
(interactive)
|
2013-10-20 22:38:10 +02:00
|
|
|
(dklrt-AppendRecurringOk t)
|
2013-10-20 12:55:48 +02:00
|
|
|
|
|
|
|
(message "Appending recurring transactions...")
|
|
|
|
(let*
|
|
|
|
((Lfn (buffer-file-name))
|
2013-10-20 22:38:10 +02:00
|
|
|
(Cfn (dklrt-RecurringConfigFileName Lfn))
|
2013-10-20 12:55:48 +02:00
|
|
|
(Pfn (expand-file-name "Recurring.py" dklrt-PackageDirectory))
|
2013-10-28 12:29:34 +01:00
|
|
|
(AppendBefore
|
|
|
|
(if (> (length dklrt-AppendBefore) 0) dklrt-AppendBefore "0h"))
|
2013-10-28 17:42:32 +01:00
|
|
|
(Td (dkmisc-TimeApplyShift (dkmisc-DateTimeToText) AppendBefore))
|
2013-10-28 12:29:34 +01:00
|
|
|
(Sc (format "\"%s\" \"%s\" \"%s\" \"%s\" \"%s\""
|
|
|
|
dklrt-PythonProgram Pfn Lfn Td Cfn)))
|
2013-10-21 23:44:46 +02:00
|
|
|
|
|
|
|
(message "Invoking: \"%s\"..." Sc)
|
|
|
|
(let*
|
|
|
|
((Fl (point-max))
|
|
|
|
(So (shell-command-to-string Sc)))
|
|
|
|
|
|
|
|
; Check for error.
|
|
|
|
(and (> (length So) 0) (error So))
|
|
|
|
|
|
|
|
; Sync buffer with (possibly) altered file.
|
|
|
|
; NB: Suppress mode reinitialisation to avoid infinite loop.
|
|
|
|
(revert-buffer t t t)
|
|
|
|
|
|
|
|
; Transactions were appended?
|
|
|
|
(if (> (point-max) Fl)
|
|
|
|
(progn
|
|
|
|
(if dklrt-SortAfterAppend
|
|
|
|
(progn
|
|
|
|
(message "Sorting buffer transactions by date...")
|
|
|
|
(ledger-sort-buffer)))
|
|
|
|
|
|
|
|
(message "Saving ledger buffer...")
|
|
|
|
(save-buffer))))))
|
2013-10-20 12:55:48 +02:00
|
|
|
|
2013-10-28 10:54:43 +01:00
|
|
|
;;;###autoload
|
2013-10-20 22:38:10 +02:00
|
|
|
(defun dklrt-AppendRecurringOk(&optional Throw)
|
2013-10-28 11:25:41 +01:00
|
|
|
"Return non nil if ok to append recurring transactions.
|
|
|
|
The current buffer must be unmodified, in `ledger-mode', and a
|
2013-10-20 22:38:10 +02:00
|
|
|
Recurring Transactions Config File must exist for the current
|
2013-10-28 11:25:41 +01:00
|
|
|
ledger file. If THROW, call error() instead of returning nil."
|
2013-10-20 22:38:10 +02:00
|
|
|
(and
|
|
|
|
(dklrt-IsLedgerMode Throw)
|
2013-10-21 23:44:46 +02:00
|
|
|
(dklrt-NotRecurringConfigFile Throw)
|
2013-10-20 22:38:10 +02:00
|
|
|
(dklrt-Unmodified Throw)
|
|
|
|
(dklrt-LedgerFileExists Throw)
|
|
|
|
(dklrt-RecurringConfigFileExists Throw)))
|
|
|
|
|
2013-10-28 10:54:43 +01:00
|
|
|
;;;###autoload
|
2013-10-20 22:38:10 +02:00
|
|
|
(defun dklrt-IsLedgerMode(&optional Throw)
|
2013-10-28 11:25:41 +01:00
|
|
|
"Return t if current buffer is a ledger buffer.
|
|
|
|
If THROW, call `error' instead of returning nil."
|
2013-10-20 12:55:48 +02:00
|
|
|
(let*
|
2013-10-20 22:38:10 +02:00
|
|
|
((Rv (equal mode-name "Ledger")))
|
|
|
|
(and (not Rv) Throw
|
|
|
|
(error "Current buffer is not in ledger mode!"))
|
|
|
|
Rv))
|
|
|
|
|
2013-10-21 23:44:46 +02:00
|
|
|
(defun dklrt-NotRecurringConfigFile(&optional Throw)
|
2013-10-28 11:25:41 +01:00
|
|
|
"Return t if current buffer is not a Recurring Config File.
|
|
|
|
If THROW, call `error' instead of returning nil."
|
2013-10-21 23:44:46 +02:00
|
|
|
(let*
|
|
|
|
((Fne (file-name-extension (buffer-file-name)))
|
|
|
|
(Rv (not (string= Fne dklrt-RecurringConfigFileSuffix))))
|
|
|
|
|
|
|
|
(and (not Rv) Throw
|
|
|
|
(error "Cannot append recurring transactions to Config File!"))
|
|
|
|
Rv))
|
|
|
|
|
2013-10-20 22:38:10 +02:00
|
|
|
(defun dklrt-Unmodified(&optional Throw)
|
2013-10-28 11:25:41 +01:00
|
|
|
"Return t if current buffer is unmodified.
|
|
|
|
If THROW, call `error' instead of returning nil."
|
2013-10-20 22:38:10 +02:00
|
|
|
(let*
|
|
|
|
((Rv (not (buffer-modified-p))))
|
2013-10-20 12:55:48 +02:00
|
|
|
(and (not Rv) Throw
|
2013-10-20 22:38:10 +02:00
|
|
|
(error "Current buffer has changed! Please save it first!"))
|
2013-10-20 12:55:48 +02:00
|
|
|
Rv))
|
|
|
|
|
2013-10-20 22:38:10 +02:00
|
|
|
(defun dklrt-LedgerFileExists(&optional Throw)
|
2013-10-28 11:25:41 +01:00
|
|
|
"Return t if the ledger file exists.
|
|
|
|
If THROW, call `error' instead of returning nil."
|
2013-10-20 22:38:10 +02:00
|
|
|
(let*
|
|
|
|
((Lfn (buffer-file-name))
|
|
|
|
(Rv (and Lfn (file-exists-p Lfn))))
|
|
|
|
(and (not Rv) Throw
|
|
|
|
(error "No such Ledger File: \"%s\"!" Lfn))
|
|
|
|
Rv))
|
|
|
|
|
|
|
|
(defun dklrt-RecurringConfigFileExists(&optional Throw)
|
2013-10-28 11:25:41 +01:00
|
|
|
"Return t if the Recurring Config File exists.
|
|
|
|
If THROW, call `error' instead of returning nil."
|
2013-10-20 22:38:10 +02:00
|
|
|
(let*
|
|
|
|
((Lfn (buffer-file-name))
|
|
|
|
(Cfn (dklrt-RecurringConfigFileName Lfn))
|
|
|
|
(Rv (and Cfn (file-exists-p Cfn))))
|
|
|
|
(and (not Rv) Throw
|
|
|
|
(error "No such Recurring Config File: \"%s\"!" Cfn))
|
|
|
|
Rv))
|
|
|
|
|
|
|
|
(defun dklrt-RecurringConfigFileName(LedgerFileName)
|
2013-10-28 11:25:41 +01:00
|
|
|
"Return the corresponding recurring configuration file name.
|
|
|
|
Remove the suffix (if any) from LEDGERFILENAME and then append
|
|
|
|
the recurring suffix as configured via
|
|
|
|
`dklrt-RecurringConfigFileSuffix'."
|
2013-10-20 22:38:10 +02:00
|
|
|
(concat (file-name-sans-extension LedgerFileName) "."
|
|
|
|
dklrt-RecurringConfigFileSuffix))
|
|
|
|
|
2013-10-20 12:13:59 +02:00
|
|
|
(provide 'dklrt)
|