まずはこの動画を見てください。
私はoj-prepareで指定した問題やコンテストに対し、テンプレートをVSCodeで開き、ブラウザで問題ページを開くスクリプトを作成して使っています。
今回はこのスクリプト、名付けてoj-prepare-openを紹介します。
実装
fish-shellスクリプトです。
#!/bin/fish # oj-prepare-open/hotarunx # oj-prepareで指定した問題やコンテストに対し、 # テンプレートをVSCodeで開き、ブラウザで問題ページを開く set templete_code_ext '.cpp' # 開くファイルの拡張子 set interval 60 # インターバル set download_history ~/.cache/online-judge-tools/download-history.jsonl # ojの履歴ファイルのパス set histories (tail -n100 $download_history) # 履歴ファイルのレコード最新100 set last_time (echo $histories[-1] | jq -r '.timestamp') # 最後の履歴のタイムスタンプ # 履歴を走査する for row in $histories # この履歴のタイムスタンプ set this_time (echo $row | jq -r '.timestamp') # この履歴と、最新の履歴の時間間隔がinterval未満のとき if test (math $last_time - $this_time) -le $interval # VSCodeで問題のテンプレートを開く find (echo $row | jq -r '.directory') -name '*'$templete_code_ext | xargs code # ブラウザで問題ページを開く open (echo $row | jq -r '.url') end end
使用方法
使用前にjq
をインストールしてください。
シェルはfish-shell
を使用してください。
oj-prepare-open.sh
をダウンロードし、適当な場所に置いてください。
oj-prepareを実行した後、fish oj-prepare-open.sh
を実行してください。
過去最新のoj-prepareした問題やコンテストに対し、テンプレートをVSCodeで開き、ブラウザで問題ページを開きます。
*.cpp
ファイル以外のテンプレートファイルを開きたいならば、次の行の拡張子を変更してください。
-set templete_code_ext '.cpp' # 開くファイルの拡張子 +set templete_code_ext '.py' # 開くファイルの拡張子
注意事項
- fish-shellスクリプトなのでbash, zsh, powershell, cmd等々では動きません
- 開くファイルの拡張子は1つしか指定できません
- 最後にoj-prepareで指定した問題と、それより60秒前以内にoj-prepareで指定した問題に対してVSCodeとブラウザで開く処理になっています(実装の都合です)。60秒以内に複数回oj-prepareすると挙動がおかしくなります
- 負荷軽減のため最新100の履歴しか参照しないようにしています。もし問題が100個以上あるコンテストに出るときは定数を変えてください
おまけ
VSCodeタスク連携
hotarunx.hatenablog.com この記事で解説したテンプレート生成とサンプルのダウンロードのタスクの後にoj-prepare-openを実行したいならば、次のように書きます。
{ "label": "oj-prepare: テンプレート生成とサンプルのダウンロード", "type": "shell", "command": "oj-prepare", "args": [ "${input:URL}", ";", "fish", "oj-prepare-open.sh" ], "group": "test" },
実装方法
oj-prepare-openの実装方法について説明します。
まず、oj
は以下のような内容の履歴ファイルを持ちます。
~/.cache/online-judge-tools/download-history.jsonl
{"timestamp": 1636281672, "directory": "/home/hotaru/procon/atcoder.jp/abs/practice_1/atcoder.jp/abs/practice_1", "url": "https://atcoder.jp/contests/abs/tasks/practice_1"} {"timestamp": 1636281672, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc086_a/atcoder.jp/abs/abc086_a", "url": "https://atcoder.jp/contests/abs/tasks/abc086_a"} {"timestamp": 1636281673, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc081_a/atcoder.jp/abs/abc081_a", "url": "https://atcoder.jp/contests/abs/tasks/abc081_a"} {"timestamp": 1636281674, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc081_b/atcoder.jp/abs/abc081_b", "url": "https://atcoder.jp/contests/abs/tasks/abc081_b"} {"timestamp": 1636281674, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc087_b/atcoder.jp/abs/abc087_b", "url": "https://atcoder.jp/contests/abs/tasks/abc087_b"} {"timestamp": 1636281675, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc083_b/atcoder.jp/abs/abc083_b", "url": "https://atcoder.jp/contests/abs/tasks/abc083_b"} {"timestamp": 1636281676, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc088_b/atcoder.jp/abs/abc088_b", "url": "https://atcoder.jp/contests/abs/tasks/abc088_b"} {"timestamp": 1636281676, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc085_b/atcoder.jp/abs/abc085_b", "url": "https://atcoder.jp/contests/abs/tasks/abc085_b"} {"timestamp": 1636281677, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc085_c/atcoder.jp/abs/abc085_c", "url": "https://atcoder.jp/contests/abs/tasks/abc085_c"} {"timestamp": 1636281678, "directory": "/home/hotaru/procon/atcoder.jp/abs/arc065_a/atcoder.jp/abs/arc065_a", "url": "https://atcoder.jp/contests/abs/tasks/arc065_a"} {"timestamp": 1636281679, "directory": "/home/hotaru/procon/atcoder.jp/abs/arc089_a/atcoder.jp/abs/arc089_a", "url": "https://atcoder.jp/contests/abs/tasks/arc089_a"} {"timestamp": 1636282092, "directory": "/home/hotaru/procon/atcoder.jp/abs/practice_1", "url": "https://atcoder.jp/contests/abs/tasks/practice_1"} {"timestamp": 1636282092, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc086_a", "url": "https://atcoder.jp/contests/abs/tasks/abc086_a"} {"timestamp": 1636282092, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc081_a", "url": "https://atcoder.jp/contests/abs/tasks/abc081_a"} {"timestamp": 1636282093, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc081_b", "url": "https://atcoder.jp/contests/abs/tasks/abc081_b"} {"timestamp": 1636282094, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc087_b", "url": "https://atcoder.jp/contests/abs/tasks/abc087_b"} {"timestamp": 1636282094, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc083_b", "url": "https://atcoder.jp/contests/abs/tasks/abc083_b"} {"timestamp": 1636282095, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc088_b", "url": "https://atcoder.jp/contests/abs/tasks/abc088_b"} {"timestamp": 1636282096, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc085_b", "url": "https://atcoder.jp/contests/abs/tasks/abc085_b"} {"timestamp": 1636282097, "directory": "/home/hotaru/procon/atcoder.jp/abs/abc085_c", "url": "https://atcoder.jp/contests/abs/tasks/abc085_c"} {"timestamp": 1636282098, "directory": "/home/hotaru/procon/atcoder.jp/abs/arc065_a", "url": "https://atcoder.jp/contests/abs/tasks/arc065_a"} {"timestamp": 1636282099, "directory": "/home/hotaru/procon/atcoder.jp/abs/arc089_a", "url": "https://atcoder.jp/contests/abs/tasks/arc089_a"} {"timestamp": 1636286446, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_a", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_a"} {"timestamp": 1636286447, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_b", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_b"} {"timestamp": 1636286447, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_c", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_c"} {"timestamp": 1636286448, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_d", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_d"} {"timestamp": 1636286449, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_e", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_e"} {"timestamp": 1636286450, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_f", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_f"} {"timestamp": 1636286450, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_g", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_g"} {"timestamp": 1636286451, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_h", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_h"} {"timestamp": 1636790564, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_a", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_a"} {"timestamp": 1636790564, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_b", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_b"} {"timestamp": 1636790565, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_c", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_c"} {"timestamp": 1636790566, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_d", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_d"} {"timestamp": 1636790566, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_e", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_e"} {"timestamp": 1636790567, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_f", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_f"} {"timestamp": 1636790567, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_g", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_g"} {"timestamp": 1636790568, "directory": "/home/hotaru/procon/atcoder.jp/abc226/abc226_h", "url": "https://atcoder.jp/contests/abc226/tasks/abc226_h"} {"timestamp": 1636790683, "directory": "/home/hotaru/procon/atcoder.jp/agc055/agc055_a", "url": "https://atcoder.jp/contests/agc055/tasks/agc055_a"} {"timestamp": 1636790683, "directory": "/home/hotaru/procon/atcoder.jp/agc055/agc055_b", "url": "https://atcoder.jp/contests/agc055/tasks/agc055_b"} {"timestamp": 1636790684, "directory": "/home/hotaru/procon/atcoder.jp/agc055/agc055_c", "url": "https://atcoder.jp/contests/agc055/tasks/agc055_c"} {"timestamp": 1636790684, "directory": "/home/hotaru/procon/atcoder.jp/agc055/agc055_d", "url": "https://atcoder.jp/contests/agc055/tasks/agc055_d"} {"timestamp": 1636790685, "directory": "/home/hotaru/procon/atcoder.jp/agc055/agc055_e", "url": "https://atcoder.jp/contests/agc055/tasks/agc055_e"} {"timestamp": 1636790686, "directory": "/home/hotaru/procon/atcoder.jp/agc055/agc055_f", "url": "https://atcoder.jp/contests/agc055/tasks/agc055_f"}
このファイルに、oj-prepareで指定した問題のディレクトリとURLがJSONL形式で書いてあります。
そこで、Linux向けJSON操作コマンドであるjq
でこのファイルをパースします。
そうしてcode
でテンプレートをVSCodeで開き、open
コマンドでブラウザで開きます。
さて、1回のoj-prepare
コマンドで複数の問題を指定可能です。
しかし、この履歴ファイルでは、ある時実行したoj-prepare
コマンドでどの問題を指定したのかわかりません。
oj-prepare-openでは過去最新のoj-prepareした問題やコンテストに対し、テンプレートをVSCodeで開き、ブラウザで問題ページを開きたいです。困りました。
そこで最後の履歴から、60秒前までの履歴が、過去最新のoj-prepareした問題という仮定を置きました。