Description

HelpOnMacros/Include states that the third parameter is an optional heading level. Several examples are given where this parameter is not given, and those examples work. However, if you include the second parameter, the heading title, and the fourth or later parameter, then the heading level is not optional.

Note: This bug was MoinMoinBugs/IncludeHeadingMishandlesQuotes, but that page title doesn't fit the actual problem.

Example

'''Include without headding and To ''succeed'' '''
[[Include(/IncludeTest, , ,to="^----")]]

'''Include with Heading and To ''Fails'' '''
[[Include(/IncludeTest, 'Heading', ,to="^----")]]

Include without headding and To succeed

Text to be included

More text

And then some

Even more...

Include with Heading and To Fails

Heading

Text to be included

More text

And then some

Even more...

Details

This Wiki (but since ../IncludeNotCacheAware, it is overlooked)

Workaround

If you use heading and another parameter, always include the level.

Discussion

The offending line is line 22 in macro/Include.py:

_arg_level = r',\s*(?P<level>\d+)'

the fix is to allow the level group to match nothing, by adding a '?' to the expression:

_arg_level = r',\s*(?P<level>\d+)?'

I've written a test procedure, Include_Test.py, which tests the arguments on HelpOnMacros/Include as well as the new ones I introduced above. It tests it under the old code first, to show the test fails, and then with the new code, to show all tests pass.

   1 """
   2     Matching tests for Include macro
   3 
   4     This module tests the matching ability of the Include regular expression  
   5     The following URL contains what should be valid examples:
   6     
   7     http://purl.net/wiki/moinmaster/HelpOnMacros/Include
   8 
   9 """
  10 
  11 """
  12   The test set is a list of dictionarys, where each dictionary contains:
  13    The test string ('test', 'test-string'),
  14    The name string ('name', 'name-string'),
  15   and these optional values: 
  16    heading, hquote, htext, level, fquote, from, tquote, to, sort, items,
  17    skipitems, titlesonly
  18   The items correspond to the named fields in the regular expression
  19 """
  20 
  21 test_sets = [  
  22 dict([('test','FooBar'), ('name','FooBar')]), 
  23 dict([('test','FooBar, '), ('name','FooBar'), ('heading',',')]),
  24 dict([('test','FooBar, , 2'), ('name','FooBar'), ('heading', ','), 
  25       ('level','2')]), 
  26 dict([('test',"FooBar, 'All about Foo Bar', 2"), ('name','FooBar'), 
  27       ('heading',','), ('hquote',"'"), ('htext',"All about Foo Bar"),
  28       ('level','2')]),
  29 dict([('test','FooBar, , from="^----$"'), ('name','FooBar'),
  30       ('heading',','), ('fquote', '"'), ('from','^----$')]),
  31 dict([('test','FooBar, , to="^----$"'), ('name','FooBar'),
  32       ('heading',','), ('tquote','"'), ('to','^----$')]),
  33 dict([('test','^FooBar/.*, , sort=descending'), ('name','^FooBar/.*'),
  34       ('heading',','), ('sort','descending')]), 
  35 dict([('test','^FooBar/.*, , items=3'), ('name','^FooBar/.*'),
  36       ('heading',','), ('items','3')]),      
  37 dict([('test','^BlogBase/.*,, to="^----$", sort=descending, items=7'),
  38       ('name','^BlogBase/.*'), ('heading',','), ('tquote','"'),
  39       ('to','^----$'), ('sort','descending'), ('items','7')]),
  40 dict([('test','^BlogBase/.*,, to="^----$", sort=descending, items=7, skipitems=7,titlesonly'),
  41       ('name','^BlogBase/.*'), ('heading',','), ('tquote','"'),
  42       ('to','^----$'), ('sort','descending'), ('items','7'),
  43       ('skipitems','7'),('titlesonly','titlesonly')]),
  44 dict([('test','^FirstnameLastname/20..-..-..,,to="^----",sort=descending,items=3'),
  45       ('name','^FirstnameLastname/20..-..-..'), ('heading',','), 
  46       ('tquote','"'),('to','^----'),('sort','descending'),('items','3')]),
  47 dict([('test','^FirstnameLastname/20..-..-..,,to="^----",sort=descending,items=4,skipitems=3,titlesonly'),
  48       ('name','^FirstnameLastname/20..-..-..'), ('heading',','), 
  49       ('tquote','"'),('to','^----'),('sort','descending'),('items','4'),
  50       ('skipitems','3'),('titlesonly','titlesonly')]),
  51 dict([('test', 'TitleTest, "Heading for Title Test 1",1,to="^----$"'),  ('name', 'TitleTest'),
  52       ('heading',','), ('hquote', '"'), ('htext', 'Heading for Title Test 1'),
  53       ('level','1'),('to', '^----$')]),
  54 dict([('test', 'TitleTest, "Heading for Title Test 2", ,to="^----$"'),  ('name', 'TitleTest'),
  55       ('heading',','), ('hquote', '"'), ('htext', 'Heading for Title Test 2'),
  56       ('to', '^----$')])
  57 ]
  58 
  59 import re
  60 
  61 _arg_heading = r'(?P<heading>,)\s*(|(?P<hquote>[\'"])(?P<htext>.+?)(?P=hquote))'
  62 OLD_arg_level = r',\s*(?P<level>\d+)'
  63 _arg_level = r',\s*(?P<level>\d+)?'
  64 _arg_from = r'(,\s*from=(?P<fquote>[\'"])(?P<from>.+?)(?P=fquote))?'
  65 _arg_to = r'(,\s*to=(?P<tquote>[\'"])(?P<to>.+?)(?P=tquote))?'
  66 _arg_sort = r'(,\s*sort=(?P<sort>(ascending|descending)))?'
  67 _arg_items = r'(,\s*items=(?P<items>\d+))?'
  68 _arg_skipitems = r'(,\s*skipitems=(?P<skipitems>\d+))?'
  69 _arg_titlesonly = r'(,\s*(?P<titlesonly>titlesonly))?'
  70 _args_re_pattern_OLD = r'^(?P<name>[^,]+)(%s(%s)?%s%s%s%s%s%s)?$' % (
  71     _arg_heading, OLD_arg_level, _arg_from, _arg_to, _arg_sort, _arg_items,
  72     _arg_skipitems, _arg_titlesonly)
  73 _args_re_pattern = r'^(?P<name>[^,]+)(%s(%s)?%s%s%s%s%s%s)?$' % (
  74     _arg_heading, _arg_level, _arg_from, _arg_to, _arg_sort, _arg_items,
  75     _arg_skipitems, _arg_titlesonly)
  76 
  77 def test_import_re(pattern=_args_re_pattern):
  78   args_re=re.compile(pattern)
  79   test_num=0
  80   number_failed=0 
  81   for test_set in test_sets:
  82     test_failed=0
  83     test_str = test_set['test']
  84     test_num=test_num+1
  85     print "Test", test_num, ":", test_str
  86     
  87     args = args_re.match(test_str)
  88     if not args:
  89       print "  FAIL: failed to match on test string"
  90       test_failed=1
  91     else:      
  92       for k, v in test_set.iteritems():
  93         if k != 'test' and k != 'hquote':
  94           re_res = args.group(k)
  95           if not re_res:
  96             print "  FAIL: failed to match on key '", k, "'"
  97             test_failed=1
  98           elif v != re_res:
  99             print "  FAIL: On key '", k, "', value was '", re_res, "', expected '", v, "'."
 100             test_failed=1
 101     number_failed += test_failed
 102   
 103   if number_failed == 0:
 104     print "All tests passed!"
 105   else:
 106     print str(number_failed), "out of", str(test_num), "tests failed."
 107 
 108 print "*** ORIGINAL CODE ***"
 109 test_import_re(_args_re_pattern_OLD)
 110 
 111 print
 112 print "*** CHANGED CODE ***"
 113 test_import_re()
