oij

test


oijonline-judge-tools を URL の指定なしで使えるようにしたコマンドです。

内部で online-judge-toolsonline-judge-api-client を使用しています。

インストール

ビルド済みバイナリから

リリースページ から oij-vA.B.C-Linuxoij-vA.B.C-maxOS をダウンロードしてください。 また、online-judge-tools のインストールが必要です。

ソースから

online-judge-toolsCrystal のインストールが必要です。

$ cd <your favorite directory>
$ git clone https://github.com/yuruhi/oij.git && cd oij
$ shards build --release
$ cp bin/oij <your favorite bin>

使い方

oij.gif

oij の設定は ~/.config/oij/config.yml で行えます。

コマンド一覧

compile, exe, run

与えられたファイルを compile はコンパイル、exe は実行、run はコンパイルして実行します。

exe, run は第二引数に入力ファイルを指定できます。input_file_mappingが使えます。

一つの言語で複数のコンパイル・実行するコマンドを使う場合は --option, -o で切り替えられます。

compile にコンパイルするコマンド、実行するコマンドを拡張子別に設定します。(変数が使えます)

# config.yml
compile:
    cr:
        default: "crystal build ${file}"
        release: "crystal build ${file} --release"
    cpp:
        debug: "g++ -g -fsanitize=undefined,address ${file}"
        fast: "g++ -O3 ${file}"
    rb: "ruby -wc ${file}"

execute:
    cr: "./${basename_no_extension}"
    cpp: "./a.out"
    rb: "ruby ${file}"
# a.cr
puts read_line.to_i * 2
$ oij compile a.cr # same to `oij compile a.cr -o default`
[INFO] $ crystal build a.cr

$ oij compile a.cr -o release
[INFO] $ crystal build a.cr --release

$ cat input
3

$ oij exe a.cr
[INFO] $ ./a
5  # input
10 # output

$ oij exe a.cr input
[INFO] $ ./a (input file: input)
6

$ oij run a.cr input
[INFO] $ crystal build a.cr
[INFO] $ ./a (input file: input)
6

test, t

test は与えられたファイルをテスト、t は与えられたファイルをコンパイルしてからテストします。 -- の後のオプションはそのまま oj test に渡されます。

$ oij compile a.cr
[INFO] $ crystal build a.cr

$ oij test a.cr
[INFO] $ oj test -c ./a
...

$ oij t a.cr
[INFO] $ crystal build a.cr
[INFO] $ oj test -c ./a
...

$ oij test a.cr -- -e=1e-5
[INFO] $ oj test -c ./a -e=1e-5
...

edit-test, et

与えられたテストケース(testcase_mappingが使えます)をエディターで開きます。 --dir, -d でテストケースが入ったディレクトリを指定できます(デフォルト: test)。

使用するエディターは editor で設定します。指定されていない場合は環境変数 EDITOR を使います。 コマンドにスペースが含まれる場合、引数を表す "${@}" を含める必要があります。

# config.yml
editor: "vim"
$ et sample-1
[INFO] $ vim test/sample-1.in test/sample-1.out

$ et sample-1 -d sample
[INFO] $ vim sample/sample-1.in sample/sample-1.out

print-test, pt

与えられたテストケース(testcase_mappingが使えます)を表示します。 --dir, -d でテストケースが入ったディレクトリを指定できます(デフォルト: test)。

表示に使用するコマンドは printer で設定することもできます。 コマンドにスペースが含まれる場合、引数を表す "${@}" を含める必要があります。

$ pt sample-1
[INFO] test/sample-1.in (2 byte):
3

[INFO] test/sample-1.out (2 byte):
6
# config.yml
printer: 'bat --style=header "${@}"'
[INFO] $ /bin/sh -c bat --style=header "${@}" -- test/sample-1.in test/sample-1.out
File: test/sample-1.in
3 6

File: test/sample-1.out
5

url, url-contest, urlc

与えられた問題コンテストの URL を表示します。

$ oij url --atcoder agc001_a
https://atcoder.jp/contests/agc001/tasks/agc001_a

$ oij url-contest --atcoder agc001
https://atcoder.jp/contests/agc001

dir, dir-contest, dirc

与えられた問題コンテストに対応するディレクトリを表示します。

各サービスに対応するディレクトリ(絶対パス)は path で設定します。

