Zim

Merge lp:~fenryxo/zim/templates-pagemenu into lp:~jaap.karssenberg/zim/pyzim

Proposed by Jiří Janoušek
Status: Merged
Merged at revision: 408
Proposed branch: lp:~fenryxo/zim/templates-pagemenu
Merge into: lp:~jaap.karssenberg/zim/pyzim
Diff against target: 580 lines (+431/-27)
7 files modified
data/manual/Help/Templates.txt (+19/-19)
data/templates/html/With_Menu.html (+73/-0)
tests/data/notebook-wiki.xml (+11/-0)
tests/parsing.py (+5/-0)
tests/templates.py (+210/-3)
zim/parsing.py (+2/-1)
zim/templates.py (+111/-4)
To merge this branch: bzr merge lp:~fenryxo/zim/templates-pagemenu
Reviewer Review Type Date Requested Status
Jaap Karssenberg Approve
Review via email: mp+60616@code.launchpad.net

Description of the change

This branch contains 3 features:
1) notebook.name template variable
2) page.menu template variable: Page menu is a tree-style menu like index, but branches not related to current page are collapsed.
3) New template using both notebook.name & page.menu

Basic test coverage and manual update included.

I've prepared an example - manual exported with the new template - see http://tmp.doublej.cz/pagemenu/

To post a comment you must log in.
Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

I like the idea - maybe we should not put it in the "page" namespace. How about exposing this as a function to the template ? That way you can (later) add parameters to control the style (folded vs unfolded etc.) and make it a bit more powerful.

Revision history for this message
Jiří Janoušek (fenryxo) wrote :

What a good idea :-) I can imagine additional paramethers
* starting path
* limit of nesting

I'll focus on it at the weekend.

Revision history for this message
Jiří Janoušek (fenryxo) wrote :

The branch is ready for the review again. The test suite probably needs to be polished.

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

Thanks, looks better this way. Will have a look at the test cases when merging.

FYI: will be offline from this Friday for about a week and a bit - will probably not have time to merge all your branched before then. But have will attend to it when I'm back.

-- Jaap

review: Approve
Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

Did some rework and afraid that changed how the options work. But it is more in line now with the index page that can be generated, so I propose keeping it like this. If you really want the style you had before maybe we can add a "sparse" option ?

* collapse now mimics the standard index, so it shows all of the toplevel pages and pages in any expanded sub page
* hide_empty now only hides pages that don't have content OR children, this is needed because otherwise namspaces were the parent has no text disappear.

Revision history for this message
Jiří Janoušek (fenryxo) wrote :

2011/6/6 Jaap Karssenberg <email address hidden>:
> Did some rework and afraid that changed how the options work. But it is more in line now with the index page that can be generated, so I propose keeping it like this. If you really want the style you had before maybe we can add a "sparse" option ?
>
> * collapse now mimics the standard index, so it shows all of the toplevel pages and pages in any expanded sub page

