;; emoney.el --- A home finance package.
-;; Copyright (C) 2003 - 2011 Steve Youngs
+;; Copyright (C) 2003 - 2017 Steve Youngs
;; Author: Steve Youngs <steve@sxemacs.org>
;; Maintainer: Steve Youngs <steve@sxemacs.org>
;; <bnewell@alum.mit.edu>.
;;
;; eMoney tries to give the user a reasonably self-contained finance
-;; solution. The target audience is the home user and possibly the
-;; small (very small) business operator. It probably will never be
-;; compatible with any commercial finance packages available.
+;; solution. The target audience is the home user. It probably
+;; will never be compatible with any commercial finance packages
+;; available.
;;
;; Right now, eMoney is nothing more than a very simple cash book
-;; program. Use it to balance your cheque book. See the "Todo:"
-;; section for a partial list of possible upcoming features.
+;; program. Use it to balance your cheque book. See the TODO file
+;; for a partial list of possible upcoming features.
;;
;; Installation (from source):
;;
;; `.emy' extension.
;;
;; Please note that I use SXEmacs exclusively, I have no idea whether
-;; or not this will run or even byte-compile with GNU/Emacs (it _will_
+;; or not this will run or even byte-compile with GNU/Emacs (it _should_
;; work fine with XEmacs). Also, I have no desire or intentions of
;; making this package portable between XEmacs and GNU/Emacs. Harsh
;; words? No, not really, I simply don't have the time or resources to
;;
;; Another note: This package uses correct English spelling wherever
;; possible. For example, "cheque" instead of "check", "summarise"
-;; instead of "summarize". Although I welcome and encourage _ALL_
-;; contributions, patches that s/que/ch/ or s/ise/ize/ will go straight
-;; to /dev/null.
+;; instead of "summarize".
;;; ChangeLog:
;;
;; This is just a place holder so `emoney-commentary' will work
-;; properly. See the ChangeLog file for changes.
+;; properly. See the ChangeLog files in ChangeLog.d and git log for
+;; changes.
;;; Code:
+;; A little compatibility insurance
+(globally-declare-fboundp
+ '(mapfam dllist-car dllist-lrotate dllist-rrotate))
+
+(defvar emoney-emacs-is-sexy-enough (and (featurep 'dllist)
+ (fboundp #'mapfam))
+ "Non-nil when emacs has some needed extra features.
+
+Currently, that is support for dllists and #'mapfam. Don't worry, if
+you're not sexy enough you can still use eMoney, you just won't be
+able to \"walk\" through accounts, but you can still switch to any
+account directly.
+
+See: `emoney-walk-accounts'.")
;; Drag in what we need.
(eval-and-compile
:prefix "emoney-"
:group 'tools)
-(defcustom emoney-accounts-buffer-width 30
+(defcustom emoney-accounts-buffer-width 35
"How wide in columns for the accounts buffer."
:type 'integer
:group 'emoney)
(defcustom emoney-credit-transaction-types
'("autotellcr" "atmcr" "bankcredit" "bcr" "deposit" "dep" "directcr"
- "dcr" "internetcr" "netcr" "phonecr" "phcr")
+ "dcr" "gencr" "internetcr" "netcr" "phonecr" "phcr")
"*List of valid credit transaction types.
The default types are:
directcr -- Direct credit transactions
dcr -- short version of 'directcr'
+ gencr -- General credit for things that don't fit like
+ reversals etc
+
internetcr -- Internet credit transactions
netcr -- short version of 'internetcr'
:group 'emoney)
(defcustom emoney-debit-transaction-types
- '("autotelldb" "atmdb" "bankfee" "fee" "directdb" "ddb" "eftpos"
- "eft" "internetdb" "netdb" "phonedb" "phdb" "withdrawal" "wdl")
+ '("autotelldb" "atmdb" "bankfee" "fee" "bpay" "cc" "directdb" "ddb"
+ "eftpos" "eft" "gendb" "internetdb" "netdb" "paypal" "phonedb"
+ "phdb" "venddb" "withdrawal" "wdl")
"*List of valid debit transaction types.
The default types are:
bankfee -- Bank fees
fee -- short version of 'bankfee'
+ bpay -- BillPayments
+
+ cc -- Credit card
+
directdb -- Direct debit transactions
ddb -- short version of 'directdb'
eftpos -- Electronic Funds Transfer Point Of Sale
eft -- short version of 'eftpos'
+ gendb -- For general debits that don't fit other types
+ like reversals.
+
internetdb -- Internet transactions
netdb -- short version of 'internet'
+ paypal -- PayPal transactions
+
phonedb -- Telephone transactions
phcr -- short version of 'phone'
+ venddb -- Vending machine transactions
+
withdrawal -- Over the counter withdrawal.
wdl -- short version of 'withdrawal'"
:type '(repeat string)
(defun emoney-version (&optional arg)
"*Display the current version information for eMoney.
-Optional prefix Argument ARG, print version information at point
+Prefix Argument ARG, print version information at point
in the current buffer."
(interactive "P")
- (if emoney-is-beta
- (if arg
- (insert (format "eMoney: %s, \"%s\" [Beta]"
- emoney-version emoney-codename))
- (message (format "eMoney: %s, \"%s\" [Beta]"
- emoney-version emoney-codename)))
+ (let ((fmt-string "eMoney: %s, \"%s\""))
+ (when emoney-is-beta
+ (setq fmt-string (concat fmt-string " [Beta]")))
(if arg
- (insert (format "eMoney: %s, \"%s\""
- emoney-version emoney-codename))
- (message (format "eMoney: %s, \"%s\""
- emoney-version emoney-codename)))))
+ (insert (format fmt-string emoney-version emoney-codename))
+ (message fmt-string emoney-version emoney-codename))))
+
;;;###autoload
(defun emoney-commentary ()
(defvar emoney-current-account-name
(file-name-sans-extension emoney-default-account))
-(defun emoney-switch-to-account (&optional account)
+(defun emoney-switch-to-account (account)
"Switch to account, ACCOUNT."
- (interactive)
- (let ((switch-acc (or account
- (emoney-completing-read "Switch to A/C: "
- emoney-chart-of-accounts nil t))))
- (select-window (get-buffer-window
- (concat emoney-current-account-name ".emy")))
- (switch-to-buffer switch-acc)
- (goto-char (point-max))
- (setq emoney-current-account-name
- (file-name-sans-extension switch-acc))
- (select-window (get-buffer-window emoney-header-buffer))
- (emoney-setup-header-buffer)
- (switch-to-buffer emoney-header-buffer)
- (select-window (get-buffer-window
- (concat emoney-current-account-name ".emy")))
- (run-hooks 'emoney-switch-account-hook)))
+ (interactive
+ (list (emoney-completing-read "Switch to A/C: "
+ emoney-chart-of-accounts nil t)))
+ (select-window (get-buffer-window
+ (concat emoney-current-account-name ".emy")))
+ (switch-to-buffer account)
+ (goto-char (point-max))
+ (setq emoney-current-account-name
+ (file-name-sans-extension account))
+ (select-window (get-buffer-window emoney-header-buffer))
+ (emoney-setup-header-buffer)
+ (switch-to-buffer emoney-header-buffer)
+ (select-window (get-buffer-window
+ (concat emoney-current-account-name ".emy")))
+ (run-hooks 'emoney-switch-account-hook))
(defun emoney-mouse-switch-to-account (event)
"Switch to account under EVENT."
(concat emoney-current-account-name ".emy")))
(run-hooks 'emoney-switch-account-hook))
+(defun emoney-goto-default-account ()
+ "Switch to `emoney-default-account'."
+ (interactive)
+ (emoney-switch-to-account emoney-default-account))
+
+;:*=======================
+;:* Walk accounts
+(defun emoney-walk-accounts (direction)
+ "Move to account in direction, DIRECTION."
+ (let ((dl-acc-list (mapfam #'identity
+ emoney-chart-of-accounts
+ :result-type 'dllist)))
+ ;; Line up the dllist's car with the current account
+ (while (not (equal (dllist-car dl-acc-list)
+ (concat emoney-current-account-name ".emy")))
+ (dllist-rrotate dl-acc-list))
+ (cond
+ ((eq direction 'next)
+ (progn
+ (dllist-lrotate dl-acc-list)
+ (emoney-switch-to-account (dllist-car dl-acc-list))))
+ ((eq direction 'previous)
+ (progn
+ (dllist-rrotate dl-acc-list)
+ (emoney-switch-to-account (dllist-car dl-acc-list))))
+ (t (error 'invalid-argument)))))
+
+(defun emoney-walk-accounts-next ()
+ "Switch to the 'next' account in the chart of accounts."
+ (interactive)
+ (and emoney-emacs-is-sexy-enough
+ (emoney-walk-accounts 'next)))
+
+
+(defun emoney-walk-accounts-previous ()
+ "Switch to the 'previous' account in the chart of accounts."
+ (interactive)
+ (and emoney-emacs-is-sexy-enough
+ (emoney-walk-accounts 'previous)))
+
(defconst emoney-accounts-buffer-map
(let* ((map (make-sparse-keymap 'emoney-accounts-buffer-map)))
(define-key map [button2] #'emoney-mouse-switch-to-account)
(re-search-backward "\\s-[\\+-=]\\s-" nil t)
(setq cbal (nth 2 (emoney-parse-transaction-data
(buffer-substring (point) (point-at-eol))))))
- (move-to-column 13 'force)
+ (move-to-column 18 'force)
(if cbal
(insert-face
(format
(setq accounts (cdr accounts)))
(set-specifier horizontal-scrollbar-visible-p nil (current-buffer))
(set-specifier has-modeline-p nil (current-buffer))
+ (goto-char (point-min))
(run-hooks 'emoney-setup-accounts-buffer-hook))))
(defconst emoney-header
(when (buffer-live-p (get-buffer-create buf))
(kill-buffer buf))
(set-buffer (get-buffer-create buf))
- (insert-face emoney-current-account-name 'emoney-account-name-face)
+ (center-line
+ (insert-face (upcase emoney-current-account-name)
+ 'emoney-header-face))
(insert "\n\n")
(insert-face emoney-header 'emoney-header-face)
(set-specifier horizontal-scrollbar-visible-p nil (current-buffer))
(define-key map [(control c) (control t)] #'emoney-append-transaction)
(define-key map [(control c) (control x)] #'emoney-transfer-funds)
(define-key map [tab] #'emoney-forward-field)
+ (define-key map [iso-left-tab] #'emoney-backward-field)
(define-key map [(control c) b] #'emoney-go-to-bank)
(define-key map [(control c) c] #'emoney-calc)
(define-key map [(control c) s] #'emoney-switch-to-account)
+ (define-key map [(control c) d] #'emoney-goto-default-account)
(define-key map [(control c) q] #'emoney-quit)
(define-key map [(control c) (control q)] #'emoney-recalc-and-exit)
+ (when emoney-emacs-is-sexy-enough
+ (define-key map [(meta n)] #'emoney-walk-accounts-next)
+ (define-key map [(meta p)] #'emoney-walk-accounts-previous))
map)
"Keymap for emoney buffer.")
(interactive)
(move-to-tab-stop))
-(defun emoney-go-to-bank (&optional url)
+(defun emoney-go-to-bank ()
"Open your bank's URL with `browse-url'."
(interactive)
- (let ((url (or url
- emoney-bank-url)))
- (if (or (equal url "unset")
- (not url))
- (message-or-box "Please customise `emoney-bank-url'.")
- (browse-url url))))
+ (if (or (equal emoney-bank-url "unset")
+ (not emoney-bank-url))
+ (message-or-box "Please customise `emoney-bank-url'.")
+ (browse-url emoney-bank-url)))
(defun emoney-calc ()
"Wrapper around `calc' to get around \"window-edges bug\"."
(defvar emoney-transfer-account-history nil)
-(defun emoney-transfer-funds (&optional from to amount)
+(defun emoney-transfer-funds (from to amount)
"Transfer funds from one eMoney account to another.
Argument FROM is the account to transfer from.
Argument To is the account to transfer to.
Argument AMOUNT is how much to transfer."
- (interactive)
- (let ((from (or from
- (emoney-completing-read "Transfer from: "
- emoney-chart-of-accounts
- nil
- t
- (concat emoney-current-account-name
- ".emy")
- emoney-transfer-account-history)))
- (to (or to
- (emoney-completing-read "Transfer to: "
- emoney-chart-of-accounts nil t nil
- emoney-transfer-account-history)))
- (amount (or amount
- (read-number "Amount: ")))
- (current-ac (concat emoney-current-account-name ".emy")))
+ (interactive
+ (list (emoney-completing-read "Transfer from: "
+ emoney-chart-of-accounts nil t
+ (concat emoney-current-account-name
+ ".emy")
+ emoney-transfer-account-history)
+ (emoney-completing-read "Transfer to: "
+ emoney-chart-of-accounts nil t nil
+ emoney-transfer-account-history)
+ (read-number "Amount: ")))
+ (let ((current-ac (concat emoney-current-account-name ".emy")))
(with-current-buffer from
(emoney-append-transaction
emoney-db-transfer-transaction-type
(goto-char (point-min))
(search-forward account)
(kill-line)
- (move-to-column 13 'force)
+ (move-to-column 18 'force)
(insert-face
(format (concat "%"
(number-to-string (- (length emoney-largest-balance)
(emoney-summarise-cheques start end)
(emoney-recalculate (point-min) (point-max)))
-(defun emoney-new-account (&optional name balance)
- "*Create a new A/C named NAME for use with eMoney."
- (interactive)
- (let ((new-acc (concat (or name
- (read-string "New A/C Name: ")) ".emy"))
- (bal (or balance
- (read-number "Initial Balance: " nil 0))))
- (find-file-noselect
- (expand-file-name new-acc emoney-accounts-directory))
- (setq emoney-chart-of-accounts
- (push new-acc emoney-chart-of-accounts))
- (select-window (get-buffer-window
- (concat emoney-current-account-name ".emy")))
- (switch-to-buffer new-acc)
- (setq emoney-current-account-name
- (file-name-sans-extension new-acc))
- (emoney-append-transaction "init" "Opening Balance" bal)
- (emoney-recalculate-buffer)
- (emoney-show-buffers)
- (switch-to-buffer new-acc)
- (goto-char (point-max))
- (run-hooks 'emoney-new-account-hook)))
+(defun emoney-new-account (new-acc bal)
+ "*Create a new A/C named NEW-ACC with initial balance BAL."
+ (interactive
+ (list (concat (read-string "New A/C Name: ") ".emy")
+ (read-number "Initial Balance: " nil 0)))
+ (find-file-noselect
+ (expand-file-name new-acc emoney-accounts-directory))
+ (setq emoney-chart-of-accounts
+ (push new-acc emoney-chart-of-accounts))
+ (select-window (get-buffer-window
+ (concat emoney-current-account-name ".emy")))
+ (switch-to-buffer new-acc)
+ (setq emoney-current-account-name
+ (file-name-sans-extension new-acc))
+ (emoney-append-transaction "init" "Opening Balance" bal)
+ (emoney-recalculate-buffer)
+ (emoney-show-buffers)
+ (switch-to-buffer new-acc)
+ (goto-char (point-max))
+ (run-hooks 'emoney-new-account-hook))
(defun emoney-setup-control-buffer ()
"Set up the eMoney \"Control\" buffer."
(set-buffer (get-buffer-create buf))
(widget-create 'push-button
:notify (lambda (&rest ignore)
- (emoney-new-account))
+ (call-interactively 'emoney-new-account))
:help-echo "Create a new eMoney account."
" New A/C ")
(widget-insert " ")
(save-excursion
(set-buffer
(concat emoney-current-account-name ".emy"))
- (emoney-transfer-funds)))
+ (call-interactively 'emoney-transfer-funds)))
:help-echo "Transfer funds between accounts."
"Transfer ")
(widget-insert " ")
(unless (frame-live-p emoney-frame)
(setq emoney-frame
(if emoney-use-new-frame
- (make-frame '((name . "eMoney")))
+ ;; FIXME: make the frame props customisable
+ (make-frame '((name . "eMoney")
+ (height . 40)
+ (width . 80)))
(selected-frame))))
(select-frame emoney-frame)
(let ((accounts emoney-chart-of-accounts)