cargo-compete

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

日本語

A Cargo subcommand for competitive programming.

Supports AtCoder, Codeforces, and yukicoder. Other websites are available via online-judge-tools/api-client.

Features

| | Registeration | Sample Test Cases | System Test Cases | Submiting | Watching Submissions | Submission Details | | :------------: | :--------------------: | :---------------------------: | :---------------------------: | :---------------------------: | :---------------------: | :----------------: | | AtCoder | :heavycheckmark: | :heavycheckmark: | :heavycheckmark: | :heavycheckmark: | :greyquestion: | :x: | | Codeforces | :x: | :heavycheckmark: | N/A | :heavycheckmark: | :x: | :x: | | yukicoder | N/A | :heavycheckmark: | :heavycheckmark: | :heavycheck_mark: | :x: | :x: | | Other websites | :x: | Depends on online-judge-tools | Depends on online-judge-tools | Depends on online-judge-tools | :x: | :x: |

Installation

From Crates.io

console $ cargo install cargo-compete

If the build fails, adding --locked may help.

From master branch

console $ 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.

Screenshot

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.

Record

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.

```toml

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

```

console ❯ 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.

Screenshot

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,

and save a JSON file in the following format to {local data directory}/cargo-compete/tokens/dropbox.json. (I'm thinking of better way)

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

asciicast

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.

asciicast

For example, you can get "the URL for the latest submission" by adding | jq -r '.summaries[0].detail.

console $ # 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.

asciicast

You can convert code with a tool such as cargo-equip and cargo-executable-payload by setting submit.transpile in the compete.toml.

```toml [submit.transpile] kind = "command" args = ["cargo", "equip", "--exclude-atcoder-crates", "--resolve-cfgs", "--remove", "docs", "--minify", "libs", "--rustfmt", "--check", "--bin", "{{ bin_name }}"]

language_id = ""

```

```toml [submit.transpile] kind = "command" args = ["cargo", "executable-payload", "--bin", "{{ bin_name }}"]

language_id = ""

```

Configuration

Here is an example for compete.toml.

```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 = "{{ manifestdir }}/testcases/{{ binalias }}.yml"

Open files with the command (jq command that outputs string[] | string[][])

#

VSCode:

open = '[["code", "-a", .manifestdir], ["code"] + (.paths | map([.src, .testsuite]) | flatten)]'

Emacs:

open = '["emacsclient", "-n"] + (.paths | map([.src, .test_suite]) | flatten)'

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

[template.new]

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 = ["smallrng"] } getrandom = "=0.1.14" randchacha = "=0.2.2" randcore = "=0.5.1" randhc = "=0.2.0" randpcg = "=0.2.1" randdistr = "=0.2.2" petgraph = "=0.5.0" indexmap = "=1.3.2" regex = "=1.3.6" lazystatic = "=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"] } textio = "=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]

Profile for cargo build. ("dev" | "release")

#

Defaults to "dev".

profile = "dev"

[submit.transpile]

kind = "command"

args = ["cargo", "equip", "--exclude-atcoder-crates", "--resolve-cfgs", "--remove", "docs", "--minify", "libs", "--rustfmt", "--check", "--bin", "{{ bin_name }}"]

language_id = ""

```

And here is an example for package.metadata in Cargo.toml.

```toml [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/practice1" } practice-b = { alias = "b", problem = "https://atcoder.jp/contests/practice/tasks/practice2" }

[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 = ["smallrng"] } getrandom = "=0.1.14" randchacha = "=0.2.2" randcore = "=0.5.1" randhc = "=0.2.0" randpcg = "=0.2.1" randdistr = "=0.2.2" petgraph = "=0.5.0" indexmap = "=1.3.2" regex = "=1.3.6" lazystatic = "=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"] } textio = "=0.1.8" whiteread = "=0.5.0" rustc-hash = "=1.1.0" smallvec = "=1.2.0" ```

Test suite

Test cases are saved as YAML files.

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

```yaml

# https://atcoder.jp/contests/ddcc2019-final/tasks/ddcc2019finala

type: Batch timelimit: 2s match: Float: relativeerror: 1e-8 absoluteerror: 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 ```

```yaml

# https://judge.yosupo.jp/problem/sqrt_mod

type: Batch timelimit: 10s match: Checker: cmd: ~/.cache/online-judge-tools/library-checker-problems/math/sqrtmod/checker "$INPUT" "$ACTUALOUTPUT" "$EXPECTED_OUTPUT" shell: Bash

cases: []

extend: - type: SystemTestCases ```

The format is TestSuite in the following schemas.

TestSuite

An internally tagged ADT.

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.

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

An internally tagged ADT.

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 testing 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.

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

Environment variables

cargo-compete reads these environment variables if they exist, and use them.

With online-judge-tools

For unsupported websites, oj-api(.exe) in the $PATH is used when downloading and submitting.

```toml [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

Compared with cargo-atcoder

See the section in the Japanese readme.

License

Dual-licensed under MIT or Apache-2.0.