From df9771f7a9286e1f5e733ad94ab266c799700f88 Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Tue, 4 Feb 2014 21:58:03 +0100 Subject: [PATCH 01/17] Bump copyright notice to 2014 in the documentation --- doc/ledger3.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 2b962180..c85f8446 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -22,7 +22,7 @@ @copying -Copyright @copyright{} 2003–2013, John Wiegley. All rights reserved. +Copyright @copyright{} 2003–2014, John Wiegley. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are From fbbb379fe08b051b40c071041108a2526533f417 Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Fri, 7 Feb 2014 00:21:38 +0100 Subject: [PATCH 02/17] Check examples in documentation when running tests The DocTests.py script will parse a given texinfo file for specially marked examples, run the ledger command from the example, and check the result against the example output from the documentation. --- doc/ledger3.texi | 75 ++++++++++++++++-------- test/CMakeLists.txt | 12 ++++ test/DocTests.py | 139 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 23 deletions(-) create mode 100755 test/DocTests.py diff --git a/doc/ledger3.texi b/doc/ledger3.texi index c85f8446..acf58698 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -346,13 +346,13 @@ for each. To find the balances of all of your accounts, run this command: -@smallexample +@smallexample @c command:1071890 $ ledger -f drewr3.dat balance @end smallexample Ledger will generate: -@smallexample +@smallexample @c output:1071890 $ -3,804.00 Assets $ 1,396.00 Checking $ 30.00 Business @@ -381,8 +381,11 @@ pare this down to show only the accounts you want. A more useful report is to show only your Assets and Liabilities: -@smallexample +@smallexample @c command:5BF4D8E $ ledger -f drewr3.dat balance Assets Liabilities +@end smallexample + +@smallexample @c output:5BF4D8E $ -3,804.00 Assets $ 1,396.00 Checking $ 30.00 Business @@ -402,16 +405,16 @@ $ ledger -f drewr3.dat balance Assets Liabilities To show all transactions and a running total: -@smallexample +@smallexample @c command:66E3A2C $ ledger -f drewr3.dat register @end smallexample @noindent Ledger will generate: -@smallexample +@smallexample @c output:66E3A2C 10-Dec-01 Checking balance Assets:Checking $ 1,000.00 $ 1,000.00 - Equity:Opening Balances $ -1,000.00 0 + Equit:Opening Balances $ -1,000.00 0 10-Dec-20 Organic Co-op Expense:Food:Groceries $ 37.50 $ 37.50 Expense:Food:Groceries $ 37.50 $ 75.00 Expense:Food:Groceries $ 37.50 $ 112.50 @@ -450,8 +453,11 @@ interested in seeing transactions for: @cindex accounts, limiting by @cindex limiting by accounts -@smallexample +@smallexample @c command:96B0EB3 $ ledger -f drewr3.dat register Groceries +@end smallexample + +@smallexample @c output:96B0EB3 10-Dec-20 Organic Co-op Expense:Food:Groceries $ 37.50 $ 37.50 Expense:Food:Groceries $ 37.50 $ 75.00 Expense:Food:Groceries $ 37.50 $ 112.50 @@ -465,8 +471,11 @@ $ ledger -f drewr3.dat register Groceries @noindent Which matches the balance reported for the @samp{Groceries} account: -@smallexample +@smallexample @c command:AECD64E $ ledger -f drewr3.dat balance Groceries +@end smallexample + +@smallexample @c output:AECD64E $ 334.00 Expenses:Food:Groceries @end smallexample @@ -474,8 +483,11 @@ $ ledger -f drewr3.dat balance Groceries If you would like to find transaction to only a certain payee use @samp{payee} or @samp{@@}: -@smallexample +@smallexample @c command:C6BC57E $ ledger -f drewr3.dat register payee "Organic" +@end smallexample + +@smallexample @c output:C10BC57E 10-Dec-20 Organic Co-op Expense:Food:Groceries $ 37.50 $ 37.50 Expense:Food:Groceries $ 37.50 $ 75.00 Expense:Food:Groceries $ 37.50 $ 112.50 @@ -497,8 +509,11 @@ a check to clear, but you should treat it as money spent. The @command{cleared} report will not format correctly for accounts that contain multiple commodities): -@smallexample +@smallexample @c command:B86F6A6 $ ledger -f drewr3.dat cleared +@end smallexample + +@smallexample @c output:B86F6A6 $ -3,804.00 $ 775.00 Assets $ 1,396.00 $ 775.00 10-Dec-20 Checking $ 30.00 0 Business @@ -517,8 +532,8 @@ $ ledger -f drewr3.dat cleared $ -20.00 0 MasterCard $ 200.00 0 Mortgage:Principal $ -243.60 0 Tithe ----------------- ---------------- --------- - $ -243.60 0 +---------------- ---------------- --------- + $ -243.60 0 @end smallexample @noindent @@ -3826,8 +3841,11 @@ note the implicit logical and between @samp{Auto} and If you want the entire contents of a branch of your account tree, use the highest common name in the branch: -@smallexample +@smallexample @c command:B0468E1 $ ledger balance -f drewr3.dat Income +@end smallexample + +@smallexample @c output:B0468E1 $ -2,030.00 Income $ -2,000.00 Salary $ -30.00 Sales @@ -3838,15 +3856,25 @@ $ ledger balance -f drewr3.dat Income You can use general regular expressions in nearly anyplace Ledger needs a string: -@smallexample +@smallexample @c command:EAE389F $ ledger balance -f drewr3.dat ^Bo +@end smallexample +@smallexample @c output:EAE389F +@end smallexample + +This first example looks for any account starting with @samp{Bo}, of +which there are none. + +@smallexample @c command:E2AF6AD $ ledger balance -f drewr3.dat Bo +@end smallexample + +@smallexample @c output:E2AF6AD $ 20.00 Expenses:Books @end smallexample -The first example looks for any account starting with @samp{Bo}, of -which there are none. The second looks for any account with @samp{Bo}, -which is @samp{Expenses:Books}. +This second example looks for any account with @samp{Bo}, which is +@samp{Expenses:Books}. @cindex limit by payees @findex --limit @var{EXPR} @@ -5596,22 +5624,23 @@ Format Codes}). @item --master-account @var{STR} Prepend all account names with the argument. -@smallexample -$ ledger -f test/input/drewr3.dat bal --master-account HUMBUG +@smallexample @c command:A76BB56 +$ ledger -f drewr3.dat bal --no-total --master-account HUMBUG +@end smallexample + +@smallexample @c output:A76BB56 0 HUMBUG $ -3,804.00 Assets $ 1,396.00 Checking $ 30.00 Business $ -5,200.00 Savings - $ 20.00 Books $ -1,000.00 Equity:Opening Balances - $ 6,634.00 Expenses - $ 11,000.00 Auto + $ 6,654.00 Expenses + $ 5,500.00 Auto $ 20.00 Books $ 300.00 Escrow $ 334.00 Food:Groceries $ 500.00 Interest:Mortgage - $ -5,520.00 Assets:Checking $ -2,030.00 Income $ -2,000.00 Salary $ -30.00 Sales diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 94ce0a0a..159ab5be 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,4 +38,16 @@ add_subdirectory(manual) add_subdirectory(baseline) add_subdirectory(regress) +if(PYTHONINTERP_FOUND) + set(_class DocTests) + file(GLOB ${_class}_TESTS ${PROJECT_SOURCE_DIR}/doc/*.texi) + foreach(TestFile ${${_class}_TESTS}) + get_filename_component(TestFile_Name ${TestFile} NAME_WE) + add_test(${_class}Test_${TestFile_Name} + ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test/DocTests.py + ${LEDGER_LOCATION} ${TestFile}) + set_target_properties(check PROPERTIES DEPENDS ${_class}Test_${TestFile_Name}) + endforeach() +endif() + ### CMakeLists.txt ends here diff --git a/test/DocTests.py b/test/DocTests.py new file mode 100755 index 00000000..daac1db5 --- /dev/null +++ b/test/DocTests.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import re +import sys +import hashlib +import subprocess + +from difflib import unified_diff + +class DocTests: + def __init__(self, argv): + if not os.path.isfile(argv[1]): + print "Cannot find ledger at '%s'" % argv[1] + sys.exit(1) + if not os.path.isfile(argv[2]): + print "Cannot find source path at '%s'" % argv[2] + sys.exit(1) + + self.ledger = os.path.abspath(argv[1]) + self.sourcepath = os.path.abspath(argv[2]) + scriptpath = os.path.dirname(os.path.realpath(__file__)) + self.verbose = False + self.debug = False + self.examples = dict() + self.testin_token = 'command' + self.testout_token = 'output' + + def read_example(self): + endexample = re.compile(r'^@end\s+smallexample\s*$') + example = str() + while True: + line = self.file.readline() + self.current_line += 1 + if len(line) <= 0 or endexample.match(line): break + example += line + return example + + def test_id(self, example): + return hashlib.sha1(example.rstrip()).hexdigest()[0:7].upper() + + def find_examples(self): + startexample = re.compile(r'^@smallexample\s+@c\s+(%s|%s)(?::([\dA-Fa-f]+))?' % (self.testin_token, self.testout_token)) + while True: + line = self.file.readline() + self.current_line += 1 + if len(line) <= 0: break + + startmatch = startexample.match(line) + if (startmatch): + test_begin_pos = self.file.tell() + test_begin_line = self.current_line + test_kind = startmatch.group(1) + test_id = startmatch.group(2) + example = self.read_example() + test_end_pos = self.file.tell() + test_end_line = self.current_line + + if not test_id: + print >> sys.stderr, 'Example', test_kind, 'in line', test_begin_line, 'is missing id.' + test_id = self.test_id(example) + if test_kind == self.testin_token: + print >> sys.stderr, 'Use', self.test_id(example) + elif test_kind == self.testin_token and test_id != self.test_id(example): + print >> sys.stderr, 'Expected test id', test_id, 'for example' \ + , test_kind, 'on line', test_begin_line, 'to be', self.test_id(example) + + try: + self.examples[test_id] + except KeyError: + self.examples[test_id] = dict() + + self.examples[test_id][test_kind] = { + 'bpos': test_begin_pos, + 'epos': test_end_pos, + 'blin': test_begin_line, + 'elin': test_end_line, + test_kind: example, + } + + def test_examples(self): + failed = 0 + for test_id in self.examples: + example = self.examples[test_id] + try: + command = example[self.testin_token][self.testin_token] + except KeyError: + command = None + try: + output = example[self.testout_token][self.testout_token] + except KeyError: + output = None + + if command and output: + command = command.rstrip().split() + if command[0] == '$': command.remove('$') + index = command.index('ledger') + command[index] = self.ledger + command.insert(index+1, '--init-file') + command.insert(index+2, '/dev/null') + scriptpath = os.path.dirname(os.path.realpath(__file__)) + test_input_dir = scriptpath + '/../test/input/' + for i, arg in enumerate(command): + if '.dat' in arg or '.ledger' in arg: + if os.path.exists(test_input_dir + arg): + command[i] = test_input_dir + arg + try: + verify = subprocess.check_output(command)#.decode('utf-8') + except: + verify = str() + valid = (output == verify) + if self.verbose: + print test_id, ':', u'Passed' if valid else u'FAILED' + else: + sys.stdout.write('.' if valid else 'E') + + if not valid: + failed += 1 + if self.debug: + print ' '.join(command) + for line in unified_diff(output.split('\n'), verify.split('\n'), fromfile='generated', tofile='expected'): + print(line) + print + print + return failed + + def main(self): + self.file = open(self.sourcepath) + self.current_line = 0 + self.find_examples() + failed_examples = self.test_examples() + self.file.close() + return failed_examples + +if __name__ == "__main__": + script = DocTests(sys.argv) + status = script.main() + sys.exit(status) From 77a9317cf47d3b808db409961a3e75b154e12304 Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Fri, 7 Feb 2014 00:23:08 +0100 Subject: [PATCH 03/17] Make spellcheck.sh tool callable from anywhere --- tools/spellcheck.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/spellcheck.sh b/tools/spellcheck.sh index ae86a3d6..7a51a18d 100755 --- a/tools/spellcheck.sh +++ b/tools/spellcheck.sh @@ -1,3 +1,3 @@ #!/bin/sh -aspell check --mode=texinfo ledger3.texi \ No newline at end of file +aspell check --mode=texinfo $(dirname $0)/../doc/ledger3.texi From c73ba9d0751cef3e8aa152ba9bd1ee09bf86449f Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Fri, 7 Feb 2014 00:23:22 +0100 Subject: [PATCH 04/17] Correct spelling mistakes --- doc/ledger3.texi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index c85f8446..646cfbf2 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -2979,7 +2979,7 @@ after the amount or amount expression: @end smallexample When you do this, since Ledger can now figure out the balancing amount -from the first posting's cost, you can elide the otheramount: +from the first posting's cost, you can elide the other amount: @smallexample 2012-03-10 My Broker @@ -5339,7 +5339,7 @@ Specify the format for the plot output. @item --display @var{EXPR} @itemx -d @var{EXPR} -Display only posting that meet the criterias in the @var{EXPR}. +Display only posting that meet the criteria in the @var{EXPR}. @item --date-format @var{DATE_FORMAT} @itemx -y @var{DATE_FORMAT} From a1cc8ca15ae1761fbcb4c6ec9f8af82e8ab411ab Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Fri, 7 Feb 2014 18:38:43 +0100 Subject: [PATCH 05/17] Add support to check documentation examples with inline data --- doc/ledger3.texi | 58 +++++++++++++++++++++++++++--------------------- test/DocTests.py | 45 +++++++++++++++++++++++++++++-------- 2 files changed, 69 insertions(+), 34 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index acf58698..48fafc13 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -1503,7 +1503,7 @@ entry. For example, the following entries reflect transaction made for a business trip to Europe from the US: -@smallexample +@smallexample @c input:82150D9 2011/09/23 Cash in Munich Assets:Cash E50.00 Assets:Checking $-66.00 @@ -1519,8 +1519,11 @@ spent on Dinner in Munich. Running a ledger balance report shows: -@smallexample +@smallexample @c command:82150D9 $ ledger -f example.dat bal +@end smallexample + +@smallexample @c output:82150D9 $-66.00 E15.00 Assets E15.00 Cash @@ -3641,7 +3644,7 @@ the money to be evenly distributed over the next six months so that your monthly budgets gradually take a hit for the vegetables you'll pick up from the co-op, even though you've already paid for them. -@smallexample +@smallexample @c input:6453542 2008/10/16 * (2090) Bountiful Blessings Farm Expenses:Food:Groceries $ 37.50 ; [=2008/10/01] Expenses:Food:Groceries $ 37.50 ; [=2008/11/01] @@ -3659,15 +3662,17 @@ really knows that it debited $225 this month. And using @option{--effective} option, initial date will be overridden by effective dates. -@smallexample +@smallexample @c command:6453542 $ ledger --effective register Groceries +@end smallexample -08-Oct-01 Bountiful Blessi.. Expe:Food:Groceries $ 37.50 $ 37.50 -08-Nov-01 Bountiful Blessi.. Expe:Food:Groceries $ 37.50 $ 75.00 -08-Dec-01 Bountiful Blessi.. Expe:Food:Groceries $ 37.50 $ 112.50 -09-Jan-01 Bountiful Blessi.. Expe:Food:Groceries $ 37.50 $ 150.00 -09-Feb-01 Bountiful Blessi.. Expe:Food:Groceries $ 37.50 $ 187.50 -09-Mar-01 Bountiful Blessi.. Expe:Food:Groceries $ 37.50 $ 225.00 +@smallexample @c output:6453542 +08-Oct-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 37.50 +08-Nov-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 75.00 +08-Dec-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 112.50 +09-Jan-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 150.00 +09-Feb-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 187.50 +09-Mar-01 Bountiful Blessings.. Expense:Food:Groceries $ 37.50 $ 225.00 @end smallexample @node Periodic Transactions, Concrete Example of Automated Transactions, Effective Dates, Automated Transactions @@ -3792,14 +3797,14 @@ options. The balance report is the most commonly used report. The simplest invocation is: -@smallexample +@smallexample @c command:1D00D56 $ ledger balance -f drewr3.dat @end smallexample @noindent which will print the balances of every account in your journal. -@smallexample +@smallexample @c output:1D00D56 $ -3,804.00 Assets $ 1,396.00 Checking $ 30.00 Business @@ -3826,8 +3831,11 @@ Most times this is more than you want. Limiting the results to specific accounts is as easy as entering the names of the accounts after the command. -@smallexample +@smallexample @c command:06B2AD4 $ ledger balance -f drewr3.dat Auto MasterCard +@end smallexample + +@smallexample @c output:06B2AD4 $ 5,500.00 Expenses:Auto $ -20.00 Liabilities:MasterCard -------------------- @@ -4992,7 +5000,7 @@ earlier postings. Here's how it works: Say you currently have this posting in your ledger file: -@smallexample +@smallexample @c input:03ACB97 2004/03/15 * Viva Italiano Expenses:Food $12.45 Expenses:Tips $2.55 @@ -5003,17 +5011,17 @@ Now it's @samp{2004/4/9}, and you've just eating at @samp{Viva Italiano} again. The exact amounts are different, but the overall form is the same. With the @command{xact} command you can type: -@smallexample +@smallexample @c command:03ACB97 $ ledger xact 2004/4/9 viva food 11 tips 2.50 @end smallexample This produces the following output: -@smallexample +@smallexample @c output:03ACB97 2004/04/09 Viva Italiano - Expenses:Food $11.00 - Expenses:Tips $2.50 - Liabilities:MasterCard $-13.50 + Expenses:Food $11.00 + Expenses:Tips $2.50 + Liabilities:MasterCard @end smallexample It works by finding a past posting matching the regular expression @@ -6491,7 +6499,7 @@ In the balance report, it shows all the accounts affected by transactions having a related posting. For example, if a file had this transaction: -@smallexample +@smallexample @c input:94C5675 2004/03/20 Safeway Expenses:Food $65.00 Expenses:Cash $20.00 @@ -6500,16 +6508,16 @@ this transaction: And the register command was: -@smallexample -$ ledger -r register food +@smallexample @c command:94C5675 +$ ledger -f example.dat -r register food @end smallexample The following would be output, showing the postings related to the posting that matched: -@smallexample -2004/03/20 Safeway Expenses:Cash $-20.00 $-20.00 - Assets:Checking $85.00 $65.00 +@smallexample @c output:94C5675 +04-Mar-20 Safeway Expenses:Cash $20.00 $20.00 + Assets:Checking $-85.00 $-65.00 @end smallexample @item --budget diff --git a/test/DocTests.py b/test/DocTests.py index daac1db5..eb1a0205 100755 --- a/test/DocTests.py +++ b/test/DocTests.py @@ -26,6 +26,8 @@ class DocTests: self.examples = dict() self.testin_token = 'command' self.testout_token = 'output' + self.testdat_token = 'input' + self.test_files = list() def read_example(self): endexample = re.compile(r'^@end\s+smallexample\s*$') @@ -41,7 +43,8 @@ class DocTests: return hashlib.sha1(example.rstrip()).hexdigest()[0:7].upper() def find_examples(self): - startexample = re.compile(r'^@smallexample\s+@c\s+(%s|%s)(?::([\dA-Fa-f]+))?' % (self.testin_token, self.testout_token)) + startexample = re.compile(r'^@smallexample\s+@c\s+(%s|%s|%s)(?::([\dA-Fa-f]+))?' + % (self.testin_token, self.testout_token, self.testdat_token)) while True: line = self.file.readline() self.current_line += 1 @@ -87,11 +90,17 @@ class DocTests: command = example[self.testin_token][self.testin_token] except KeyError: command = None + try: output = example[self.testout_token][self.testout_token] except KeyError: output = None + try: + input = example[self.testdat_token][self.testdat_token] + except KeyError: + input = None + if command and output: command = command.rstrip().split() if command[0] == '$': command.remove('$') @@ -99,19 +108,37 @@ class DocTests: command[index] = self.ledger command.insert(index+1, '--init-file') command.insert(index+2, '/dev/null') - scriptpath = os.path.dirname(os.path.realpath(__file__)) - test_input_dir = scriptpath + '/../test/input/' - for i, arg in enumerate(command): - if '.dat' in arg or '.ledger' in arg: - if os.path.exists(test_input_dir + arg): - command[i] = test_input_dir + arg try: - verify = subprocess.check_output(command)#.decode('utf-8') + findex = command.index('-f') + except ValueError: + try: + findex = command.index('--file') + except ValueError: + findex = index+1 + command.insert(findex, '--file') + command.insert(findex+1, test_id + '.dat') + + if findex: + scriptpath = os.path.dirname(os.path.realpath(__file__)) + test_input_dir = scriptpath + '/../test/input/' + test_file = command[findex+1] + test_file_created = False + if not os.path.exists(test_file): + if input: + test_file_created = True + with open(test_file, 'w') as f: + f.write(input) + elif os.path.exists(test_input_dir + test_file): + command[findex+1] = test_input_dir + test_file + try: + verify = subprocess.check_output(command) except: verify = str() + if test_file_created: + os.remove(test_file) valid = (output == verify) if self.verbose: - print test_id, ':', u'Passed' if valid else u'FAILED' + print test_id, ':', 'Passed' if valid else 'FAILED' else: sys.stdout.write('.' if valid else 'E') From 960ebc2a572e3ff90d2b95c54f618430df5db351 Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Sun, 9 Feb 2014 07:20:03 +0100 Subject: [PATCH 06/17] Print summary list of failed doc tests if any --- test/DocTests.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/DocTests.py b/test/DocTests.py index eb1a0205..736be6c7 100755 --- a/test/DocTests.py +++ b/test/DocTests.py @@ -83,7 +83,7 @@ class DocTests: } def test_examples(self): - failed = 0 + failed = set() for test_id in self.examples: example = self.examples[test_id] try: @@ -143,14 +143,17 @@ class DocTests: sys.stdout.write('.' if valid else 'E') if not valid: - failed += 1 if self.debug: + failed.add(test_id) print ' '.join(command) for line in unified_diff(output.split('\n'), verify.split('\n'), fromfile='generated', tofile='expected'): print(line) print print - return failed + if len(failed) > 0: + print "\nThe following examples failed:" + print " ", "\n ".join(failed) + return len(failed) def main(self): self.file = open(self.sourcepath) From c566afe3b1b24d3efea0e14c17a45d0987f42f16 Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Sun, 9 Feb 2014 07:28:58 +0100 Subject: [PATCH 07/17] Add proper argument parsing to DocTests.py --- test/CMakeLists.txt | 2 +- test/DocTests.py | 54 +++++++++++++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 159ab5be..796ef0a2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -45,7 +45,7 @@ if(PYTHONINTERP_FOUND) get_filename_component(TestFile_Name ${TestFile} NAME_WE) add_test(${_class}Test_${TestFile_Name} ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test/DocTests.py - ${LEDGER_LOCATION} ${TestFile}) + --ledger ${LEDGER_LOCATION} --file ${TestFile}) set_target_properties(check PROPERTIES DEPENDS ${_class}Test_${TestFile_Name}) endforeach() endif() diff --git a/test/DocTests.py b/test/DocTests.py index 736be6c7..a50ec03d 100755 --- a/test/DocTests.py +++ b/test/DocTests.py @@ -5,29 +5,23 @@ import os import re import sys import hashlib +import argparse import subprocess from difflib import unified_diff class DocTests: - def __init__(self, argv): - if not os.path.isfile(argv[1]): - print "Cannot find ledger at '%s'" % argv[1] - sys.exit(1) - if not os.path.isfile(argv[2]): - print "Cannot find source path at '%s'" % argv[2] - sys.exit(1) + def __init__(self, args): + scriptpath = os.path.dirname(os.path.realpath(__file__)) + self.ledger = os.path.abspath(args.ledger) + self.sourcepath = os.path.abspath(args.file) + self.verbose = args.verbose - self.ledger = os.path.abspath(argv[1]) - self.sourcepath = os.path.abspath(argv[2]) - scriptpath = os.path.dirname(os.path.realpath(__file__)) - self.verbose = False - self.debug = False - self.examples = dict() - self.testin_token = 'command' + self.examples = dict() + self.test_files = list() + self.testin_token = 'command' self.testout_token = 'output' self.testdat_token = 'input' - self.test_files = list() def read_example(self): endexample = re.compile(r'^@end\s+smallexample\s*$') @@ -137,19 +131,20 @@ class DocTests: if test_file_created: os.remove(test_file) valid = (output == verify) - if self.verbose: + if self.verbose > 0: print test_id, ':', 'Passed' if valid else 'FAILED' else: sys.stdout.write('.' if valid else 'E') if not valid: - if self.debug: failed.add(test_id) + if self.verbose > 1: print ' '.join(command) for line in unified_diff(output.split('\n'), verify.split('\n'), fromfile='generated', tofile='expected'): print(line) print - print + if not self.verbose: + print if len(failed) > 0: print "\nThe following examples failed:" print " ", "\n ".join(failed) @@ -164,6 +159,27 @@ class DocTests: return failed_examples if __name__ == "__main__": - script = DocTests(sys.argv) + def getargs(): + parser = argparse.ArgumentParser(description='DocTests', prefix_chars='-') + parser.add_argument('-v', '--verbose', + dest='verbose', + action='count', + help='be verbose. Add -vv for more verbosity') + 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('-f', '--file', + dest='file', + type=str, + action='store', + required=True, + help='the texinfo documentation file to run the examples from') + return parser.parse_args() + + args = getargs() + script = DocTests(args) status = script.main() sys.exit(status) From a2f86c85df7ac9f00facefbc9318e2a06e41b73b Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Sun, 9 Feb 2014 07:54:27 +0100 Subject: [PATCH 08/17] Add explanation on how to validate documentation examples by specially marking @smallexample, which will be used by DocTests.py --- doc/ledger3.texi | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 48fafc13..425dd5bc 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -20,6 +20,67 @@ @c Restructuring manual ideas @c http://beyondgrep.com/documentation/ack-2.04-man.html +@c How to make documented ledger examples validate automatically. +@c +@c The test/DocTests.py script will be run along the with the other +@c tests when using ctest or acprep check. +@c The script parses the texinfo file and looks for three kinds of +@c specially marked @smallexamples, then it will run the ledger +@c command from the exmaple, and compare the results with the output +@c from the documentation. +@c +@c To specially mark a @smallexample append @c command:UUID, where +@c UUID is the first 7 digits from the commands sha1sum, e.g.: +@c +@c @smallexample @c command:CDE330A +@c $ ledger -f sample.dat reg expenses +@c @end smallexample +@c +@c Then DocTests.py will look for corresponding documented output, +@c which may appear anywhere in the file, and is marked with +@c @smallexample @c output:UUID where UUID is the UUID from the +@c corresponding ledger command example, e.g.: +@c +@c @smallexample @c output:CDE330A +@c 04-May-27 Book Store Expenses:Books $20.00 $20.00 +@c Expenses:Cards $40.00 $60.00 +@c Expenses:Docs $30.00 $90.0 +@c @end smallexample +@c +@c Now where does this data in sample.dat come from? +@c DocTests.py is a bit smart about ledger's file argument, since +@c it will check if the given filename exists in the test/input/ +@c directory. +@c +@c Sometimes the journal data for an example is specified within +@c the documentation itself, in that case the journal example data +@c needs to be specially marked as well using @smallexample @c input:UUID, +@c again with the UUID being the UUID of the corresponding ledger example +@c command, e.g.: +@c +@c @smallexample @c input:35CB2A3 +@c 2014/02/09 The Italian Place +@c Expenses:Food:Dining $ 36.84 +@c Assets:Cash +@c @end smallexample +@c +@c @smallexample @c command:35CB2A3 +@c $ ledger -f inline.dat accounts +@c @end smallexample +@c +@c @smallexample @c output:35CB2A3 +@c Assets:Cash +@c Expenses:Food:Dining +@c @end smallexample +@c +@c Additionally DocTests.py will pass --init-file /dev/null to ledger to +@c ignore any default arguments to ledger the user running the tests +@c has configured. +@c +@c To manually run the tests in this file run: +@c $ ./test/DocTests.py -vv --ledger ./ledger --file ./test/ledger3.texi + + @copying Copyright @copyright{} 2003–2014, John Wiegley. All rights reserved. From e7cfaa1e652ac989ac33bda824b53dee401c10b3 Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Wed, 12 Feb 2014 10:38:07 +0100 Subject: [PATCH 09/17] Fix apply_year_directive Using the Y 2014 syntax works fine, but using apply year 2014 resulted in the following error: Error: Year is out of valid range: 1400..10000 since part of the given year string was chopped off. --- src/textual.cc | 4 ++-- test/regress/6E7C2DF9.test | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 test/regress/6E7C2DF9.test diff --git a/src/textual.cc b/src/textual.cc index 5536359d..d8648c93 100644 --- a/src/textual.cc +++ b/src/textual.cc @@ -416,7 +416,7 @@ void instance_t::read_next_directive(bool& error_flag) price_xact_directive(line); break; case 'Y': // set the current year - apply_year_directive(line); + apply_year_directive(line + 1); break; } } @@ -865,7 +865,7 @@ void instance_t::apply_year_directive(char * line) // This must be set to the last day of the year, otherwise partial // dates like "11/01" will refer to last year's november, not the // current year. - unsigned short year(lexical_cast(skip_ws(line + 1))); + unsigned short year(lexical_cast(skip_ws(line))); DEBUG("times.epoch", "Setting current year to " << year); epoch = datetime_t(date_t(year, 12, 31)); } diff --git a/test/regress/6E7C2DF9.test b/test/regress/6E7C2DF9.test new file mode 100644 index 00000000..c55fbdcc --- /dev/null +++ b/test/regress/6E7C2DF9.test @@ -0,0 +1,24 @@ +Y 2010 +10/10 * TwentyTen + Account:Ten $ 10.10 + Assets:Cash + +apply year 2011 +11/11 * TwentyEleven + Account:Eleven $ 11.11 + Assets:Cash + +2012/12/12 * TwentyTwelve + Account:Twelve $ 12.12 + Assets:Cash + +11/11 * TwentyEleven Again + Account:Eleven $ 11.11 + Assets:Cash + +test reg --sort date account +10-Oct-10 TwentyTen Account:Ten $ 10.10 $ 10.10 +11-Nov-11 TwentyEleven Account:Eleven $ 11.11 $ 21.21 +11-Nov-11 TwentyEleven Again Account:Eleven $ 11.11 $ 32.32 +12-Dec-12 TwentyTwelve Account:Twelve $ 12.12 $ 44.44 +end test From d5b5ea02138107918642532a266550d1ab4122d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kan-Ru=20Chen=20=28=E9=99=B3=E4=BE=83=E5=A6=82=29?= Date: Thu, 13 Feb 2014 18:40:06 +0800 Subject: [PATCH 10/17] Correctly justify Unicode characters in terminal Many Unicode characters take more spaces than one ASCII character. For example, Chinese characters are two characters wide when using monospace font in terminal. This patch use wcwidth of Markus Kuhn to count the correct width for justification. --- src/CMakeLists.txt | 3 +- src/unistring.h | 12 +- src/wcwidth.c.patch | 74 ++++++++++ src/wcwidth.cc | 319 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 src/wcwidth.c.patch create mode 100644 src/wcwidth.cc diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7bf7aad6..8359308e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -49,7 +49,8 @@ set(LEDGER_SOURCES times.cc error.cc utils.cc - strptime.cc) + strptime.cc + wcwidth.cc) if(HAVE_BOOST_PYTHON) list(APPEND LEDGER_SOURCES diff --git a/src/unistring.h b/src/unistring.h index 8e963f37..7d78d134 100644 --- a/src/unistring.h +++ b/src/unistring.h @@ -44,6 +44,8 @@ namespace ledger { +int mk_wcwidth_cjk(boost::uint32_t ucs); + /** * @class unistring * @@ -81,6 +83,14 @@ public: return utf32chars.size(); } + std::size_t width() const { + std::size_t width = 0; + foreach (const boost::uint32_t& ch, utf32chars) { + width += mk_wcwidth_cjk(ch); + } + return width; + } + std::string extract(const std::string::size_type begin = 0, const std::string::size_type len = 0) const { @@ -133,7 +143,7 @@ inline void justify(std::ostream& out, unistring temp(str); - int spacing = width - int(temp.length()); + int spacing = width - int(temp.width()); while (spacing-- > 0) out << ' '; diff --git a/src/wcwidth.c.patch b/src/wcwidth.c.patch new file mode 100644 index 00000000..5864a056 --- /dev/null +++ b/src/wcwidth.c.patch @@ -0,0 +1,74 @@ +--- wcwidth.c 2007-05-26 18:06:24.000000000 +0800 ++++ wcwidth.cc 2014-02-13 18:36:18.668331252 +0800 +@@ -59,15 +59,23 @@ + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +-#include ++/* This file is modified to work with C++ and present Unicode ++ * characters in uint32_t type. ++ */ ++ ++#include ++ ++namespace ledger { + +-struct interval { +- int first; +- int last; +-}; ++namespace { ++ struct interval { ++ int first; ++ int last; ++ }; ++} + + /* auxiliary function for binary search in interval table */ +-static int bisearch(wchar_t ucs, const struct interval *table, int max) { ++static int bisearch(boost::uint32_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + +@@ -119,7 +127,7 @@ + * in ISO 10646. + */ + +-int mk_wcwidth(wchar_t ucs) ++int mk_wcwidth(boost::uint32_t ucs) + { + /* sorted list of non-overlapping intervals of non-spacing characters */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ +@@ -204,7 +212,7 @@ + } + + +-int mk_wcswidth(const wchar_t *pwcs, size_t n) ++int mk_wcswidth(const boost::uint32_t *pwcs, size_t n) + { + int w, width = 0; + +@@ -227,7 +235,7 @@ + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +-int mk_wcwidth_cjk(wchar_t ucs) ++int mk_wcwidth_cjk(boost::uint32_t ucs) + { + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ +@@ -295,7 +303,7 @@ + } + + +-int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n) ++int mk_wcswidth_cjk(const boost::uint32_t *pwcs, size_t n) + { + int w, width = 0; + +@@ -307,3 +315,5 @@ + + return width; + } ++ ++} // namespace ledger diff --git a/src/wcwidth.cc b/src/wcwidth.cc new file mode 100644 index 00000000..71eaf6d6 --- /dev/null +++ b/src/wcwidth.cc @@ -0,0 +1,319 @@ +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +/* This file is modified to work with C++ and present Unicode + * characters in uint32_t type. + */ + +#include + +namespace ledger { + +namespace { + struct interval { + int first; + int last; + }; +} + +/* auxiliary function for binary search in interval table */ +static int bisearch(boost::uint32_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that wchar_t characters are encoded + * in ISO 10646. + */ + +int mk_wcwidth(boost::uint32_t ucs) +{ + /* sorted list of non-overlapping intervals of non-spacing characters */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + static const struct interval combining[] = { + { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, + { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, + { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, + { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, + { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, + { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, + { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, + { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, + { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, + { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, + { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, + { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, + { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, + { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, + { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, + { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, + { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, + { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, + { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, + { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, + { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, + { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, + { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, + { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, + { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, + { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, + { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, + { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, + { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, + { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, + { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, + { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, + { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, + { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, + { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, + { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, + { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, + { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, + { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, + { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, + { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, + { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, + { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, + { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, + { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, + { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, + { 0xE0100, 0xE01EF } + }; + + /* test for 8-bit control characters */ + if (ucs == 0) + return 0; + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) + return -1; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, combining, + sizeof(combining) / sizeof(struct interval) - 1)) + return 0; + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || /* Hangul Jamo init. consonants */ + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || /* CJK ... Yi */ + (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */ + (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */ + (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */ + (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */ + (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */ + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} + + +int mk_wcswidth(const boost::uint32_t *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + + +/* + * The following functions are the same as mk_wcwidth() and + * mk_wcswidth(), except that spacing characters in the East Asian + * Ambiguous (A) category as defined in Unicode Technical Report #11 + * have a column width of 2. This variant might be useful for users of + * CJK legacy encodings who want to migrate to UCS without changing + * the traditional terminal character-width behaviour. It is not + * otherwise recommended for general use. + */ +int mk_wcwidth_cjk(boost::uint32_t ucs) +{ + /* sorted list of non-overlapping intervals of East Asian Ambiguous + * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */ + static const struct interval ambiguous[] = { + { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 }, + { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 }, + { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 }, + { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 }, + { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED }, + { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA }, + { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 }, + { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B }, + { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 }, + { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 }, + { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 }, + { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE }, + { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 }, + { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA }, + { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 }, + { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB }, + { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB }, + { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 }, + { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 }, + { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 }, + { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 }, + { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 }, + { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 }, + { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 }, + { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC }, + { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 }, + { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 }, + { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 }, + { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 }, + { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 }, + { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 }, + { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B }, + { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 }, + { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 }, + { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E }, + { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 }, + { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 }, + { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F }, + { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 }, + { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF }, + { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B }, + { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 }, + { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 }, + { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 }, + { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 }, + { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 }, + { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 }, + { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 }, + { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 }, + { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F }, + { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF }, + { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD } + }; + + /* binary search in table of non-spacing characters */ + if (bisearch(ucs, ambiguous, + sizeof(ambiguous) / sizeof(struct interval) - 1)) + return 2; + + return mk_wcwidth(ucs); +} + + +int mk_wcswidth_cjk(const boost::uint32_t *pwcs, size_t n) +{ + int w, width = 0; + + for (;*pwcs && n-- > 0; pwcs++) + if ((w = mk_wcwidth_cjk(*pwcs)) < 0) + return -1; + else + width += w; + + return width; +} + +} // namespace ledger From 9c7e5a612c09ddae8ce36f012a7025f5a3eb780a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kan-Ru=20Chen=20=28=E9=99=B3=E4=BE=83=E5=A6=82=29?= Date: Thu, 13 Feb 2014 19:19:40 +0800 Subject: [PATCH 11/17] Use mk_wcwidth instead mk_wcwidth_cjk --- src/unistring.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/unistring.h b/src/unistring.h index 7d78d134..340115eb 100644 --- a/src/unistring.h +++ b/src/unistring.h @@ -44,7 +44,7 @@ namespace ledger { -int mk_wcwidth_cjk(boost::uint32_t ucs); +int mk_wcwidth(boost::uint32_t ucs); /** * @class unistring @@ -86,7 +86,7 @@ public: std::size_t width() const { std::size_t width = 0; foreach (const boost::uint32_t& ch, utf32chars) { - width += mk_wcwidth_cjk(ch); + width += mk_wcwidth(ch); } return width; } From 59fde5e777cd86f3bb6572f5cdfb06122d9da9d9 Mon Sep 17 00:00:00 2001 From: Matijs van Zuijlen Date: Mon, 17 Feb 2014 07:25:07 +0100 Subject: [PATCH 12/17] Implicit logical and is really a logical or --- doc/ledger3.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 5518a9a9..0d337b86 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -3904,7 +3904,7 @@ $ ledger balance -f drewr3.dat Auto MasterCard @end smallexample @noindent -note the implicit logical and between @samp{Auto} and +note the implicit logical or between @samp{Auto} and @samp{Mastercard}. If you want the entire contents of a branch of your account tree, use From 90988feebcd2f37c2715627d53b9c4a12dea51a5 Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Tue, 11 Feb 2014 09:47:38 +0100 Subject: [PATCH 13/17] DocTests: Allow multiple example inputs to be used as single ledger data for an example command --- doc/ledger3.texi | 13 ++++++++++--- test/DocTests.py | 5 +++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 0d337b86..ac5939aa 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -700,7 +700,7 @@ owe. ``Liabilities'' is just a more inclusive name for Debts. An Asset is typically increased by transferring money from an Income account, such as when you get paid. Here is a typical transaction: -@smallexample +@smallexample @c input:6B43DD4 2004/09/29 My Employer Assets:Checking $500.00 Income:Salary @@ -715,7 +715,7 @@ borrow money to buy something, or if you owe someone money. Here is an example of increasing a MasterCard liability by spending money with it: -@smallexample +@smallexample @c input:6B43DD4 2004/09/30 Restaurant Expenses:Dining $25.00 Liabilities:MasterCard @@ -729,10 +729,17 @@ offsets the value of your assets. The combined total of your Assets and Liabilities is your net worth. So to see your current net worth, use this command: -@smallexample +@smallexample @c command:6B43DD4 $ ledger balance ^assets ^liabilities @end smallexample +@smallexample @c output:6B43DD4 + $500.00 Assets:Checking + $-25.00 Liabilities:MasterCard +-------------------- + $475.00 +@end smallexample + In a similar vein, your Income accounts show up negative, because they transfer money @emph{from} an account in order to increase your assets. Your Expenses show up positive because that is where the diff --git a/test/DocTests.py b/test/DocTests.py index a50ec03d..60d8c637 100755 --- a/test/DocTests.py +++ b/test/DocTests.py @@ -68,6 +68,11 @@ class DocTests: except KeyError: self.examples[test_id] = dict() + try: + example = self.examples[test_id][test_kind][test_kind] + example + except KeyError: + pass + self.examples[test_id][test_kind] = { 'bpos': test_begin_pos, 'epos': test_end_pos, From 3d9faef448fcbf85bf565ffa9a5830a9fb67fcdd Mon Sep 17 00:00:00 2001 From: Alexis Hildebrandt Date: Sat, 15 Feb 2014 17:01:33 +0100 Subject: [PATCH 14/17] DocTests: Allow inline input to be used with different example commands --- doc/ledger3.texi | 89 +++++++++++++++++++++++++++++++++++++----------- test/DocTests.py | 73 +++++++++++++++++++++++++-------------- 2 files changed, 116 insertions(+), 46 deletions(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index ac5939aa..54441188 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -56,7 +56,8 @@ @c the documentation itself, in that case the journal example data @c needs to be specially marked as well using @smallexample @c input:UUID, @c again with the UUID being the UUID of the corresponding ledger example -@c command, e.g.: +@c command. If multiple inputs with the same UUID are found they will be +@c concatenated together and given as one set of data to the example command. @c @c @smallexample @c input:35CB2A3 @c 2014/02/09 The Italian Place @@ -72,7 +73,19 @@ @c Assets:Cash @c Expenses:Food:Dining @c @end smallexample -@c +@c +@c To use different example commands with the same input from the documentation +@c add with_input:UUID to the example command, where UUID is the UUID of the input, +@c e.g.: +@c +@c @smallexample @c command:94FD2B6,with_input:35CB2A3 +@c $ ledger -f inline.dat bal expenses +@c @end smallexample +@c +@c @smallexample @c output:94FD2B6 +@c $ 36.84 Expenses:Food:Dining +@c @end smallexample +@c @c Additionally DocTests.py will pass --init-file /dev/null to ledger to @c ignore any default arguments to ledger the user running the tests @c has configured. @@ -306,7 +319,7 @@ And just for the sake of example---as a starting point for those who want to dive in head-first---here are the journal transactions from above, formatted as the Ledger program wishes to see them: -@smallexample +@smallexample @c input:48DDF26 2004/09/29 Pacific Bell Expenses:Pacific Bell $23.00 Assets:Checking @@ -315,12 +328,37 @@ above, formatted as the Ledger program wishes to see them: The account balances and registers in this file, if saved as @file{ledger.dat}, could be reported using: -@smallexample +@smallexample @c command:48DDF26 $ ledger -f ledger.dat balance +@end smallexample + +@smallexample @c output:48DDF26 + $-23.00 Assets:Checking + $23.00 Expenses:Pacific Bell +-------------------- + 0 +@end smallexample + +Or + +@smallexample @c command:8C7295F,with_input:48DDF26 $ ledger -f ledger.dat register checking +@end smallexample + +@smallexample @c output:8C7295F +04-Sep-29 Pacific Bell Assets:Checking $-23.00 $-23.00 +@end smallexample + +And even: + +@smallexample @c command:BB32EF2,with_input:48DDF26 $ ledger -f ledger.dat register Bell @end smallexample +@smallexample @c output:BB32EF2 +04-Sep-29 Pacific Bell Expenses:Pacific Bell $23.00 $23.00 +@end smallexample + An important difference between Ledger and other finance packages is that Ledger will never alter your input file. You can create and edit that file in any way you prefer, but Ledger is only for analyzing the @@ -548,7 +586,7 @@ If you would like to find transaction to only a certain payee use $ ledger -f drewr3.dat register payee "Organic" @end smallexample -@smallexample @c output:C10BC57E +@smallexample @c output:C6BC57E 10-Dec-20 Organic Co-op Expense:Food:Groceries $ 37.50 $ 37.50 Expense:Food:Groceries $ 37.50 $ 75.00 Expense:Food:Groceries $ 37.50 $ 112.50 @@ -748,10 +786,17 @@ flow. A positive cash flow means you are spending more than you make, since income is always a negative figure. To see your current cash flow, use this command: -@smallexample +@smallexample @c command:DB128F3,with_input:6B43DD4 $ ledger balance ^income ^expenses @end smallexample +@smallexample @c output:DB128F3 + $25.00 Expenses:Dining + $-500.00 Income:Salary +-------------------- + $-475.00 +@end smallexample + Another common question to ask of your expenses is: How much do I spend each month on X? Ledger provides a simple way of displaying monthly totals for any account. Here is an example that summarizes @@ -1827,7 +1872,7 @@ function on a transaction-wide or per-posting basis. Lastly, you can specify the valuation function/value for any specific amount using the @samp{(( ))} commodity annotation. -@smallexample +@smallexample @c input:814A366 2012-03-02 KFC Expenses:Food2 $1 ((2 EUR)) Assets:Cash2 @@ -1863,20 +1908,24 @@ amount using the @samp{(( ))} commodity annotation. Assets:Cash9 @end smallexample -@smallexample -ledger reg -V food +@smallexample @c command:814A366 +$ ledger reg -V food +@end smallexample + +@smallexample @c output:814A366 12-Mar-02 KFC Expenses:Food2 2 EUR 2 EUR -12-Mar-03 KFC -1 EUR 1 EUR - Expenses:Food3 3 EUR 4 EUR -12-Mar-04 KFC -2 EUR 2 EUR - Expenses:Food4 4 EUR 6 EUR -12-Mar-05 KFC -3 EUR 3 EUR - Expenses:Food5 5 EUR 8 EUR -12-Mar-06 KFC -4 EUR 4 EUR - Expenses:Food6 6 EUR 10 EUR -12-Mar-07 KFC Expenses:Food7 7 EUR 17 EUR -12-Mar-08 XACT Expenses:Food8 8 EUR 25 EUR -12-Mar-09 POST (Expenses:Food9) 9 EUR 34 EUR +12-Mar-03 KFC Expenses:Food3 3 EUR 5 EUR +12-Mar-04 KFC Expenses:Food4 4 EUR 9 EUR +12-Mar-05 KFC Expenses:Food5 $1 $1 + 9 EUR +12-Mar-06 KFC Expenses:Food6 $1 $2 + 9 EUR +12-Mar-07 KFC Expenses:Food7 1 CAD $2 + 1 CAD + 9 EUR +12-Mar-08 XACT Expenses:Food8 $1 $3 + 1 CAD + 9 EUR @end smallexample @node Keeping it Consistent, Journal Format, Currency and Commodities, Keeping a Journal diff --git a/test/DocTests.py b/test/DocTests.py index 60d8c637..cc540aa9 100755 --- a/test/DocTests.py +++ b/test/DocTests.py @@ -22,6 +22,7 @@ class DocTests: self.testin_token = 'command' self.testout_token = 'output' self.testdat_token = 'input' + self.testwithdat_token = 'with_input' def read_example(self): endexample = re.compile(r'^@end\s+smallexample\s*$') @@ -37,7 +38,7 @@ class DocTests: return hashlib.sha1(example.rstrip()).hexdigest()[0:7].upper() def find_examples(self): - startexample = re.compile(r'^@smallexample\s+@c\s+(%s|%s|%s)(?::([\dA-Fa-f]+))?' + startexample = re.compile(r'^@smallexample\s+@c\s+(%s|%s|%s)(?::([\dA-Fa-f]+))?(?:,(.*))?' % (self.testin_token, self.testout_token, self.testdat_token)) while True: line = self.file.readline() @@ -50,6 +51,13 @@ class DocTests: test_begin_line = self.current_line test_kind = startmatch.group(1) test_id = startmatch.group(2) + test_options = dict() + for pair in re.split(r',\s*', str(startmatch.group(3))): + kv = re.split(r':\s*', pair, 2) + try: + test_options[kv[0]] = kv[1] + except IndexError: + pass example = self.read_example() test_end_pos = self.file.tell() test_end_line = self.current_line @@ -78,17 +86,42 @@ class DocTests: 'epos': test_end_pos, 'blin': test_begin_line, 'elin': test_end_line, + 'opts': test_options, test_kind: example, } + def parse_command(self, test_id, example): + try: + command = example[self.testin_token][self.testin_token] + except KeyError: + return None + + command = command.rstrip().split() + if command[0] == '$': command.remove('$') + index = command.index('ledger') + command[index] = self.ledger + command.insert(index+1, '--init-file') + command.insert(index+2, '/dev/null') + try: + findex = command.index('-f') + except ValueError: + try: + findex = command.index('--file') + except ValueError: + findex = index+1 + command.insert(findex, '--file') + command.insert(findex+1, test_id + '.dat') + return (command, findex+1) + def test_examples(self): failed = set() for test_id in self.examples: example = self.examples[test_id] try: - command = example[self.testin_token][self.testin_token] - except KeyError: - command = None + (command, findex) = self.parse_command(test_id, example) + except TypeError: + failed.add(test_id) + continue try: output = example[self.testout_token][self.testout_token] @@ -98,44 +131,32 @@ class DocTests: try: input = example[self.testdat_token][self.testdat_token] except KeyError: - input = None + try: + with_input = example[self.testin_token]['opts'][self.testwithdat_token] + input = self.examples[with_input][self.testdat_token][self.testdat_token] + except KeyError: + input = None if command and output: - command = command.rstrip().split() - if command[0] == '$': command.remove('$') - index = command.index('ledger') - command[index] = self.ledger - command.insert(index+1, '--init-file') - command.insert(index+2, '/dev/null') - try: - findex = command.index('-f') - except ValueError: - try: - findex = command.index('--file') - except ValueError: - findex = index+1 - command.insert(findex, '--file') - command.insert(findex+1, test_id + '.dat') - + test_file_created = False if findex: scriptpath = os.path.dirname(os.path.realpath(__file__)) test_input_dir = scriptpath + '/../test/input/' - test_file = command[findex+1] - test_file_created = False + test_file = command[findex] if not os.path.exists(test_file): if input: test_file_created = True with open(test_file, 'w') as f: f.write(input) elif os.path.exists(test_input_dir + test_file): - command[findex+1] = test_input_dir + test_file + command[findex] = test_input_dir + test_file try: verify = subprocess.check_output(command) except: verify = str() - if test_file_created: - os.remove(test_file) valid = (output == verify) + if valid and test_file_created: + os.remove(test_file) if self.verbose > 0: print test_id, ':', 'Passed' if valid else 'FAILED' else: From de8b078849e2254353cd0b04488f6d582e4e84c2 Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Fri, 21 Feb 2014 13:04:45 -0600 Subject: [PATCH 15/17] Attempt to convert balances to amounts before failing comparisons --- src/value.cc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/value.cc b/src/value.cc index 75dc7d44..70a8ab43 100644 --- a/src/value.cc +++ b/src/value.cc @@ -870,6 +870,8 @@ bool value_t::is_less_than(const value_t& val) const return as_long() < val.as_long(); case AMOUNT: return val.as_amount() > as_long(); + case BALANCE: + return val.to_amount() > as_long(); default: break; } @@ -886,6 +888,8 @@ bool value_t::is_less_than(const value_t& val) const return as_amount() < val.as_amount(); else return commodity_t::compare_by_commodity()(&as_amount(), &val.as_amount()); + case BALANCE: + return val.to_amount() > as_amount(); default: break; } @@ -904,6 +908,8 @@ bool value_t::is_less_than(const value_t& val) const } return ! no_amounts; } + case BALANCE: + return val.to_amount() > to_amount(); default: break; } @@ -990,6 +996,8 @@ bool value_t::is_greater_than(const value_t& val) const return as_long() > val.as_long(); case AMOUNT: return val.as_amount() < as_long(); + case BALANCE: + return val.to_amount() < as_long(); default: break; } @@ -1001,6 +1009,8 @@ bool value_t::is_greater_than(const value_t& val) const return as_amount() > val.as_long(); case AMOUNT: return as_amount() > val.as_amount(); + case BALANCE: + return val.to_amount() < as_amount(); default: break; } @@ -1019,6 +1029,8 @@ bool value_t::is_greater_than(const value_t& val) const } return ! no_amounts; } + case BALANCE: + return val.to_amount() < to_amount(); default: break; } From f3731a93db9d0091778e835700537fe78f2c4b8c Mon Sep 17 00:00:00 2001 From: John Wiegley Date: Fri, 21 Feb 2014 13:05:05 -0600 Subject: [PATCH 16/17] Added to ignore file --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 5ceabf59..af879db2 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,10 @@ doc/ledger-mode.info doc/ledger3.info-1 doc/ledger3.info-2 CTestTestfile.cmake +.ninja_deps +.ninja_log +build.ninja +rules.ninja +Testing/Temporary +/MathTests +/UtilTests From dacb5f9823bcb93da95e1ed5c3c313bd4c7fa7dc Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sun, 23 Feb 2014 00:02:22 +1000 Subject: [PATCH 17/17] Fixing typo: test sweet -> test suite --- doc/ledger3.texi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ledger3.texi b/doc/ledger3.texi index 54441188..4ab8f4fd 100644 --- a/doc/ledger3.texi +++ b/doc/ledger3.texi @@ -9225,7 +9225,7 @@ test subdirectory for the build. For example, @node Running Tests, Writing Tests, Testing Framework, Testing Framework @subsubsection Running Tests -The complete test sweet can be run from the build directory using the +The complete test suite can be run from the build directory using the check option for the build tool you use. For example, @code{make check}. The entire test suit lasts around a minute for the optimized built and many times longer for the debug version. While developing