search.shtml revision 3171
742N/A## -*- coding: utf-8 -*-
742N/A##
742N/A## CDDL HEADER START
742N/A##
742N/A## The contents of this file are subject to the terms of the
742N/A## Common Development and Distribution License (the "License").
742N/A## You may not use this file except in compliance with the License.
742N/A##
742N/A## You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
742N/A## or http://www.opensolaris.org/os/licensing.
742N/A## See the License for the specific language governing permissions
742N/A## and limitations under the License.
742N/A##
742N/A## When distributing Covered Code, include this CDDL HEADER in each
742N/A## file and include the License file at usr/src/OPENSOLARIS.LICENSE.
742N/A## If applicable, add the following below this CDDL HEADER, with the
742N/A## fields enclosed by brackets "[]" replaced with your own identifying
742N/A## information: Portions Copyright [yyyy] [name of copyright owner]
742N/A##
742N/A## CDDL HEADER END
742N/A##
3158N/A## Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
742N/A##
742N/A<%!
2946N/A import cgi
1116N/A import itertools
1116N/A import pkg.actions as actions
1116N/A import pkg.query_parser as qp
1116N/A import pkg.server.api_errors as api_errors
1116N/A import pkg.version as version
2662N/A import re
742N/A import urllib
1116N/A import urlparse
742N/A%>\
742N/A<%inherit file="layout.shtml"/>\
742N/A<%page args="g_vars"/>\
742N/A<%
742N/A catalog = g_vars["catalog"]
742N/A request = g_vars["request"]
2852N/A http_depot = g_vars["http_depot"]
742N/A%>\
742N/A<%def name="page_title(g_vars)"><%
742N/A return "Package Search"
742N/A%></%def>\
1116N/A<%def name="get_search_criteria(request)"><%
1116N/A # Based on the request parameters, return a dict representing the
1116N/A # search criteria.
1116N/A criteria = {
1116N/A "token": request.params.get("token", ""),
1116N/A "query_error": request.params.get("qe", ""),
1116N/A }
1116N/A
1116N/A criteria["searched"] = len(criteria["token"])
1116N/A
1116N/A show = request.params.get("show", "p")
1116N/A if show == "p" or show not in ("a", "p"):
1116N/A criteria["return_type"] = qp.Query.RETURN_PACKAGES
1116N/A elif show == "a":
1116N/A criteria["return_type"] = qp.Query.RETURN_ACTIONS
1116N/A
1116N/A for name, default in (("rpp", 50), ("start", 0), ("failed", 0),
1116N/A ("sav", 0), ("cs", 0)):
1116N/A val = request.params.get(name, default)
1116N/A try:
1116N/A val = int(val)
1116N/A except ValueError:
1116N/A val = default
1116N/A
1431N/A # Force boolean type for these parameters.
1431N/A if name in ("cs", "sav"):
1431N/A if val:
1431N/A val = True
1431N/A else:
1431N/A val = False
1431N/A
1116N/A criteria[name] = val
1116N/A
1116N/A return criteria
1116N/A%></%def>\
1116N/A<%def name="search(catalog, request, criteria)"><%
1116N/A # Gets the search results for the specified catalog based on the
1116N/A # provided search criteria.
1116N/A query_error = None
1116N/A token = criteria["token"]
1116N/A cs = criteria["cs"]
1116N/A rpp = criteria["rpp"]
1116N/A return_type = criteria["return_type"]
1116N/A start_val = criteria["start"]
1116N/A
1116N/A # This criteria is optional, so use get to retrieve it.
1116N/A mver = criteria.get("selected_ver", None)
1116N/A
1116N/A if mver:
1116N/A # Replace leading version components with wildcard so that
1116N/A # matching is only performed using build_release and branch.
3158N/A mver = "*,{0}-{1}".format(mver.build_release, mver.branch)
1116N/A
1116N/A # Determines whether all versions or only the latest vresion of a
1116N/A # package is shown. This is ignored when the return_type is not
1116N/A # qp.Query.RETURN_PACKAGES.
1116N/A sav = criteria["sav"]
1116N/A
1116N/A try:
1116N/A # Search results are limited to just one more than the
1116N/A # results per page so that a query that exceeds it can be
1116N/A # detected.
1116N/A results = catalog.search(token,
1116N/A case_sensitive=cs,
1116N/A return_type=return_type,
1116N/A start_point=start_val,
1116N/A num_to_return=(rpp + 1),
1116N/A matching_version=mver,
1116N/A return_latest=not sav)
3171N/A except qp.QueryLengthExceeded as e:
2961N/A results = None
2961N/A query_error = str(e)
3171N/A except qp.QueryException as e:
1116N/A results = None
2946N/A query_error = str(e)
3171N/A except Exception as e:
2028N/A results = None
2028N/A query_error = urllib.quote(str(e))
1116N/A
1116N/A # Before showing the results, the type of results being shown has to be
2028N/A # determined since the user might have overridden the return_type
1116N/A # selection above using query syntax. To do that, the first result will
1116N/A # have to be checked for the real return type.
1116N/A if results:
1116N/A try:
1116N/A result = results.next()
1116N/A except StopIteration:
1116N/A result = None
1116N/A results = None
1116N/A pass
1116N/A
1116N/A if result and result[1] == qp.Query.RETURN_PACKAGES:
1116N/A return_type = result[1]
1116N/A results = itertools.chain([result], results)
1116N/A elif result and result[1] == qp.Query.RETURN_ACTIONS:
1116N/A return_type = result[1]
1116N/A results = itertools.chain([result], results)
1116N/A elif result:
1116N/A return_type = qp.Query.RETURN_PACKAGES
1116N/A query_error = "Only the display of packages or " \
1116N/A "actions for search results is supported."
1116N/A
3158N/A request.log("Unsupported return_type '{0}' "
3158N/A "requested for search query: '{1}'.".format(
3158N/A return_type, token))
1116N/A
1116N/A return return_type, results, query_error
1116N/A%></%def>\
1116N/A<%def name="display_search_form(criteria, request)"><%
1116N/A # Returns an HTML form with all of the elements needed to perform a
1116N/A # search using the specified search criteria and request.
1116N/A token = criteria["token"]
1116N/A
1116N/A search_uri = "advanced_search.shtml"
1116N/A if criteria["searched"]:
1116N/A search_uri = request.url(qs=request.query_string, relative=True)
1116N/A search_uri = search_uri.replace("search.shtml",
1116N/A "advanced_search.shtml")
1116N/A%>\
1116N/A<form class="search" action="search.shtml">
1116N/A <p>
1116N/A <input id="search-field" type="text" size="40"
1116N/A maxlength="512" name="token"
2603N/A value="${token | h}" title="search field"/>
1116N/A <input id="submit-search" type="submit"
1116N/A name="action" value="Search"/>
1116N/A <a href="${search_uri | h}">Advanced Search</a>
1116N/A </p>
1116N/A</form>
1116N/A</%def>\
1116N/A<%def name="get_prev_page_uri(criteria, request)"><%
1116N/A # Returns a URL relative to the current request path with the
1116N/A # starting range of the previous page of search results set.
1116N/A
1116N/A uri = request.url(qs=request.query_string, relative=True)
1116N/A scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
1116N/A
1116N/A nparams = []
1116N/A for name, val in request.params.iteritems():
1116N/A if name == "start":
1116N/A continue
1116N/A nparams.append((name, val))
1116N/A
1116N/A start = criteria["start"]
1116N/A start = start - criteria["rpp"]
1116N/A if start < 0:
1116N/A start = 0
1116N/A nparams.append(("start", start))
1116N/A
1116N/A qs = urllib.urlencode(nparams)
1116N/A uri = urlparse.urlunparse((scheme, netloc, path, params, qs, fragment))
1116N/A
1116N/A return uri
1116N/A%></%def>\
1116N/A<%def name="get_next_page_uri(criteria, request, result_count)"><%
1116N/A # Returns a URL relative to the current request path with the
1116N/A # starting range of the next page of search results set.
1116N/A
1116N/A uri = request.url(qs=request.query_string, relative=True)
1116N/A scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
1116N/A
1116N/A nparams = []
1116N/A for name, val in request.params.iteritems():
1116N/A if name == "start":
1116N/A continue
1116N/A nparams.append((name, val))
1116N/A
1116N/A start = criteria["start"]
1116N/A nparams.append(("start", (start + result_count - 1)))
1116N/A
1116N/A qs = urllib.urlencode(nparams)
1116N/A uri = urlparse.urlunparse((scheme, netloc, path, params, qs, fragment))
1116N/A
1116N/A return uri
1116N/A%></%def>\
1116N/A<%def name="display_pagination(criteria, result_count, colspan=1)"><%
1116N/A # Returns a table row with the appropriate pagination controls
1116N/A # based on the provided search criteria.
1116N/A
1116N/A show_prev = criteria["start"] > 0
1116N/A show_next = result_count > criteria["rpp"]
1116N/A if not (show_prev or show_next):
1116N/A # Nothing to display.
1116N/A return ""
1116N/A%>\
1116N/A<tr class="last">
1116N/A% if show_prev:
1116N/A <td colspan="${colspan}">
1116N/A <a href="${self.get_prev_page_uri(criteria, request) | h}">
1116N/APrevious</a>
1116N/A </td>
1116N/A% else:
1116N/A <td colspan="${colspan}">&nbsp;</td>
1116N/A% endif
1116N/A% if show_next:
1116N/A <td class="last" colspan="${colspan}">
1116N/A <a href="${self.get_next_page_uri(criteria, request,
1116N/A result_count) | h}">
1116N/ANext</a>
1116N/A </td>
1116N/A% else:
1116N/A <td class="last" colspan="${colspan}">&nbsp;</td>
1116N/A% endif
1116N/A</tr>
1116N/A</%def>\
742N/A<div id="yui-main">
742N/A% if not catalog.search_available:
742N/A <div class="yui-b">
742N/A <p>Search functionality is not available at this time.</p>
742N/A </div>
742N/A% else:
742N/A<%
1116N/A results = None
1116N/A return_type = None
1116N/A
1116N/A criteria = self.get_search_criteria(request)
1116N/A searched = criteria["searched"]
1116N/A failed = criteria["failed"]
1116N/A query_error = criteria["query_error"]
1116N/A
2946N/A if query_error:
2946N/A # Sanitize query_error to prevent misuse;
2946N/A lines = cgi.escape(query_error, True).splitlines(True)
2946N/A n_qe = ""
2946N/A last_pre = False
2946N/A
2946N/A # Put all lines which start with a \t in <pre> tags since these
2946N/A # contain pre-formatted error descriptions.
2946N/A for l in lines:
2946N/A if l.startswith("\t"):
2946N/A if not last_pre:
2946N/A n_qe += "<pre>"
2946N/A n_qe += l
2946N/A last_pre = True
2946N/A else:
2946N/A if last_pre:
2946N/A n_qe += "</pre>"
2946N/A last_pre = False
2946N/A n_qe += l.replace("\n","<br/>")
2946N/A else:
2946N/A if last_pre:
2946N/A last_pre = False
2946N/A n_qe += "</pre>"
2946N/A
2946N/A query_error = n_qe
2662N/A
1116N/A if not failed and searched:
1116N/A return_type, results, query_error = self.search(
1116N/A catalog, request, criteria)
1116N/A
1116N/A if query_error or not results:
1116N/A # Reload the page with a few extra query parameters set
1116N/A # so that failed searches can be detected in the server
1116N/A # logs (including that of any proxies in front of the
1116N/A # depot server).
1116N/A uri = request.url(qs=request.query_string,
1116N/A relative=True)
2852N/A if http_depot:
2852N/A # if using the http-depot, we need to redirect
2852N/A # to the appropriate repository within the
2852N/A # webapp.
2852N/A lang = request.path_info.split("/")[1]
3158N/A uri = "/depot/{0}/{1}/{2}".format(http_depot,
3158N/A lang, uri)
1116N/A scheme, netloc, path, params, query, \
1116N/A fragment = urlparse.urlparse(uri)
1116N/A
1116N/A nparams = []
1116N/A for name, val in request.params.iteritems():
1116N/A if name in ("failed", "query_error"):
1116N/A continue
1116N/A nparams.append((name, val))
1116N/A
1116N/A nparams.append(("failed", 1))
1116N/A if query_error:
1116N/A nparams.append(("qe", query_error))
1116N/A
1116N/A qs = urllib.urlencode(nparams)
1116N/A uri = urlparse.urlunparse((scheme, netloc, path, params,
1116N/A qs, fragment))
1116N/A
1116N/A raise api_errors.RedirectException(uri)
1116N/A
1116N/A rpp = criteria["rpp"]
1116N/A result_count = 0
742N/A%>\
742N/A <div class="yui-b">
1116N/A ${self.display_search_form(criteria, request)}
742N/A% if not searched:
1116N/A <p>Search Tips:</p>
1116N/A <ul class="tips">
1116N/A <li>All searches are case-insensitive.</li>
1116N/A <li>To find packages that contain a specific
742N/Afile, start your search criteria with a '/':<br/>
742N/A<kbd>/usr/bin/vim</kbd></li>
1116N/A <li>To find packages based on a partial match,
742N/Ause the wildcard characters '*' or '?':<br/>
742N/A<kbd>*.xhtm?</kbd></li>
1116N/A <li>To find packages based on specific
742N/Amatching characters use '[' and ']':<br/>
742N/A<kbd>/usr/bin/[ca]t</kbd></li>
1116N/A </ul>
742N/A% endif
742N/A </div>
742N/A <div class="yui-b results">
1116N/A% if searched and return_type == qp.Query.RETURN_PACKAGES:
1116N/A ## Showing packages.
817N/A <table summary="A list of packages from the repository catalog
817N/A that matched the specified search criteria.">
758N/A <tr class="first">
742N/A <th>Package</th>
1117N/A <th>Install</th>
1117N/A <th colspan="2">Manifest</th>
742N/A </tr>
1116N/A% for v, return_type, vals in results:
742N/A<%
1116N/A pfmri = vals
1116N/A if result_count % 2:
1116N/A rclass = ' class="odd"'
1116N/A else:
1116N/A rclass = ""
1116N/A result_count += 1
1116N/A
1116N/A if result_count > rpp:
1116N/A break
742N/A
1850N/A stem = pfmri.pkg_name
1850N/A pfmri_str = pfmri.get_fmri(anarchy=True,
2958N/A include_scheme=False, include_build=False)
2989N/A pfmri_uri = pfmri.get_fmri(anarchy=True,
2989N/A include_scheme=False)
3158N/A phref = self.shared.rpath(g_vars, "info/0/{0}".format(
2989N/A urllib.quote(pfmri_uri, "")))
1117N/A # XXX the .p5i extension is a bogus hack because
1117N/A # packagemanager requires it and shouldn't.
3158N/A p5ihref = self.shared.rpath(g_vars,
3158N/A "p5i/0/{0}.p5i".format(urllib.quote(stem, "")))
2852N/A mhref = self.shared.rpath(g_vars,
3158N/A "manifest/0/{0}".format(pfmri.get_url_path()))
742N/A%>\
1116N/A <tr${rclass}>
1117N/A <td>
1117N/A <a title="Package Information Summary"
1850N/A href="${phref}">${pfmri_str}</a>
1117N/A </td>
1117N/A <td>
1117N/A <a class="p5i"
1117N/A title="Launch the Package Manager and install this package"
1117N/A href="${p5ihref}">Install</a>
1117N/A </td>
1117N/A <td colspan="2">
1117N/A <a title="Package Manifest"
1117N/A href="${mhref}">Manifest</a>
1117N/A </td>
1116N/A </tr>
1116N/A% endfor
1117N/A${display_pagination(criteria, result_count, colspan=2)}
1116N/A </table>
1116N/A% elif searched and return_type == qp.Query.RETURN_ACTIONS:
1116N/A <table summary="A list of actions from the repository that
1116N/A matched the specified search criteria.">
1116N/A <tr class="first">
1116N/A <th>Index</th>
1116N/A <th>Action</th>
1116N/A <th>Value</th>
1116N/A <th>Package</th>
1116N/A </tr>
1116N/A% for v, return_type, vals in results:
1116N/A<%
1116N/A pfmri, match, action = vals
1116N/A
1116N/A a = actions.fromstr(action.rstrip())
1116N/A action = a.name
1116N/A if isinstance(a, actions.attribute.AttributeAction):
1116N/A index = a.attrs.get(a.key_attr)
1116N/A value = match
1116N/A else:
1116N/A index = match
1116N/A value = a.attrs.get(a.key_attr)
1116N/A%>
1116N/A<%
1116N/A if result_count % 2:
1116N/A rclass = ' class="odd"'
1116N/A else:
1116N/A rclass = ""
1116N/A result_count += 1
1116N/A
1116N/A if result_count > rpp:
1116N/A break
1116N/A
1850N/A pfmri_str = pfmri.get_fmri(anarchy=True,
1850N/A include_scheme=False)
3158N/A phref = self.shared.rpath(g_vars, "info/0/{0}".format(
1850N/A urllib.quote(pfmri_str, "")))
1116N/A%>\
1116N/A <tr${rclass}>
1116N/A <td>${index | h}</td>
1116N/A <td>${action | h}</td>
1116N/A <td>${value | h}</td>
1850N/A <td><a href="${phref}">${pfmri_str}</a></td>
742N/A </tr>
1116N/A% endfor
1116N/A${display_pagination(criteria, result_count, colspan=2)}
1116N/A </table>
1116N/A% elif query_error:
759N/A<%
1116N/A token = criteria["token"]
759N/A%>
1116N/A <p>Your search - <b>${token | h}</b> - is not a valid query.</p>
1053N/A <p>Suggestions:</p>
1053N/A <ul>
1116N/A <li>Remove special characters ('(', ')', '&lt;', '&gt;')
1116N/Afrom your tokens. Replace them with '*' or '?'.</li>
1116N/A <li>Ensure that each branch of your boolean query is
1116N/Areturning the same kind of information. 'foo AND &lt; bar &gt;' will not work.</li>
1053N/A </ul>
1053N/A <p>Details:</p>
1116N/A ${query_error}
1116N/A% elif failed:
1116N/A<%
1116N/A token = criteria["token"]
1116N/A%>
1116N/A <p>Your search - <b>${token | h}</b> - did not match any
1116N/Apackages.</p>
742N/A <p>Suggestions:</p>
742N/A <ul>
742N/A <li>Ensure that all words are spelled correctly.</li>
941N/A <li>Try different keywords.</li>
941N/A <li>Try more general keywords.</li>
742N/A </ul>
742N/A% endif
742N/A </div>
742N/A% endif
742N/A</div>