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