Skip to content

Instantly share code, notes, and snippets.

@qryxip
Last active May 14, 2020 18:23
Show Gist options
  • Save qryxip/d154944658df7d31e926993d675378a3 to your computer and use it in GitHub Desktop.
Save qryxip/d154944658df7d31e926993d675378a3 to your computer and use it in GitHub Desktop.
Scrape submitted code from AtCoder
//! 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