Skip to content

Commit 69cf8e2

Browse files
committed
initial commit
1 parent c59b2ab commit 69cf8e2

7 files changed

+420
-0
lines changed

.nojekyll

Whitespace-only changes.

crawl.py

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import json
4+
import pathlib
5+
import random
6+
import subprocess
7+
import sys
8+
import tempfile
9+
from logging import DEBUG, INFO, basicConfig, getLogger
10+
from typing import *
11+
12+
import requests
13+
14+
logger = getLogger(__name__)
15+
16+
17+
class Problem(NamedTuple):
18+
url: str
19+
title: str
20+
21+
22+
def list_atcoder_problems() -> List[Problem]:
23+
resp = requests.get('https://kenkoooo.com/atcoder/resources/problems.json')
24+
resp.raise_for_status()
25+
data = json.loads(resp.content)
26+
problems = []
27+
for row in data:
28+
url = 'https://atcoder.jp/contests/{}/tasks/{}'.format(row['contest_id'], row['id'])
29+
problems.append(Problem(url=url, title=row['title']))
30+
random.shuffle(problems)
31+
return problems
32+
33+
34+
def generate(url: str) -> Dict[str, str]:
35+
templates = [
36+
'main.cpp',
37+
'main.py',
38+
'generate.cpp',
39+
'generate.py',
40+
]
41+
42+
with tempfile.TemporaryDirectory() as tempdir_:
43+
tempdir = pathlib.Path(tempdir_)
44+
45+
# TODO: Make this robust. This implementation highly depends oj-prepare.
46+
config_toml = tempdir / 'config.toml'
47+
with open(config_toml, 'w') as fh:
48+
print('[templates]', file=fh)
49+
for template in templates:
50+
print('"{}" = "{}"'.format(template, template), file=fh)
51+
52+
subprocess.run(['oj-prepare', '--config-file', str(config_toml), url], cwd=tempdir)
53+
54+
result = {}
55+
for template in templates:
56+
with open(tempdir / template) as fh:
57+
result[template] = fh.read()
58+
return result
59+
60+
61+
def update(problems: Dict[str, Dict[str, Any]]) -> None:
62+
for problem in list_atcoder_problems():
63+
try:
64+
template = generate(problem.url)
65+
except Exception:
66+
logger.exception('failed for problem: %s', problem.url)
67+
template = {}
68+
problems[problem.url] = {
69+
'url': problem.url,
70+
'title': problem.title,
71+
'template': template,
72+
}
73+
74+
75+
def main() -> int:
76+
parser = argparse.ArgumentParser()
77+
parser.add_argument('--file', default='data.json')
78+
parser.add_argument('-v', '--verbose', action='store_true')
79+
args = parser.parse_args()
80+
81+
# config the global logger
82+
level = INFO
83+
if args.verbose:
84+
level = DEBUG
85+
basicConfig(level=level)
86+
87+
problems = {}
88+
path = pathlib.Path(args.file)
89+
if path.exists():
90+
with open(path) as fh:
91+
try:
92+
data = json.load(fh)
93+
except ValueError:
94+
logger.exception('data is broken')
95+
return 1
96+
else:
97+
problems = {problem['url']: problem for problem in data}
98+
try:
99+
update(problems)
100+
finally:
101+
with open(path, 'w') as fh:
102+
json.dump(list(problems.values()), fh)
103+
return 0
104+
105+
106+
if __name__ == '__main__':
107+
sys.exit(main())

index.html

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>online-judge-tools/template-generator-webapp</title>
7+
8+
<!-- Bootstrap -->
9+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
10+
11+
<!-- highlight.js -->
12+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/styles/default.min.css">
13+
</head>
14+
15+
<body>
16+
<nav class="navbar navbar-dark bg-dark">
17+
<div class="container-fluid">
18+
<span class="navbar-brand"><span class="text-white-50">online-judge-tools/</span><span class="display-6">template-generator-webapp</span></span>
19+
</div>
20+
</nav>
21+
22+
<section class="container-fluid mt-5 mb-5">
23+
<div class="row mb-5">
24+
<div class="col-auto">
25+
<label for="problemURL">
26+
Problem URL:
27+
</label>
28+
</div>
29+
<div class="col-auto">
30+
<form id="generateForm" onsubmit="return false;">
31+
<div class="input-group">
32+
<input type="text" class="form-control" size="40" id="urlInput" placeholder="https://atcoder.jp/contests/agc006/tasks/agc006_c">
33+
<button type="submit" class="btn btn-primary">Generate!</button>
34+
</div>
35+
</form>
36+
</div>
37+
</div>
38+
39+
<div class="row">
40+
<div class="col-auto">
41+
Problem Name:
42+
</div>
43+
<div class="col-auto">
44+
<div class="input-group">
45+
<input type="text" disabled class="form-control" id="nameInput"></div>
46+
</div>
47+
</div>
48+
</div>
49+
<div class="row mt-1">
50+
<div class="col-auto">
51+
Language:
52+
</div>
53+
<div class="col-auto">
54+
<div class="input-group">
55+
<select class="form-select" id="templateSelect">
56+
<option selected value="main.cpp">main.cpp</option>
57+
<option value="main.py">main.py</option>
58+
<option value="generate.cpp">generate.cpp</option>
59+
<option value="generate.py">generate.py</option>
60+
</select>
61+
</div>
62+
</div>
63+
</div>
64+
<div class="row mt-2 mb-1">
65+
<div class="col-auto">
66+
Generated Code:
67+
</div>
68+
</div>
69+
<div class="row">
70+
<div class="col-auto pr-3">
71+
</div>
72+
<div class="col-auto" id="codeContainer">
73+
<pre><code></code></pre>
74+
</div>
75+
</div>
76+
</section>
77+
78+
<hr>
79+
80+
<footer class="container-fluid">
81+
<small><a class="text-muted" href="#">See GitHub repository for this page.</a></small>
82+
</footer>
83+
84+
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
85+
<script src="https://cdn.jsdelivr.net/gh/highlightjs/[email protected]/build/highlight.min.js"></script>
86+
<script src="index.js"></script>
87+
</body>
88+
</html>

index.ts

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
declare const hljs: any;
2+
3+
interface Problem {
4+
url: string;
5+
title: string;
6+
template: { [name: string]: string };
7+
}
8+
9+
function loadPrecomputedData(): Problem[] {
10+
const url = "data.json";
11+
const req = new XMLHttpRequest();
12+
req.open("GET", url, true);
13+
req.send();
14+
if (req.status !== 200) {
15+
throw new Error(req.statusText);
16+
}
17+
return JSON.parse(req.responseText);
18+
}
19+
20+
function loadPrecomputedDataWithCache(): Problem[] {
21+
const key = "cache";
22+
const cache = JSON.parse(sessionStorage.getItem(key)) as Problem[];
23+
if (cache) {
24+
return cache;
25+
}
26+
const data = loadPrecomputedData();
27+
sessionStorage.setItem(key, JSON.stringify(data));
28+
return data;
29+
}
30+
31+
function readProblemUrl(): string {
32+
const defaultUrl = "https://atcoder.jp/contests/agc006/tasks/agc006_c";
33+
const input = document.getElementById("urlInput") as HTMLInputElement;
34+
return input.value ? input.value : defaultUrl;
35+
}
36+
37+
function writeProblemName(name: string): void {
38+
const input = document.getElementById("nameInput") as HTMLInputElement;
39+
input.value = name;
40+
}
41+
42+
function writeGeneratedCode(result: string, lang: string) {
43+
const code = document.createElement("code");
44+
code.innerText = result;
45+
code.classList.add(lang);
46+
hljs.highlightBlock(code);
47+
48+
const pre = document.createElement("pre");
49+
pre.appendChild(code);
50+
51+
const container = document.getElementById(
52+
"codeContainer"
53+
) as HTMLInputElement;
54+
while (container.firstChild) {
55+
container.removeChild(container.lastChild);
56+
}
57+
container.appendChild(pre);
58+
}
59+
60+
function isEquivalentUrl(s1: string, s2: string): boolean {
61+
const url1 = new URL(s1);
62+
const url2 = new URL(s2);
63+
const pathname1 = url1.pathname.replace("//", "/").replace(/\/$/, "");
64+
const pathname2 = url2.pathname.replace("//", "/").replace(/\/$/, "");
65+
return url1.host === url2.host && pathname1 === pathname2;
66+
}
67+
68+
function lookupProblemFromUrl(url: string, data: Problem[]): Problem | null {
69+
try {
70+
new URL(url);
71+
} catch (err) {
72+
return null;
73+
}
74+
for (const problem of data) {
75+
if (isEquivalentUrl(url, problem.url)) {
76+
return problem;
77+
}
78+
}
79+
return null;
80+
}
81+
82+
function getLanguageFromTemplate(template: string): string {
83+
if (template === "main.cpp") return "cpp";
84+
if (template === "main.py") return "python";
85+
if (template === "generate.cpp") return "cpp";
86+
if (template === "generate.py") return "python";
87+
return "plaintext";
88+
}
89+
90+
function update(): void {
91+
const data = loadPrecomputedDataWithCache();
92+
const url = readProblemUrl();
93+
94+
const problem = lookupProblemFromUrl(url, data);
95+
if (problem === null) {
96+
writeProblemName("error");
97+
writeGeneratedCode("invalid URL: " + JSON.stringify(url), "plaintext");
98+
return;
99+
}
100+
writeProblemName(problem.title);
101+
102+
const select = document.getElementById("templateSelect") as HTMLInputElement;
103+
const template = select.value;
104+
if (!(template in problem["template"])) {
105+
writeGeneratedCode(
106+
"failed to geneate the template: " + template,
107+
"plaintext"
108+
);
109+
return;
110+
}
111+
const result = problem.template[template];
112+
const lang = getLanguageFromTemplate(template);
113+
writeGeneratedCode(result, template);
114+
}
115+
116+
document.addEventListener("DOMContentLoaded", (event) => {
117+
const input = document.getElementById("urlInput");
118+
input.addEventListener("input", update);
119+
120+
const form = document.getElementById("generateForm");
121+
form.addEventListener("submit", update);
122+
123+
const select = document.getElementById("templateSelect");
124+
select.addEventListener("change", update);
125+
});

package.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "template-generator-webapp",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"author": "",
10+
"license": "ISC",
11+
"devDependencies": {
12+
"prettier": "2.2.1",
13+
"typescript": "^4.1.3"
14+
}
15+
}

setup.cfg

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[options.extras_require]
2+
dev =
3+
isort == 5.5.2
4+
mypy == 0.782
5+
pylint == 2.6.0
6+
yapf == 0.30.0
7+
8+
[yapf]
9+
column_limit = 9999
10+
11+
[isort]
12+
line_length = 9999
13+
default_section = THIRDPARTY
14+
15+
[mypy]

0 commit comments

Comments
 (0)