Snowchains

Build Status codecov

Tools for online programming contests.

Features

| | Target | "contest" attribute | Scrape samples | Download system tests | Submit | | :---------------------- | :------------------------------------------- | :------------------ | :-------------: | :-------------------: | :-------------: | | AtCoder | atcoder.jp/contests/{} | .* | ✓ | ✓ | ✓ | | yukicoder (Problems) | yukicoder.me/problems/no/{} | no | ✓ | ✓ | ✓ | | yukicoder (Contests) | yukicoder.me/contests/{} | (?!no) | ✓ | ✓ | ✓ |

Instrallation

GitHub Releases

https://github.com/qryxip/snowchains/releases

Crates.io

Install Cargo with rustup, add ~/.cargo/bin to your $PATH, and

console $ cargo install snowchains

To update:

console $ cargo uninstall snowchains && cargo install snowchains

Or

$ cargo install cargo-update $ cargo install-update snowchains

Usage

``` snowchains 0.1.0 Ryo Yamashita qryxip@gmail.com Tools for online programming contests

USAGE: snowchains [OPTIONS] [directory] snowchains [OPTIONS] snowchains [OPTIONS] snowchains [OPTIONS] snowchains [FLAGS] [OPTIONS] snowchains [OPTIONS] snowchains [FLAGS] [OPTIONS] snowchains [FLAGS] [OPTIONS] snowchains show num-cases [OPTIONS] snowchains show timelimit-millis [OPTIONS] snowchains show in [OPTIONS] snowchains show accepts [OPTIONS] snowchains modify timelimit [OPTIONS] [timelimit] snowchains modify append [OPTIONS] [output] snowchains modify match [OPTIONS]

FLAGS: -h, --help Prints help information -V, --version Prints version information

SUBCOMMANDS: init Creates a config file ("snowchains.toml") switch Modifies values in a config file login Logges in to a service participate Participates in a contest download Downloads test cases restore Downloads source files you have submitted judge Tests a binary or script submit Submits a source file show Prints information modify Modifies values in a config file or test files help Prints this message or the help of the given subcommand(s) ```

console $ snowchains init ./ $ snowchains switch --service atcoder --contest practice --language c++ $ # snowchains login atcoder $ # snowchains participate atcoder practice $ snowchains download --open # does not ask your username and password unless they are needed $ $EDITOR ./snowchains/atcoder/practice/a.yml # add more test cases $ $EDITOR ./cpp/a.cpp $ # snowchains judge a $ snowchains submit a --open # executes `judge` command before submitting

Examples

Config File (snowchains.toml)

