【AtCoder】atcoder-tools + ファイルオープン + 問題オープン 自動化スクリプト

## 概要

`atcoder-tools` はAtCoderのコンテストのディレクトリ作成、テストケースダウンロード、提出などを全て行うことができる便利なツールです。

↓お世話になってます

https://github.com/kyuridenamida/atcoder-tools

`atcoder-tools gen {コンテストID}` 機能を使うとソースファイル自動生成+テストケースのダウンロードまでができて非常に便利です。
ただ、その後問題文をブラウザで、ソースファイルをエディタで一括で開くところまで自動化したかったのでシェルスクリプトの勉強もかねて書いてみました。

## できること：

1) 指定したコンテストIDのディレクトリを atcoder-tools を用いて作成
2) 全問題の`main.cpp` をエディタで開く
3) 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` に書き換えてください

atcoder-tools と jq は pip 経由でインストールできます。

## 使い方

https://github.com/Capobmb/CP-library/blob/1d89195ff155fd01a6c3b5cd19e0e438630da282/Scripts/atcoder_prepare

1) 適当なディレクトリに`atcoder_prepare`などの名前で上のスクリプトを保存し、そのディレクトリで`chmod 700 atcoder_prepare` などを実行して実行権限を付与します。

2) ターミナルで以下を実行

    ```bash
    # スクリプトが置いてあるディレクトリで
    ./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/` となります
    ```bash
    # 例
    # 環境変数を設定
    export ATCODER_TOOLS_WORKSPACE=`~/CompetitiveProgramming/AtCoder/`
    # 実行
    atcoder_prepare abc300
    ```

## やっていること (24/09/09追記)

### 1. 指定したコンテストIDのディレクトリを atcoder-tools を用いて作成

`atcoder-tools gen` を実行すると、生成されたディレクトリ内は以下のような構造になります；

```text
.
├── 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`コマンドで開くことができます。ユーザー設定におけるデフォルトのエディタが開かれるはずです。

```bash
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を開いた後に少しの間スリープしています。

```bash
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
}
```