“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. 1つを作ることができます _ext 挟まれている source

  2. 新しい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 ただの“一般”ノードです

注釈

多くの拡張は、自分のノードクラスを作成する必要がなく、提供されたノードをうまく使用することができる docutils そして Sphinx それがそうです。

注意

重要なのはあなたから離れることなく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つのステップが必要です

  1. 追加 _ext ディレクトリをコピーして Python path Vbl.使用 sys.path.append それがそうです。これはファイルの上部に置かれなければならない。

  2. 更新または作成 extensions リストを作成し、拡張ファイル名をリストに追加します。

さらに私たちは todo_include_todos 値を配置する。上述したように、このデフォルトは False しかし私たちはそれを明確に設定することができる。

例:

import os
import sys

sys.path.append(os.path.abspath("./_ext"))

extensions = ['todo']

todo_include_todos = False

今、あなたはプロジェクト全体でこの拡張を使用することができます。例:

index.rst
Hello, world
============

.. toctree::
   somefile.rst
   someotherfile.rst

Hello world. Below is the list of TODOs.

.. todolist::
somefile.rst
foo
===

Some intro text here...

.. todo:: Fix this
someotherfile.rst
bar
===

Some more text here...

.. todo:: Fix that

私たちはすでに構成されているからです todo_include_todos 至る False 実際には見られません todo そして todolist 指令する。しかし,これをtrueに切り替えると,先に述べた出力が見られる.

さらに読む

詳細についてはご参照ください docutils 文書と Sphinxの開発拡張 それがそうです。