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 + "&nbsp;&nbsp;&nbsp;<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 + "&nbsp;&nbsp;&nbsp;<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.
  • [get | view] (2004-07-18 19:23:39, 12.4 KB) [[attachment:SearchInPagesAndSort-0.2.2.py]]
  • [get | view] (2004-07-18 19:25:17, 13.0 KB) [[attachment:SearchInPagesAndSort-0.2.3.py]]
  • [get | view] (2004-07-19 21:57:17, 12.8 KB) [[attachment:SearchInPagesAndSort-0.2.4.py]]
  • [get | view] (2004-07-19 21:56:29, 12.8 KB) [[attachment:SearchInPagesAndSort.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.