| 問題の URL | 対応するディレクトリ | | ------------------------------------------------------------ | --------------------------------------- | | https://atcoder.jp/contests/{contest}/tasks/{problem} | path[atcoder]/{contest}/{problem}/ | | https://yukicoder.me/problems/no/{number} | path[yukicoder]/{number}/ | | https://codeforces.com/contest/{contest}/problem/{problem} | path[codeforces]/{contest}/{problem}/ |

# config.yml
path:
    atcoder: "~/contest/AtCoder"
    yukicoder: "~/contest/yukicoder"
    codeforces: "~/contest/Codeforces"
$ oij dir --atcoder agc001_a
/home/user/contest/AtCoder/agc001/agc001_a

$ oij dir-contest --atcoder agc001_a
/home/user/contest/AtCoder/agc001

bundle

設定されたコマンドに、与えられたファイルを渡して実行します。

コマンドは bundler で拡張子ごとに指定します(変数が使えます)。 ファイル分割されたプログラムを一つのファイルに展開するコマンド(C++ の場合は oj-bundle など)を指定することが想定されています。

bundler:
    cr: "cr-bundle ${file}"
    cpp: "oj-bundle -I path/to/your/library ${file}"

submit, s

与えられたファイルを現在のディレクトリに対応する問題に提出します。 bundler が設定されている場合はそのコマンドが出力した内容が提出されます。

/home/user/contest/AtCoder/agc001/agc001_a$ oij s a.rb
[INFO] $ oj submit https://atcoder.jp/contests/abc213/tasks/abc213_a a.rb
...

/home/user/contest/AtCoder/agc001/agc001_a$ oij s a.cr
[INFO] $ cr-bundle a.cr
[INFO] $ oj submit https://atcoder.jp/contests/abc213/tasks/abc213_a /tmp/bundled.e3lK5W.cr
...

download, d

与えられた問題の入出力例をその問題に対応するディレクトリにダウンロードします。 -- の後のオプションはそのまま oj download に渡されます。

/home/user/contest/AtCoder/agc001/agc001_a$ oij d
[INFO] $ oj download https://atcoder.jp/contests/agc001/tasks/agc001_a

/home/user/contest/AtCoder/agc001/agc001_a$ oij d --atcoder agc001_b
[INFO] $ oj download https://atcoder.jp/contests/abc213/tasks/abc213_b # at /home/user/contest/AtCoder/agc001/agc001_b

download-contest, dc

与えられたコンテストの各問題の入出力例をその問題に対応するディレクトリにダウンロードします。 --silent, -soj download の出力を非表示にできます。 -- の後のオプションはそのまま oj download に渡されます。

$ oij dc --atcoder abc005 -s
[INFO] Download https://atcoder.jp/contests/abc005/tasks/abc005_1 in /home/user/programming/contest/AtCoder/abc005/abc005_1
[INFO] Make directory: /home/user/programming/contest/AtCoder/abc005/abc005_1
[INFO] $ oj download https://atcoder.jp/contests/abc005/tasks/abc005_1  > /dev/null

[INFO] Download https://atcoder.jp/contests/abc005/tasks/abc005_2 in /home/user/programming/contest/AtCoder/abc005/abc005_2
[INFO] Make directory: /home/user/programming/contest/AtCoder/abc005/abc005_2
[INFO] $ oj download https://atcoder.jp/contests/abc005/tasks/abc005_2  > /dev/null

[INFO] Download https://atcoder.jp/contests/abc005/tasks/abc005_3 in /home/user/programming/contest/AtCoder/abc005/abc005_3
[INFO] Make directory: /home/user/programming/contest/AtCoder/abc005/abc005_3
[INFO] $ oj download https://atcoder.jp/contests/abc005/tasks/abc005_3  > /dev/null

[INFO] Download https://atcoder.jp/contests/abc005/tasks/abc005_4 in /home/user/programming/contest/AtCoder/abc005/abc005_4
[INFO] Make directory: /home/user/programming/contest/AtCoder/abc005/abc005_4
[INFO] $ oj download https://atcoder.jp/contests/abc005/tasks/abc005_4  > /dev/null

template

現在のディレクトリに --ext, -e で指定された言語(指定されなかった場合はすべて)のテンプレートを生成します。 テンプレートファイルを指定されたファイル名にそのままコピーします。

