(11/28 修正: et コマンドの場合、日付未確定タスクが選択できるのは変なので表示されないように変更しました)
1. はじめに
ターミナルで動作するコマンドライン版 NotionTimeRecording を作成したので、タスク管理がかなり楽になりました。ターミナルを開いていない時でも Alfred から「> st スタートしたいタスク」のようにすることで、開始時間が記録されたタスクが生成され、Notionアプリで表示されます。
使っていくうちにいくつか気になる点が出てきました。
- 現状、日付未確定のタスクは To Do リストにしていました。To Do リストをカレンダーにドラッグすることでタスクページに変換していましたが、この作業は GUI で行う必要がありました。
- st コマンドはすでに作成済みのタスクに時間を付与することができませんでした。st だけを実行した時には、完了していないタスクを表示し、開始時間を再設定したい。
- その際に日付未設定のタスクも選択できるようにしたい。
- ot, et などでタスクが一つの時には無条件で選択していましたが、その場合でもタスクを選択するようにしたい。
- これらの選択の時に、q とタイプすることで選択しないで処理をやめるようにしたい。
- 全てのプログラムがほぼ同型になるので、一つのスクリプトとしコマンド名で作業を切り分けるようにしたい。
2. 作成するコマンドの Usage
2.1 st
st タスク名
とするとタスク名のタスクを生成します。開始時間が現在時刻に設定されます。また、st
だけを実行すると、本日および日付未確定の未完了タスク一覧が表示されます。ここで番号(0の時は省略可能)をタイプすると、そのタスクの開始時刻が設定されます。どれも開始したくない場合には、q とタイプします。
2.2 ot
ot
を実行すると、本日および日付未確定の未完了タスク一覧が表示されます。ここで番号(0の時は省略可能)をタイプすると、そのタスクが表示されます。どれも表示したくない場合には、q とタイプします。
2.3 et
et
を実行すると、本日および日付未確定の未完了タスク一覧が表示されます。ここで番号(0の時は省略可能)をタイプすると、そのタスクの終了時刻が記録され、完了もチェックされます。どれも終了したくない場合には、q とタイプします。
2.4 nt
nt タスク名
を実行すると、タスク名のタスクが生成されます。この際、日付は設定されません。
3. スクリプト解説
一つのファイルになるので部分ごとに解説していきます。
最初は NotionRubyMapping の初期設定と各種定数の設定です。CMD_NAME にスクリプト名が入るので、これで処理を切り分けます。
#!/usr/bin/env ruby # frozen_string_literal: true require "date" require "fileutils" require "notion_ruby_mapping" include NotionRubyMapping NotionCache.instance.create_client ENV["NOTION_API_KEY"], wait: 0 DATABASE_ID = "2395e3ffb55e4a8abc1ba426243776e3" TOUCH_FILE = "#{ENV["HOME"]}/Dropbox/NotionTimeRecording/touch_file.txt" CMD_NAME = File.basename $0
次にタスク選択部分を関数化します。この部分はコマンドで共通になるので、メソッド化しておきました。先に日付が設定されているものを日付順で取得し、その後日付が設定されていないものを名前順で取得します。並び順が異なるので、2回クエリを実行しています。1回でもできるのですが、クエリが汚くなるのでやめました。また、q
で終了する処理も追加しています。
def select_task(end_task: false) db = Database.find DATABASE_ID dps = db.properties dp, cp, tp = dps.values_at *%w[日付 Done タスク名] date_query = dp.filter_equals(Date.today) .and(cp.filter_equals false) .ascending(dp) date_exists = db.query_database(date_query).to_a unsettled_query = dp.filter_is_empty .and(cp.filter_equals false) .ascending(tp) date_unsettled = end_task ? [] : db.query_database(unsettled_query).to_a pages = date_exists + date_unsettled if pages.count.zero? puts "There are no tasks." exit else pages.each_with_index do |apage, i| puts "#{i}: #{apage.title}" end num_or_q = gets.chomp exit if num_or_q == "q" pages[num_or_q.to_i] end end
あとはコマンドごとに処理を分けるだけです。ot 以外のコマンドは task_update 用に touch_file を作成したいので、デフォルトで touch を true にしています。全てのコマンドが最後に page を返します。
nt
コマンドは引数がタイトルとなるページを作成します。日付は設定しません。すぐに作業しないので、Notion ページは開きません。ot
コマンドは select_task で取得したページを page に格納するだけです。et
コマンドは select_task で取得したページの end_date を設定し、Done にチェックを入れます。始まっていないタスクがリストに出るのはおかしかったので、日付未設定のタスクは表示されないようにしました。st
コマンドは引数がない場合には、select_task で取得したページの開始時間を設定します。日付が未設定のものも選択できるようになっています。- 引数が設定された
st
コマンドはnt
と同様にページを作成しますが、現在時刻を開始時刻として設定します。
touch = true case CMD_NAME when "nt" page = Database.find(DATABASE_ID).create_child_page do |p, pp| pp["タスク名"] << ARGV.join(" ") end exit when "ot" page = select_task touch = false when "et" page = select_task end_task: true pp = page.properties pp["日付"].end_date = DateTime.now pp["Done"].checkbox = true page.save when "st" if ARGV.empty? page = select_task pp = page.properties pp["日付"].start_date = DateTime.now page.save else page = Database.find(DATABASE_ID).create_child_page do |p, pp| pp["タスク名"] << ARGV.join(" ") pp["日付"].start_date = DateTime.now end end end url = page["url"].gsub "https", "notion" system("open #{url}") FileUtils.touch TOUCH_FILE if touch
4. コマンドの展開
プログラムは nt にのみ記述し、st, ot, et はシンボリックリンクにしました。当初ハードリンクにしたのですが、Dropbox がハードリンクを認識してくれず、別のマシンでは異なるファイルに設定されてしまいました。そこで、Dropbox でも認識できるシンボリックリンクにしました。こんな感じになっています。
hkob@hM1mini ~/bin> ls -l nt st ot et lrwxr-xr-x@ 1 hkob staff 2 11 21 15:28 et@ -> nt -rwxr--r--@ 1 hkob staff 1873 11 21 15:51 nt* lrwxr-xr-x@ 1 hkob staff 2 11 21 15:28 ot@ -> nt lrwxr-xr-x@ 1 hkob staff 2 11 21 15:51 st@ -> nt
5. おわりに
nt
コマンドで日付が決まっていないタスクを簡単に登録できるようになりました。また、st
コマンドのみで日付が決まっていないタスクをすぐに開始できることができるようになりました。めちゃくちゃタスク管理が効率化しました。ターミナルが立ち上がってなくても、Alfred から呼べるのが便利ですね。