CLI での作業メモをディレクトリ単位で管理する
Posted on 2021-01-16作業メモの管理どうする問題
コンピュータ上で何かしら作業をしながらメモを取る場合、人によって色々な流儀があると思う。私はターミナル上でテキストエディタ (Vim) を起動してプレーンテキストで書くことが多い。
そうしているとテキストファイルの管理に困りがちで、
- 何も考えずに作業ディレクトリに転がしておく → ディレクトリが散らかる。特にバージョン管理されているディレクトリでは、個人的なメモを誤ってリポジトリに入れないように気を付けるのが面倒
- メモ置き場を一つ決めてそこに全部放り込む → 作業とメモが紐づかない。しばらく作業から離れるとメモを探すのに手間取ったり、メモの存在自体を忘れたりする
というようなことがあり、折衷案として作業ディレクトリごとにメモ置き場を用意する方法を考えた。
案というほど大したものではなくて、単に専用のディレクトリ (.notes
) を作って全部そこに入れるだけ。
$ mkdir .notes
$ vim .notes/TODO
こうしておくと作業ディレクトリの直下がメモで散らかることはないし、バージョン管理システムに無視させる場合もディレクトリ一つの方が扱いやすい。Git の場合は ~/.config/git/ignore
にグローバルな ignore 設定を書ける1 ので、そこに .notes
を追加するだけで済む。
作業ディレクトリからたどって参照したいけれど、ファイルの実体は作業ディレクトリの外に置いておきたい、という場合もある。例えば定期的にメモのバックアップを取ることを考えると、作業ディレクトリごとに散らばっているよりは一か所にまとまっているほうが扱いやすい。これはシンボリックリンクを使えば簡単に実現できる。
$ mkdir -p ~/notes/some-project
$ ln -s ~/notes/some-project/ .notes
$ vim .notes/TODO
これですべて解決……とはならなくて、
- 毎回
vim .notes/TODO
のようにパスを指定するのは面倒 - サブディレクトリからメモを開こうとすると
vim ../../.notes/TODO
みたいなのが発生しがちで、やはり面倒
という問題がある。このあたりの手間を減らすものがあるとうれしい……ということで、sidenote という小さい CLI ツールを作って使っている。
sidenote のかんたんな紹介
インストール方法は README を参照。 シングルバイナリで動くコマンドで、Bash 用のコマンドライン補完もある。
機能はサブコマンドに分かれていて、v0.1.6 時点では以下の通り。
$ sidenote -h
Usage: sidenote [-d path] [-version] <command> [command-arguments]
options:
-d string
Specify the directory for notes (env: SIDENOTE_DIR)
-version
Print the version and exit
commands:
cat Print contents of notes
edit Open notes with the editor ($VISUAL or $EDITOR)
import Import a note from the existing file or the standard input
init Initialize the directory for notes
ls List notes
path Print the path of notes
rm Remove notes
serve Serve notes over HTTP
show Open notes with $PAGER
Run sidenote <command> -h for usage of each command.
何かを書きたくなったら、まず init
コマンドでカレントディレクトリ直下にディレクトリ (デフォルトでは .notes
、名前は変更可能) を作る。
$ sidenote init
initialized .notes
$ ls -dl .notes
drwxr-xr-x 2 ryot4 ryot4 4096 Feb 9 19:03 .notes
カレントディレクトリの外にファイルを置きたい場合は init -l
にパスを与えてその場所へのシンボリックリンクを作る。
指定したパスにディレクトリが存在しない場合は自動的に作成される。
$ sidenote init -l ~/Documents/notes
initialized .notes (-> /home/ryot4/Documents/notes)
$ ls -l .notes
lrwxrwxrwx 1 ryot4 ryot4 31 Feb 9 19:03 .notes -> /home/ryot4/Documents/notes
準備は以上で、メモの作成・編集は edit
コマンドで行う。例えば、
$ sidenote edit TODO
とすると .notes/TODO
をデフォルトのエディタ ($VISUAL
または $EDITOR
に設定されているもの) で開く。
.notes/
の部分は裏側で自動的に補われるので手で指定する必要はない。
カレントディレクトリに .notes
が存在しない場合、sidenote
はディレクトリ階層を上にたどって探す。2 例えば
$ tree -aF project
project/
├── .notes/
│ └── TODO
└── src/
└── module/
3 directories, 1 file
のようなディレクトリ構造がある場合、module
ディレクトリの中で sidenote edit TODO
を実行すると
module
の直下には.notes
が無いので一階層上のsrc
を見るsrc
の直下にも.notes
が無いので一階層上のproject
を見るproject
の直下に.notes
があるので、その中のTODO
をエディタで開く
という動きになる。この挙動によってサブディレクトリの中でも相対パスを意識せずにファイルを扱える。
基本はこれだけで、他にファイルの一覧 (ls
) や内容の表示 (cat
/ show
)、削除 (rm
) などを行うコマンドがある (より詳しい使い方は README を参照)。
既存のコマンドを使って楽をする
sidenote
コマンドはなるべく単純なものにしたいと思っていて、例えば検索を行うサブコマンドは存在しない。
代わりに .notes
ディレクトリの場所を出力する path
コマンドがあって、これと既存の検索コマンドの組み合わせで検索を行えるようになっている。
path
コマンドは今いるディレクトリに対応する .notes
ディレクトリのパスを出力するもので、上の module
ディレクトリの例だと以下のようになる。
module$ sidenote path
../../.notes
これを使うと、例えば全てのメモの中から XXX
という文字列を grep
したい場合は以下のようにできる。
$ grep -R XXX "$(sidenote path)"
例えば sidenote grep XXX
のような組み込みの検索コマンドを用意する方が直感的でコマンド入力も楽になるけれど、path
と外部コマンドを組み合わせる方法は検索に grep
以外のものを使いたくなった場合でも対応できるし、検索に限らず「ファイルパスを受け取って何かする」類の処理全般に応用できるので柔軟性がある。
何より、実装量を減らせるので楽で良い。
同様に、cd "$(sidenote path)"
でメモの在りかに移動すれば通常のファイル操作を行えるので、cp
や mv
のような複雑なファイル操作 3 もサブコマンドとしては用意していない。
実装
sidenote は Go で書いた。この手のツールは簡単に使い始められることが重要だと思うので、シングルバイナリで動作する点は大きい。 また、依存関係のメンテナンスを考えずに済むように Go の標準ライブラリだけを使用して書いている。 大したことはしていないので実際のところシェルスクリプトで書いても良かったかもしれないけれど、それはそれで実装が辛いことになっていたかもしれない。
あと、Bash のコマンドライン補完をまともに実装したのはこれが初めてだった。
通常と異なるファイルパスの解釈をするツールなのでファイルパスの補完を行う _filedir
がそのまま使えなかったりしてやや苦労したものの、やはり補完の有り無しでコマンドの使い勝手に天と地の差があるので用意してよかったと思う。