概要
atcoder-tools
はAtCoderのコンテストのディレクトリ作成、テストケースダウンロード、提出などを全て行うことができる便利なツールです。
↓お世話になってます
atcoder-tools gen {コンテストID}
機能を使うとソースファイル自動生成+テストケースのダウンロードまでができて非常に便利です。
ただ、その後問題文をブラウザで、ソースファイルをエディタで一括で開くところまで自動化したかったのでシェルスクリプトの勉強もかねて書いてみました。
できること:
- 指定したコンテストIDのディレクトリを atcoder-tools を用いて作成
- 全問題の
main.cpp
をエディタで開く - atcoder-tools が自動生成した各問題の
metadata.json
から問題IDを取得しブラウザで該当する問題を開く
前提:
- bashスクリプトです。Windows PowerShell では動きません
- 以下のコマンドに依存します:
- atcoder-tools v2.14.0
- jq-1.7.1
- JSONのパースに用います
- open
- Mac, Linux ではデフォルトで使用できます(はずです)。エディタとブラウザのオープンに用います。WSLやLinux では
xdg-open
に書き換えてください
- Mac, Linux ではデフォルトで使用できます(はずです)。エディタとブラウザのオープンに用います。WSLやLinux では
atcoder-tools と jq は pip 経由でインストールできます。
使い方
-
適当なディレクトリに
atcoder_prepare
などの名前で上のスクリプトを保存し、そのディレクトリでchmod 700 atcoder_prepare
などを実行して実行権限を付与します。 -
ターミナルで以下を実行
# スクリプトが置いてあるディレクトリで ./atcoder_prepare <contest_id> # またはPATHを通して atcoder_prepare <contest_id>
<contest_id>
には、AtCoderのコンテストIDを指定してください。
例) atcoder_prepare abc390
その他設定
-
ATCODER_TOOLS_WORKSPACE
環境変数を設定することでatcoder-tools
の作業ディレクトリをスクリプトに教えることができ、(2) と (3) のステップで使用されます- 設定されていない場合 atcoder-tools本家にならい
~/atcoder-workspace/
となります
# 例 # 環境変数を設定 export ATCODER_TOOLS_WORKSPACE=`~/CompetitiveProgramming/AtCoder/` # 実行 atcoder_prepare abc300
- 設定されていない場合 atcoder-tools本家にならい
やっていること (24/09/09追記)
1. 指定したコンテストIDのディレクトリを atcoder-tools を用いて作成
atcoder-tools gen
を実行すると、生成されたディレクトリ内は以下のような構造になります;
.
├── A
│ ├── in_1.txt # 入力サンプル
│ ├── in_2.txt # 入力サンプル
│ ├── main
│ ├── main.cpp
│ ├── metadata.json # 問題に関するメタデータが記述されている。
│ ├── out_1.txt # 出力サンプル
│ └── out_2.txt # 出力サンプル
└── B
├── in_1.txt # 入力サンプル
├── in_2.txt # 入力サンプル
├── main.cpp
├── metadata.json # 問題に関するメタデータが記述されている。
├── out_1.txt # 出力サンプル
└── out_2.txt # 出力サンプル
└── C
......
metadata.json を後で使います。
2. 全問題のmain.cpp をエディタで開く
スクリプト内の codeopen_tooldir
関数で実装しています。
コンテストのディレクトリ内の全てのディレクトリを走査して、その中のmain.cpp
を全てエディタで開ければ良さそうです。
*
によるワイルドカードマッチングを用いると "${workspace_dir}"*/
で workspace_dir 以下のサブディレクトリのフルパスが配列として得られます。この要素ひとつひとつを directory
変数としてfor文で回しています。
directory
変数の後ろにmain.cpp
を結合させることで CPPファイルの絶対パスを得られるため open
コマンドで開くことができます。ユーザー設定におけるデフォルトのエディタが開かれるはずです。
function codeopen_tooldir() {
local workspace_dir="$1"
# Ensure path ends with /
[[ "${workspace_dir}" != */ ]] && workspace_dir="${workspace_dir}/"
for directory in "${workspace_dir}"*/; do
# コンテストディレクトリ内の全てのディレクトリを走査
# 変数 directory は "/path/to/contest/directory/A/" のような形式になっているので、
local cppfile="${directory}main.cpp"
if [[ ! -f "${cppfile}" ]]; then
log_warn "main.cpp does not exist for directory \"$(basename "${directory}")\". Skipping opening cpp file with default editor."
continue
fi
open "${cppfile}"
done
}
3. 全問題のURLをブラウザで開く
browseropen_tooldir
関数で実装しています。
これまたatcoder-tools
が自動生成してくれる情報を使います。便利ですね。
各ディレクトリにある metadata.json
には問題のIDなどのメタ情報が含まれていて problem_id
を抽出すればよさそうです。
ここではJSONのパースに jq
コマンドを用いており、JSON形式のデータから特定のフィールドをパースし、AtCoderのURLにアクセスします。
jq
を使っている部分以外は 2 とほぼ同じ処理を行っています。各ディレクトリにある metadata.json
から problem_id
を抽出していますが、もし何らかの理由で jq
コマンドが失敗したり、metadata.json
に problem_id
が含まれていない場合警告メッセージを表示し、その問題の処理はスキップされます。
workspace_dir
以下の各ディレクトリを走査し、metadata.json
が存在すればその中から problem_id
を取得します。そして、コンテストIDおよび problem_id
に基づき、AtCoderのコンテストページに対応するURLを生成します。ここはハードコードしています。
最後に open
コマンドを使ってブラウザでページを開きます。
ここで、一度に8問くらい開いてしまうとToo many requests エラーが出てしまうので、各URLを開いた後に少しの間スリープしています。
function browseropen_tooldir() {
local workspace_dir="$1"
local contest_id
contest_id="$(basename "${workspace_dir}")"
# Ensure path ends with /
if [[ "${workspace_dir}" != */ ]]; then
workspace_dir="${workspace_dir}/"
fi
for directory in "${workspace_dir}"*/; do
local metadata="${directory}metadata.json"
if [[ ! -f "${metadata}" ]]; then
log_warn "metadata.json for directory \"$(basename "${directory}")\" doesn't exist. Skipping opening problem."
continue
fi
local problem_id
problem_id="$(jq -r '.problem.problem_id' "${metadata}" 2>/dev/null)"
if [[ $? -ne 0 || "${problem_id}" == "null" ]]; then
log_warn "Failed to get problem ID for \"$(basename "${directory}")\". Skipping opening problem."
continue
fi
local url="https://atcoder.jp/contests/${contest_id}/tasks/${problem_id}"
open "${url}"
log_info "Opened ${url} for problem \"$(basename "${directory}")\"."
# sleep to avoid too many requests
sleep 0.2
done
}