00001
00002 """
00003 MoinMoin - FullSearch Macro
00004
00005 <<FullSearch>>
00006 displays a search dialog, as it always did.
00007
00008 <<FullSearch()>>
00009 does the same as clicking on the page title, only that
00010 the result is embedded into the page. note the '()' after
00011 the macro name, which is an empty argument list.
00012
00013 <<FullSearch(Help)>>
00014 embeds a search result into a page, as if you entered
00015 'Help' into the search box.
00016
00017 The macro creates a page list without context or match info, just
00018 like PageList macro. It does not make sense to have context in non
00019 interactive search, and this kind of search is used usually for
00020 Category pages, where we don't care about the context.
00021
00022 TODO: If we need to have context for some cases, either we add a context argument,
00023 or make another macro that uses context, which may be easier to use.
00024
00025 @copyright: 2000-2004 Juergen Hermann <jh@web.de>,
00026 2006 MoinMoin:FranzPletz
00027 @license: GNU GPL, see COPYING for details.
00028 """
00029
00030 import operator
00031 import re
00032 from MoinMoin import wikiutil, search
00033 from StackNaviPackageNames import macro_StackNaviPackageNames
00034 from StackNavi import macro_StackNavi
00035
00036 from MoinMoin.parser import text_moin_wiki as wiki
00037 import string, StringIO
00038
00039 Dependencies = ["pages"]
00040
00041
00042 def search_box(type, macro):
00043 """ Make a search box
00044
00045 Make both Title Search and Full Search boxes, according to type.
00046
00047 @param type: search box type: 'titlesearch' or 'fullsearch'
00048 @rtype: unicode
00049 @return: search box html fragment
00050 """
00051 _ = macro._
00052 if 'value' in macro.form:
00053 default = wikiutil.escape(macro.form["value"][0], quote=1)
00054 else:
00055 default = ''
00056
00057
00058 boxes = ''
00059 button = _("Search Titles")
00060
00061
00062 if type == "fullsearch":
00063 boxes = [
00064 u'<br>',
00065 u'<input type="checkbox" name="context" value="160" checked="checked">',
00066 _('Display context of search results'),
00067 u'<br>',
00068 u'<input type="checkbox" name="case" value="1">',
00069 _('Case-sensitive searching'),
00070 ]
00071 boxes = u'\n'.join(boxes)
00072 button = _("Search Text")
00073
00074
00075 type = (type == "titlesearch")
00076 html = [
00077 u'<form method="get" action="%s/%s">' % (macro.request.getScriptname(), wikiutil.quoteWikinameURL(macro.request.formatter.page.page_name)),
00078 u'<div>',
00079 u'<input type="hidden" name="action" value="fullsearch">',
00080 u'<input type="hidden" name="titlesearch" value="%i">' % type,
00081 u'<input type="text" name="value" size="30" value="%s">' % default,
00082 u'<input type="submit" value="%s">' % button,
00083 boxes,
00084 u'</div>',
00085 u'</form>',
00086 ]
00087 html = u'\n'.join(html)
00088 return macro.formatter.rawHTML(html)
00089
00090
00091
00092 class StackInfo:
00093 def __init__(self, stack_name, packages, description):
00094 self.stack_name = stack_name
00095 self.description = description
00096 self.stack_tutorials = []
00097 self.package_dict = dict()
00098 for package in packages:
00099 self.package_dict[package] = []
00100
00101
00102 stack_list = []
00103
00104 def getStackInfo(self, macro, request, formatter, info=1, context=180,
00105 maxlines=1, paging=True, hitsFrom=0, hitsInfo=0):
00106 displayHits = self.hits
00107
00108
00109 for page in displayHits:
00110 stack_information = macro_StackNaviPackageNames(macro, page.page_name)
00111 if len(stack_information) == 3:
00112 stack_list.append(StackInfo(*stack_information))
00113
00114
00115 def pageListWithContext(self, macro, request, formatter, info=1, context=180,
00116 maxlines=1, paging=True, hitsFrom=0, hitsInfo=0):
00117 """ Format a list of found pages with context
00118
00119 The default parameter values will create Google-like search
00120 results, as this is the most known search interface. Good
00121 interface is familiar interface, so unless we have much better
00122 solution (we don't), being like Google is the way.
00123
00124 @param request: current request
00125 @param formatter: formatter to use
00126 @param info: show match info near the page link
00127 @param context: how many characters to show around each match.
00128 @param maxlines: how many contexts lines to show.
00129 @param paging: toggle paging
00130 @param hitsFrom: current position in the hits
00131 @param hitsInfo: toggle hits info line
00132 @rtype: unicode
00133 @return formatted page list with context
00134 """
00135 self._reset(request, formatter)
00136 f = formatter
00137 write = self.buffer.write
00138 _ = request.getText
00139
00140 if paging and len(self.hits) <= request.cfg.search_results_per_page:
00141 paging = False
00142
00143 if len(self.hits) == 0:
00144 write(f.definition_list(1) + f.definition_term(1) + "No results found." + f.definition_term(0) + f.definition_list(0))
00145
00146 else:
00147 write(f.number_list(1))
00148
00149 if paging:
00150 hitsTo = hitsFrom + request.cfg.search_results_per_page
00151 displayHits = self.hits[hitsFrom:hitsTo]
00152 else:
00153 displayHits = self.hits
00154
00155 display_results = []
00156 no_stack_tutorials = []
00157
00158
00159 for page in displayHits:
00160
00161 matchInfo = ''
00162 next_page = None
00163 if info:
00164 matchInfo = self.formatInfo(f, page)
00165 if page.attachment:
00166 fmt_context = ""
00167 querydict = {
00168 'action': 'AttachFile',
00169 'do': 'view',
00170 'target': page.attachment,
00171 }
00172 elif page.page_name.startswith('FS/'):
00173 fmt_context = ""
00174 querydict = None
00175 else:
00176 title, fmt_context, next_pages = formatContext(self, macro, page, context, maxlines)
00177 if page.rev and page.rev != page.page.getRevList()[0]:
00178 querydict = {
00179 'rev': page.rev,
00180 }
00181 else:
00182 querydict = None
00183 querystr = self.querystring(querydict)
00184 item = [
00185 f.listitem(1),
00186
00187 f.pagelink(1, page.page_name),
00188 title,
00189 f.pagelink(0),
00190 "<p>",
00191 fmt_context,
00192 "</p>",
00193 f.listitem(0),
00194 ]
00195 urllist = item[1].split('/')
00196 package_name = urllist[2]
00197 display_results.append((page.page_name, next_pages, ''.join(item), package_name))
00198
00199 sorted_display_results = noSortResults(display_results)
00200
00201
00202 def check_dup(tutoriallist, newtutorial):
00203 for tutorial in tutoriallist:
00204 if newtutorial.pagename == tutorial.pagename:
00205 return 1
00206 return 0
00207
00208
00209 p = macro.formatter.paragraph
00210 for tutorial_node in sorted_display_results:
00211 packagefound = 0
00212 for stack in stack_list:
00213 if tutorial_node.packagename == stack.stack_name:
00214 if not check_dup(stack.stack_tutorials, tutorial_node):
00215 stack.stack_tutorials.append(tutorial_node)
00216 packagefound = 1
00217 break
00218 for (package, tutoriallist) in stack.package_dict.iteritems():
00219 if tutorial_node.packagename == package:
00220 if not check_dup(tutoriallist, tutorial_node):
00221 tutoriallist.append(tutorial_node)
00222 packagefound = 1
00223 break
00224 if packagefound:
00225 break
00226 else:
00227 if not check_dup(no_stack_tutorials, tutorial_node):
00228 no_stack_tutorials.append(tutorial_node)
00229
00230
00231 for stack in stack_list:
00232 tutorialspresent = 0
00233 tutorialsstr = '<p><ol>'
00234 if len(stack.stack_tutorials) != 0:
00235 tutorialspresent = 1
00236 tutorialtextlist = [tutorial.body for tutorial in stack.stack_tutorials]
00237 tutorialsstr += '\n'.join(tutorialtextlist)+'\n'
00238 for (package, tutoriallist) in stack.package_dict.iteritems():
00239 if len(tutoriallist) != 0:
00240 tutorialspresent = 1
00241 tutorialsstr += '\n'.join([tutorial.body for tutorial in tutoriallist])
00242 if tutorialspresent:
00243 write(stack.description)
00244 tutorialsstr += '</ol></p>'
00245 write(tutorialsstr)
00246
00247
00248
00249
00250 desc = "<h3 id=\"no-stack\">Tutorials with no stack</h3><p><ol>"
00251 write(desc)
00252 write('\n'.join([tutorial.body for tutorial in no_stack_tutorials])+'</p></ol>')
00253
00254
00255
00256
00257 write(f.number_list(0))
00258 if paging:
00259 write(self.formatPageLinks(hitsFrom=hitsFrom,
00260 hitsPerPage=request.cfg.search_results_per_page,
00261 hitsNum=len(self.hits)))
00262
00263 return self.getvalue()
00264
00265
00266
00267 class Node:
00268 def __init__(self, pagename, body, dependencies, packagename):
00269 self.pagename = pagename
00270 self.body = body
00271 self.dependencies = dependencies
00272 self.packagename = packagename
00273
00274 def __repr__(self):
00275 return "<Node %s %s>" % (self.pagename, self.dependencies)
00276
00277 def topoSort(dependencies):
00278 dead = {}
00279 list = []
00280
00281 for node in dependencies.values(): dead[node] = False
00282
00283 nonterminals = []
00284 terminals = []
00285 for node in dependencies.values():
00286 if node.dependencies:
00287 nonterminals.append(node)
00288 else:
00289 terminals.append(node)
00290
00291 for node in nonterminals:
00292 visit(dependencies, terminals, node, list, dead);
00293
00294 list.reverse()
00295
00296 list = list + terminals
00297 list.sort(key=operator.attrgetter('packagename'))
00298
00299 return list
00300
00301 def visit(dependencies, terminals, dependency, list, dead):
00302 if dependency is None: return
00303 if dead.get(dependency, False): return
00304
00305 dead[dependency] = True
00306
00307 if dependency.dependencies:
00308 for node in dependency.dependencies:
00309 visit(dependencies, terminals, dependencies.get(node, None), list, dead)
00310 try:
00311 terminals.remove(dependency)
00312 except ValueError: pass
00313
00314 list.append(dependency)
00315
00316
00317 def noSortResults(display_results):
00318 node_list = []
00319 for pagename, nextpages, body, packagename in display_results:
00320 node = Node(pagename, body, nextpages, packagename)
00321 node_list.append(node)
00322 return node_list
00323
00324 def sortResults(display_results):
00325 dependencies = {}
00326
00327 for pagename, nextpages, body, packagename in display_results:
00328 node = Node(pagename, body, nextpages, packagename)
00329 dependencies[pagename] = node
00330
00331 results = topoSort(dependencies)
00332
00333 return results
00334
00335
00336
00337 def formatContext(self, macro, page, context, maxlines):
00338 """ Format search context for each matched page
00339
00340 Try to show first maxlines interesting matches context.
00341 """
00342 f = self.formatter
00343 if not page.page:
00344 page.page = Page(self.request, page.page_name)
00345 body = page.page.get_raw_body()
00346 last = len(body) - 1
00347 lineCount = 0
00348 output = ""
00349 next_page = None
00350
00351 pagedict = {}
00352 for line in body.split("\n"):
00353 if line.startswith("##"):
00354 line = line[2:].strip()
00355 parts = line.split("=", 1)
00356 if len(parts) == 2:
00357 pagedict[parts[0].strip()] = parts[1].strip()
00358
00359 title = pagedict.get("title", "No Title")
00360 description = pagedict.get("description", "No Description")
00361
00362 next_pages = []
00363 linkpat = re.compile("\[\[([^|]*)(\|([^]]*))?\]\]")
00364 for key,val in pagedict.items():
00365 if key.startswith("next.") and key.find(".link") != -1:
00366 m = linkpat.search(val)
00367 if m:
00368 next_pages.append(m.group(1))
00369
00370 if description:
00371 out=StringIO.StringIO()
00372 macro.request.redirect(out)
00373 wikiizer = wiki.Parser(description, macro.request)
00374 wikiizer.format(macro.formatter)
00375 description=out.getvalue()
00376 macro.request.redirect()
00377 del out
00378
00379 return title, description, next_pages
00380
00381 def macro_AllTutorialsSorted(macro, needle):
00382 request = macro.request
00383 _ = request.getText
00384
00385
00386 if needle is None:
00387 return search_box("fullsearch", macro)
00388
00389
00390 elif needle == '':
00391 needle = '"%s"' % macro.formatter.page.page_name
00392
00393
00394
00395 elif needle.isspace():
00396 err = _('Please use a more selective search term instead of '
00397 '{{{"%s"}}}', wiki=True) % needle
00398 return '<span class="error">%s</span>' % err
00399
00400 needle = needle.strip()
00401
00402
00403 results = search.searchPages(request, 'CategoryStack -StackList -StackTemplate', sort='page_name')
00404 getStackInfo(results, macro, request, macro.formatter, paging=False)
00405
00406
00407 results = search.searchPages(request, needle, sort='page_name')
00408 return pageListWithContext(results, macro, request, macro.formatter, paging=False)
00409
00410
00411 ret = []
00412 for result in results:
00413 pass
00414
00415 return string.join(ret)
00416
00417