Snowchains
Tools for online programming contests.
Features
- Scrapes sample cases as YAML, TOML, or JSON
- Tests a source file with downloaded sample cases
- Submits a source file
- Downloads source file you have submitted
| 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
$ cargo install snowchains
To update:
$ 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 <i|init> [OPTIONS] [directory]
snowchains <w|switch|c|checkout> [OPTIONS]
snowchains <l|login> [OPTIONS] <service>
snowchains <p|participate> [OPTIONS] <service> <contest>
snowchains <d|download> [FLAGS] [OPTIONS]
snowchains <r|restore> [OPTIONS]
snowchains <j|judge|t|test> [FLAGS] [OPTIONS] <problem>
snowchains <s|submit> [FLAGS] [OPTIONS] <problem>
snowchains show num-cases [OPTIONS] <problem> <extension>
snowchains show timelimit-millis [OPTIONS] <problem> <nth>
snowchains show in [OPTIONS] <problem> <nth>
snowchains show accepts [OPTIONS] <problem> <nth>
snowchains modify timelimit [OPTIONS] <problem> <nth> [timelimit]
snowchains modify append [OPTIONS] <problem> <extensioon> <input> [output]
snowchains modify match [OPTIONS] <problem> <extension> <match>
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)
$ 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)
= "atcoder"
= "arc100"
= "c++"
[]
= false
# alt_width = 100
[]
= ["/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}"]
[]
= "${service}/${snake_case(contest)}/tests/${snake_case(problem)}.${extension}"
[]
= "60s"
= false
= "~/.local/share/snowchains/${service}"
= false
# dropbox = { auth: "~/.local/share/snowchains/dropbox.json" }
[]
= "yml"
= "${service}/${snake_case(contest)}/tests/${snake_case(problem)}"
[]
= ["json", "toml", "yaml", "yml"]
# jobs = 4
= "1KiB"
[]
= "-std=gnu++1y -I/usr/include/boost -g -fsanitize=undefined -D_GLIBCXX_DEBUG -Wall -Wextra"
= "1.15.1"
[]
= "-std=gnu++14 -lm -g -fsanitize=undefined -D_GLIBCXX_DEBUG -Wall -Wextra"
= "1.30.1"
[]
= "-std=gnu++17 -g -fsanitize=undefined -D_GLIBCXX_DEBUG -Wall -Wextra"
= "stable"
[[]]
= '''
service="$(echo "$SNOWCHAINS_RESULT" | jq -r .new.service)"
contest="$(echo "$SNOWCHAINS_RESULT" | jq -r .new.contest_snake_case)"
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
'''
[[]]
= '''
if [ "$(echo "$SNOWCHAINS_RESULT" | jq -r .open_in_browser)" = true ]; then
service="$(echo "$SNOWCHAINS_RESULT" | jq -r .service)"
echo "$SNOWCHAINS_RESULT" |
jq -r '
. as $root
| .problems
| map("./" + $root.service + "/" + $root.contest.slug_lower_case + "/rs/src/bin/" + .name_kebab_case + ".rs")
| join("\n")
' | xargs -d \\n -I % -r cp "./templates/rs/src/bin/$service.rs" % &&
echo "$SNOWCHAINS_RESULT" |
jq -r '
. as $root
| .problems
| map(["./" + $root.service + "/" + $root.contest.slug_lower_case + "/rs/src/bin/" + .name_kebab_case + ".rs", .test_suite_path])
| flatten
| join("\n")
' | xargs -d \\n -r emacsclient -n
fi
'''
[]
= "testers/py/${kebab_case(problem)}.py"
= { = './venv/bin/python3 "$SNOWCHAINS_SRC" $SNOWCHAINS_ARGS_JOINED' }
= "testers/py"
# [tester]
# src = "testers/hs/app/${pascal_case(problem)}.hs"
# bin = "testers/hs/target/${pascal_case(problem)}"
# run = { bash = '"$SNOWCHAINS_BIN" "$SNOWCHAINS_SRC" $SNOWCHAINS_ARGS_JOINED' }
# working_directory = "testers/hs"
[]
= "${service}/${snake_case(contest)}/cpp/${kebab_case(problem)}.cpp"
= "${service}/${snake_case(contest)}/cpp/build/${kebab_case(problem)}"
= { = 'g++ $CXXFLAGS -o "$SNOWCHAINS_BIN" "$SNOWCHAINS_SRC"' }
= ["${bin}"]
= "${service}/${snake_case(contest)}/cpp"
= { = "3003", = "cpp14" }
[]
= "${service}/${snake_case(contest)}/rs/src/bin/${kebab_case(problem)}.rs"
= "${service}/${snake_case(contest)}/rs/target/manually/${kebab_case(problem)}"
= ["rustc", "+${env:RUST_VERSION}", "-o", "${bin}", "${src}"]
= ["${bin}"]
= "${service}/${snake_case(contest)}/rs"
= { = "3504", = "rust" }
[]
= "${service}/${snake_case(contest)}/go/${kebab_case(problem)}.go"
= "${service}/${snake_case(contest)}/go/${kebab_case(problem)}"
= ["go", "build", "-o", "${bin}", "${src}"]
= ["${bin}"]
= "${service}/${snake_case(contest)}/go"
= { = "3013", = "go" }
[]
= "${service}/${snake_case(contest)}/hs/app/${pascal_case(problem)}.hs"
= "${service}/${snake_case(contest)}/hs/target/${pascal_case(problem)}"
= ["stack", "ghc", "--", "-O2", "-o", "${bin}", "${src}"]
= ["${bin}"]
= "${service}/${snake_case(contest)}/hs"
= { = "3014", = "haskell" }
[]
= "${service}/${snake_case(contest)}/bash/${kebab_case(problem)}.bash"
= ["bash", "${src}"]
= "${service}/${snake_case(contest)}/bash"
= { = "3001", = "sh" }
[]
= "${service}/${snake_case(contest)}/py/${kebab_case(problem)}.py"
= ["../../../venvs/python3_${service}/bin/python3", "${src}"]
= "${service}/${snake_case(contest)}/py"
= { = "3023", = "python3" }
[]
= "${service}/${snake_case(contest)}/py/${kebab_case(problem)}.py"
= ["../../../venvs/pypy3_${service}/bin/python3", "${src}"]
= "${service}/${snake_case(contest)}/py"
= { = "3510", = "pypy3" }
[]
= "${service}/${snake_case(contest)}/java/src/main/java/${pascal_case(problem)}.java"
= "${service}/${snake_case(contest)}/java/build/replaced/${lower_case(problem)}/src/Main.java"
= "${service}/${snake_case(contest)}/java/build/replaced/${lower_case(problem)}/classes/Main.class"
= { = 'cat "$SNOWCHAINS_SRC" | sed -r "s/class\s+$SNOWCHAINS_PROBLEM_PASCAL_CASE/class Main/g" > "$SNOWCHAINS_TRANSPILED"' }
= ["javac", "-d", "./build/replaced/${lower_case(problem)}/classes", "${transpiled}"]
= ["java", "-classpath", "./build/replaced/${lower_case(problem)}/classes", "Main"]
= "${service}/${snake_case(contest)}/java"
= { = "3016", = "java8" }
[]
= "${service}/${snake_case(contest)}/scala/src/main/scala/${pascal_case(problem)}.scala"
= "${service}/${snake_case(contest)}/scala/target/replaced/${lower_case(problem)}/src/Main.scala"
= "${service}/${snake_case(contest)}/scala/target/replaced/${lower_case(problem)}/classes/Main.class"
= { = 'cat "$SNOWCHAINS_SRC" | sed -r "s/object\s+$SNOWCHAINS_PROBLEM_PASCAL_CASE/object Main/g" > "$SNOWCHAINS_TRANSPILED"' }
= ["scalac", "-optimise", "-d", "./target/replaced/${lower_case(problem)}/classes", "${transpiled}"]
= ["scala", "-classpath", "./target/replaced/${lower_case(problem)}/classes", "Main"]
= "${service}/${snake_case(contest)}/scala"
= { = "3025", = "scala" }
[]
= "${service}/${snake_case(contest)}/cs/${pascal_case(problem)}/${pascal_case(problem)}.cs"
= "${service}/${snake_case(contest)}/cs/${pascal_case(problem)}/bin/Release/${pascal_case(problem)}.exe"
= ["mcs", "-o+", "-r:System.Numerics", "-out:${bin}", "${src}"]
= ["mono", "${bin}"]
= "${service}/${snake_case(contest)}/cs"
= { = "3006", = "csharp_mono" }
# [languages.'c#']
# src = "${service}/${snake_case(contest)}/cs/${pascal_case(problem)}/${pascal_case(problem)}.cs"
# bin = "${service}/${snake_case(contest)}/cs/${pascal_case(problem)}/bin/Release/${pascal_case(problem)}.exe"
# compile = ["csc", "/o+", "/r:System.Numerics", "/out:${bin}", "${src}"]
# run = ["${bin}"]
# crlf_to_lf: true
# working_directory = "${service}/${snake_case(contest)}/cs"
# language_ids = { atcoder = "3006", yukicoder = "csharp" }
[]
= "${service}/${snake_case(contest)}/txt/${snake_case(problem)}.txt"
= ["cat", "${src}"]
= "${service}/${snake_case(contest)}/txt"
= { = "3027", = "text" }
Test file
- YAML
- TOML
- JSON
Batch (one input, one output)
https://atcoder.jp/contests/practice/tasks/practice_1
---
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
= 'batch'
= '2000ms'
= 'exact'
[[]]
= 'Sample 1'
= '''
1
2 3
test
'''
= '''
6 test
'''
[[]]
= 'Sample 2'
= '''
72
128 256
myonmyon
'''
= '''
456 myonmyon
'''
[[]]
= '''
1000
1000 1000
oooooooooooooo
'''
https://atcoder.jp/contests/tricky/tasks/tricky_2
---
type: batch
timelimit: 2000ms
match:
float:
absolute_error: 1e-9
relative_error: 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
= 'batch'
= '2000ms'
[]
= 1e-9
= 1e-9
[[]]
= 'Sample 1'
= '''
3
1 -3 2
-10 30 -20
100 -300 200
'''
= '''
2 1.000 2.000
2 1.000 2.000
2 1.000 2.000
'''
Interactive
https://atcoder.jp/contests/practice/tasks/practice_2
---
type: interactive
timelimit: 2000ms
each_args:
-
-
-
-
=
=
= 7
=
=
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.