テンプレートファイル(絶対パス)とファイル名は template に拡張子ごとに指定します。

template:
    cr: ["~/.config/oij/template.cr", "a.cr"]
    cpp: ["~/.config/oij/template.cpp", "a.cpp"]
$ oij template -e cr
[INFO] Generate template file in {current path}/a.cr  # a.cr is same to template.cr

$ oij template # same to oij `template -e cr -e cpp`
[INFO] Generate template file in {current path}/a.cr  # a.cr  is same to template.cr
[INFO] Generate template file in {current path}/a.cpp # a.cpp is same to template.cpp

prepare, p

与えられた問題に対して downloadtemplate を実行して、最後に問題の URL を出力します。 -- の後のオプションはそのまま oj download に渡されます。

/home/user/contest/AtCoder/abc213$ oij p --atcoder abc213_a
[INFO] $ oj download https://atcoder.jp/contests/abc213/tasks/abc213_a  > /dev/null
[INFO] Generate template file in /home/user/programming/contest/AtCoder/abc213/abc213_a/a.cr
/home/user/programming/contest/AtCoder/abc213/abc213_a

/home/user/contest/AtCoder/abc213$ cd `oij p --atcoder abc213_a`
[INFO] $ oj download https://atcoder.jp/contests/abc213/tasks/abc213_a  > /dev/null
[INFO] Generate template file in /home/user/programming/contest/AtCoder/abc213/abc213_a/a.cr

/home/user/contest/AtCoder/abc213/abc213_a$ ls
a.cr test

prepare-contest, pc

与えられたコンテストの各問題について prepare を実行します。 -- の後のオプションはそのまま oj download に渡されます。

$ oij pc --atcoder abc006
[INFO] Prepare https://atcoder.jp/contests/abc006/tasks/abc006_1 in /home/user/programming/contest/AtCoder/abc006/abc006_1
[INFO] Make directory: /home/user/programming/contest/AtCoder/abc006/abc006_1
[INFO] $ oj download https://atcoder.jp/contests/abc006/tasks/abc006_1  > /dev/null
[INFO] Generate template file in /home/user/programming/contest/AtCoder/abc006/abc006_1/a.cr

[INFO] Prepare https://atcoder.jp/contests/abc006/tasks/abc006_2 in /home/user/programming/contest/AtCoder/abc006/abc006_2
[INFO] Make directory: /home/user/programming/contest/AtCoder/abc006/abc006_2
[INFO] $ oj download https://atcoder.jp/contests/abc006/tasks/abc006_2  > /dev/null
[INFO] Generate template file in /home/user/programming/contest/AtCoder/abc006/abc006_2/a.cr

[INFO] Prepare https://atcoder.jp/contests/abc006/tasks/abc006_3 in /home/user/programming/contest/AtCoder/abc006/abc006_3
[INFO] Make directory: /home/user/programming/contest/AtCoder/abc006/abc006_3
[INFO] $ oj download https://atcoder.jp/contests/abc006/tasks/abc006_3  > /dev/null
[INFO] Generate template file in /home/user/programming/contest/AtCoder/abc006/abc006_3/a.cr

[INFO] Prepare https://atcoder.jp/contests/abc006/tasks/abc006_4 in /home/user/programming/contest/AtCoder/abc006/abc006_4
[INFO] Make directory: /home/user/programming/contest/AtCoder/abc006/abc006_4
[INFO] $ oj download https://atcoder.jp/contests/abc006/tasks/abc006_4  > /dev/null
[INFO] Generate template file in /home/user/programming/contest/AtCoder/abc006/abc006_4/a.cr

generate-input, gi

入力をランダムに生成するプログラムを受け取って、テストケースの入力を生成します。 第二引数で生成するテストケースの数を指定できます(デフォルト: 100)。 一つの言語で複数のコンパイル・実行するコマンドを使う場合は --option, -o で切り替えられます。 -- の後のオプションはそのまま oj generate-input に渡されます。

$ oij gi generate.cr
[INFO] $ crystal build generate.cr
[INFO] $ oj generate-input ./generate 100
...

$ oij gi generate.cr 10 -- -d=dir
[INFO] $ crystal build generate.cr
[INFO] $ oj generate-input ./generate 10 -d=dir
...

generate-output, go

