Merge commit 'bec52e3221d74ce3bf1383b245beae5a6214e387' into next
Conflicts: lisp/ledger-reconcile.el
This commit is contained in:
commit
28b0d67567
18 changed files with 752 additions and 549 deletions
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
14
doc/ledger.1
14
doc/ledger.1
|
|
@ -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
|
||||
|
|
|
|||
761
doc/ledger3.texi
761
doc/ledger3.texi
File diff suppressed because it is too large
Load diff
8
doc/version.texi.in
Normal file
8
doc/version.texi.in
Normal 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}
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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@
|
||||
|
|
|
|||
|
|
@ -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
55
test/CheckBaselineTests.py
Executable 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
44
test/CheckManpage.py
Executable 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
93
test/CheckOptions.py
Executable 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
|
||||
|
|
@ -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
78
test/CheckTexinfo.py
Executable 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)
|
||||
|
|
@ -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
11
test/regress/1106.test
Normal 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
|
||||
Loading…
Add table
Reference in a new issue