```toml service = "atcoder" contest = "arc100" language = "c++"

[console] cjk = false

alt_width = 100

[shell] bash = ["/usr/bin/bash", "-c", "${command}"]

bash = ["C:/tools/msys64/usr/bin/bash.exe", "-c", "PATH=/usr/bin:$$PATH; ${command}"]

bash = ["C:/msys64/usr/bin/bash.exe", "-c", "PATH=/usr/bin:$$PATH; ${command}"]

bash = ["C:/Program Files/Git/usr/bin/bash.exe", "-c", "PATH=/usr/bin:$$PATH; ${command}"]

ps = ["C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe", "-Command", "${command}"]

cmd = ["C:/Windows/System32/cmd.exe", "/C", "${command}"]

[testfiles] path = "${service}/${snakecase(contest)}/tests/${snakecase(problem)}.${extension}"

[session] timeout = "60s" silent = false cookies = "~/.local/share/snowchains/${service}" dropbox = false

dropbox = { auth: "~/.local/share/snowchains/dropbox.json" }

[session.download] extension = "yml" textfiledir = "${service}/${snakecase(contest)}/tests/${snakecase(problem)}"

[judge] testfile_extensions = ["json", "toml", "yaml", "yml"]

jobs = 4

display_limit = "1KiB"

[env.atcoder] CXXFLAGS = "-std=gnu++1y -I/usr/include/boost -g -fsanitize=undefined -DGLIBCXXDEBUG -Wall -Wextra" RUST_VERSION = "1.15.1"

[env.yukicoder] CXXFLAGS = "-std=gnu++14 -lm -g -fsanitize=undefined -DGLIBCXXDEBUG -Wall -Wextra" RUST_VERSION = "1.30.1"

[env.other] CXXFLAGS = "-std=gnu++17 -g -fsanitize=undefined -DGLIBCXXDEBUG -Wall -Wextra" RUST_VERSION = "stable"

[[hooks.switch]] bash = ''' service="$(echo "$SNOWCHAINSRESULT" | jq -r .new.service)" contest="$(echo "$SNOWCHAINSRESULT" | jq -r .new.contestsnakecase)" if [ ! -d "./$service/$contest/rs" ]; then mkdir -p "./$service/$contest" && cargo new --lib --edition 2015 --name "$contest" "./$service/$contest/rs" && mkdir "./$service/$contest/rs/src/bin" && rm "./$service/$contest/rs/src/lib.rs" fi '''

[[hooks.download]] bash = ''' if [ "$(echo "$SNOWCHAINSRESULT" | jq -r .openinbrowser)" = true ]; then service="$(echo "$SNOWCHAINSRESULT" | jq -r .service)" echo "$SNOWCHAINSRESULT" | jq -r ' . as $root | .problems | map("./" + $root.service + "/" + $root.contest.sluglowercase + "/rs/src/bin/" + .namekebabcase + ".rs") | join("\n") ' | xargs -d \n -I % -r cp "./templates/rs/src/bin/$service.rs" % && echo "$SNOWCHAINSRESULT" | jq -r ' . as $root | .problems | map(["./" + $root.service + "/" + $root.contest.sluglowercase + "/rs/src/bin/" + .namekebabcase + ".rs", .testsuitepath]) | flatten | join("\n") ' | xargs -d \n -r emacsclient -n fi '''

[tester] src = "testers/py/${kebabcase(problem)}.py" run = { bash = './venv/bin/python3 "$SNOWCHAINSSRC" $SNOWCHAINSARGSJOINED' } working_directory = "testers/py"

[tester]

src = "testers/hs/app/${pascal_case(problem)}.hs"

bin = "testers/hs/target/${pascal_case(problem)}"

run = { bash = '"$SNOWCHAINSBIN" "$SNOWCHAINSSRC" $SNOWCHAINSARGSJOINED' }

working_directory = "testers/hs"

[languages.'c++'] src = "${service}/${snakecase(contest)}/cpp/${kebabcase(problem)}.cpp" bin = "${service}/${snakecase(contest)}/cpp/build/${kebabcase(problem)}" compile = { bash = 'g++ $CXXFLAGS -o "$SNOWCHAINSBIN" "$SNOWCHAINSSRC"' } run = ["${bin}"] workingdirectory = "${service}/${snakecase(contest)}/cpp" language_ids = { atcoder = "3003", yukicoder = "cpp14" }

[languages.rust] src = "${service}/${snakecase(contest)}/rs/src/bin/${kebabcase(problem)}.rs" bin = "${service}/${snakecase(contest)}/rs/target/manually/${kebabcase(problem)}" compile = ["rustc", "+${env:RUSTVERSION}", "-o", "${bin}", "${src}"] run = ["${bin}"] workingdirectory = "${service}/${snakecase(contest)}/rs" languageids = { atcoder = "3504", yukicoder = "rust" }

[languages.go] src = "${service}/${snakecase(contest)}/go/${kebabcase(problem)}.go" bin = "${service}/${snakecase(contest)}/go/${kebabcase(problem)}" compile = ["go", "build", "-o", "${bin}", "${src}"] run = ["${bin}"] workingdirectory = "${service}/${snakecase(contest)}/go" language_ids = { atcoder = "3013", yukicoder = "go" }

[languages.haskell] src = "${service}/${snakecase(contest)}/hs/app/${pascalcase(problem)}.hs" bin = "${service}/${snakecase(contest)}/hs/target/${pascalcase(problem)}" compile = ["stack", "ghc", "--", "-O2", "-o", "${bin}", "${src}"] run = ["${bin}"] workingdirectory = "${service}/${snakecase(contest)}/hs" language_ids = { atcoder = "3014", yukicoder = "haskell" }

[languages.bash] src = "${service}/${snakecase(contest)}/bash/${kebabcase(problem)}.bash" run = ["bash", "${src}"] workingdirectory = "${service}/${snakecase(contest)}/bash" language_ids = { atcoder = "3001", yukicoder = "sh" }

[languages.python3] src = "${service}/${snakecase(contest)}/py/${kebabcase(problem)}.py" run = ["../../../venvs/python3${service}/bin/python3", "${src}"] workingdirectory = "${service}/${snakecase(contest)}/py" languageids = { atcoder = "3023", yukicoder = "python3" }

[languages.pypy3] src = "${service}/${snakecase(contest)}/py/${kebabcase(problem)}.py" run = ["../../../venvs/pypy3${service}/bin/python3", "${src}"] workingdirectory = "${service}/${snakecase(contest)}/py" languageids = { atcoder = "3510", yukicoder = "pypy3" }

[languages.java] src = "${service}/${snakecase(contest)}/java/src/main/java/${pascalcase(problem)}.java" transpiled = "${service}/${snakecase(contest)}/java/build/replaced/${lowercase(problem)}/src/Main.java" bin = "${service}/${snakecase(contest)}/java/build/replaced/${lowercase(problem)}/classes/Main.class" transpile = { bash = 'cat "$SNOWCHAINSSRC" | sed -r "s/class\s+$SNOWCHAINSPROBLEMPASCALCASE/class Main/g" > "$SNOWCHAINSTRANSPILED"' } compile = ["javac", "-d", "./build/replaced/${lowercase(problem)}/classes", "${transpiled}"] run = ["java", "-classpath", "./build/replaced/${lowercase(problem)}/classes", "Main"] workingdirectory = "${service}/${snakecase(contest)}/java" languageids = { atcoder = "3016", yukicoder = "java8" }

[languages.scala] src = "${service}/${snakecase(contest)}/scala/src/main/scala/${pascalcase(problem)}.scala" transpiled = "${service}/${snakecase(contest)}/scala/target/replaced/${lowercase(problem)}/src/Main.scala" bin = "${service}/${snakecase(contest)}/scala/target/replaced/${lowercase(problem)}/classes/Main.class" transpile = { bash = 'cat "$SNOWCHAINSSRC" | sed -r "s/object\s+$SNOWCHAINSPROBLEMPASCALCASE/object Main/g" > "$SNOWCHAINSTRANSPILED"' } compile = ["scalac", "-optimise", "-d", "./target/replaced/${lowercase(problem)}/classes", "${transpiled}"] run = ["scala", "-classpath", "./target/replaced/${lowercase(problem)}/classes", "Main"] workingdirectory = "${service}/${snakecase(contest)}/scala" languageids = { atcoder = "3025", yukicoder = "scala" }

[languages.'c#'] src = "${service}/${snakecase(contest)}/cs/${pascalcase(problem)}/${pascalcase(problem)}.cs" bin = "${service}/${snakecase(contest)}/cs/${pascalcase(problem)}/bin/Release/${pascalcase(problem)}.exe" compile = ["mcs", "-o+", "-r:System.Numerics", "-out:${bin}", "${src}"] run = ["mono", "${bin}"] workingdirectory = "${service}/${snakecase(contest)}/cs" languageids = { atcoder = "3006", yukicoder = "csharpmono" }

[languages.'c#']

src = "${service}/${snakecase(contest)}/cs/${pascalcase(problem)}/${pascal_case(problem)}.cs"

bin = "${service}/${snakecase(contest)}/cs/${pascalcase(problem)}/bin/Release/${pascal_case(problem)}.exe"

compile = ["csc", "/o+", "/r:System.Numerics", "/out:${bin}", "${src}"]

run = ["${bin}"]

crlftolf: true

workingdirectory = "${service}/${snakecase(contest)}/cs"

language_ids = { atcoder = "3006", yukicoder = "csharp" }

[languages.text] src = "${service}/${snakecase(contest)}/txt/${snakecase(problem)}.txt" run = ["cat", "${src}"] workingdirectory = "${service}/${snakecase(contest)}/txt" language_ids = { atcoder = "3027", yukicoder = "text" } ```

