Table of Contents
1. はじめに
前回の記事で、emacsのorg-modeとgithub(/gitlab)でプライベートなWikiを作る提案をしましたが、各ドキュメント(.org ファイル)をカテゴリ分けしてリンクするトップページ(README.orgファイル)の手動作成が課題でした。これまで作成したメモをいくつかプライベートWikiに追加したのですが、それに伴うリンクの手動でのメンテナンスが相当に面倒であることがわかりました。
2. orgidx.py
というわけで、Pythonスクリプトの orgidx.py を作成し、githubで 公開 しました。これは、各 .org ファイルへのリンクを、サブディレクトリ単位でカテゴリー分けして並べたトップページ(README.org)を自動生成するスクリプトです。カテゴリーやリンクとして表示するテキストを付けることができます。
2.1. 使い方
- Wiki用ディレクトリを用意する(eg, /home/jon/wiki)
- その下にカテゴリー(のキー)となるサブディレクトリを作成する
.orgファイルをそれぞれのカテゴリーになるサブディレクトリ下に置く- 各
.orgファイルに#+TITLE:メタデータを用意してください #+SUBTITLE:メタデータはオプションで、その.orgファイルの説明などに使ってください。複数の#+SUBTITLE:行を持つことが出来ます。無くても構いません。
- 各
- Python 3.9以降をインストールする
pipによるライブラリ等の追加は不要です
- 好きな場所に
orgidx.pyを置く - Wiki用の先頭ディテクトリに
config.json(下記参照)を用意する。- これはしなくてもよいですが、
categ_dictに無いサブディレクトリ名は、そのままカテゴリー文字列として使用されます。
- これはしなくてもよいですが、
- スクリプトを実行する(eg,
python orgidx.py /home/jon/wiki) README.orgがトップWikiディレクトリ下に作られる
2.2. ポイント
- 各リンクに使われる文字列は
#+TITLE:メタデータを使います。各.orgファイルに必ず用意してください +SUBTITLE:メタデータは.orgファイルの説明用です。適宜使用すると使いやすくなると思います。タイトルだけだと何のファイルなのか忘れてしまうことが(私の場合)よくあるので。。。config.jsonをうまく用意すると、とても見やすくなります- なお、複数のサブディレクトリ名が同じカテゴリー文字列を使う場合、これらはまとめられて一つのカテゴリーとなります
2.3. config.json
config.json は以下のようになっています。
{
"#+TITLE": "My Personal Wiki pages",
"distrib": "分散システム",
"storage": "ストレージ",
"virtual": "コンテナ・VM",
"linux": "Linux",
"etc": "未分類"
}
2行目の "#+TITLE" は、これだけが特別扱いで、 README.org のタイトル行文字列を定義します。
3行目以降は、
"<サブディレクトリ名>": "カテゴリー文字列",
の定義が続きます。JSONのルールとして、最後の定義行("etc": "未分類")のみ、行末にカンマ(,)が付いていないことに注意してください。(カンマを付けるとエラーになります)
3. gitlabは?
gitlabで試したところ、gitlabでも .org ファイルのレンダリングをしてくれることがわかりました。前回と今回の記事で紹介したパーソナルWikiページの作り方や、今回のPythonスクリプトがそのままgitlabにも適用可能です。
個人でgithub、オフィスでgitlabといった使い分けが考えられますね。
4. 終わりに
プライベートなレポジトリの用意、 .org ファイルのレンダリング、複数デバイスからの閲覧・編集といったことは github(/gitlab) が用意してくれています。唯一、欠けているピースがインデックスの作成だったのですが、今回それを用意しました。良いプライベートWikiライフを!
5. Appendix
短いので、ソースを貼っておきます。
import sys
import os
from collections import defaultdict
import json
if __name__ == "__main__":
if len(sys.argv) != 2:
print(f"Usage: python {sys.argv[0]} <base_dir>")
sys.exit(1)
if not os.path.isdir(basedir := sys.argv[1]):
print(f"{basedir} is not a valid directory.")
sys.exit(1)
basedir = basedir.rstrip("/")
# dir_name to category string
categ_dict = {
"distrib": "分散システム",
"storage": "ストレージ",
"virtual": "コンテナ・VM",
"linux": "Linux",
"etc": "未分類",
}
TITLE_HEAD = "#+TITLE: "
SUBTITLE_HEAD = "#+SUBTITLE: "
top_title = "#+TITLE: Personal Wiki Index"
# Read config.json
try:
with open("/".join([basedir, "config.json"])) as f:
config = json.load(f)
top_title = "#+TITLE: " + config.pop("#+TITLE", top_title)
categ_dict = config
except Exception:
print("Skip loading config.json")
# Save README.org if exists
readme_path = "/".join([basedir, "README.org"])
readme_bk_path = readme_path + ".bk"
try:
os.remove(readme_bk_path)
except Exception:
pass
if os.path.exists(readme_path):
os.rename(readme_path, readme_bk_path)
# Create org_dic
# category_string: list of (full_path, title) tuples
# Note: multiple categories could share the same category_string
org_dic = defaultdict(list)
for dpath, _, fnames in os.walk(basedir):
for fname in fnames:
if fname.endswith(".org"):
full_path = "/".join([dpath, fname])
categ = categ_str = dpath[dpath.rfind("/") + 1 :].lower()
try:
categ_str = categ_dict[categ]
except Exception:
pass
with open(full_path, "r") as f:
title = "No #+TITLE: header!"
subtitles = []
for line in f:
if line.startswith(TITLE_HEAD):
title = line[len(TITLE_HEAD) :].rstrip()
if line.startswith(SUBTITLE_HEAD):
subtitles.append(line[len(SUBTITLE_HEAD) :].rstrip())
link_path = "." + full_path[len(basedir) :]
org_dic[categ_str].append((link_path, (title, fname, subtitles)))
# Generate README.org based on org_dic
with open(readme_path, "w") as f:
print(top_title, file=f)
print("\nPersonal memos\n", file=f)
for categ_str, link_list in org_dic.items():
print(f"** {categ_str}", file=f)
print("", file=f)
for link in link_list:
print(f"- [[{link[0]}][{link[1][0]}]] ({link[1][1]})", file=f)
for subttl in link[1][2]:
print(f" - {subttl}", file=f)
print("", file=f)