Back to index

moin  1.9.0~rc2
LikePages.py
Go to the documentation of this file.
00001 # -*- coding: iso-8859-1 -*-
00002 """
00003     MoinMoin - LikePages action
00004 
00005     This action generates a list of pages that either start or end
00006     with the same word as the current pagename. If only one matching
00007     page is found, that page is displayed directly.
00008 
00009     @copyright: 2001 Richard Jones <richard@bizarsoftware.com.au>,
00010                 2001 Juergen Hermann <jh@web.de>
00011     @license: GNU GPL, see COPYING for details.
00012 """
00013 
00014 import re
00015 
00016 from MoinMoin import config, wikiutil
00017 from MoinMoin.support import difflib
00018 from MoinMoin.Page import Page
00019 
00020 
00021 def execute(pagename, request):
00022     _ = request.getText
00023     start, end, matches = findMatches(pagename, request)
00024 
00025     # Error?
00026     if isinstance(matches, (str, unicode)):
00027         request.theme.add_msg(matches, "info")
00028         Page(request, pagename).send_page()
00029         return
00030 
00031     # No matches
00032     if not matches:
00033         request.theme.add_msg(_('No pages like "%s"!') % (pagename, ), "error")
00034         Page(request, pagename).send_page()
00035         return
00036 
00037     # One match - display it
00038     if len(matches) == 1:
00039         request.theme.add_msg(_('Exactly one page like "%s" found, redirecting to page.') % (pagename, ), "info")
00040         Page(request, matches.keys()[0]).send_page()
00041         return
00042 
00043     # more than one match, list 'em
00044     # This action generate data using the user language
00045     request.setContentLanguage(request.lang)
00046 
00047     request.theme.send_title(_('Pages like "%s"') % (pagename), pagename=pagename)
00048 
00049     # Start content - IMPORTANT - without content div, there is no
00050     # direction support!
00051     request.write(request.formatter.startContent("content"))
00052 
00053     showMatches(pagename, request, start, end, matches)
00054 
00055     # End content and send footer
00056     request.write(request.formatter.endContent())
00057     request.theme.send_footer(pagename)
00058     request.theme.send_closing_html()
00059 
00060 def findMatches(pagename, request, s_re=None, e_re=None):
00061     """ Find like pages
00062 
00063     @param pagename: name to match
00064     @param request: current reqeust
00065     @param s_re: start re for wiki matching
00066     @param e_re: end re for wiki matching
00067     @rtype: tuple
00068     @return: start word, end word, matches dict
00069     """
00070     # Get full list of pages, with no filtering - very fast. We will
00071     # first search for like pages, then filter the results.
00072     pages = request.rootpage.getPageList(user='', exists='')
00073 
00074     # Remove current page
00075     try:
00076         pages.remove(pagename)
00077     except ValueError:
00078         pass
00079 
00080     # Get matches using wiki way, start and end of word
00081     start, end, matches = wikiMatches(pagename, pages, start_re=s_re,
00082                                       end_re=e_re)
00083 
00084     # Get the best 10 close matches
00085     close_matches = {}
00086     found = 0
00087     for name in closeMatches(pagename, pages):
00088         # Skip names already in matches
00089         if name in matches:
00090             continue
00091 
00092         # Filter deleted pages or pages the user can't read
00093         page = Page(request, name)
00094         if page.exists() and request.user.may.read(name):
00095             close_matches[name] = 8
00096             found += 1
00097             # Stop after 10 matches
00098             if found == 10:
00099                 break
00100 
00101     # Filter deleted pages or pages the user can't read from
00102     # matches. Order is important!
00103     for name in matches.keys(): # we need .keys() because we modify the dict
00104         page = Page(request, name)
00105         if not (page.exists() and request.user.may.read(name)):
00106             del matches[name]
00107 
00108     # Finally, merge both dicts
00109     matches.update(close_matches)
00110 
00111     return start, end, matches
00112 
00113 
00114 def wikiMatches(pagename, pages, start_re=None, end_re=None):
00115     """
00116     Get pages that starts or ends with same word as this page
00117 
00118     Matches are ranked like this:
00119         4 - page is subpage of pagename
00120         3 - match both start and end
00121         2 - match end
00122         1 - match start
00123 
00124     @param pagename: page name to match
00125     @param pages: list of page names
00126     @param start_re: start word re (compile regex)
00127     @param end_re: end word re (compile regex)
00128     @rtype: tuple
00129     @return: start, end, matches dict
00130     """
00131     if start_re is None:
00132         start_re = re.compile('([%s][%s]+)' % (config.chars_upper,
00133                                                config.chars_lower))
00134     if end_re is None:
00135         end_re = re.compile('([%s][%s]+)$' % (config.chars_upper,
00136                                               config.chars_lower))
00137 
00138     # If we don't get results with wiki words matching, fall back to
00139     # simple first word and last word, using spaces.
00140     words = pagename.split()
00141     match = start_re.match(pagename)
00142     if match:
00143         start = match.group(1)
00144     else:
00145         start = words[0]
00146 
00147     match = end_re.search(pagename)
00148     if match:
00149         end = match.group(1)
00150     else:
00151         end = words[-1]
00152 
00153     matches = {}
00154     subpage = pagename + '/'
00155 
00156     # Find any matching pages and rank by type of match
00157     for name in pages:
00158         if name.startswith(subpage):
00159             matches[name] = 4
00160         else:
00161             if name.startswith(start):
00162                 matches[name] = 1
00163             if name.endswith(end):
00164                 matches[name] = matches.get(name, 0) + 2
00165 
00166     return start, end, matches
00167 
00168 
00169 def closeMatches(pagename, pages):
00170     """ Get close matches.
00171 
00172     Return all matching pages with rank above cutoff value.
00173 
00174     @param pagename: page name to match
00175     @param pages: list of page names
00176     @rtype: list
00177     @return: list of matching pages, sorted by rank
00178     """
00179     # Match using case insensitive matching
00180     # Make mapping from lowerpages to pages - pages might have same name
00181     # with different case (although its stupid).
00182     lower = {}
00183     for name in pages:
00184         key = name.lower()
00185         if key in lower:
00186             lower[key].append(name)
00187         else:
00188             lower[key] = [name]
00189 
00190     # Get all close matches
00191     all_matches = difflib.get_close_matches(pagename.lower(), lower.keys(),
00192                                             len(lower), cutoff=0.6)
00193 
00194     # Replace lower names with original names
00195     matches = []
00196     for name in all_matches:
00197         matches.extend(lower[name])
00198 
00199     return matches
00200 
00201 
00202 def showMatches(pagename, request, start, end, matches, show_count=True):
00203     keys = matches.keys()
00204     keys.sort()
00205     _showMatchGroup(request, matches, keys, 8, pagename, show_count)
00206     _showMatchGroup(request, matches, keys, 4, "%s/..." % pagename, show_count)
00207     _showMatchGroup(request, matches, keys, 3, "%s...%s" % (start, end), show_count)
00208     _showMatchGroup(request, matches, keys, 1, "%s..." % (start, ), show_count)
00209     _showMatchGroup(request, matches, keys, 2, "...%s" % (end, ), show_count)
00210 
00211 
00212 def _showMatchGroup(request, matches, keys, match, title, show_count=True):
00213     _ = request.getText
00214     matchcount = matches.values().count(match)
00215 
00216     if matchcount:
00217         if show_count:
00218             # Render title line
00219             request.write(request.formatter.paragraph(1))
00220             request.write(request.formatter.strong(1))
00221             request.write(request.formatter.text(
00222                 _('%(matchcount)d %(matches)s for "%(title)s"') % {
00223                     'matchcount': matchcount,
00224                     'matches': ' ' + (_('match'), _('matches'))[matchcount != 1],
00225                     'title': title}))
00226             request.write(request.formatter.strong(0))
00227             request.write(request.formatter.paragraph(0))
00228 
00229         # Render links
00230         request.write(request.formatter.bullet_list(1))
00231         for key in keys:
00232             if matches[key] == match:
00233                 request.write(request.formatter.listitem(1))
00234                 request.write(request.formatter.pagelink(1, key, generated=True))
00235                 request.write(request.formatter.text(key))
00236                 request.write(request.formatter.pagelink(0, key, generated=True))
00237                 request.write(request.formatter.listitem(0))
00238         request.write(request.formatter.bullet_list(0))
00239 
00240