AtCoderのテストや提出をVsCode上で自動化する（C++、Python）

# 実現したいこと
Visual Studio Codeにおいてショートカット一つで、テストまたは提出を行いたいです。
また、言語に依らずに提出できるようにしたいです。

# 環境
OS : Windows 11
コマンドライン : PowerShell
IDE : Visual Studio Code



# 目次
[online-judge-toolsインストールする](#online-judge-toolsインストールする)
[PowerShellのスクリプトを作成する](#powershellのスクリプトを作成する)
[task.jsonの設定をする](#taskjsonの設定をする)
[ショートカットの設定をする](#ショートカットの設定をする)

# 方法
## online-judge-toolsインストールする
https://github.com/online-judge-tools/oj

```powershell: PowerShell
pip install online-judge-tools
```
これでインストールできます
## PowerShellのスクリプトを作成する
以下のようなスクリプトを作成しました。
```oj_test.ps1 
function Test {
    param (
        [Parameter(Mandatory=$true)]
        [string]$test_directory,
        [Parameter(Mandatory=$true)]
        [string]$atcoder_url,
        [Parameter(Mandatory=$true)]
        [string]$solved_file_extension
    )

    # Check if the test directory already exists
    if (Test-Path $test_directory -PathType Container) {
        Write-Host "Test directory already exists. Skipping download."
    } else {
        # Download the test cases for the specific AtCoder problem
        oj dl -d $test_directory $atcoder_url
    }

    if ($solved_file_extension -eq "cpp") {
        # Compile the C++ code using g++ with specified options and output directory
        $compiler_options = "-std=c++20 -g -Wall -Wextra -Wshadow -Wfloat-equal -Wno-char-subscripts -ftrapv -fno-omit-frame-pointer -fno-sanitize-recover -I C:\ac-library"
        $compiled_output =  Join-Path (Get-Item $solved_file_path).Directory "$solved_file_name.out"
        Write-Host "Output executable path: $compiled_output"
        $command = "g++ $compiler_options -o $compiled_output $solved_file_path"

        # Execute the compilation command
        Invoke-Expression $command

        # Check if the compilation was successful
        if($LASTEXITCODE -ne 0) {
            Write-Host "Compilation failed."
            exit
        }
        # Run tests using the compiled C++ executable
        oj t -c $compiled_output -d $test_directory --ignore-spaces

        # Remove the compiled executable after testing
        Remove-Item $compiled_output -Force
    } elseif ($solved_file_extension -eq "py") {
        # Run tests using the provided Python script
        oj test -c "python $solved_file_path" -d $test_directory --ignore-spaces
    } else {
        Write-Host "Unsupported file extension: $solved_file_extension"
    }
}

function Submmit{
    param (
        [Parameter(Mandatory=$true)]
        [string]$atcoder_url,
        [Parameter(Mandatory=$true)]
        [string]$solved_file_path,
        [Parameter(Mandatory=$true)]
        [string]$solved_file_extension
    )

    if ($LASTEXITCODE -eq 0) {
        $result = "OK"
    } else {
        $result = "NG"
    }
    Write-Host "Result: $result"
    if($result -ne "OK") {
        exit
    }
    
    if($solved_file_extension -eq "cpp"){
        $language_code = 5001  # 5001 (C++ 20 (gcc 12.2))
    }elseif($solved_file_extension -eq "py"){
        $language_code = 5078  # 5078 (Python (PyPy 3.10-v7.3.12))
    }else{
        Write-Host "Unsupported file extension: $solved_file_extension"
        exit
    }

    oj s $atcoder_url $solved_file_path -y -l $language_code
}

#####################################################################

# Get the file path from the command line arguments
$solved_file_path = $Args[0]  # Example: .\158\a_re.py
Write-Host "slolved_file_path : $solved_file_path"
$is_submit = [System.Convert]::ToBoolean($Args[1])
Write-Host "is_submit : $is_submit"

# Check if no arguments were provided
if ($Args.Count -eq 0) {
    Write-Host "No arguments provided. Please specify a file path."
    exit
}

$file_path_components = $Args[0] -split "\\"
$problem_number = $file_path_components[-2]
$solved_file = $file_path_components[-1]
Write-Host "solved_file : $solved_file"
Write-Host "problem_number : $problem_number"

$solved_file_components = $solved_file -split "\."
$solved_file_name = $solved_file_components[0]  # Example: a_re.py
$solved_file_extension = $solved_file_components[1]  # Example: py
Write-Host "solved_file_name : $solved_file_name"
Write-Host "solved_file_extension : $solved_file_extension"

$problem_alphabet = $solved_file.Substring(0, 1)  # Example: a
Write-Host "problem_alphabet : $problem_alphabet"

$test_directory = (($file_path_components[0..($file_path_components.Length - 2)]) -join "/") + "/test/$problem_alphabet/"
Write-Host "test_directory : $test_directory"

$atcoder_url = "https://atcoder.jp/contests/abc$problem_number/tasks/abc${problem_number}_$problem_alphabet"
Write-Host "atcoder_url : $atcoder_url"

if ($is_submit) {
    Test $test_directory $atcoder_url $solved_file_extension
    Submmit $atcoder_url $solved_file_path $solved_file_extension
} else {
    Test $test_directory $atcoder_url $solved_file_extension
}

```
1つ目の引数で、テストしたいスクリプトのパスを受け取り、2つ目の引数で提出するかどうかを決めます。
提出する場合であっても、すべてのテストケースでACしないと提出されないようにしてあります。

<details>
<summary>実行結果の例です</summary>

``` a_wa.py
S = input()
S = sorted(S)

if S[0] != S[2]:
    print("Yes")
else:
    print("Noooooooooo")
```

```powershell
 *  Executing task: D:\AtCoder\ABC\powershell\oj_test.ps1 D:\AtCoder\ABC\158\a_wa.py false 

slolved_file_path : D:\AtCoder\ABC\158\a_wa.py
is_submit : False
sloved_file : a_wa.py
problem_number : 158
sloved_file_name : a_wa
solved_file_extension : py
problem_alphabet : a
test_directory : D:/AtCoder/ABC/158/test/a/
atcoder_url : https://atcoder.jp/contests/abc158/tasks/abc158_a
Test directory already exists. Skipping download.
[INFO] online-judge-tools 11.5.1 (+ online-judge-api-client 10.10.1)
[INFO] 3 cases found
[WARNING] GNU time is not available: time

[INFO] sample-1
[INFO] time: 0.022923 sec
[SUCCESS] AC

[INFO] sample-2
[INFO] time: 0.022173 sec
[SUCCESS] AC

[INFO] sample-3
[INFO] time: 0.020233 sec
[FAILURE] WA
input:
BBB

output:
Noooooooooo\r

expected:
No


[INFO] slowest: 0.022923 sec  (for sample-1)
[FAILURE] test failed: 2 AC / 3 cases
```  
</details>


ご自身の環境に合わせて以下の部分を修正すれば同じように動作すると思います。
```powershell
$file_path_components = $Args[0] -split "\\"
$problem_number = $file_path_components[-2]
$solved_file = $file_path_components[-1]
Write-Host "solved_file : $solved_file"
Write-Host "problem_number : $problem_number"

$solved_file_components = $solved_file -split "\."
$solved_file_name = $solved_file_components[0]  # Example: a_re.py
$solved_file_extension = $solved_file_components[1]  # Example: py
Write-Host "solved_file_name : $solved_file_name"
Write-Host "solved_file_extension : $solved_file_extension"

$problem_alphabet = $solved_file.Substring(0, 1)  # Example: a
Write-Host "problem_alphabet : $problem_alphabet"

$test_directory = (($file_path_components[0..($file_path_components.Length - 2)]) -join "/") + "/test/$problem_alphabet/"
Write-Host "test_directory : $test_directory"

$atcoder_url = "https://atcoder.jp/contests/abc$problem_number/tasks/abc${problem_number}_$problem_alphabet"
Write-Host "atcoder_url : $atcoder_url"
```

参考までに私は以下のようなフォルダー構造になっています。
```shell
AtCoder
├── ABC
    ├── 158
        ├── test
            ├── a
                ├── sample-1.in
                ├── sample-1.out
                ...
            ├── b
            ...
        ├── a.cpp
        ├── b.cpp
        ├── c.py
        ├── c_ans.py
    ├── 159
    ...
```
また、提出時の言語のコードの対応は以下です。
<details>
<summary>[INFO] supported languages are:</summary>

```
[INFO] supported languages are:
5001 (C++ 20 (gcc 12.2))
5002 (Go (go 1.20.6))
5003 (C# 11.0 (.NET 7.0.7))
5004 (Kotlin (Kotlin/JVM 1.8.20))
5005 (Java (OpenJDK 17))
5006 (Nim (Nim 1.6.14))
5007 (V (V 0.4))
5008 (Zig (Zig 0.10.1))
5009 (JavaScript (Node.js 18.16.1))
5010 (JavaScript (Deno 1.35.1))
5011 (R (GNU R 4.2.1))
5012 (D (DMD 2.104.0))
5013 (D (LDC 1.32.2))
5014 (Swift (swift 5.8.1))
5015 (Dart (Dart 3.0.5))
5016 (PHP (php 8.2.8))
5017 (C (gcc 12.2.0))
5018 (Ruby (ruby 3.2.2))
5019 (Crystal (Crystal 1.9.1))
5020 (Brainfuck (bf 20041219))
5021 (F# 7.0 (.NET 7.0.7))
5022 (Julia (Julia 1.9.2))
5023 (Bash (bash 5.2.2))
5024 (Text (cat 8.32))
5025 (Haskell (GHC 9.4.5))
5026 (Fortran (gfortran 12.2))
5027 (Lua (LuaJIT 2.1.0-beta3))
5028 (C++ 23 (gcc 12.2))
5029 (Common Lisp (SBCL 2.3.6))
5030 (COBOL (Free) (GnuCOBOL 3.1.2))
5031 (C++ 23 (Clang 16.0.6))
5032 (Zsh (Zsh 5.9))
5033 (SageMath (SageMath 9.5))
5034 (Sed (GNU sed 4.8))
5035 (bc (bc 1.07.1))
5036 (dc (dc 1.07.1))
5037 (Perl (perl  5.34))
5038 (AWK (GNU Awk 5.0.1))
5039 (なでしこ (cnako3 3.4.20))
5040 (Assembly x64 (NASM 2.15.05)) 
5041 (Pascal (fpc 3.2.2))
5042 (C# 11.0 AOT (.NET 7.0.7))
5043 (Lua (Lua 5.4.6))
5044 (Prolog (SWI-Prolog 9.0.4))
5045 (PowerShell (PowerShell 7.3.1))
5046 (Scheme (Gauche 0.9.12))
5047 (Scala 3.3.0 (Scala Native 0.4.14))
5048 (Visual Basic 16.9 (.NET 7.0.7))
5049 (Forth (gforth 0.7.3))
5050 (Clojure (babashka 1.3.181))
5051 (Erlang (Erlang 26.0.2))
5052 (TypeScript 5.1 (Deno 1.35.1))
5053 (C++ 17 (gcc 12.2))
5054 (Rust (rustc 1.70.0))
5055 (Python (CPython 3.11.4))
5056 (Scala (Dotty 3.3.0))
5057 (Koka (koka 2.4.0))
5058 (TypeScript 5.1 (Node.js 18.16.1))
5059 (OCaml (ocamlopt 5.0.0))
5060 (Raku (Rakudo 2023.06))
5061 (Vim (vim 9.0.0242))
5062 (Emacs Lisp (Native Compile) (GNU Emacs 28.2))
5063 (Python (Mambaforge / CPython 3.10.10))
5064 (Clojure (clojure 1.11.1))
5065 (プロデル (mono版プロデル 1.9.1182))
5066 (ECLiPSe (ECLiPSe 7.1_13))
5067 (Nibbles (literate form) (nibbles 1.01))
5068 (Ada (GNAT 12.2))
5069 (jq (jq 1.6))
5070 (Cyber (Cyber v0.2-Latest))
5071 (Carp (Carp 0.5.5))
5072 (C++ 17 (Clang 16.0.6))
5073 (C++ 20 (Clang 16.0.6))
5074 (LLVM IR (Clang 16.0.6))
5075 (Emacs Lisp (Byte Compile) (GNU Emacs 28.2))
5076 (Factor (Factor 0.98))
5077 (D (GDC 12.2))
5078 (Python (PyPy 3.10-v7.3.12))
5079 (Whitespace (whitespacers 1.0.0))
5080 (><> (fishr 0.1.0))
5081 (ReasonML (reason 3.9.0))
5082 (Python (Cython 0.29.34))
5083 (Octave (GNU Octave 8.2.0))
5084 (Haxe (JVM) (Haxe 4.3.1))
5085 (Elixir (Elixir 1.15.2))
5086 (Mercury (Mercury 22.01.6))
5087 (Seed7 (Seed7 3.2.1))
5088 (Emacs Lisp (No Compile) (GNU Emacs 28.2))
5089 (Unison (Unison M5b))
5090 (COBOL (GnuCOBOL(Fixed) 3.1.2))
```
</details>


## task.jsonの設定をする

```json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Test scripts for Atcoder",
            "type": "shell",
            "command": "${workspaceFolder}\\ABC\\powershell\\oj_test.ps1",
            "args": [
                "${fileDirname}\\${fileBasename}",
                "false"
            ],
            "problemMatcher": []
        },
        {
            "label": "Submit scripts for Atcoder",
            "type": "shell",
            "command": "${workspaceFolder}\\ABC\\powershell\\oj_test.ps1",
            "args": [
                "${fileDirname}\\${fileBasename}",
                "true"
            ],
            "problemMatcher": []
        }
    ]
}

```

## ショートカットの設定をする
以下に、"Hello"というメッセージを出力する簡単なタスクを設定する手順を示します。
これを参考にしてショートカットの設定を行ってください。

プロジェクトフォルダーで、.vscodeという名前のフォルダーがない場合は、新しく作成します。このフォルダーはVSCodeの設定ファイルを保存するためのものです。

.vscodeフォルダー内に、tasks.jsonという名前のファイルを作成または編集します。

以下のようにtasks.jsonファイルを設定します：

```json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Hello World",
            "type": "shell",
            "command": "echo",
            "args": [
                "Hello"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}
```
この設定は、"Hello World"というラベルのタスクを定義し、それが実行されたときにシェルコマンドecho "Hello"を実行するものです。

メニューの「表示 (View)」→「コマンド パレット (Command Palette)」を開き、コマンドパレットにTasks: Run Taskと入力します。

プロンプトに表示されるタスクリストから "Hello World" を選択して実行します。

これで、タスクが実行され、"Hello"というメッセージが出力されるはずです。

また、このタスクを特定のショートカットキーに関連付けることもできます。メニューの「ファイル (File)」→「基本設定 (Preferences)」→「キーボード ショートカット (Keyboard Shortcuts)」を開き、検索ボックスに"workbench.action.tasks.runTask"と入力して、タスクの実行コマンドにショートカットキーを関連付けることができます。

### [追記]
上記の方法では、ショートカットを実行した後にどのタスクを実行するかを決める必要がありましたが、それぞれのタスクにショートカットを割り当てる方法が以下の記事で解説されていました。

https://qiita.com/sakano/items/685b6a15dd600ccd34bd

# 参考記事
https://qiita.com/AokabiC/items/af685bfd205dda44ec45#%E8%87%AA%E5%8B%95%E3%81%A7%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B1%E3%83%BC%E3%82%B9%E3%82%92%E6%A4%9C%E8%A8%BC%E3%81%99%E3%82%8B

https://qiita.com/ajim/items/4d350710ba70056f5f6f#_reference-43840d611e79ea0760cc

https://qiita.com/chokoryu/items/4b31ffb89dbc8cb86971#taskjson