Merge commit 'bec52e3221d74ce3bf1383b245beae5a6214e387' into next

Conflicts:
	lisp/ledger-reconcile.el
This commit is contained in:
Craig Earls 2015-01-21 22:03:11 -07:00
commit 28b0d67567
18 changed files with 752 additions and 549 deletions

View file

@ -8,7 +8,8 @@ PROJECT(ledger)
set(Ledger_VERSION_MAJOR 3)
set(Ledger_VERSION_MINOR 1)
set(Ledger_VERSION_PATCH 0)
set(Ledger_VERSION_PATCH 1)
set(Ledger_VERSION_PRERELEASE "-alpha.1")
set(Ledger_VERSION_DATE 20141005)
enable_testing()
@ -270,7 +271,7 @@ include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE.md")
set (CPACK_PACKAGE_VERSION_MAJOR "${Ledger_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Ledger_VERSION_MINOR}")
set (CPACK_PACKAGE_VERSION_PATCH "${Ledger_VERSION_PATCH}")
set (CPACK_PACKAGE_VERSION_PATCH "${Ledger_VERSION_PATCH}${Ledger_VERSION_PRERELEASE}")
set (CPACK_GENERATOR "TBZ2")
set (CPACK_SOURCE_GENERATOR "TBZ2")

View file

@ -5,6 +5,10 @@
########################################################################
configure_file(
${PROJECT_SOURCE_DIR}/doc/version.texi.in
${PROJECT_BINARY_DIR}/doc/version.texi)
if (USE_DOXYGEN)
find_package(Doxygen)
if (NOT DOXYGEN_FOUND)

View file