Test file

Batch (one input, one output)

https://atcoder.jp/contests/practice/tasks/practice_1

```yaml

type: batch # "batch", "interactive", or "unsubmittable" timelimit: 2000ms # optional match: exact # "any", "exact", or "float"

cases: - name: Sample 1 in: | 1 2 3 test out: | 6 test - name: Sample 2 in: | 72 128 256 myonmyon out: | 456 myonmyon # "name" and "out" are optional - in: | 1000 1000 1000 oooooooooooooo ```

```toml type = 'batch' timelimit = '2000ms' match = 'exact'

[[cases]] name = 'Sample 1' in = ''' 1 2 3 test ''' out = ''' 6 test '''

[[cases]] name = 'Sample 2' in = ''' 72 128 256 myonmyon ''' out = ''' 456 myonmyon '''

[[cases]] in = ''' 1000 1000 1000 oooooooooooooo ''' ```

https://atcoder.jp/contests/tricky/tasks/tricky_2

```yaml

type: batch timelimit: 2000ms match: float: absoluteerror: 1e-9 relativeerror: 1e-9

cases: - name "Sample 1" in: | 3 1 -3 2 -10 30 -20 100 -300 200 out: | 2 1.000 2.000 2 1.000 2.000 2 1.000 2.000 ```

```toml type = 'batch' timelimit = '2000ms'

[match.float] absoluteerror = 1e-9 relativeerror = 1e-9

[[cases]] name = 'Sample 1' in = ''' 3 1 -3 2 -10 30 -20 100 -300 200 ''' out = ''' 2 1.000 2.000 2 1.000 2.000 2 1.000 2.000 ''' ```

