Project: | ${meta.project} | License: | ${meta.license} |
---|---|---|---|
Version: | ${meta.version} (${meta.date if not meta.devel else last_changed}) | Download: | ${meta.download_url} |
Author: | ${meta.author} <${meta.author_email}> | Repository: | ${url} (Browse) |
Overview
${meta.project} is a Mercurial extension that allows serving a dynamic, read-only website using a Mercurial repository as the backend storage. Pages are served by hgweb, the same component that serves the Mercurial repository, and no additional configuration is necessary in the web server.
Current features include:
- A subset of the files in the repository are rendered through a number of renderers, based on their path. Built-in renderers include support for Genshi templates, static files and in-repository renderers, and you can also add your own.
- It is possible to view a site as it was at any revision, thanks to the consistent versioning of the rendering configuration and all the files composing a site. Have your own Wayback Machine for your site!
- Rendered pages have full access to the repository. This allows linking to specific files in the repository browser, including snippets of code in a page (with syntax highlighting), or even implementing a fully custom repository browser.
These features allow a number of interesting use cases, for example:
- Serve a README file at the top-level of your repository, GitHub-style.
- Serve the web site for a small project directly out of the project's repository. Using a templating engine like Genshi allows writing HTML pages relatively comfortably, as it allows factoring out common parts to avoid duplication. Editing and testing is very easy thanks to the immediate feedback provided by hg serve. And pushing changes to production is as easy as hg push.
- Keep the documentation of a project together with the project's code in a single repository, and serve it directly out of the repository. As both are versioned together, it's easy to get the documentation for a specific version of the code. Moreover, the templates have access to all the files in the repository, so the documentation can link to files in the repository browser or inline snippets directly. You could even create a fully custom repository browser.
- Run a small blog. The site structure can be done with a few HTML templates, and the articles could be stored as individual files, which could be listed and rendered in the templates.
This site is (of course) powered by hgweb with ${meta.project}, and demonstrates most of the available features.
Quick start
Here's a very short guide to get started with ${meta.project}. The sample files are available in the examples directory.
- Install setuptools, Genshi and Pygments.
- Install ${meta.project}:
$ easy_install ${meta.name}
- In a local clone of your project, edit .hg/hgrc and add:
[extensions] cspace.hgsite = [hgsite] allow_wc = true default_revision = - Create a file named .hgsite at the top-level of your project, with the following
content:
[render] html = .*\.html$ html.renderer = genshi:xhtml html.mimetype = text/html - Create a directory named site, and create the file site/index.html with the following content: ${include('examples/hello_world.html', 'genshi')}
- Run hg serve and go to ${url}.
Congratulations! You are viewing your new project site.
URL structure
${meta.project} URLs have the following form:
http://{host}/{path/to/repo}/[~{node}/]{path}
The file to be rendered for a given URL is determined by the following rules:
- If the file {base_path}/{path} exists in the repository, it is selected.
- For each extension {ext} in ext_search_path, if the file {base_path}/{path}.{ext} exists in the repository, it is selected.
- Otherwise, the list of all files in the repository starting with {base_path}/{path}. is sorted lexically, and the first file in the list is selected.
This allows having nice URLs like http://example.com/repo/documentation that don't expose file extensions.
The optional ~-prefixed {node} before the file path can be any revision reference (as defined by hg help revisions), and specifies which revision of the file must be rendered. An empty {node} resolves to the working copy (if allow_wc is true). This allows viewing the site as it was at any revision.
Note that ${meta.project} shares the URL path namespace with hgweb, and the latter has priority. This is only an issue when no node is specified. For example, if path starts with log/, then the request will be handled by hgweb. Page and directory names should therefore be chosen so as not to conflict with hgweb web commands, as listed in __all__. In particular, static resources should not be placed in a directory named static, as this name is already used by hgweb.
Installation
${meta.project} requires the following packages:
- Genshi (optional), for the XML and text renderers
- Pygments (optional), for syntax highlighting
- Setuptools or distribute
The extension can be installed with either of the following methods, which may require administrator privileges.
- With easy_install:
$ easy_install ${meta.name}
- From a source tarball or a ZIP archive:
$ tar -xvzf ${meta.name}-${meta.version}.tar.gz $ cd ${meta.name}-${meta.version} $ python setup.py install
- From a clone of the repository:
$ hg clone ${req.abs_url(repo_href())} ${meta.project} $ cd ${meta.project} $ python setup.py install
Configuration
The configuration is split into two parts:
- An [hgsite] section in hgrc.
- A versioned file named .hgsite in the repository root.
The extension must be enabled by adding the following lines in hgrc:
Once enabled, it is activated when a file named .hgsite exists at the requested revision (it can be empty).
Section [hgsite] in hgrc
This section configures the aspects of the extension that are independent of the served content. The following options are available:
- allow_wc (boolean): Specifies if content can be served from the the working copy. When true, URLs with an empty revision will render files from the working copy. This is most useful during development (when using hg serve), as it allows to see the effect of edits without having to commit. However, it may be a security issue, as it may allow accessing files outside of the working copy. The default is (of course) false.
- context.genshi (path): The path to a Python module that will be executed in the rendering context of Genshi prior to rendering. Relative paths are resolved relative to the .hg directory of the repository. This allows manipulating the context independently of the (immutable) content of the repository. This can be used e.g. to fix the rendering of old site revisions when dependencies (Genshi, ${meta.project} itself) change in backward-incompatible ways.
- default_revision (string): A revision reference (as defined by hg help revisions) or a revset expression (see hg help revsets) that specifies the revision to use when no {node} is specified in the URL. An empty value resolves to the working copy (if allow_wc is true). When a revset expression resolves to multiple revisions, the first revision in the set is used. The default is default, the tip of the default branch.
A typical [hgsite] section for a production site could look like this:
Section [config] in .hgsite
This section configures the location of the site files, and the way the path section of the URL is mapped to repository paths. The following options are available:
- base_path (string): The path within the repository that contains the files to be rendered. The default is site.
- error_page (string): The site-relative path to use to render a custom error page. If this option is empty (the default), errors are propagated to hgweb.
- ext_search_path (string): A comma-separated list of extensions that will be appended to the path specified in the URL to try and find the file to be rendered. The default is empty.
- site_index (string): The site-relative path to use for the site root URL (i.e. http://example.com/repo/site). The default is index.
Here's a sample [config] section:
Section [render] in .hgsite
This section configures how files are rendered. Option names that don't contain a . (period) define regexp patterns to be matched against the file path, and rendering arguments are specified in options with the same name and a .{argument} suffix. For example, if a pattern is specified in the option html, the corresponding arguments are taken from html.renderer, html.mimetype and so on.
To determine the renderer to be used for a file, the options are first sorted lexically, then the (repository-relative) path is matched against each pattern in order. The first matching pattern wins. If none of the patterns matches, a "404 Not found" error is returned.
The following options define the renderer for a set of paths ({name} can be an arbitrary identifier not containing .):
- {name} (string): A regexp pattern to match against the (repository-relative) path of the file to be rendered.
- {name}.renderer (string): The renderer to use for files matching the pattern. The default is static.
Renderers can accept additional arguments, which are passed in the config argument of the rendering function. The following arguments are supported by most renderers:
- {name}.mimetype (string): The MIME type to return in the Content-Type header of the reply. If this option isn't set, the MIME type is guessed from the file extension, with a renderer-dependent fallback.
- {name}.encoding (string): The encoding used in the file. This value is used by the renderer and is also returned as the charset= parameter in the Content-Type header of the reply. The default is renderer-dependent.
Here's a sample [render] section (see the .hgsite file for this site for even more examples):
Renderers
Static renderer
The static renderer serves the target file as-is, without any processing. The fallback MIME type for this renderer is application/octet-stream.
Genshi renderers
The following renderers use Genshi for rendering:
- genshi:html, genshi:xhtml and genshi:xml render files as XML templates with the html, xhtml and xml serialization method, respectively. The fallback MIME type for these renderers, if guessing fails, is text/xml.
- genshi:text renders files as text templates with the text serialization method. The fallback MIME type for this renderer, if guessing fails, is text/plain.
Files included from within templates with <xi:include> and {% include %} are read from the same revision as the including file. This ensures that rendering is consistent at any requested revision.
Rendering context
The context in which templates are evaluated is pre-populated with the following symbols:
- highlight(text, lexer, tabsize=0) → stream Apply Pygments syntax highlighting to the given text, with the given lexer, and return a Genshi stream with the markup. The stream can be inserted into a markup template with standard $${} syntax. If tabsize is nonzero, expand tabs to that size. For an example how to use syntax highlighting, see the py:match directives and functions used for this site.
- req: A request object for the current request.
- All additional arguments passed to the renderer as **kwargs.
Pygments CSS renderer
The pygments:css renderer outputs the CSS for Pygments syntax highlighting. It doesn't need a target file, and can therefore be set up to render any path. It accepts the following options:
- {name}.container (string): A comma-separated list of CSS selectors that
identify the containers of syntax-highlighted sections. For example, if this option is set to
pre.highlight, the highlighting styles will only be active within
<pre class="highlight"> tags. - {name}.style (string): The highlighting style to use. The default is trac.
When using syntax highlighting, this renderer should be configured for a (virtual) path (for example site/res/pygments.css):
and pages using highlighting should link to it with:
Exec renderer
The exec renderer executes the target file as a Python module, then calls the render() function that must be defined in the module for rendering. The function has the same signature as custom renderers.
API
Request object
The req object is a wrapper around Mercurial's wsgirequest object, with additional functionality. All attributes and methods of the wsgirequest are accessible through req.
Attributes
The following attributes are set on the req object:
- ctx: The change context for the node at which to render.
- cfg: The Config object holding the .hgsite configuration.
- path_info: The full PATH_INFO for the current request.
- repo: The repository object.
- repo_default: The URL of the default hgweb page for the selected Mercurial template style (the page normally displayed at the root of the repository, e.g. shortlog for the "paper" style). This is typically used for a "Browse repository" link.
- url_node: The requested node. This is an empty string for the working copy, and None for the default revision.
- url_path: The requested path.
- web: The hgweb instance handling the request.
URL builders
The req object provides methods to build URLs relative to various bases. Methods taking generic argument lists buid the URL by starting from the base, appending the /-joined positional arguments, and appending a query string built from the keyword arguments. Empty path segments and keyword arguments with None values are discarded. See href_builder() for details.
- href(*args, **kwargs) → url Build a URL relative to the requested node. The generated URL has the same {node} as the rendered file. This should be used for inter-page links.
- rev_href(*args, **kwargs) → url Build a URL relative to the exact revision at which a file is rendered. The generated URL specifies the full hex revision as the {node}. This should be used for links where the revision must be consistent with the rendered file.
- static_href(path) → url Build a URL for the static resource at the given (site) path. The generated URL specifies the full hex revision where the file was last modified as the {node}. This should be used to reference files rendered with the static renderer, typically stylesheets, scripts and images.
- hist_href(node, *args, **kwargs) → url Build a URL relative to the given node (a "historical" URL). node can be any revision reference (as defined by hg help revisions), an empty string for the working copy, or None for the default revision. This can be used to generate links to a different versions of the site, for example for each release tag.
- repo_href(*args, **kwargs) → url Build a URL relative to the repository root. This can be used to link to other parts of hgweb, for example the revision log, or specific files or changesets.
- browse_href(path, line=None, n=1) → url Build a URL to the Mercurial file browser page for the given file, at the requested revision. If line is a number, the URL is anchored at that line. If it's a string, it is compiled as a regexp and the URL is anchored at the nth match, at the start of the first capturing group or at the start of the match if there are no capturing groups.
The URLs generated by the builders are site-relative, i.e. they don't specify the scheme and host. They can be made absolute with req.abs_url().
As an example, the call req.href('doc', page, q='test', lang='en'), assuming the repository base URL is '/hg/repo', the node is 'default' and page is 'config', will return the URL /hg/repo/~default/doc/config?q=test&lang=en.
Reply methods
The req object provides methods for renderers to simplify the generation of a reply. These methods set the status code, appropriate headers, and return an iterable that yields content chunks. This iterable should be returned by the renderer.
- redirect(url) → chunks Send a reply to redirect the browser to the given URL.
- send(content, mimetype=None, encoding=None, status=200, headers=None) → chunks Send a reply with the given status, optionally with a Content-Type header and additional headers (passed as a list of (name, value) pairs) and the given (string) content.
Alternatively, renderers can generate a reply "by hand" by calling req.header() and req.respond(), and returning an iterable yielding content chunks.
Adding renderers
Extensions can register additional renderers by decorating functions with the @renderer() decorator defined in cspace.hgsite.site. The decorator takes the following arguments:
- @renderer(*names, file_must_exist=True)
- names: The names under which this renderer must be registered.
- file_must_exist: When False, the file to be rendered doesn't have to exist in the repository. This allows defining "pseudo-renderers" like the Pygments CSS renderer.
A renderer is a function with the following signature:
- render(req, path, name, config, **kwargs) → chunks
- req: A request object for the current request.
- path: The repository-relative path of the file to be rendered.
- name: The name of the renderer, as specified at registration time.
- config: A dict of configuration arguments for this renderer, as set in the [render] section of .hgsite.
- **kwargs: Additional context-dependent arguments for the renderer.
- exc_info: The value returned by sys.exc_info() when handling an exception. This symbol is only present when rendering a custom error page.
- chunks: An iterable yielding chunks of reply content.
Renderers will typically use the req.send() method to generate the reply.
Contribute
Found a bug? Got a great idea to improve ${meta.project}? A patch to add a renderer for your favorite templating engine? Suggestions for improving the documentation?
Please let me know!
Releases
- Development (${util.shortdate(req.repo[dev_tip].date())})
- ${tag} (${util.shortdate(date)})