diff options
author | Stephen Finucane <stephen@that.guru> | 2019-02-08 17:47:19 +0000 |
---|---|---|
committer | Stephen Finucane <stephen@that.guru> | 2019-02-09 17:58:30 +0000 |
commit | b1ebdcd3c68cb3942ad407b39c994b3fa05ad257 (patch) | |
tree | ed628b9f116e82091cd5b2c465cdb37a54f9dd25 /doc/development/tutorials/examples/recipe.py | |
parent | 93081e2fce3a7795ddbc23fb4f1df9224f17d3fc (diff) | |
download | sphinx-git-b1ebdcd3c68cb3942ad407b39c994b3fa05ad257.tar.gz |
doc: Add "recipe" tutorial
This is based on a post from opensource.com [1] and demonstrates how one
can use indexes for cross-referencing and domains to group these indexes
along with domains and roles. The source code was taken from [2] after
getting the license changed [3].
[1] https://opensource.com/article/18/11/building-custom-workflows-sphinx
[2] https://github.com/ofosos/sphinxrecipes
[3] https://github.com/ofosos/sphinxrecipes/issues/1
Signed-off-by: Stephen Finucane <stephen@that.guru>
Diffstat (limited to 'doc/development/tutorials/examples/recipe.py')
-rw-r--r-- | doc/development/tutorials/examples/recipe.py | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/doc/development/tutorials/examples/recipe.py b/doc/development/tutorials/examples/recipe.py new file mode 100644 index 000000000..213d30ff6 --- /dev/null +++ b/doc/development/tutorials/examples/recipe.py @@ -0,0 +1,239 @@ +import docutils +from docutils import nodes +from docutils.parsers import rst +from docutils.parsers.rst import directives +import sphinx +from sphinx import addnodes +from sphinx.directives import ObjectDescription +from sphinx.domains import Domain +from sphinx.domains import Index +from sphinx.domains.std import StandardDomain +from sphinx.roles import XRefRole +from sphinx.util.nodes import make_refnode + + +class RecipeDirective(ObjectDescription): + """A custom directive that describes a recipe.""" + + has_content = True + required_arguments = 1 + option_spec = { + '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)) + + +class IngredientIndex(Index): + """A custom directive that creates an ingredient matrix.""" + + name = 'ing' + localname = 'Ingredient Index' + shortname = 'Ingredient' + + def __init__(self, *args, **kwargs): + super(IngredientIndex, self).__init__(*args, **kwargs) + + def generate(self, docnames=None): + """Return entries for the index given by *name*. + + If *docnames* is given, restrict to entries referring to these + docnames. 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). + + *content* is a sequence of ``(letter, entries)`` tuples, where *letter* + is the "heading" for the given *entries*, usually the starting letter. + + *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 = {} + + 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) + + +class RecipeIndex(Index): + name = 'rcp' + localname = 'Recipe Index' + shortname = 'Recipe' + + def __init__(self, *args, **kwargs): + super(RecipeIndex, self).__init__(*args, **kwargs) + + def generate(self, docnames=None): + """Return entries for the index given by *name*. + + If *docnames* is given, restrict to entries referring to these + docnames. 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). + + *content* is a sequence of ``(letter, entries)`` tuples, where *letter* + is the "heading" for the given *entries*, usually the starting letter. + + *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) + + +class RecipeDomain(Domain): + + name = 'rcp' + label = 'Recipe Sample' + + roles = { + 'reref': XRefRole() + } + + directives = { + 'recipe': RecipeDirective, + } + + indices = { + RecipeIndex, + IngredientIndex + } + + initial_data = { + 'objects': [], # object list + 'obj2ingredient': {}, # 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]) + + def get_objects(self): + for obj in self.data['objects']: + 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] + + if len(match) > 0: + todocname = match[0][0] + targ = match[0][1] + + return make_refnode(builder, fromdocname, todocname, targ, + contnode, targ) + else: + print("Awww, found nothing") + return None + + +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 |