diff options
Diffstat (limited to 'doc/development/tutorials/examples')
-rw-r--r-- | doc/development/tutorials/examples/README.rst | 11 | ||||
-rw-r--r-- | doc/development/tutorials/examples/autodoc_intenum.py | 58 | ||||
-rw-r--r-- | doc/development/tutorials/examples/helloworld.py | 19 | ||||
-rw-r--r-- | doc/development/tutorials/examples/recipe.py | 159 | ||||
-rw-r--r-- | doc/development/tutorials/examples/todo.py | 135 |
5 files changed, 382 insertions, 0 deletions
diff --git a/doc/development/tutorials/examples/README.rst b/doc/development/tutorials/examples/README.rst new file mode 100644 index 0000000..2b9c01b --- /dev/null +++ b/doc/development/tutorials/examples/README.rst @@ -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. diff --git a/doc/development/tutorials/examples/autodoc_intenum.py b/doc/development/tutorials/examples/autodoc_intenum.py new file mode 100644 index 0000000..75fa204 --- /dev/null +++ b/doc/development/tutorials/examples/autodoc_intenum.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from enum import IntEnum +from typing import TYPE_CHECKING, Any + +from sphinx.ext.autodoc import ClassDocumenter, bool_option + +if TYPE_CHECKING: + from docutils.statemachine import StringList + + from sphinx.application import Sphinx + + +class IntEnumDocumenter(ClassDocumenter): + objtype = 'intenum' + directivetype = ClassDocumenter.objtype + priority = 10 + ClassDocumenter.priority + option_spec = dict(ClassDocumenter.option_spec) + option_spec['hex'] = bool_option + + @classmethod + def can_document_member(cls, + member: Any, membername: str, + isattr: bool, parent: Any) -> bool: + try: + return issubclass(member, IntEnum) + except TypeError: + return False + + def add_directive_header(self, sig: str) -> None: + super().add_directive_header(sig) + self.add_line(' :final:', self.get_sourcename()) + + def add_content(self, + more_content: StringList | None, + no_docstring: bool = False, + ) -> None: + + super().add_content(more_content, no_docstring) + + source_name = self.get_sourcename() + enum_object: IntEnum = self.object + use_hex = self.options.hex + self.add_line('', source_name) + + for the_member_name, enum_member in enum_object.__members__.items(): + the_member_value = enum_member.value + if use_hex: + the_member_value = hex(the_member_value) + + self.add_line( + f"**{the_member_name}**: {the_member_value}", source_name) + self.add_line('', source_name) + + +def setup(app: Sphinx) -> None: + app.setup_extension('sphinx.ext.autodoc') # Require autodoc extension + app.add_autodocumenter(IntEnumDocumenter) diff --git a/doc/development/tutorials/examples/helloworld.py b/doc/development/tutorials/examples/helloworld.py new file mode 100644 index 0000000..d6d81fd --- /dev/null +++ b/doc/development/tutorials/examples/helloworld.py @@ -0,0 +1,19 @@ +from docutils import nodes +from docutils.parsers.rst import Directive + + +class HelloWorld(Directive): + + def run(self): + paragraph_node = nodes.paragraph(text='Hello World!') + return [paragraph_node] + + +def setup(app): + app.add_directive("helloworld", HelloWorld) + + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/doc/development/tutorials/examples/recipe.py b/doc/development/tutorials/examples/recipe.py new file mode 100644 index 0000000..c7ebf2a --- /dev/null +++ b/doc/development/tutorials/examples/recipe.py @@ -0,0 +1,159 @@ +from collections import defaultdict + +from docutils.parsers.rst import directives + +from sphinx import addnodes +from sphinx.directives import ObjectDescription +from sphinx.domains import Domain, Index +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) + return sig + + def add_target_and_index(self, name_cls, sig, signode): + signode['ids'].append('recipe' + '-' + sig) + if 'contains' in self.options: + 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 index that creates an ingredient matrix.""" + + name = 'ingredient' + localname = 'Ingredient Index' + shortname = 'Ingredient' + + def generate(self, docnames=None): + content = defaultdict(list) + + 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) + + # 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) + + # 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)) + + # convert the dict to the sorted list of tuples expected + content = sorted(content.items()) + + return content, True + + +class RecipeIndex(Index): + """A custom index that creates an recipe matrix.""" + + name = 'recipe' + localname = 'Recipe Index' + shortname = 'Recipe' + + def generate(self, docnames=None): + content = defaultdict(list) + + # sort the list of recipes in alphabetical order + recipes = self.domain.get_objects() + recipes = sorted(recipes, key=lambda recipe: recipe[0]) + + # 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, _priority in recipes: + content[dispname[0].lower()].append( + (dispname, 0, docname, anchor, docname, '', typ)) + + # convert the dict to the sorted list of tuples expected + content = sorted(content.items()) + + return content, True + + +class RecipeDomain(Domain): + + name = 'recipe' + label = 'Recipe Sample' + roles = { + 'ref': XRefRole(), + } + directives = { + 'recipe': RecipeDirective, + } + indices = { + RecipeIndex, + IngredientIndex, + } + initial_data = { + 'recipes': [], # object list + 'recipe_ingredients': {}, # name -> object + } + + def get_full_qualified_name(self, node): + return f'recipe.{node.arguments[0]}' + + def get_objects(self): + yield from self.data['recipes'] + + 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 add_recipe(self, signature, ingredients): + """Add a new recipe to the domain.""" + name = f'recipe.{signature}' + anchor = f'recipe-{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) + + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/doc/development/tutorials/examples/todo.py b/doc/development/tutorials/examples/todo.py new file mode 100644 index 0000000..15368f4 --- /dev/null +++ b/doc/development/tutorials/examples/todo.py @@ -0,0 +1,135 @@ +from docutils import nodes +from docutils.parsers.rst import Directive + +from sphinx.locale import _ +from sphinx.util.docutils import SphinxDirective + + +class todo(nodes.Admonition, nodes.Element): + pass + + +class todolist(nodes.General, nodes.Element): + pass + + +def visit_todo_node(self, node): + self.visit_admonition(node) + + +def depart_todo_node(self, node): + self.depart_admonition(node) + + +class TodolistDirective(Directive): + + def run(self): + return [todolist('')] + + +class TodoDirective(SphinxDirective): + + # this enables content in the directive + has_content = True + + def run(self): + targetid = 'todo-%d' % self.env.new_serialno('todo') + targetnode = nodes.target('', '', ids=[targetid]) + + todo_node = todo('\n'.join(self.content)) + todo_node += nodes.title(_('Todo'), _('Todo')) + self.state.nested_parse(self.content, self.content_offset, todo_node) + + if not hasattr(self.env, 'todo_all_todos'): + self.env.todo_all_todos = [] + + self.env.todo_all_todos.append({ + 'docname': self.env.docname, + 'lineno': self.lineno, + 'todo': todo_node.deepcopy(), + 'target': targetnode, + }) + + return [targetnode, todo_node] + + +def purge_todos(app, env, docname): + if not hasattr(env, 'todo_all_todos'): + return + + env.todo_all_todos = [todo for todo in env.todo_all_todos + if todo['docname'] != docname] + + +def merge_todos(app, env, docnames, other): + if not hasattr(env, 'todo_all_todos'): + env.todo_all_todos = [] + if hasattr(other, 'todo_all_todos'): + env.todo_all_todos.extend(other.todo_all_todos) + + +def process_todo_nodes(app, doctree, fromdocname): + if not app.config.todo_include_todos: + for node in doctree.findall(todo): + node.parent.remove(node) + + # Replace all todolist nodes with a list of the collected todos. + # Augment each todo with a backlink to the original location. + env = app.builder.env + + if not hasattr(env, 'todo_all_todos'): + env.todo_all_todos = [] + + for node in doctree.findall(todolist): + if not app.config.todo_include_todos: + node.replace_self([]) + continue + + content = [] + + for todo_info in env.todo_all_todos: + para = nodes.paragraph() + filename = env.doc2path(todo_info['docname'], base=None) + description = ( + _('(The original entry is located in %s, line %d and can be found ') % + (filename, todo_info['lineno'])) + para += nodes.Text(description) + + # Create a reference + newnode = nodes.reference('', '') + innernode = nodes.emphasis(_('here'), _('here')) + newnode['refdocname'] = todo_info['docname'] + newnode['refuri'] = app.builder.get_relative_uri( + fromdocname, todo_info['docname']) + newnode['refuri'] += '#' + todo_info['target']['refid'] + newnode.append(innernode) + para += newnode + para += nodes.Text('.)') + + # Insert into the todolist + content.append(todo_info['todo']) + content.append(para) + + node.replace_self(content) + + +def setup(app): + app.add_config_value('todo_include_todos', False, 'html') + + app.add_node(todolist) + app.add_node(todo, + html=(visit_todo_node, depart_todo_node), + latex=(visit_todo_node, depart_todo_node), + text=(visit_todo_node, depart_todo_node)) + + app.add_directive('todo', TodoDirective) + app.add_directive('todolist', TodolistDirective) + app.connect('doctree-resolved', process_todo_nodes) + app.connect('env-purge-doc', purge_todos) + app.connect('env-merge-info', merge_todos) + + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } |