Attachment 'SearchInPagesAndSort.py'

Download

   1 """
   2 MoinMoin - SearchInPagesAndSort Macro
   3 A line-oriented search macro over multiple pages, with sorting
   4 
   5 @copyright: Pascal Bauermeister <pascal DOT bauermeister AT hispeed DOT ch>
   6 @license: GPL
   7 
   8 Updates:
   9   * [v0.3.6] Brecht Fri Jul 18 10:05:21 CEST 2008
  10     * Made compatible with MoinMoin 1.6/1.7
  11 
  12   * [v0.3.5pre] Pascal Tue Sep 13 11:33:31 CEST 2005
  13     * Added AutoHeading, AutoHeadingFormat
  14 
  15   * [v0.3.4] Pascal Sat Mar  5 17:53:08 CET 2005
  16     * MoinMoin 1.3.x _and_ 1.2.x compatible
  17     * Added arguments: Format, HeaderFormat and FormatSort
  18     
  19   * [v0.3.3] Pascal
  20     * Fixed a security hole (eval used for arguments parsing)
  21     * Added argument: ExcludePages=regex
  22 
  23   * [v0.3.2] Pascal
  24     * Use StringIO instead of cStringIO, for unicode compatibility
  25 
  26   * [v0.3.1] Pascal Sat Nov  6 16:03:01 CET 2004
  27     * Added NoText, RawText, NbSubs and MoreSubsText arguments
  28 
  29   * [v0.3.1] Pascal Mon Aug 30 21:27:36 CEST 2004
  30     * Corrected bug: did not work well with multiple pages hit.
  31       Bug reported by Craig Johnson.
  32       It worked in 0.2.x because one bug corrected another one...
  33     * If args are not a kw list (e.g. old macro form) inserts usage in html
  34       page (brutal, but we really don't want to support the old form any more)
  35 
  36   * [v0.3.0] Pascal Wed Aug 18 15:39:54 CEST 2004
  37     * macro arguments are now passed as a list of KEYWORD=VALUE
  38     * ACL is handled
  39     * new options: Reverse and NoHeader
  40 
  41   * [v0.2.4] Pascal Mon Jul 19 23:40:54 CEST 2004
  42     * Comparisons to None use the 'is' and 'is not' operator (nicer)
  43     * Use get() for dict lookup w/ default value
  44     * Do not quote args and retry to compile if they are not valid regexes
  45     * Corrected usage samples in the comment below
  46 
  47   * [v0.2.3] Pascal Sun Jul 18 13:45:46 CEST 2004
  48     Avoid endless recursion when matching page contains this macro
  49 
  50   * [v0.2.2] Fri Jul 16 14:43:23 CEST 2004
  51     * Use Request.redirect(). Thanks to Craig Johnson <cpjohnson AT edcon DOT
  52       co DOT za>
  53       and Thomas Waldmann <tw DASH public AT g m x DOT d e>.
  54     * No more unused imports.
  55     * Catch only expected exceptions.
  56 
  57   * [v0.2.1] Mon Jun  7 11:54:52 CEST 2004
  58     * options: links, heading
  59     * works now with MoinMoin Release 1.2 too
  60 
  61   * [v0.1.1] Wed Oct 29 14:48:02 CET 2003
  62     works with MoinMoin Release 1.1 [Revision 1.173] and Python 2.3.2
  63 
  64   * [v0.1.0] 2003/04/24 10:32:04
  65     Original version
  66 
  67 ----
  68 
  69 Usage:
  70   [[ SearchInPagesAndSort ]]
  71   [[ SearchInPagesAndSort (KEYWORD=VALUE [, ...] ) ]]
  72 
  73 Search for 'searchtext' regex in pages marching 'pages' regex, and
  74 sort the found lines (=hits) in this order:
  75   1) substring of the hit matching 'sortkey'; group same matches of
  76      'sortkey' by a header
  77   2) substring of the hit matching 'searchtext'
  78   3) the hit itself
  79 
  80 If no arguments are given, the usage is inserted in the HTML result.
  81 Possible keywords:
  82 
  83   Help           = 0, 1, 2         Displays 1:short or 2:full help in the page.
  84                                    Default: 0 (i.e. no help).
  85 
  86   Pages          = 'PAGES REGEX'   Pages in which the text is sought. If
  87                                    empty (default) search in the current page
  88                                    and defaults 'NoLinks' to 1.
  89                                    Default: empty (i.e. current page).
  90 
  91   ExcludePages   = 'PAGES REGEX'   Exclude these pages (i.e. remove these pages
  92                                    from the list collected by 'Pages').
  93                                    Default: empty (i.e. don't exclude any).
  94 
  95   SearchText     = 'TEXT REGEX'    To search for lines in matching pages.
  96                                    Mandatory!
  97 
  98   SortKey        = 'TEXT REGEX'    Criterion to sort matching lines (=hits).
  99                                    Default: empty (i.e. no sorting).
 100 
 101   Heading        = 'TEXT REGEX'    Follow each hit by the text maching Regex,
 102                                    that preceeds the hit in its source page.
 103                                    Default: empty (i.e. no headings).
 104 
 105   UnassignedText = 'WIKI TEXT'     Header for hits not matching the sort key.
 106                                    Default: '[unassigned]'.
 107 
 108   Reverse        = 0 or 1          Reverse-sort the hits.
 109                                    Default: 0 (i.e. forward sort).
 110 
 111   RawText        = 0 or 1          Do not format found text.
 112                                    Default: 0 (i.e. formatted).
 113 
 114   Format         = 'STRING'        Explicitely format the output using this
 115                                    string, which can contain wiki formatting
 116                                    as well as these tokens:
 117                                      @KT@ : text matching 'SortKey'
 118                                      @ST@ : text matching 'SearchText'
 119                                      @FT@ : line of text
 120                                      @PN@ : page name
 121                                      @HT@ : heading text
 122                                      @SU@ : subtext
 123                                      @@   : the '@' character
 124                                      \\n  : newline (of wiki source text).
 125 
 126                                    Each token can contain a regex acting as
 127                                    a filter for displaying the value, e.g:
 128                                      @FT:{[123]}@      displays the prio smiley
 129 
 130                                    Multiple groups can be defined, in which
 131                                    case the text matching them will be
 132                                    displayed, e.g:
 133                                      @FT:{[123]}(.*)@  displays text after prio
 134 
 135                                    Default: '' (i.e. auto-formatting).
 136 
 137   HeaderFormat   = 'STRING'        If specified, use this instead of 'Format'
 138                                    for headers.
 139                                    Default: '' (i.e. do not display headers).
 140 
 141   FormatSort     = 0 or 1          If 1, sort the output generated by 'Format'
 142                                    (if 'Reverse' is 1, reverse-sort). If 0,
 143                                    leave the output sorted by the 'SortKey'
 144                                    criterion (if specified).
 145                                    Default: 0 (i.e. no sorting).
 146 
 147   Unique         = 0 or 1          If 1, make formatted output lines unique.
 148                                    Default: 0 (i.e. no filtering).
 149 
 150   NbSubs         = 0, N, or 'all'  Follow each hit by max N sub lines (i.e.
 151                                    next lines with greater indent) of source
 152                                    text following the hit. If N is 'all', take
 153                                    *all* sub lines. If N is positive, take N
 154                                    *first* sub lines. If N is negative, take
 155                                    the |N| *last* sub lines.
 156                                    Default: 0 (i.e. do *not* include subs).
 157 
 158   MoreSubsText   = 'WIKI TEXT'     If there are more sub lines than 'NbSubs',
 159                                    follow/preceed the last/first sub lines by
 160                                    this text.
 161                                    Default: '...'.
 162 
 163   NoHeader       = 0 or 1          Disable showing the headers as subtitles.
 164                                    Default: 0 (i.e. show headers).
 165 
 166   NoLinks        = 0 or 1          Disable following each hit by a link to its
 167                                    page.
 168                                    Default: 0 (i.e. show links) or 1 if
 169                                    'Pages' is omitted.
 170 
 171   NoPageText     = 'HTML TEXT'     Text displayed if no page match 'Pages'.
 172                                    Default: an error message w/ Page regex
 173 
 174   NoText         = 0 or 1          Disables showing the found text.
 175                                    Default: 0 (i.e. show found text).
 176 
 177 Keywords can be also given in upper or lower cases, or abbreviated.
 178 Example: SearchText, searchtext, SEARCHTEXT, st, ST, Pages, p, etc.
 179 
 180 ----
 181 
 182 Sample 1:
 183 
 184   Given a page named 'ProjectA':
 185         1. Action Items
 186           1. [Alan] {2} to launch this task
 187           1. [Alan] {1} to do this urgent thing
 188           1. [Ben][Clara] {3} do this as background task
 189 
 190         1. Deadlines
 191           1. 2003-03-12 <!> [Alan][Clara]: deliver 1st version of the Release X
 192 
 193   ...and a page named 'ProjectB':
 194         * [Denise] {2} Development of task Xyz
 195         * [Eric] {1} Tests of feature F
 196         * [Eric] (./) Tests of feature E
 197 
 198   ...using the macro in a page named 'ActionItems' like this:
 199         = ActionItems =
 200         [[SearchInPagesAndSort(pages="Project.*", searchtext="{[123]}", sortkey="\[[A-Za-z_]*\]")]]
 201 
 202         = Deadlines =
 203         [[SearchInPagesAndSort(pages="Project.*", searchtext="<!>")]]
 204 
 205         = Completed tasks =
 206         [[SearchInPagesAndSort(pages="Project.*", searchtext"(\./)", sortkey="\[[A-Za-z_]*\]")]]
 207 
 208   ...will give this output (note: _text_ are links):
 209         ActionItems
 210           * [Alan]
 211             * [Alan] {1} to do this urgent thing _ProjectA_
 212             * [Alan] {2} to launch this task _ProjectA_
 213           * [Denise]
 214             * [Denise] {2} Development of task Xyz _ProjectB_
 215           * [Ben]
 216             * [Ben][Clara] {3} do this as background task _ProjectA_
 217           * [Eric]
 218             * [Eric] {1} Tests of feature F _ProjectB_
 219           * [Clara]
 220             * [Ben][Clara] {3} do this as background task _ProjectA_
 221 
 222         Deadlines
 223           * 2003-03-12 <!> [Alan][Clara]: deliver 1st version of the Release X
 224             _ProjectA_
 225 
 226         Completed tasks
 227           * [Eric]
 228             * [Eric] (./) Tests of feature E _ProjectB_
 229 
 230 
 231 Sample 2:
 232 
 233   Given a page containing:
 234         == Tasks for (ABC) ==
 235          * {1} (due:2003-12-16) [Mike] Do this
 236         == Tasks for (XYZ) ==
 237          * {2} (due:2003-12-17) [John_Doe][Mike] Do that
 238 
 239   ...the following macro call in the same page:
 240         [[SearchInPagesAndSort(searchtext="{[123]}", sortkey="\[[A-Za-z_ -]*\]", links=0, heading="\([ab]*[0-9][0-9][0-9]\)")]]
 241 
 242   ...will produce:
 243         * [John_Doe]
 244           * {2} (due:2003-12-17) [John_Doe][Mike] Do that (XYZ)
 245 
 246         * [Mike]
 247           * {1} (due:2003-12-16) [Mike] Do this (ABC)
 248           * {2} (due:2003-12-17) [John_Doe][Mike] Do that (XYZ)
 249 """
 250 
 251 # Imports
 252 import re, sys, StringIO, urllib
 253 from string import ascii_lowercase, maketrans
 254 from MoinMoin import config, wikiutil, version
 255 from MoinMoin.Page import Page
 256 from MoinMoin.parser import text_moin_wiki
 257 
 258 before_1_3 = version.release < '1.3'
 259 
 260 Dependencies = ["time"] # macro cannot be cached
 261 
 262 _recursions = 0
 263 FAKETRANS = maketrans ("","")
 264 
 265 
 266 class _Error (Exception):
 267     pass
 268 
 269 
 270 def execute (macro, text, args_re=None):
 271 
 272     global _recursions
 273     if _recursions: return ''
 274 
 275     _recursions += 1
 276     try:     res = _execute (macro, text)
 277     except _Error, msg:
 278         _recursions = 0
 279         return """
 280         <p><strong class="error">
 281         Error: macro SearchInPagesAndSort: %s</strong> </p>
 282         """ % msg
 283 
 284     _recursions -=1
 285     return res
 286 
 287 
 288 def _delparam (keyword, params):
 289     value = params [keyword]
 290     del params [keyword]
 291     return value
 292 
 293 
 294 def _param_get (params, spec, default):
 295 
 296     """Returns the value for a parameter, if specified with one of
 297     several acceptable keyword names, or returns its default value if
 298     it is missing from the macro call. If the parameter is specified,
 299     it is removed from the list, so that remaining params can be
 300     signalled as unknown"""
 301 
 302     # param name is litteral ?
 303     if params.has_key (spec): return _delparam (spec, params)
 304 
 305     # param name is all lower or all upper ?
 306     lspec = spec.lower ()
 307     if params.has_key (lspec): return _delparam (lspec, params)
 308     uspec = spec.upper ()
 309     if params.has_key (uspec): return _delparam (uspec, params)
 310 
 311     # param name is abbreviated ?
 312     cspec = spec [0].upper () + spec [1:] # capitalize 1st letter
 313     cspec = cspec.translate (FAKETRANS, ascii_lowercase)
 314     if params.has_key (cspec): return _delparam (cspec, params)
 315     cspec = cspec.lower ()
 316     if params.has_key (cspec): return _delparam (cspec, params)
 317 
 318     # nope: return default value
 319     return default
 320 
 321 
 322 def _usage (full = False):
 323 
 324     """Returns the interesting part of the module's doc"""
 325 
 326     if full: return __doc__
 327 
 328     lines = __doc__.replace ('\\n', '\\\\n'). splitlines ()
 329     start = 0
 330     end = len (lines)
 331     for i in range (end):
 332         if lines [i].strip ().lower () == "usage:":
 333             start = i
 334             break
 335     for i in range (start, end):
 336         if lines [i].startswith ('--'):
 337             end = i
 338             break
 339     return '\n'.join (lines [start:end])
 340 
 341 
 342 def _re_compile (text, name):
 343     try:
 344         return re.compile (text, re.IGNORECASE)
 345     except Exception, msg:
 346         raise _Error ("%s for regex argument %s: '%s'" % (msg, name, text))
 347 
 348 
 349 def _indent_of (line, pos=0):
 350     n = 0
 351     for c in line [pos:]:
 352         if c != ' ': break
 353         n = n + 1
 354     return n
 355 
 356 
 357 last_request_h = None
 358 last_pages_list = []
 359 
 360 def _get_all_pages (request):
 361     global last_request_h
 362     global last_pages_list
 363     request_h = hash (request)
 364     if request_h != last_request_h:
 365         if before_1_3: all_pages = wikiutil.getPageList (config.text_dir)
 366         else: all_pages = request.rootpage.getPageList()
 367         last_request_h = request_h
 368         last_pages_list = all_pages
 369     return last_pages_list
 370 
 371 
 372 def _subtext_get (body, pos, nbsubs, indent, moresubs):
 373     # dirty hack to remove empty lines
 374     text = body [pos:]
 375     l = 0
 376     while l != len(text):
 377         l = len (text)
 378         text = text.replace(' \n', '\n')
 379     l = 0
 380     while l != len(text):
 381         l = len (text)
 382         text = text.replace('\n\n', '\n')
 383     text = body [:pos] + text
 384 
 385     subpos = pos+1
 386     end = len (text)
 387     lead = ' '*indent
 388     while True:
 389         if subpos>=end: break
 390         ind = _indent_of (text, subpos)
 391         if ind <= indent: break
 392         p = text.find ("\n", subpos)
 393         if p == -1: break
 394         else: subpos = p + 1
 395     subs = text [pos:subpos].strip ('\n').split ('\n')
 396     ls = len (subs)
 397     if (nbsubs=='all'): pass
 398     elif nbsubs>0 and ls>nbsubs:
 399         subs = subs [0:nbsubs]
 400         subs.append (lead + moresubs)
 401     elif nbsubs<0 and ls>-nbsubs:
 402         subs = subs [nbsubs:]
 403         subs.insert (0, lead + moresubs)
 404         lead + '\n'.join (subs)
 405     return lead + '\n'.join (subs)
 406 
 407 
 408 # The "raison d'etre" of this module
 409 def _execute (macro, text):
 410 
 411     result = ""
 412 
 413     # new args syntax
 414     try:
 415         params = eval ("(lambda **opts: opts)(%s)" % text,
 416                        {'__builtins__': []}, {})
 417     except Exception, msg:
 418         raise _Error ("""<pre>malformed arguments list:
 419         %s<br>cause:
 420         %s
 421         </pre>
 422         <br> usage:
 423         <pre>%s</pre>
 424         """ % (text, msg, _usage () ) )
 425 
 426     arg_text             = _param_get (params, 'SearchText',   None)
 427     arg_pages            = _param_get (params, 'Pages',        '')
 428     arg_excl_pages       = _param_get (params, 'ExcludePages', '')
 429     arg_key              = _param_get (params, 'SortKey',      None)
 430 
 431     opt_heading          = _param_get (params, 'Heading',      None)
 432     opt_unassigned_text  = _param_get (params, 'UnassignedText',
 433                                        "[unassigned]")
 434     opt_reverse          = _param_get (params, 'Reverse',      False)
 435     opt_rawtext          = _param_get (params, 'RawText',      False)
 436 
 437     opt_format           = _param_get (params, 'Format',       '')
 438     opt_headerformat     = _param_get (params, 'HeaderFormat', '')
 439     opt_formatsort       = _param_get (params, 'FormatSort',   0)
 440     opt_unique           = _param_get (params, 'Unique',       0)
 441     
 442     opt_autoheading      = _param_get (params, 'AutoHeading',  '')
 443     opt_autoheadingformat= _param_get (params, 'AutoHeadingFormat',  '')
 444 
 445     def_nolinks          = (1,0) [len (arg_pages)>0]
 446     opt_nolinks          = _param_get (params, 'NoLinks',      def_nolinks)
 447     opt_noheader         = _param_get (params, 'NoHeader',     False)
 448     opt_notext           = _param_get (params, 'NoText',       False)
 449     opt_nopage           = _param_get (params, 'NoPageText',   None)
 450     opt_help             = _param_get (params, 'Help',         0)
 451 
 452     opt_nbsubs           = _param_get (params, 'NbSubs',       0)
 453     def_moresubs         = ('...', None) [opt_nbsubs=='all']
 454     opt_moresubs         = _param_get (params, 'MoreSubsText', def_moresubs)
 455 
 456     # help ?
 457     if opt_help:
 458         return """
 459         <p>
 460         Macro SearchInPagesAndSort usage:
 461         <pre>%s</pre></p>
 462         """ % _usage (opt_help==2)
 463 
 464     # check the args a little bit
 465     if len (params):
 466         raise _Error ("""unknown argument(s): %s
 467         <br> usage:
 468         <pre>%s</pre>
 469         """ % (`params.keys ()`, _usage () ) )
 470 
 471     if arg_text is None:
 472         raise _Error ("missing 'searchtext' argument")
 473 
 474     # empty page means this page; subpage are also handled
 475     if len (arg_pages) == 0 or arg_pages.startswith ('/'):
 476         arg_pages = macro.formatter.page.page_name + arg_pages
 477 
 478     # get a list of pages matching the PageRegex
 479     pages_re = _re_compile (arg_pages, 'Pages')
 480     all_pages = _get_all_pages (macro.request)
 481     hits = filter (pages_re.search, all_pages)
 482     if arg_excl_pages:
 483         excl_pages_re = _re_compile (arg_excl_pages, 'ExcludePages')
 484         hits = filter (lambda hit: not excl_pages_re.search (hit), hits)
 485 
 486     if before_1_3:
 487         # check ACL now (since we may end up with no pages)
 488         if config.acl_enabled:
 489             me = macro.request.user.name
 490             def _check_page (page_name):
 491                 page = Page (page_name) # too bad we must instanciate...
 492                 return page.getACL ().may (macro.request, me, "read")
 493             hits = filter (_check_page, hits)
 494 
 495     # sort pages, check if we have pages
 496     if len (hits) == 0:
 497         if opt_nopage: return "%s" % opt_nopage
 498         else:
 499             raise _Error ("no page matching '%s'!" % arg_pages)
 500     else: hits.sort ()
 501 
 502     # compile all regex
 503     text_re = _re_compile (arg_text, 'SearchText')
 504 
 505     if arg_key is not None:
 506         key_re = _re_compile (arg_key, 'SortKey')
 507 
 508     if opt_heading is not None:
 509         heading_re = _re_compile (opt_heading, 'Heading')
 510 
 511     # we will collect matching lines in each matching page
 512     all_matches = []
 513 
 514     # treat each found page
 515     for page_name in hits:
 516         if before_1_3: body = Page (page_name).get_raw_body ()
 517         else: body = Page (macro.request, page_name).get_raw_body () 
 518         pos = 0
 519         last_start = -1
 520         last_end = -1
 521         heading_text = ""
 522         while 1:
 523             keep_line = 1
 524 
 525             # search text
 526             match = text_re.search (body, pos)
 527             if not match: break
 528 
 529             # text is found; now search for heading
 530             if opt_heading is not None:
 531                 heading_pos = pos
 532                 heading_match = True
 533                 # keep the nearest heading to the found text
 534                 while heading_match:
 535                     heading_match = heading_re.search (body, heading_pos)
 536                     if heading_match and \
 537                            heading_match.start () < match.start ():
 538                         heading_text = heading_match.group (0)
 539                         heading_pos = heading_match.end ()
 540                     else: heading_match = False
 541 
 542             # point to found text
 543             pos = match.end ()+1
 544 
 545             # cut before start of line
 546             start_pos = match.start ()
 547             rev = 0
 548             while body [start_pos] != '\n' and start_pos:
 549                 start_pos = start_pos - 1
 550                 rev = 1
 551             if rev:
 552                 start_pos = start_pos + 1
 553 
 554             # cut at end of line
 555             end_pos = body.find ("\n", match.end ())
 556 
 557             # extract line
 558             raw_line = body [start_pos:end_pos]
 559             indent = _indent_of (raw_line)
 560             line = raw_line.strip ()
 561 
 562             # store this record if it differs from previous one
 563             if start_pos == last_start or end_pos == last_end: keep_line = 0
 564 
 565             # store this record if it it is not a comment
 566             elif line.startswith ("##"): keep_line = 0
 567 
 568             # remove possible list item leaders
 569             if keep_line:
 570                 for heading in ["*", "1.", "a.", "A.", "i.", "I."]:
 571                     if line.startswith (heading):
 572                         line = line.replace (heading, "", 1)
 573                 line = line.strip ()
 574                 if len (line)==0: keep_line = 0
 575 
 576             # handle this record
 577             if keep_line:
 578 
 579                 # get sub sections
 580                 if opt_nbsubs:
 581                     subtext = '\n' + _subtext_get (body, end_pos, opt_nbsubs,
 582                                                    indent, opt_moresubs)
 583                 else: subtext = ''
 584 
 585                 # find the sort key
 586                 nbmatches = 0
 587                 keypos = 0
 588                 found = 0
 589                 while 1:
 590                     if arg_key is None:
 591                         keyval = ""
 592                     else:
 593                         keymatch = key_re.search (line, keypos)
 594                         if keymatch:
 595                             keyval = line [keymatch.start ():keymatch.end ()]
 596                             keypos = keymatch.end ()
 597                             nbmatches = nbmatches + 1
 598                             found = 1
 599                         else:
 600                             if nbmatches>0: break
 601                             keyval = opt_unassigned_text
 602 
 603                     # store info
 604                     item = []
 605                     def append (txt): item.append (txt.strip ())
 606                     def append_rstrip (txt): item.append (txt.rstrip ())
 607                     append (keyval)                          # key text
 608                     append (body [match.start ():match.end ()]) # srch txt
 609                     append (line)                            # line text
 610                     append (page_name)                       # page name
 611                     append (heading_text)                    # heading
 612                     append_rstrip (subtext)                  # subsections
 613                     all_matches.append (item)
 614                     if found == 0: break
 615 
 616                 last_start = start_pos
 617                 last_end = end_pos
 618 
 619                 # all occurences of sort key found
 620             # this line handled
 621         # all lines handled
 622     # all pages handled
 623 
 624     # prepare some formatting text
 625     bullet_list_open = macro.formatter.bullet_list (1)
 626     bullet_list_close = macro.formatter.bullet_list (0)
 627     listitem_open = macro.formatter.listitem (1)
 628     listitem_close = macro.formatter.listitem (0)
 629 
 630     # now sort and format records
 631     if not opt_notext and not opt_reverse: all_matches.sort ()
 632     if opt_reverse: all_matches.reverse ()
 633 
 634     # explicitely-formatted output
 635     if opt_format:
 636         block = ""
 637         last_keytext = None
 638         rx = re.compile (r'([^@]*?)(@[^@]*?@)')
 639         pairs = re.findall (rx, opt_format+"@-@")
 640         if opt_headerformat: hpairs = re.findall (rx, opt_headerformat+"@-@")
 641         else: hpairs = None
 642         rx2d = {}
 643         for item in all_matches:
 644             keytext, srchtext, text, pagename, heading_text, subtext = item
 645             subtext = subtext.replace ("\n", "[[BR]]")
 646             if keytext == last_keytext: plist = (pairs,)
 647             elif hpairs: plist = (hpairs, pairs)
 648             else: plist = (pairs,)
 649             last_keytext = keytext
 650             for p in plist:
 651                 for txt, token in p:
 652                     txt = txt.replace ("\\n", "\n")
 653                     if not token: continue
 654                     token = token.strip ("@")
 655                     block += txt
 656                     rx2 = None
 657                     if len (token)>2 and token [2]==":":
 658                         token, rx2 = token [:2], token [3:]
 659                         if not rx2d.has_key (rx2): rx2d [rx2] = \
 660                            re.compile (rx2)
 661                         rx2 = rx2d [rx2]
 662                     token = token.replace ("\\n", "\n")
 663                     d = { "KT": keytext,      "ST": srchtext,
 664                           "FT": text,         "PN": pagename,
 665                           "HT": heading_text, "SU": subtext,
 666                           "":   "@",
 667                           "-":  "",
 668                           }
 669                     if rx2:
 670                         tx = d.get (token, None)
 671                         if tx:
 672                             tx = map ("".join, re.findall (rx2, tx))
 673                             if tx: tx = tx [0]
 674                             else: tx = ""
 675                         else: tx = token
 676                         block += tx
 677                     else:
 678                         block += d.get (token, token)
 679 	# sort lines
 680         if opt_formatsort or opt_reverse or opt_unique:
 681             lines = block.split ("\n")
 682             if opt_formatsort: lines.sort ()
 683             if opt_reverse: lines.reverse ()
 684             if opt_unique:
 685                 newlines = []
 686                 last = None
 687                 for l in lines:
 688                     if l != last: newlines.append(l)
 689                     last = l
 690                 lines = newlines
 691             block = "\n".join (lines)
 692 
 693         # now generate auto-headers
 694 
 695 	# format lines
 696         result += "\n%s\n" % _format (block, macro.request, macro.formatter)
 697 
 698     # auto-formatted output treat records for output
 699     else:
 700         head_count = 0
 701         result = result+"\n" + bullet_list_open
 702         keyval = ""
 703         last_pagename = ""
 704         for item in all_matches:
 705             keytext, srchtext, text, pagename, heading_text, subtext = item
 706 
 707             if opt_notext:
 708                 text_fmtted = ""
 709                 if last_pagename == pagename: continue
 710                 else: last_pagename = pagename
 711             elif opt_rawtext:
 712                 text_fmtted = wikiutil.escape (text)
 713             else:
 714                 # parse the text  (in wiki source format) and make HTML,
 715                 # after diverting sys.stdout to a string
 716                 text_fmtted = _format (text, macro.request, macro.formatter)
 717                 text_fmtted = text_fmtted.strip (' ') # preserve newlines
 718 
 719                 # empty text => drop this item
 720                 if len (text_fmtted)==0: continue
 721 
 722             # insert heading  (only if not yet done)
 723             if not opt_noheader \
 724                and arg_key is not None \
 725                and keytext != keyval:
 726                 # this is a new heading
 727                 keyval = keytext
 728                 if head_count:
 729                     result = result+"\n    " + bullet_list_close
 730                     result = result+"\n  " + listitem_close
 731                 head_count = head_count +1
 732                 result = result+"\n  " + listitem_open
 733                 result = result+ _format (keyval,
 734                                           macro.request, macro.formatter)
 735                 result = result+"\n    " + bullet_list_open
 736 
 737             # correct the text format (berk)
 738             if text_fmtted.startswith ("\n<p>"):
 739                  text_fmtted = text_fmtted [4:]
 740             if text_fmtted.endswith ("</p>\n"):
 741                 text_fmtted = text_fmtted [:-5]
 742                 text_trailer = "\n</p>\n"
 743             else: text_trailer = ""
 744 
 745             # insert formatted text
 746             result = result+"\n      " + listitem_open
 747             result = result + text_fmtted
 748             if not opt_nolinks:
 749                 result = result + "&nbsp;&nbsp;&nbsp;<font size=-1>["
 750                 if arg_text:
 751                     if before_1_3:
 752                         pageurl = '%s?action=highlight&value=%s' % (
 753                             pagename,
 754                             urllib.quote_plus (re.escape (text)))
 755                     else:
 756                         pageurl = pagename
 757  # HACKX!                       
 758  #                       pageurl = '%s?highlight=%s' % (
 759  #                           pagename,
 760  #                           urllib.quote_plus (re.escape (text)))
 761 
 762                 else: pageurl = wikiutil.quoteWikiname (pagename)
 763                 link_text = wikiutil.link_tag (macro.request,
 764                                                pageurl, pagename)
 765 
 766                 result = result + link_text
 767                 result = result + "]</font>"
 768             if opt_heading is not None:
 769                 result = result + "&nbsp;&nbsp;&nbsp;<font size=-1>["
 770                 result = result + heading_text
 771                 result = result + "]</font>"
 772 
 773             if opt_nbsubs:
 774                 result = result + _format (subtext,
 775                                            macro.request, macro.formatter)
 776 
 777             result = result + text_trailer + "\n      " + listitem_close
 778 
 779     # all items done, close  (hopefully) gracefully
 780     if not opt_format:
 781         if head_count:
 782             result = result+"\n      " + listitem_close
 783             result = result+"\n    " + bullet_list_close
 784         if not opt_noheader and arg_key is not None:
 785             result = result+"\n  " + listitem_close
 786         result = result+"\n" + bullet_list_close
 787 
 788     # done
 789     return result
 790 
 791 def _format (src_text, request, formatter):
 792     # parse the text (in wiki source format) and make HTML,
 793     # after diverting sys.stdout to a string
 794     str_out = StringIO.StringIO ()      # create str to collect output
 795     request.redirect (str_out)          # divert output to that string
 796     # parse this line
 797     text_moin_wiki.Parser (src_text, request).format (formatter)
 798     request.redirect ()                 # restore output
 799     return str_out.getvalue ()          # return what was generated

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] (2008-07-18 08:06:03, 29.1 KB) [[attachment:SearchInPagesAndSort.py]]
  • [get | view] (2004-11-15 23:43:23, 12.8 KB) [[attachment:SearchInPagesAndSort.py--old-0.2.4]]
  • [get | view] (2005-01-31 19:52:30, 21.2 KB) [[attachment:SearchInPagesAndSort.py--old-0.3.1--dont-use]]
  • [get | view] (2005-03-15 20:10:14, 21.9 KB) [[attachment:SearchInPagesAndSort.py--old-0.3.3]]
  • [get | view] (2005-12-13 20:53:17, 27.6 KB) [[attachment:SearchInPagesAndSort.py--old-0.3.4]]
  • [get | view] (2005-12-13 20:54:16, 28.9 KB) [[attachment:SearchInPagesAndSort.py--old-0.3.5]]
 All files | Selected Files: delete move to page copy to page

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