If I understand it correctly, collapse works like the original example
(e.g. http://tmp.doublej.cz/pagemenu /Help/Export.html ) Or if I'm
missing something, can you put somewhere similar example output?

> * hide_empty now only hides pages that don't have content OR children, this is needed because otherwise namspaces were the parent has no text disappear.

The hide_empty option was intended to prevent creating links to
non-existing pages. But the proper solution could be to create little
index pages listing child nodes (like in WWWInterface).

> --
> https://code.launchpad.net/~janousek.jiri/zim/templates-pagemenu/+merge/60616
> You are the owner of lp:~janousek.jiri/zim/templates-pagemenu.
>

Revision history for this message
Jaap Karssenberg (jaap.karssenberg) wrote :

2011/6/6 Jiří Janoušek <email address hidden>

> > * collapse now mimics the standard index, so it shows all of the toplevel
> pages and pages in any expanded sub page
>

> If I understand it correctly, collapse works like the original example
> (e.g. http://tmp.doublej.cz/pagemenu /Help/Export.html ) Or if I'm
> missing something, can you put somewhere similar example output?
>
>
Yes, for two level depth the behavior is the same. However it now keeps
showing the top level also for deeper nested pages.

> > * hide_empty now only hides pages that don't have content OR children,
> this is needed because otherwise namspaces were the parent has no text
> disappear.
>
> The hide_empty option was intended to prevent creating links to
> non-existing pages. But the proper solution could be to create little
> index pages listing child nodes (like in WWWInterface).
>

Maybe we need to check common code between IndexPage and this function.

-- Jaap

Revision history for this message
Jiří Janoušek (fenryxo) wrote :

2011/6/6 Jaap Karssenberg <email address hidden>:
> 2011/6/6 Jiří Janoušek <email address hidden>
>
>> > * collapse now mimics the standard index, so it shows all of the toplevel
>> pages and pages in any expanded sub page
>>
>
>
>> If I understand it correctly, collapse works like the original example
>> (e.g. http://tmp.doublej.cz/pagemenu /Help/Export.html ) Or if I'm
>> missing something, can you put somewhere similar example output?
>>
>>
> Yes, for two level depth the behavior is the same. However it now keeps
> showing the top level also for deeper nested pages

My code should keep top level too (unless a different starting
namespace is explicitly given). If not, it's a mistake, not purpose.

>> > * hide_empty now only hides pages that don't have content OR children,
>> this is needed because otherwise namspaces were the parent has no text
>> disappear.
>>
>> The hide_empty option was intended to prevent creating links to
>> non-existing pages. But the proper solution could be to create little
>> index pages listing child nodes (like in WWWInterface).
>>
>
> Maybe we need to check common code between IndexPage and this function.

The menu function is based on IndexPage code, but I think IndexPage
doesn't require extra logic from menu (home page order manipulation,
current page in bold, ...). It should get starting namespace and level
limit to be usable as a page body for empty pages with children.

> -- Jaap
>
> --
> https://code.launchpad.net/~janousek.jiri/zim/templates-pagemenu/+merge/60616
> You are the owner of lp:~janousek.jiri/zim/templates-pagemenu.
>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/manual/Help/Templates.txt'
2--- data/manual/Help/Templates.txt 2011-04-06 20:00:02 +0000
3+++ data/manual/Help/Templates.txt 2011-05-16 21:27:32 +0000
4@@ -56,31 +56,31 @@
5
6 '''
7 zim.version # version of zim
8+notebook.name # name of the notebook
9
10 page.name # complete page name
11 page.namespace # namespace
12 page.basename # last part of the page name
13-page.title #
14+page.title # first heading in the page or the basename
15+page.heading # first heading in the page
16+page.body # content of the page (without the leading heading)
17+page.links # list of page objects for pages linked in this page
18+page.backlinks # list of page objects for pages linking to this page
19+page.properties # dict with page properties
20+
21+# These special pages have the same properties as the 'page' object
22+pages.index # the index page generated when exporting
23+pages.home # the home page
24+pages.next # the next page in the index (if any)
25+pages.previous # the previous page in the index (if any)
26+
27+options # dict where format specific options can be set
28 '''
29-''first heading in the page or the basename''
30-''page.heading # first heading in the page''
31-''page.body # content of the page (without the leading heading)''
32-''page.links # list of page objects for pages linked in this page''
33-''page.backlinks # ''''list of page objects for pages linking to this page''
34-''page.properties # dict with page properties''
35-
36-''# These special pages have the same properties as the 'page' object''
37-''pages.index # the index page generated when exporting''
38-''pages.home # the home page''
39-''pages.next # the next page in the index (if any)''
40-''pages.previous # the previous page in the index (if any)''
41-
42-''options # dict where format specific options can be set''
43+
44
45 Functions available:
46
47-'''
48-url(link) # turns a zim link into an URL
49-strftime(template, date) # format a date
50-'''
51+* ''url(link)'' - turns a zim link into an URL
52+* ''strftime(template, date)'' - format a date
53+* ''menu(namespace, collapse, ignore_empty)'' - creates a tree-style menu starting with given namespace (''":"'' by default). ''collapse'' can be ''TRUE'' (only branches related to the current page are visible, //default value//) or ''FALSE'' (all branches are visible). ''ignore_empty'' can be ''TRUE'' (empty pages are ignored, //default value//) or ''FALSE''. Example: ''menu(page.namespace, TRUE, FALSE)''
54
55
56=== added file 'data/templates/html/With_Menu.html'
57--- data/templates/html/With_Menu.html 1970-01-01 00:00:00 +0000
58+++ data/templates/html/With_Menu.html 2011-05-16 21:27:32 +0000
59@@ -0,0 +1,73 @@
60+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
61+<html>
62+ <head>
63+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
64+ <title>[% page.title %]</title>
65+ <meta name='Generator' content='Zim [% zim.version %]'>
66+ <style type='text/css'>
67+ a { text-decoration: none }
68+ a:hover { text-decoration: underline }
69+ a:active { text-decoration: underline }
70+ strike { color: grey }
71+ u { text-decoration: none;
72+ background-color: yellow }
73+ tt { color: #2e3436; }
74+ pre { color: #2e3436;
75+ margin-left: 20px }
76+ h1 { text-align: center;
77+ color: #4e9a06 }
78+ h2 { color: #4e9a06 }
79+ h3 { color: #4e9a06 }
80+ h4 { color: #4e9a06 }
81+ h5 { color: #4e9a06 }
82+ span.insen { color: grey }
83+ .page { max-width: 1000px;}
84+ .menu{
85+ float:left; width: 300px;
86+ }
87+
88+ .content { padding-left: 320px;}
89+ .notebook{font-variant: small-caps;
90+ color:#4e9a06;
91+ padding: 0px 20px;}
92+ hr{clear:both;}
93+ </style>
94+
95+ </head>
96+ <body>
97+ <div class="page">
98+ <div class="heading">
99+ <h1><a href="[% url(pages.home.name) %]" class="notebook">[% notebook.name %]:</a>
100+
101+
102+ [% IF page.properties.type == 'namespace-index' -%]
103+ Document Index</h1>
104+ [%- ELSE -%]
105+ [% page.heading %]</h1>
106+ [%- END %]
107+ </div>
108+ <hr>
109+ <div class="menu">
110+ [% menu(page) %]
111+ </div>
112+ <div class="content">
113+ <!-- Wiki content -->
114+ [% page.body %]
115+
116+ <!-- End wiki content -->
117+ </div>
118+ <hr>
119+ <!-- Backlinks -->
120+ <div class="footer">
121+ [% IF page.backlinks -%] Backlinks:
122+ [%- FOREACH link = page.backlinks -%]
123+ <a href='[% url(link) %]'>[% link.name %]</a></li>
124+ [%- END -%]
125+ [%- ELSE -%]
126+ No backlinks to this page.
127+ [%- END %]
128+ <!-- End Backlinks -->
129+ </div>
130+ </div>
131+ </body>
132+</html>
133
134=== modified file 'tests/data/notebook-wiki.xml'
135--- tests/data/notebook-wiki.xml 2011-04-05 20:11:51 +0000
136+++ tests/data/notebook-wiki.xml 2011-05-16 21:27:32 +0000
137@@ -312,4 +312,15 @@
138 [[Linking:Foo:Bar]]
139 [[Dus]]
140 </page>
141+<page name="Parent">For page.menu template variable test</page>
142+<page name="Parent:Son">For page.menu template variable test</page>
143+<page name="Parent:Son:Grandson">For page.menu template variable test</page>
144+<page name="Parent:Son:Granddaughter">For page.menu template variable test</page>
145+<page name="Parent:Daughter">For page.menu template variable test</page>
146+<page name="Parent:Daughter:Grandson">For page.menu template variable test</page>
147+<page name="Parent:Daughter:Granddaughter">For page.menu template variable test</page>
148+<page name="Parent:Daughter:SomeOne:Foo">For page.menu template variable test</page>
149+<page name="Parent:Daughter:SomeOne:Bar">For page.menu template variable test</page>
150+<page name="Parent:Child:Grandchild">For page.menu template variable test</page>
151+
152 </pagelist>
153
154=== modified file 'tests/parsing.py'
155--- tests/parsing.py 2011-04-02 12:36:48 +0000
156+++ tests/parsing.py 2011-05-16 21:27:32 +0000
157@@ -17,6 +17,11 @@
158 list = ['"foo bar"', ',', r'"\"foooo bar\""', 'dusss', 'ja']
159 result = split_quoted_strings(string, unescape=False)
160 self.assertEquals(result, list)
161+
162+ string = r'''"foo bar", False, True'''
163+ list = ['foo bar', ',', 'False', ',', 'True']
164+ result = split_quoted_strings(string)
165+ self.assertEquals(result, list)
166
167 def testParseDate(self):
168 '''Test parsing dates'''
169
170=== modified file 'tests/templates.py'
171--- tests/templates.py 2011-05-14 11:08:50 +0000
172+++ tests/templates.py 2011-05-16 21:27:32 +0000
173@@ -14,6 +14,7 @@
174 TemplateParam, TemplateDict, TemplateFunction, PageProxy
175 from zim.notebook import Notebook, Path
176 import zim.formats
177+from zim.parsing import link_type
178
179
180 class TestTemplateParam(tests.TestCase):
181@@ -159,14 +160,14 @@
182 input = u'''\
183 Version [% zim.version %]
184 <title>[% page.title %]</title>
185-<h1>[% page.name %]</h1>
186+<h1>[% notebook.name %]: [% page.name %]</h1>
187 <h2>[% page.heading %]</h2>
188 [% page.body %]
189 '''
190 wantedresult = u'''\
191 Version %s
192 <title>Page Heading</title>
193-<h1>FooBar</h1>
194+<h1>Unnamed Notebook: FooBar</h1>
195 <h2>Page Heading</h2>
196 <p>
197 <strong>foo bar !</strong><br>
198@@ -190,7 +191,213 @@
199 tree = template.process_to_parsetree(notebook, page) # No linker !
200 self.assertEqual(tree.find('h').text, u'Some New None existing page')
201
202-
203+class TestTemplatePageMenu(tests.TestCase):
204+ def runTest(self):
205+ # menu(root, collapse, ignore_empty)
206+ data = (
207+# Test default settings
208+(u"[% menu() %]", '''\
209+<ul>
210+<li><a href="page://Parent" title="Parent">Parent</a></li>
211+<ul>
212+<li><strong>Daughter</strong></li>
213+<ul>
214+<li><a href="page://Parent:Daughter:Granddaughter" title="Granddaughter">Granddaughter</a></li>
215+<li><a href="page://Parent:Daughter:Grandson" title="Grandson">Grandson</a></li>
216+</ul>
217+<li><a href="page://Parent:Son" title="Son">Son</a></li>
218+</ul>
219+<li><a href="page://roundtrip" title="roundtrip">roundtrip</a></li>
220+<li><a href="page://TODOList" title="TODOList">TODOList</a></li>
221+<li><a href="page://TrashMe" title="TrashMe">TrashMe</a></li>
222+</ul>
223+'''),
224+# Collapsing turned off
225+(u"[% menu(':', FALSE, TRUE) %]", '''\
226+<ul>
227+<li><a href="page://Parent" title="Parent">Parent</a></li>
228+<ul>
229+<li><strong>Daughter</strong></li>
230+<ul>
231+<li><a href="page://Parent:Daughter:Granddaughter" title="Granddaughter">Granddaughter</a></li>
232+<li><a href="page://Parent:Daughter:Grandson" title="Grandson">Grandson</a></li>
233+</ul>
234+<li><a href="page://Parent:Son" title="Son">Son</a></li>
235+<ul>
236+<li><a href="page://Parent:Son:Granddaughter" title="Granddaughter">Granddaughter</a></li>
237+<li><a href="page://Parent:Son:Grandson" title="Grandson">Grandson</a></li>
238+</ul>
239+</ul>
240+<li><a href="page://roundtrip" title="roundtrip">roundtrip</a></li>
241+<li><a href="page://TODOList" title="TODOList">TODOList</a></li>
242+<ul>
243+<li><a href="page://TODOList:bar" title="bar">bar</a></li>
244+<li><a href="page://TODOList:foo" title="foo">foo</a></li>
245+</ul>
246+<li><a href="page://TrashMe" title="TrashMe">TrashMe</a></li>
247+<ul>
248+<li><a href="page://TrashMe:sub page 1" title="sub page 1">sub page 1</a></li>
249+<li><a href="page://TrashMe:sub page 2" title="sub page 2">sub page 2</a></li>
250+</ul>
251+</ul>
252+'''),
253+# Empty pages are not ignored
254+(u"[% menu(':', TRUE, FALSE) %]", '''\
255+<ul>
256+<li><a href="page://Bar" title="Bar">Bar</a></li>
257+<li><a href="page://foo" title="foo">foo</a></li>
258+<li><a href="page://Linking" title="Linking">Linking</a></li>
259+<li><a href="page://Parent" title="Parent">Parent</a></li>
260+<ul>
261+<li><a href="page://Parent:Child" title="Child">Child</a></li>
262+<li><strong>Daughter</strong></li>
263+<ul>
264+<li><a href="page://Parent:Daughter:Granddaughter" title="Granddaughter">Granddaughter</a></li>
265+<li><a href="page://Parent:Daughter:Grandson" title="Grandson">Grandson</a></li>
266+<li><a href="page://Parent:Daughter:SomeOne" title="SomeOne">SomeOne</a></li>
267+</ul>
268+<li><a href="page://Parent:Son" title="Son">Son</a></li>
269+</ul>
270+<li><a href="page://roundtrip" title="roundtrip">roundtrip</a></li>
271+<li><a href="page://Test" title="Test">Test</a></li>
272+<li><a href="page://TODOList" title="TODOList">TODOList</a></li>
273+<li><a href="page://TrashMe" title="TrashMe">TrashMe</a></li>
274+<li><a href="page://utf8" title="utf8">utf8</a></li>
275+</ul>
276+'''),
277+# Both
278+(u"[% menu(':', FALSE, FALSE) %]", '''\
279+<ul>
280+<li><a href="page://Bar" title="Bar">Bar</a></li>
281+<li><a href="page://foo" title="foo">foo</a></li>
282+<ul>
283+<li><a href="page://foo:bar" title="bar">bar</a></li>
284+</ul>
285+<li><a href="page://Linking" title="Linking">Linking</a></li>
286+<ul>
287+<li><a href="page://Linking:Dus" title="Dus">Dus</a></li>
288+<ul>
289+<li><a href="page://Linking:Dus:Ja" title="Ja">Ja</a></li>
290+</ul>
291+<li><a href="page://Linking:Foo" title="Foo">Foo</a></li>
292+<ul>
293+<li><a href="page://Linking:Foo:Bar" title="Bar">Bar</a></li>
294+<ul>
295+<li><a href="page://Linking:Foo:Bar:Baz" title="Baz">Baz</a></li>
296+</ul>
297+</ul>
298+</ul>
299+<li><a href="page://Parent" title="Parent">Parent</a></li>
300+<ul>
301+<li><a href="page://Parent:Child" title="Child">Child</a></li>
302+<ul>
303+<li><a href="page://Parent:Child:Grandchild" title="Grandchild">Grandchild</a></li>
304+</ul>
305+<li><strong>Daughter</strong></li>
306+<ul>
307+<li><a href="page://Parent:Daughter:Granddaughter" title="Granddaughter">Granddaughter</a></li>
308+<li><a href="page://Parent:Daughter:Grandson" title="Grandson">Grandson</a></li>
309+<li><a href="page://Parent:Daughter:SomeOne" title="SomeOne">SomeOne</a></li>
310+<ul>
311+<li><a href="page://Parent:Daughter:SomeOne:Bar" title="Bar">Bar</a></li>
312+<li><a href="page://Parent:Daughter:SomeOne:Foo" title="Foo">Foo</a></li>
313+</ul>
314+</ul>
315+<li><a href="page://Parent:Son" title="Son">Son</a></li>
316+<ul>
317+<li><a href="page://Parent:Son:Granddaughter" title="Granddaughter">Granddaughter</a></li>
318+<li><a href="page://Parent:Son:Grandson" title="Grandson">Grandson</a></li>
319+</ul>
320+</ul>
321+<li><a href="page://roundtrip" title="roundtrip">roundtrip</a></li>
322+<li><a href="page://Test" title="Test">Test</a></li>
323+<ul>
324+<li><a href="page://Test:foo" title="foo">foo</a></li>
325+<ul>
326+<li><a href="page://Test:foo:bar" title="bar">bar</a></li>
327+</ul>
328+<li><a href="page://Test:Foo Bar" title="Foo Bar">Foo Bar</a></li>
329+<ul>
330+<li><a href="page://Test:Foo Bar:Dus Ja Hmm" title="Dus Ja Hmm">Dus Ja Hmm</a></li>
331+</ul>
332+<li><a href="page://Test:tags" title="tags">tags</a></li>
333+<li><a href="page://Test:wiki" title="wiki">wiki</a></li>
334+</ul>
335+<li><a href="page://TODOList" title="TODOList">TODOList</a></li>
336+<ul>
337+<li><a href="page://TODOList:bar" title="bar">bar</a></li>
338+<li><a href="page://TODOList:foo" title="foo">foo</a></li>
339+</ul>
340+<li><a href="page://TrashMe" title="TrashMe">TrashMe</a></li>
341+<ul>
342+<li><a href="page://TrashMe:sub page 1" title="sub page 1">sub page 1</a></li>
343+<li><a href="page://TrashMe:sub page 2" title="sub page 2">sub page 2</a></li>
344+</ul>
345+<li><a href="page://utf8" title="utf8">utf8</a></li>
346+<ul>
347+<li><a href="page://utf8:αβγ" title="αβγ">αβγ</a></li>
348+<li><a href="page://utf8:בדיקה" title="בדיקה">בדיקה</a></li>
349+<ul>
350+<li><a href="page://utf8:בדיקה:טכניון" title="טכניון">טכניון</a></li>
351+<ul>
352+<li><a href="page://utf8:בדיקה:טכניון:הנדסת מכונות" title="הנדסת מכונות">הנדסת מכונות</a></li>
353+<li><a href="page://utf8:בדיקה:טכניון:מדעי המחשב" title="מדעי המחשב">מדעי המחשב</a></li>
354+</ul>
355+</ul>
356+<li><a href="page://utf8:中文" title="中文">中文</a></li>
357+</ul>
358+</ul>
359+'''),
360+# Let's chenge the root
361+(u"[% menu(page, FALSE) %]", '''\
362+<ul>
363+<li><a href="page://Parent:Daughter:Granddaughter" title="Granddaughter">Granddaughter</a></li>
364+<li><a href="page://Parent:Daughter:Grandson" title="Grandson">Grandson</a></li>
365+</ul>
366+'''),
367+(u"[% menu(page.name, FALSE, FALSE) %]", '''\
368+<ul>
369+<li><a href="page://Parent:Daughter:Granddaughter" title="Granddaughter">Granddaughter</a></li>
370+<li><a href="page://Parent:Daughter:Grandson" title="Grandson">Grandson</a></li>
371+<li><a href="page://Parent:Daughter:SomeOne" title="SomeOne">SomeOne</a></li>
372+<ul>
373+<li><a href="page://Parent:Daughter:SomeOne:Bar" title="Bar">Bar</a></li>
374+<li><a href="page://Parent:Daughter:SomeOne:Foo" title="Foo">Foo</a></li>
375+</ul>
376+</ul>
377+'''),
378+(u"[% menu(page.namespace, FALSE, FALSE) %]", '''\
379+<ul>
380+<li><a href="page://Parent:Child" title="Child">Child</a></li>
381+<ul>
382+<li><a href="page://Parent:Child:Grandchild" title="Grandchild">Grandchild</a></li>
383+</ul>
384+<li><strong>Daughter</strong></li>
385+<ul>
386+<li><a href="page://Parent:Daughter:Granddaughter" title="Granddaughter">Granddaughter</a></li>
387+<li><a href="page://Parent:Daughter:Grandson" title="Grandson">Grandson</a></li>
388+<li><a href="page://Parent:Daughter:SomeOne" title="SomeOne">SomeOne</a></li>
389+<ul>
390+<li><a href="page://Parent:Daughter:SomeOne:Bar" title="Bar">Bar</a></li>
391+<li><a href="page://Parent:Daughter:SomeOne:Foo" title="Foo">Foo</a></li>
392+</ul>
393+</ul>
394+<li><a href="page://Parent:Son" title="Son">Son</a></li>
395+<ul>
396+<li><a href="page://Parent:Son:Granddaughter" title="Granddaughter">Granddaughter</a></li>
397+<li><a href="page://Parent:Son:Grandson" title="Grandson">Grandson</a></li>
398+</ul>
399+</ul>
400+'''),
401+)
402+
403+ notebook = notebook = tests.new_notebook()
404+ page = notebook.get_page(Path(':Parent:Daughter'))
405+ for input, wantedresult in data:
406+ result = Template(input, 'html', linker=StubLinker()).process(notebook, page)
407+ self.assertEqual(result, wantedresult.splitlines(True))
408+
409+
410
411 class StubLinker(object):
412
413
414=== modified file 'zim/parsing.py'
415--- zim/parsing.py 2011-05-13 05:44:02 +0000
416+++ zim/parsing.py 2011-05-16 21:27:32 +0000
417@@ -12,7 +12,8 @@
418 word_re = Re(r'''
419 ( '(\\'|[^'])*' | # single quoted word
420 "(\\"|[^"])*" | # double quoted word
421- \S+ # word without spaces
422+ [^\s,]+ | # word without spaces and commas
423+ , # comma
424 )''', re.X)
425 string = string.strip()
426 words = []
427
428=== modified file 'zim/templates.py'
429--- zim/templates.py 2011-05-13 05:44:02 +0000
430+++ zim/templates.py 2011-05-16 21:27:32 +0000
431@@ -71,8 +71,9 @@
432 from zim.fs import File
433 from zim.config import data_dirs
434 from zim.parsing import Re, TextBuffer, split_quoted_strings, unescape_quoted_string, is_path_re
435-from zim.formats import ParseTree, Element
436+from zim.formats import ParseTree, Element, TreeBuilder
437 from zim.index import LINK_DIR_BACKWARD
438+from zim.notebook import Path
439
440 logger = logging.getLogger('zim.templates')
441
442@@ -198,7 +199,6 @@
443 '''
444 if not isinstance(dict, TemplateDict):
445 dict = TemplateDict(dict)
446-
447 output = TextBuffer(self.tokens.process(dict))
448 return output.get_lines()
449
450@@ -334,7 +334,11 @@
451 'pages': pages,
452 'strftime': StrftimeFunction(),
453 'url': TemplateFunction(self.url),
454- 'options': options
455+ 'options': options,
456+ 'notebook': {'name' : notebook.name},
457+ 'menu' : MenuFunction(notebook, page, self.format, self.linker, options),
458+ # helpers that allow to write TRUE instead of 'TRUE' in template functions
459+ 'TRUE' : 'True', 'FALSE' : 'False',
460 }
461
462 if self.linker:
463@@ -625,6 +629,109 @@
464 else:
465 raise AssertionError, 'Not a datetime object: %s', date
466
467+class MenuFunction(TemplateFunction):
468+ '''Template function wrapper for strftime'''
469+
470+ def __init__(self, notebook, page, format, linker, options):
471+ self._notebook = notebook
472+ self._page = page
473+ self._format = format
474+ self._linker = linker
475+ self._options = options
476+
477+
478+ def __call__(self, dict, root=':', collapse=True, ignore_empty=True):
479+ builder = TreeBuilder()
480+ level = self._page.name.count(':')
481+ home = Path(self._notebook.config['Notebook']['home'])
482+
483+ # TODO: Some logic to parse boolean template function params
484+ if isinstance(collapse, (TemplateParam, TemplateLiteral, basestring)):
485+ collapse = (str(collapse).strip().lower() == 'true')
486+ if isinstance(ignore_empty, (TemplateParam, TemplateLiteral, basestring)):
487+ ignore_empty = (str(ignore_empty).strip().lower() == 'true')
488+
489+ # [% menu(page) %] vs [% menu(page.name) %]
490+ if isinstance(root, PageProxy): root = root.name
491+ #~ print "!!! ROOT, ", root
492+
493+ def add_namespace(path):
494+ mylevel = path.name.count(':')
495+
496+ if collapse:
497+ # Menu doesn't show whole tree, but only pages related to the current page.
498+ if mylevel > level:
499+ # "path" is more nested then the current page
500+ return
501+ elif level == mylevel:
502+ # "path" has same level as current page
503+ if not self._page.name == path.name and path.name:
504+ # Show only children of the current page or namespace pages
505+ return
506+ elif not self._page.name.startswith(path.name):
507+ # "path" is less nested, show only children of the ancestors
508+ # of the current page
509+ return
510+
511+ #~ print "!!! path & level", path, mylevel
512+ pagelist = list(self._notebook.index.list_pages(path))
513+ # find home page
514+ for page in pagelist:
515+ if page.name == home.name:
516+ home_page = page
517+ break
518+ else:
519+ # home page not in list
520+ home_page = None
521+
522+ # home page will be first item
523+ if not home_page is None:
524+ pagelist.remove(home_page)
525+ pagelist.insert(0, home_page)
526+
527+ builder.start('ul')
528+ for page in pagelist:
529+ if ignore_empty and not page.hascontent:
530+ #~ print "!!! skip page", page
531+ continue
532+ builder.start('li')
533+ text = page.basename
534+
535+ if self._page.name == page.name:
536+ # Current page is marked with the strong style
537+ builder.start('strong')
538+ builder.data(text)
539+ builder.end('strong')
540+ else:
541+ # links to other pages
542+ builder.start('link', {'type': 'page', 'href': page.name})
543+ builder.data(text)
544+ builder.end('link')
545+ builder.end('li')
546+ if page.haschildren:
547+ add_namespace(page) # recurs
548+ builder.end('ul')
549+
550+ builder.start('page')
551+ add_namespace(Path(root))
552+ builder.end('page')
553+
554+ tree = ParseTree(builder.close())
555+ if not tree:
556+ return None
557+
558+ #~ print "!!!", tree.tostring()
559+
560+ format = self._format
561+ linker = self._linker
562+ if linker:
563+ linker.set_path(self._page)
564+
565+ dumper = format.Dumper(
566+ linker=linker,
567+ template_options=self._options )
568+
569+ return ''.join(dumper.dump(tree))
570
571 class TemplateDict(object):
572 '''Object behaving like a dict for storing values of template parameters.
573@@ -751,7 +858,7 @@
574 for link in blinks:
575 source = self._notebook.get_page(link.source)
576 yield PageProxy(self._notebook, source, self._format, self._linker, self._options)
577-
578+
579
580 class ParseTreeProxy(object):
581