インラインコードの自動リンクツールを開発したい
文章中のバッククォートで囲まれたコードへのリンク貼りが、けっこう大きな作業負担になりやすいので、ここの自動リンクツールを開発したいです。
作るのは私がやるつもりではありますが、まずは要件定義と懸念事項の洗い出しをしたいです。
- 基本的にはGLOBAL_QUALIFY_LIST.txtに登録されたものを自動リンクする
- すでに手動リンクされているものは自動リンクの対象外にする
std::move()のような、1引数版が<utility>、3〜4引数版が<algorithm>で定義されるようなものは、* std::move(_1)[link /reference/utility/move.md]のように記述できるようにすることを目指す
型の推論をするとかは、少なくとも初期バージョンではむずかしい気がしています。現在は、std::vectorの変数はv、v1、v2みたいに、グローバル修飾である程度決め打ちして対応しています。
それとこのタスクと関連して、グローバル修飾に無制限に登録できるよう、コードブロック中に登場する識別子だけ使う、という修正をどこかでやる必要があります。いまは個数の制限自体はとくにないのですが、そのうち何らかの弊害がでそうで怖いところではあります。
そのほか、考えられそうな要件と懸念があれば教えてください。
同じクラス (同じ階層) のメンバ関数へのリンクも自動化しないといけないですね。 これはもしかしたら、ファイルの探索とかしないといけないかも?
型の推論をするとかは、少なくとも初期バージョンではむずかしい気がしています。現在は、
std::vectorの変数はv、v1、v2みたいに、グローバル修飾である程度決め打ちして対応しています。
同じクラス (同じ階層) のメンバ関数へのリンクも自動化しないといけないですね。
v.begin() etc. .だけでなく任意のメンバ関数に対して型推論なしで自動リンクにすると、誤爆が発生する頻度が上がると思うのですが、
- 実際に誤爆があった時にそれらを個別に抑制する方法は提供しますか?
- 先に手動でリンクをつけてしまえば良い? → コードブロック内にある場合はどうするか? またコードブロック外でも 1つの記事内or段落内で繰り返し登場する場合に、全てリンクするのか?
- 新しい記事の場合は編集時に誤ったリンク付けがないか確認すれば良いですが、古い記事はどうしますか?
- 例えば新しいリンクは opt-in にして、明示的に有効化する指定を記事内に埋め込まない限りは発動しない様にする? → これだと面倒だし将来的に完全に移行した後に指定が残り続ける。
- 逆に opt-out にして、古い記事には無効化指定を一括で追加する。確認できた記事から順番に無効化指定を外していく。
- 或いは誤爆があるのは仕方がないことにして放置する。
これはもしかしたら、ファイルの探索とかしないといけないかも?
crsearch で使っているデータベースを流用できないでしょうか。
文章中のバッククォートで囲まれたコードへのリンク貼り 基本的にはGLOBAL_QUALIFY_LIST.txtに登録されたものを自動リンク
経験則として、サンプルコード中では名前空間が原則明示(例:std::tuple)され、文章中のコードブロックでは省略表記(例:tuple)が多いようです。
現時点では GLOBAL_QUALIFY_LIST.txt に何を登録すべきか/すべきでないかも曖昧ですが、以下の懸念が生まれそうです。
- 名前空間の明示版と省略版を二重登録してゆく?(例:
std::tupleとtuple) - 名前空間を除く識別子名が短い一般名詞(例:
std::ranges::to,std::execution::on)の場合、名前空間なしにリスト登録すると問題が生じないか?(意図しない自動リンクが張られるリスクが高い) - 長い名前空間名(例:
std::ranges::views,std::execution)に対して、名前空間の表記ルールを明文化する必要がある?(エイリアス定義using ex = std::exectionしてよい?)
個人的には、本件については中立の立場です。執筆者の作業負担となるのは理解しつつも、その名前が何を指すかの機械判別は困難ですし、適切なリンクを張る作業を通じ仕様理解が進むという側面もあります。
安全側に倒すと、名前空間指定ありの完全修飾名のみを登録する方針となりそうですが、本文中のコードブロックでは(名前空間省略されるため)ほとんど役に立たない気がしています。
グローバル修飾を使う点について、安全に関する懸念があると理解しました。 全体を再確認するのも大変でしょうから、ここでは安全側に倒した方針として、以下を提案します。
- インラインコードのリンク用に
GLOBAL_INLINE_QUALIFY_LIST.txtを用意して、GLOBAL_QUALIFY_LIST.txtと分離して管理するmoveとかforwardとかの幅広く使うものを定義
- ページ内にインラインコードのリンクを定義できるようにする
- ページ先頭の
[meta cpp]とかのうしろにでもbegin[inlink begin.md]みたいに指定 (主に同じクラスのメンバ関数を指定する用)
- ページ先頭の
- 指定したコード修飾の影響範囲を調べるツールを用意する (
check_qualify_use.py --inline "move"(--inlineの反対は--block))- site_generator / markdown_to_htmlの対象コード抽出と同じコードで検出する (リポジトリが違うのでコードのコピーにはなりますが)
- 修飾対象となるファイル名と行番号、行の内容を出力する
古い記事・新しい記事関係なく全体に適用するつもりではありますが、誤爆の懸念への対策としては、以下のような作業フローにすれば十分かなと思います。
GLOBAL_INLINE_QUALIFY_LIST.txtには少しずつ登録し、- 影響範囲をツールでチェック
これでたとえば、v.begin()に対してvalarrayのファイルが検出されたら怪しいとわかります。
また、現時点でのGLOBAL_QUALIFY_LIST.txtの運用についてですが、
- 名前空間のありなし
- 両方登録する
- 名前空間ありは、主にサンプルコード用
- 名前空間なしは、主に宣言用
- 名前空間の別名ルール
namespace fs = std::filesystem;とかもしてあるので、ライブラリごとに決めてしまってよいかと思います
途中でコメント投稿してしまったので編集しました
GLOBAL_QUALIFY_LIST.txtですが、コメントでルールを書けるようにしたいです。
スペースを取り除いた行頭#をコメントとして扱おうかと思いますが、これに異論ある方はいますでしょうか?
ここになにを書くかは継続議論しましょう。 ひとまずツールを整備をしたいです。
- https://github.com/cpprefjp/site_generator/pull/91
Pull Requestで置いてあります。
自分としては賛成なのですが、間違ってリンクされた場合に簡単に修正できることが大切だと思っています。
最善策ではないとは思いますが、例えばMarkdownの記事に直接リンクを自動で追加するようにし、そのリンクが間違っていれば、その行に変更を加える、などはどうでしょうか。
たとえば
* v.begin[autolink /reference/vector/vector.md]
のリンクを消したければ、
# v.begin[autolink /reference/vector/vector.md]
にする、みたいな感じです。
自動生成は基本的に反対です。 管理するものが大量に増え、人によって空行を挿入したりしなかったり、書き方がバラバラになるリスクがあり、管理コストが大幅に増加してしまいます。 将来的にそのリンクだけで全ページに100行とかが挿入されてしまって編集の邪魔にもなります。
グローバル修飾リストは、実装としては個別ページの修飾のうしろにくっつける形になるので、個別ページの方が優先されます。 なので個別ページで修飾を書けばグローバル修飾リストを上書きできるはずです。
ここまで出てきてなさそうなので、ズレてたら無視していただいていいのですが、
例えば
https://github.com/cpprefjp/site/blob/master/reference/chrono/duration.md?plain=1
## メンバ関数
### 構築/コピー/破棄
| 名前 | 説明 | 対応バージョン |
|-------------------------------------------|-----------------------|----------------|
| [`(constructor)`](duration/op_constructor.md) | コンストラクタ | C++11 |
| `~duration() = default;` | デストラクタ | C++11 |
| `operator=(const duration&) = default;` | 代入演算子 | C++11 |
### 観測
| 名前 | 説明 | 対応バージョン |
|--------------------------------|--------------|----------------|
| [`count`](duration/count.md) | 値を取得する | C++11 |
### 算術演算
| 名前 | 説明 | 対応バージョン |
|--------------------------------------------------|--------------------------|----------------|
| [`operator+`](duration/op_unary_plus.md) | 正の符号 | C++11 |
| [`operator-`](duration/op_unary_minus.md) | 負の符号 (符号反転する) | C++11 |
| [`operator++`](duration/op_increment.md) | 値をインクリメントする | C++11 |
| [`operator--`](duration/op_decrement.md) | 値をデクリメントする | C++11 |
| [`operator+=`](duration/op_plus_assign.md) | `+`の複合代入 | C++11 |
| [`operator-=`](duration/op_minus_assign.md) | `-`の複合代入 | C++11 |
| [`operator*=`](duration/op_multiply_assign.md) | `*`の複合代入 | C++11 |
| [`operator/=`](duration/op_divide_assign.md) | `/`の複合代入 | C++11 |
| [`operator%=`](duration/op_modulo_assign.md) | `%`の複合代入 | C++11 |
を
## メンバ関数
### 構築/コピー/破棄
| 名前 | 説明 | 対応バージョン |
|-------------------------------------------|-----------------------|----------------|
| [`(constructor)`]) | コンストラクタ | C++11 |
| `~duration() = default;` | デストラクタ | C++11 |
| `operator=(const duration&) = default;` | 代入演算子 | C++11 |
### 観測
| 名前 | 説明 | 対応バージョン |
|--------------------------------|--------------|----------------|
| [`count`] | 値を取得する | C++11 |
### 算術演算
| 名前 | 説明 | 対応バージョン |
|--------------------------------------------------|--------------------------|----------------|
| [`operator+`] | 正の符号 | C++11 |
| [`operator-`] | 負の符号 (符号反転する) | C++11 |
| [`operator++`] | 値をインクリメントする | C++11 |
| [`operator--`] | 値をデクリメントする | C++11 |
| [`operator+=`] | `+`の複合代入 | C++11 |
| [`operator-=`] | `-`の複合代入 | C++11 |
| [`operator*=`] | `*`の複合代入 | C++11 |
| [`operator/=`] | `/`の複合代入 | C++11 |
| [`operator%=`] | `%`の複合代入 | C++11 |
とかけるようにすることは検討していますでしょうか?
/reference/chrono/duration.mdからみてコンストラクタとかメンバ関数のリンクは推論できるのではないかという考え方です。
リンクする場所がどこか自明に推定できるようにという観点で、Markdownのリンク文とリンク先を分けて書ける機能を真似た文法とします。
パッと思いつく課題は
.nolinkへのリンクをどうするか- どこまで推論するのか、兄弟関係と親子関係のページまで推論すれば足りるのか、そうではないのか
あたりでしょうか。
PRのプレビュー機能も導入されて大分経ちますし、 #917 も達成できている今ならありなのではと思ったのでした。
んー。それが実際の編集で苦になっているかというのが導入を検討するスタートになるかと思いますが、いまのところ私としては苦になっていないというのはあります。
.nolinkはMarkdown上で作られていないページを可視化することにも役立っているので (Issueとして計上しきれないタスク)、それが見えなくなると困りそうではありますね。