Interactive

https://atcoder.jp/contests/practice/tasks/practice_2

```yaml

type: interactive timelimit: 2000ms

each_args: - [ABCDE] - [EDCBA] - [ABCDEFGHIJKLMNOPQRSTUVWXYZ] - [ZYXWVUTSRQPONMLKJIHGFEDCBA] ```

```python import re import sys

def main() -> None: bs = sys.argv[1] n = len(bs) q = 7 if n == 5 else 100

def reply(c1, c2):
    print('<' if bs.index(c1) < bs.index(c2) else '>', flush=True)

def judge(a):
    if a == bs:
        sys.exit(0)
    else:
        print('wrong', file=sys.stderr)
        sys.exit(1)

print(f'{n} {q}', flush=True)
for _ in range(q):
    ts = re.split(r'[ \n]', sys.stdin.readline())
    if len(ts) == 4 and ts[0] == '?':
        reply(ts[1], ts[2])
    elif len(ts) == 3 and ts[0] == '!':
        judge(ts[1])
    else:
        raise RuntimeError('invalid')
else:
    ts = re.split(r'[ \n]', sys.stdin.readline())
    if len(ts) == 3 and ts[0] == '!':
        judge(ts[1])
    raise RuntimeError('answer me')

if name == 'main': main() ```

```haskell {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} module Main (main) where

import RIO import qualified RIO.ByteString as B import RIO.List import RIO.List.Partial import System.Environment import System.Exit import System.IO import Text.Printf

main :: IO () main = do RIO.hSetBuffering stdout LineBuffering bs <- (!! 0) <$> getArgs let n = length bs q = if n == 5 then 7 else 100 :: Int reply c1 c2 = B.putStr (if weight c1 < weight c2 then "<\n" else ">\n") judge a | a == bs = exitSuccess | otherwise = die "wrong" weight c = fromMaybe (error "out of bounds") (c elemIndex bs) printf "%d %d\n" n q forM_ [1..q] $ _ -> words <$> getLine >>= \case ["?", [c1], [c2]] -> reply c1 c2 ["!", a] -> judge a _ -> error "invalid" ```

License

Dual-licensed under MIT or Apache-2.0.