summaryrefslogtreecommitdiffstats
path: root/doc/development/tutorials/examples
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development/tutorials/examples')
-rw-r--r--doc/development/tutorials/examples/README.rst11
-rw-r--r--doc/development/tutorials/examples/autodoc_intenum.py58
-rw-r--r--doc/development/tutorials/examples/helloworld.py19
-rw-r--r--doc/development/tutorials/examples/recipe.py159
-rw-r--r--doc/development/tutorials/examples/todo.py135
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,
+ }