@ -367,7 +367,7 @@ some additional meaning to the states:
No state. This is equivalent to sticking a check in the mail. It has
been obligated, but not been cashed by the recipient. It could also
apply to credit/debit card transactions that have not been cleared into
your account balance. You bank may call these transactions ``pending'',
your account balance. You bank may call these transactions @emph{pending},
but Ledger-mode uses a slightly different meaning.
@item Pending.
@ -550,7 +550,7 @@ about. You can get this from a monthly statement, or from checking your
on-line transaction history. It also helps immensely to know the final
cleared balance you are aiming for.
Use menu @samp{Reconcile Account} or @kbd{C-c C-r} and enter the account
Use menu @samp{Reconcile Account} or keyboard shortcut @kbd{C-c C-r} and enter the account
you wish to reconcile in the Minibuffer. Ledger-mode is not particular
about what you enter for the account. You can leave it blank and
@file{*Reconcile*} buffer will show you @emph{all} uncleared
@ -589,7 +589,7 @@ difference from your target is zero.
If you find errors during reconciliation. You can visit the transaction
under point in the @file{*Reconcile*} buffer by hitting the @kbd{RET}
key. This will take you to the transaction in the Ledger buffer. When
you have finished editing the transaction saving the buffer will
you have finished editing the transaction, saving the buffer will
automatically return you to the @file{*Reconcile*} buffer and you can
mark the transaction if appropriate.
@ -599,7 +599,7 @@ mark the transaction if appropriate.
Once you have marked all transactions as pending and the cleared balance
is correct. Finish the reconciliation by typing @kbd{C-c C-c}. This
marks all pending transaction as cleared and saves the ledger buffer.
marks all pending transactions as cleared and saves the ledger buffer.
@node Adding and Deleting Transactions during Reconciliation, Changing Reconciliation Account, Finalize Reconciliation, The Reconcile Buffer
@section Adding and Deleting Transactions during Reconciliation
@ -621,7 +621,7 @@ Typing @kbd{d} will delete the transaction under point in the
You can conveniently switch the account being reconciled by typing
@kbd{g}, and entering a new account to reconcile. This simply restarts
the reconcile process. Any transactions that were marked `pending' in
the reconcile process. Any transactions that were marked @emph{pending} in
the ledger buffer are left in that state when the account is switched.
@node Changing Reconciliation Target, , Changing Reconciliation Account, The Reconcile Buffer
@ -795,7 +795,7 @@ maintain the proper mathematical sense.
@chapter Scheduling Transactions
The Ledger program provides for automating transactions but these
transaction aren't ``real'', they only exist inside a ledger session and
transaction aren't @emph{real}, they only exist inside a ledger session and
are not reflected in the actual data file. Many transactions are very
repetitive, but may vary slightly in the date they occur on, or the
amount. Some transactions are weekly, monthly, quarterly or annually.
@ -929,29 +929,63 @@ If non-nil, highlight transaction under point using
@ftable @option
@item ledger-reconcile-default-commodity
The default commodity for use in target calculations in ledger
reconcile. Defaults to @samp{$} (USD).
@item ledger-recon-buffer-name
Name to use for reconciliation buffer. Defaults to @file{*Reconcile*}.
@item ledger-narrow-on-reconcile
If non-nil, limit transactions shown in main buffer to those matching
If t, limit transactions shown in main buffer to those matching
the reconcile regex.
@item ledger-buffer-tracks-reconcile-buffer
If non-nil, then when the cursor is moved to a new transaction in the
@file{*Reconcile*} window.
If t, then when the cursor is moved to a new transaction in the
@file{*Reconcile*} buffer. Then that transaction will be shown in its
source buffer.
@item ledger-reconcile-force-window-bottom
If non-nil, make the @file{*Reconcile*} window appear along the bottom
If t, make the @file{*Reconcile*} window appear along the bottom
of the register window and resize.
@item ledger-reconcile-toggle-to-pending
If non-nil, then toggle between uncleared and pending @samp{!}. If
If t, then toggle between uncleared and pending @samp{!}. If
false toggle between uncleared and cleared @samp{*}.
@item ledger-reconcile-default-date-format
Date format for the reconcile buffer. Defaults to
ledger-default-date-format.
@item ledger-reconcile-target-prompt-string
Prompt for recon target. Defaults to "Target amount for reconciliation ".
@item ledger-reconcile-buffer-header
Header string for the reconcile buffer. If non-nil, the name of the
account being reconciled will be substituted into the '%s'. If nil, no
header will be displayed. Defaults to "Reconciling account %s\n\n".
@item ledger-reconcile-buffer-line-format
Format string for the ledger reconcile posting format. Available fields
are date, status, code, payee, account, amount. The format for each
field is %WIDTH(FIELD), WIDTH can be preced by a minus sign which mean
to left justify and pad the field. WIDTH is the minimum number of
characters to display; if string is longer, it is not truncated unless
ledger-reconcile-buffer-payee-max-chars or
ledger-reconcile-buffer-account-max-chars is defined. Defaults to
"%(date)s %-4(code)s %-50(payee)s %-30(account)s %15(amount)s\n"
@item ledger-reconcile-buffer-payee-max-chars
If positive, truncate payee name right side to max number of characters.
@item ledger-reconcile-buffer-account-max-chars
If positive, truncate account name left side to max number of characters.
@item ledger-reconcile-sort-key
Key for sorting reconcile buffer. Possible values are '(date)',
'(amount)', '(payee)' or '(0)' for no sorting, i.e. using
ledger file order. Defaults to '(0)'.
@item ledger-reconcile-insert-effective-date nil
If t, prompt for effective date when clearing transactions during
reconciliation.
@end ftable
@node Ledger Report Customization Group, Ledger Faces Customization Group, Ledger Reconcile Customization Group, Customization Variables

View file

@ -173,7 +173,7 @@ Show any gains (or losses) in commodity values over time.
Only show the top
.Ar number
postings.
.It Fl \-historical Pq Fl H
.\".It Fl \-historical Pq Fl H
.It Fl \-invert
Invert the value of amounts shown.
.It Fl \-market Pq Fl V
@ -410,7 +410,7 @@ Transform the date of the transaction using
.Ar EXPR .
.It Fl \-date-format Ar DATEFMT Pq Fl y
Specify the format ledger should use to print dates.
.It Fl \-datetime-format Ar FMT
.\" .It Fl \-datetime-format Ar FMT
.It Fl \-date-width Ar INT
Specify the width, in characters, of the date column in the
.Nm register
@ -925,6 +925,9 @@ interpret parentheses, you should always escape them:
.El
.Sh EXPRESSIONS
.Bl -tag -width "partial_account"
.It Fn abs value
Return the absolute value of the given
.Ar value .
.It Nm account
.It Nm account_base
.It Nm account_amount
@ -943,22 +946,29 @@ is true. It typically checks the value of the option
for example:
.Dl ansify_if(amount, "blue", options.color)
.It Nm beg_line
Line number where entry for posting begins.
.It Nm beg_pos
Character position where entry for posting begins.
.It Nm calculated
.It Nm cleared
.It Nm code
Return the transaction code, the string between the parenthesis after the date.
.It Nm comment
.It Nm commodity
.It Nm cost
.It Nm count
.It Nm date
Return the date of the posting.
.It Nm depth
.It Nm depth_spacer
.It Nm display_amount
.It Nm display_total
.It Nm end_line
Line number where entry for posting ends.
.It Nm end_pos
Character position where entry for posting ends.
.It Nm filename
The name of the ledger data file from whence the posting came.
.It Nm format_date
.It Nm get_at
.It Nm has_meta

File diff suppressed because it is too large Load diff

8
doc/version.texi.in Normal file
View file

@ -0,0 +1,8 @@
@set Ledger_VERSION_MAJOR @Ledger_VERSION_MAJOR@
@set Ledger_VERSION_MINOR @Ledger_VERSION_MINOR@
@set Ledger_VERSION_PATCH @Ledger_VERSION_PATCH@
@set Ledger_VERSION_PRERELEASE @Ledger_VERSION_PRERELEASE@
@set Ledger_VERSION_DATE @Ledger_VERSION_DATE@
@set VERSION @value{Ledger_VERSION_MAJOR}.@value{Ledger_VERSION_MINOR}.@value{Ledger_VERSION_PATCH}@value{Ledger_VERSION_PRERELEASE}

View file

@ -1,3 +1,7 @@
set(LEDGER_CLI_SOURCES
global.cc
main.cc)
set(LEDGER_SOURCES
stats.cc
generate.cc
@ -255,7 +259,7 @@ else()
endmacro(ADD_PCH_RULE _header_filename _src_list _other_srcs)
endif()
add_pch_rule(${PROJECT_BINARY_DIR}/system.hh LEDGER_SOURCES main.cc global.cc)
add_pch_rule(${PROJECT_BINARY_DIR}/system.hh LEDGER_SOURCES LEDGER_CLI_SOURCES)
include(GNUInstallDirs)

View file

@ -123,6 +123,8 @@ public:
out <<
"Ledger " << Ledger_VERSION_MAJOR << '.' << Ledger_VERSION_MINOR << '.'
<< Ledger_VERSION_PATCH;
if (Ledger_VERSION_PRERELEASE != 0)
out << Ledger_VERSION_PRERELEASE;
if (Ledger_VERSION_DATE != 0)
out << '-' << Ledger_VERSION_DATE;
out << _(", the command-line accounting tool");

View file

@ -631,7 +631,7 @@ value_t report_t::fn_trim(call_scope_t& args)
while (*p && std::isspace(*p))
p++;
const char * e = buf.get() + temp.length();
const char * e = buf.get() + temp.length() - 1;
while (e > p && std::isspace(*e))
e--;
@ -643,7 +643,7 @@ value_t report_t::fn_trim(call_scope_t& args)
return string_value(empty_string);
}
else {
return string_value(string(p, static_cast<std::string::size_type>(e - p)));
return string_value(string(p, static_cast<std::string::size_type>(e - p + 1)));
}
}

View file

@ -54,6 +54,7 @@
#define Ledger_VERSION_MAJOR @Ledger_VERSION_MAJOR@
#define Ledger_VERSION_MINOR @Ledger_VERSION_MINOR@
#define Ledger_VERSION_PATCH @Ledger_VERSION_PATCH@
#define Ledger_VERSION_PRERELEASE "@Ledger_VERSION_PRERELEASE@"
#define Ledger_VERSION_DATE @Ledger_VERSION_DATE@
#define HAVE_EDIT @HAVE_EDIT@

View file

@ -47,10 +47,12 @@ if (PYTHONINTERP_FOUND)
set_target_properties(check PROPERTIES DEPENDS ${_class}Test_${TestFile_Name})
endforeach()
set(_class CheckTests)
add_test(NAME ${_class}
COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test/${_class}.py
--ledger $<TARGET_FILE:ledger> --source ${PROJECT_SOURCE_DIR})
list(APPEND CheckOptions CheckManpage CheckTexinfo CheckBaselineTests)
foreach(_class ${CheckOptions})
add_test(NAME ${_class}
COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test/${_class}.py
--ledger $<TARGET_FILE:ledger> --source ${PROJECT_SOURCE_DIR})
endforeach()
endif()
### CMakeLists.txt ends here

55
test/CheckBaselineTests.py Executable file
View file

@ -0,0 +1,55 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import sys
import re
import os
import argparse
from os.path import *
from subprocess import Popen, PIPE
from CheckOptions import CheckOptions
class CheckBaselineTests (CheckOptions):
def __init__(self, args):
CheckOptions.__init__(self, args)
self.missing_baseline_tests = set()
def main(self):
for option in self.ledger_options():
if option in self.untested_options: continue
baseline_testpath = join(self.source, 'test', 'baseline', 'opt-%s.test' % option)
if exists(baseline_testpath) and getsize(baseline_testpath) > 0: continue
self.missing_baseline_tests.add(option)
if len(self.missing_baseline_tests):
print("Missing Baseline test for:%s%s\n" % (self.sep, self.sep.join(sorted(list(self.missing_baseline_tests)))))
errors = len(self.missing_baseline_tests)
return errors
if __name__ == "__main__":
def getargs():
parser = argparse.ArgumentParser(prog='CheckBaselineTests',
description='Check that ledger options are tested')
parser.add_argument('-l', '--ledger',
dest='ledger',
type=str,
action='store',
required=True,
help='the path to the ledger executable to test with')
parser.add_argument('-s', '--source',
dest='source',
type=str,
action='store',
required=True,
help='the path to the top level ledger source directory')
return parser.parse_args()
args = getargs()
script = CheckBaselineTests(args)
status = script.main()
sys.exit(status)

44
test/CheckManpage.py Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import sys
import re
import os
import argparse
from os.path import *
from subprocess import Popen, PIPE
from CheckOptions import CheckOptions
class CheckManpage (CheckOptions):
def __init__(self, args):
CheckOptions.__init__(self, args)
self.option_pattern = '\.It Fl \\\\-([-A-Za-z]+)'
self.source_file = join(self.source, 'doc', 'ledger.1')
self.source_type = 'manpage'
if __name__ == "__main__":
def getargs():
parser = argparse.ArgumentParser(prog='CheckManpage',
description='Check that ledger options are documented in the manpage')
parser.add_argument('-l', '--ledger',
dest='ledger',
type=str,
action='store',
required=True,
help='the path to the ledger executable to test with')
parser.add_argument('-s', '--source',
dest='source',
type=str,
action='store',
required=True,
help='the path to the top level ledger source directory')
return parser.parse_args()
args = getargs()
script = CheckManpage(args)
status = script.main()
sys.exit(status)

93
test/CheckOptions.py Executable file
View file

@ -0,0 +1,93 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import sys
import re
import os
import argparse
from os.path import *
from subprocess import Popen, PIPE
class CheckOptions (object):
def __init__(self, args):
self.option_pattern = None
self.source_file = None
self.sep = "\n --"
self.ledger = os.path.abspath(args.ledger)
self.source = os.path.abspath(args.source)
self.missing_baseline_tests = set()
self.missing_options = set()
self.unknown_options = set()
self.untested_options = [
'anon',
'args-only',
'cache',
'debug',
'download',
'file',
'force-color',
'force-pager',
'full-help',
'help',
'help-calc',
'help-comm',
'help-disp',
'import',
'init-file',
'no-color',
'options',
'price-db',
'price-exp',
'revalued-total',
'script',
'seed',
'trace',
'verbose',
'verify',
'version'
]
self.known_alternates = [
'cost',
'first',
'import',
'last',
'leeway',
'period-sort'
]
def find_options(self, filename):
regex = re.compile(self.option_pattern)
return {match.group(1) for match in {regex.match(line) for line in open(filename)} if match}
def ledger_options(self):
pipe = Popen('%s --debug option.names parse true' %
self.ledger, shell=True, stdout=PIPE, stderr=PIPE)
regex = re.compile('\[DEBUG\]\s+Option:\s+(.*?)_?$')
ledger_options = {match.group(1).replace('_', '-') for match in {regex.search(line.decode()) for line in pipe.stderr} if match}
return ledger_options
def main(self):
options = self.find_options(self.source_file)
for option in self.ledger_options():
if option not in options:
self.missing_options.add(option)
else:
options.remove(option)
self.unknown_options = {option for option in options if option not in self.known_alternates}
if len(self.missing_options):
print("Missing %s entries for:%s%s\n" % (self.source_type, self.sep, self.sep.join(sorted(list(self.missing_options)))))
if len(self.unknown_options):
print("%s entry for unknown options:%s%s" % (self.source_type, self.sep, self.sep.join(sorted(list(self.unknown_options)))))
errors = len(self.missing_options) + len(self.unknown_options)
return errors

View file

@ -1,136 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import sys
import re
import os
import argparse
from os.path import *
from subprocess import Popen, PIPE
class CheckTests (object):
def __init__(self, args):
self.ledger = os.path.abspath(args.ledger)
self.source = os.path.abspath(args.source)
self.missing_baseline_tests = set()
self.missing_texi_options = set()
self.unknown_texi_options = set()
self.missing_man_options = set()
self.unknown_man_options = set()
self.untested_options = [
'anon',
'args-only',
'cache',
'debug',
'download',
'file',
'force-color',
'force-pager',
'full-help',
'help',
'help-calc',
'help-comm',
'help-disp',
'import',
'init-file',
'no-color',
'options',
'price-db',
'price-exp',
'revalued-total',
'script',
'seed',
'trace',
'verbose',
'verify',
'version'
]
self.known_alternates = [
'cost',
'first',
'import',
'last',
'leeway',
'period-sort'
]
def find_options(self, pattern, filename):
regex = re.compile(pattern)
return {match.group(1) for match in {regex.match(line) for line in open(filename)} if match}
def main(self):
man_options = self.find_options('\.It Fl \\\\-([-A-Za-z]+)',
join(self.source, 'doc', 'ledger.1'))
texi_options = self.find_options('@item --([-A-Za-z]+).*@c option',
join(self.source, 'doc', 'ledger3.texi'))
pipe = Popen('%s --debug option.names parse true' % self.ledger,
shell=True, stdout=PIPE, stderr=PIPE)
regex = re.compile('\[DEBUG\] Option: (.*)')
for line in filter(regex.search, [line.decode() for line in pipe.stderr]):
match = regex.search(line)
option = match.group(1)
option = re.sub('_', '-', option)
option = re.sub('-$', '', option)
if option not in self.untested_options and \
not exists(join(self.source, 'test', 'baseline',
'opt-%s.test' % option)):
self.missing_baseline_tests.add(option)
if option not in man_options:
self.missing_man_options.add(option)
else:
man_options.remove(option)
if option not in texi_options:
self.missing_texi_options.add(option)
else:
texi_options.remove(option)
self.unknown_man_options = [option for option in man_options if option not in self.known_alternates]
self.unknown_texi_options = [option for option in texi_options if option not in self.known_alternates]
sep = "\n --"
if len(self.missing_baseline_tests):
print("Missing Baseline test for:%s%s\n" % (sep, sep.join(sorted(list(self.missing_baseline_tests)))))
if len(self.missing_man_options):
print("Missing man page entries for:%s%s\n" % (sep, sep.join(sorted(list(self.missing_man_options)))))
if len(self.missing_texi_options):
print("Missing texi entries for:%s%s\n" % (sep, sep.join(sorted(list(self.missing_texi_options)))))
if len(self.unknown_man_options):
print("Man page entry for unknown options:%s%s" % (sep, sep.join(sorted(list(self.unknown_man_options)))))
if len(self.unknown_texi_options):
print("Texi entry for unknown option:%s%s" % (sep, sep.join(sorted(list(self.unknown_texi_options)))))
errors = len(self.missing_baseline_tests) + len(self.missing_man_options) + len(self.missing_baseline_tests)
return errors
if __name__ == "__main__":
def getargs():
parser = argparse.ArgumentParser(prog='CheckTests', description='Check that ledger options are tested and documented', prefix_chars='-')
parser.add_argument('-l', '--ledger',
dest='ledger',
type=str,
action='store',
required=True,
help='the path to the ledger executable to test with')
parser.add_argument('-s', '--source',
dest='source',
type=str,
action='store',
required=True,
help='the path to the top level ledger source directory')
return parser.parse_args()
args = getargs()
script = CheckTests(args)
status = script.main()
sys.exit(status)

78
test/CheckTexinfo.py Executable file
View file

@ -0,0 +1,78 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
import sys
import re
import os
import argparse
from os.path import *
from subprocess import Popen, PIPE
from CheckOptions import CheckOptions
class CheckTexinfo (CheckOptions):
def __init__(self, args):
CheckOptions.__init__(self, args)
self.option_pattern = '@item --([-A-Za-z]+).*@c option'
self.source_file = join(self.source, 'doc', 'ledger3.texi')
self.source_type = 'texinfo'
def find_options(self, filename):
options = set()
state_normal = 0
state_option_table = 1
state = state_normal
option = None
opt_doc = str()
item_regex = re.compile('^@item --([-A-Za-z]+)')
itemx_regex = re.compile('^@itemx')
fix_regex = re.compile('FIX')
for line in open(filename):
line = line.strip()
if state == state_normal:
if line == '@ftable @option':
state = state_option_table
elif state == state_option_table:
if line == '@end ftable':
if option and len(opt_doc) and not fix_regex.search(opt_doc):
options.add(option)
state = state_normal
option = None
continue
match = item_regex.match(line)
if match:
if option and len(opt_doc) and not fix_regex.search(opt_doc):
options.add(option)
option = match.group(1)
opt_doc = str()
elif itemx_regex.match(line):
continue
else:
opt_doc += line
return options
if __name__ == "__main__":
def getargs():
parser = argparse.ArgumentParser(prog='CheckTexinfo',
description='Check that ledger options are documented in the texinfo manual')
parser.add_argument('-l', '--ledger',
dest='ledger',
type=str,
action='store',
required=True,
help='the path to the ledger executable to test with')
parser.add_argument('-s', '--source',
dest='source',
type=str,
action='store',
required=True,
help='the path to the top level ledger source directory')
return parser.parse_args()
args = getargs()
script = CheckTexinfo(args)
status = script.main()
sys.exit(status)

View file

@ -184,7 +184,7 @@ class DocTests:
command[findex] = test_input_dir + test_file
error = False
try:
verify = subprocess.check_output(command)
verify = subprocess.check_output(command, stderr=subprocess.STDOUT)
except:
verify = str()
error = True
@ -221,7 +221,8 @@ class DocTests:
if __name__ == "__main__":
def getargs():
parser = argparse.ArgumentParser(prog='DocTests', description='Test ledger examples from the documentation', prefix_chars='-')
parser = argparse.ArgumentParser(prog='DocTests',
description='Test and validate ledger examples from the texinfo manual')
parser.add_argument('-v', '--verbose',
dest='verbose',
action='count',

11
test/regress/1106.test Normal file
View file

@ -0,0 +1,11 @@
2015/01/20 Payee
Assets:Cash ¤ 12,34
Expenses:Food
test -F "»%(trim(' Trimmed '))«\n" reg expenses
»Trimmed«
end test
test -F "»%(trim('Trimmed'))«\n" reg expenses
»Trimmed«
end test