後日談(最新)
jindex ディレクティブの機能を内容したSphinx拡張を作りました。
本記事は「開発日誌」的な意味合い以外には、特に読む必要はありません。
後日談
使ってみると「分類」に応じて表示場所は変わるけど、同じ分類の中では期待する順番では表示されない。色々調べてみて、挙動にクセはありますがGlossaryにある機能で十分であることがわかりました。
詳細は別途記事を起こして、こちらからリンクを貼る予定です。本記事は「これくらいの労力で拡張ディレクティブを作ることができるよ」のサンプル事例として残していおきます。
※T.B.D.はなくす予定です。
1.はじめに
-
glossaryディレクティブは用語<spc>:<spc>分類とすることで、索引ページで分類に応じた分類をする。ひらがな1文字なら「読み」で分類。 - 同じことを
indexでもやりたい。
という動機で始めたjindexディレクティブを作成するまでの過程をQiitaで共有。
使い方
-
.. index::の代わりに.. jindex::を使用 - 名称の末尾に「
<spc>:<spc>分類名」を追記する。- この「分類名」が「ひらがな一文字」なら、それで分類される。
補足
「2.管渠」「3.作業概要」を見て必要な情報を拾ってください。
2.環境
- Windows10/cygwin64
- Python 3.8.10
- pip 21.2.4(2021-09-07現在の最新)
- spihnx 4.1.2(2021-09-07現在の最新)
3.作業概要
-
indexディレクティブ - データ構造
-
domains/index.pyの改良 -
jindex.pyの作成(T.B.D.) -
conf.pyの編集(T.B.D.) - サンプルと動作確認(T.B.D.)
4.作業詳細
index ディレクティブ
-
sphinxcontrib.ytを眺めた経験から、「classで定義されている」「名前にindexがある」「directiveという名前の何かを継承している」と当たりが付ける。 - Sphinxがインストールされているディレクトリでファイルを下がる。
-
domains/index.pyを見つける。 - これを調べて
indexnodes['entries']のデータ構造が知りたくなる。
実行したコマンド。
egrep -i `class.*index.*directive' */*.py
-
*/は適当に増減する。 - ファイルが多かったり、ディレクトリが深い場合は
findを使う。
データ構造
- それっぽい文字列を予想して、
egrepで探す。 -
addnodes.pyを見つける。
addnodes.py にあった説明
Node for index entries.
This node is created by the
indexdirective and has one attribute,
entries. Its value is a list of 5-tuples of(entrytype, entryname, target, ignored, key).entrytype is one of "single", "pair", "double", "triple".
key is categorization characters (usually a single character) for
general index page. For the details of this, please see also:
:rst:dir:glossaryand issue #2320.
-
ignoredと説明されている場所は。entrytypeがsingleの時は、entrynameの先頭に!があるとmainを設定している。何か意味があるのかもしれない。
domains/index.py の改良
-
cp -p index.py index.py.origでオリジナルを保存 - print文でentryデータの内容を確認しつつ、改良を使える。
diff -u で出力した変更内容は次の通り
--- index.py.orig 2021-09-05 01:23:03.208772600 +0900
+++ index.py 2021-09-07 01:03:51.222260000 +0900
@@ -8,6 +8,7 @@
:license: BSD, see LICENSE for details.
"""
+import re
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Tuple
from docutils import nodes
@@ -87,8 +88,15 @@
indexnode['entries'] = []
indexnode['inline'] = False
self.set_source_info(indexnode)
+ entries = []
for entry in arguments:
- indexnode['entries'].extend(process_index_entry(entry, targetnode['ids'][0]))
+ entries.extend(process_index_entry(entry, targetnode['ids'][0]))
+ #print(entries)
+ for entry in entries:
+ parts = re.split(' +: +', entry[1]) + [None]
+ # entry: (indextype, text, target, 'main' or '', index_key)
+ indexnode['entries'].extend([(entry[0], parts[0], entry[2], entry[3], parts[1])])
+ #print(indexnode['entries'])
return [indexnode, targetnode]
-
process_index_entry()で作られたデータを一端作業用の変数にいれて、entryname(コード内ではentry[1])の内容を見てデータを作り直す。
jindex.py の作成(T.B.D.)
※ディレクトリは読み替えてください。
関連ファイルの整理
動くコードがあるので、これをSphinx拡張に仕立て上げる。
その1
cd /usr/local/lib/python3.8/site-packages/sphinx
mv domains/index.py domains/index.py.new
cp -p domains/index.py.orig domains/index.py
cp domains/index.py.new ~/sphinx/_ext/jindex.py
その2
cd
cd ~/sphinx/_ext/
svn add jindex.py
svn ci -m "copied from site-packages/sphinx/domains/index.py"
vi jindex.py
jindex.py 編集内容
-
不要な
importを削除 -
Class IndexをClass JIndexに変更
class JIndexDirective(SphinxDirective):
- 末尾に
: 文字列があれば取り出して分類名として使う
for entry in entries:
parts = re.split(' +: +', entry[1]) + [None]
# entry: (indextype, text, target, 'main' or '', index_key)
indexnode['entries'].extend([(entry[0], parts[0], entry[2], entry[3], parts[1])])
-
setup()は このJIndexを使うように変更
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_directive('jindex', JIndexDirective)
return {
'version': '4.1.2k1',
'parallel_read_safe': True,
'parallel_write_safe': True,
}
編集した jindex.py の内容
import re
from typing import TYPE_CHECKING, Any, Dict, List, Tuple
from docutils import nodes
from docutils.nodes import Node
from docutils.parsers.rst import directives
from sphinx import addnodes
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import process_index_entry
from sphinx.util.typing import OptionSpec
if TYPE_CHECKING:
from sphinx.application import Sphinx
class JIndexDirective(SphinxDirective):
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec: OptionSpec = {
'name': directives.unchanged,
}
def run(self) -> List[Node]:
arguments = self.arguments[0].split('\n')
if 'name' in self.options:
targetname = self.options['name']
targetnode = nodes.target('', '', names=[targetname])
else:
targetid = 'index-%s' % self.env.new_serialno('index')
targetnode = nodes.target('', '', ids=[targetid])
self.state.document.note_explicit_target(targetnode)
indexnode = addnodes.index()
indexnode['entries'] = []
indexnode['inline'] = False
self.set_source_info(indexnode)
entries = []
for entry in arguments:
entries.extend(process_index_entry(entry, targetnode['ids'][0]))
#print(entries)
for entry in entries:
parts = re.split(' +: +', entry[1]) + [None]
# entry: (indextype, text, target, 'main' or '', index_key)
indexnode['entries'].extend([(entry[0], parts[0], entry[2], entry[3], parts[1])])
#print(indexnode['entries'])
return [indexnode, targetnode]
def setup(app: "Sphinx") -> Dict[str, Any]:
app.add_directive('jindex', JIndexDirective)
return {
'version': '4.1.2k1',
'parallel_read_safe': True,
'parallel_write_safe': True,
}