1 # copyright 2003-2012 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 # |
|
4 # This file is part of CubicWeb. |
|
5 # |
|
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 # terms of the GNU Lesser General Public License as published by the Free |
|
8 # Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 # any later version. |
|
10 # |
|
11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 # details. |
|
15 # |
|
16 # You should have received a copy of the GNU Lesser General Public License along |
|
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 """inline help system, rendering ReST files in the `wdoc` subdirectory of |
|
19 CubicWeb and cubes |
|
20 |
|
21 """ |
|
22 __docformat__ = "restructuredtext en" |
|
23 |
|
24 from itertools import chain |
|
25 from os.path import join |
|
26 from bisect import bisect_right |
|
27 from datetime import date |
|
28 |
|
29 from logilab.common.changelog import ChangeLog |
|
30 from logilab.common.date import strptime, todate |
|
31 from logilab.common.registry import yes |
|
32 from logilab.mtconverter import CHARSET_DECL_RGX |
|
33 |
|
34 from cubicweb.predicates import match_form_params |
|
35 from cubicweb.view import StartupView |
|
36 from cubicweb.uilib import rest_publish |
|
37 from cubicweb.web import NotFound, action |
|
38 from cubicweb import _ |
|
39 |
|
40 # table of content management ################################################# |
|
41 |
|
42 try: |
|
43 from xml.etree.ElementTree import parse |
|
44 except ImportError: |
|
45 from elementtree.ElementTree import parse |
|
46 |
|
47 def build_toc_index(node, index): |
|
48 try: |
|
49 nodeidx = node.attrib['resource'] |
|
50 assert not nodeidx in index, nodeidx |
|
51 index[nodeidx] = node |
|
52 except KeyError: |
|
53 pass |
|
54 for child in node: |
|
55 build_toc_index(child, index) |
|
56 child.parent = node |
|
57 |
|
58 def get_insertion_point(section, index): |
|
59 if section.attrib.get('insertafter'): |
|
60 snode = index[section.attrib['insertafter']] |
|
61 node = snode.parent |
|
62 idx = node.getchildren().index(snode) + 1 |
|
63 elif section.attrib.get('insertbefore'): |
|
64 snode = index[section.attrib['insertbefore']] |
|
65 node = snode.parent |
|
66 idx = node.getchildren().index(snode) |
|
67 elif 'appendto' in section.attrib: |
|
68 node = index[section.attrib['appendto']] |
|
69 idx = None |
|
70 else: |
|
71 node, idx = None, None |
|
72 return node, idx |
|
73 |
|
74 def build_toc(config): |
|
75 alltocfiles = reversed(tuple(config.locate_all_files('toc.xml'))) |
|
76 maintoc = parse(next(alltocfiles)).getroot() |
|
77 maintoc.parent = None |
|
78 index = {} |
|
79 build_toc_index(maintoc, index) |
|
80 # insert component documentation into the tree according to their toc.xml |
|
81 # file |
|
82 for fpath in alltocfiles: |
|
83 toc = parse(fpath).getroot() |
|
84 for section in toc: |
|
85 node, idx = get_insertion_point(section, index) |
|
86 if node is None: |
|
87 continue |
|
88 if idx is None: |
|
89 node.append(section) |
|
90 else: |
|
91 node.insert(idx, section) |
|
92 section.parent = node |
|
93 build_toc_index(section, index) |
|
94 return index |
|
95 |
|
96 def title_for_lang(node, lang): |
|
97 fallback_title = None |
|
98 for title in node.findall('title'): |
|
99 title_lang = title.attrib['{http://www.w3.org/XML/1998/namespace}lang'] |
|
100 if title_lang == lang: |
|
101 return unicode(title.text) |
|
102 if title_lang == 'en': |
|
103 fallback_title = unicode(title.text) |
|
104 return fallback_title |
|
105 |
|
106 def subsections(node): |
|
107 return [child for child in node if child.tag == 'section'] |
|
108 |
|
109 # help views ################################################################## |
|
110 |
|
111 class InlineHelpView(StartupView): |
|
112 __select__ = match_form_params('fid') |
|
113 __regid__ = 'wdoc' |
|
114 title = _('site documentation') |
|
115 |
|
116 def call(self): |
|
117 fid = self._cw.form['fid'] |
|
118 vreg = self._cw.vreg |
|
119 for lang in chain((self._cw.lang, vreg.property_value('ui.language')), |
|
120 vreg.config.available_languages()): |
|
121 rid = '%s_%s.rst' % (fid, lang) |
|
122 resourcedir = vreg.config.locate_doc_file(rid) |
|
123 if resourcedir: |
|
124 break |
|
125 else: |
|
126 raise NotFound |
|
127 self.tocindex = build_toc(vreg.config) |
|
128 try: |
|
129 node = self.tocindex[fid] |
|
130 except KeyError: |
|
131 node = None |
|
132 else: |
|
133 self.navigation_links(node) |
|
134 self.w(u'<div class="hr"></div>') |
|
135 self.w(u'<h1>%s</h1>' % (title_for_lang(node, self._cw.lang))) |
|
136 data = open(join(resourcedir, rid)).read() |
|
137 self.w(rest_publish(self, data)) |
|
138 if node is not None: |
|
139 self.subsections_links(node) |
|
140 self.w(u'<div class="hr"></div>') |
|
141 self.navigation_links(node) |
|
142 |
|
143 def navigation_links(self, node): |
|
144 req = self._cw |
|
145 parent = node.parent |
|
146 if parent is None: |
|
147 return |
|
148 brothers = subsections(parent) |
|
149 self.w(u'<div class="docnav">\n') |
|
150 previousidx = brothers.index(node) - 1 |
|
151 if previousidx >= 0: |
|
152 self.navsection(brothers[previousidx], 'prev') |
|
153 self.navsection(parent, 'up') |
|
154 nextidx = brothers.index(node) + 1 |
|
155 if nextidx < len(brothers): |
|
156 self.navsection(brothers[nextidx], 'next') |
|
157 self.w(u'</div>\n') |
|
158 |
|
159 navinfo = {'prev': ('', 'data/previous.png', _('i18nprevnext_previous')), |
|
160 'next': ('', 'data/next.png', _('i18nprevnext_next')), |
|
161 'up': ('', 'data/up.png', _('i18nprevnext_up'))} |
|
162 |
|
163 def navsection(self, node, navtype): |
|
164 htmlclass, imgpath, msgid = self.navinfo[navtype] |
|
165 self.w(u'<span class="%s">' % htmlclass) |
|
166 self.w(u'%s : ' % self._cw._(msgid)) |
|
167 self.w(u'<a href="%s">%s</a>' % ( |
|
168 self._cw.build_url('doc/'+node.attrib['resource']), |
|
169 title_for_lang(node, self._cw.lang))) |
|
170 self.w(u'</span>\n') |
|
171 |
|
172 def subsections_links(self, node, first=True): |
|
173 sub = subsections(node) |
|
174 if not sub: |
|
175 return |
|
176 if first: |
|
177 self.w(u'<div class="hr"></div>') |
|
178 self.w(u'<ul class="docsum">') |
|
179 for child in sub: |
|
180 self.w(u'<li><a href="%s">%s</a>' % ( |
|
181 self._cw.build_url('doc/'+child.attrib['resource']), |
|
182 title_for_lang(child, self._cw.lang))) |
|
183 self.subsections_links(child, False) |
|
184 self.w(u'</li>') |
|
185 self.w(u'</ul>\n') |
|
186 |
|
187 |
|
188 |
|
189 class InlineHelpImageView(StartupView): |
|
190 __regid__ = 'wdocimages' |
|
191 __select__ = match_form_params('fid') |
|
192 binary = True |
|
193 templatable = False |
|
194 content_type = 'image/png' |
|
195 |
|
196 def call(self): |
|
197 fid = self._cw.form['fid'] |
|
198 for lang in chain((self._cw.lang, self._cw.vreg.property_value('ui.language')), |
|
199 self._cw.vreg.config.available_languages()): |
|
200 rid = join('images', '%s_%s.png' % (fid, lang)) |
|
201 resourcedir = self._cw.vreg.config.locate_doc_file(rid) |
|
202 if resourcedir: |
|
203 break |
|
204 else: |
|
205 raise NotFound |
|
206 self.w(open(join(resourcedir, rid)).read()) |
|
207 |
|
208 |
|
209 |
|
210 class HelpAction(action.Action): |
|
211 __regid__ = 'help' |
|
212 __select__ = yes() |
|
213 |
|
214 category = 'footer' |
|
215 order = 0 |
|
216 title = _('Help') |
|
217 |
|
218 def url(self): |
|
219 return self._cw.build_url('doc/main') |
|
220 |
|
221 |
|
222 class AboutAction(action.Action): |
|
223 __regid__ = 'about' |
|
224 __select__ = yes() |
|
225 |
|
226 category = 'footer' |
|
227 order = 2 |
|
228 title = _('About this site') |
|
229 |
|
230 def url(self): |
|
231 return self._cw.build_url('doc/about') |
|