diff options
-rw-r--r-- | sphinx/builders/versioning.py | 2 | ||||
-rw-r--r-- | sphinx/builders/websupport.py | 95 | ||||
-rw-r--r-- | sphinx/themes/basic/layout.html | 45 | ||||
-rw-r--r-- | sphinx/themes/basic/searchresults.html | 20 | ||||
-rw-r--r-- | sphinx/themes/basic/static/websupport.js | 51 | ||||
-rw-r--r-- | sphinx/websupport/__init__.py | 106 | ||||
-rw-r--r-- | sphinx/websupport/errors.py | 7 | ||||
-rw-r--r-- | sphinx/writers/websupport.py | 2 | ||||
-rw-r--r-- | tests/test_websupport.py | 2 |
9 files changed, 175 insertions, 155 deletions
diff --git a/sphinx/builders/versioning.py b/sphinx/builders/versioning.py index 6c2bccca4..f0646a829 100644 --- a/sphinx/builders/versioning.py +++ b/sphinx/builders/versioning.py @@ -7,7 +7,7 @@ :license: BSD, see LICENSE for details. """ import os -import pickle +import cPickle as pickle from docutils.utils import Reporter diff --git a/sphinx/builders/websupport.py b/sphinx/builders/websupport.py index 7970f8bd7..9ca9f3bb7 100644 --- a/sphinx/builders/websupport.py +++ b/sphinx/builders/websupport.py @@ -9,31 +9,43 @@ :license: BSD, see LICENSE for details. """ -import cPickle as pickle from os import path import posixpath import shutil from docutils.io import StringOutput +from sphinx.jinja2glue import BuiltinTemplateLoader from sphinx.util.osutil import os_path, relative_uri, ensuredir, copyfile -from sphinx.util.jsonimpl import dumps as dump_json from sphinx.util.websupport import is_commentable -from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.builders.html import PickleHTMLBuilder from sphinx.builders.versioning import VersioningBuilderMixin from sphinx.writers.websupport import WebSupportTranslator -class WebSupportBuilder(StandaloneHTMLBuilder, VersioningBuilderMixin): +class WebSupportBuilder(PickleHTMLBuilder, VersioningBuilderMixin): """ Builds documents for the web support package. """ name = 'websupport' - out_suffix = '.fpickle' def init(self): - StandaloneHTMLBuilder.init(self) + PickleHTMLBuilder.init(self) VersioningBuilderMixin.init(self) + # templates are needed for this builder, but the serializing + # builder does not initialize them + self.init_templates() + if not isinstance(self.templates, BuiltinTemplateLoader): + raise RuntimeError('websupport builder must be used with ' + 'the builtin templates') + # add our custom JS + self.script_files.append('_static/websupport.js') + + def set_webinfo(self, staticdir, virtual_staticdir, search, storage): + self.staticdir = staticdir + self.virtual_staticdir = virtual_staticdir + self.search = search + self.storage = storage def init_translator_class(self): self.translator_class = WebSupportTranslator @@ -46,9 +58,9 @@ class WebSupportBuilder(StandaloneHTMLBuilder, VersioningBuilderMixin): self.cur_docname = docname self.secnumbers = self.env.toc_secnumbers.get(docname, {}) - self.imgpath = '/' + posixpath.join(self.app.staticdir, '_images') + self.imgpath = '/' + posixpath.join(self.virtual_staticdir, '_images') self.post_process_images(doctree) - self.dlpath = '/' + posixpath.join(self.app.staticdir, '_downloads') + self.dlpath = '/' + posixpath.join(self.virtual_staticdir, '_downloads') self.docwriter.write(doctree, destination) self.docwriter.assemble_parts() body = self.docwriter.parts['fragment'] @@ -58,11 +70,8 @@ class WebSupportBuilder(StandaloneHTMLBuilder, VersioningBuilderMixin): self.index_page(docname, doctree, ctx.get('title', '')) self.handle_page(docname, ctx, event_arg=doctree) - def get_target_uri(self, docname, typ=None): - return docname - def load_indexer(self, docnames): - self.indexer = self.app.search + self.indexer = self.search self.indexer.init_indexing(changed=docnames) def handle_page(self, pagename, addctx, templatename='page.html', @@ -75,11 +84,13 @@ class WebSupportBuilder(StandaloneHTMLBuilder, VersioningBuilderMixin): def pathto(otheruri, resource=False, baseuri=self.get_target_uri(pagename)): - if not resource: + if resource and '://' in otheruri: + return otheruri + elif not resource: otheruri = self.get_target_uri(otheruri) return relative_uri(baseuri, otheruri) or '#' else: - return '/' + posixpath.join(self.app.staticdir, otheruri) + return '/' + posixpath.join(self.virtual_staticdir, otheruri) ctx['pathto'] = pathto ctx['hasdoc'] = lambda name: name in self.env.all_docs ctx['encoding'] = self.config.html_output_encoding @@ -90,47 +101,41 @@ class WebSupportBuilder(StandaloneHTMLBuilder, VersioningBuilderMixin): self.app.emit('html-page-context', pagename, templatename, ctx, event_arg) - # Create a dict that will be pickled and used by webapps. - css = '<link rel="stylesheet" href="%s" type=text/css />' % \ - pathto('_static/pygments.css', 1) - doc_ctx = {'body': ctx.get('body', ''), - 'title': ctx.get('title', ''), - 'css': css, - 'js': self._make_js(ctx)} - # Partially render the html template to proved a more useful ctx. + # create a dict that will be pickled and used by webapps + doc_ctx = { + 'body': ctx.get('body', ''), + 'title': ctx.get('title', ''), + } + # partially render the html template to get at interesting macros template = self.templates.environment.get_template(templatename) template_module = template.make_module(ctx) - if hasattr(template_module, 'sidebar'): - doc_ctx['sidebar'] = template_module.sidebar() - if hasattr(template_module, 'relbar'): - doc_ctx['relbar'] = template_module.relbar() + for item in ['sidebar', 'relbar', 'script', 'css']: + if hasattr(template_module, item): + doc_ctx[item] = getattr(template_module, item)() if not outfilename: outfilename = path.join(self.outdir, 'pickles', os_path(pagename) + self.out_suffix) - ensuredir(path.dirname(outfilename)) - f = open(outfilename, 'wb') - try: - pickle.dump(doc_ctx, f, pickle.HIGHEST_PROTOCOL) - finally: - f.close() + self.dump_context(doc_ctx, outfilename) # if there is a source file, copy the source file for the # "show source" link if ctx.get('sourcename'): - source_name = path.join(self.app.builddir, self.app.staticdir, + source_name = path.join(self.staticdir, '_sources', os_path(ctx['sourcename'])) ensuredir(path.dirname(source_name)) copyfile(self.env.doc2path(pagename), source_name) def handle_finish(self): - StandaloneHTMLBuilder.handle_finish(self) + PickleHTMLBuilder.handle_finish(self) VersioningBuilderMixin.finish(self) + + # move static stuff over to separate directory directories = ['_images', '_static'] for directory in directories: src = path.join(self.outdir, directory) - dst = path.join(self.app.builddir, self.app.staticdir, directory) + dst = path.join(self.staticdir, directory) if path.isdir(src): if path.isdir(dst): shutil.rmtree(dst) @@ -138,23 +143,3 @@ class WebSupportBuilder(StandaloneHTMLBuilder, VersioningBuilderMixin): def dump_search_index(self): self.indexer.finish_indexing() - - def _make_js(self, ctx): - def make_script(file): - path = ctx['pathto'](file, 1) - return '<script type="text/javascript" src="%s"></script>' % path - - opts = { - 'URL_ROOT': ctx.get('url_root', ''), - 'VERSION': ctx['release'], - 'COLLAPSE_INDEX': False, - 'FILE_SUFFIX': '', - 'HAS_SOURCE': ctx['has_source'] - } - scripts = [make_script(file) for file in ctx['script_files']] - scripts.append(make_script('_static/websupport.js')) - return '\n'.join([ - '<script type="text/javascript">' - 'var DOCUMENTATION_OPTIONS = %s;' % dump_json(opts), - '</script>' - ] + scripts) diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index e31e85443..82b40e1af 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -16,7 +16,13 @@ {%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and (sidebars != []) %} {%- set url_root = pathto('', 1) %} +{# XXX necessary? #} {%- if url_root == '#' %}{% set url_root = '' %}{% endif %} +{%- if not embedded and docstitle %} + {%- set titlesuffix = " — "|safe + docstitle|e %} +{%- else %} + {%- set titlesuffix = "" %} +{%- endif %} {%- macro relbar() %} <div class="related"> @@ -78,24 +84,7 @@ {%- endif %} {%- endmacro %} -<html xmlns="http://www.w3.org/1999/xhtml"> - <head> - <meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" /> - {{ metatags }} - {%- if not embedded and docstitle %} - {%- set titlesuffix = " — "|safe + docstitle|e %} - {%- else %} - {%- set titlesuffix = "" %} - {%- endif %} - {%- block htmltitle %} - <title>{{ title|striptags|e }}{{ titlesuffix }}</title> - {%- endblock %} - <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" /> - <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" /> - {%- for cssfile in css_files %} - <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" /> - {%- endfor %} - {%- if not embedded %} +{%- macro script() %} <script type="text/javascript"> var DOCUMENTATION_OPTIONS = { URL_ROOT: '{{ url_root }}', @@ -108,6 +97,26 @@ {%- for scriptfile in script_files %} <script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script> {%- endfor %} +{%- endmacro %} + +{%- macro css() %} + <link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" /> + <link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" /> + {%- for cssfile in css_files %} + <link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" /> + {%- endfor %} +{%- endmacro %} + +<html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" /> + {{ metatags }} + {%- block htmltitle %} + <title>{{ title|striptags|e }}{{ titlesuffix }}</title> + {%- endblock %} + {{ css() }} + {%- if not embedded %} + {{ script() }} {%- if use_opensearch %} <link rel="search" type="application/opensearchdescription+xml" title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}" diff --git a/sphinx/themes/basic/searchresults.html b/sphinx/themes/basic/searchresults.html index 4b5da1a39..d7af48b2f 100644 --- a/sphinx/themes/basic/searchresults.html +++ b/sphinx/themes/basic/searchresults.html @@ -1,6 +1,6 @@ {# basic/searchresults.html - ~~~~~~~~~~~~~~~~~ + ~~~~~~~~~~~~~~~~~~~~~~~~ Template for the body of the search results page. @@ -17,20 +17,20 @@ <input type="submit" value="search" /> <span id="search-progress" style="padding-left: 10px"></span> </form> -{% if search_performed %} -<h2>Search Results</h2> -{% if not search_results %} -<p>Your search did not match any results.</p> -{% endif %} -{% endif %} +{%- if search_performed %} + <h2>Search Results</h2> + {%- if not search_results %} + <p>Your search did not match any results.</p> + {%- endif %} +{%- endif %} <div id="search-results"> - {% if search_results %} + {%- if search_results %} <ul class="search"> {% for href, caption, context in search_results %} - <li><a href="{{ href }}?highlight={{ q }}">{{ caption }}</a> + <li><a href="{{ docroot }}{{ href }}/?highlight={{ q }}">{{ caption }}</a> <div class="context">{{ context|e }}</div> </li> {% endfor %} </ul> - {% endif %} + {%- endif %} </div> diff --git a/sphinx/themes/basic/static/websupport.js b/sphinx/themes/basic/static/websupport.js index 51a5a7d7c..f545b5716 100644 --- a/sphinx/themes/basic/static/websupport.js +++ b/sphinx/themes/basic/static/websupport.js @@ -214,10 +214,18 @@ * Add a comment via ajax and insert the comment into the comment tree. */ function addComment(form) { - // Disable the form that is being submitted. - form.find('textarea,input').attr('disabled', 'disabled'); var node_id = form.find('input[name="node"]').val(); var parent_id = form.find('input[name="parent"]').val(); + var text = form.find('textarea[name="comment"]').val(); + var proposal = form.find('textarea[name="proposal"]').val(); + + if (text == '') { + showError('Please enter a comment.'); + return; + } + + // Disable the form that is being submitted. + form.find('textarea,input').attr('disabled', 'disabled'); // Send the comment to the server. $.ajax({ @@ -227,8 +235,8 @@ data: { node: node_id, parent: parent_id, - text: form.find('textarea[name="comment"]').val(), - proposal: form.find('textarea[name="proposal"]').val() + text: text, + proposal: proposal }, success: function(data, textStatus, error) { // Reset the form. @@ -311,7 +319,7 @@ $('#cm' + id).fadeOut('fast'); }, error: function(request, textStatus, error) { - showError("Oops, there was a problem accepting the comment."); + showError('Oops, there was a problem accepting the comment.'); } }); } @@ -328,7 +336,7 @@ }); }, error: function(request, textStatus, error) { - showError("Oops, there was a problem rejecting the comment."); + showError('Oops, there was a problem rejecting the comment.'); } }); } @@ -354,7 +362,7 @@ div.data('comment', comment); }, error: function(request, textStatus, error) { - showError("Oops, there was a problem deleting the comment."); + showError('Oops, there was a problem deleting the comment.'); } }); } @@ -395,7 +403,7 @@ var classes = link.attr('class').split(/\s+/); for (var i=0; i<classes.length; i++) { if (classes[i] != 'sort-option') { - by = classes[i]; + by = classes[i].substring(2); } } setComparator(); @@ -464,7 +472,7 @@ url: opts.processVoteURL, data: d, error: function(request, textStatus, error) { - showError("Oops, there was a problem casting that vote."); + showError('Oops, there was a problem casting that vote.'); } }); } @@ -589,7 +597,8 @@ function showError(message) { $(document.createElement('div')).attr({'class': 'popup-error'}) - .append($(document.createElement('h1')).text(message)) + .append($(document.createElement('div')) + .attr({'class': 'error-header'}).text(message)) .appendTo('body') .fadeIn("slow") .delay(2000) @@ -642,12 +651,12 @@ }; var opts = jQuery.extend({ - processVoteURL: '/process_vote', - addCommentURL: '/add_comment', - getCommentsURL: '/get_comments', - acceptCommentURL: '/accept_comment', - rejectCommentURL: '/reject_comment', - deleteCommentURL: '/delete_comment', + processVoteURL: '/_process_vote', + addCommentURL: '/_add_comment', + getCommentsURL: '/_get_comments', + acceptCommentURL: '/_accept_comment', + rejectCommentURL: '/_reject_comment', + deleteCommentURL: '/_delete_comment', commentImage: '/static/_static/comment.png', closeCommentImage: '/static/_static/comment-close.png', loadingImage: '/static/_static/ajax-loader.gif', @@ -727,7 +736,7 @@ var popupTemplate = '\ <div class="sphinx-comments" id="sc<%id%>">\ - <h1>Comments</h1>\ + <div class="comment-header">Comments</div>\ <form method="post" id="cf<%id%>" class="comment-form" action="/docs/add_comment">\ <textarea name="comment" cols="80"></textarea>\ <p class="propose-button">\ @@ -744,12 +753,12 @@ <input type="hidden" name="parent" value="" />\ <p class="sort-options">\ Sort by:\ - <a href="#" class="sort-option rating">top</a>\ - <a href="#" class="sort-option ascage">newest</a>\ - <a href="#" class="sort-option age">oldest</a>\ + <a href="#" class="sort-option byrating">top</a>\ + <a href="#" class="sort-option byascage">newest</a>\ + <a href="#" class="sort-option byage">oldest</a>\ </p>\ </form>\ - <h3 id="cn<%id%>">loading comments... <img src="<%loadingImage%>" alt="" /></h3>\ + <div class="comment-loading" id="cn<%id%>">loading comments... <img src="<%loadingImage%>" alt="" /></div>\ <ul id="cl<%id%>" class="comment-ul"></ul>\ </div>'; diff --git a/sphinx/websupport/__init__.py b/sphinx/websupport/__init__.py index f410e10d3..611c262d5 100644 --- a/sphinx/websupport/__init__.py +++ b/sphinx/websupport/__init__.py @@ -24,29 +24,35 @@ from sphinx.websupport.search import BaseSearch, SEARCH_ADAPTERS from sphinx.websupport.storage import StorageBackend -class WebSupportApp(Sphinx): - def __init__(self, *args, **kwargs): - self.staticdir = kwargs.pop('staticdir', None) - self.builddir = kwargs.pop('builddir', None) - self.search = kwargs.pop('search', None) - self.storage = kwargs.pop('storage', None) - Sphinx.__init__(self, *args, **kwargs) - - class WebSupport(object): """The main API class for the web support package. All interactions with the web support package should occur through this class. """ - def __init__(self, srcdir='', builddir='', datadir='', search=None, - storage=None, status=sys.stdout, warning=sys.stderr, - moderation_callback=None, staticdir='static', - docroot=''): + def __init__(self, + srcdir=None, # only required for building + builddir='', # the dir with data/static/doctrees subdirs + datadir=None, # defaults to builddir/data + staticdir=None, # defaults to builddir/static + doctreedir=None, # defaults to builddir/doctrees + search=None, # defaults to no search + storage=None, # defaults to SQLite in datadir + status=sys.stdout, + warning=sys.stderr, + moderation_callback=None, + docroot='', + staticroot='static', + ): + # directories self.srcdir = srcdir self.builddir = builddir self.outdir = path.join(builddir, 'data') self.datadir = datadir or self.outdir - self.staticdir = staticdir.strip('/') + self.staticdir = staticdir or path.join(self.builddir, 'static') + self.doctreedir = staticdir or path.join(self.builddir, 'doctrees') + # web server virtual paths + self.staticroot = staticroot.strip('/') self.docroot = docroot.strip('/') + self.status = status self.warning = warning self.moderation_callback = moderation_callback @@ -55,6 +61,8 @@ class WebSupport(object): self._init_search(search) self._init_storage(storage) + self._globalcontext = None + self._make_base_comment_options() def _init_storage(self, storage): @@ -103,19 +111,27 @@ class WebSupport(object): It will also save node data to the database. """ if not self.srcdir: - raise errors.SrcdirNotSpecifiedError( \ - 'No srcdir associated with WebSupport object') - doctreedir = path.join(self.outdir, 'doctrees') - app = WebSupportApp(self.srcdir, self.srcdir, - self.outdir, doctreedir, 'websupport', - search=self.search, status=self.status, - warning=self.warning, storage=self.storage, - staticdir=self.staticdir, builddir=self.builddir) + raise RuntimeError('No srcdir associated with WebSupport object') + app = Sphinx(self.srcdir, self.srcdir, self.outdir, self.doctreedir, + 'websupport', status=self.status, warning=self.warning) + app.builder.set_webinfo(self.staticdir, self.staticroot, + self.search, self.storage) self.storage.pre_build() app.build() self.storage.post_build() + def get_globalcontext(self): + """Load and return the "global context" pickle.""" + if not self._globalcontext: + infilename = path.join(self.datadir, 'globalcontext.pickle') + f = open(infilename, 'rb') + try: + self._globalcontext = pickle.load(f) + finally: + f.close() + return self._globalcontext + def get_document(self, docname, username='', moderator=False): """Load and return a document from a pickle. The document will be a dict object which can be used to render a template:: @@ -146,28 +162,35 @@ class WebSupport(object): * **relbar**: A div containing links to related documents * **title**: The title of the document * **css**: Links to css files used by Sphinx - * **js**: Javascript containing comment options + * **script**: Javascript containing comment options This raises :class:`~sphinx.websupport.errors.DocumentNotFoundError` if a document matching `docname` is not found. :param docname: the name of the document to load. """ - infilename = path.join(self.datadir, 'pickles', docname + '.fpickle') + docpath = path.join(self.datadir, 'pickles', docname) + if path.isdir(docpath): + infilename = docpath + '/index.fpickle' + else: + infilename = docpath + '.fpickle' try: f = open(infilename, 'rb') except IOError: raise errors.DocumentNotFoundError( 'The document "%s" could not be found' % docname) + try: + document = pickle.load(f) + finally: + f.close() - document = pickle.load(f) comment_opts = self._make_comment_options(username, moderator) comment_metadata = self.storage.get_metadata(docname, moderator) - document['js'] = '\n'.join([comment_opts, - self._make_metadata(comment_metadata), - document['js']]) + document['script'] = '\n'.join([comment_opts, + self._make_metadata(comment_metadata), + document['script']]) return document def get_search_results(self, q): @@ -181,9 +204,12 @@ class WebSupport(object): :param q: the search query """ results = self.search.query(q) - ctx = {'search_performed': True, - 'search_results': results, - 'q': q} + ctx = { + 'q': q, + 'search_performed': True, + 'search_results': results, + 'docroot': '../', # XXX + } document = self.get_document('search') document['body'] = self.results_template.render(ctx) document['title'] = 'Search Results' @@ -359,17 +385,17 @@ class WebSupport(object): if self.docroot != '': comment_urls = [ - ('addCommentURL', 'add_comment'), - ('getCommentsURL', 'get_comments'), - ('processVoteURL', 'process_vote'), - ('acceptCommentURL', 'accept_comment'), - ('rejectCommentURL', 'reject_comment'), - ('deleteCommentURL', 'delete_comment') + ('addCommentURL', '_add_comment'), + ('getCommentsURL', '_get_comments'), + ('processVoteURL', '_process_vote'), + ('acceptCommentURL', '_accept_comment'), + ('rejectCommentURL', '_reject_comment'), + ('deleteCommentURL', '_delete_comment') ] for key, value in comment_urls: self.base_comment_opts[key] = \ '/' + posixpath.join(self.docroot, value) - if self.staticdir != 'static': + if self.staticroot != 'static': static_urls = [ ('commentImage', 'comment.png'), ('closeCommentImage', 'comment-close.png'), @@ -382,7 +408,7 @@ class WebSupport(object): ] for key, value in static_urls: self.base_comment_opts[key] = \ - '/' + posixpath.join(self.staticdir, '_static', value) + '/' + posixpath.join(self.staticroot, '_static', value) def _make_comment_options(self, username, moderator): """Helper method to create the parts of the COMMENT_OPTIONS @@ -391,8 +417,6 @@ class WebSupport(object): :param username: The username of the user making the request. :param moderator: Whether the user making the request is a moderator. """ - # XXX parts is not used? - #parts = [self.base_comment_opts] rv = self.base_comment_opts.copy() if username: rv.update({ diff --git a/sphinx/websupport/errors.py b/sphinx/websupport/errors.py index 53106dfb8..62309ed20 100644 --- a/sphinx/websupport/errors.py +++ b/sphinx/websupport/errors.py @@ -9,18 +9,11 @@ :license: BSD, see LICENSE for details. """ -__all__ = ['DocumentNotFoundError', 'SrcdirNotSpecifiedError', - 'UserNotAuthorizedError', 'CommentNotAllowedError', - 'NullSearchException'] class DocumentNotFoundError(Exception): pass -class SrcdirNotSpecifiedError(Exception): - pass - - class UserNotAuthorizedError(Exception): pass diff --git a/sphinx/writers/websupport.py b/sphinx/writers/websupport.py index bb80fb7ee..224329d7e 100644 --- a/sphinx/writers/websupport.py +++ b/sphinx/writers/websupport.py @@ -39,7 +39,7 @@ class WebSupportTranslator(HTMLTranslator): node.attributes['classes'].append(self.comment_class) def add_db_node(self, node): - storage = self.builder.app.storage + storage = self.builder.storage if not storage.has_node(node.uid): storage.add_node(id=node.uid, document=self.builder.cur_docname, diff --git a/tests/test_websupport.py b/tests/test_websupport.py index 65957378b..84d6f9e0f 100644 --- a/tests/test_websupport.py +++ b/tests/test_websupport.py @@ -65,7 +65,7 @@ class NullStorage(StorageBackend): @with_support(storage=NullStorage()) def test_no_srcdir(support): """Make sure the correct exception is raised if srcdir is not given.""" - raises(SrcdirNotSpecifiedError, support.build) + raises(RuntimeError, support.build) @skip_if(sqlalchemy_missing, 'needs sqlalchemy') |