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)