Include_Test.py

Output is:

*** ORIGINAL CODE ***
Test 1 : FooBar
Test 2 : FooBar, 
Test 3 : FooBar, , 2
Test 4 : FooBar, 'All about Foo Bar', 2
Test 5 : FooBar, , from="^----$"
Test 6 : FooBar, , to="^----$"
Test 7 : ^FooBar/.*, , sort=descending
Test 8 : ^FooBar/.*, , items=3
Test 9 : ^BlogBase/.*,, to="^----$", sort=descending, items=7
Test 10 : ^BlogBase/.*,, to="^----$", sort=descending, items=7, skipitems=7,titlesonly
Test 11 : ^FirstnameLastname/20..-..-..,,to="^----",sort=descending,items=3
Test 12 : ^FirstnameLastname/20..-..-..,,to="^----",sort=descending,items=4,skipitems=3,titlesonly
Test 13 : TitleTest, "Heading for Title Test 1",1,to="^----$"
Test 14 : TitleTest, "Heading for Title Test 2", ,to="^----$"
  FAIL: On key ' htext ', value was ' Heading for Title Test 2", ,to="^----$ ', expected ' Heading for Title Test 2 '.
  FAIL: failed to match on key ' to '
1 out of 14 tests failed.

*** CHANGED CODE ***
Test 1 : FooBar
Test 2 : FooBar, 
Test 3 : FooBar, , 2
Test 4 : FooBar, 'All about Foo Bar', 2
Test 5 : FooBar, , from="^----$"
Test 6 : FooBar, , to="^----$"
Test 7 : ^FooBar/.*, , sort=descending
Test 8 : ^FooBar/.*, , items=3
Test 9 : ^BlogBase/.*,, to="^----$", sort=descending, items=7
Test 10 : ^BlogBase/.*,, to="^----$", sort=descending, items=7, skipitems=7,titlesonly
Test 11 : ^FirstnameLastname/20..-..-..,,to="^----",sort=descending,items=3
Test 12 : ^FirstnameLastname/20..-..-..,,to="^----",sort=descending,items=4,skipitems=3,titlesonly
Test 13 : TitleTest, "Heading for Title Test 1",1,to="^----$"
Test 14 : TitleTest, "Heading for Title Test 2", ,to="^----$"
All tests passed!

I'd appreciate if someone could add this one-line fix to the development code, as it will take me a few more weekends to figure out how to make an official diff -- -- JohnWhitlock 2004-08-28 04:47:00

Do you have test code for this complext test code? :)

Test code should be very very simple, and test only one thing for each test. Use MoinMoin unittests framework and create simple test for each case. -- NirSoffer 2004-10-21 11:36:51

Plan

maybe this should be checked before 1.3 is out of beta


CategoryMoinMoinBugFixed

MoinMoin: MoinMoinBugs/IncludeLevelNotOptional (last edited 2007-10-29 19:06:48 by localhost)