Merged in bug fixes from master (done for 2.6.1b)

This commit is contained in:
John Wiegley 2008-07-19 21:36:34 -04:00
commit d568319495
17 changed files with 783 additions and 206 deletions

1
.gitignore vendored
View file

@ -14,6 +14,7 @@
/COPYING
/Makefile
/Makefile.in
/TAGS
/acconf.h
/acconf.h.in
/acconf.h.in~

View file

@ -67,7 +67,7 @@ if HAVE_LIBOFX
libledger_la_SOURCES += ofx.cc
endif
libledger_la_LDFLAGS = -release 2.6.0.90
libledger_la_LDFLAGS = -release 2.7.0
pkginclude_HEADERS = \
acconf.h \

8
NEWS
View file

@ -1,5 +1,9 @@
Ledger NEWS
* 2.6.1
- This version has no new features, it's all bug fixes.
* 2.6.0.90
- Gnucash parser is fixed.
@ -197,9 +201,9 @@
register, print, etc), you can now descend into the component
transactions that made up any of the values you see.
For example, say you request a --monthtly expenses report:
For example, say you request a --monthly expenses report:
$ ledger --monthly --descend "\$500.00" register ^Expenses
$ ledger --monthly register ^Expenses
Now, in one of the reported months you see $500.00 spent on
Expenses:Food. You can ask Ledger to "descend" into, and show the

View file

@ -977,7 +977,7 @@ void amount_t::parse(std::istream& in, flags_t flags)
// Set the commodity's flags and precision accordingly
if (commodity_ && (newly_created || ! (flags & AMOUNT_PARSE_NO_MIGRATE))) {
if (commodity_ && ! (flags & AMOUNT_PARSE_NO_MIGRATE)) {
commodity().add_flags(comm_flags);
if (quantity->prec > commodity().precision())

View file

@ -112,6 +112,8 @@ entry_t * derive_new_entry(journal_t& journal,
}
}
else {
account_t * draw_acct = NULL;
while (i != end) {
string& re_pat(*i++);
account_t * acct = NULL;
@ -135,11 +137,6 @@ entry_t * derive_new_entry(journal_t& journal,
}
found:
if (! acct)
acct = journal.find_account_re(re_pat);
if (! acct)
acct = journal.find_account(re_pat);
transaction_t * xact;
if (i == end) {
if (amt)
@ -147,7 +144,22 @@ entry_t * derive_new_entry(journal_t& journal,
else
xact = new transaction_t(acct);
} else {
xact = new transaction_t(acct, amount_t(*i++));
amount_t amount(*i++);
strings_list::iterator x = i;
if (i != end && ++x == end) {
draw_acct = journal.find_account_re(*i);
if (! draw_acct)
draw_acct = journal.find_account(*i);
i++;
}
if (! acct)
acct = journal.find_account_re(re_pat);
if (! acct)
acct = journal.find_account(re_pat);
xact = new transaction_t(acct, amount);
if (! xact->amount.commodity()) {
if (amt)
xact->amount.set_commodity(amt->commodity());
@ -158,8 +170,11 @@ entry_t * derive_new_entry(journal_t& journal,
added->add_transaction(xact);
}
assert(matching->transactions.back()->account);
if (account_t * draw_acct = matching->transactions.back()->account)
if (! draw_acct) {
assert(matching->transactions.back()->account);
draw_acct = matching->transactions.back()->account;
}
if (draw_acct)
added->add_transaction(new transaction_t(draw_acct));
}

View file

@ -13,10 +13,11 @@ void format_emacs_transactions::write_entry(entry_t& entry)
break;
}
out << (((unsigned long)entry.beg_pos) + 1) << " ";
out << (((unsigned long)entry.beg_line) + 1) << " ";
tm when = boost::posix_time::to_tm(entry.date());
std::time_t date = std::mktime(&when); // jww (2008-04-20): Is this GMT or local?
out << "(" << (date / 65536) << " " << (date % 65536) << " 0) ";
if (entry.code.empty())
@ -48,7 +49,7 @@ void format_emacs_transactions::operator()(transaction_t& xact)
out << "\n";
}
out << " (" << (((unsigned long)xact.beg_pos) + 1) << " ";
out << " (" << (((unsigned long)xact.beg_line) + 1) << " ";
out << "\"" << xact_account(xact)->fullname() << "\" \""
<< xact.amount << "\"";

View file

@ -162,13 +162,9 @@ static void endElement(void *userData, const char *name)
// the transaction is worth.
amount_t value;
commodity_t * default_commodity = NULL;
if (entry_comm) {
default_commodity = entry_comm;
} else {
account_comm_map::iterator ac = account_comms.find(xact->account);
if (ac != account_comms.end())
default_commodity = (*ac).second;
}
account_comm_map::iterator ac = account_comms.find(xact->account);
if (ac != account_comms.end())
default_commodity = (*ac).second;
if (default_commodity) {
curr_quant.set_commodity(*default_commodity);

View file

@ -164,28 +164,30 @@ bool entry_base_t::finalize()
if (this_amt == other_amt)
other_amt++;
amount_t per_unit_cost =
amount_t((*other_amt).second / (*this_amt).second).unround();
if (this_amt != bal.amounts.end()) {
amount_t per_unit_cost =
amount_t((*other_amt).second / (*this_amt).second).unround();
for (; x != transactions.end(); x++) {
if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL) ||
! (*x)->amount || (*x)->amount.commodity() != this_comm)
continue;
for (; x != transactions.end(); x++) {
if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL) ||
! (*x)->amount || (*x)->amount.commodity() != this_comm)
continue;
assert((*x)->amount);
balance -= (*x)->amount;
assert((*x)->amount);
balance -= (*x)->amount;
entry_t * entry = dynamic_cast<entry_t *>(this);
entry_t * entry = dynamic_cast<entry_t *>(this);
if ((*x)->amount.commodity() &&
! (*x)->amount.commodity().annotated)
(*x)->amount.annotate_commodity
(annotation_t(per_unit_cost.abs(),
entry ? optional<datetime_t>(entry->actual_date()) : none,
entry ? optional<string>(entry->code) : none));
if ((*x)->amount.commodity() &&
! (*x)->amount.commodity().annotated)
(*x)->amount.annotate_commodity
(annotation_t(per_unit_cost.abs(),
entry ? optional<datetime_t>(entry->actual_date()) : none,
entry ? optional<string>(entry->code) : none));
(*x)->cost = new amount_t(- (per_unit_cost * (*x)->amount));
balance += *(*x)->cost;
(*x)->cost = new amount_t(- (per_unit_cost * (*x)->amount));
balance += *(*x)->cost;
}
}
}
@ -264,6 +266,8 @@ bool entry_base_t::finalize()
error * err =
new balance_error("Entry does not balance",
new entry_context(*this, "While balancing entry:"));
DEBUG("ledger.journal.unbalanced_remainder", "balance = " << balance);
balance.round();
err->context.push_front
(new value_context(balance, "Unbalanced remainder is:"));
throw err;

660
ledger.el
View file

