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 + "&nbsp;&nbsp;&nbsp;<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 + "&nbsp;&nbsp;&nbsp;<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.
  • [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.