generate-input で生成したテストケースを解くプログラムを受け取って、テストケースの出力を生成します。 一つの言語で複数のコンパイル・実行するコマンドを使う場合は --option, -o で切り替えられます。 -- の後のオプションはそのまま oj generate-output に渡されます。

$ oij go solve.cr
[INFO] $ crystal build solve.cr
[INFO] $ oj generate-output -c ./solve
...

$ oij go solve.cr -- -d=dir
[INFO] $ crystal build solve.cr
[INFO] $ oj generate-output -c ./solve -d=dir
...

hack

間違っているプログラム 、入力を生成するプログラム、出力を生成するプログラムを受け取ってハックケースを生成します。 一つの言語で複数のコンパイル・実行するコマンドを使う場合は

で切り替えられます。また、全てのプログラムに適用したい場合は --option, -o を使います。

-- の後のオプションはそのまま oj generate-input に渡されます。

$ oij hack a.cr generate.cr solve.cr
[INFO] $ crystal build a.cr
[INFO] $ crystal build generate.cr
[INFO] $ crystal build solve.cr
[INFO] $ oj generate-input --hack-expected ./solve --hack ./a ./generate
...

複数のファイルをコンパイルするので、ファイル名によって生成する実行ファイルの名前を変えないと壊れることに注意してください。

# config.yml
compile:
    cpp:
        default: "g++ ${file}"
        hack: "g++ ${file} -o ${basename_no_extension}"

execute:
    cpp:
        default: "./a.out"
        hack: "./${basename_no_extension}"
$ oij hack a.cpp generate.cpp solve.cpp
[INFO] $ g++ a.cpp
[INFO] $ g++ generate.cpp
[INFO] $ g++ solve.cpp
[INFO] $ oj generate-input --hack-expected ./a.out --hack ./a.out ./a.out
(壊れる)

$ oij hack a.cpp generate.cpp solve.cpp -o hack
[INFO] $ g++ a.cpp -o a
[INFO] $ g++ gene.cpp -o gene
[INFO] $ g++ solve.cpp -o solve
[INFO] $ oj generate-input --hack-expected ./solve --hack ./a ./generate
...

変数

/home/user/directorydir/file.ext が渡されたとすると以下のように置き換えられます。

| 変数 | 内容 | | -------------------------- | ----------------------------------- | | ${file} | dir/file.ext | | ${basename} | file.ext | | ${dirname} | dir | | ${basename_no_extension} | file | | ${relative_file} | /home/user/directory/dir/file.ext | | ${relative_dirname} | /home/user/directory/dir |

問題の指定

オプションがない場合は現在のディレクトリに対応する問題が指定されたとみなされます。

| オプション | 問題 | | -------------------------------------------- | ----------------------------------------------------------------- | | --atcoder agc001/agc001_a | https://atcoder.jp/contests/agc001/tasks/agc001_a | | --atcoder agc001_a | https://atcoder.jp/contests/agc001/tasks/agc001_a | | --yukicoder 1 | https://yukicoder.me/problems/no/1 | | --codeforces 1000/A | https://codeforces.com/contest/1000/problem/A | | --next (at agc001/agc001_a) | https://atcoder.jp/contests/agc001/tasks/agc001_b | | --prev (at agc001/agc001_a) | https://atcoder.jp/contests/agc001/tasks/agc001_(invalid) | |--prev --strict(at agc001/agc001_a) | error | |--next(at typical90/typical90_z) | https://atcoder.jp/contests/typical90/tasks/typical90_{ (invalid) | |--next --strict` (at typical90/typical90_z) | https://atcoder.jp/contests/typical90/tasks/typical90_aa |

コンテストの指定

オプションがない場合は現在のディレクトリに対応するコンテストが指定されたとみなされます。

| オプション | コンテスト | | --------------------------------- | ----------------------------------------------- | | --atcoder agc001 | https://atcoder.jp/contests/agc001 | | --codeforces 1000 | https://codeforces.com/contest/1000 | | --next (at agc001/) | https://atcoder.jp/contests/agc002 | | --prev (at agc001/) | https://atcoder.jp/contests/agc000 (invalid) | | --prev --strict (at agc001/) | error | | --next (at typical90/) | https://atcoder.jp/contests/typical91 (invalid) | | --next --strict (at typical90/) | error |

