Attachment 'SearchInPagesAndSort-0.2.3.py'
Download 1 """
2 MoinMoin - SearchInPagesAndSort Macro
3 A line-oriented search macro over multiple pages, with sorting
4
5 Pascal Bauermeister <pascal.bauermeister@hispeed.ch>
6
7 Original version:
8 * [v0.1.0] 2003/04/24 10:32:04
9
10 Updates:
11
12 * [v0.2.3] Pascal Sun Jul 18 13:45:46 CEST 2004
13 Avoid endless recursion when matching page contains this macro
14
15 * [v0.2.2] Fri Jul 16 14:43:23 CEST 2004
16 - Use Request.redirect(). Thanks to Craig Johnson <cpjohnson@edcon.co.za>
17 and Thomas Waldmann <tw DASH public AT g m x DOT d e>.
18 - No more unused imports.
19 - Catch only expected exceptions.
20
21 * [v0.2.1] Mon Jun 7 11:54:52 CEST 2004
22 - options: links, heading
23 - works now with MoinMoin Release 1.2 too
24
25 * [v0.1.1] Wed Oct 29 14:48:02 CET 2003
26 works with MoinMoin Release 1.1 [Revision 1.173] and Python 2.3.2
27
28 -------------------------------------------------------------------------------
29
30 Usage:
31 [[ SearchInPagesAndSort (PageRegex, TextRegex, SortKey [,OPTIONS] ) ]]
32
33 Search for TextRegex in pages marching PagesRegex, and sort on
34 1) SortKey
35 2) TextRegex
36 3) found line
37
38 Options: (they are all contained in a coma-separated list of name=value pairs)
39 links=0 (or 1) Turns links to page for each hit off or on ; default is on.
40
41 heading=Regex After each hit, insert the string maching Regex, that
42 preceeds the hit in the source page.
43
44 unassigned=text Header for hits not matching the sort key. Default:
45 '[unassigned]'
46
47
48 -------------------------------------------------------------------------------
49
50 Sample 1:
51
52 Given a page named 'ProjectA':
53 1. Action Items
54 1. [Alan] {2} to launch this task
55 1. [Alan] {1} to do this urgent thing
56 1. [Ben][Clara] {3} do this as background task
57
58 1. Deadlines
59 1. 2003-03-12 <!> [Alan][Clara]: deliver 1st version of the Release X
60
61 ...and a page named 'ProjectB':
62 * [Denise] {2} Development of task Xyz
63 * [Eric] {1} Tests of feature F
64 * [Eric] (./) Tests of feature E
65
66 ...using the macro in a page named 'ActionItems' like this:
67 = ActionItems =
68 [[SearchInPagesAndSort("Project.*","{[123]}","\[[A-Za-z_]*\]")]]
69
70 = Deadlines =
71 [[SearchInPagesAndSort("Project.*","<!>")]]
72
73 = Completed tasks =
74 [[SearchInPagesAndSort("Project.*","(\./)","\[[A-Za-z_]*\]")]]
75
76 ...will give this output (note: _text_ are links):
77 ActionItems
78 + [Alan]
79 o [Alan] {1} to do this urgent thing _ProjectA_
80 o [Alan] {2} to launch this task _ProjectA_
81 + [Denise]
82 o [Denise] {2} Development of task Xyz _ProjectB_
83 + [Ben]
84 o [Ben][Clara] {3} do this as background task _ProjectA_
85 + [Eric]
86 o [Eric] {1} Tests of feature F _ProjectB_
87 + [Clara]
88 o [Ben][Clara] {3} do this as background task _ProjectA_
89
90 Deadlines
91 + 2003-03-12 <!> [Alan][Clara]: deliver 1st version of the Release X
92 _ProjectA_
93
94 Completed tasks
95 + [Eric]
96 o [Eric] (./) Tests of feature E _ProjectB_
97
98
99 Sample 2:
100
101 Given a page containing:
102 == Tasks for (ABC) ==
103 * {1} (due:2003-12-16) [Mike] Do this
104 == Tasks for (XYZ) ==
105 * {2} (due:2003-12-17) [John_Doe][Mike] Do that
106
107 ...the following macro call:
108 [[SearchInPagesAndSort("MyProjectStatus","{[123]}","\[[A-Za-z_ -]*\]", "links=0,heading=\([ab]*[0-9][0-9][0-9]\)")]]
109
110 ...will produce:
111 * [John_Doe]
112 * {2} (due:2003-12-17) [John_Doe][Mike] Do that (XYZ)
113
114 * [Mike]
115 * {1} (due:2003-12-16) [Mike] Do this (ABC)
116 * {2} (due:2003-12-17) [John_Doe][Mike] Do that (XYZ)
117 """
118
119 # Imports
120 import re, sys, cStringIO
121 from MoinMoin import config, wikiutil
122 from MoinMoin.Page import Page
123 from MoinMoin.parser.wiki import Parser
124
125
126 # Constants
127 _arg_page = r'(?P<hquote1>[\'"])(?P<hpage>.+?)(?P=hquote1)'
128 _arg_text = r'(?P<hquote2>[\'"])(?P<htext>.+?)(?P=hquote2)'
129 _arg_key = r'(?P<hquote3>[\'"])(?P<hkey>.+?)(?P=hquote3)'
130 _arg_opts = r'(?P<hquote4>[\'"])(?P<hopts>.+?)(?P=hquote4)'
131 _args_re = re.compile(r'^(%s( *, *%s( *, *%s( *, *%s)?)?)?)?$' %
132 (_arg_page, _arg_text, _arg_key, _arg_opts))
133
134 recursions = 0
135
136 def execute(macro, text, args_re=_args_re):
137
138 global recursions
139 if recursions: return '' ## 'SearchInPagesAndSort(%s)' % text
140
141 recursions += 1
142 try: return _execute(macro, text, args_re)
143 finally: recursions -=1
144
145
146 # The "raison d'etre" of this module
147 def _execute(macro, text, args_re=_args_re):
148
149 # parse and check arguments
150 args = args_re.match(text)
151 if text is None or not args:
152 return ( '<p><strong class="error">Invalid SearchInPages arguments' +
153 ' "%s"!</strong></p>' ) % text
154
155 text = args.group('htext')
156 pages = args.group('hpage')
157 key = args.group('hkey')
158 opts = args.group('hopts')
159
160 # get a list of pages matching the PageRegex
161 try:
162 pages_re = re.compile(pages, re.IGNORECASE)
163 except re.error:
164 pages_re = re.compile(re.escape(pages), re.IGNORECASE)
165 all_pages = wikiutil.getPageList(config.text_dir)
166 hits = filter(pages_re.search, all_pages)
167 hits.sort()
168
169 if len(hits) == 0:
170 return (
171 '<p><strong class="error">'
172 'No page matching "%s"!</strong></p>' % pages )
173
174 # parse options
175 options = {}
176 if opts != None:
177 for element in opts.split(','):
178 pair = element.split('=')
179 options[ pair[0] ] = pair[1]
180
181 try:
182 opt_links = eval(options['links'])
183 except KeyError:
184 opt_links = 1
185
186 try:
187 opt_heading = options['heading']
188 except KeyError:
189 opt_heading = None
190
191 try:
192 opt_unassigned_text = options['unassigned']
193 except KeyError:
194 opt_unassigned_text = "[unassigned]"
195
196 # compile all regex
197 try:
198 text_re = re.compile(text, re.IGNORECASE)
199 except re.error:
200 text_re = re.compile(re.escape(text), re.IGNORECASE)
201
202 if key != None:
203 try:
204 key_re = re.compile(key, re.IGNORECASE)
205 except re.error:
206 key_re = re.compile(re.escape(key), re.IGNORECASE)
207
208 if opt_heading != None:
209 try:
210 heading_re = re.compile(opt_heading, re.IGNORECASE)
211 except re.error:
212 heading_re = re.compile(re.escape(opt_heading), re.IGNORECASE)
213
214 #!!!
215 # we will collect matching lines in each matching page
216 all_matches = []
217
218 # treat each found page
219 for page_name in hits:
220 body = Page(page_name).get_raw_body()
221 pos = 0
222 last_start = -1
223 last_end = -1
224 heading_text = ""
225 while 1:
226 keep_line = 1
227
228 # search text
229 match = text_re.search(body, pos)
230 if not match: break
231
232 # text is found; now search for heading
233 if opt_heading != None:
234 heading_pos = pos
235 heading_match = True
236 # keep the nearest heading to the found text
237 while heading_match:
238 heading_match = heading_re.search(body, heading_pos)
239 if heading_match and heading_match.start() < match.start():
240 heading_text = heading_match.group(0)
241 heading_pos = heading_match.end()
242 else: heading_match = False
243
244 # point to found text
245 pos = match.end()+1
246
247 # cut before start of line
248 start_pos = match.start()
249 rev = 0
250 while body[start_pos] != '\n' and start_pos:
251 start_pos = start_pos - 1
252 rev = 1
253 if rev:
254 start_pos = start_pos + 1
255
256 # cut at end of line
257 end_pos = body.find("\n", match.end())
258
259 # extract line
260 line = body[start_pos:end_pos].strip()
261
262 # store this record if it differs from previous one
263 if start_pos == last_start or end_pos == last_end: keep_line = 0
264
265 # store this record if it it is not a comment
266 elif line.startswith("##"): keep_line = 0
267
268 # remove possible list item leaders
269 if keep_line:
270 for heading in ["*", "1.", "a.", "A.", "i.", "I."]:
271 if line.startswith(heading):
272 line = line.replace(heading, "", 1)
273 line = line.strip()
274 if len(line)==0: keep_line = 0
275
276 # handle this record
277 if keep_line:
278
279 # find the sort key
280 nbmatches = 0
281 keypos = 0
282 found = 0
283 while 1:
284 if key == None:
285 keyval = ""
286 else:
287 keymatch = key_re.search(line, keypos)
288 if keymatch:
289 keyval = line[keymatch.start():keymatch.end()]
290 keypos = keymatch.end()
291 nbmatches = nbmatches + 1
292 found = 1
293 else:
294 if nbmatches>0: break
295 keyval = opt_unassigned_text
296
297 # store info
298 item = []
299 item.append(keyval) # key text
300 item.append(body[match.start():match.end()]) # search text
301 item.append(line) # line text
302 item.append(page_name) # page name
303 item.append(heading_text) # heading
304 all_matches.append(item)
305 if found == 0: break
306
307 last_start = start_pos
308 last_end = end_pos
309
310 # sort and format records
311 bullet_list_open = macro.formatter.bullet_list(1)
312 bullet_list_close = macro.formatter.bullet_list(0)
313 listitem_open = macro.formatter.listitem(1)
314 listitem_close = macro.formatter.listitem(0)
315
316 all_matches.sort()
317 result = ""
318 result = result+"\n" + bullet_list_open
319 keyval = ""
320 head_count = 0
321
322 # treat records for output
323 for item in all_matches:
324 text = item[2]
325 pagename = item[3]
326 heading_text = item[4]
327
328 # parse the text (in wiki source format) and make HTML,
329 # after diverting sys.stdout to a string
330 str_out = cStringIO.StringIO() # create str to collect output
331 macro.request.redirect(str_out) # divert output to that string
332 # parse this line (this will also execute macros !) :
333 Parser(text, macro.request).format(macro.formatter)
334 macro.request.redirect() # restore output
335 text_fmtted = str_out.getvalue() # get what was generated
336 text_fmtted = text_fmtted.strip(' ') # preserve newlines
337
338 # empty text => drop this item
339 if len(text_fmtted)==0: continue
340
341 # insert heading (only if not yet done)
342 if key != None and item[0] != keyval:
343 # this is a new heading
344 keyval = item[0]
345 if head_count:
346 result = result+"\n " + bullet_list_close
347 result = result+"\n " + listitem_close
348 head_count = head_count +1
349 result = result+"\n " + listitem_open
350 result = result+ keyval
351 result = result+"\n " + bullet_list_open
352
353 # correct text the format (berk)
354 if text_fmtted.startswith("\n<p>"):
355 text_fmtted = text_fmtted[4:]
356 if text_fmtted.endswith("</p>\n"):
357 text_fmtted = text_fmtted[:-5]
358 text_trailer = "\n</p>\n"
359 else: text_trailer = ""
360
361 # insert text
362 result = result+"\n " + listitem_open
363 result = result + text_fmtted
364 if opt_links:
365 result = result + " <font size=-1>"
366 try: # try MoinMoin 1.1 API
367 link_text = wikiutil.link_tag(pagename)
368 except TypeError: # try MoinMoin 1.2 API
369 link_text = wikiutil.link_tag(macro.request, pagename)
370 result = result + link_text
371 result = result + "</font>"
372 if opt_heading != None:
373 result = result + " <font size=-1>"
374 result = result + heading_text
375 result = result + "</font>"
376 result = result + text_trailer + "\n " + listitem_close
377
378 # all items done, close (hopefully) gracefully
379 if head_count:
380 result = result+"\n " + listitem_close
381 result = result+"\n " + bullet_list_close
382 if key != None:
383 result = result+"\n " + listitem_close
384 result = result+"\n" + bullet_list_close
385
386 # done
387 return result
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.