mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
docs: Address review comments
helloworld: - Return version metadata from extension - Use 'reST' instead of 'rST' - Don't use single backticks todo: - Return version metadata from extension - Link to events section of API guide, rather than entire document - Include name of phases in when describing build phases - Use more method cross-references where possible - Fix typo in usage example recipe: - Return version metadata from extension - Rework code to simplify things - Remove docstrings from 'generate' functions, which are simply duplicates of the original docstring - Rename 'rcp' directive to 'recipe', the 'reref' role to 'ref', and a whole lot of variables to something more grokable - Fix typos in both documentation and code I've also fixed up the docstrings for some of the 'domain' functions to make them render a little nicer (they took me a while to figure out). Signed-off-by: Stephen Finucane <stephen@that.guru>
This commit is contained in:
11
doc/development/tutorials/examples/README.rst
Normal file
11
doc/development/tutorials/examples/README.rst
Normal file
@@ -0,0 +1,11 @@
|
||||
:orphan:
|
||||
|
||||
Tutorial examples
|
||||
=================
|
||||
|
||||
This directory contains a number of examples used in the tutorials. These are
|
||||
intended to be increasingly complex to demonstrate the various features of
|
||||
Sphinx, but should aim to be as complicated as necessary but no more.
|
||||
Individual sections are referenced by line numbers, meaning if you make changes
|
||||
to the source files, you should update the references in the documentation
|
||||
accordingly.
|
||||
@@ -11,3 +11,9 @@ class HelloWorld(Directive):
|
||||
|
||||
def setup(app):
|
||||
app.add_directive("helloworld", HelloWorld)
|
||||
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from collections import defaultdict
|
||||
|
||||
import docutils
|
||||
from docutils import nodes
|
||||
from docutils.parsers import rst
|
||||
@@ -18,33 +20,27 @@ class RecipeDirective(ObjectDescription):
|
||||
has_content = True
|
||||
required_arguments = 1
|
||||
option_spec = {
|
||||
'contains': directives.unchanged_required
|
||||
'contains': directives.unchanged_required,
|
||||
}
|
||||
|
||||
def handle_signature(self, sig, signode):
|
||||
signode += addnodes.desc_name(text=sig)
|
||||
signode += addnodes.desc_type(text='Recipe')
|
||||
return sig
|
||||
|
||||
def add_target_and_index(self, name_cls, sig, signode):
|
||||
signode['ids'].append('recipe' + '-' + sig)
|
||||
if 'noindex' not in self.options:
|
||||
name = '{}.{}.{}'.format('rcp', type(self).__name__, sig)
|
||||
imap = self.env.domaindata['rcp']['obj2ingredient']
|
||||
imap[name] = list(self.options.get('contains').split(' '))
|
||||
objs = self.env.domaindata['rcp']['objects']
|
||||
objs.append((name,
|
||||
sig,
|
||||
'Recipe',
|
||||
self.env.docname,
|
||||
'recipe' + '-' + sig,
|
||||
0))
|
||||
ingredients = [
|
||||
x.strip() for x in self.options.get('contains').split(',')]
|
||||
|
||||
recipes = self.env.get_domain('recipe')
|
||||
recipes.add_recipe(sig, ingredients)
|
||||
|
||||
|
||||
class IngredientIndex(Index):
|
||||
"""A custom directive that creates an ingredient matrix."""
|
||||
"""A custom index that creates an ingredient matrix."""
|
||||
|
||||
name = 'ing'
|
||||
name = 'ingredient'
|
||||
localname = 'Ingredient Index'
|
||||
shortname = 'Ingredient'
|
||||
|
||||
@@ -52,69 +48,39 @@ class IngredientIndex(Index):
|
||||
super(IngredientIndex, self).__init__(*args, **kwargs)
|
||||
|
||||
def generate(self, docnames=None):
|
||||
"""Return entries for the index given by *name*.
|
||||
content = defaultdict(list)
|
||||
|
||||
If *docnames* is given, restrict to entries referring to these
|
||||
docnames. The return value is a tuple of ``(content, collapse)``,
|
||||
where:
|
||||
recipes = {name: (dispname, typ, docname, anchor)
|
||||
for name, dispname, typ, docname, anchor, _
|
||||
in self.domain.get_objects()}
|
||||
recipe_ingredients = self.domain.data['recipe_ingredients']
|
||||
ingredient_recipes = defaultdict(list)
|
||||
|
||||
*collapse* is a boolean that determines if sub-entries should
|
||||
start collapsed (for output formats that support collapsing
|
||||
sub-entries).
|
||||
# flip from recipe_ingredients to ingredient_recipes
|
||||
for recipe_name, ingredients in recipe_ingredients.items():
|
||||
for ingredient in ingredients:
|
||||
ingredient_recipes[ingredient].append(recipe_name)
|
||||
|
||||
*content* is a sequence of ``(letter, entries)`` tuples, where *letter*
|
||||
is the "heading" for the given *entries*, usually the starting letter.
|
||||
# convert the mapping of ingredient to recipes to produce the expected
|
||||
# output, shown below, using the ingredient name as a key to group
|
||||
#
|
||||
# name, subtype, docname, anchor, extra, qualifier, description
|
||||
for ingredient, recipe_names in ingredient_recipes.items():
|
||||
for recipe_name in recipe_names:
|
||||
dispname, typ, docname, anchor = recipes[recipe_name]
|
||||
content[ingredient].append(
|
||||
(dispname, 0, docname, anchor, docname, '', typ))
|
||||
|
||||
*entries* is a sequence of single entries, where a single entry is a
|
||||
sequence ``[name, subtype, docname, anchor, extra, qualifier, descr]``.
|
||||
# convert the dict to the sorted list of tuples expected
|
||||
content = sorted(content.items())
|
||||
|
||||
The items in this sequence have the following meaning:
|
||||
|
||||
- `name` -- the name of the index entry to be displayed
|
||||
- `subtype` -- sub-entry related type:
|
||||
- ``0`` -- normal entry
|
||||
- ``1`` -- entry with sub-entries
|
||||
- ``2`` -- sub-entry
|
||||
- `docname` -- docname where the entry is located
|
||||
- `anchor` -- anchor for the entry within `docname`
|
||||
- `extra` -- extra info for the entry
|
||||
- `qualifier` -- qualifier for the description
|
||||
- `descr` -- description for the entry
|
||||
|
||||
Qualifier and description are not rendered by some builders, such as
|
||||
the LaTeX builder.
|
||||
"""
|
||||
|
||||
content = {}
|
||||
|
||||
objs = {name: (dispname, typ, docname, anchor)
|
||||
for name, dispname, typ, docname, anchor, prio
|
||||
in self.domain.get_objects()}
|
||||
|
||||
imap = {}
|
||||
ingr = self.domain.data['obj2ingredient']
|
||||
for name, ingr in ingr.items():
|
||||
for ig in ingr:
|
||||
imap.setdefault(ig,[])
|
||||
imap[ig].append(name)
|
||||
|
||||
for ingredient in imap.keys():
|
||||
lis = content.setdefault(ingredient, [])
|
||||
objlis = imap[ingredient]
|
||||
for objname in objlis:
|
||||
dispname, typ, docname, anchor = objs[objname]
|
||||
lis.append((
|
||||
dispname, 0, docname,
|
||||
anchor,
|
||||
docname, '', typ
|
||||
))
|
||||
re = [(k, v) for k, v in sorted(content.items())]
|
||||
|
||||
return (re, True)
|
||||
return content, True
|
||||
|
||||
|
||||
class RecipeIndex(Index):
|
||||
name = 'rcp'
|
||||
"""A custom index that creates an recipe matrix."""
|
||||
|
||||
name = 'recipe'
|
||||
localname = 'Recipe Index'
|
||||
shortname = 'Recipe'
|
||||
|
||||
@@ -122,92 +88,54 @@ class RecipeIndex(Index):
|
||||
super(RecipeIndex, self).__init__(*args, **kwargs)
|
||||
|
||||
def generate(self, docnames=None):
|
||||
"""Return entries for the index given by *name*.
|
||||
content = defaultdict(list)
|
||||
|
||||
If *docnames* is given, restrict to entries referring to these
|
||||
docnames. The return value is a tuple of ``(content, collapse)``,
|
||||
where:
|
||||
# sort the list of recipes in alphabetical order
|
||||
recipes = self.domain.get_objects()
|
||||
recipes = sorted(recipes, key=lambda recipe: recipe[0])
|
||||
|
||||
*collapse* is a boolean that determines if sub-entries should
|
||||
start collapsed (for output formats that support collapsing
|
||||
sub-entries).
|
||||
# generate the expected output, shown below, from the above using the
|
||||
# first letter of the recipe as a key to group thing
|
||||
#
|
||||
# name, subtype, docname, anchor, extra, qualifier, description
|
||||
for name, dispname, typ, docname, anchor, _ in recipes:
|
||||
content[dispname[0].lower()].append(
|
||||
(dispname, 0, docname, anchor, docname, '', typ))
|
||||
|
||||
*content* is a sequence of ``(letter, entries)`` tuples, where *letter*
|
||||
is the "heading" for the given *entries*, usually the starting letter.
|
||||
# convert the dict to the sorted list of tuples expected
|
||||
content = sorted(content.items())
|
||||
|
||||
*entries* is a sequence of single entries, where a single entry is a
|
||||
sequence ``[name, subtype, docname, anchor, extra, qualifier, descr]``.
|
||||
|
||||
The items in this sequence have the following meaning:
|
||||
|
||||
- `name` -- the name of the index entry to be displayed
|
||||
- `subtype` -- sub-entry related type:
|
||||
- ``0`` -- normal entry
|
||||
- ``1`` -- entry with sub-entries
|
||||
- ``2`` -- sub-entry
|
||||
- `docname` -- docname where the entry is located
|
||||
- `anchor` -- anchor for the entry within `docname`
|
||||
- `extra` -- extra info for the entry
|
||||
- `qualifier` -- qualifier for the description
|
||||
- `descr` -- description for the entry
|
||||
|
||||
Qualifier and description are not rendered by some builders, such as
|
||||
the LaTeX builder.
|
||||
"""
|
||||
|
||||
content = {}
|
||||
items = ((name, dispname, typ, docname, anchor)
|
||||
for name, dispname, typ, docname, anchor, prio
|
||||
in self.domain.get_objects())
|
||||
items = sorted(items, key=lambda item: item[0])
|
||||
for name, dispname, typ, docname, anchor in items:
|
||||
lis = content.setdefault('Recipe', [])
|
||||
lis.append((
|
||||
dispname, 0, docname,
|
||||
anchor,
|
||||
docname, '', typ
|
||||
))
|
||||
re = [(k, v) for k, v in sorted(content.items())]
|
||||
|
||||
return (re, True)
|
||||
return content, True
|
||||
|
||||
|
||||
class RecipeDomain(Domain):
|
||||
|
||||
name = 'rcp'
|
||||
name = 'recipe'
|
||||
label = 'Recipe Sample'
|
||||
|
||||
roles = {
|
||||
'reref': XRefRole()
|
||||
'ref': XRefRole()
|
||||
}
|
||||
|
||||
directives = {
|
||||
'recipe': RecipeDirective,
|
||||
}
|
||||
|
||||
indices = {
|
||||
RecipeIndex,
|
||||
IngredientIndex
|
||||
}
|
||||
|
||||
initial_data = {
|
||||
'objects': [], # object list
|
||||
'obj2ingredient': {}, # name -> object
|
||||
'recipes': [], # object list
|
||||
'recipe_ingredients': {}, # name -> object
|
||||
}
|
||||
|
||||
def get_full_qualified_name(self, node):
|
||||
"""Return full qualified name for a given node"""
|
||||
return "{}.{}.{}".format('rcp',
|
||||
type(node).__name__,
|
||||
node.arguments[0])
|
||||
return '{}.{}'.format('recipe', node.arguments[0])
|
||||
|
||||
def get_objects(self):
|
||||
for obj in self.data['objects']:
|
||||
for obj in self.data['recipes']:
|
||||
yield(obj)
|
||||
|
||||
def resolve_xref(self, env, fromdocname, builder, typ, target, node,
|
||||
contnode):
|
||||
|
||||
match = [(docname, anchor)
|
||||
for name, sig, typ, docname, anchor, prio
|
||||
in self.get_objects() if sig == target]
|
||||
@@ -219,21 +147,25 @@ class RecipeDomain(Domain):
|
||||
return make_refnode(builder, fromdocname, todocname, targ,
|
||||
contnode, targ)
|
||||
else:
|
||||
print("Awww, found nothing")
|
||||
print('Awww, found nothing')
|
||||
return None
|
||||
|
||||
def add_recipe(self, signature, ingredients):
|
||||
"""Add a new recipe to the domain."""
|
||||
name = '{}.{}'.format('recipe', signature)
|
||||
anchor = 'recipe-{}'.format(signature)
|
||||
|
||||
self.data['recipe_ingredients'][name] = ingredients
|
||||
# name, dispname, type, docname, anchor, priority
|
||||
self.data['recipes'].append(
|
||||
(name, signature, 'Recipe', self.env.docname, anchor, 0))
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.add_domain(RecipeDomain)
|
||||
|
||||
StandardDomain.initial_data['labels']['recipeindex'] = (
|
||||
'rcp-rcp', '', 'Recipe Index')
|
||||
StandardDomain.initial_data['labels']['ingredientindex'] = (
|
||||
'rcp-ing', '', 'Ingredient Index')
|
||||
|
||||
StandardDomain.initial_data['anonlabels']['recipeindex'] = (
|
||||
'rcp-rcp', '')
|
||||
StandardDomain.initial_data['anonlabels']['ingredientindex'] = (
|
||||
'rcp-ing', '')
|
||||
|
||||
return {'version': '0.1'} # identifies the version of our extension
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
|
||||
@@ -117,4 +117,8 @@ def setup(app):
|
||||
app.connect('doctree-resolved', process_todo_nodes)
|
||||
app.connect('env-purge-doc', purge_todos)
|
||||
|
||||
return {'version': '0.1'} # identifies the version of our extension
|
||||
return {
|
||||
'version': '0.1',
|
||||
'parallel_read_safe': True,
|
||||
'parallel_write_safe': True,
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ Developing a "Hello world" extension
|
||||
====================================
|
||||
|
||||
The objective of this tutorial is to create a very basic extension that adds a
|
||||
new directive. This directive will output a paragraph containing `hello world`.
|
||||
new directive. This directive will output a paragraph containing "hello world".
|
||||
|
||||
Only basic information is provided in this tutorial. For more information, refer
|
||||
to the :doc:`other tutorials <index>` that go into more details.
|
||||
|
||||
.. warning::
|
||||
|
||||
For this extension, you will need some basic understanding of docutils_
|
||||
and Python.
|
||||
|
||||
@@ -17,7 +18,7 @@ Overview
|
||||
|
||||
We want the extension to add the following to Sphinx:
|
||||
|
||||
* A ``helloworld`` directive, that will simply output the text `hello world`.
|
||||
* A ``helloworld`` directive, that will simply output the text "hello world".
|
||||
|
||||
|
||||
Prerequisites
|
||||
@@ -104,10 +105,10 @@ Sphinx.
|
||||
:linenos:
|
||||
:lines: 12-
|
||||
|
||||
The simplest thing you can do it call the :meth:`~Sphinx.add_directive`
|
||||
method, which is what we've done here. For this particular call, the first
|
||||
argument is the name of the directive itself as used in an rST file. In this
|
||||
case, we would use ``helloworld``. For example:
|
||||
The simplest thing you can do it call the :meth:`~Sphinx.add_directive` method,
|
||||
which is what we've done here. For this particular call, the first argument is
|
||||
the name of the directive itself as used in a reST file. In this case, we would
|
||||
use ``helloworld``. For example:
|
||||
|
||||
.. code-block:: rst
|
||||
|
||||
@@ -117,6 +118,10 @@ case, we would use ``helloworld``. For example:
|
||||
|
||||
Some more text here...
|
||||
|
||||
We also return the :ref:`extension metadata <ext-metadata>` that indicates the
|
||||
version of our extension, along with the fact that it is safe to use the
|
||||
extension for both parallel reading and writing.
|
||||
|
||||
|
||||
Using the extension
|
||||
-------------------
|
||||
|
||||
@@ -19,14 +19,14 @@ Overview
|
||||
We want the extension to add the following to Sphinx:
|
||||
|
||||
* A ``recipe`` :term:`directive`, containing some content describing the recipe
|
||||
steps, along with a ``:contains:`` argument highlighting the main ingredients
|
||||
steps, along with a ``:contains:`` option highlighting the main ingredients
|
||||
of the recipe.
|
||||
|
||||
* A ``reref`` :term:`role`, which provides a cross-reference to the recipe
|
||||
* A ``ref`` :term:`role`, which provides a cross-reference to the recipe
|
||||
itself.
|
||||
|
||||
* A ``rcp`` :term:`domain`, which allows us to tie together the above role and
|
||||
domain, along with things like indices.
|
||||
* A ``recipe`` :term:`domain`, which allows us to tie together the above role
|
||||
and domain, along with things like indices.
|
||||
|
||||
For that, we will need to add the following elements to Sphinx:
|
||||
|
||||
@@ -34,24 +34,15 @@ For that, we will need to add the following elements to Sphinx:
|
||||
|
||||
* New indexes to allow us to reference ingredient and recipes
|
||||
|
||||
* A new domain called ``rcp``, which will contain the ``recipe`` directive and
|
||||
``reref`` role
|
||||
* A new domain called ``recipe``, which will contain the ``recipe`` directive
|
||||
and ``ref`` role
|
||||
|
||||
|
||||
Prerequisites
|
||||
-------------
|
||||
|
||||
As with :doc:`the previous extensions <todo>`, we will not be distributing this
|
||||
plugin via PyPI so once again we need a Sphinx project to call this from. You
|
||||
can use an existing project or create a new one using
|
||||
:program:`sphinx-quickstart`.
|
||||
|
||||
We assume you are using separate source (:file:`source`) and build
|
||||
(:file:`build`) folders. Your extension file could be in any folder of your
|
||||
project. In our case, let's do the following:
|
||||
|
||||
#. Create an :file:`_ext` folder in :file:`source`
|
||||
#. Create a new Python file in the :file:`_ext` folder called :file:`recipe.py`
|
||||
We need the same setup as in :doc:`the previous extensions <todo>`. This time,
|
||||
we will be putting out extension in a file called :file:`recipe.py`.
|
||||
|
||||
Here is an example of the folder structure you might obtain:
|
||||
|
||||
@@ -59,7 +50,7 @@ Here is an example of the folder structure you might obtain:
|
||||
|
||||
└── source
|
||||
├── _ext
|
||||
│ └── todo.py
|
||||
│ └── recipe.py
|
||||
├── conf.py
|
||||
└── index.rst
|
||||
|
||||
@@ -67,8 +58,8 @@ Here is an example of the folder structure you might obtain:
|
||||
Writing the extension
|
||||
---------------------
|
||||
|
||||
Open :file:`receipe.py` and paste the following code in it, all of which we
|
||||
will explain in detail shortly:
|
||||
Open :file:`recipe.py` and paste the following code in it, all of which we will
|
||||
explain in detail shortly:
|
||||
|
||||
.. literalinclude:: examples/recipe.py
|
||||
:language: python
|
||||
@@ -79,12 +70,12 @@ on.
|
||||
|
||||
.. rubric:: The directive class
|
||||
|
||||
The first thing to examine is the ``RecipeNode`` directive:
|
||||
The first thing to examine is the ``RecipeDirective`` directive:
|
||||
|
||||
.. literalinclude:: examples/recipe.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 15-40
|
||||
:lines: 17-37
|
||||
|
||||
Unlike :doc:`helloworld` and :doc:`todo`, this directive doesn't derive from
|
||||
:class:`docutils.parsers.rst.Directive` and doesn't define a ``run`` method.
|
||||
@@ -100,7 +91,7 @@ for this node.
|
||||
We also see that this directive defines ``has_content``, ``required_arguments``
|
||||
and ``option_spec``. Unlike the ``TodoDirective`` directive added in the
|
||||
:doc:`previous tutorial <todo>`, this directive takes a single argument, the
|
||||
recipe name, and an optional argument, ``contains``, in addition to the nested
|
||||
recipe name, and an option, ``contains``, in addition to the nested
|
||||
reStructuredText in the body.
|
||||
|
||||
.. rubric:: The index classes
|
||||
@@ -112,11 +103,11 @@ reStructuredText in the body.
|
||||
.. literalinclude:: examples/recipe.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 44-172
|
||||
:lines: 40-108
|
||||
|
||||
Both ``IngredientIndex`` and ``RecipeIndex`` are derived from :class:`Index`.
|
||||
They implement custom logic to generate a tuple of values that define the
|
||||
index. Note that ``RecipeIndex`` is a degenerate index that has only one entry.
|
||||
index. Note that ``RecipeIndex`` is a simple index that has only one entry.
|
||||
Extending it to cover more object types is not yet part of the code.
|
||||
|
||||
Both indices use the method :meth:`Index.generate` to do their work. This
|
||||
@@ -135,9 +126,9 @@ creating here.
|
||||
.. literalinclude:: examples/recipe.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 175-223
|
||||
:lines: 111-161
|
||||
|
||||
There are some interesting things to note about this ``rcp`` domain and domains
|
||||
There are some interesting things to note about this ``recipe`` domain and domains
|
||||
in general. Firstly, we actually register our directives, roles and indices
|
||||
here, via the ``directives``, ``roles`` and ``indices`` attributes, rather than
|
||||
via calls later on in ``setup``. We can also note that we aren't actually
|
||||
@@ -146,19 +137,21 @@ defining a custom role and are instead reusing the
|
||||
:class:`sphinx.domains.Domain.resolve_xref` method. This method takes two
|
||||
arguments, ``typ`` and ``target``, which refer to the cross-reference type and
|
||||
its target name. We'll use ``target`` to resolve our destination from our
|
||||
domain's ``objects`` because we currently have only one type of node.
|
||||
domain's ``recipes`` because we currently have only one type of node.
|
||||
|
||||
Moving on, we can see that we've defined two items in ``intitial_data``:
|
||||
``objects`` and ``obj2ingredient``. These contain a list of all objects defined
|
||||
(i.e. all recipes) and a hash that maps a canonical ingredient name to the list
|
||||
of objects. The way we name objects is common across our extension and is
|
||||
defined in the ``get_full_qualified_name`` method. For each object created, the
|
||||
canonical name is ``rcp.<typename>.<objectname>``, where ``<typename>`` is the
|
||||
Python type of the object, and ``<objectname>`` is the name the documentation
|
||||
writer gives the object. This enables the extension to use different object
|
||||
types that share the same name. Having a canonical name and central place for
|
||||
our objects is a huge advantage. Both our indices and our cross-referencing
|
||||
code use this feature.
|
||||
Moving on, we can see that we've defined ``initial_data``. The values defined in
|
||||
``initial_data`` will be copied to ``env.domaindata[domain_name]`` as the
|
||||
initial data of the domain, and domain instances can access it via
|
||||
``self.data``. We see that we have defined two items in ``initial_data``:
|
||||
``recipes`` and ``recipe2ingredient``. These contain a list of all objects
|
||||
defined (i.e. all recipes) and a hash that maps a canonical ingredient name to
|
||||
the list of objects. The way we name objects is common across our extension and
|
||||
is defined in the ``get_full_qualified_name`` method. For each object created,
|
||||
the canonical name is ``recipe.<recipename>``, where ``<recipename>`` is the
|
||||
name the documentation writer gives the object (a recipe). This enables the
|
||||
extension to use different object types that share the same name. Having a
|
||||
canonical name and central place for our objects is a huge advantage. Both our
|
||||
indices and our cross-referencing code use this feature.
|
||||
|
||||
.. rubric:: The ``setup`` function
|
||||
|
||||
@@ -171,7 +164,7 @@ hook the various parts of our extension into Sphinx. Let's look at the
|
||||
.. literalinclude:: examples/recipe.py
|
||||
:language: python
|
||||
:linenos:
|
||||
:lines: 226-
|
||||
:lines: 164-
|
||||
|
||||
This looks a little different to what we're used to seeing. There are no calls
|
||||
to :meth:`~Sphinx.add_directive` or even :meth:`~Sphinx.add_role`. Instead, we
|
||||
@@ -192,8 +185,8 @@ You can now use the extension throughout your project. For example:
|
||||
Joe's Recipes
|
||||
=============
|
||||
|
||||
Below are a collection of my favourite receipes. I highly recommend the
|
||||
:rcp:reref:`TomatoSoup` receipe in particular!
|
||||
Below are a collection of my favourite recipes. I highly recommend the
|
||||
:recipe:ref:`TomatoSoup` recipe in particular!
|
||||
|
||||
.. toctree::
|
||||
|
||||
@@ -204,15 +197,15 @@ You can now use the extension throughout your project. For example:
|
||||
|
||||
The recipe contains `tomato` and `cilantro`.
|
||||
|
||||
.. rcp:recipe:: TomatoSoup
|
||||
.. recipe:recipe:: TomatoSoup
|
||||
:contains: tomato cilantro salt pepper
|
||||
|
||||
This recipe is a tasty tomato soup, combine all ingredients
|
||||
and cook.
|
||||
|
||||
The important things to note are the use of the ``:rcp:recipe:`` role to
|
||||
The important things to note are the use of the ``:recipe:ref:`` role to
|
||||
cross-reference the recipe actually defined elsewhere (using the
|
||||
``:rcp:recipe:`` directive.
|
||||
``:recipe:recipe:`` directive.
|
||||
|
||||
|
||||
Further reading
|
||||
|
||||
@@ -174,10 +174,10 @@ The node structure that the directive returns looks like this::
|
||||
|
||||
.. rubric:: The event handlers
|
||||
|
||||
Event handlers are one of Sphinx's most powerful features, providing a way to do
|
||||
hook into any part of the documentation process. There are many hooks available,
|
||||
as detailed in :doc:`/extdev/appapi`, and we're going to use a subset of them
|
||||
here.
|
||||
Event handlers are one of Sphinx's most powerful features, providing a way to
|
||||
do hook into any part of the documentation process. There are many events
|
||||
provided by Sphinx itself, as detailed in :ref:`the API guide <events>`, and
|
||||
we're going to use a subset of them here.
|
||||
|
||||
Let's look at the event handlers used in the above example. First, the one for
|
||||
the :event:`env-purge-doc` event:
|
||||
@@ -203,18 +203,19 @@ The other handler belongs to the :event:`doctree-resolved` event:
|
||||
:lines: 64-103
|
||||
|
||||
The :event:`doctree-resolved` event is emitted at the end of :ref:`phase 3
|
||||
<build-phases>` and allows custom resolving to be done. The handler we have
|
||||
written for this event is a bit more involved. If the ``todo_include_todos``
|
||||
config value (which we'll describe shortly) is false, all ``todo`` and
|
||||
``todolist`` nodes are removed from the documents. If not, ``todo`` nodes just
|
||||
stay where and how they are. ``todolist`` nodes are replaced by a list of todo
|
||||
entries, complete with backlinks to the location where they come from. The list
|
||||
items are composed of the nodes from the ``todo`` entry and docutils nodes
|
||||
created on the fly: a paragraph for each entry, containing text that gives the
|
||||
location, and a link (reference node containing an italic node) with the
|
||||
backreference. The reference URI is built by ``app.builder.get_relative_uri``
|
||||
which creates a suitable URI depending on the used builder, and appending the
|
||||
todo node's (the target's) ID as the anchor name.
|
||||
(resolving) <build-phases>` and allows custom resolving to be done. The handler
|
||||
we have written for this event is a bit more involved. If the
|
||||
``todo_include_todos`` config value (which we'll describe shortly) is false,
|
||||
all ``todo`` and ``todolist`` nodes are removed from the documents. If not,
|
||||
``todo`` nodes just stay where and how they are. ``todolist`` nodes are
|
||||
replaced by a list of todo entries, complete with backlinks to the location
|
||||
where they come from. The list items are composed of the nodes from the
|
||||
``todo`` entry and docutils nodes created on the fly: a paragraph for each
|
||||
entry, containing text that gives the location, and a link (reference node
|
||||
containing an italic node) with the backreference. The reference URI is built
|
||||
by :meth:`sphinx.builders.Builder.get_relative_uri`` which creates a suitable
|
||||
URI depending on the used builder, and appending the todo node's (the target's)
|
||||
ID as the anchor name.
|
||||
|
||||
.. rubric:: The ``setup`` function
|
||||
|
||||
@@ -238,13 +239,13 @@ What the individual calls do is the following:
|
||||
|
||||
If the third argument was ``'html'``, HTML documents would be full rebuild if the
|
||||
config value changed its value. This is needed for config values that
|
||||
influence reading (build :ref:`phase 1 <build-phases>`).
|
||||
influence reading (build :ref:`phase 1 (reading) <build-phases>`).
|
||||
|
||||
* :meth:`~Sphinx.add_node` adds a new *node class* to the build system. It also
|
||||
can specify visitor functions for each supported output format. These visitor
|
||||
functions are needed when the new nodes stay until :ref:`phase 4 <build-phases>`
|
||||
-- since the ``todolist`` node is always replaced in :ref:`phase 3 <build-phases>`,
|
||||
it doesn't need any.
|
||||
functions are needed when the new nodes stay until :ref:`phase 4 (writing)
|
||||
<build-phases>`. Since the ``todolist`` node is always replaced in
|
||||
:ref:`phase 3 (resolving) <build-phases>`, it doesn't need any.
|
||||
|
||||
* :meth:`~Sphinx.add_directive` adds a new *directive*, given by name and class.
|
||||
|
||||
@@ -279,7 +280,7 @@ For example:
|
||||
|
||||
sys.path.append(os.path.abspath("./_ext"))
|
||||
|
||||
extensions = ['helloworld']
|
||||
extensions = ['todo']
|
||||
|
||||
todo_include_todos = False
|
||||
|
||||
|
||||
@@ -91,32 +91,54 @@ class Index:
|
||||
|
||||
def generate(self, docnames=None):
|
||||
# type: (Iterable[str]) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]
|
||||
"""Return entries for the index given by *name*. If *docnames* is
|
||||
given, restrict to entries referring to these docnames.
|
||||
"""Get entries for the index.
|
||||
|
||||
The return value is a tuple of ``(content, collapse)``, where *collapse*
|
||||
is a boolean that determines if sub-entries should start collapsed (for
|
||||
output formats that support collapsing sub-entries).
|
||||
If ``docnames`` is given, restrict to entries referring to these
|
||||
docnames.
|
||||
|
||||
*content* is a sequence of ``(letter, entries)`` tuples, where *letter*
|
||||
is the "heading" for the given *entries*, usually the starting letter.
|
||||
The return value is a tuple of ``(content, collapse)``:
|
||||
|
||||
*entries* is a sequence of single entries, where a single entry is a
|
||||
sequence ``[name, subtype, docname, anchor, extra, qualifier, descr]``.
|
||||
The items in this sequence have the following meaning:
|
||||
``collapse``
|
||||
A boolean that determines if sub-entries should start collapsed (for
|
||||
output formats that support collapsing sub-entries).
|
||||
|
||||
- `name` -- the name of the index entry to be displayed
|
||||
- `subtype` -- sub-entry related type:
|
||||
0 -- normal entry
|
||||
1 -- entry with sub-entries
|
||||
2 -- sub-entry
|
||||
- `docname` -- docname where the entry is located
|
||||
- `anchor` -- anchor for the entry within `docname`
|
||||
- `extra` -- extra info for the entry
|
||||
- `qualifier` -- qualifier for the description
|
||||
- `descr` -- description for the entry
|
||||
``content``:
|
||||
A sequence of ``(letter, entries)`` tuples, where ``letter`` is the
|
||||
"heading" for the given ``entries``, usually the starting letter, and
|
||||
``entries`` is a sequence of single entries. Each entry is a sequence
|
||||
``[name, subtype, docname, anchor, extra, qualifier, descr]``. The
|
||||
items in this sequence have the following meaning:
|
||||
|
||||
Qualifier and description are not rendered e.g. in LaTeX output.
|
||||
``name``
|
||||
The name of the index entry to be displayed.
|
||||
|
||||
``subtype``
|
||||
The sub-entry related type. One of:
|
||||
|
||||
``0``
|
||||
A normal entry.
|
||||
``1``
|
||||
An entry with sub-entries.
|
||||
``2``
|
||||
A sub-entry.
|
||||
|
||||
``docname``
|
||||
*docname* where the entry is located.
|
||||
|
||||
``anchor``
|
||||
Anchor for the entry within ``docname``
|
||||
|
||||
``extra``
|
||||
Extra info for the entry.
|
||||
|
||||
``qualifier``
|
||||
Qualifier for the description.
|
||||
|
||||
``descr``
|
||||
Description for the entry.
|
||||
|
||||
Qualifier and description are not rendered for some output formats such
|
||||
as LaTeX.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -318,21 +340,37 @@ class Domain:
|
||||
|
||||
def get_objects(self):
|
||||
# type: () -> Iterable[Tuple[str, str, str, str, str, int]]
|
||||
"""Return an iterable of "object descriptions", which are tuples with
|
||||
five items:
|
||||
"""Return an iterable of "object descriptions".
|
||||
|
||||
* `name` -- fully qualified name
|
||||
* `dispname` -- name to display when searching/linking
|
||||
* `type` -- object type, a key in ``self.object_types``
|
||||
* `docname` -- the document where it is to be found
|
||||
* `anchor` -- the anchor name for the object
|
||||
* `priority` -- how "important" the object is (determines placement
|
||||
in search results)
|
||||
Object descriptions are tuples with six items:
|
||||
|
||||
- 1: default priority (placed before full-text matches)
|
||||
- 0: object is important (placed before default-priority objects)
|
||||
- 2: object is unimportant (placed after full-text matches)
|
||||
- -1: object should not show up in search at all
|
||||
``name``
|
||||
Fully qualified name.
|
||||
|
||||
``dispname``
|
||||
Name to display when searching/linking.
|
||||
|
||||
``type``
|
||||
Object type, a key in ``self.object_types``.
|
||||
|
||||
``docname``
|
||||
The document where it is to be found.
|
||||
|
||||
``anchor``
|
||||
The anchor name for the object.
|
||||
|
||||
``priority``
|
||||
How "important" the object is (determines placement in search
|
||||
results). One of:
|
||||
|
||||
``1``
|
||||
Default priority (placed before full-text matches).
|
||||
``0``
|
||||
Object is important (placed before default-priority objects).
|
||||
``2``
|
||||
Object is unimportant (placed after full-text matches).
|
||||
``-1``
|
||||
Object should not show up in search at all.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user