input_file_mapping

exe, run において入力ファイル名を省略したいときに設定します。 input_file_mapping に パターン(正規表現)と置き換えられる文字列の配列を設定します。

String#sub が順番に適用されます。 また、正規表現についてはこちらをご覧ください。

# config.yml
input_file_mapping:
    - ["^s(\\d+)$", "test/sample-\\1.in"]
    - ["^r(\\d+)$", "test/random-\\1.in"]
    - ["^h(\\d+)$", "test/hack-\\1.in"]
$ oij exe a.rb s1
[INFO] $ ruby a.rb (input file: test/sample-1.in)

$ oij exe a.rb h001
[INFO] $ ruby a.rb (input file: test/random-001.in)

testcase_mapping

print-test, edit-test においてテストケース名(ディレクトリ、拡張子を含まない)を省略したいときに設定します。 testcase_mapping に パターン(正規表現)と置き換えられる文字列の配列を設定します。

String#sub が順番に適用されます。 また、正規表現についてはこちらをご覧ください。

# config.yml
input_file_mapping:
    - ["^s(\\d+)$", "sample-\\1"]
    - ["^r(\\d+)$", "random-\\1"]
    - ["^h(\\d+)$", "hack-\\1"]

editor: "vim"
$ oij edit-test s1
[INFO] $ vim test/sample-1.in test/sample-1.out

$ oij edit-test r001 -d dir
[INFO] $ vim dir/random-001.in dir/random-001.out

プログラムファイルの推測

compile, exe, run, test, t, bundle, submit はプログラムファイルを指定しなかった場合、現在のディレクトリにあり、名前に . が含まれているファイルの内、変更日時が一番新しいものが渡されます。

exe, run に一つの引数が与えられた場合、. が含まれていて、拡張子が config.ymlcompileexecute にある場合はプログラムファイルだとみなされ、そうでない場合は入力ファイルだとみなされます。

# config.yml
compile:
    cpp: "g++ ${file}"

execute:
    cpp: "./a.out"
$ oij run # same to `oij run a.cpp`
[INFO] $ g++ a.cpp
[INFO] $ ./a.out

$ oij run a.cpp
[INFO] $ g++ a.cpp
[INFO] $ ./a.out

$ oij run test/sample-1.in # same to `oij run a.cpp test/sample-1.in`
[INFO] $ g++ a.cpp
[INFO] $ ./a.out (input file: test/sample-1.in)

$ oij run a.rb # same to `oij run a.cpp a.rb`
[INFO] $ g++ a.cpp
[INFO] $ ./a.out (input file: a.rb)

$ oij run a.cpp test/sample-1.in
[INFO] $ g++ a.cpp
[INFO] $ ./a.out (input file: test/sample-1.in)

config.yml の例

compile:
    cpp:
        default: "g++ ${file}"
        fast: "g++ -O3 ${file}"
        hack: "g++ ${file} -o ${basename_no_extension}"
    cr:
        default: "crystal build ${file}"
        release: "crystal build ${file} --no-debug --release"
    rb: "ruby -wc ${file}"

execute:
    cpp:
        default: "./a.out"
        fast: "./a.out"
        hack: "./${basename_no_extension}"
    cr: "./${basename_no_extension}"
    rb: "ruby ${file}"
    py: "python3 ${file}"
    txt: "cat ${file}"

path:
    atcoder: "~/contest/AtCoder"
    yukicoder: "~/contest/yukicoder"
    codeforces: "~/contest/Codeforces"

bundler:
    cpp: "oj-bundle -I path/to/your/library ${file}"
    cr: "cr-bundle -f ${file}"

template:
    cpp: ["~/.config/oij/template.cpp", "a.cpp"]
    cr: ["~/.config/oij/template.cr", "a.cr"]

editor: "vim"

input_file_mapping:
    - ["^s(\\d+)$", "test/sample-\\1.in"]
    - ["^r(\\d+)$", "test/random-\\1.in"]
    - ["^h(\\d+)$", "test/hack-\\1.in"]

testcase_mapping:
    - ["^s(\\d+)$", "sample-\\1"]
    - ["^r(\\d+)$", "random-\\1"]
    - ["^h(\\d+)$", "hack-\\1"]

Contributing

  1. Fork it (https://github.com/yuruhi/oij/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

Contributors