“TODO”拡張の開発¶
このチュートリアルの目標は、作成された拡張モジュールよりも包括的な拡張モジュールを作成することです “Hello world”拡張の開発 それがそうです。このガイドラインはカスタマイズされたものを作成しているだけです directive 本マニュアルでは,複数の命令,およびカスタムノード,付加配置値,カスタムイベントハンドラを追加している.そこで私たちは todo
文書内に処理すべき項目を含む機能を追加し、これらの項目を中心位置に集中させる拡張。これは似たようなものです sphinxext.todo
Sphinxとともに配布された拡張。
概要¶
拡張してSphinxに以下を追加したい:
A
todo
コマンドには,“TODO”とラベル付けされたものがいくつか含まれており,新たな構成値が設定された場合にのみ出力に表示される.デフォルトの場合、TODOエントリは出力に現れるべきではありません。A
todolist
文書内のすべての処理すべき事項エントリのリストを作成する命令。
そのためには、Sphinxに以下の要素を追加する必要があります。
新しい指令は
todo
そしてtodolist
それがそうです。これらの命令を表す新しい文書木ノードは,一般にも呼ばれる.
todo
そしてtodolist
それがそうです。新しい命令が既存のノードで表現可能なものをいくつか生成するだけであれば,新しいノードを必要としない.新しい構成値
todo_include_todos
(設定値名は、一意を維持するために拡張子で始まる必要があります)、TODOエントリが出力に入るかどうかを制御します。新しいイベント処理プログラム:1つは
doctree-resolved
イベントは、todoとtodolistノードを置き換えるために使用されるenv-merge-info
並行して生成された中間結果をマージし,env-purge-doc
(その理由は後述する)。
前提条件.¶
と同じ “Hello world”拡張の開発 このプラグインはPyPIで配布されませんので、このプラグインを呼び出すSphinxプロジェクトが再び必要となります。既存のプロジェクトをご利用いただくこともできますし、ご利用いただけます sphinx-quickstart それがそうです。
私たちはあなたが単独のソースを使用していると仮定します (source
)と構築 (build
)フォルダ。あなたの拡張ファイルはプロジェクトの任意のフォルダに配置することができます。私たちの例では、以下の操作を実行しましょう。
1つを作ることができます
_ext
挟まれているsource
新しいPythonファイルを作成します
_ext
名前のフォルダtodo.py
以下は、入手可能なフォルダ構造の例です。
└── source
├── _ext
│ └── todo.py
├── _static
├── conf.py
├── somefolder
├── index.rst
├── somefile.rst
└── someotherfile.rst
拡張を作成しています¶
開いている. todo.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 | 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.traverse(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.traverse(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, 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,
}
|
これは詳細に説明された拡張よりもはるかに広い。 “Hello world”拡張の開発 しかし、私たちはすべてのブロックを一歩一歩見て起きていることを説明するつもりだ。
ノード類
ノードクラスから始めましょう
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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)
|
ノードクラスは通常何の操作も実行する必要はなく,そこから定義された標準docutilsクラス継承のみでよい. docutils.nodes
それがそうです。 todo
自己を継承する. Admonition
メモや警告のように処理すべきだからです todolist
ただの“一般”ノードです
注意
重要なのはあなたから離れることなくSphinxを拡張することができますが conf.py
もしあなたがそこで継承のノードを宣言したら、あなたは明らかではないことに出会うだろう。 PickleError
それがそうです。したがって、問題が発生した場合、継承されたノードを別個のPythonモジュールに入れることを確認してください。
詳細については、以下を参照されたい。
指令類.
指令類は通常派生したものである docutils.parsers.rst.Directive
それがそうです。コマンドインタフェースの詳細を紹介した。 docutils documentation ;重要なことは、クラスは構成許可されたタグの属性を持つべきであり、 run
方法,この方法はノードリストを返す.
まず見てみましょう TodolistDirective
指示:
1 2 3 4 | class TodolistDirective(Directive):
def run(self):
return [todolist('')]
|
これはとても簡単で私たちのものを作って返すだけです todolist
ノードクラス。♪the TodolistDirective
コマンド自体には処理すべき内容もなく,処理すべきパラメータもない.これで私たちを連れて行きました TodoDirective
指示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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]
|
ここではいくつかの重要なことを紹介した。まず、ご覧のように、私たちは今サブクラス化しています SphinxDirective
Helper類は通常のものではありません Directive
級友たち。これにより私たちはアクセスできるようになりました build environment instance 使用 self.env
財産です。これがなければかなり複雑なものを使わなければなりません self.state.document.settings.env
それがそうです。そしてリンク先として機能します TodolistDirective
)、 TodoDirective
命令は,ターゲットノードに戻る以外に,1つのターゲットノードを返す必要がある. todo
ノードです。ターゲットID(HTMLでは、これはアンカー名となる)を使用することにより env.new_serialno
これは、呼び出しごとに新しい一意の整数を返すので、一意のターゲット名を生成する。ターゲットノードは、テキスト(最初の2つのパラメータ)が何もない場合にインスタンス化される。
警告ノードを作成する際には,以下の命令を用いて命令の内容本文を解析する. self.state.nested_parse
それがそうです。第1のパラメータはコンテンツ本文を提供し、第2のパラメータはコンテンツオフセットを提供する。3つ目のパラメータは解析結果の親ノードを与えています私たちの例では todo
ノードです。その後、 todo
ノードは環境に追加される.文書全体ですべての処理すべき事項項目のリストを作成できるようにするためには、これが必要である、著者らは todolist
指令する。本例では環境属性は todo_all_todos
(同様に、名前は一意でなければならないので、拡張子をプレフィックスとする)。それは新しい環境を作成する時には存在しないので、指示は確認し、必要に応じて作成しなければならない。処理すべき事項エントリ位置に関する様々な情報は、ノードのコピーと共に格納される。
最終行では,doctreeに入れるべきノード:ターゲットノードと警告ノードを返す.
命令が返されるノード構造は以下のとおりである.
+--------------------+
| target node |
+--------------------+
+--------------------+
| todo node |
+--------------------+
\__+--------------------+
| admonition title |
+--------------------+
| paragraph |
+--------------------+
| ... |
+--------------------+
イベント処理プログラム
イベントハンドラはSphinxの最も強力な特性の1つであり,文書の流れの任意の部分にリンクする方法を提供する.Sphinx自体は多くの事件を提供しています詳細は the API guide ここでサブセットの1つを使用します。
上の例で用いたイベントハンドラを見てみよう.まず第一に env-purge-doc
イベント:
1 2 3 4 5 6 | 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]
|
ソースファイル中の情報を永続的な環境に格納するため,ソースファイルが変更された場合,これらの情報は時代遅れになる可能性がある.このため、各ソースファイルを読み出す前に、その環境記録が消去され、 env-purge-doc
イベントは拡張のために同じ動作を実行する機会を提供する.ここでは,文書名が与えられた文書名にマッチするすべての処理すべき事項をクリアする. todo_all_todos
リストです。文書中に残りの待機事項があれば,解析過程でこれらの処理待ち事項を再追加する.
次の処理プログラムは env-merge-info
イベントは,並列生成の間に用いる.並列構築中には,すべてのスレッドが自分のスレッドを持っているからである. env
はい、複数あります。 todo_all_todos
マージが必要なリスト:
1 2 3 4 5 | 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)
|
別の処理プログラムは doctree-resolved
イベント:
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 | def process_todo_nodes(app, doctree, fromdocname):
if not app.config.todo_include_todos:
for node in doctree.traverse(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.traverse(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, 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)
|
♪the doctree-resolved
事件のある phase 3 (resolving) カスタマイズ解析を許可する.私たちがこのイベントのために作った処理プログラムは少し複雑だ。もし todo_include_todos
構成値(後述)はFALSE,すべて todo
そして todolist
ノードは文書から削除される.もしなければ、 todo
ノードはそれらの位置と方式にとどまるだけである. todolist
ノードは、処理すべきエントリリストに置き換えられ、そのソース位置への逆方向リンクを有する。リスト項目は todo
動的に作成されたEntryおよびdocutilsノード:各エントリは、位置を与えるテキストと、逆参照を有するリンク(斜体ノードを含む参照ノード)とを含む段落である。URIを参照することは sphinx.builders.Builder.get_relative_uri()
これは、使用するビルダから適切なURIを作成し、todoノード(ターゲット)のIDをアンカー名に付加する。
♪the setup
機能
以上のように、 previously vt.的 setup
関数は必要であり,命令をSphinxに挿入するために用いられる.しかし、私たちはまたそれを使用して拡張された他の部分を接続する。私たちのを見てみましょう setup
機能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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,
}
|
この関数における呼び出しは,我々が先に追加したクラスと関数を引用している.各呼び出しは以下の操作を実行する.
add_config_value()
スフィンクスに新しいものを識別すべきだと知ってもらう 配置値todo_include_todos
デフォルト値は、False
(Sphinxはブール値であるとも言われている)。もし3つ目のパラメータが
'html'
配置値がその値を変更すると,HTML文書は完全に再生成される.これは,読み取り(内部バージョン)に影響を与える構成値が必要である. phase 1 (reading) )。add_node()
新しいのを追加します ノード類 構築システムに追加する.これはまた、各サポートされる出力フォーマットのためのアクセス関数を指定することができる。新しいノードが phase 4 (writing) それがそうです。自.自.以来.todolist
ノードは常に置換されている phase 3 (resolving) 何も必要ありませんadd_directive()
新しいのを追加します 指令 名前とカテゴリごとに名前を付けます。最後に、
connect()
1つ追加する イベント処理プログラム その名前が第1のパラメータによって提供されるイベントに追加される。イベントハンドラ関数は,イベントに記述されているいくつかのパラメータで呼び出される.
これで私たちの拡張は完了した。
拡張モジュールの使用¶
前と同じように私たちは conf.py
ファイルです。ここには2つのステップが必要です
追加
_ext
ディレクトリをコピーして Python path Vbl.使用sys.path.append
それがそうです。これはファイルの上部に置かれなければならない。更新または作成
extensions
リストを作成し、拡張ファイル名をリストに追加します。
さらに私たちは todo_include_todos
値を配置する。上述したように、このデフォルトは False
しかし私たちはそれを明確に設定することができる。
例:
import os
import sys
sys.path.append(os.path.abspath("./_ext"))
extensions = ['todo']
todo_include_todos = False
今、あなたはプロジェクト全体でこの拡張を使用することができます。例:
Hello, world
============
.. toctree::
somefile.rst
someotherfile.rst
Hello world. Below is the list of TODOs.
.. todolist::
foo
===
Some intro text here...
.. todo:: Fix this
bar
===
Some more text here...
.. todo:: Fix that
私たちはすでに構成されているからです todo_include_todos
至る False
実際には見られません todo
そして todolist
指令する。しかし,これをtrueに切り替えると,先に述べた出力が見られる.
さらに読む¶
詳細についてはご参照ください docutils 文書と Sphinxの開発拡張 それがそうです。