Texdocは与えられたキーワードに対して適切なドキュメントを表示するため、TEXMFツリーの中で発見したドキュメントに対して順位付けを行います。この順位付けのために用いられるスコアは、公式ドキュメントによると「単純なヒューリスティクスに基づいて計算される」ことになっているのですが、あまり詳しいことは述べられていません。そこで、Texdocの内部で具体的にどのようなスコア計算が行われているのか、Luaで書かれたソースコードから追ってみようと思います。
追跡の方法
実際にスコア計算のロジックについて見ていく前に、どのようにしてTexdocのソースコード構成を把握し、またその挙動について追跡を行ったのかということを簡単に説明しておきます。
ソースコードの見つけ方と構成
Texdocの実装はtexluaで動作するように書かれた2,300行程度のLuaスクリプトです1。Texdocを実行する際に、一番最初に読まれるのはtexdoc.tluというファイルで、その所在はkpsewhichで簡単に確認できます。
$ kpsewhich texdoc.tlu
Texdocのソース・スクリプトファイルは、すべてこのtexdoc.tluと同じディレクトリに水平に配置されていますが、実際にコードを読んでみると、これらのファイル(モジュール)は対等な関係ではありません。スコア計算と密接に関連しているファイルだけを取り出してその構成をまとめると、概ね次のようになっています。
- texdoc.tlu: main.tluのラッパー(kpseライブラリの初期化)
- main.tlu: メインプログラム
- config.tlu: 設定の管理
- search.tlu: ドキュメントの検索と順位付け
- score.tlu: スコア計算
- view.tlu: ドキュメントの表示(順位付け結果の使用)
- main.tlu: メインプログラム
デバッグ情報
ソースコードを読むだけでプログラムの挙動を完全に把握するのは大変なので、実際にTexdocを動かしてその挙動を確かめながら作業をすすめることも大切です。その際に便利なのがTexdocの-dオプションで、これを付けてTexdocを実行すると内部で行われているスコア計算の途中経過などをターミナルに吐き出させることができます。
スコア計算
スコアの使われ方
Texdocのスコアは、基本的には見つかったドキュメントの順位付けのために用いられます。この順位付けはdocfile_order()を比較関数とするソート処理によって実現していますが、この関数は以下の項目をこの順序で検査して初めて差があったときに上位のドキュメントを決定します。
- スコア
- 拡張子
- ファイル名(辞書順)
- ファイルの位置
すなわち、この順位付けにおいて最も重要な項目がスコアで、その値が高ければその時点で優先的に表示されることが確定します。次に重要なのが拡張子で、内部変数ext_listの中で前に出てくるものほど優先されることになります。ext_listのデフォルト値(built-in)は
ext_list = "pdf", "htm", "html", "txt", "ps", "dvi", ""
です。そして、スコアと拡張子を比較しても差がない場合はファイル名やファイル位置に基づいて順位が決定されます。
ところで、スコアにはもう1つの用途があり、Texdocがドキュメントの品質を評価する際にも利用されます。あるドキュメントのスコアが $s$ のとき、このドキュメントの品質は次のように評価されます。
- good: $s > 0$
- bad: $-100 < s \leq 0$
- killed: $s \leq -100$
Texdocでは-lオプションを付けた場合は基本的に“good”と判定されたドキュメントのみがリスト表示されます。Texdocに“bad”と評価されたドキュメントは、-sオプションを付けたときに限りリストに載せられることになります。
スコア計算の手順
スコア計算は、基本的にはユーザ入力の「キーワード」と各ドキュメントの名称のパターンマッチによって行われます。この際に用いられるドキュメントの名称は、具体的にはTexdocの用語で「ショートネーム」と呼ばれるファイル名とその直上ディレクトリの組み合わせ(<directory name>/<file name>の形)を小文字化したものです。
各ドキュメントには、始めに初期値としてスコア-10が与えられます。そして、以下に説明する各段階でそのショートネームやメタデータに応じたスコアが加点または減点されて、最終的なスコアが決定されます。
1. パターンに基づくスコア付け:ヒューリスティック・スコアの計算
Texdocのスコア計算は、まずドキュメントのショートネームとキーワードのパターンマッチによってスコアの基礎点とも言えるヒューリスティック・スコアを算出するところから始まります。
ヒューリスティック・スコアは-10から10の範囲に収まる数値で、以下この節内で列挙する条件に基づいて決定されます。ただし、ユーザ入力のキーワードがエイリアスであった場合、ヒューリスティック・スコアは原則として10になるようです。
- ショートネームまたはファイル名がキーワードと完全に一致し、ロケール2も一致する場合:5点
- ショートネームまたはファイル名がキーワードと完全に一致する場合:4点
- ショートネームまたはファイル名にキーワードが含まれる場合:1点
ここで「ショートネームまたはファイル名がキーワードと完全に一致」しているかの判定(is_exact()関数)では、拡張子に対しては十分な柔軟性があります3。一方で「ショートネームまたはファイル名にキーワードが含まれる」かどうかの判定(is_subword()関数)では、キーワードがデリミタ4で区切られて存在している必要があります(これらのルールは以降の項目でも同様です)。
さて、ユーザ入力のキーワードにスラッシュが含まれていない場合は、キーワードにsuffix_listに含まれる接尾辞をそれぞれ取り付けた「派生キーワード」が生成され、これらについてもパターンマッチが試行されます。
- ショートネームまたはファイル名が派生キーワードと完全に一致する場合:3点
- ショートネームまたはファイル名に派生キーワードが含まれる場合:2点5
なお、TeX Live 2017に付属するtexdoc.cnfによってsuffix_listは次のように設定されています。
suffix_list = "doc", "-doc", "_doc", ".doc", "/doc", "manual", "/manual", "-manual", "userguide", "/user_guide", "-guide", "-user", "-man", "notes", "-info", "ref"
- 上記の条件をすべて満たさない場合:-10点
ドキュメントがここまでに挙げた条件を満たしている場合、それぞれ対応するスコア(複数の条件を満たす場合はその最大値)が与えられるわけですが、最終的なヒューリスティック・スコアが決定される前に少し調整が入ります。
まず、ここまでで正のスコアを得ているドキュメントのうち、拡張子がbadext_listに登録されているものと、ベースネームがbadbasename_listに登録されているものは、それまでの獲得スコアとは無関係にスコアが0.1に下げられます。ここでbadext_listとbadbasename_listのデフォルト値(built-in)は次のようになっています。
badext_list = "txt", ""
badbasename_list = "readme", "00readme"
一方、ドキュメントの直上ディレクトリがキーワードと完全に一致する場合は、1.5点のボーナス・スコアが加算されます。
2. texlive.tlpdbに基づくスコア付け
もしヒューリスティック・スコアが最低の-10となった場合、TeX Liveのパッケージデータベースであるtexlive.tlpdbの情報に基づいて救済のスコアが与えられるようです。
例えば、一般的なTexdocの設定において
$ texdoc jsarticle
とするとjsclasses/jsclasses.pdfという、キーワード “jsarticle” をまったく含まないドキュメントが表示されますが、これはこの救済スコアが与えられる影響です。
しかし、救済スコアは必ず0以下の値となるため、正のヒューリスティック・スコアが付くドキュメントより優先順位が高くなる可能性は大変低いと考えられます。
3. メタデータによる加点
各ドキュメントについて、texlive.tlpdbからドキュメントの種類に関する属性情報(メタデータ)が得られた場合には、これまでの得点に1.5点のボーナスが追加されます。ただし、ドキュメントの種類が “readme” であった場合はボーナスは0.1点です。
4. texdoc.cnfに基づく調整
texdoc.cnfに記述可能な設定項目にadjscoreというものがあり、これよってユーザが直接スコア計算に介入することが可能です。Texdocではスコア計算の最後の過程として、これらの設定に基づくスコアの調整を行います。
texdoc.cnfにおけるadjscoreの設定シンタックスは簡単には次のようになっています。
adjscore <pattern> = <score>
adjscore(<keyword>) <pattern> = <score>
(<keyword>)が指定されていないものは、すべてのキーワードに対して適用されるグローバルな設定で、<pattern>がショートネーム中に含まれる場合に対応する<score>が加算されます。一方、(<keyword>)が指定されている場合は、ユーザ入力のキーワードが<keyword>と一致する場合に限りスコアの調整が行われます。
以下に、参考のためTeX Live 2017に含まれるtexdoc.cnfのadjscore設定を掲載しておきます。
## General adjustments
# Makefile are never documentation, just as documents in src or source subdir
# -1000 should be enough to kill them
adjscore /Makefile = -1000
adjscore /src/ = -1000
adjscore /source/ = -1000
# licence files aren't very likely to contain relevant documentation, but it
# feels wrong to totally kill them
adjscore copying = -10
adjscore license = -10
adjscore gpl = -10
# tex-virtual-academy provides a lot of spurious matches
adjscore /tex-virtual-academy-pl/ = -50
# test and example files are not likely the best documentation
adjscore test = -3
adjscore tests = -3
adjscore example = -3
adjscore examples = -3
adjscore sample = -3
adjscore samples = -3
adjscore /images/ = -3
# readme's usually get a negative score because they have a bad extension,
# but they're still slightly better than other results with negative scores
adjscore readme = 0.1
# uncomment this to make the man pages have a greater priority
#adjscore .man1. = 5
#adjscore .man5. = 5
## Specific adjustments
# 'texdoc' may look like "tex's documentation" but it isn't
# similar problem with 'tex-*'
adjscore(tex) texdoc = -10
adjscore(tex) tex-gyre = -10
adjscore(tex) tex-ps = -10
# avoid too many results to be shown for 'latex'
# package names
adjscore(latex) cjw-latex = -10
adjscore(latex) cweb-latex = -10
adjscore(latex) duerer-latex = -10
adjscore(latex) guide-to-latex = -10 # only useful with the book
adjscore(latex) latex-web-companion = -10
adjscore(latex) ocr-latex = -10
adjscore(latex) tufte-latex = -10
# file names
adjscore(latex) Content_LaTeX_Package_Demo = -10
adjscore(latex) example_latex = -10
adjscore(latex) test_latex = -10
# beamer
adjscore(beamer) beamer-tut-pt/tutorialbeamer = +10
adjscore(beamer) beamer-FUBerlin = -3
adjscore(beamer) beamer-tut-pt = -10
adjscore(beamer) presentations = -10
# misc
adjscore(context) circuitikz = -10
adjscore(context) /gnuplot/ = -3
adjscore(context) context.man1 = +2
adjscore(symbols) /staves/ = -5
# catalogue info missing in the tlcontrib version of the package :-(
adjscore(pgf) pgfmanual.pdf = +5
# context version is found first
adjscore(fixme) /third/ = -6
画期的なTexdoc
これまで見てきたように、Texdocは表示するドキュメントを決定するにあたり、かなりad-hocで複雑なスコア計算をしていることがわかりました。Texdocは世界のTeX Liveユーザがドキュメントを探すのに使用しているツールなので、(La)TeXパッケージの開発者としては同梱ドキュメントの表示における優先順位をコントロールしたいところですが、現状のTexdocにはこれを陽に制御する方法はなく6、またスコア計算が複雑でその挙動を正確に推定するのも容易ではありません。
そもそも、Texdocがなぜこれほど複雑なスコア計算をしなければならないかといえば、世の中のドキュメントの中に「本質的」でないものがあまりにも多いからです。よくよく考えてみれば、本当に表示されるべき本質的なドキュメントといえば、アレしかないはず……そう、ナントカです。
というわけで、どのようなキーワードに対しても本当に表示されるべき本質的なドキュメントを表示する画期的なTexdocを開発してみました。
使い方は本家Texdocと同様で、次のようにすればただちに真に本質的なドキュメントが表示されるはずです。
$ texlua sctexdoc.lua <keyword>
なお、オプションの体系も本家Texdocとまったく同様です。☃
-
Data.tlpdb.luaというデータベースのキャッシュ・ファイルは除きます。 ↩︎
-
ショートネーム中に
-<lang>の形で言語の略称が含まれる場合に、その略称からそのドキュメントのロケールが判断されます。 ↩︎ -
具体的には、まず拡張子を除いたベースネームとキーワードの直接比較が行われ、一致しない場合にはパラメタ
ext_listに含まれる拡張子をキーワードにそれぞれ結合して一致するものがないかが調べられます。 ↩︎ -
ここでのデリミタはLuaの正規表現において
%pにマッチする記号を指します。 ↩︎ -
派生キーワードの完全一致はキーワードとの完全一致よりも1点低い一方で、派生キーワードの部分一致はキーワードとの部分一致よりも1点高いのは不可解ですが、理由は不明です。おそらくテキトーに決められたのでしょう。ヒューリスティックなので。 ↩︎
-
ユーザ・サイドではエイリアスやスコア補正の設定を行うことで実現可能です。 ↩︎