@ -1,17 +1,17 @@
;;; ledger.el --- Helper code for use with the "ledger" command-line tool
;; Copyright (C) 2004 John Wiegley (johnw AT gnu DOT org)
;; Copyright (C) 2008 John Wiegley (johnw AT gnu DOT org)
;; Emacs Lisp Archive Entry
;; Filename: ledger.el
;; Version: 1.2
;; Date: Thu 02-Apr-2004
;; Version: 2.7
;; Date: Thu 18-Jul-2008
;; Keywords: data
;; Author: John Wiegley (johnw AT gnu DOT org)
;; Maintainer: John Wiegley (johnw AT gnu DOT org)
;; Description: Helper code for using my "ledger" command-line tool
;; URL: http://www.newartisans.com/johnw/emacs.html
;; Compatibility: Emacs21
;; Compatibility: Emacs22
;; This file is not part of GNU Emacs.
@ -35,10 +35,11 @@
;; To use this module: Load this file, open a ledger data file, and
;; type M-x ledger-mode. Once this is done, you can type:
;;
;; C-c C-a add a new entry, based on previous entries
;; C-c C-y set default year for entry mode
;; C-c C-m set default month for entry mode
;; C-c C-r reconcile uncleared entries related to an account
;; C-c C-a add a new entry, based on previous entries
;; C-c C-e toggle cleared status of an entry
;; C-c C-y set default year for entry mode
;; C-c C-m set default month for entry mode
;; C-c C-r reconcile uncleared entries related to an account
;; C-c C-o C-r run a ledger report
;; C-C C-o C-g goto the ledger report buffer
;; C-c C-o C-e edit the defined ledger reports
@ -71,6 +72,7 @@
(require 'esh-util)
(require 'esh-arg)
(require 'pcomplete)
(defvar ledger-version "1.2"
"The version of ledger.el currently loaded")
@ -79,7 +81,7 @@
"Interface to the Ledger command-line accounting program."
:group 'data)
(defcustom ledger-binary-path (executable-find "ledger")
(defcustom ledger-binary-path "ledger"
"Path to the ledger executable."
:type 'file
:group 'ledger)
@ -90,19 +92,47 @@
:group 'ledger)
(defcustom ledger-reports
'(("bal" "ledger bal")
("reg" "ledger reg"))
"Definition of reports to run.
'(("bal" "ledger -f %(ledger-file) bal")
("reg" "ledger -f %(ledger-file) reg")
("payee" "ledger -f %(ledger-file) reg -- %(payee)")
("account" "ledger -f %(ledger-file) reg %(account)"))
"Definition of reports to run.
Each element has the form (NAME CMDLINE)"
Each element has the form (NAME CMDLINE). The command line can
contain format specifiers that are replaced with context sensitive
information. Format specifiers have the format '%(<name>)' where
<name> is an identifier for the information to be replaced. The
`ledger-report-format-specifiers' alist variable contains a mapping
from format specifier identifier to a lisp function that implements
the substitution. See the documentation of the individual functions
in that variable for more information on the behavior of each
specifier."
:type '(repeat (list (string :tag "Report Name")
(string :tag "Command Line")))
(string :tag "Command Line")))
:group 'ledger)
(defcustom ledger-report-format-specifiers
'(("ledger-file" . ledger-report-ledger-file-format-specifier)
("payee" . ledger-report-payee-format-specifier)
("account" . ledger-report-account-format-specifier))
"Alist mapping ledger report format specifiers to implementing functions
The function is called with no parameters and expected to return the
text that should replace the format specifier."
:type 'alist
:group 'ledger)
(defcustom ledger-default-acct-transaction-indent " "
"Default indentation for account transactions in an entry."
:type 'string
:group 'ledger)
(defvar bold 'bold)
(defvar ledger-font-lock-keywords
'(("^[0-9./=]+\\s-+\\(?:([^)]+)\\s-+\\)?\\([^*].+\\)" 1 bold)
("^\\s-+.+?\\( \\|\t\\|\\s-+$\\)" . font-lock-keyword-face))
`((,(concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+"
"\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") 3 bold)
(";.+" . font-lock-comment-face)
("^\\s-+.+?\\( \\|\t\\|\n\\|\\s-+$\\)" . font-lock-keyword-face))
"Default expressions to highlight in Ledger mode.")
(defsubst ledger-current-year ()
@ -231,6 +261,35 @@ Return the difference in the format of a time value."
(setq clear t))))
clear))
(defun ledger-move-to-next-field ()
(re-search-forward "\\( \\|\t\\)" (line-end-position) t))
(defun ledger-toggle-state (state &optional style)
(if (not (null state))
(if (and style (eq style 'cleared))
'cleared)
(if (and style (eq style 'pending))
'pending
'cleared)))
(defun ledger-entry-state ()
(save-excursion
(when (or (looking-at "^[0-9]")
(re-search-backward "^[0-9]" nil t))
(skip-chars-forward "0-9./=")
(skip-syntax-forward " ")
(cond ((looking-at "!\\s-*") 'pending)
((looking-at "\\*\\s-*") 'cleared)
(t nil)))))
(defun ledger-transaction-state ()
(save-excursion
(goto-char (line-beginning-position))
(skip-syntax-forward " ")
(cond ((looking-at "!\\s-*") 'pending)
((looking-at "\\*\\s-*") 'cleared)
(t (ledger-entry-state)))))
(defun ledger-toggle-current-transaction (&optional style)
"Toggle the cleared status of the transaction under point.
Optional argument STYLE may be `pending' or `cleared', depending
@ -295,8 +354,15 @@ dropped."
(insert "* ")
(setq inserted t))))
(if (and inserted
(search-forward " " (line-end-position) t))
(delete-char 2))
(re-search-forward "\\(\t\\| [ \t]\\)"
(line-end-position) t))
(cond
((looking-at "\t")
(delete-char 1))
((looking-at " [ \t]")
(delete-char 2))
((looking-at " ")
(delete-char 1))))
(setq clear inserted)))))
;; Clean up the entry so that it displays minimally
(save-excursion
@ -326,14 +392,22 @@ dropped."
(let ((width (- (point) here)))
(when (> width 0)
(delete-region here (point))
(if (search-forward " " (line-end-position) t)
(if (re-search-forward "\\(\t\\| [ \t]\\)"
(line-end-position) t)
(insert (make-string width ? ))))))
(forward-line))
(goto-char (car bounds))
(skip-chars-forward "0-9./= \t")
(insert state " ")
(if (search-forward " " (line-end-position) t)
(delete-char 2)))))
(if (re-search-forward "\\(\t\\| [ \t]\\)"
(line-end-position) t)
(cond
((looking-at "\t")
(delete-char 1))
((looking-at " [ \t]")
(delete-char 2))
((looking-at " ")
(delete-char 1)))))))
clear))
(defun ledger-toggle-current (&optional style)
@ -344,32 +418,42 @@ dropped."
(defvar ledger-mode-abbrev-table)
;;;###autoload
(define-derived-mode ledger-mode text-mode "Ledger"
"A mode for editing ledger data files."
(set (make-local-variable 'comment-start) ";")
(set (make-local-variable 'comment-start) " ; ")
(set (make-local-variable 'comment-end) "")
(set (make-local-variable 'indent-tabs-mode) nil)
(if (boundp 'font-lock-defaults)
(set (make-local-variable 'font-lock-defaults)
'(ledger-font-lock-keywords nil t)))
(set (make-local-variable 'pcomplete-parse-arguments-function)
'ledger-parse-arguments)
(set (make-local-variable 'pcomplete-command-completion-function)
'ledger-complete-at-point)
(set (make-local-variable 'pcomplete-termination-string) "")
(let ((map (current-local-map)))
(define-key map [(control ?c) (control ?a)] 'ledger-add-entry)
(define-key map [(control ?c) (control ?d)] 'ledger-delete-current-entry)
(define-key map [(control ?c) (control ?y)] 'ledger-set-year)
(define-key map [(control ?c) (control ?m)] 'ledger-set-month)
(define-key map [(control ?c) (control ?c)] 'ledger-toggle-current)
(define-key map [(control ?c) (control ?e)] 'ledger-toggle-current-entry)
(define-key map [(control ?c) (control ?r)] 'ledger-reconcile)
(define-key map [(control ?c) (control ?s)] 'ledger-sort)
(define-key map [tab] 'pcomplete)
(define-key map [(control ?i)] 'pcomplete)
(define-key map [(control ?c) tab] 'ledger-fully-complete-entry)
(define-key map [(control ?c) (control ?i)] 'ledger-fully-complete-entry)
(define-key map [(control ?c) (control ?o) (control ?r)] 'ledger-report)
(define-key map [(control ?c) (control ?o) (control ?g)]
'ledger-report-goto)
(define-key map [(control ?c) (control ?o) (control ?a)]
'ledger-report-redo)
(define-key map [(control ?c) (control ?o) (control ?s)]
'ledger-report-save)
(define-key map [(control ?c) (control ?o) (control ?e)]
'ledger-report-edit)
(define-key map [(control ?c) (control ?o) (control ?k)]
'ledger-report-kill)))
(define-key map [(control ?c) (control ?o) (control ?g)] 'ledger-report-goto)
(define-key map [(control ?c) (control ?o) (control ?a)] 'ledger-report-redo)
(define-key map [(control ?c) (control ?o) (control ?s)] 'ledger-report-save)
(define-key map [(control ?c) (control ?o) (control ?e)] 'ledger-report-edit)
(define-key map [(control ?c) (control ?o) (control ?k)] 'ledger-report-kill)))
;; Reconcile mode
@ -416,7 +500,7 @@ dropped."
;; attempt to auto-reconcile in the background
(with-temp-buffer
(let ((exit-code
(ledger-run-ledger buffer "--format" "%xB\\n"
(ledger-run-ledger buffer "--format" "%xb\\n"
"--reconcile" balance "--reconcile-date" date
"register" account)))
(if (/= 0 exit-code)
@ -427,7 +511,9 @@ dropped."
(error (buffer-string)))
(while (not (eobp))
(setq cleared
(cons (1+ (read (current-buffer))) cleared))
(cons (save-excursion
(goto-line (1+ (read (current-buffer))))
(point-marker)) cleared))
(forward-line)))))
(goto-char (point-min))
(with-current-buffer ledger-buf
@ -531,8 +617,12 @@ dropped."
(cons
(nth 0 item)
(if ledger-clear-whole-entries
(copy-marker (nth 1 item))
(copy-marker (nth 0 xact)))))))
(save-excursion
(goto-line (nth 1 item))
(point-marker))
(save-excursion
(goto-line (nth 0 xact))
(point-marker)))))))
(insert (format "%s %-30s %-25s %15s\n"
(format-time-string "%m/%d" (nth 2 item))
(nth 4 item) (nth 1 xact) (nth 2 xact)))
@ -585,6 +675,152 @@ dropped."
(define-key map [?q] 'ledger-reconcile-quit)
(use-local-map map)))
;; Context sensitivity
(defconst ledger-line-config
'((entry
(("^\\(\\([0-9][0-9][0-9][0-9]/\\)?[01]?[0-9]/[0123]?[0-9]\\)[ \t]+\\(\\([!*]\\)[ \t]\\)?[ \t]*\\((\\(.*\\))\\)?[ \t]*\\(.*?\\)[ \t]*;\\(.*\\)[ \t]*$"
(date nil status nil nil code payee comment))
("^\\(\\([0-9][0-9][0-9][0-9]/\\)?[01]?[0-9]/[0123]?[0-9]\\)[ \t]+\\(\\([!*]\\)[ \t]\\)?[ \t]*\\((\\(.*\\))\\)?[ \t]*\\(.*\\)[ \t]*$"
(date nil status nil nil code payee))))
(acct-transaction
(("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\([$]\\)\\(-?[0-9]*\\(\\.[0-9]*\\)?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$"
(indent account commodity amount nil comment))
("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\([$]\\)\\(-?[0-9]*\\(\\.[0-9]*\\)?\\)[ \t]*$"
(indent account commodity amount nil))
("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?[0-9]+\\(\\.[0-9]*\\)?\\)[ \t]+\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$"
(indent account amount nil commodity comment))
("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?[0-9]+\\(\\.[0-9]*\\)?\\)[ \t]+\\(.*?\\)[ \t]*$"
(indent account amount nil commodity))
("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?\\(\\.[0-9]*\\)\\)[ \t]+\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$"
(indent account amount nil commodity comment))
("\\(^[ \t]+\\)\\(.*?\\)[ \t]+\\(-?\\(\\.[0-9]*\\)\\)[ \t]+\\(.*?\\)[ \t]*$"
(indent account amount nil commodity))
("\\(^[ \t]+\\)\\(.*?\\)[ \t]*;[ \t]*\\(.*?\\)[ \t]*$"
(indent account comment))
("\\(^[ \t]+\\)\\(.*?\\)[ \t]*$"
(indent account))))))
(defun ledger-extract-context-info (line-type pos)
"Get context info for current line.
Assumes point is at beginning of line, and the pos argument specifies
where the \"users\" point was."
(let ((linfo (assoc line-type ledger-line-config))
found field fields)
(dolist (re-info (nth 1 linfo))
(let ((re (nth 0 re-info))
(names (nth 1 re-info)))
(unless found
(when (looking-at re)
(setq found t)
(dotimes (i (length names))
(when (nth i names)
(setq fields (append fields
(list
(list (nth i names)
(match-string-no-properties (1+ i))
(match-beginning (1+ i))))))))
(dolist (f fields)
(and (nth 1 f)
(>= pos (nth 2 f))
(setq field (nth 0 f))))))))
(list line-type field fields)))
(defun ledger-context-at-point ()
"Return a list describing the context around point.
The contents of the list are the line type, the name of the field
point containing point, and for selected line types, the content of
the fields in the line in a association list."
(let ((pos (point)))
(save-excursion
(beginning-of-line)
(let ((first-char (char-after)))
(cond ((equal (point) (line-end-position))
'(empty-line nil nil))
((memq first-char '(?\ ?\t))
(ledger-extract-context-info 'acct-transaction pos))
((memq first-char '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9))
(ledger-extract-context-info 'entry pos))
((equal first-char ?\=)
'(automated-entry nil nil))
((equal first-char ?\~)
'(period-entry nil nil))
((equal first-char ?\!)
'(command-directive))
((equal first-char ?\;)
'(comment nil nil))
((equal first-char ?Y)
'(default-year nil nil))
((equal first-char ?P)
'(commodity-price nil nil))
((equal first-char ?N)
'(price-ignored-commodity nil nil))
((equal first-char ?D)
'(default-commodity nil nil))
((equal first-char ?C)
'(commodity-conversion nil nil))
((equal first-char ?i)
'(timeclock-i nil nil))
((equal first-char ?o)
'(timeclock-o nil nil))
((equal first-char ?b)
'(timeclock-b nil nil))
((equal first-char ?h)
'(timeclock-h nil nil))
(t
'(unknown nil nil)))))))
(defun ledger-context-other-line (offset)
"Return a list describing context of line offset for existing position.
Offset can be positive or negative. If run out of buffer before reaching
specified line, returns nil."
(save-excursion
(let ((left (forward-line offset)))
(if (not (equal left 0))
nil
(ledger-context-at-point)))))
(defun ledger-context-line-type (context-info)
(nth 0 context-info))
(defun ledger-context-current-field (context-info)
(nth 1 context-info))
(defun ledger-context-field-info (context-info field-name)
(assoc field-name (nth 2 context-info)))
(defun ledger-context-field-present-p (context-info field-name)
(not (null (ledger-context-field-info context-info field-name))))
(defun ledger-context-field-value (context-info field-name)
(nth 1 (ledger-context-field-info context-info field-name)))
(defun ledger-context-field-position (context-info field-name)
(nth 2 (ledger-context-field-info context-info field-name)))
(defun ledger-context-field-end-position (context-info field-name)
(+ (ledger-context-field-position context-info field-name)
(length (ledger-context-field-value context-info field-name))))
(defun ledger-context-goto-field-start (context-info field-name)
(goto-char (ledger-context-field-position context-info field-name)))
(defun ledger-context-goto-field-end (context-info field-name)
(goto-char (ledger-context-field-end-position context-info field-name)))
(defun ledger-entry-payee ()
"Returns the payee of the entry containing point or nil."
(let ((i 0))
(while (eq (ledger-context-line-type (ledger-context-other-line i)) 'acct-transaction)
(setq i (- i 1)))
(let ((context-info (ledger-context-other-line i)))
(if (eq (ledger-context-line-type context-info) 'entry)
(ledger-context-field-value context-info 'payee)
nil))))
;; Ledger report mode
(defvar ledger-report-buffer-name "*Ledger Report*")
@ -606,13 +842,13 @@ dropped."
(define-key map [?k] 'ledger-report-kill)
(define-key map [?e] 'ledger-report-edit)
(define-key map [?q] 'ledger-report-quit)
(define-key map [(control ?c) (control ?l) (control ?r)]
(define-key map [(control ?c) (control ?l) (control ?r)]
'ledger-report-redo)
(define-key map [(control ?c) (control ?l) (control ?S)]
(define-key map [(control ?c) (control ?l) (control ?S)]
'ledger-report-save)
(define-key map [(control ?c) (control ?l) (control ?k)]
(define-key map [(control ?c) (control ?l) (control ?k)]
'ledger-report-kill)
(define-key map [(control ?c) (control ?l) (control ?e)]
(define-key map [(control ?c) (control ?l) (control ?e)]
'ledger-report-edit)
(use-local-map map)))
@ -620,9 +856,9 @@ dropped."
"Read the name of a ledger report to use, with completion.
The empty string and unknown names are allowed."
(completing-read "Report name: "
ledger-reports nil nil nil
'ledger-report-name-prompt-history nil))
(completing-read "Report name: "
ledger-reports nil nil nil
'ledger-report-name-prompt-history nil))
(defun ledger-report (report-name edit)
"Run a user-specified report from `ledger-reports'.
@ -638,13 +874,17 @@ editing before the command is run.
The output buffer will be in `ledger-report-mode', which defines
commands for saving a new named report based on the command line
used to generate the buffer, navigating the buffer, etc."
(interactive
(let ((rname (ledger-report-read-name))
(edit (not (null current-prefix-arg))))
(list rname edit)))
(interactive
(progn
(when (and (buffer-modified-p)
(y-or-n-p "Buffer modified, save it? "))
(save-buffer))
(let ((rname (ledger-report-read-name))
(edit (not (null current-prefix-arg))))
(list rname edit))))
(let ((buf (current-buffer))
(rbuf (get-buffer ledger-report-buffer-name))
(wcfg (current-window-configuration)))
(wcfg (current-window-configuration)))
(if rbuf
(kill-buffer rbuf))
(with-current-buffer
@ -671,15 +911,73 @@ If name exists, returns the object naming the report, otherwise returns nil."
"Add a new report to `ledger-reports'."
(setq ledger-reports (cons (list name cmd) ledger-reports)))
(defun ledger-reports-custom-save ()
(defun ledger-reports-custom-save ()
"Save the `ledger-reports' variable using the customize framework."
(customize-save-variable 'ledger-reports ledger-reports))
(defun ledger-report-read-command (report-cmd)
"Read the command line to create a report."
(read-from-minibuffer "Report command line: "
(if (null report-cmd) "ledger " report-cmd)
nil nil 'ledger-report-cmd-prompt-history))
(if (null report-cmd) "ledger " report-cmd)
nil nil 'ledger-report-cmd-prompt-history))
(defun ledger-report-ledger-file-format-specifier ()
"Substitute the full path to master or current ledger file
The master file name is determined by the ledger-master-file buffer-local
variable which can be set using file variables. If it is set, it is used,
otherwise the current buffer file is used."
(ledger-master-file))
(defun ledger-read-string-with-default (prompt default)
(let ((default-prompt (concat prompt
(if default
(concat " (" default "): ")
": "))))
(read-string default-prompt nil nil default)))
(defun ledger-report-payee-format-specifier ()
"Substitute a payee name
The user is prompted to enter a payee and that is substitued. If
point is in an entry, the payee for that entry is used as the
default."
;; It is intended copmletion should be available on existing
;; payees, but the list of possible completions needs to be
;; developed to allow this.
(ledger-read-string-with-default "Payee" (regexp-quote (ledger-entry-payee))))
(defun ledger-report-account-format-specifier ()
"Substitute an account name
The user is prompted to enter an account name, which can be any
regular expression identifying an account. If point is on an account
transaction line for an entry, the full account name on that line is
the default."
;; It is intended completion should be available on existing account
;; names, but it remains to be implemented.
(let* ((context (ledger-context-at-point))
(default
(if (eq (ledger-context-line-type context) 'acct-transaction)
(regexp-quote (ledger-context-field-value context 'account))
nil)))
(ledger-read-string-with-default "Account" default)))
(defun ledger-report-expand-format-specifiers (report-cmd)
(let ((expanded-cmd report-cmd))
(while (string-match "%(\\([^)]*\\))" expanded-cmd)
(let* ((specifier (match-string 1 expanded-cmd))
(f (cdr (assoc specifier ledger-report-format-specifiers))))
(if f
(setq expanded-cmd (replace-match
(save-match-data
(with-current-buffer ledger-buf
(shell-quote-argument (funcall f))))
t t expanded-cmd))
(progn
(set-window-configuration ledger-original-window-cfg)
(error "Invalid ledger report format specifier '%s'" specifier)))))
expanded-cmd))
(defun ledger-report-cmd (report-name edit)
"Get the command line to run the report."
@ -687,17 +985,18 @@ If name exists, returns the object naming the report, otherwise returns nil."
;; logic for substitution goes here
(when (or (null report-cmd) edit)
(setq report-cmd (ledger-report-read-command report-cmd)))
(setq report-cmd (ledger-report-expand-format-specifiers report-cmd))
(set (make-local-variable 'ledger-report-cmd) report-cmd)
(or (string-empty-p report-name)
(ledger-report-name-exists report-name)
(ledger-reports-add report-name report-cmd)
(ledger-reports-custom-save))
(ledger-report-name-exists report-name)
(ledger-reports-add report-name report-cmd)
(ledger-reports-custom-save))
report-cmd))
(defun ledger-do-report (cmd)
"Run a report command line."
(goto-char (point-min))
(insert (format "Report: %s\n" cmd)
(insert (format "Report: %s\n" cmd)
(make-string (- (window-width) 1) ?=)
"\n")
(shell-command cmd t nil))
@ -707,7 +1006,7 @@ If name exists, returns the object naming the report, otherwise returns nil."
(interactive)
(let ((rbuf (get-buffer ledger-report-buffer-name)))
(if (not rbuf)
(error "There is no ledger report buffer"))
(error "There is no ledger report buffer"))
(pop-to-buffer rbuf)
(shrink-window-if-larger-than-buffer)))
@ -740,7 +1039,7 @@ If name exists, returns the object naming the report, otherwise returns nil."
(let ((name ""))
(while (string-empty-p name)
(setq name (read-from-minibuffer "Report name: " nil nil nil
'ledger-report-name-prompt-history)))
'ledger-report-name-prompt-history)))
name))
(defun ledger-report-save ()
@ -752,45 +1051,210 @@ If name exists, returns the object naming the report, otherwise returns nil."
(setq ledger-report-name (ledger-report-read-new-name)))
(while (setq existing-name (ledger-report-name-exists ledger-report-name))
(cond ((y-or-n-p (format "Overwrite existing report named '%s' "
ledger-report-name))
(when (string-equal
ledger-report-cmd
(car (cdr (assq existing-name ledger-reports))))
(error "Current command is identical to existing saved one"))
(setq ledger-reports
(assq-delete-all existing-name ledger-reports)))
(t
(setq ledger-report-name (ledger-report-read-new-name)))))
(cond ((y-or-n-p (format "Overwrite existing report named '%s' "
ledger-report-name))
(when (string-equal
ledger-report-cmd
(car (cdr (assq existing-name ledger-reports))))
(error "Current command is identical to existing saved one"))
(setq ledger-reports
(assq-delete-all existing-name ledger-reports)))
(t
(setq ledger-report-name (ledger-report-read-new-name)))))
(ledger-reports-add ledger-report-name ledger-report-cmd)
(ledger-reports-custom-save)))
;; In-place completion support
(defun ledger-thing-at-point ()
(let ((here (point)))
(goto-char (line-beginning-position))
(cond ((looking-at "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.+?)\\)?\\s-+")
(goto-char (match-end 0))
'entry)
((looking-at "^\\s-+\\([*!]\\s-+\\)?[[(]?\\(.\\)")
(goto-char (match-beginning 2))
'transaction)
(t
(ignore (goto-char here))))))
(defun ledger-parse-arguments ()
"Parse whitespace separated arguments in the current region."
(let* ((info (save-excursion
(cons (ledger-thing-at-point) (point))))
(begin (cdr info))
(end (point))
begins args)
(save-excursion
(goto-char begin)
(when (< (point) end)
(skip-chars-forward " \t\n")
(setq begins (cons (point) begins))
(setq args (cons (buffer-substring-no-properties
(car begins) end)
args)))
(cons (reverse args) (reverse begins)))))
(defun ledger-entries ()
(let ((origin (point))
entries-list)
(save-excursion
(goto-char (point-min))
(while (re-search-forward
(concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+"
"\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") nil t)
(unless (and (>= origin (match-beginning 0))
(< origin (match-end 0)))
(setq entries-list (cons (match-string-no-properties 3)
entries-list)))))
(pcomplete-uniqify-list (nreverse entries-list))))
(defvar ledger-account-tree nil)
(defun ledger-find-accounts ()
(let ((origin (point)) account-path elements)
(save-excursion
(setq ledger-account-tree (list t))
(goto-char (point-min))
(while (re-search-forward
"^[ \t]+\\([*!]\\s-+\\)?[[(]?\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)" nil t)
(unless (and (>= origin (match-beginning 0))
(< origin (match-end 0)))
(setq account-path (match-string-no-properties 2))
(setq elements (split-string account-path ":"))
(let ((root ledger-account-tree))
(while elements
(let ((entry (assoc (car elements) root)))
(if entry
(setq root (cdr entry))
(setq entry (cons (car elements) (list t)))
(nconc root (list entry))
(setq root (cdr entry))))
(setq elements (cdr elements)))))))))
(defun ledger-accounts ()
(ledger-find-accounts)
(let* ((current (caar (ledger-parse-arguments)))
(elements (and current (split-string current ":")))
(root ledger-account-tree)
(prefix nil))
(while (cdr elements)
(let ((entry (assoc (car elements) root)))
(if entry
(setq prefix (concat prefix (and prefix ":")
(car elements))
root (cdr entry))
(setq root nil elements nil)))
(setq elements (cdr elements)))
(and root
(sort
(mapcar (function
(lambda (x)
(let ((term (if prefix
(concat prefix ":" (car x))
(car x))))
(if (> (length (cdr x)) 1)
(concat term ":")
term))))
(cdr root))
'string-lessp))))
(defun ledger-complete-at-point ()
"Do appropriate completion for the thing at point"
(interactive)
(while (pcomplete-here
(if (eq (save-excursion
(ledger-thing-at-point)) 'entry)
(ledger-entries)
(ledger-accounts)))))
(defun ledger-fully-complete-entry ()
"Do appropriate completion for the thing at point"
(interactive)
(let ((name (caar (ledger-parse-arguments)))
xacts)
(save-excursion
(when (eq 'entry (ledger-thing-at-point))
(when (re-search-backward
(concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+"
(regexp-quote name) "\\(\t\\|\n\\| [ \t]\\)") nil t)
(forward-line)
(while (looking-at "^\\s-+")
(setq xacts (cons (buffer-substring-no-properties
(line-beginning-position)
(line-end-position))
xacts))
(forward-line))
(setq xacts (nreverse xacts)))))
(when xacts
(save-excursion
(insert ?\n)
(while xacts
(insert (car xacts) ?\n)
(setq xacts (cdr xacts))))
(forward-line)
(goto-char (line-end-position))
(if (re-search-backward "\\(\t\\| [ \t]\\)" nil t)
(goto-char (match-end 0))))))
;; A sample function for $ users
(defun ledger-align-dollars (&optional column)
(defun ledger-next-amount (&optional end)
(when (re-search-forward "\\( \\|\t\\| \t\\)[ \t]*-?\\([A-Z$]+ *\\)?\\(-?[0-9,]+?\\)\\(.[0-9]+\\)?\\( *[A-Z$]+\\)?\\([ \t]*@@?[^\n;]+?\\)?\\([ \t]+;.+?\\)?$" (marker-position end) t)
(goto-char (match-beginning 0))
(skip-syntax-forward " ")
(- (or (match-end 4)
(match-end 3)) (point))))
(defun ledger-align-amounts (&optional column)
"Align amounts in the current region.
This is done so that the last digit falls in COLUMN, which defaults to 52."
(interactive "p")
(if (= column 1)
(setq column 48))
(while (search-forward "$" nil t)
(backward-char)
(let ((col (current-column))
(beg (point))
target-col len)
(skip-chars-forward "-$0-9,.")
(setq len (- (point) beg))
(setq target-col (- column len))
(if (< col target-col)
(progn
(goto-char beg)
(insert (make-string (- target-col col) ? )))
(move-to-column target-col)
(if (looking-back " ")
(delete-char (- col target-col))
(skip-chars-forward "^ \t")
(delete-horizontal-space)
(insert " ")))
(forward-line))))
(setq column 52))
(save-excursion
(let* ((mark-first (< (mark) (point)))
(begin (if mark-first (mark) (point)))
(end (if mark-first (point-marker) (mark-marker)))
offset)
(goto-char begin)
(while (setq offset (ledger-next-amount end))
(let ((col (current-column))
(target-col (- column offset))
adjust)
(setq adjust (- target-col col))
(if (< col target-col)
(insert (make-string (- target-col col) ? ))
(move-to-column target-col)
(if (looking-back " ")
(delete-char (- col target-col))
(skip-chars-forward "^ \t")
(delete-horizontal-space)
(insert " ")))
(forward-line))))))
(defalias 'ledger-align-dollars 'ledger-align-amounts)
;; A sample entry sorting function, which works if entry dates are of
;; the form YYYY/mm/dd.
(defun ledger-sort ()
(interactive)
(save-excursion
(goto-char (point-min))
(sort-subr
nil
(function
(lambda ()
(if (re-search-forward
(concat "^[0-9/.=-]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+"
"\\(.+?\\)\\(\t\\|\n\\| [ \t]\\)") nil t)
(goto-char (match-beginning 0))
(goto-char (point-max)))))
(function
(lambda ()
(forward-paragraph))))))
;; General helper functions
@ -798,15 +1262,10 @@ If name exists, returns the object naming the report, otherwise returns nil."
(defun ledger-run-ledger (buffer &rest args)
"run ledger with supplied arguments"
;; Let's try again, just in case they moved it while we were sleeping.
(cond
((null ledger-binary-path)
(error "The variable `ledger-binary-path' has not been set"))
((not (file-exists-p ledger-binary-path))
(error "The file `ledger-binary-path' (\"%s\") does not exist"
ledger-binary-path))
((not (file-executable-p ledger-binary-path))
(error "The file `ledger-binary-path' (\"%s\") cannot be executed"
ledger-binary-path))
(t
(let ((buf (current-buffer)))
(with-current-buffer buffer
@ -834,6 +1293,19 @@ If name exists, returns the object naming the report, otherwise returns nil."
(setq ledger-month (read-string "Month: " (ledger-current-month)))
(setq ledger-month (format "%02d" newmonth))))
(defvar ledger-master-file nil)
(defun ledger-master-file ()
"Return the master file for a ledger file.
The master file is either the file for the current ledger buffer or the
file specified by the buffer-local variable ledger-master-file. Typically
this variable would be set in a file local variable comment block at the
end of a ledger file which is included in some other file."
(if ledger-master-file
(expand-file-name ledger-master-file)
(buffer-file-name)))
(provide 'ledger)
;;; ledger.el ends here

View file

@ -3703,16 +3703,17 @@ reports in a format directly @code{read}-able from Emacs Lisp.
The Ledger tool is fast and simple, but it offers no custom method for
actually editing the ledger. It assumes you know how to use a text
editor, and like doing so. Perhaps an Emacs mode will appear someday
soon to make editing Ledger's data files much easier.
editor, and like doing so. There is, at least, an Emacs mode that
makes editing Ledger's data files much easier.
Until then, you are free to use GnuCash to maintain your ledger, and
the Ledger program for querying and reporting on the contents of that
You are also free to use GnuCash to maintain your ledger, and the
Ledger program for querying and reporting on the contents of that
ledger. It takes a little longer to parse the XML data format that
GnuCash uses, but the end result is identical.
Then again, why would anyone use a Gnome-centric, 35 megabyte behemoth
to edit their data, and a one megabyte binary to query it?
Then again, why would anyone use a Gnome-centric, multi-megabyte
behemoth to edit their data, and only a one megabyte binary to query
it?
@node Using timeclock to record billable time, , Using GnuCash to Keep Your Ledger, Keeping a ledger
@section Using timeclock to record billable time

14
main.cc
View file

@ -22,8 +22,8 @@
using namespace ledger;
int parse_and_report(config_t& config, report_t& report,
int argc, char * argv[], char * envp[])
int parse_and_report(config_t& config, std::auto_ptr<journal_t>& journal,
report_t& report, int argc, char * argv[], char * envp[])
{
// Configure the terminus for value expressions
@ -148,7 +148,7 @@ int parse_and_report(config_t& config, report_t& report,
// Parse initialization files, ledger data, price database, etc.
std::auto_ptr<journal_t> journal(new journal_t);
journal.reset(new journal_t);
#if 0
{ TRACE_PUSH(parser, "Parsing journal file");
@ -481,6 +481,12 @@ appending the output of this command to your Ledger file if you so choose."
int main(int argc, char * argv[], char * envp[])
{
// This variable must be defined here so that any memory it holds is still
// available should the subsequent exception handlers catch an inner
// exception and need to report something on the invalid state of the
// journal (such as an unbalanced entry).
std::auto_ptr<journal_t> journal;
try {
#if DEBUG_LEVEL < BETA
ledger::do_cleanup = false;
@ -492,7 +498,7 @@ int main(int argc, char * argv[], char * envp[])
#if 0
TRACE_PUSH(main, "Ledger starting");
#endif
int status = parse_and_report(config, report, argc, argv, envp);
int status = parse_and_report(config, journal, report, argc, argv, envp);
#if 0
TRACE_POP(main, "Ledger done");
#endif

View file

@ -461,25 +461,22 @@ OPT_BEGIN(begin, "b:") {
if (! report->predicate.empty())
report->predicate += "&";
report->predicate += "d>=[";
// jww (2008-04-20): fix
//report->predicate += interval.begin.to_string();
report->predicate += format_datetime(interval.begin);
report->predicate += "]";
} OPT_END(begin);
OPT_BEGIN(end, "e:") {
interval_t interval(optarg);
if (! is_valid(interval.end))
throw new error(string("Could not determine end of period '") +
optarg + "'");
if (! is_valid(interval.begin))
throw new error(string("Could not determine end of period '") + optarg + "'");
if (! report->predicate.empty())
report->predicate += "&";
report->predicate += "d<[";
// jww (2008-04-20): fix
//report->predicate += interval.end.to_string();
report->predicate += format_datetime(interval.begin);
report->predicate += "]";
terminus = interval.end;
terminus = interval.begin;
} OPT_END(end);
OPT_BEGIN(current, "c") {
@ -693,8 +690,7 @@ OPT_BEGIN(period, "p:") {
if (! report->predicate.empty())
report->predicate += "&";
report->predicate += "d>=[";
// jww (2008-04-20): fix
//report->predicate += interval.begin.to_string();
report->predicate += format_datetime(interval.begin);
report->predicate += "]";
}
@ -702,8 +698,7 @@ OPT_BEGIN(period, "p:") {
if (! report->predicate.empty())
report->predicate += "&";
report->predicate += "d<[";
// jww (2008-04-20): fix
//report->predicate += interval.end.to_string();
report->predicate += format_datetime(interval.end);
report->predicate += "]";
terminus = interval.end;
@ -895,8 +890,10 @@ namespace {
if (commodity_t * commodity =
amount_t::current_pool->find_or_create(symbol)) {
commodity->add_price(current_moment, price);
#if 0
// jww (2008-04-20): what was this?
//commodity->history()->bogus_time = current_moment;
commodity->history()->bogus_time = current_moment;
#endif
}
}
}

View file

@ -27,9 +27,18 @@ static std::list<std::pair<path, int> > include_stack;
struct time_entry_t {
datetime_t checkin;
account_t * account;
string desc;
string desc;
time_entry_t() : account(NULL) {}
time_entry_t(datetime_t _checkin,
account_t * _account = NULL,
std::string _desc = "")
: checkin(_checkin), account(_account), desc(_desc) {}
time_entry_t(const time_entry_t& entry)
: checkin(entry.checkin), account(entry.account),
desc(entry.desc) {}
};
std::list<time_entry_t> time_entries;
#endif
static value_expr parse_amount_expr(std::istream& in, amount_t& amount,
@ -54,8 +63,18 @@ static value_expr parse_amount_expr(std::istream& in, amount_t& amount,
if (! compute_amount(expr, amount, xact))
throw new parse_error("Amount expression failed to compute");
if (expr->kind == value_expr_t::CONSTANT)
#if 0
if (expr->kind == value_expr_t::CONSTANT) {
expr = NULL;
} else {
DEBUG_IF("ledger.textual.parse") {
std::cout << "Value expression tree:" << std::endl;
ledger::dump_value_expr(std::cout, expr.get());
}
}
#else
expr = NULL;
#endif
DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
"The transaction amount is " << xact->amount);
@ -102,7 +121,8 @@ transaction_t * parse_transaction(char * line, account_t * account,
while (! in.eof()) {
in.get(p);
if (in.eof() || (std::isspace(p) &&
(p == '\t' || std::isspace(in.peek()))))
(p == '\t' || in.peek() == EOF ||
std::isspace(in.peek()))))
break;
account_end++;
}
@ -186,7 +206,7 @@ transaction_t * parse_transaction(char * line, account_t * account,
if (parse_amount_expr(in, *xact->cost, xact.get(),
PARSE_VALEXPR_NO_MIGRATE))
throw new parse_error
("A transaction's cost must evalute to a constant value");
("A transaction's cost must evaluate to a constant value");
unsigned long end = (long)in.tellg();
@ -213,9 +233,11 @@ transaction_t * parse_transaction(char * line, account_t * account,
if (xact->amount.commodity() &&
! xact->amount.commodity().annotated)
xact->amount.annotate_commodity(annotation_t(per_unit_cost,
xact->entry->actual_date(),
xact->entry->code));
xact->amount.annotate_commodity
(annotation_t
(per_unit_cost,
xact->entry ? optional<datetime_t>(xact->entry->actual_date()) : none,
xact->entry ? optional<string>(xact->entry->code) : none));
DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
"Total cost is " << *xact->cost);
@ -290,12 +312,17 @@ bool parse_transactions(std::istream& in,
in.getline(line, MAX_LINE);
if (in.eof())
break;
beg_pos += std::strlen(line) + 1;
int len = std::strlen(line);
if (line[len - 1] == '\r')
line[--len] = '\0';
beg_pos += len + 1;
linenum++;
if (line[0] == ' ' || line[0] == '\t' || line[0] == '\r') {
if (line[0] == ' ' || line[0] == '\t') {
char * p = skip_ws(line);
if (! *p || *p == '\r')
if (! *p)
break;
}
if (transaction_t * xact = parse_transaction(line, account)) {
@ -372,12 +399,17 @@ entry_t * parse_entry(std::istream& in, char * line, account_t * master,
in.getline(line, MAX_LINE);
if (in.eof() && line[0] == '\0')
break;
end_pos = beg_pos + std::strlen(line) + 1;
int len = std::strlen(line);
if (line[len - 1] == '\r')
line[--len] = '\0';
end_pos = beg_pos + len + 1;
linenum++;
if (line[0] == ' ' || line[0] == '\t' || line[0] == '\r') {
if (line[0] == ' ' || line[0] == '\t') {
char * p = skip_ws(line);
if (! *p || *p == '\r')
if (! *p)
break;
}
@ -451,10 +483,11 @@ bool textual_parser_t::test(std::istream& in) const
return true;
}
static void clock_out_from_timelog(const datetime_t& when,
account_t * account,
const char * desc,
journal_t * journal)
static void clock_out_from_timelog(std::list<time_entry_t>& time_entries,
const datetime_t& when,
account_t * account,
const char * desc,
journal_t * journal)
{
time_entry_t event;
@ -530,8 +563,9 @@ unsigned int textual_parser_t::parse(std::istream& in,
unsigned int count = 0;
unsigned int errors = 0;
std::list<account_t *> account_stack;
auto_entry_finalizer_t auto_entry_finalizer(journal);
std::list<account_t *> account_stack;
auto_entry_finalizer_t auto_entry_finalizer(journal);
std::list<time_entry_t> time_entries;
if (! master)
master = journal->master;
@ -551,18 +585,22 @@ unsigned int textual_parser_t::parse(std::istream& in,
in.getline(line, MAX_LINE);
if (in.eof())
break;
end_pos = beg_pos + std::strlen(line) + 1;
int len = std::strlen(line);
if (line[len - 1] == '\r')
line[--len] = '\0';
end_pos = beg_pos + len + 1;
linenum++;
switch (line[0]) {
case '\0':
case '\r':
break;
case ' ':
case '\t': {
char * p = skip_ws(line);
if (*p && *p != '\r')
if (*p)
throw new parse_error("Line begins with whitespace");
break;
}
@ -575,10 +613,8 @@ unsigned int textual_parser_t::parse(std::istream& in,
char * p = skip_ws(line + 22);
char * n = next_element(p, true);
time_entry_t event;
event.desc = n ? n : "";
event.checkin = parse_datetime(date);
event.account = account_stack.front()->find_account(p);
time_entry_t event(parse_datetime(date),
account_stack.front()->find_account(p), n ? n : "");
if (! time_entries.empty())
for (std::list<time_entry_t>::iterator i = time_entries.begin();
@ -602,9 +638,9 @@ unsigned int textual_parser_t::parse(std::istream& in,
char * p = skip_ws(line + 22);
char * n = next_element(p, true);
clock_out_from_timelog(parse_datetime(date),
p ? account_stack.front()->find_account(p) : NULL,
n, journal);
clock_out_from_timelog
(time_entries, parse_datetime(date),
p ? account_stack.front()->find_account(p) : NULL, n, journal);
count++;
}
break;
@ -671,7 +707,7 @@ unsigned int textual_parser_t::parse(std::istream& in,
}
case 'Y': // set the current year
current_year = std::atoi(skip_ws(line + 1)) - 1900;
current_year = std::atoi(skip_ws(line + 1));
break;
#ifdef TIMELOG_SUPPORT
@ -848,11 +884,19 @@ unsigned int textual_parser_t::parse(std::istream& in,
}
if (! time_entries.empty()) {
std::list<account_t *> accounts;
for (std::list<time_entry_t>::iterator i = time_entries.begin();
i != time_entries.end();
i++)
clock_out_from_timelog(current_moment, (*i).account, NULL, journal);
time_entries.clear();
accounts.push_back((*i).account);
for (std::list<account_t *>::iterator i = accounts.begin();
i != accounts.end();
i++)
clock_out_from_timelog(time_entries, current_moment, *i, NULL, journal);
assert(time_entries.empty());
}
if (added_auto_entry_hook)

View file

@ -53,10 +53,13 @@ string output_time_format = "%Y/%m/%d";
#if 0
static const char * formats[] = {
"%y/%m/%d",
"%Y/%m/%d",
"%m/%d",
"%y.%m.%d",
"%Y.%m.%d",
"%m.%d",
"%y-%m-%d",
"%Y-%m-%d",
"%m-%d",
"%a",
@ -68,8 +71,26 @@ static const char * formats[] = {
};
#endif
bool day_before_month = false;
static bool day_before_month_initialized = false;
bool day_before_month = false;
static bool day_before_month_initialized = false;
#if 0
datetime_t datetime_t::now(std::time(NULL));
namespace {
static std::time_t base = -1;
static int base_year = -1;
static const int month_days[12] = {
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
bool parse_date_mask(const char * date_str, struct std::tm * result);
bool parse_date(const char * date_str, std::time_t * result,
const int year = -1);
bool quick_parse_date(const char * date_str, std::time_t * result);
}
#endif
datetime_t parse_datetime(const char * str)
{
@ -172,17 +193,17 @@ namespace {
if (! parse_date_mask(word.c_str(), &when))
throw new datetime_error(string("Could not parse date mask: ") + word);
when.tm_hour = 0;
when.tm_min = 0;
when.tm_sec = 0;
when.tm_isdst = -1;
when.tm_hour = 0;
when.tm_min = 0;
when.tm_sec = 0;
when.tm_isdst = -1;
bool saw_year = true;
bool saw_mon = true;
bool saw_day = true;
if (when.tm_year == -1) {
when.tm_year = date_t::current_year;
when.tm_year = date_t::current_year - 1900;
saw_year = false;
}
if (when.tm_mon == -1) {
@ -195,18 +216,22 @@ namespace {
when.tm_mday = 1;
saw_day = false;
} else {
saw_mon = false; // don't increment by month if day used
saw_mon = false; // don't increment by month if day used
saw_year = false; // don't increment by year if day used
}
if (begin) {
*begin = std::mktime(&when);
if (end)
*end = interval_t(saw_day ? 86400 : 0, saw_mon ? 1 : 0,
assert(int(*begin) != -1);
if (end) {
*end = interval_t(saw_day ? 1 : 0, saw_mon ? 1 : 0,
saw_year ? 1 : 0).increment(*begin);
assert(int(*end) != -1);
}
}
else if (end) {
*end = std::mktime(&when);
assert(int(*end) != -1);
}
#endif
}
@ -370,7 +395,7 @@ namespace {
#if 0
// jww (2008-05-08):
if (! date_t::input_format.empty()) {
std::memset(result, INT_MAX, sizeof(struct std::tm));
std::memset(result, -1, sizeof(struct std::tm));
if (strptime(date_str, date_t::input_format.c_str(), result))
return true;
}
@ -397,7 +422,7 @@ namespace {
when.tm_sec = 0;
if (when.tm_year == -1)
when.tm_year = ((year == -1) ? date_t::current_year : (year - 1900));
when.tm_year = ((year == -1) ? date_t::current_year : year) - 1900;
if (when.tm_mon == -1)
when.tm_mon = 0;
@ -415,7 +440,7 @@ namespace {
{
#if 0
// jww (2008-05-08):
return parse_date(date_str, result, date_t::current_year + 1900);
return parse_date(date_str, result, date_t::current_year);
#else
return false;
#endif

View file

@ -127,6 +127,10 @@ inline datetime_t parse_datetime(const string& str) {
return parse_datetime(str.c_str());
}
inline string format_datetime(const datetime_t& when) {
return ""; // jww (2008-07-19): NYI
}
extern const ptime time_now;
extern const date date_now;
extern bool day_before_month;

View file

@ -582,8 +582,12 @@ void value_expr_t::compute(value_t& result, const details_t& details,
}
case O_COM:
assert(left);
assert(right);
if (! left)
throw new compute_error("Comma operator missing left operand",
new valexpr_context(this));
if (! right)
throw new compute_error("Comma operator missing right operand",
new valexpr_context(this));
left->compute(result, details, context);
right->compute(result, details, context);
break;
@ -1039,8 +1043,8 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope,
unexpected(c, ']');
in.get(c);
node.reset(new value_expr_t(value_expr_t::CONSTANT));
interval_t timespan(buf);
node.reset(new value_expr_t(value_expr_t::CONSTANT));
node->value = new value_t(timespan.first());
break;
}
@ -1734,10 +1738,12 @@ bool print_value_expr(std::ostream& out,
break;
case value_expr_t::O_COM:
if (print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
if (node->left &&
print_value_expr(out, node->left, relaxed, node_to_find, start_pos, end_pos))
found = true;
out << ", ";
if (print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
if (node->right &&
print_value_expr(out, node->right, relaxed, node_to_find, start_pos, end_pos))
found = true;
break;
case value_expr_t::O_QUES:

View file

@ -1093,6 +1093,7 @@ void value_t::in_place_negate()
set_boolean(! as_boolean());
return;
case INTEGER:
case DATETIME:
set_long(- as_long());
return;
case AMOUNT: