Skip to content

Latest commit

 

History

History
965 lines (761 loc) · 32.3 KB

README-ja.md

File metadata and controls

965 lines (761 loc) · 32.3 KB

cargo-compete

CI codecov dependency status Crates.io Crates.io Join the chat at https://gitter.im/cargo-compete/community

English

競技プログラミングのためのCargoコマンドです。

AtCoder、Codeforces、yukicoderをサポートしています。 その他のサイトもonline-judge-tools/api-clientを使うことで利用可能です。

機能

  • サイトへのログイン
  • (自動で)コンテストへの参加登録
  • サンプルテストケース/システムテストケースを取得し、YAMLで保存
  • コードのテスト
  • 提出
  • 提出一覧のストリーミング (AtCoderのみ)
参加登録 サンプルテストケース システムテストケース 提出 提出一覧をwatch 提出の詳細
AtCoder ✔️ ✔️ ✔️ ✔️
Codeforces ✔️ N/A ✔️
yukicoder N/A ✔️ ✔️ ✔️
その他のサイト online-judge-tools次第 online-judge-tools次第 online-judge-tools次第

インストール

Crates.ioからインストール

$ cargo install cargo-compete

ビルドが失敗するなら、--lockedを付けると成功する場合があります。

masterブランチからインストール

$ cargo install --git https://github.com/qryxip/cargo-compete

GitHub Releasesからバイナリをダウンロード

バイナリでの提供もしています。

使い方

cargo compete init

他のコマンドのためにいくつかのファイルを生成します。

最初に実行してください。 生成するファイルは以下の通りです。

  • compete.toml

    他のコマンドに必要です。cargo-atcoderのように自動で生成しません。

  • .cargo/config.toml

    build/target-dirを設定し、targetディレクトリを共有するようにします。

  • template-cargo-lock.toml

    cargo compete newに使うCargo.lockのテンプレートです。 質問に「AtCoderでクレートを使用するがバイナリ提出はしない」と回答した場合のみ生成されます。 生成された場合、compete.tomlnew.template.lockfileにこのファイルへのパスが追加されます。

Screenshot

cargo compete migrate cargo-atcoder

cargo-atcoderで作ったパッケージをそれぞれcargo-compete用にマイグレートし、compete.toml等のファイルも追加します。

TODO: ↓のスクショをアップデート。今package.metadata.cargo-compete.bin.*.problemはURLの文字列です。

Screenshot

cargo compete login

サイトにログインします。

パッケージを対象に取りません。 引数で与えられたplatformに対してログインします。

ただしnewコマンド等ではログインが必要になった場合でも認証情報を聞いてログインし、続行するため事前にこのコマンドを実行しなくてもよいです。

cargo compete participate

コンテストに参加登録します。

パッケージを対象に取りません。 引数で与えられたplatformcontestに対して参加登録します。

(loginコマンドと同様に、)newコマンド等で自動で参加登録するため事前にこのコマンドを実行しなくてもよいです。

cargo compete new

テストケースを取得し、コンテストに応じたパッケージを作ります。

compete.tomlが必要です。 最初にcargo compete initで生成してください。

--openで問題のページをブラウザで開きます。 またcompete.tomlopenを設定することで、ソースコードとテストケースのYAMLをエディタで開くことができます。 --openを付け忘れた場合は生成されたパッケージにcdした後にcargo compete openで開いてください。

Record

.cargo/config.tomlによりtarget directoryが共有されるので、クレートを使う場合も初回を除いて"warmup"は不要です。

cargo compete add

コンテストもしくは問題に対してbinターゲットを生成し、テストケースをダウンロードします。

compete.tomlが必要です。 最初にcargo compete initで生成してください。

設定はcompete.tomladdで行ってください。

# for yukicoder
[add]
url = '{% case args[0] %}{% when "contest" %}https://yukicoder.me/contests/{{ args[1] }}{% when "problem" %}https://yukicoder.me/problems/no/{{ args[1] }}{% endcase %}'
is-contest = ["bash", "-c", '[[ $(cut -d / -f 4) == "contests" ]]'] # optional
bin-name = '{% assign segments = url | split: "/" %}{{ segments[5] }}'
#bin-alias = '{% assign segments = url | split: "/" %}{{ segments[5] }}' # optional
#bin-src-path = './src/bin/{{ bin_alias }}.rs' # optional
cargo compete a contest 296
    Added `1358` (bin) for https://yukicoder.me/problems/no/1358
    Added `1359` (bin) for https://yukicoder.me/problems/no/1359
    Added `1360` (bin) for https://yukicoder.me/problems/no/1360
    Added `1361` (bin) for https://yukicoder.me/problems/no/1361
    Added `1362` (bin) for https://yukicoder.me/problems/no/1362
    Added `1363` (bin) for https://yukicoder.me/problems/no/1363
    Added `1364` (bin) for https://yukicoder.me/problems/no/1364
    Added `1365` (bin) for https://yukicoder.me/problems/no/1365
    Saved 1 test case to /home/ryo/src/competitive/yukicoder/testcases/1358.yml
    Saved 3 test cases to /home/ryo/src/competitive/yukicoder/testcases/1359.yml
    Saved 3 test cases to /home/ryo/src/competitive/yukicoder/testcases/1360.yml
    Saved 3 test cases to /home/ryo/src/competitive/yukicoder/testcases/1361.yml
    Saved 3 test cases to /home/ryo/src/competitive/yukicoder/testcases/1362.yml
    Saved 1 test case to /home/ryo/src/competitive/yukicoder/testcases/1363.yml
    Saved 3 test cases to /home/ryo/src/competitive/yukicoder/testcases/1364.yml
    Saved 3 test cases to /home/ryo/src/competitive/yukicoder/testcases/1365.ymlcargo compete a problem 9001
    Added `9001` (bin) for https://yukicoder.me/problems/no/9001
    Saved 1 test case to /home/ryo/src/competitive/yukicoder/testcases/9001.yml

cargo compete retrieve testcases / cargo compete download

テストケースの再取得を行います。

パッケージを対象に取ります。 パッケージにcdして実行してください。

Screenshot

プラットフォームが使っているテストケースを公開している場合、--fullを指定することでそちらをダウンロードすることができます。

AtCoderの場合、テストケースはDropboxにアップロードされているのでそちらからダウンロードします。ただしDropbox APIを使用するため

  • files.metadata.read
  • sharing.read

の2つのパーミッションが有効なアクセストークンが必要です。 何らかの方法でアクセストークンを取得し、以下の形式のJSONファイルを{local data directory}/cargo-compete/tokens/dropbox.jsonに保存してください。 (この辺はなんとかしたいと考えてます)

{
  "access_token": "<access token>"
}

asciicast

cargo compete retrieve submission-summaries

自分の提出の一覧を取得し、JSONで出力します。

パッケージを対象に取ります。 パッケージにcdして実行してください。

asciicast

例えばAtCoderであれば(AtCoderしか実装してませんが)| jq -r '.summaries[0].detailとすることで「最新の提出の詳細ページのURL」が得られます。

$ # 最新の提出の詳細ページをブラウザで開く (Linuxの場合)
$ xdg-open "$(cargo compete r ss | jq -r '.summaries[0].detail')"

cargo compete open

new--openと同様に問題のページをブラウザで、コードとテストファイルをエディタで開きます。

パッケージを対象に取ります。 パッケージにcdして実行してください。

cargo compete test

テストを行います。

パッケージを対象に取ります。 パッケージにcdして実行してください。

submit時にも提出するコードをテストするため、提出前にこのコマンドを実行しておく必要はありません。

cargo compete submit

提出を行います。

パッケージを対象に取ります。 パッケージにcdして実行してください。

asciicast

compete.tomlsubmitを設定することで、cargo-equipcargo-executable-payload等のコード変換ツールを使って提出するコードを変換できます。

[submit]
kind = "command"
args = ["cargo", "+1.70.0", "equip", "--exclude-atcoder-202301-crates", "--remove", "docs", "--minify", "libs", "--bin", "{{ bin_name }}"]
language_id = "5054"
[submit]
kind = "command"
args = ["cargo", "executable-payload", "--bin", "{{ bin_name }}"]
language_id = "5054"

設定

設定は各ワークスペース下にあるcompete.tomlにあります。

# Path to the test file (Liquid template)
#
# Variables:
#
# - `manifest_dir`: Package directory
# - `contest`:      Contest ID (e.g. "abc100")
# - `bin_name`:     Name of a `bin` target (e.g. "abc100-a")
# - `bin_alias`:    "Alias" for a `bin` target defined in `pacakge.metadata.cargo-compete` (e.g. "a")
# - `problem`:      Alias for `bin_alias` (deprecated)
#
# Additional filters:
#
# - `kebabcase`: Convert to kebab case (by using the `heck` crate)
test-suite = "{{ manifest_dir }}/testcases/{{ bin_alias }}.yml"

# Open files with the command (`jq` command that outputs `string[] | string[][]`)
#
# VSCode:
#open = '[["code", "-a", .manifest_dir], ["code"] + (.paths | map([.src, .test_suite]) | flatten)]'
# Emacs:
#open = '["emacsclient", "-n"] + (.paths | map([.src, .test_suite]) | flatten)'

[template]
src = '''
fn main() {
    todo!();
}
'''

[template.new]
# `edition` for `Cargo.toml`.
edition = "2018"
# `profile` for `Cargo.toml`.
#
# By setting this, you can run tests with `opt-level=3` while enabling `debug-assertions` and `overflow-checks`.
#profile = '''
#[dev]
#opt-level = 3
#'''
dependencies = '''
num = "=0.2.1"
num-bigint = "=0.2.6"
num-complex = "=0.2.4"
num-integer = "=0.1.42"
num-iter = "=0.1.40"
num-rational = "=0.2.4"
num-traits = "=0.2.11"
num-derive = "=0.3.0"
ndarray = "=0.13.0"
nalgebra = "=0.20.0"
alga = "=0.9.3"
libm = "=0.2.1"
rand = { version = "=0.7.3", features = ["small_rng"] }
getrandom = "=0.1.14"
rand_chacha = "=0.2.2"
rand_core = "=0.5.1"
rand_hc = "=0.2.0"
rand_pcg = "=0.2.1"
rand_distr = "=0.2.2"
petgraph = "=0.5.0"
indexmap = "=1.3.2"
regex = "=1.3.6"
lazy_static = "=1.4.0"
ordered-float = "=1.0.2"
ascii = "=1.0.0"
permutohedron = "=0.2.4"
superslice = "=1.0.0"
itertools = "=0.9.0"
itertools-num = "=0.1.3"
maplit = "=1.0.2"
either = "=1.5.3"
im-rc = "=14.3.0"
fixedbitset = "=0.2.0"
bitset-fixed = "=0.1.0"
proconio = { version = "=0.3.6", features = ["derive"] }
text_io = "=0.1.8"
whiteread = "=0.5.0"
rustc-hash = "=1.1.0"
smallvec = "=1.2.0"
'''
dev-dependencies = '''
#atcoder-202004-lock = { git = "https://github.com/qryxip/atcoder-202004-lock" }
'''

[template.new.copy-files]
"./template-cargo-lock.toml" = "Cargo.lock"

[new]
kind = "cargo-compete"
# Platform
#
# - atcoder
# - codeforces
# - yukicoder
platform = "atcoder"
# Path (Liquid template)
#
# Variables:
#
# - `contest`:      Contest ID. **May be nil**
# - `package_name`: Package name
path = "./{{ contest }}"

#[new]
#kind = "oj-api"
#url = "https://atcoder.jp/contests/{{ id }}"
#path = "./{{ contest }}"

# for Library-Checker
#[add]
#url = "https://judge.yosupo.jp/problem/{{ args[0] }}"
##is-contest = ["false"] # optional
#bin-name = '{{ args[0] }}'
##bin-alias = '{{ args[0] }}' # optional
##bin-src-path = './src/bin/{{ bin_alias }}.rs' # optional

# for yukicoder
#[add]
#url = '{% case args[0] %}{% when "contest" %}https://yukicoder.me/contests/{{ args[1] }}{% when "problem" %}https://yukicoder.me/problems/no/{{ args[1] }}{% endcase %}'
#is-contest = ["bash", "-c", '[[ $(cut -d / -f 4) == "contests" ]]'] # optional
#bin-name = '{% assign segments = url | split: "/" %}{{ segments[5] }}'
##bin-alias = '{% assign segments = url | split: "/" %}{{ segments[5] }}' # optional
##bin-src-path = './src/bin/{{ bin_alias }}.rs' # optional

[test]
# Toolchain for the test. (optional)
toolchain = "1.42.0"
# Profile for `cargo build`. ("dev" | "release")
#
# Defaults to `"dev"`.
#profile = "dev"

[submit]
kind = "file"
path = "{{ src_path }}"
language_id = "5054"
#[submit]
#kind = "command"
#args = ["cargo", "+1.70.0", "equip", "--exclude-atcoder-202301-crates", "--remove", "docs", "--minify", "libs", "--bin", "{{ bin_name }}"]
#language_id = "5054"

bin targetに紐付くサイト上の問題は、パッケージのCargo.toml[package.metadata]に記述されます。

[package]
name = "practice"
version = "0.1.0"
authors = ["Ryo Yamashita <qryxip@gmail.com>"]
edition = "2018"

[package.metadata.cargo-compete.bin]
practice-a = { alias = "a", problem = "https://atcoder.jp/contests/practice/tasks/practice_1" }
practice-b = { alias = "b", problem = "https://atcoder.jp/contests/practice/tasks/practice_2" }

#[package.metadata.cargo-compete.example]

[[bin]]
name = "practice-a"
path = "src/bin/a.rs"

[[bin]]
name = "practice-b"
path = "src/bin/b.rs"

[dependencies]
num = "=0.2.1"
num-bigint = "=0.2.6"
num-complex = "=0.2.4"
num-integer = "=0.1.42"
num-iter = "=0.1.40"
num-rational = "=0.2.4"
num-traits = "=0.2.11"
num-derive = "=0.3.0"
ndarray = "=0.13.0"
nalgebra = "=0.20.0"
alga = "=0.9.3"
libm = "=0.2.1"
rand = { version = "=0.7.3", features = ["small_rng"] }
getrandom = "=0.1.14"
rand_chacha = "=0.2.2"
rand_core = "=0.5.1"
rand_hc = "=0.2.0"
rand_pcg = "=0.2.1"
rand_distr = "=0.2.2"
petgraph = "=0.5.0"
indexmap = "=1.3.2"
regex = "=1.3.6"
lazy_static = "=1.4.0"
ordered-float = "=1.0.2"
ascii = "=1.0.0"
permutohedron = "=0.2.4"
superslice = "=1.0.0"
itertools = "=0.9.0"
itertools-num = "=0.1.3"
maplit = "=1.0.2"
either = "=1.5.3"
im-rc = "=14.3.0"
fixedbitset = "=0.2.0"
bitset-fixed = "=0.1.0"
proconio = { version = "=0.3.6", features = ["derive"] }
text_io = "=0.1.8"
whiteread = "=0.5.0"
rustc-hash = "=1.1.0"
smallvec = "=1.2.0"

[dev-dependencies]

テストファイルのYAML

テストケースは以下のような形でYAMLに保存されます。

# https://atcoder.jp/contests/practice/tasks/practice_1
---
type: Batch
timelimit: 2s
match: Lines

cases:
  - name: sample1
    in: |
      1
      2 3
      test
    out: |
      6 test
  - name: sample2
    in: |
      72
      128 256
      myonmyon
    out: |
      456 myonmyon

extend:
  - type: Text
    path: "./a"
    in: /in/*.txt
    out: /out/*.txt
# https://atcoder.jp/contests/ddcc2019-final/tasks/ddcc2019_final_a
---
type: Batch
timelimit: 2s
match:
  Float:
    relative_error: 1e-8
    absolute_error: 1e-8

cases:
  - name: sample1
    in: |
      5
      -->--
    out: |
      3.83333333333333
  - name: sample2
    in: |
      7
      -------
    out: |
      6.5
  - name: sample3
    in: |
      10
      -->>>-->--
    out: |
      6.78333333333333

extend:
  - type: Text
    path: "./a"
    in: /in/*.txt
    out: /out/*.txt
# https://judge.yosupo.jp/problem/sqrt_mod
---
type: Batch
timelimit: 10s
match:
  Checker:
    cmd: ~/.cache/online-judge-tools/library-checker-problems/math/sqrt_mod/checker "$INPUT" "$ACTUAL_OUTPUT" "$EXPECTED_OUTPUT"
    shell: Bash

cases: []

extend:
  - type: SystemTestCases

形式は以下のスキーマにおけるTestSuiteです。

TestSuite

typeをタグとしたinternally taggedのADTです。

TestSuite::Batch

通常の問題に対するテストスイートです。

フィールド デフォルト 説明
timelimit Duration | null ~ 実行時間制限
match Match 出力の判定方法
cases Case[] [] 入出力のセット
extend Extend[] [] 入出力のセットの追加

Duration

humantime::format_durationでパースできる文字列です。

Match

untaggedなADTです。

Match::Exact = "Exact"

文字列全体の一致で判定します。

Match::SplitWhiteSpace = "SplitWhitespace"

空白区切りでの単語の一致で判定します。

Match::Lines = "Lines"

各行の一致で判定します。

Match::Float

空白区切りでの単語の一致で判定します。

この際数値として読める単語は浮動小数点数とみなし、誤差を許容します。

フィールド デフォルト 説明
relative_error PositiveFiniteFloat64 | null ~ 相対誤差
absolute_error PositiveFiniteFloat64 | null ~ 絶対誤差

PositiveFiniteFloat64

正かつinfではない64-bitの浮動小数点数です。

Match::Checker

フィールド デフォルト 説明
cmd str コマンド
shell Shell シェル

Shell

untaggedなADTです。

Shell::Bash = "Bash"

Bashです。

Case

フィールド デフォルト 説明
name str "" 名前
in str 入力
out str | null ~ 出力
timelimit Duration | null ~ timelimitをオーバーライド
match Match | null ~ matchをオーバーライド

Extend

typeをタグとしたinternally taggedのADTです。

Extend::Text

フィールド デフォルト 説明
path str ディレクトリ
in Glob 入力のテキストファイル
out Glob 出力のテキストファイル
timelimit Duration | null ~ timelimitをオーバーライド
match Match | null ~ matchをオーバーライド

Glob

globを示す文字列です。

Extend::SystemTestCases

システムテストケースです。

システムテストケースは { cache directory }/cargo-compete/system-test-cases下に保存されます。 test時に見つからない場合、自動でダウンロードされます。

フィールド デフォルト 説明
problem Url | null ~ 問題のURL

Url

URLを示す文字列です。

TestSuite::Interactive

フィールド デフォルト 説明
timelimit Duration | null ~ 実行時間制限

TestSuite::Unsubmittable

APG4bの問題のようなもののためのダミーのテストスイートです。

フィールド デフォルト 説明

Cookieとトークン

Cookieと各トークンは{ local data directory }/cargo-compete下に保存されています。

.
├── cookies.jsonl
└── tokens
    ├── codeforces.json
    ├── dropbox.json
    └── yukicoder.json

環境変数

cargo-competeは以下の環境変数が存在する場合、それらを読んで使います。

  • $DROPBOX_ACCESS_TOKEN
  • $YUKICODER_API_KEY
  • $CODEFORCES_API_KEY
  • $CODEFORCES_API_SECRET

download時とsubmit時に対象のURLがサポートされていないサイトを指しているのなら、$PATH内にあるoj-api(.exe)が使われます。

[package]
name = "library-checker"
version = "0.0.0"
edition = "2018"
publish = false

[package.metadata.cargo-compete.bin]
aplusb = { problem = "https://judge.yosupo.jp/problem/aplusb" }

Video

cargo-atcoderとの対応

cargo atcoder new

cargo compete newでパッケージを作成します。

compete.tomlを起点とします。 cargo compete initcargo compete migrate cargo-atcoderで作成してください。

なお、開始前のコンテストには使えません。 targetディレクトリを共有する限り"warmup"が不要なためです。 ブラウザとエディタを開くのも--openで自動で行えます。

cargo atcoder submit

cargo compete submitでソースコードを提出します。

他のコマンドと同様に、ワークスペース下にcompete.tomlがある必要があります。

「バイナリ提出」を行う場合、cargo-executable-payloadを使うようにcompete.tomlsubmit.transpileを設定してください。

cargo atcoder test

cargo compete testでテストを実行します。

cargo-atcoderと同様にパッケージを対象に取ります。

一部のテストのみを実行する場合は、<case-num>...の代わりに--testcases <NAME>..."sample1"等の「名前」で絞ります。

cargo atcoder login

cargo compete loginでログインします。

cargo atcoder status

cargo compete watch submission-summariesで提出一覧をwatchします。

cargo-competeの方はブラウザ上の表示に近い挙動をするため、実行時点で「ジャッジ待ち」/「ジャッジ中」のものが無い場合には直近20件を表示だけして終了します。

cargo atcoder result

今のところありません。 cargo compete watch submission-summariesの出力を| jq -r ".summaries[$nth].detail"して得たURLをブラウザで開いてください。

cargo atcoder clear-session

今のところありません。 local data directory下のcargo-competeを削除してください。

cargo atcoder info

今のところありません。 ログインしているかを確認する場合、practice contestのテストケースをダウンロードしてください。 practice contestの場合問題の閲覧にログインが必要です。

cargo atcoder warmup

今のところありません。 上で述べた通り、targetディレクトリを共有する場合初回を除きwarmupは不要です。

cargo atcoder gen-binary

cargo-executable-payloadを使ってください。

ライセンス

MIT or Apache-2.0のデュアルライセンスです。