|
1 """inline help system, using ReST file in products `wdoc` directory |
|
2 |
|
3 :organization: Logilab |
|
4 :copyright: 2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
6 """ |
|
7 __docformat__ = "restructuredtext en" |
|
8 |
|
9 from itertools import chain |
|
10 from os.path import join |
|
11 from bisect import bisect_right |
|
12 |
|
13 from mx.DateTime import strptime, today |
|
14 |
|
15 from logilab.common.changelog import ChangeLog |
|
16 from logilab.mtconverter import CHARSET_DECL_RGX |
|
17 |
|
18 from cubicweb.common.selectors import req_form_params_selector |
|
19 from cubicweb.common.view import StartupView |
|
20 from cubicweb.common.uilib import rest_publish |
|
21 from cubicweb.web import NotFound |
|
22 |
|
23 _ = unicode |
|
24 |
|
25 # table of content management ################################################# |
|
26 |
|
27 try: |
|
28 from xml.etree.ElementTree import parse |
|
29 except ImportError: |
|
30 from elementtree.ElementTree import parse |
|
31 |
|
32 def build_toc_index(node, index): |
|
33 try: |
|
34 nodeidx = node.attrib['resource'] |
|
35 assert not nodeidx in index, nodeidx |
|
36 index[nodeidx] = node |
|
37 except KeyError: |
|
38 pass |
|
39 for child in node: |
|
40 build_toc_index(child, index) |
|
41 child.parent = node |
|
42 |
|
43 def get_insertion_point(section, index): |
|
44 if section.attrib.get('insertafter'): |
|
45 snode = index[section.attrib['insertafter']] |
|
46 node = snode.parent |
|
47 idx = node.getchildren().index(snode) + 1 |
|
48 elif section.attrib.get('insertbefore'): |
|
49 snode = index[section.attrib['insertbefore']] |
|
50 node = snode.parent |
|
51 idx = node.getchildren().index(snode) |
|
52 else: |
|
53 node = index[section.attrib['appendto']] |
|
54 idx = None |
|
55 return node, idx |
|
56 |
|
57 def build_toc(config): |
|
58 alltocfiles = reversed(tuple(config.locate_all_files('toc.xml'))) |
|
59 maintoc = parse(alltocfiles.next()).getroot() |
|
60 maintoc.parent = None |
|
61 index = {} |
|
62 build_toc_index(maintoc, index) |
|
63 # insert component documentation into the tree according to their toc.xml |
|
64 # file |
|
65 for fpath in alltocfiles: |
|
66 toc = parse(fpath).getroot() |
|
67 for section in toc: |
|
68 node, idx = get_insertion_point(section, index) |
|
69 if idx is None: |
|
70 node.append(section) |
|
71 else: |
|
72 node.insert(idx, section) |
|
73 section.parent = node |
|
74 build_toc_index(section, index) |
|
75 return index |
|
76 |
|
77 def title(node, lang): |
|
78 for title in node.findall('title'): |
|
79 if title.attrib['{http://www.w3.org/XML/1998/namespace}lang'] == lang: |
|
80 return unicode(title.text) |
|
81 |
|
82 def subsections(node): |
|
83 return [child for child in node if child.tag == 'section'] |
|
84 |
|
85 # help views ################################################################## |
|
86 |
|
87 class InlineHelpView(StartupView): |
|
88 __selectors__ = (req_form_params_selector,) |
|
89 form_params = ('fid',) |
|
90 id = 'wdoc' |
|
91 title = _('site documentation') |
|
92 |
|
93 def call(self): |
|
94 fid = self.req.form['fid'] |
|
95 for lang in chain((self.req.lang, self.vreg.property_value('ui.language')), |
|
96 self.config.available_languages()): |
|
97 rid = '%s_%s.rst' % (fid, lang) |
|
98 resourcedir = self.config.locate_doc_file(rid) |
|
99 if resourcedir: |
|
100 break |
|
101 else: |
|
102 raise NotFound |
|
103 self.tocindex = build_toc(self.config) |
|
104 try: |
|
105 node = self.tocindex[fid] |
|
106 except KeyError: |
|
107 node = None |
|
108 else: |
|
109 self.navigation_links(node) |
|
110 self.w(u'<div class="hr"></div>') |
|
111 self.w(u'<h1>%s</h1>' % (title(node, self.req.lang))) |
|
112 data = open(join(resourcedir, rid)).read() |
|
113 self.w(rest_publish(self, data)) |
|
114 if node is not None: |
|
115 self.subsections_links(node) |
|
116 self.w(u'<div class="hr"></div>') |
|
117 self.navigation_links(node) |
|
118 |
|
119 def navigation_links(self, node): |
|
120 req = self.req |
|
121 parent = node.parent |
|
122 if parent is None: |
|
123 return |
|
124 brothers = subsections(parent) |
|
125 self.w(u'<div class="docnav">\n') |
|
126 previousidx = brothers.index(node) - 1 |
|
127 if previousidx >= 0: |
|
128 self.navsection(brothers[previousidx], 'prev') |
|
129 self.navsection(parent, 'up') |
|
130 nextidx = brothers.index(node) + 1 |
|
131 if nextidx < len(brothers): |
|
132 self.navsection(brothers[nextidx], 'next') |
|
133 self.w(u'</div>\n') |
|
134 |
|
135 navinfo = {'prev': ('', 'data/previous.png', _('i18nprevnext_previous')), |
|
136 'next': ('', 'data/next.png', _('i18nprevnext_next')), |
|
137 'up': ('', 'data/up.png', _('i18nprevnext_up'))} |
|
138 |
|
139 def navsection(self, node, navtype): |
|
140 htmlclass, imgpath, msgid = self.navinfo[navtype] |
|
141 self.w(u'<span class="%s">' % htmlclass) |
|
142 self.w(u'%s : ' % self.req._(msgid)) |
|
143 self.w(u'<a href="%s">%s</a>' % ( |
|
144 self.req.build_url('doc/'+node.attrib['resource']), |
|
145 title(node, self.req.lang))) |
|
146 self.w(u'</span>\n') |
|
147 |
|
148 def subsections_links(self, node, first=True): |
|
149 sub = subsections(node) |
|
150 if not sub: |
|
151 return |
|
152 if first: |
|
153 self.w(u'<div class="hr"></div>') |
|
154 self.w(u'<ul class="docsum">') |
|
155 for child in sub: |
|
156 self.w(u'<li><a href="%s">%s</a>' % ( |
|
157 self.req.build_url('doc/'+child.attrib['resource']), |
|
158 title(child, self.req.lang))) |
|
159 self.subsections_links(child, False) |
|
160 self.w(u'</li>') |
|
161 self.w(u'</ul>\n') |
|
162 |
|
163 |
|
164 |
|
165 class InlineHelpImageView(StartupView): |
|
166 __selectors__ = (req_form_params_selector,) |
|
167 form_params = ('fid',) |
|
168 id = 'wdocimages' |
|
169 binary = True |
|
170 templatable = False |
|
171 content_type = 'image/png' |
|
172 |
|
173 def call(self): |
|
174 fid = self.req.form['fid'] |
|
175 for lang in chain((self.req.lang, self.vreg.property_value('ui.language')), |
|
176 self.config.available_languages()): |
|
177 rid = join('images', '%s_%s.png' % (fid, lang)) |
|
178 resourcedir = self.config.locate_doc_file(rid) |
|
179 if resourcedir: |
|
180 break |
|
181 else: |
|
182 raise NotFound |
|
183 self.w(open(join(resourcedir, rid)).read()) |
|
184 |
|
185 |
|
186 class ChangeLogView(StartupView): |
|
187 id = 'changelog' |
|
188 title = _('What\'s new?') |
|
189 maxentries = 25 |
|
190 |
|
191 def call(self): |
|
192 rid = 'ChangeLog_%s' % (self.req.lang) |
|
193 allentries = [] |
|
194 title = self.req._(self.title) |
|
195 restdata = ['.. -*- coding: utf-8 -*-', '', title, '='*len(title), ''] |
|
196 w = restdata.append |
|
197 for fpath in self.config.locate_all_files(rid): |
|
198 cl = ChangeLog(fpath) |
|
199 encoding = 'utf-8' |
|
200 # additional content may be found in title |
|
201 for line in (cl.title + cl.additional_content).splitlines(): |
|
202 m = CHARSET_DECL_RGX.search(line) |
|
203 if m is not None: |
|
204 encoding = m.group(1) |
|
205 continue |
|
206 elif line.startswith('.. '): |
|
207 w(unicode(line, encoding)) |
|
208 for entry in cl.entries: |
|
209 if entry.date: |
|
210 date = strptime(entry.date, '%Y-%m-%d') |
|
211 else: |
|
212 date = today() |
|
213 messages = [] |
|
214 for msglines, submsgs in entry.messages: |
|
215 msgstr = unicode(' '.join(l.strip() for l in msglines), encoding) |
|
216 msgstr += u'\n\n' |
|
217 for submsglines in submsgs: |
|
218 msgstr += ' - ' + unicode(' '.join(l.strip() for l in submsglines), encoding) |
|
219 msgstr += u'\n' |
|
220 messages.append(msgstr) |
|
221 entry = (date, messages) |
|
222 allentries.insert(bisect_right(allentries, entry), entry) |
|
223 latestdate = None |
|
224 i = 0 |
|
225 for date, messages in reversed(allentries): |
|
226 if latestdate != date: |
|
227 fdate = self.format_date(date) |
|
228 w(u'\n%s' % fdate) |
|
229 w('~'*len(fdate)) |
|
230 latestdate = date |
|
231 for msg in messages: |
|
232 w(u'* %s' % msg) |
|
233 i += 1 |
|
234 if i > self.maxentries: |
|
235 break |
|
236 w('') # blank line |
|
237 self.w(rest_publish(self, '\n'.join(restdata))) |
|
238 |