Last active
May 14, 2020 18:23
-
-
Save qryxip/d154944658df7d31e926993d675378a3 to your computer and use it in GitHub Desktop.
Scrape submitted code from AtCoder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! This code is licensed under [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0). | |
//! | |
//! ```cargo | |
//! [package] | |
//! name = "atcoder-submissions" | |
//! version = "0.0.0" | |
//! authors = ["Ryo Yamashita <qryxip@gmail.com>"] | |
//! edition = "2018" | |
//! publish = false | |
//! license = "CC0-1.0" | |
//! description = "Scrape submitted code from AtCoder" | |
//! | |
//! [dependencies] | |
//! anyhow = "1.0.30" | |
//! chrono = "0.4.11" | |
//! env_logger = "0.7.1" | |
//! itertools = "0.9.0" | |
//! log = "0.4.8" | |
//! once_cell = "1.4.0" | |
//! regex = "1.3.7" | |
//! scraper = "0.12.0" | |
//! structopt = "0.3.14" | |
//! strum = { version = "0.18.0", features = ["derive"] } | |
//! ureq = "1.0.0" | |
//! url = "2.1.1" | |
//! ``` | |
use anyhow::{anyhow, ensure, Context as _}; | |
use chrono::{DateTime, FixedOffset}; | |
use env_logger::fmt::Color; | |
use itertools::Itertools as _; | |
use log::{info, Level, LevelFilter}; | |
use once_cell::sync::Lazy; | |
use regex::Regex; | |
use scraper::{Html, Selector}; | |
use structopt::clap::AppSettings; | |
use structopt::StructOpt; | |
use strum::{EnumString, EnumVariantNames, IntoStaticStr, VariantNames as _}; | |
use ureq::Response; | |
use url::Url; | |
use std::fs; | |
use std::io::Write as _; | |
use std::path::{Path, PathBuf}; | |
macro_rules! static_regex { | |
($s:literal $(,)?) => {{ | |
static REGEX: Lazy<Regex> = Lazy::new(|| Regex::new($s).unwrap()); | |
&*REGEX | |
}}; | |
} | |
macro_rules! static_selector { | |
($s:literal $(,)?) => {{ | |
static SELECTOR: Lazy<Selector> = Lazy::new(|| Selector::parse($s).unwrap()); | |
&*SELECTOR | |
}}; | |
} | |
#[derive(StructOpt, Debug)] | |
#[structopt(author, about, setting(AppSettings::DeriveDisplayOrder))] | |
struct Opt { | |
#[structopt(long, help("Scrapes only AC submissions"))] | |
ac: bool, | |
#[structopt( | |
short, | |
long, | |
value_name("DIR"), | |
default_value("./submissions"), | |
help("Saves scraped code to the directory") | |
)] | |
out: PathBuf, | |
#[structopt( | |
short, | |
long, | |
value_name("LANG"), | |
possible_values(Lang::VARIANTS), | |
required(true), | |
min_values(1), | |
help("Languages") | |
)] | |
langs: Vec<Lang>, | |
#[structopt( | |
short, | |
long, | |
value_name("SCREEN_NAME"), | |
required(true), | |
min_values(1), | |
help("Screen names of the contests (https://atcoder.jp/contests/{}/submissions)") | |
)] | |
contests: Vec<String>, | |
} | |
fn main() -> anyhow::Result<()> { | |
let Opt { | |
ac, | |
out, | |
langs, | |
contests, | |
} = Opt::from_args(); | |
env_logger::builder() | |
.format(|buf, record| { | |
let mut style = buf.style(); | |
let mut write_with_style = |color, bold, intense, value| -> _ { | |
let value = style | |
.set_color(color) | |
.set_bold(bold) | |
.set_intense(intense) | |
.value(value); | |
write!(buf, "{}", value) | |
}; | |
write_with_style(Color::Black, false, true, "[")?; | |
match record.level() { | |
Level::Info => write_with_style(Color::Cyan, true, false, "INFO"), | |
Level::Warn => write_with_style(Color::Yellow, true, false, "WARN"), | |
Level::Error => write_with_style(Color::Red, true, false, "ERROR"), | |
_ => unreachable!(), | |
}?; | |
write_with_style(Color::Black, false, true, "]")?; | |
writeln!(buf, " {}", record.args()) | |
}) | |
.filter_module("atcoder_submissions", LevelFilter::Info) | |
.init(); | |
get("/robots.txt", 404)?; | |
for &lang in &langs { | |
for contest in &contests { | |
for (path, code) in retrieve_codes(contest, &out, lang, ac)? { | |
if let Some(parent) = path.parent() { | |
create_dir_all(parent)?; | |
} | |
write(&path, &code)?; | |
} | |
} | |
} | |
Ok(()) | |
} | |
fn retrieve_codes( | |
contest: &str, | |
out: &Path, | |
lang: Lang, | |
only_ac: bool, | |
) -> anyhow::Result<Vec<(PathBuf, String)>> { | |
let path = |lang_id: u32| -> _ { | |
let mut acc = format!( | |
"/contests/{}/submissions?desc=true&orderBy=created&f.Language={}", | |
contest, lang_id, | |
); | |
if only_ac { | |
acc += "&f.Status=AC"; | |
} | |
acc | |
}; | |
let mut acc = vec![]; | |
let mut retrieve_codes = |summaries: &[Summary]| -> anyhow::Result<_> { | |
for summary in summaries { | |
let path = out | |
.join(<&str>::from(lang)) | |
.join(contest) | |
.join(&summary.user) | |
.join(summary.file_stem()) | |
.with_extension(<&str>::from(lang)); | |
let code = get_html(&summary.detail)?.extract_code()?; | |
acc.push((path, code)) | |
} | |
Ok(()) | |
}; | |
for &lang_id in lang.ids() { | |
let path = path(lang_id); | |
let (summaries, num_pages) = get_html(&path)?.extract_summaries()?; | |
retrieve_codes(&summaries)?; | |
for i in 1..num_pages { | |
let (summaries, _) = | |
get_html(&format!("{}&page={}", path, i + 1))?.extract_summaries()?; | |
retrieve_codes(&summaries)?; | |
} | |
} | |
Ok(acc) | |
} | |
fn get(path: &str, expected: u16) -> anyhow::Result<Response> { | |
static BASE: Lazy<Url> = Lazy::new(|| "https://atcoder.jp".parse().unwrap()); | |
let url = BASE.join(path)?; | |
info!("GET: {}", url); | |
let res = ureq::get(url.as_ref()) | |
.set( | |
"User-Agent", | |
"atcoder-submissions <https://gist.github.com/qryxip/d154944658df7d31e926993d675378a3>", | |
) | |
.call(); | |
if let Some(err) = res.synthetic_error() { | |
let mut err = err as &dyn std::error::Error; | |
let mut displays = vec![err.to_string()]; | |
while let Some(source) = err.source() { | |
displays.push(source.to_string()); | |
err = source; | |
} | |
let mut displays = displays.into_iter().rev(); | |
let cause = anyhow!("{}", displays.next().unwrap()); | |
return Err(displays.fold(cause, |err, display| err.context(display))); | |
} | |
info!("{} {}", res.status(), res.status_text()); | |
ensure!(res.status() == expected, "expected {}", expected); | |
Ok(res) | |
} | |
fn get_html(path: &str) -> anyhow::Result<Html> { | |
let text = get(path, 200)?.into_string()?; | |
Ok(Html::parse_document(&text)) | |
} | |
trait HtmlExt { | |
fn extract_summaries(&self) -> anyhow::Result<(Vec<Summary>, u32)>; | |
fn extract_code(&self) -> anyhow::Result<String>; | |
} | |
impl HtmlExt for Html { | |
fn extract_summaries(&self) -> anyhow::Result<(Vec<Summary>, u32)> { | |
(|| { | |
let summaries = self | |
.select(static_selector!( | |
"#main-container > div.row > div.col-sm-12 > div.panel-submission > \ | |
div.table-responsive > table.table > tbody > tr", | |
)) | |
.map(|tr| { | |
let submission_time = tr | |
.select(static_selector!("td > time")) | |
.exactly_one() | |
.ok()? | |
.text() | |
.exactly_one() | |
.ok()? | |
.to_owned(); | |
let submission_time = | |
DateTime::parse_from_str(&submission_time, "%F %T%z").ok()?; | |
let a = tr.select(static_selector!("td > a")).next()?; | |
let task_display = a.text().exactly_one().ok()?.to_owned(); | |
let task_path = a.value().attr("href")?.to_owned(); | |
let user = tr | |
.select(static_selector!("td")) | |
.nth(2)? | |
.text() | |
.next()? | |
.to_owned(); | |
let language = tr | |
.select(static_selector!("td")) | |
.nth(3)? | |
.text() | |
.exactly_one() | |
.ok()? | |
.to_owned(); | |
let score = tr | |
.select(static_selector!("td")) | |
.nth(4)? | |
.text() | |
.exactly_one() | |
.ok()? | |
.parse() | |
.ok()?; | |
let code_size = tr | |
.select(static_selector!("td")) | |
.nth(5)? | |
.text() | |
.exactly_one() | |
.ok()?; | |
let code_size = static_regex!(r"\A([0-9]+) Byte\z").captures(code_size)?[1] | |
.parse() | |
.ok()?; | |
let status = tr | |
.select(static_selector!("td > span")) | |
.next()? | |
.text() | |
.exactly_one() | |
.ok()? | |
.to_owned(); | |
let exec_time = tr | |
.select(static_selector!("td")) | |
.nth(7)? | |
.text() | |
.exactly_one() | |
.ok()?; | |
let exec_time_millis = static_regex!(r"\A([0-9]+) ms\z").captures(exec_time)? | |
[1] | |
.parse() | |
.ok()?; | |
let memory = tr | |
.select(static_selector!("td")) | |
.nth(8)? | |
.text() | |
.exactly_one() | |
.ok()? | |
.to_owned(); | |
let detail = tr | |
.select(static_selector!("td.text-center > a")) | |
.flat_map(|a| { | |
let text = a.text().next()?; | |
if !["詳細", "Detail"].contains(&text) { | |
return None; | |
} | |
a.value().attr("href").map(ToOwned::to_owned) | |
}) | |
.next()?; | |
Some(Summary { | |
submission_time, | |
task_display, | |
task_path, | |
user, | |
language, | |
score, | |
code_size, | |
status, | |
exec_time_millis, | |
memory, | |
detail, | |
}) | |
}) | |
.collect::<Option<Vec<_>>>()?; | |
let num_pages = self | |
.select(static_selector!( | |
"#main-container > div.row > div.col-sm-12 > div.text-center > ul > li > a", | |
)) | |
.flat_map(|r| r.text()) | |
.flat_map(|t| t.parse::<u32>().ok()) | |
.max() | |
.unwrap_or(1); | |
Some((summaries, num_pages)) | |
})() | |
.ok_or_else(|| anyhow!("Failed to scrape (note: try `--ac`)")) | |
} | |
fn extract_code(&self) -> anyhow::Result<String> { | |
(|| { | |
self.select(static_selector!("#submission-code")) | |
.exactly_one() | |
.ok()? | |
.text() | |
.exactly_one() | |
.ok() | |
.map(ToOwned::to_owned) | |
})() | |
.ok_or_else(|| anyhow!("Failed to scrape")) | |
} | |
} | |
#[derive(Debug, Clone, Copy, EnumString, EnumVariantNames, IntoStaticStr)] | |
#[strum(serialize_all = "lowercase")] | |
enum Lang { | |
Cpp, | |
Bash, | |
C, | |
Cs, | |
Clj, | |
Lisp, | |
D, | |
F08, | |
Go, | |
Hs, | |
Java, | |
Js, | |
Ml, | |
Pas, | |
Pl, | |
Php, | |
Py, | |
Rb, | |
Scala, | |
Scm, | |
Txt, | |
Vb, | |
Swift, | |
Rs, | |
Sed, | |
Awk, | |
Bf, | |
Sml, | |
Cr, | |
Fs, | |
Unl, | |
Lua, | |
Moon, | |
Ceylon, | |
Jl, | |
Nim, | |
Ts, | |
P6, | |
Kt, | |
Cob, | |
Bc, | |
Dart, | |
Dc, | |
Erl, | |
Ex, | |
Forth, | |
F18, // ??? | |
Hx, | |
Sh, // dash | |
Raku, | |
Pro, | |
Rkt, | |
} | |
impl Lang { | |
fn ids(self) -> &'static [u32] { | |
// 2016: | |
// ┌─────────────────────────────────┬──────┐ | |
// │ Name │ ID │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ C++14 (GCC 5.4.1) │ 3003 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Bash (GNU bash v4.3.11) │ 3001 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ C (GCC 5.4.1) │ 3002 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ C (Clang 3.8.0) │ 3004 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ C++14 (Clang 3.8.0) │ 3005 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ C# (Mono 4.6.2.0) │ 3006 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Clojure (1.8.0) │ 3007 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Common Lisp (SBCL 1.1.14) │ 3008 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ D (DMD64 v2.070.1) │ 3009 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ D (LDC 0.17.0) │ 3010 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ D (GDC 4.9.4) │ 3011 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Fortran (gfortran v4.8.4) │ 3012 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Go (1.6) │ 3013 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Haskell (GHC 7.10.3) │ 3014 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Java7 (OpenJDK 1.7.0) │ 3015 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Java8 (OpenJDK 1.8.0) │ 3016 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ JavaScript (node.js v5.12) │ 3017 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ OCaml (4.02.3) │ 3018 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Pascal (FPC 2.6.2) │ 3019 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Perl (v5.18.2) │ 3020 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ PHP (5.6.30) │ 3021 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Python2 (2.7.6) │ 3022 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Python3 (3.4.3) │ 3023 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Ruby (2.3.3) │ 3024 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Scala (2.11.7) │ 3025 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Scheme (Gauche 0.9.3.3) │ 3026 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Text (cat) │ 3027 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Visual Basic (Mono 4.0.1) │ 3028 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ C++ (GCC 5.4.1) │ 3029 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ C++ (Clang 3.8.0) │ 3030 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Objective-C (GCC 5.3.0) │ 3501 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Objective-C (Clang3.8.0) │ 3502 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Swift (swift-2.2-RELEASE) │ 3503 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Rust (1.15.1) │ 3504 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Sed (GNU sed 4.2.2) │ 3505 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Awk (mawk 1.3.3) │ 3506 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Brainfuck (bf 20041219) │ 3507 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Standard ML (MLton 20100608) │ 3508 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ PyPy2 (5.6.0) │ 3509 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ PyPy3 (2.4.0) │ 3510 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Crystal (0.20.5) │ 3511 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ F# (Mono 4.0) │ 3512 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Unlambda (0.1.3) │ 3513 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Lua (5.3.2) │ 3514 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ LuaJIT (2.0.4) │ 3515 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ MoonScript (0.5.0) │ 3516 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Ceylon (1.2.1) │ 3517 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Julia (0.5.0) │ 3518 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Octave (4.0.2) │ 3519 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Nim (0.13.0) │ 3520 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ TypeScript (2.1.6) │ 3521 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Perl6 (rakudo-star 2016.01) │ 3522 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ Kotlin (1.0.0) │ 3523 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ PHP7 (7.0.15) │ 3524 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ COBOL - Fixed (OpenCOBOL 1.1.0) │ 3525 │ | |
// ├─────────────────────────────────┼──────┤ | |
// │ COBOL - Free (OpenCOBOL 1.1.0) │ 3526 │ | |
// └─────────────────────────────────┴──────┘ | |
// | |
// 2020: | |
// ┌──────────────────────────────────┬──────┐ | |
// │ Name │ ID │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ C (GCC 9.2.1) │ 4001 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ C (Clang 10.0.0) │ 4002 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ C++ (GCC 9.2.1) │ 4003 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ C++ (Clang 10.0.0) │ 4004 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Java (OpenJDK 11.0.6) │ 4005 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Python (3.8.2) │ 4006 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Bash (5.0.11) │ 4007 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ bc (1.07.1) │ 4008 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Awk (GNU Awk 4.1.4) │ 4009 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ C# (.NET Core 3.1.201) │ 4010 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ C# (Mono-mcs 6.8.0.105) │ 4011 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ C# (Mono-csc 3.5.0) │ 4012 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Clojure (1.10.1.536) │ 4013 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Crystal (0.33.0) │ 4014 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ D (DMD 2.091.0) │ 4015 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ D (GDC 9.2.1) │ 4016 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ D (LDC 1.20.1) │ 4017 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Dart (2.7.2) │ 4018 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ dc (1.4.1) │ 4019 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Erlang (22.3) │ 4020 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Elixir (1.10.2) │ 4021 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ F# (.NET Core 3.1.201) │ 4022 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ F# (Mono 10.2.3) │ 4023 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Forth (gforth 0.7.3) │ 4024 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Fortran(GNU Fortran 9.2.1) │ 4025 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Go (1.14.1) │ 4026 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Haskell (GHC 8.8.3) │ 4027 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Haxe (4.0.3); js │ 4028 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Haxe (4.0.3); Java │ 4029 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ JavaScript (Node.js 12.16.1) │ 4030 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Julia (1.4.0) │ 4031 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Kotlin (1.3.71) │ 4032 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Lua (Lua 5.3.5) │ 4033 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Lua (LuaJIT 2.1.0) │ 4034 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Dash (0.5.8) │ 4035 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Nim (1.0.6) │ 4036 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Objective-C (Clang 10.0.0) │ 4037 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Common Lisp (SBCL 2.0.3) │ 4038 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ OCaml (4.10.0) │ 4039 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Octave (5.2.0) │ 4040 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Pascal (FPC 3.0.4) │ 4041 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Perl (5.26.1) │ 4042 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Raku (Rakudo 2020.02.1) │ 4043 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ PHP (7.4.4) │ 4044 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Prolog (SWI-Prolog 8.0.3) │ 4045 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ PyPy2 (7.3.0) │ 4046 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ PyPy3 (7.3.0) │ 4047 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Racket (7.6) │ 4048 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Ruby (2.7.1) │ 4049 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Rust (1.42.0) │ 4050 │ | |
// ├──────────────────────────────────┼──────┤ // ← TODO | |
// │ Scala (2.13.1) │ 4051 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Java (OpenJDK 1.8.0) │ 4052 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Scheme (Gauche 0.9.9) │ 4053 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Standard ML (MLton 20130715) │ 4054 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Swift (5.2.1) │ 4055 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Text (cat 8.28) │ 4056 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ TypeScript (3.8) │ 4057 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Visual Basic (.NET Core 3.1.101) │ 4058 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Zsh (5.4.2) │ 4059 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ COBOL - Fixed (OpenCOBOL 1.1.0) │ 4060 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ COBOL - Free (OpenCOBOL 1.1.0) │ 4061 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Brainfuck (bf 20041219) │ 4062 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Ada2012 (GNAT 9.2.1) │ 4063 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Unlambda (2.0.0) │ 4064 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Cython (0.29.16) │ 4065 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Sed (4.4) │ 4066 │ | |
// ├──────────────────────────────────┼──────┤ | |
// │ Vim (8.2.0460) │ 4067 │ | |
// └──────────────────────────────────┴──────┘ | |
match self { | |
Self::Cpp => &[3003, 3005, 3029, 3030, 4003, 4004], | |
Self::Bash => &[3001, 4007], | |
Self::C => &[3002, 3004, 4001, 4002], | |
Self::Cs => &[3006, 4007, 4008, 4009], | |
Self::Clj => &[3007, 4013], | |
Self::Lisp => &[3008, 4038], | |
Self::D => &[3009, 3010, 3011, 4015, 4016, 4017], | |
Self::F08 => &[3012], | |
Self::Go => &[3013, 4026], | |
Self::Hs => &[3014, 4027], | |
Self::Java => &[3015, 3016, 4006], | |
Self::Js => &[3017, 4030], | |
Self::Ml => &[3018, 4039], | |
Self::Pas => &[3019, 4041], | |
Self::Pl => &[3020, 4042], | |
Self::Php => &[3021, 4044], | |
Self::Py => &[3022, 3023, 3509, 3510, 4006, 4046, 4047], | |
Self::Rb => &[3024, 4049], | |
Self::Scala => &[3025], | |
Self::Scm => &[3026], | |
Self::Txt => &[3027], | |
Self::Vb => &[3028], | |
Self::Swift => &[3503], | |
Self::Rs => &[3504, 4050], | |
Self::Sed => &[3505], | |
Self::Awk => &[3506, 4009], | |
Self::Bf => &[3507], | |
Self::Sml => &[3508], | |
Self::Cr => &[3511, 4014], | |
Self::Fs => &[3512, 4022, 4023], | |
Self::Unl => &[3513], | |
Self::Lua => &[3514, 3515, 4033, 4034], | |
Self::Moon => &[3516], | |
Self::Ceylon => &[3517], | |
Self::Jl => &[3518, 4031], | |
Self::Nim => &[3520, 4036], | |
Self::Ts => &[3512], | |
Self::P6 => &[3522], | |
Self::Kt => &[3523, 4032], | |
Self::Cob => &[3525, 3526], | |
Self::Bc => &[4008], | |
Self::Dart => &[4018], | |
Self::Dc => &[4019], | |
Self::Erl => &[4020], | |
Self::Ex => &[4021], | |
Self::Forth => &[4024], | |
Self::F18 => &[4025], | |
Self::Hx => &[4028, 4029], | |
Self::Sh => &[4035], | |
Self::Raku => &[4043], | |
Self::Pro => &[4045], | |
Self::Rkt => &[4048], | |
} | |
} | |
} | |
#[derive(Debug)] | |
struct Summary { | |
submission_time: DateTime<FixedOffset>, | |
task_display: String, | |
task_path: String, | |
user: String, | |
language: String, | |
score: u32, | |
code_size: u32, | |
status: String, | |
exec_time_millis: u64, | |
memory: String, | |
detail: String, | |
} | |
impl Summary { | |
fn file_stem(&self) -> impl AsRef<Path> { | |
format!( | |
"{}-{}Z-{}", | |
self.task_display | |
.split_whitespace() | |
.next() | |
.unwrap_or_default() | |
.to_lowercase(), | |
self.submission_time.naive_utc().format("%Y-%m-%d-%H-%M-%S"), | |
self.status, | |
) | |
} | |
} | |
fn create_dir_all(path: &Path) -> anyhow::Result<()> { | |
fs::create_dir_all(path).with_context(|| format!("Failed to crate {}", path.display())) | |
} | |
fn write(path: &Path, contents: &str) -> anyhow::Result<()> { | |
fs::write(path, contents).with_context(|| format!("Failed to write {}", path.display()))?; | |
info!("Wrote {}", path.display()); | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment