cargo-compete
A Cargo subcommand for competitive programming.
Supports AtCoder, Codeforces, and yukicoder. Other websites are available via online-judge-tools/api-client.
Features
- Log in a website,
- (Automatically) register in a contest,
- Retrieves sample/system test cases, and save them as YAML files,
- Test your code for the YAML files,
- Submit your code,
- Watch your submissions. (available for only AtCoder)
Registeration | Sample Test Cases | System Test Cases | Submiting | Watching Submissions | Submission Details | |
---|---|---|---|---|---|---|
AtCoder | βοΈ | β | ||||
Codeforces | N/A | βοΈ | β | β | ||
yukicoder | N/A | βοΈ | ||||
Other websites | Depends on online-judge-tools | Depends on online-judge-tools | Depends on online-judge-tools | β |
Installation
From Crates.io
$ cargo install cargo-compete
If the build fails, adding --locked
may help.
master
branch
From $ cargo install --git https://github.com/qryxip/cargo-compete
From GitHub Releases
We provide the binaries in GitHub Releases.
Usages
cargo compete init
Generates some files for other commands.
Run this command first. It generates the following files.
-
Required for other commands.
-
Sets
build/target-dir
to share thetarget
directory. -
template-cargo-lock.toml
A template of
Cargo.lock
forcargo compete new
. Generated only if you answer2 Yes
toDo you use crates on AtCoder?
question. If this file is generated, file path to it is added tonew.template.lockfile
incompete.toml
.
cargo compete migrate cargo-atcoder
See the section in the Japanese readme.
cargo compete login
Logges in a website.
This is not a command for a package.
You don't have to run this command beforehand, because cargo-compete asks credentials if necessary.
cargo compete participate
Registeres in a contest.
This is not a command for a package.
You don't have to run this command beforehand, because cargo-compete registers in the contest if necessary.
cargo compete new
Retrieves test cases and creates a package for the contest.
Requires compete.toml
.
Generate it with cargo compete init
first.
You can opens the pages in your browser with the --open
option.
And you can also open the source files and the test cases in your browser by testing open
in compete.toml
.
If you forget to add --open
, cd
to the generated package and run cargo compete open
.
cargo compete add
Generates bin
targets and retrieves the test cases for them.
Requires compete.toml
.
Generate it with cargo compete init
first.
To use this function, configure add
in the compete.toml
like this.
# 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
#target-kind = "bin" # ["bin", "example"]. default to "bin"
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.yml
β― cargo 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
Retrieves test cases for an existing package.
This is a command for a package.
cd
to the package generated with cargo compete new
.
With --open
option, you can download system test cases instead of sample ones.
For AtCoder, we have to use Dropbox API. Generate an access token with these two permissions in some way,
files.metadata.read
sharing.read
and save a JSON file in the following format to {local data directory}/cargo-compete/tokens/dropbox.json
.
(I'm thinking of better way)
{
"access_token": "<access token>"
}
cargo compete retrieve submission-summaries
Retrieves your submissions, and outputs as JSON.
This is a command for a package.
cd
to the package generated with cargo compete new
.
For example, you can get "the URL for the latest submission" by adding | jq -r '.summaries[0].detail
.
$ # for Linux
$ xdg-open "$(cargo compete r ss | jq -r '.summaries[0].detail')"
cargo compete open
Opens pages in your browser, and opens source and test cases in your editor.
This is a command for a package.
cd
to the package generated with cargo compete new
.
cargo compete test
Runs tests.
This is a command for a package.
cd
to the package generated with cargo compete new
.
You don't have to run this command beforehand, because the tests are run in the submit
command.
cargo compete submit
Submits your code.
This is a command for a package.
cd
to the package generated with cargo compete new
.
You can convert code with a tool such as cargo-equip and cargo-executable-payload by setting submit.transpile
in the compete.toml
.
[submit.transpile]
kind = "command"
args = ["cargo", "equip", "--exclude-atcoder-crates", "--resolve-cfgs", "--remove", "docs", "--minify", "libs", "--rustfmt", "--check", "--bin", "{{ bin_name }}"]
#language_id = ""
[submit.transpile]
kind = "command"
args = ["cargo", "executable-payload", "--bin", "{{ bin_name }}"]
#language_id = ""
Configuration
Here is an example for 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
##target-kind = "bin" # ["bin", "example"]. default to "bin"
#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
##target-kind = "bin" # ["bin", "example"]. default to "bin"
#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.transpile]
#kind = "command"
#args = ["cargo", "equip", "--exclude-atcoder-crates", "--remove", "docs", "--minify", "libs", "--bin", "{{ bin_name }}"]
##language_id = ""
And here is an example for package.metadata
in Cargo.toml
.
[package]
name = "practice"
version = "0.1.0"
authors = ["Ryo Yamashita <[email protected]>"]
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]
Test suite
Test cases are saved as YAML files.
# 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
The format is TestSuite
in the following schemas.
TestSuite
TestSuite::Batch
A test suite for a normal problem.
Field | Type | Default | Description |
---|---|---|---|
timelimit |
Duration | null |
~ |
Time limit |
match |
Match |
Judging method | |
cases |
Case[] |
[] |
Sets of input and output |
extend |
Extend[] |
[] |
Additional sets of input and output |
Duration
A string that can parsed with humantime::format_duration
.
Match
An untagged ADT.
Match::Exact
= "Exact"
Compares whole strings.
Match::SplitWhiteSpace
= "SplitWhitespace"
Compares words splitted by whitespace.
Match::Lines
= "Lines"
Compares lines.
Match::Float
Compares words splitted by whitespace.
absolute_error
and relative_error
are applied for pairs of words that can parsed as floating point numbers.
Field | Type | Default | Description |
---|---|---|---|
relative_error |
PositiveFiniteFloat64 | null |
~ |
Relative error |
absolute_error |
PositiveFiniteFloat64 | null |
~ |
Absolute error |
PositiveFiniteFloat64
A 64-bit floating point number that is positive and is not inf
.
Match::Checker
Checks with a shell script.
The following environment variables are given for the script.
INPUT
ACTUAL_OUTPUT
EXPECTED_OUTPUT
(only if theCase.out
is present)
Field | Type | Default | Description |
---|---|---|---|
cmd |
str |
Command | |
shell |
Shell |
Shell |
Shell
An untagged ADT.
Shell::Bash
= "Bash"
Bash.
Case
Field | Type | Default | Description |
---|---|---|---|
name |
str |
"" |
Name |
in |
str |
Input | |
out |
str | null |
~ |
Output |
timelimit |
Duration | null |
~ |
Overrides timelimit |
match |
Match | null |
~ |
Overrides match |
Extend
Extend::Text
Field | Type | Default | Description |
---|---|---|---|
path |
str |
Directory | |
in |
Glob |
Text files for input | |
out |
Glob |
Text files for output | |
timelimit |
Duration | null |
~ |
Overrides timelimit |
match |
Match | null |
~ |
Overrides match |
Glob
A glob.
Extend::SystemTestCases
System test cases.
System test cases are stored under { cache directory }/cargo-compete/system-test-cases
.
They are automatically downloaded if missing when test
ing code.
Field | Type | Default | Description |
---|---|---|---|
problem |
Url | null |
~ | URL of the problem |
Url
A URL.
TestSuite::Interactive
A test suite for an interactive problem.
Field | Type | Default | Description |
---|---|---|---|
timelimit |
Duration | null |
~ |
Time limit |
TestSuite::Unsubmittable
A dummy test suite for dummy problems such as ones in APG4b.
Field | Type | Default | Description |
---|
Cookies and tokens
The cookies and tokens are saved under { local data directory }/cargo-compete
.
.
βββ cookies.jsonl
βββ tokens
βββ codeforces.json
βββ dropbox.json
βββ yukicoder.json
Environment variables
cargo-compete reads these environment variables if they exist, and use them.
$DROPBOX_ACCESS_TOKEN
$YUKICODER_API_KEY
$CODEFORCES_API_KEY
$CODEFORCES_API_SECRET
online-judge-tools
WithFor unsupported websites, oj-api(.exe)
in the $PATH
is used when download
ing and submit
ting.
[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" }
Compared with cargo-atcoder
See the section in the Japanese readme.
License
Dual-licensed under MIT or Apache-2.0.