“レシピ”拡張を開発¶
このチュートリアルの目標は、役割、コマンド、ドメインを説明することです。完成後,この拡張を用いてレシピを記述し,文書中の他の場所からレシピを引用することができる.
注釈
このチュートリアルは初めての発表に基づいて opensource.com 原作者の許可を得た場合にはここで提供する.
概要¶
拡張してSphinxに以下を追加したい:
A
recipe
directive レシピを記述する手順が含まれています:contains:
レシピの主成分を強調する選択肢。A
ref
role これは,レシピ自体への交差引用を提供する.A
recipe
domain それは、私たちが上の役割とドメインとインデックスのようなものを一緒に束ねることを可能にする。
そのためには、Sphinxに以下の要素を追加する必要があります。
1つの名前は
recipe
新しいインデックスはトッピングやレシピを参考にすることができます
一つの名は
recipe
そしてそれはrecipe
指令とref
役柄
前提条件.¶
中と同じ設定が必要です the previous extensions それがそうです。今回私たちは recipe.py
それがそうです。
以下は、入手可能なフォルダ構造の例です。
└── source
├── _ext
│ └── recipe.py
├── conf.py
└── index.rst
拡張を作成しています¶
開いている. recipe.py
以下のコードを貼り付け、後で詳細に説明する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | 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 'noindex' not 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, _ 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 '{}.{}'.format('recipe', node.arguments[0])
def get_objects(self):
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]
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 = '{}.{}'.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)
return {
'version': '0.1',
'parallel_read_safe': True,
'parallel_write_safe': True,
}
|
どうなっているのか説明するために、この拡張のすべての部分を一歩ずつ見てみましょう。
指令類.
まず検査するのは RecipeDirective
指示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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 'noindex' not 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)
|
似ていない “Hello world”拡張の開発 そして “TODO”拡張の開発 この指示は派生したものではありません docutils.parsers.rst.Directive
定義されていません run
方法です。逆に派生したのは sphinx.directives.ObjectDescription
そして定義しました handle_signature
そして add_target_and_index
方法です。それは ObjectDescription
クラス、関数、またはレシピ(我々の例では)などを記述するための特別な用途の命令である。より具体的には handle_signature
命令署名の解析を実現し,オブジェクトの名前とタイプをクラスに渡す. add_taget_and_index
ターゲット(リンク)とエントリをそのノードのインデックスに追加する.
We also see that this directive defines has_content
, required_arguments
and option_spec
. Unlike the TodoDirective
directive added in the
previous tutorial, this directive takes a single argument, the
recipe name, and an option, contains
, in addition to the nested
reStructuredText in the body.
索引類.
課題
索引付けの簡単な概要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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, _ 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
|
どちらもある IngredientIndex
そして RecipeIndex
次のような由来から派生しています Index
それがそうです。これらは、インデックスを定義する値タプルを生成するためにカスタム論理を実装する。ご注意ください RecipeIndex
1つの項目しかない簡単なインデックスです。これは、より多くのオブジェクトタイプがコードの一部ではないことをカバーするように拡張される。
この2つの指数はどちらもこの方法を使っています Index.generate()
彼らの仕事をしていますこの方法では,我々のドメインからの情報を組み合わせてソートし,受信したリスト構造をSphinxで返す.これは複雑に見えるかもしれませんが実際は次のようにタプルのリストにすぎません ('tomato', 'TomatoSoup', 'test', 'rec-TomatoSoup',...)
それがそうです。参考にする domain API guide このインタフェースのより多くの情報を表示します。
これらのインデックスページはドメイン名とその組み合わせで参照することができる name
Vbl.使用 ref
役。例えば RecipeIndex
以下のように引用できる :ref:`recipe-recipe
`.
定義域.
Sphinxドメインは,ロール,コマンド,インデックスなどを連携させた専用コンテナである.私たちがここで作ったドメインを見てみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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 '{}.{}'.format('recipe', node.arguments[0])
def get_objects(self):
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]
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 = '{}.{}'.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))
|
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
defining a custom role and are instead reusing the
sphinx.roles.XRefRole
role and defining the
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 recipes
because we currently have only one type of node.
次に私たちが定義したのは initial_data
それがそうです。定義された値 initial_data
コピーすることになります env.domaindata[domain_name]
ドメインの初期データとして,ドメインインスタンスは self.data
それがそうです。ご覧のように私たちは2つのプロジェクトを定義しています initial_data
: recipes
そして recipe2ingredient
それがそうです。これらは、定義されたすべてのオブジェクトのリスト(すなわち、すべてのレシピ)と、仕様成分名をオブジェクトリストにマッピングするハッシュとを含む。我々が対象を命名する方式は,我々の拡張モジュールでは汎用的であり, get_full_qualified_name
方法です。作成された各オブジェクトについて、仕様名は recipe.<recipename>
どこですか <recipename>
文書作成者が対象に指定した名前(レシピ)である.これにより、拡張は、同じ名前を共有する異なるオブジェクトタイプを使用することができる。私たちの対象は規範的な名前と中心的な位置を持っていることが大きな利点だ。我々のインデックスと交差引用コードはこの機能を用いている.
♪the setup
機能
As always vt.的 setup
関数は必要であり,拡張された各部分をSphinxにフックするために用いられる.ちょっと見てみましょう setup
関数はこの拡張に用いる.
1 2 3 4 5 6 7 8 | def setup(app):
app.add_domain(RecipeDomain)
return {
'version': '0.1',
'parallel_read_safe': True,
'parallel_write_safe': True,
}
|
これは私たちが慣れて見ているのとは少し違うように見える。電話がかかっていない add_directive()
甚だしくは add_role()
それがそうです。逆に私たちは1つを呼び出すだけで add_domain()
そしてそうだ standard domain それがそうです。これは私たちが私たちの指示、役割、そしてインデックスを指示自体の一部として登録したからだ。
拡張モジュールの使用¶
今、あなたはプロジェクト全体でこの拡張を使用することができます。例:
Joe's Recipes
=============
Below are a collection of my favourite recipes. I highly recommend the
:recipe:ref:`TomatoSoup` recipe in particular!
.. toctree::
tomato-soup
The recipe contains `tomato` and `cilantro`.
.. recipe:recipe:: TomatoSoup
:contains: tomato cilantro salt pepper
This recipe is a tasty tomato soup, combine all ingredients
and cook.
注意すべき重要な事項は使用です :recipe:ref:
ロールクロス引用他の場所で実際に定義されたレシピ(使用 :recipe:recipe:
指令する。
さらに読む¶
詳細についてはご参照ください docutils 文書と Sphinxの開発拡張 それがそうです。