[Python-checkins] r66360 - in doctools/trunk/sphinx: builder.py search.py static/doctools.js static/searchtools.js templates/search.html
armin.ronacher
python-checkins at python.org
Wed Sep 10 11:11:57 CEST 2008
Author: armin.ronacher
Date: Wed Sep 10 11:11:56 2008
New Revision: 66360
Log:
Improved search system. The search index is now a regular javascript file which should speed things up because browsers can cache it. Removed unused code from doctools.js
Modified:
doctools/trunk/sphinx/builder.py
doctools/trunk/sphinx/search.py
doctools/trunk/sphinx/static/doctools.js
doctools/trunk/sphinx/static/searchtools.js
doctools/trunk/sphinx/templates/search.html
Modified: doctools/trunk/sphinx/builder.py
==============================================================================
--- doctools/trunk/sphinx/builder.py (original)
+++ doctools/trunk/sphinx/builder.py Wed Sep 10 11:11:56 2008
@@ -34,6 +34,7 @@
from sphinx.environment import BuildEnvironment, NoUri
from sphinx.highlighting import PygmentsBridge
from sphinx.util.console import bold, purple, darkgreen
+from sphinx.search import js_index
# side effect: registers roles and directives
from sphinx import roles
@@ -338,10 +339,10 @@
name = 'html'
copysource = True
out_suffix = '.html'
- indexer_format = json
+ indexer_format = js_index
supported_image_types = ['image/svg+xml', 'image/png', 'image/gif',
'image/jpeg']
- searchindex_filename = 'searchindex.json'
+ searchindex_filename = 'searchindex.js'
add_header_links = True
add_definition_links = True
Modified: doctools/trunk/sphinx/search.py
==============================================================================
--- doctools/trunk/sphinx/search.py (original)
+++ doctools/trunk/sphinx/search.py Wed Sep 10 11:11:56 2008
@@ -10,6 +10,7 @@
"""
import re
import cPickle as pickle
+from cStringIO import StringIO
from docutils.nodes import Text, NodeVisitor
@@ -20,6 +21,37 @@
word_re = re.compile(r'\w+(?u)')
+class _JavaScriptIndex(object):
+ """
+ The search index as javascript file that calls a function
+ on the documentation search object to register the index.
+ This serializing system does not support chaining because
+ simplejson (which it depends on) doesn't support it either.
+ """
+
+ PREFIX = 'Search.setIndex('
+ SUFFIX = ')'
+
+ def dumps(self, data):
+ return self.PREFIX + json.dumps(data) + self.SUFFIX
+
+ def loads(self, s):
+ data = s[len(self.PREFIX):-len(self.SUFFIX)]
+ if not data or not s.startswith(self.PREFIX) or not \
+ s.endswith(self.SUFFIX):
+ raise ValueError('invalid data')
+ return json.loads(data)
+
+ def dump(self, data, f):
+ f.write(self.dumps(data))
+
+ def load(self, f):
+ return self.loads(f.read())
+
+
+js_index = _JavaScriptIndex()
+
+
class Stemmer(PorterStemmer):
"""
All those porter stemmer implementations look hideous.
Modified: doctools/trunk/sphinx/static/doctools.js
==============================================================================
--- doctools/trunk/sphinx/static/doctools.js (original)
+++ doctools/trunk/sphinx/static/doctools.js Wed Sep 10 11:11:56 2008
@@ -94,11 +94,9 @@
var Documentation = {
init : function() {
- /* this.addContextElements(); -- now done statically */
this.fixFirefoxAnchorBug();
this.highlightSearchWords();
this.initModIndex();
- this.initComments();
},
/**
@@ -108,6 +106,8 @@
PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; },
LOCALE : 'unknown',
+ // gettext and ngettext don't access this so that the functions
+ // can savely bound to a different name (_ = Documentation.gettext)
gettext : function(string) {
var translated = Documentation.TRANSLATIONS[string];
if (typeof translated == 'undefined')
@@ -133,14 +133,12 @@
* add context elements like header anchor links
*/
addContextElements : function() {
- for (var i = 1; i <= 6; i++) {
- $('h' + i + '[@id]').each(function() {
- $('<a class="headerlink">\u00B6</a>').
- attr('href', '#' + this.id).
- attr('title', _('Permalink to this headline')).
- appendTo(this);
- });
- }
+ $('div[@id] > :header:first').each(function() {
+ $('<a class="headerlink">\u00B6</a>').
+ attr('href', '#' + this.id).
+ attr('title', _('Permalink to this headline')).
+ appendTo(this);
+ });
$('dt[@id]').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
@@ -197,37 +195,6 @@
},
/**
- * init the inline comments
- */
- initComments : function() {
- $('.inlinecomments div.actions').each(function() {
- this.innerHTML += ' | ';
- $(this).append($('<a href="#">hide comments</a>').click(function() {
- $(this).parent().parent().toggle();
- return false;
- }));
- });
- $('.inlinecomments .comments').hide();
- $('.inlinecomments a.bubble').each(function() {
- $(this).click($(this).is('.emptybubble') ? function() {
- var params = $.getQueryParameters(this.href);
- Documentation.newComment(params.target[0]);
- return false;
- } : function() {
- $('.comments', $(this).parent().parent()[0]).toggle();
- return false;
- });
- });
- $('#comments div.actions a.newcomment').click(function() {
- Documentation.newComment();
- return false;
- });
- if (document.location.hash.match(/^#comment-/))
- $('.inlinecomments .comments ' + document.location.hash)
- .parent().toggle();
- },
-
- /**
* helper function to hide the search marks again
*/
hideSearchWords : function() {
@@ -236,22 +203,6 @@
},
/**
- * show the comment window for a certain id or the whole page.
- */
- newComment : function(id) {
- Documentation.CommentWindow.openFor(id || '');
- },
-
- /**
- * write a new comment from within a comment view box
- */
- newCommentFromBox : function(link) {
- var params = $.getQueryParameters(link.href);
- $(link).parent().parent().fadeOut('slow');
- this.newComment(params.target);
- },
-
- /**
* make the url absolute
*/
makeURL : function(relativeURL) {
@@ -270,108 +221,7 @@
});
var url = parts.join('/');
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
- },
-
- /**
- * class that represents the comment window
- */
- CommentWindow : (function() {
- var openWindows = {};
-
- var Window = function(sectionID) {
- this.url = Documentation.makeURL('@comments/' + Documentation.getCurrentURL()
- + '/?target=' + $.urlencode(sectionID) + '&mode=ajax');
- this.sectionID = sectionID;
-
- this.root = $('<div class="commentwindow"></div>');
- this.root.appendTo($('body'));
- this.title = $('<h3>New Comment</h3>').appendTo(this.root);
- this.body = $('<div class="form">please wait...</div>').appendTo(this.root);
- this.resizeHandle = $('<div class="resizehandle"></div>').appendTo(this.root);
-
- this.root.Draggable({
- handle: this.title[0]
- });
-
- this.root.css({
- left: window.innerWidth / 2 - $(this.root).width() / 2,
- top: window.scrollY + (window.innerHeight / 2 - 150)
- });
- this.root.fadeIn('slow');
- this.updateView();
- };
-
- Window.prototype.updateView = function(data) {
- var self = this;
- function update(data) {
- if (data.posted) {
- document.location.hash = '#comment-' + data.commentID;
- document.location.reload();
- }
- else {
- self.body.html(data.body);
- $('div.actions', self.body).append($('<input>')
- .attr('type', 'button')
- .attr('value', 'Close')
- .click(function() { self.close(); })
- );
- $('div.actions input[@name="preview"]')
- .attr('type', 'button')
- .click(function() { self.submitForm($('form', self.body)[0], true); });
- $('form', self.body).bind("submit", function() {
- self.submitForm(this);
- return false;
- });
-
- if (data.error) {
- self.root.Highlight(1000, '#aadee1');
- $('div.error', self.root).slideDown(500);
- }
- }
- }
-
- if (typeof data == 'undefined')
- $.getJSON(this.url, function(json) { update(json); });
- else
- $.ajax({
- url: this.url,
- type: 'POST',
- dataType: 'json',
- data: data,
- success: function(json) { update(json); }
- });
- }
-
- Window.prototype.getFormValue = function(name) {
- return $('*[@name="' + name + '"]', this.body)[0].value;
- }
-
- Window.prototype.submitForm = function(form, previewMode) {
- this.updateView({
- author: form.author.value,
- author_mail: form.author_mail.value,
- title: form.title.value,
- comment_body: form.comment_body.value,
- preview: previewMode ? 'yes' : ''
- });
- }
-
- Window.prototype.close = function() {
- var self = this;
- delete openWindows[this.sectionID];
- this.root.fadeOut('slow', function() {
- self.root.remove();
- });
- }
-
- Window.openFor = function(sectionID) {
- if (sectionID in openWindows)
- return openWindows[sectionID];
- return new Window(sectionID);
- }
-
- return Window;
- })()
+ }
};
// quick alias for translations
Modified: doctools/trunk/sphinx/static/searchtools.js
==============================================================================
--- doctools/trunk/sphinx/static/searchtools.js (original)
+++ doctools/trunk/sphinx/static/searchtools.js Wed Sep 10 11:11:56 2008
@@ -224,6 +224,10 @@
*/
var Search = {
+ _index : null,
+ _queued_query : null,
+ _pulse_status : -1,
+
init : function() {
var params = $.getQueryParameters();
if (params.q) {
@@ -234,33 +238,68 @@
},
/**
- * perform a search for something
+ * Sets the index
*/
- performSearch : function(query) {
- // create the required interface elements
- var out = $('#search-results');
- var title = $('<h2>' + _('Searching') + '</h2>').appendTo(out);
- var dots = $('<span></span>').appendTo(title);
- var status = $('<p style="display: none"></p>').appendTo(out);
- var output = $('<ul class="search"/>').appendTo(out);
- $('#search-progress').text(_('Getting search index...'));
-
- // spawn a background runner for updating the dots
- // until the search has finished
- var pulseStatus = 0;
+ setIndex : function(index) {
+ var q;
+ this._index = index;
+ if ((q = this._queued_query) !== null) {
+ this._queued_query = null;
+ Search.query(q);
+ }
+ },
+
+ hasIndex : function() {
+ return self._index !== null;
+ },
+
+ deferQuery : function(query) {
+ this._queued_query = query;
+ },
+
+ stopPulse : function() {
+ this._pulse_status = 0;
+ },
+
+ startPulse : function() {
+ if (this._pulse_status >= 0)
+ return;
function pulse() {
- pulseStatus = (pulseStatus + 1) % 4;
+ Search._pulse_status = (Search._pulse_status + 1) % 4;
var dotString = '';
- for (var i = 0; i < pulseStatus; i++) {
+ for (var i = 0; i < Search._pulse_status; i++) {
dotString += '.';
}
- dots.text(dotString);
- if (pulseStatus > -1) {
+ Search.dots.text(dotString);
+ if (Search._pulse_status > -1) {
window.setTimeout(pulse, 500);
}
};
pulse();
+ },
+ /**
+ * perform a search for something
+ */
+ performSearch : function(query) {
+ // create the required interface elements
+ this.out = $('#search-results');
+ this.title = $('<h2>' + _('Searching') + '</h2>').appendTo(this.out);
+ this.dots = $('<span></span>').appendTo(this.title);
+ this.status = $('<p style="display: none"></p>').appendTo(this.out);
+ this.output = $('<ul class="search"/>').appendTo(this.out);
+
+ $('#search-progress').text(_('Preparing search...'));
+ this.startPulse();
+
+ // index already loaded, the browser was quick!
+ if (this.hasIndex())
+ this.query(query);
+ else
+ this.setQuery(query);
+ },
+
+ query : function(query) {
// stem the searchwords and add them to the
// correct list
var stemmer = new PorterStemmer();
@@ -291,112 +330,102 @@
console.info('required: ', searchwords);
console.info('excluded: ', excluded);
- // fetch searchindex and perform search
- $.getJSON('searchindex.json', function(data) {
+ // prepare search
+ var filenames = this._index[0];
+ var titles = this._index[1];
+ var words = this._index[2];
+ var fileMap = {};
+ var files = null;
+ $('#search-progress').empty();
+
+ // perform the search on the required words
+ for (var i = 0; i < searchwords.length; i++) {
+ var word = searchwords[i];
+ // no match but word was a required one
+ if ((files = words[word]) == null)
+ break;
+ // create the mapping
+ for (var j = 0; j < files.length; j++) {
+ var file = files[j];
+ if (file in fileMap)
+ fileMap[file].push(word);
+ else
+ fileMap[file] = [word];
+ }
+ }
- // prepare search
- var filenames = data[0];
- var titles = data[1]
- var words = data[2];
- var fileMap = {};
- var files = null;
-
- $('#search-progress').empty()
-
- // perform the search on the required words
- for (var i = 0; i < searchwords.length; i++) {
- var word = searchwords[i];
- // no match but word was a required one
- if ((files = words[word]) == null) {
- break;
- }
- // create the mapping
- for (var j = 0; j < files.length; j++) {
- var file = files[j];
- if (file in fileMap) {
- fileMap[file].push(word);
- }
- else {
- fileMap[file] = [word];
- }
- }
+ // now check if the files are in the correct
+ // areas and if the don't contain excluded words
+ var results = [];
+ for (var file in fileMap) {
+ var valid = true;
+
+ // check if all requirements are matched
+ if (fileMap[file].length != searchwords.length)
+ continue;
+
+ // ensure that none of the excluded words is in the
+ // search result.
+ for (var i = 0; i < excluded.length; i++) {
+ if ($.contains(words[excluded[i]] || [], file)) {
+ valid = false;
+ break;
}
+ }
- // now check if the files are in the correct
- // areas and if the don't contain excluded words
- var results = [];
- for (var file in fileMap) {
- var valid = true;
-
- // check if all requirements are matched
- if (fileMap[file].length != searchwords.length) {
- continue;
- }
- // ensure that none of the excluded words is in the
- // search result.
- for (var i = 0; i < excluded.length; i++) {
- if ($.contains(words[excluded[i]] || [], file)) {
- valid = false;
- break;
- }
- }
-
- // if we have still a valid result we can add it
- // to the result list
- if (valid) {
- results.push([filenames[file], titles[file]]);
- }
- }
+ // if we have still a valid result we can add it
+ // to the result list
+ if (valid)
+ results.push([filenames[file], titles[file]]);
+ }
- // delete unused variables in order to not waste
- // memory until list is retrieved completely
- delete filenames, titles, words, data;
-
- // now sort the results by title
- results.sort(function(a, b) {
- var left = a[1].toLowerCase();
- var right = b[1].toLowerCase();
- return (left > right) ? -1 : ((left < right) ? 1 : 0);
+ // delete unused variables in order to not waste
+ // memory until list is retrieved completely
+ delete filenames, titles, words;
+
+ // now sort the results by title
+ results.sort(function(a, b) {
+ var left = a[1].toLowerCase();
+ var right = b[1].toLowerCase();
+ return (left > right) ? -1 : ((left < right) ? 1 : 0);
+ });
+
+ // print the results
+ var resultCount = results.length;
+ function displayNextItem() {
+ // results left, load the summary and display it
+ if (results.length) {
+ var item = results.pop();
+ var listItem = $('<li style="display:none"></li>');
+ listItem.append($('<a/>').attr(
+ 'href',
+ item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
+ highlightstring).html(item[1]));
+ $.get('_sources/' + item[0] + '.txt', function(data) {
+ listItem.append($.makeSearchSummary(data, searchwords, hlwords));
+ Search.output.append(listItem);
+ listItem.slideDown(10, function() {
+ displayNextItem();
});
-
- // print the results
- var resultCount = results.length;
- function displayNextItem() {
- // results left, load the summary and display it
- if (results.length) {
- var item = results.pop();
- var listItem = $('<li style="display:none"></li>');
- listItem.append($('<a/>').attr(
- 'href',
- item[0] + DOCUMENTATION_OPTIONS.FILE_SUFFIX +
- highlightstring).html(item[1]));
- $.get('_sources/' + item[0] + '.txt', function(data) {
- listItem.append($.makeSearchSummary(data, searchwords, hlwords));
- output.append(listItem);
- listItem.slideDown(10, function() {
- displayNextItem();
- });
- });
- }
- // search finished, update title and status message
- else {
- pulseStatus = -1;
- title.text(_('Search Results'));
- if (!resultCount) {
- status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
- }
- else {
- status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
- }
- status.fadeIn(500);
- }
+ });
+ }
+ // search finished, update title and status message
+ else {
+ Search.stopPulse();
+ Search.title.text(_('Search Results'));
+ if (!resultCount) {
+ Search.status.text(_('Your search did not match any documents. Please make sure that all words are spelled correctly and that you\'ve selected enough categories.'));
}
- displayNextItem();
- });
+ else {
+ Search.status.text(_('Search finished, found %s page(s) matching the search query.').replace('%s', resultCount));
+ }
+ Search.status.fadeIn(500);
+ }
+ }
+ displayNextItem();
}
-
}
$(document).ready(function() {
- Search.init();
- });
+ Search.init();
+});
Modified: doctools/trunk/sphinx/templates/search.html
==============================================================================
--- doctools/trunk/sphinx/templates/search.html (original)
+++ doctools/trunk/sphinx/templates/search.html Wed Sep 10 11:11:56 2008
@@ -1,6 +1,6 @@
{% extends "layout.html" %}
{% set title = _('Search') %}
-{% set script_files = script_files + ['_static/searchtools.js'] %}
+{% set script_files = script_files + ['_static/searchtools.js', 'searchindex.js'] %}
{% block body %}
<h1 id="search-documentation">{{ _('Search') }}</h1>
<p>
More information about the Python-checkins
mailing list