Greasy Fork is available in English.

AtCoder Easy Test v2

Make testing sample cases easy

2021-10-26 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

  1. // ==UserScript==
  2. // @name AtCoder Easy Test v2
  3. // @namespace https://atcoder.jp/
  4. // @version 2.7.0
  5. // @description Make testing sample cases easy
  6. // @author magurofly
  7. // @license MIT
  8. // @supportURL https://github.com/magurofly/atcoder-easy-test/
  9. // @match https://atcoder.jp/contests/*/tasks/*
  10. // @match https://yukicoder.me/problems/no/*
  11. // @match http://codeforces.com/contest/*/problem/*
  12. // @match http://codeforces.com/gym/*/problem/*
  13. // @match http://codeforces.com/problemset/problem/*
  14. // @match http://codeforces.com/group/*/contest/*/problem/*
  15. // @match http://*.contest.codeforces.com/group/*/contest/*/problem/*
  16. // @match https://codeforces.com/contest/*/problem/*
  17. // @match https://codeforces.com/gym/*/problem/*
  18. // @match https://codeforces.com/problemset/problem/*
  19. // @match https://codeforces.com/group/*/contest/*/problem/*
  20. // @match https://*.contest.codeforces.com/group/*/contest/*/problem/*
  21. // @match https://greatest.deepsurf.us/*/scripts/433152-atcoder-easy-test-v2
  22. // @grant unsafeWindow
  23. // @grant GM_getValue
  24. // @grant GM_setValue
  25. // ==/UserScript==
  26. (function() {
  27. const codeSaver = {
  28. LIMIT: 10,
  29. get() {
  30. // `json` は、ソースコード文字列またはJSON文字列
  31. let json = unsafeWindow.localStorage.AtCoderEasyTest$lastCode;
  32. let data = [];
  33. try {
  34. if (typeof json == "string") {
  35. data.push(...JSON.parse(json));
  36. }
  37. else {
  38. data = [];
  39. }
  40. }
  41. catch (e) {
  42. data.push({
  43. path: unsafeWindow.localStorage.AtCoderEasyTset$lastPage,
  44. code: json,
  45. });
  46. }
  47. return data;
  48. },
  49. set(data) {
  50. unsafeWindow.localStorage.AtCoderEasyTest$lastCode = JSON.stringify(data);
  51. },
  52. save(code) {
  53. let data = codeSaver.get();
  54. const idx = data.findIndex(({ path }) => path == location.pathname);
  55. if (idx != -1)
  56. data.splice(idx, idx + 1);
  57. data.push({
  58. path: location.pathname,
  59. code,
  60. });
  61. while (data.length > codeSaver.LIMIT)
  62. data.shift();
  63. codeSaver.set(data);
  64. },
  65. restore() {
  66. const data = codeSaver.get();
  67. const idx = data.findIndex(({ path }) => path == location.pathname);
  68. if (idx == -1 || !(data[idx] instanceof Object))
  69. return Promise.reject(`No saved code found for ${location.pathname}`);
  70. return Promise.resolve(data[idx].code);
  71. }
  72. };
  73.  
  74. function similarLangs(targetLang, candidateLangs) {
  75. const [targetName, targetDetail] = targetLang.split(" ", 2);
  76. const selectedLangs = candidateLangs.filter(candidateLang => {
  77. const [name, _] = candidateLang.split(" ", 2);
  78. return name == targetName;
  79. }).map(candidateLang => {
  80. const [_, detail] = candidateLang.split(" ", 2);
  81. return [candidateLang, similarity(detail, targetDetail)];
  82. });
  83. return selectedLangs.sort((a, b) => a[1] - b[1]).map(([lang, _]) => lang);
  84. }
  85. function similarity(s, t) {
  86. const n = s.length, m = t.length;
  87. let dp = new Array(m + 1).fill(0);
  88. for (let i = 0; i < n; i++) {
  89. const dp2 = new Array(m + 1).fill(0);
  90. for (let j = 0; j < m; j++) {
  91. const cost = (s.charCodeAt(i) - t.charCodeAt(j)) ** 2;
  92. dp2[j + 1] = Math.min(dp[j] + cost, dp[j + 1] + cost * 0.25, dp2[j] + cost * 0.25);
  93. }
  94. dp = dp2;
  95. }
  96. return dp[m];
  97. }
  98.  
  99. class CodeRunner {
  100. get label() {
  101. return this._label;
  102. }
  103. constructor(label, site) {
  104. this._label = `${label} [${site}]`;
  105. }
  106. async test(sourceCode, input, expectedOutput, options) {
  107. let result = { status: "IE", input };
  108. try {
  109. result = await this.run(sourceCode, input);
  110. }
  111. catch (e) {
  112. result.error = e.toString();
  113. return result;
  114. }
  115. if (expectedOutput != null)
  116. result.expectedOutput = expectedOutput;
  117. if (result.status != "OK" || typeof expectedOutput != "string")
  118. return result;
  119. let output = result.output || "";
  120. if (options.trim) {
  121. expectedOutput = expectedOutput.trim();
  122. output = output.trim();
  123. }
  124. let equals = (x, y) => x === y;
  125. if (options.allowableError) {
  126. const floatPattern = /^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$/;
  127. const superEquals = equals;
  128. equals = (x, y) => {
  129. if (floatPattern.test(x) && floatPattern.test(y))
  130. return Math.abs(parseFloat(x) - parseFloat(y)) <= options.allowableError;
  131. return superEquals(x, y);
  132. };
  133. }
  134. if (options.split) {
  135. const superEquals = equals;
  136. equals = (x, y) => {
  137. const xs = x.split(/\s+/);
  138. const ys = y.split(/\s+/);
  139. if (xs.length != ys.length)
  140. return false;
  141. const len = xs.length;
  142. for (let i = 0; i < len; i++) {
  143. if (!superEquals(xs[i], ys[i]))
  144. return false;
  145. }
  146. return true;
  147. };
  148. }
  149. result.status = equals(output, expectedOutput) ? "AC" : "WA";
  150. return result;
  151. }
  152. }
  153.  
  154. class CustomRunner extends CodeRunner {
  155. run;
  156. constructor(label, run) {
  157. super(label, "Browser");
  158. this.run = run;
  159. }
  160. }
  161.  
  162. function buildParams(data) {
  163. return Object.entries(data).map(([key, value]) => encodeURIComponent(key) + "=" + encodeURIComponent(value)).join("&");
  164. }
  165. function sleep(ms) {
  166. return new Promise(done => setTimeout(done, ms));
  167. }
  168. function doneOrFail(p) {
  169. return p.then(() => Promise.resolve(), () => Promise.resolve());
  170. }
  171. function html2element(html) {
  172. const template = document.createElement("template");
  173. template.innerHTML = html;
  174. return template.content.firstChild;
  175. }
  176. function newElement(tagName, attrs = {}) {
  177. const e = document.createElement(tagName);
  178. for (const [key, value] of Object.entries(attrs)) {
  179. e[key] = value;
  180. }
  181. return e;
  182. }
  183. async function loadScript(src, ctx = null, env = {}) {
  184. const js = await fetch(src).then(res => res.text());
  185. const keys = [];
  186. const values = [];
  187. for (const [key, value] of Object.entries(env)) {
  188. keys.push(key);
  189. values.push(value);
  190. }
  191. unsafeWindow["Function"](keys.join(), js).apply(ctx, values);
  192. }
  193. const eventListeners = {};
  194. const events = {
  195. on(name, listener) {
  196. const listeners = (name in eventListeners ? eventListeners[name] : eventListeners[name] = []);
  197. listeners.push(listener);
  198. },
  199. trig(name) {
  200. if (name in eventListeners) {
  201. for (const listener of eventListeners[name])
  202. listener();
  203. }
  204. },
  205. };
  206. class ObservableValue {
  207. _value;
  208. _listeners;
  209. constructor(value) {
  210. this._value = value;
  211. this._listeners = new Set();
  212. }
  213. get value() {
  214. return this._value;
  215. }
  216. set value(value) {
  217. this._value = value;
  218. for (const listener of this._listeners)
  219. listener(value);
  220. }
  221. addListener(listener) {
  222. this._listeners.add(listener);
  223. listener(this._value);
  224. }
  225. removeListener(listener) {
  226. this._listeners.delete(listener);
  227. }
  228. map(f) {
  229. const y = new ObservableValue(f(this.value));
  230. this.addListener(x => {
  231. y.value = f(x);
  232. });
  233. return y;
  234. }
  235. }
  236.  
  237. let waitAtCoderCustomTest = Promise.resolve();
  238. const AtCoderCustomTestBase = location.href.replace(/\/tasks\/.+$/, "/custom_test");
  239. const AtCoderCustomTestResultAPI = AtCoderCustomTestBase + "/json?reload=true";
  240. const AtCoderCustomTestSubmitAPI = AtCoderCustomTestBase + "/submit/json";
  241. class AtCoderRunner extends CodeRunner {
  242. languageId;
  243. constructor(languageId, label) {
  244. super(label, "AtCoder");
  245. this.languageId = languageId;
  246. }
  247. async run(sourceCode, input) {
  248. const promise = this.submit(sourceCode, input);
  249. waitAtCoderCustomTest = promise;
  250. return await promise;
  251. }
  252. async submit(sourceCode, input) {
  253. try {
  254. await waitAtCoderCustomTest;
  255. }
  256. catch (error) {
  257. console.error(error);
  258. }
  259. const error = await fetch(AtCoderCustomTestSubmitAPI, {
  260. method: "POST",
  261. credentials: "include",
  262. headers: {
  263. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
  264. },
  265. body: buildParams({
  266. "data.LanguageId": String(this.languageId),
  267. sourceCode,
  268. input,
  269. csrf_token: unsafeWindow.csrfToken,
  270. }),
  271. }).then(r => r.text());
  272. if (error) {
  273. throw new Error(error);
  274. }
  275. await sleep(100);
  276. for (;;) {
  277. const data = await fetch(AtCoderCustomTestResultAPI, {
  278. method: "GET",
  279. credentials: "include",
  280. }).then(r => r.json());
  281. if (!("Result" in data))
  282. continue;
  283. const result = data.Result;
  284. if ("Interval" in data) {
  285. await sleep(data.Interval);
  286. continue;
  287. }
  288. return {
  289. status: (result.ExitCode == 0) ? "OK" : (result.TimeConsumption == -1) ? "CE" : "RE",
  290. exitCode: result.ExitCode,
  291. execTime: result.TimeConsumption,
  292. memory: result.MemoryConsumption,
  293. input,
  294. output: data.Stdout,
  295. error: data.Stderr,
  296. };
  297. }
  298. }
  299. }
  300.  
  301. class PaizaIORunner extends CodeRunner {
  302. name;
  303. constructor(name, label) {
  304. super(label, "PaizaIO");
  305. this.name = name;
  306. }
  307. async run(sourceCode, input) {
  308. let id, status, error;
  309. try {
  310. const res = await fetch("https://api.paiza.io/runners/create?" + buildParams({
  311. source_code: sourceCode,
  312. language: this.name,
  313. input,
  314. longpoll: "true",
  315. longpoll_timeout: "10",
  316. api_key: "guest",
  317. }), {
  318. method: "POST",
  319. mode: "cors",
  320. }).then(r => r.json());
  321. id = res.id;
  322. status = res.status;
  323. error = res.error;
  324. }
  325. catch (error) {
  326. return {
  327. status: "IE",
  328. input,
  329. error: String(error),
  330. };
  331. }
  332. while (status == "running") {
  333. const res = await fetch("https://api.paiza.io/runners/get_status?" + buildParams({
  334. id,
  335. api_key: "guest",
  336. }), {
  337. mode: "cors",
  338. }).then(res => res.json());
  339. status = res.status;
  340. error = res.error;
  341. }
  342. const res = await fetch("https://api.paiza.io/runners/get_details?" + buildParams({
  343. id,
  344. api_key: "guest",
  345. }), {
  346. mode: "cors",
  347. }).then(r => r.json());
  348. const result = {
  349. status: "OK",
  350. exitCode: String(res.exit_code),
  351. execTime: +res.time * 1e3,
  352. memory: +res.memory * 1e-3,
  353. input,
  354. };
  355. if (res.build_result == "failure") {
  356. result.status = "CE";
  357. result.exitCode = res.build_exit_code;
  358. result.output = res.build_stdout;
  359. result.error = res.build_stderr;
  360. }
  361. else {
  362. result.status = (res.result == "timeout") ? "TLE" : (res.result == "failure") ? "RE" : "OK";
  363. result.exitCode = res.exit_code;
  364. result.output = res.stdout;
  365. result.error = res.stderr;
  366. }
  367. return result;
  368. }
  369. }
  370.  
  371. class WandboxRunner extends CodeRunner {
  372. name;
  373. options;
  374. constructor(name, label, options = {}) {
  375. super(label, "Wandbox");
  376. this.name = name;
  377. this.options = options;
  378. }
  379. getOptions(sourceCode, input) {
  380. if (typeof this.options == "function")
  381. return this.options(sourceCode, input);
  382. return this.options;
  383. }
  384. run(sourceCode, input) {
  385. const options = this.getOptions(sourceCode, input);
  386. return this.request(Object.assign({
  387. compiler: this.name,
  388. code: sourceCode,
  389. stdin: input,
  390. }, options));
  391. }
  392. async request(body) {
  393. const startTime = Date.now();
  394. let res;
  395. try {
  396. res = await fetch("https://wandbox.org/api/compile.json", {
  397. method: "POST",
  398. mode: "cors",
  399. headers: {
  400. "Content-Type": "application/json",
  401. },
  402. body: JSON.stringify(body),
  403. }).then(r => r.json());
  404. }
  405. catch (error) {
  406. console.error(error);
  407. return {
  408. status: "IE",
  409. input: body.stdin,
  410. error: String(error),
  411. };
  412. }
  413. const endTime = Date.now();
  414. const result = {
  415. status: "OK",
  416. exitCode: String(res.status),
  417. execTime: endTime - startTime,
  418. input: body.stdin,
  419. output: String(res.program_output || ""),
  420. error: String(res.program_error || ""),
  421. };
  422. // 正常終了以外の場合
  423. if (res.status != 0) {
  424. if (res.signal) {
  425. result.exitCode += ` (${res.signal})`;
  426. }
  427. result.output = String(res.compiler_output || "") + String(result.output || "");
  428. result.error = String(res.compiler_error || "") + String(result.error || "");
  429. if (res.compiler_output || res.compiler_error) {
  430. result.status = "CE";
  431. }
  432. else {
  433. result.status = "RE";
  434. }
  435. }
  436. return result;
  437. }
  438. }
  439.  
  440. class WandboxCppRunner extends WandboxRunner {
  441. async run(sourceCode, input) {
  442. // ACL を結合する
  443. const ACLBase = "https://cdn.jsdelivr.net/gh/atcoder/ac-library/";
  444. const files = new Map();
  445. const includeHeader = async (source) => {
  446. const pattern = /^#\s*include\s*[<"]atcoder\/([^>"]+)[>"]/gm;
  447. const loaded = [];
  448. let match;
  449. while (match = pattern.exec(source)) {
  450. const file = "atcoder/" + match[1];
  451. if (files.has(file))
  452. continue;
  453. files.set(file, null);
  454. loaded.push([file, fetch(ACLBase + file, { mode: "cors", cache: "force-cache", }).then(r => r.text())]);
  455. }
  456. const included = await Promise.all(loaded.map(async ([file, r]) => {
  457. const source = await r;
  458. files.set(file, source);
  459. return source;
  460. }));
  461. for (const source of included) {
  462. await includeHeader(source);
  463. }
  464. };
  465. await includeHeader(sourceCode);
  466. const codes = [];
  467. for (const [file, code] of files) {
  468. codes.push({ file, code, });
  469. }
  470. const options = this.getOptions(sourceCode, input);
  471. return await this.request(Object.assign({
  472. compiler: this.name,
  473. code: sourceCode,
  474. stdin: input,
  475. codes,
  476. "compiler-option-raw": "-I.",
  477. }, options));
  478. }
  479. }
  480.  
  481. let brythonRunnerLoaded = false;
  482. const brythonRunner = new CustomRunner("Brython", async (sourceCode, input) => {
  483. if (!brythonRunnerLoaded) {
  484. // BrythonRunner を読み込む
  485. await new Promise((resolve) => {
  486. const script = document.createElement("script");
  487. script.src = "https://cdn.jsdelivr.net/gh/pythonpad/brython-runner/lib/brython-runner.bundle.js";
  488. script.onload = () => {
  489. brythonRunnerLoaded = true;
  490. resolve(null);
  491. };
  492. document.head.appendChild(script);
  493. });
  494. }
  495. let stdout = "";
  496. let stderr = "";
  497. let stdinOffset = 0;
  498. const BrythonRunner = unsafeWindow.BrythonRunner;
  499. const runner = new BrythonRunner({
  500. stdout: { write(content) { stdout += content; }, flush() { } },
  501. stderr: { write(content) { stderr += content; }, flush() { } },
  502. stdin: { async readline() {
  503. let index = input.indexOf("\n", stdinOffset) + 1;
  504. if (index == 0)
  505. index = input.length;
  506. const text = input.slice(stdinOffset, index);
  507. stdinOffset = index;
  508. return text;
  509. } },
  510. });
  511. const timeStart = Date.now();
  512. await runner.runCode(sourceCode);
  513. const timeEnd = Date.now();
  514. return {
  515. status: "OK",
  516. exitCode: "0",
  517. execTime: (timeEnd - timeStart),
  518. input,
  519. output: stdout,
  520. error: stderr,
  521. };
  522. });
  523.  
  524. async function loadPyodide() {
  525. const script = await fetch("https://cdn.jsdelivr.net/pyodide/v0.18.1/full/pyodide.js").then(res => res.text());
  526. unsafeWindow["Function"](script)();
  527. const pyodide = await unsafeWindow["loadPyodide"]({
  528. indexURL: "https://cdn.jsdelivr.net/pyodide/v0.18.1/full/",
  529. });
  530. await pyodide.runPythonAsync(`
  531. import contextlib, io, platform
  532. class __redirect_stdin(contextlib._RedirectStream):
  533. _stream = "stdin"
  534. __version = platform.python_version()
  535. `);
  536. pyodideRunner._label = `Python (${pyodide.globals.__version}) [Pyodide ${pyodide.version}]`;
  537. return pyodide;
  538. }
  539. let _pyodide = Promise.reject();
  540. let _serial = Promise.resolve();
  541. const pyodideRunner = new CustomRunner("Pyodide", (sourceCode, input) => new Promise((resolve, reject) => {
  542. _serial = _serial.finally(async () => {
  543. const pyodide = await (_pyodide = _pyodide.catch(loadPyodide));
  544. const code = `
  545. def __run():
  546. global __stdout, __stderr, __stdin
  547. with __redirect_stdin(io.StringIO(__stdin)):
  548. with contextlib.redirect_stdout(io.StringIO()) as __stdout:
  549. with contextlib.redirect_stderr(io.StringIO()) as __stderr:
  550. ` + sourceCode.split("\n").map(line => " " + line).join("\n");
  551. let status = "OK";
  552. let exitCode = "0";
  553. let stdout = "";
  554. let stderr = "";
  555. let startTime = -Infinity;
  556. let endTime = Infinity;
  557. pyodide.globals.__stdin = input;
  558. try {
  559. await pyodide.loadPackagesFromImports(code);
  560. await pyodide.runPythonAsync(code);
  561. startTime = Date.now();
  562. pyodide.runPython("__run()");
  563. endTime = Date.now();
  564. stdout += pyodide.globals.__stdout.getvalue();
  565. stderr += pyodide.globals.__stderr.getvalue();
  566. }
  567. catch (error) {
  568. status = "RE";
  569. exitCode = "-1";
  570. stderr += error.toString();
  571. }
  572. resolve({
  573. status,
  574. exitCode,
  575. execTime: (startTime - endTime),
  576. input,
  577. output: stdout,
  578. error: stderr,
  579. });
  580. });
  581. }));
  582.  
  583. function pairs(list) {
  584. const pairs = [];
  585. const len = list.length >> 1;
  586. for (let i = 0; i < len; i++)
  587. pairs.push([list[i * 2], list[i * 2 + 1]]);
  588. return pairs;
  589. }
  590. async function init$4() {
  591. if (location.host != "atcoder.jp")
  592. throw "Not AtCoder";
  593. const doc = unsafeWindow.document;
  594. const eLanguage = unsafeWindow.$("#select-lang>select");
  595. const langMap = {
  596. 4001: "C GCC 9.2.1",
  597. 4002: "C Clang 10.0.0",
  598. 4003: "C++ GCC 9.2.1",
  599. 4004: "C++ Clang 10.0.0",
  600. 4005: "Java OpenJDK 11.0.6",
  601. 4006: "Python3 CPython 3.8.2",
  602. 4007: "Bash 5.0.11",
  603. 4008: "bc 1.07.1",
  604. 4009: "Awk GNU Awk 4.1.4",
  605. 4010: "C# .NET Core 3.1.201",
  606. 4011: "C# Mono-mcs 6.8.0.105",
  607. 4012: "C# Mono-csc 3.5.0",
  608. 4013: "Clojure 1.10.1.536",
  609. 4014: "Crystal 0.33.0",
  610. 4015: "D DMD 2.091.0",
  611. 4016: "D GDC 9.2.1",
  612. 4017: "D LDC 1.20.1",
  613. 4018: "Dart 2.7.2",
  614. 4019: "dc 1.4.1",
  615. 4020: "Erlang 22.3",
  616. 4021: "Elixir 1.10.2",
  617. 4022: "F# .NET Core 3.1.201",
  618. 4023: "F# Mono 10.2.3",
  619. 4024: "Forth gforth 0.7.3",
  620. 4025: "Fortran GNU Fortran 9.2.1",
  621. 4026: "Go 1.14.1",
  622. 4027: "Haskell GHC 8.8.3",
  623. 4028: "Haxe 4.0.3",
  624. 4029: "Haxe 4.0.3",
  625. 4030: "JavaScript Node.js 12.16.1",
  626. 4031: "Julia 1.4.0",
  627. 4032: "Kotlin 1.3.71",
  628. 4033: "Lua Lua 5.3.5",
  629. 4034: "Lua LuaJIT 2.1.0",
  630. 4035: "Dash 0.5.8",
  631. 4036: "Nim 1.0.6",
  632. 4037: "Objective-C Clang 10.0.0",
  633. 4038: "Lisp SBCL 2.0.3",
  634. 4039: "OCaml 4.10.0",
  635. 4040: "Octave 5.2.0",
  636. 4041: "Pascal FPC 3.0.4",
  637. 4042: "Perl 5.26.1",
  638. 4043: "Raku Rakudo 2020.02.1",
  639. 4044: "PHP 7.4.4",
  640. 4045: "Prolog SWI-Prolog 8.0.3",
  641. 4046: "Python PyPy2 7.3.0",
  642. 4047: "Python3 PyPy3 7.3.0",
  643. 4048: "Racket 7.6",
  644. 4049: "Ruby 2.7.1",
  645. 4050: "Rust 1.42.0",
  646. 4051: "Scala 2.13.1",
  647. 4052: "Java OpenJDK 1.8.0",
  648. 4053: "Scheme Gauche 0.9.9",
  649. 4054: "ML MLton 20130715",
  650. 4055: "Swift 5.2.1",
  651. 4056: "Text cat 8.28",
  652. 4057: "TypeScript 3.8",
  653. 4058: "Basic .NET Core 3.1.101",
  654. 4059: "Zsh 5.4.2",
  655. 4060: "COBOL Fixed OpenCOBOL 1.1.0",
  656. 4061: "COBOL Free OpenCOBOL 1.1.0",
  657. 4062: "Brainfuck bf 20041219",
  658. 4063: "Ada Ada2012 GNAT 9.2.1",
  659. 4064: "Unlambda 2.0.0",
  660. 4065: "Cython 0.29.16",
  661. 4066: "Sed 4.4",
  662. 4067: "Vim 8.2.0460",
  663. };
  664. const languageId = new ObservableValue(eLanguage.val());
  665. eLanguage.change(() => {
  666. languageId.value = eLanguage.val();
  667. });
  668. const language = languageId.map(lang => langMap[lang]);
  669. function getTestCases() {
  670. const selectors = [
  671. ["#task-statement p+pre.literal-block", ".section"],
  672. ["#task-statement pre.source-code-for-copy", ".part"],
  673. ["#task-statement .lang>*:nth-child(1) .div-btn-copy+pre", ".part"],
  674. ["#task-statement .div-btn-copy+pre", ".part"],
  675. ["#task-statement>.part pre.linenums", ".part"],
  676. ["#task-statement>.part:not(.io-style)>h3+section>pre", ".part"],
  677. ["#task-statement pre", ".part"],
  678. ];
  679. for (const [selector, closestSelector] of selectors) {
  680. let e = [...doc.querySelectorAll(selector)];
  681. e = e.filter(e => !e.closest(".io-style")); // practice2
  682. if (e.length == 0)
  683. continue;
  684. return pairs(e).map(([input, output], index) => ({
  685. title: `Sample ${index + 1}`,
  686. input: input.textContent,
  687. output: output.textContent,
  688. anchor: (input.closest(closestSelector) || input.parentElement).querySelector(".btn-copy"),
  689. }));
  690. }
  691. { // maximum_cup_2018_d
  692. let e = [...doc.querySelectorAll("#task-statement .div-btn-copy+pre")];
  693. e = e.filter(f => !f.childElementCount);
  694. if (e.length) {
  695. return pairs(e).map(([input, output], index) => ({
  696. title: `Sample ${index + 1}`,
  697. input: input.textContent,
  698. output: output.textContent,
  699. anchor: (input.closest(".part") || input.parentElement).querySelector(".btn-copy"),
  700. }));
  701. }
  702. }
  703. return [];
  704. }
  705. return {
  706. name: "AtCoder",
  707. language,
  708. get sourceCode() {
  709. return unsafeWindow.getSourceCode();
  710. },
  711. set sourceCode(sourceCode) {
  712. doc.querySelector(".plain-textarea").value = sourceCode;
  713. unsafeWindow.$(".editor").data("editor").doc.setValue(sourceCode);
  714. },
  715. submit() {
  716. doc.querySelector("#submit").click();
  717. },
  718. get testButtonContainer() {
  719. return doc.querySelector("#submit").parentElement;
  720. },
  721. get sideButtonContainer() {
  722. return doc.querySelector(".editor-buttons");
  723. },
  724. get bottomMenuContainer() {
  725. return doc.getElementById("main-div");
  726. },
  727. get resultListContainer() {
  728. return doc.querySelector(".form-code-submit");
  729. },
  730. get testCases() {
  731. return getTestCases();
  732. },
  733. get jQuery() {
  734. return unsafeWindow["jQuery"];
  735. },
  736. };
  737. }
  738.  
  739. async function init$3() {
  740. if (location.host != "yukicoder.me")
  741. throw "Not yukicoder";
  742. const $ = unsafeWindow.$;
  743. const doc = unsafeWindow.document;
  744. const editor = unsafeWindow.ace.edit("rich_source");
  745. const eSourceObject = $("#source");
  746. const eLang = $("#lang");
  747. const eSamples = $(".sample");
  748. const langMap = {
  749. "cpp14": "C++ C++14 GCC 11.1.0 + Boost 1.77.0",
  750. "cpp17": "C++ C++17 GCC 11.1.0 + Boost 1.77.0",
  751. "cpp-clang": "C++ C++17 Clang 10.0.0 + Boost 1.76.0",
  752. "cpp23": "C++ C++11 GCC 8.4.1",
  753. "c11": "C++ C++11 GCC 11.1.0",
  754. "c": "C C90 GCC 8.4.1",
  755. "java8": "Java Java16 OpenJDK 16.0.1",
  756. "csharp": "C# CSC 3.9.0",
  757. "csharp_mono": "C# Mono 6.12.0.147",
  758. "csharp_dotnet": "C# .NET 5.0",
  759. "perl": "Perl 5.26.3",
  760. "raku": "Raku Rakudo v2021-07-2-g74d7ff771",
  761. "php": "PHP 7.2.24",
  762. "php7": "PHP 8.0.8",
  763. "python3": "Python3 3.9.6 + numpy 1.14.5 + scipy 1.1.0",
  764. "pypy2": "Python PyPy2 7.3.5",
  765. "pypy3": "Python3 PyPy3 7.3.5",
  766. "ruby": "Ruby 3.0.2p107",
  767. "d": "D DMD 2.097.1",
  768. "go": "Go 1.16.6",
  769. "haskell": "Haskell 8.10.5",
  770. "scala": "Scala 2.13.6",
  771. "nim": "Nim 1.4.8",
  772. "rust": "Rust 1.53.0",
  773. "kotlin": "Kotlin 1.5.21",
  774. "scheme": "Scheme Gauche 0.9.10",
  775. "crystal": "Crystal 1.1.1",
  776. "swift": "Swift 5.4.2",
  777. "ocaml": "OCaml 4.12.0",
  778. "clojure": "Clojure 1.10.2.790",
  779. "fsharp": "F# 5.0",
  780. "elixir": "Elixir 1.7.4",
  781. "lua": "Lua LuaJIT 2.0.5",
  782. "fortran": "Fortran gFortran 8.4.1",
  783. "node": "JavaScript Node.js 15.5.0",
  784. "typescript": "TypeScript 4.3.5",
  785. "lisp": "Lisp Common Lisp sbcl 2.1.6",
  786. "sml": "ML Standard ML MLton 20180207-6",
  787. "kuin": "Kuin KuinC++ v.2021.7.17",
  788. "vim": "Vim v8.2",
  789. "sh": "Bash 4.4.19",
  790. "nasm": "Assembler nasm 2.13.03",
  791. "clay": "cLay 20210917-1",
  792. "bf": "Brainfuck BFI 1.1",
  793. "Whitespace": "Whitespace 0.3",
  794. "text": "Text cat 8.3",
  795. };
  796. const language = new ObservableValue(langMap[eLang.val()]);
  797. eLang.on("change", () => {
  798. language.value = langMap[eLang.val()];
  799. });
  800. return {
  801. name: "yukicoder",
  802. language,
  803. get sourceCode() {
  804. if (eSourceObject.is(":visible"))
  805. return eSourceObject.val();
  806. return editor.getSession().getValue();
  807. },
  808. set sourceCode(sourceCode) {
  809. eSourceObject.val(sourceCode);
  810. editor.getSession().setValue(sourceCode);
  811. },
  812. submit() {
  813. doc.querySelector(`#submit_form input[type="submit"]`).click();
  814. },
  815. get testButtonContainer() {
  816. return doc.querySelector("#submit_form");
  817. },
  818. get sideButtonContainer() {
  819. return doc.querySelector("#toggle_source_editor").parentElement;
  820. },
  821. get bottomMenuContainer() {
  822. return doc.body;
  823. },
  824. get resultListContainer() {
  825. return doc.querySelector("#content");
  826. },
  827. get testCases() {
  828. const testCases = [];
  829. let sampleId = 1;
  830. for (let i = 0; i < eSamples.length; i++) {
  831. const eSample = eSamples.eq(i);
  832. const [eInput, eOutput] = eSample.find("pre");
  833. const anchorContainer = $(`<span>`);
  834. const anchor = $(`<span>`);
  835. anchorContainer.append(anchor);
  836. eSample.find("h6").eq(0).appendTo(anchorContainer);
  837. anchorContainer.insertAfter(eSample.find("button").eq(0));
  838. testCases.push({
  839. title: `Sample ${sampleId++}`,
  840. input: eInput.textContent,
  841. output: eOutput.textContent,
  842. anchor: anchor[0],
  843. });
  844. }
  845. return testCases;
  846. },
  847. get jQuery() {
  848. return $;
  849. },
  850. };
  851. }
  852.  
  853. let data = {};
  854. function toString() {
  855. return JSON.stringify(data);
  856. }
  857. function save() {
  858. GM_setValue("config", toString());
  859. }
  860. function load() {
  861. data = JSON.parse(GM_getValue("config") || "{}");
  862. }
  863. load();
  864. /** プロパティ名は camelCase にすること */
  865. const config = {
  866. getString(key, defaultValue = "") {
  867. if (!(key in data))
  868. config.setString(key, defaultValue);
  869. return data[key];
  870. },
  871. setString(key, value) {
  872. data[key] = value;
  873. save();
  874. },
  875. has(key) {
  876. return key in data;
  877. },
  878. get(key, defaultValue = null) {
  879. if (!(key in data))
  880. config.set(key, defaultValue);
  881. return JSON.parse(data[key]);
  882. },
  883. set(key, value) {
  884. config.setString(key, JSON.stringify(value));
  885. },
  886. save,
  887. load,
  888. toString,
  889. };
  890.  
  891. class Editor {
  892. _element;
  893. constructor(lang) {
  894. this._element = document.createElement("textarea");
  895. this._element.style.fontFamily = "monospace";
  896. this._element.style.width = "100%";
  897. this._element.style.minHeight = "5em";
  898. }
  899. get element() {
  900. return this._element;
  901. }
  902. get sourceCode() {
  903. return this._element.value;
  904. }
  905. set sourceCode(sourceCode) {
  906. this._element.value = sourceCode;
  907. }
  908. setLanguage(lang) {
  909. }
  910. }
  911.  
  912. var hPage = "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n <title>AtCoder Easy Test</title>\n <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css\" rel=\"stylesheet\">\n </head>\n <body>\n <div class=\"container\">\n <div class=\"panel panel-default\">\n <div class=\"panel-heading\">Settings</div>\n <div class=\"panel-body\">\n <form id=\"options\">\n </form>\n </div>\n </div>\n </div>\n <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\"></script>\n <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js\"></script>\n </body>\n</html>";
  913.  
  914. const options = [];
  915. const settings = {
  916. /** 設定ページを開く
  917. * クリックなどのイベント時にしか正しく実行できない
  918. */
  919. open() {
  920. const win = window.open("about:blank");
  921. const doc = win.document;
  922. doc.open();
  923. doc.write(hPage);
  924. doc.close();
  925. const root = doc.getElementById("options");
  926. for (const { type, key, defaultValue, description } of options) {
  927. switch (type) {
  928. case "checkbox": {
  929. const container = newElement("div", { className: "checkbox" });
  930. root.appendChild(container);
  931. const label = newElement("label");
  932. container.appendChild(label);
  933. const element = newElement("input", {
  934. type: "checkbox",
  935. checked: config.get(key, defaultValue),
  936. });
  937. element.addEventListener("change", () => {
  938. config.set(key, element.checked);
  939. });
  940. label.appendChild(element);
  941. label.appendChild(document.createTextNode(description));
  942. break;
  943. }
  944. default:
  945. throw new TypeError(`AtCoderEasyTest.setting: undefined option type ${type} for ${key}`);
  946. }
  947. }
  948. },
  949. /** 設定項目を登録 */
  950. registerFlag(key, defaultValue, description) {
  951. options.push({
  952. type: "checkbox",
  953. key,
  954. defaultValue,
  955. description,
  956. });
  957. }
  958. };
  959.  
  960. settings.registerFlag("codeforces.showEditor", true, "Show Editor in Codeforces Problem Page");
  961. async function init$2() {
  962. if (location.host != "codeforces.com")
  963. throw "not Codeforces";
  964. //TODO: m1.codeforces.com, m2.codeforces.com, m3.codeforces.com に対応する
  965. const doc = unsafeWindow.document;
  966. const eLang = doc.querySelector("select[name='programTypeId']");
  967. doc.head.appendChild(newElement("link", {
  968. rel: "stylesheet",
  969. href: "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css",
  970. }));
  971. const eButtons = newElement("span");
  972. doc.querySelector(".submitForm").appendChild(eButtons);
  973. await loadScript("https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js");
  974. const jQuery = unsafeWindow["jQuery"].noConflict();
  975. unsafeWindow["jQuery"] = unsafeWindow["$"];
  976. unsafeWindow["jQuery11"] = jQuery;
  977. await loadScript("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js", null, { jQuery, $: jQuery });
  978. const langMap = {
  979. 3: "Delphi 7",
  980. 4: "Pascal Free Pascal 3.0.2",
  981. 6: "PHP 7.2.13",
  982. 7: "Python 2.7.18",
  983. 9: "C# Mono 6.8",
  984. 12: "Haskell GHC 8.10.1",
  985. 13: "Perl 5.20.1",
  986. 19: "OCaml 4.02.1",
  987. 20: "Scala 2.12.8",
  988. 28: "D DMD32 v2.091.0",
  989. 31: "Python3 3.8.10",
  990. 32: "Go 1.15.6",
  991. 34: "JavaScript V8 4.8.0",
  992. 36: "Java 1.8.0_241",
  993. 40: "Python PyPy2 2.7 (7.3.0)",
  994. 41: "Python3 PyPy3 3.7 (7.3.0)",
  995. 43: "C C11 GCC 5.1.0",
  996. 48: "Kotlin 1.5.31",
  997. 49: "Rust 1.49.0",
  998. 50: "C++ C++14 G++ 6.4.0",
  999. 51: "Pascal PascalABC.NET 3.4.1",
  1000. 52: "C++ C++17 Clang++",
  1001. 54: "C++ C++17 G++ 7.3.0",
  1002. 55: "JavaScript Node.js 12.6.3",
  1003. 59: "C++ Microsoft Visual C++ 2017",
  1004. 60: "Java 11.0.6",
  1005. 61: "C++ C++17 9.2.0 (64 bit, msys 2)",
  1006. 65: "C# 8, .NET Core 3.1",
  1007. 67: "Ruby 3.0.0",
  1008. 70: "Python3 PyPy 3.7 (7.3.5, 64bit)",
  1009. 72: "Kotlin 1.5.31",
  1010. 73: "C++ GNU G++ 11.2.0 (64 bit, winlibs)",
  1011. };
  1012. const language = new ObservableValue(langMap[eLang.value]);
  1013. eLang.addEventListener("change", () => {
  1014. language.value = langMap[eLang.value];
  1015. });
  1016. let _sourceCode = "";
  1017. const eFile = doc.querySelector(".submitForm").elements["sourceFile"];
  1018. eFile.addEventListener("change", async () => {
  1019. if (eFile.files[0]) {
  1020. _sourceCode = await eFile.files[0].text();
  1021. if (editor)
  1022. editor.sourceCode = _sourceCode;
  1023. }
  1024. });
  1025. let editor = null;
  1026. let waitCfFastSubmitCount = 0;
  1027. const waitCfFastSubmit = setInterval(() => {
  1028. if (document.getElementById("editor")) {
  1029. // cf-fast-submit
  1030. if (editor && editor.element)
  1031. editor.element.style.display = "none";
  1032. // 言語セレクトを同期させる
  1033. const eLang2 = doc.querySelector(".submit-form select[name='programTypeId']");
  1034. if (eLang2) {
  1035. eLang.addEventListener("change", () => {
  1036. eLang2.value = eLang.value;
  1037. });
  1038. eLang2.addEventListener("change", () => {
  1039. eLang.value = eLang2.value;
  1040. language.value = langMap[eLang.value];
  1041. });
  1042. }
  1043. // TODO: 選択されたファイルをどうかする
  1044. // エディタを使う
  1045. const aceEditor = unsafeWindow["ace"].edit("editor");
  1046. editor = {
  1047. get sourceCode() {
  1048. return aceEditor.getValue();
  1049. },
  1050. set sourceCode(sourceCode) {
  1051. aceEditor.setValue(sourceCode);
  1052. },
  1053. setLanguage(lang) { },
  1054. };
  1055. // ボタンを追加する
  1056. const buttonContainer = doc.querySelector(".submit-form .submit").parentElement;
  1057. buttonContainer.appendChild(newElement("a", {
  1058. className: "btn btn-info",
  1059. textContent: "Test & Submit",
  1060. onclick: () => events.trig("testAndSubmit"),
  1061. }));
  1062. buttonContainer.appendChild(newElement("a", {
  1063. className: "btn btn-default",
  1064. textContent: "Test All Samples",
  1065. onclick: () => events.trig("testAllSamples"),
  1066. }));
  1067. clearInterval(waitCfFastSubmit);
  1068. }
  1069. else {
  1070. waitCfFastSubmitCount++;
  1071. if (waitCfFastSubmitCount >= 100)
  1072. clearInterval(waitCfFastSubmit);
  1073. }
  1074. }, 100);
  1075. if (config.get("codeforces.showEditor", true)) {
  1076. editor = new Editor(langMap[eLang.value].split(" ")[0]);
  1077. doc.getElementById("pageContent").appendChild(editor.element);
  1078. language.addListener(lang => {
  1079. editor.setLanguage(lang);
  1080. });
  1081. }
  1082. return {
  1083. name: "Codeforces",
  1084. language,
  1085. get sourceCode() {
  1086. if (editor)
  1087. return editor.sourceCode;
  1088. return _sourceCode;
  1089. },
  1090. set sourceCode(sourceCode) {
  1091. const container = new DataTransfer();
  1092. container.items.add(new File([sourceCode], "prog.txt", { type: "text/plain" }));
  1093. const eFile = doc.querySelector(".submitForm").elements["sourceFile"];
  1094. eFile.files = container.files;
  1095. _sourceCode = sourceCode;
  1096. if (editor)
  1097. editor.sourceCode = sourceCode;
  1098. },
  1099. submit() {
  1100. if (editor)
  1101. _sourceCode = editor.sourceCode;
  1102. this.sourceCode = _sourceCode;
  1103. doc.querySelector(`.submitForm .submit`).click();
  1104. },
  1105. get testButtonContainer() {
  1106. return eButtons;
  1107. },
  1108. get sideButtonContainer() {
  1109. return eButtons;
  1110. },
  1111. get bottomMenuContainer() {
  1112. return doc.body;
  1113. },
  1114. get resultListContainer() {
  1115. return doc.querySelector("#pageContent");
  1116. },
  1117. get testCases() {
  1118. return [...doc.querySelectorAll(".sample-test")].map((e, i) => ({
  1119. title: `Sample ${i + 1}`,
  1120. input: e.querySelector(".input pre").textContent,
  1121. output: e.querySelector(".output pre").textContent,
  1122. anchor: e.querySelector(".input .title"),
  1123. }));
  1124. },
  1125. get jQuery() {
  1126. return jQuery;
  1127. },
  1128. };
  1129. }
  1130.  
  1131. async function init$1() {
  1132. if (location.host != "greatest.deepsurf.us" && !location.href.match(/433152-atcoder-easy-test-v2/))
  1133. throw "Not about page";
  1134. const doc = unsafeWindow.document;
  1135. await loadScript("https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js");
  1136. const jQuery = unsafeWindow["jQuery"];
  1137. await loadScript("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js", null, { jQuery, $: jQuery });
  1138. const e = newElement("div");
  1139. doc.getElementById("install-area").appendChild(newElement("button", {
  1140. type: "button",
  1141. textContent: "Open Settings",
  1142. onclick: () => settings.open(),
  1143. }));
  1144. return {
  1145. name: "About Page",
  1146. language: new ObservableValue(""),
  1147. get sourceCode() { return ""; },
  1148. set sourceCode(sourceCode) { },
  1149. submit() { },
  1150. get testButtonContainer() { return e; },
  1151. get sideButtonContainer() { return e; },
  1152. get bottomMenuContainer() { return e; },
  1153. get resultListContainer() { return e; },
  1154. get testCases() { return []; },
  1155. get jQuery() { return jQuery; },
  1156. };
  1157. }
  1158.  
  1159. // 設定ページが開けなくなるのを避ける
  1160. const inits = [init$1()];
  1161. settings.registerFlag("site.atcoder", true, "Use AtCoder Easy Test in AtCoder");
  1162. if (config.get("site.atcoder", true))
  1163. inits.push(init$4());
  1164. settings.registerFlag("site.yukicoder", true, "Use AtCoder Easy Test in yukicoder");
  1165. if (config.get("site.yukicoder", true))
  1166. inits.push(init$3());
  1167. settings.registerFlag("site.codeforces", true, "Use AtCoder Easy Test in Codeforces");
  1168. if (config.get("site.codeforces", true))
  1169. inits.push(init$2());
  1170. var pSite = Promise.any(inits);
  1171.  
  1172. const runners = {
  1173. "C GCC 10.1.0 Wandbox": new WandboxRunner("gcc-10.1.0-c", "C (GCC 10.1.0)"),
  1174. "C C17 Clang 10.0.0 paiza.io": new PaizaIORunner("c", "C (C17 / Clang 10.0.0)"),
  1175. "C++ GCC 10.1.0 + Boost 1.73.0 + ACL Wandbox": new WandboxCppRunner("gcc-10.1.0", "C++ (GCC 10.1.0) + ACL", { options: "warning,boost-1.73.0-gcc-9.2.0,gnu++17" }),
  1176. "C++ Clang 10.0.0 + ACL Wandbox": new WandboxCppRunner("clang-10.0.0", "C++ (Clang 10.0.0) + ACL", { options: "warning,boost-nothing-clang-10.0.0,c++17" }),
  1177. "Python3 CPython 3.8.2 paiza.io": new PaizaIORunner("python3", "Python (3.8.2)"),
  1178. "Python3 Brython": brythonRunner,
  1179. "Python3 Pyodide": pyodideRunner,
  1180. "Bash 5.0.17 paiza.io": new PaizaIORunner("bash", "Bash (5.0.17)"),
  1181. "C# .NET Core 6.0.100-alpha.1.20562.2 Wandbox": new WandboxRunner("csharp", "C# (.NET Core 6.0.100-alpha.1.20562.2)"),
  1182. "C# Mono-mcs HEAD Wandbox": new WandboxRunner("mono-head", "C# (Mono-mcs HEAD)"),
  1183. "Clojure 1.10.1-1 paiza.io": new PaizaIORunner("clojure", "Clojure (1.10.1-1)"),
  1184. "D LDC 1.23.0 paiza.io": new PaizaIORunner("d", "D (LDC 1.23.0)"),
  1185. "Erlang 10.6.4 paiza.io": new PaizaIORunner("erlang", "Erlang (10.6.4)"),
  1186. "Elixir 1.10.4 paiza.io": new PaizaIORunner("elixir", "Elixir (1.10.4)"),
  1187. "F# Interactive 4.0 paiza.io": new PaizaIORunner("fsharp", "F# (Interactive 4.0)"),
  1188. "Go 1.14.1 Wandbox": new WandboxRunner("go-1.14.1", "Go (1.14.1)"),
  1189. "Haskell GHC HEAD Wandbox": new WandboxRunner("ghc-head", "Haskell (GHC HEAD)"),
  1190. "JavaScript Node.js paiza.io": new PaizaIORunner("javascript", "JavaScript (Node.js 12.18.3)"),
  1191. "Kotlin 1.4.0 paiza.io": new PaizaIORunner("kotlin", "Kotlin (1.4.0)"),
  1192. "Lua 5.3.4 Wandbox": new WandboxRunner("lua-5.3.4", "Lua (Lua 5.3.4)"),
  1193. "Lua LuaJIT HEAD Wandbox": new WandboxRunner("luajit-head", "Lua (LuaJIT HEAD)"),
  1194. "Nim 1.0.6 Wandbox": new WandboxRunner("nim-1.0.6", "Nim (1.0.6)"),
  1195. "Objective-C Clang 10.0.0 paiza.io": new PaizaIORunner("objective-c", "Objective-C (Clang 10.0.0)"),
  1196. "Ocaml HEAD Wandbox": new WandboxRunner("ocaml-head", "OCaml (HEAD)"),
  1197. "Pascal FPC 3.0.2 Wandbox": new WandboxRunner("fpc-3.0.2", "Pascal (FPC 3.0.2)"),
  1198. "Perl 5.30.0 paiza.io": new PaizaIORunner("perl", "Perl (5.30.0)"),
  1199. "PHP 7.4.10 paiza.io": new PaizaIORunner("php", "PHP (7.4.10)"),
  1200. "PHP 7.3.3 Wandbox": new WandboxRunner("php-7.3.3", "PHP (7.3.3)"),
  1201. "Python PyPy HEAD Wandbox": new WandboxRunner("pypy-head", "PyPy2 (HEAD)"),
  1202. "Python3 PyPy3 7.2.0 Wandbox": new WandboxRunner("pypy-7.2.0-3", "PyPy3 (7.2.0)"),
  1203. "Ruby 2.7.1 paiza.io": new PaizaIORunner("ruby", "Ruby (2.7.1)"),
  1204. "Ruby HEAD Wandbox": new WandboxRunner("ruby-head", "Ruby (HEAD)"),
  1205. "Ruby 2.7.1 Wandbox": new WandboxRunner("ruby-2.7.1", "Ruby (2.7.1)"),
  1206. "Rust 1.42.0 AtCoder": new AtCoderRunner("4050", "Rust (1.42.0)"),
  1207. "Rust HEAD Wandbox": new WandboxRunner("rust-head", "Rust (HEAD)"),
  1208. "Rust 1.43.0 paiza.io": new PaizaIORunner("rust", "Rust (1.43.0)"),
  1209. "Scala 2.13.3 paiza": new PaizaIORunner("scala", "Scala (2.13.3)"),
  1210. "Scheme Gauche 0.9.6 paiza.io": new PaizaIORunner("scheme", "Scheme (Gauche 0.9.6)"),
  1211. "Swift 5.2.5 paiza.io": new PaizaIORunner("swift", "Swift (5.2.5)"),
  1212. "Text local": new CustomRunner("Text", async (sourceCode, input) => {
  1213. return {
  1214. status: "OK",
  1215. exitCode: "0",
  1216. input,
  1217. output: sourceCode,
  1218. };
  1219. }),
  1220. "Basic Visual Basic .NET Core 4.0.1 paiza.io": new PaizaIORunner("vb", "Visual Basic (.NET Core 4.0.1)"),
  1221. "COBOL Free OpenCOBOL 2.2.0 paiza.io": new PaizaIORunner("cobol", "COBOL - Free (OpenCOBOL 2.2.0)"),
  1222. "COBOL Fixed OpenCOBOL 1.1.0 AtCoder": new AtCoderRunner("4060", "COBOL - Fixed (OpenCOBOL 1.1.0)"),
  1223. "COBOL Free OpenCOBOL 1.1.0 AtCoder": new AtCoderRunner("4061", "COBOL - Free (OpenCOBOL 1.1.0)"),
  1224. "C++ GCC 9.2.0 + ACL Wandbox": new WandboxCppRunner("gcc-9.2.0", "C++ (GCC 9.2.0) + ACL"),
  1225. };
  1226. pSite.then(site => {
  1227. if (site.name == "AtCoder") {
  1228. // AtCoderRunner がない場合は、追加する
  1229. for (const e of document.querySelectorAll("#select-lang option[value]")) {
  1230. const m = e.textContent.match(/([^ ]+) \(([^)]+)\)/);
  1231. if (m) {
  1232. const name = `${m[1]} ${m[2]} AtCoder`;
  1233. const languageId = e.value;
  1234. runners[name] = new AtCoderRunner(languageId, e.textContent);
  1235. }
  1236. }
  1237. }
  1238. });
  1239. console.info("AtCoder Easy Test: codeRunner OK");
  1240. var codeRunner = {
  1241. // 指定した環境でコードを実行する
  1242. run(languageId, sourceCode, input, expectedOutput, options = { trim: true, split: true }) {
  1243. // CodeRunner が存在しない言語ID
  1244. if (!(languageId in runners))
  1245. return Promise.reject("Language not supported");
  1246. // 最後に実行したコードを保存
  1247. if (sourceCode.length > 0)
  1248. codeSaver.save(sourceCode);
  1249. // 実行
  1250. return runners[languageId].test(sourceCode, input, expectedOutput, options);
  1251. },
  1252. // 環境の名前の一覧を取得する
  1253. async getEnvironment(languageId) {
  1254. const langs = similarLangs(languageId, Object.keys(runners));
  1255. if (langs.length == 0)
  1256. throw `Undefined language: ${languageId}`;
  1257. return langs.map(lang => [lang, runners[lang].label]);
  1258. },
  1259. };
  1260.  
  1261. var hBottomMenu = "<div id=\"bottom-menu-wrapper\" class=\"navbar navbar-default navbar-fixed-bottom\">\n <div class=\"container\">\n <div class=\"navbar-header\">\n <button id=\"bottom-menu-key\" type=\"button\" class=\"navbar-toggle collapsed glyphicon glyphicon-menu-down\" data-toggle=\"collapse\" data-target=\"#bottom-menu\"></button>\n </div>\n <div id=\"bottom-menu\" class=\"collapse navbar-collapse\">\n <ul id=\"bottom-menu-tabs\" class=\"nav nav-tabs\"></ul>\n <div id=\"bottom-menu-contents\" class=\"tab-content\"></div>\n </div>\n </div>\n</div>";
  1262.  
  1263. var hStyle$1 = "<style>\n#bottom-menu-wrapper {\n background: transparent;\n border: none;\n pointer-events: none;\n padding: 0;\n}\n\n#bottom-menu-wrapper>.container {\n position: absolute;\n bottom: 0;\n width: 100%;\n padding: 0;\n}\n\n#bottom-menu-wrapper>.container>.navbar-header {\n float: none;\n}\n\n#bottom-menu-key {\n display: block;\n float: none;\n margin: 0 auto;\n padding: 10px 3em;\n border-radius: 5px 5px 0 0;\n background: #000;\n opacity: 0.5;\n color: #FFF;\n cursor: pointer;\n pointer-events: auto;\n text-align: center;\n}\n\n@media screen and (max-width: 767px) {\n #bottom-menu-key {\n opacity: 0.25;\n }\n}\n\n#bottom-menu-key.collapsed:before {\n content: \"\\e260\";\n}\n\n#bottom-menu-tabs {\n padding: 3px 0 0 10px;\n cursor: n-resize;\n}\n\n#bottom-menu-tabs a {\n pointer-events: auto;\n}\n\n#bottom-menu {\n pointer-events: auto;\n background: rgba(0, 0, 0, 0.8);\n color: #fff;\n max-height: unset;\n}\n\n#bottom-menu.collapse:not(.in) {\n display: none !important;\n}\n\n#bottom-menu-tabs>li>a {\n background: rgba(150, 150, 150, 0.5);\n color: #000;\n border: solid 1px #ccc;\n filter: brightness(0.75);\n}\n\n#bottom-menu-tabs>li>a:hover {\n background: rgba(150, 150, 150, 0.5);\n border: solid 1px #ccc;\n color: #111;\n filter: brightness(0.9);\n}\n\n#bottom-menu-tabs>li.active>a {\n background: #eee;\n border: solid 1px #ccc;\n color: #333;\n filter: none;\n}\n\n.bottom-menu-btn-close {\n font-size: 8pt;\n vertical-align: baseline;\n padding: 0 0 0 6px;\n margin-right: -6px;\n}\n\n#bottom-menu-contents {\n padding: 5px 15px;\n max-height: 50vh;\n overflow-y: auto;\n}\n\n#bottom-menu-contents .panel {\n color: #333;\n}\n</style>";
  1264.  
  1265. async function init() {
  1266. const site = await pSite;
  1267. const style = html2element(hStyle$1);
  1268. const bottomMenu = html2element(hBottomMenu);
  1269. unsafeWindow.document.head.appendChild(style);
  1270. site.bottomMenuContainer.appendChild(bottomMenu);
  1271. const bottomMenuKey = bottomMenu.querySelector("#bottom-menu-key");
  1272. const bottomMenuTabs = bottomMenu.querySelector("#bottom-menu-tabs");
  1273. const bottomMenuContents = bottomMenu.querySelector("#bottom-menu-contents");
  1274. // メニューのリサイズ
  1275. {
  1276. let resizeStart = null;
  1277. const onStart = (event) => {
  1278. const target = event.target;
  1279. const pageY = event.pageY;
  1280. if (target.id != "bottom-menu-tabs")
  1281. return;
  1282. resizeStart = { y: pageY, height: bottomMenuContents.getBoundingClientRect().height };
  1283. };
  1284. const onMove = (event) => {
  1285. if (!resizeStart)
  1286. return;
  1287. event.preventDefault();
  1288. bottomMenuContents.style.height = `${resizeStart.height - (event.pageY - resizeStart.y)}px`;
  1289. };
  1290. const onEnd = () => {
  1291. resizeStart = null;
  1292. };
  1293. bottomMenuTabs.addEventListener("mousedown", onStart);
  1294. bottomMenuTabs.addEventListener("mousemove", onMove);
  1295. bottomMenuTabs.addEventListener("mouseup", onEnd);
  1296. bottomMenuTabs.addEventListener("mouseleave", onEnd);
  1297. }
  1298. let tabs = new Set();
  1299. let selectedTab = null;
  1300. /** 下メニューの操作 */
  1301. const menuController = {
  1302. /** タブを選択 */
  1303. selectTab(tabId) {
  1304. const tab = site.jQuery(`#bottom-menu-tab-${tabId}`);
  1305. if (tab && tab[0]) {
  1306. tab.tab("show"); // Bootstrap 3
  1307. selectedTab = tabId;
  1308. }
  1309. },
  1310. /** 下メニューにタブを追加する */
  1311. addTab(tabId, tabLabel, paneContent, options = {}) {
  1312. console.log(`AtCoder Easy Test: addTab: ${tabLabel} (${tabId})`, paneContent);
  1313. // タブを追加
  1314. const tab = document.createElement("a");
  1315. tab.textContent = tabLabel;
  1316. tab.id = `bottom-menu-tab-${tabId}`;
  1317. tab.href = "#";
  1318. tab.dataset.target = `#bottom-menu-pane-${tabId}`;
  1319. tab.dataset.toggle = "tab";
  1320. tab.addEventListener("click", event => {
  1321. event.preventDefault();
  1322. menuController.selectTab(tabId);
  1323. });
  1324. const tabLi = document.createElement("li");
  1325. tabLi.appendChild(tab);
  1326. bottomMenuTabs.appendChild(tabLi);
  1327. // 内容を追加
  1328. const pane = document.createElement("div");
  1329. pane.className = "tab-pane";
  1330. pane.id = `bottom-menu-pane-${tabId}`;
  1331. pane.appendChild(paneContent);
  1332. bottomMenuContents.appendChild(pane);
  1333. const controller = {
  1334. get id() {
  1335. return tabId;
  1336. },
  1337. close() {
  1338. bottomMenuTabs.removeChild(tabLi);
  1339. bottomMenuContents.removeChild(pane);
  1340. tabs.delete(tab);
  1341. if (selectedTab == tabId) {
  1342. selectedTab = null;
  1343. if (tabs.size > 0) {
  1344. menuController.selectTab(tabs.values().next().value.id);
  1345. }
  1346. }
  1347. },
  1348. show() {
  1349. menuController.show();
  1350. menuController.selectTab(tabId);
  1351. },
  1352. set color(color) {
  1353. tab.style.backgroundColor = color;
  1354. },
  1355. };
  1356. // 閉じるボタン
  1357. if (options.closeButton) {
  1358. const btn = document.createElement("a");
  1359. btn.className = "bottom-menu-btn-close btn btn-link glyphicon glyphicon-remove";
  1360. btn.addEventListener("click", () => {
  1361. controller.close();
  1362. });
  1363. tab.appendChild(btn);
  1364. }
  1365. // 選択されているタブがなければ選択
  1366. if (!selectedTab)
  1367. menuController.selectTab(tabId);
  1368. return controller;
  1369. },
  1370. /** 下メニューを表示する */
  1371. show() {
  1372. if (bottomMenuKey.classList.contains("collapsed"))
  1373. bottomMenuKey.click();
  1374. },
  1375. /** 下メニューの表示/非表示を切り替える */
  1376. toggle() {
  1377. bottomMenuKey.click();
  1378. },
  1379. };
  1380. console.info("AtCoder Easy Test: bottomMenu OK");
  1381. return menuController;
  1382. }
  1383.  
  1384. var hRowTemplate = "<div class=\"atcoder-easy-test-cases-row alert alert-dismissible\">\n <button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"close\">\n <span aria-hidden=\"true\">×</span>\n </button>\n <div class=\"progress\">\n <div class=\"progress-bar\" style=\"width: 0%;\">0 / 0</div>\n </div>\n <!--div class=\"label label-default label-warning\" style=\"margin: 3px; cursor: pointer;\">WA</div>\n <div class=\"label label-default label-warning\" style=\"margin: 3px; cursor: pointer;\">WA</div>\n <div class=\"label label-default label-warning\" style=\"margin: 3px; cursor: pointer;\">WA</div-->\n</div>";
  1385.  
  1386. class ResultRow {
  1387. _tabs;
  1388. _element;
  1389. _promise;
  1390. constructor(pairs) {
  1391. this._tabs = pairs.map(([_, tab]) => tab);
  1392. this._element = html2element(hRowTemplate);
  1393. const numCases = pairs.length;
  1394. let numFinished = 0;
  1395. let numAccepted = 0;
  1396. const progressBar = this._element.querySelector(".progress-bar");
  1397. progressBar.textContent = `${numFinished} / ${numCases}`;
  1398. this._promise = Promise.all(pairs.map(([pResult, tab]) => {
  1399. const button = html2element(`<div class="label label-default" style="margin: 3px; cursor: pointer;">WJ</div>`);
  1400. button.addEventListener("click", async () => {
  1401. (await tab).show();
  1402. });
  1403. this._element.appendChild(button);
  1404. return pResult.then(result => {
  1405. button.textContent = result.status;
  1406. if (result.status == "AC") {
  1407. button.classList.add("label-success");
  1408. }
  1409. else if (result.status != "OK") {
  1410. button.classList.add("label-warning");
  1411. }
  1412. numFinished++;
  1413. if (result.status == "AC")
  1414. numAccepted++;
  1415. progressBar.textContent = `${numFinished} / ${numCases}`;
  1416. progressBar.style.width = `${100 * numFinished / numCases}%`;
  1417. if (numFinished == numCases) {
  1418. if (numAccepted == numCases)
  1419. this._element.classList.add("alert-success");
  1420. else
  1421. this._element.classList.add("alert-warning");
  1422. }
  1423. }).catch(reason => {
  1424. button.textContent = "IE";
  1425. button.classList.add("label-danger");
  1426. console.error(reason);
  1427. });
  1428. }));
  1429. }
  1430. get element() {
  1431. return this._element;
  1432. }
  1433. onFinish(listener) {
  1434. this._promise.then(listener);
  1435. }
  1436. remove() {
  1437. for (const pTab of this._tabs)
  1438. pTab.then(tab => tab.close());
  1439. const parent = this._element.parentElement;
  1440. if (parent)
  1441. parent.removeChild(this._element);
  1442. }
  1443. }
  1444.  
  1445. var hResultList = "<div class=\"row\"></div>";
  1446.  
  1447. const eResultList = html2element(hResultList);
  1448. pSite.then(site => site.resultListContainer.appendChild(eResultList));
  1449. const resultList = {
  1450. addResult(pairs) {
  1451. const result = new ResultRow(pairs);
  1452. eResultList.insertBefore(result.element, eResultList.firstChild);
  1453. return result;
  1454. },
  1455. };
  1456.  
  1457. var hTabTemplate = "<div class=\"atcoder-easy-test-result container\">\n <div class=\"row\">\n <div class=\"atcoder-easy-test-result-col-input col-xs-12\" data-if-expected-output=\"col-sm-6 col-sm-push-6\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Standard Input\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-input form-control\" rows=\"3\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n <div class=\"atcoder-easy-test-result-col-expected-output col-xs-12 col-sm-6 hidden\" data-if-expected-output=\"!hidden col-sm-pull-6\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Expected Output\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-expected-output form-control\" rows=\"3\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n </div>\n <div class=\"row\"><div class=\"col-sm-6 col-sm-offset-3\">\n <div class=\"panel panel-default\">\n <table class=\"table table-condensed\">\n <tbody>\n <tr>\n <th class=\"text-center\">Exit Code</th>\n <th class=\"text-center\">Exec Time</th>\n <th class=\"text-center\">Memory</th>\n </tr>\n <tr>\n <td class=\"atcoder-easy-test-result-exit-code text-center\"></td>\n <td class=\"atcoder-easy-test-result-exec-time text-center\"></td>\n <td class=\"atcoder-easy-test-result-memory text-center\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n </div></div>\n <div class=\"row\">\n <div class=\"atcoder-easy-test-result-col-output col-xs-12\" data-if-error=\"col-md-6\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Standard Output\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-output form-control\" rows=\"5\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n <div class=\"atcoder-easy-test-result-col-error col-xs-12 col-md-6 hidden\" data-if-error=\"!hidden\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Standard Error\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-error form-control\" rows=\"5\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n </div>\n</div>";
  1458.  
  1459. function setClassFromData(element, name) {
  1460. const classes = element.dataset[name].split(/\s+/);
  1461. for (let className of classes) {
  1462. let flag = true;
  1463. if (className[0] == "!") {
  1464. className = className.slice(1);
  1465. flag = false;
  1466. }
  1467. element.classList.toggle(className, flag);
  1468. }
  1469. }
  1470. class ResultTabContent {
  1471. _title;
  1472. _uid;
  1473. _element;
  1474. _result;
  1475. constructor() {
  1476. this._uid = Date.now().toString(16);
  1477. this._result = null;
  1478. this._element = html2element(hTabTemplate);
  1479. this._element.id = `atcoder-easy-test-result-${this._uid}`;
  1480. }
  1481. set result(result) {
  1482. this._result = result;
  1483. if (result.status == "AC") {
  1484. this.outputStyle.backgroundColor = "#dff0d8";
  1485. }
  1486. else if (result.status != "OK") {
  1487. this.outputStyle.backgroundColor = "#fcf8e3";
  1488. }
  1489. this.input = result.input;
  1490. if ("expectedOutput" in result)
  1491. this.expectedOutput = result.expectedOutput;
  1492. this.exitCode = result.exitCode;
  1493. if ("execTime" in result)
  1494. this.execTime = `${result.execTime} ms`;
  1495. if ("memory" in result)
  1496. this.memory = `${result.memory} KB`;
  1497. if ("output" in result)
  1498. this.output = result.output;
  1499. if (result.error)
  1500. this.error = result.error;
  1501. }
  1502. get result() {
  1503. return this._result;
  1504. }
  1505. get uid() {
  1506. return this._uid;
  1507. }
  1508. get element() {
  1509. return this._element;
  1510. }
  1511. set title(title) {
  1512. this._title = title;
  1513. }
  1514. get title() {
  1515. return this._title;
  1516. }
  1517. set input(input) {
  1518. this._get("input").value = input;
  1519. }
  1520. get inputStyle() {
  1521. return this._get("input").style;
  1522. }
  1523. set expectedOutput(output) {
  1524. this._get("expected-output").value = output;
  1525. setClassFromData(this._get("col-input"), "ifExpectedOutput");
  1526. setClassFromData(this._get("col-expected-output"), "ifExpectedOutput");
  1527. }
  1528. get expectedOutputStyle() {
  1529. return this._get("expected-output").style;
  1530. }
  1531. set output(output) {
  1532. this._get("output").value = output;
  1533. }
  1534. get outputStyle() {
  1535. return this._get("output").style;
  1536. }
  1537. set error(error) {
  1538. this._get("error").value = error;
  1539. setClassFromData(this._get("col-output"), "ifError");
  1540. setClassFromData(this._get("col-error"), "ifError");
  1541. }
  1542. set exitCode(code) {
  1543. const element = this._get("exit-code");
  1544. element.textContent = code;
  1545. const isSuccess = code == "0";
  1546. element.classList.toggle("bg-success", isSuccess);
  1547. element.classList.toggle("bg-danger", !isSuccess);
  1548. }
  1549. set execTime(time) {
  1550. this._get("exec-time").textContent = time;
  1551. }
  1552. set memory(memory) {
  1553. this._get("memory").textContent = memory;
  1554. }
  1555. _get(name) {
  1556. return this._element.querySelector(`.atcoder-easy-test-result-${name}`);
  1557. }
  1558. }
  1559.  
  1560. var hRoot = "<form id=\"atcoder-easy-test-container\" class=\"form-horizontal\">\n <div class=\"row\">\n <div class=\"col-xs-12 col-lg-8\">\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\">Test Environment</label>\n <div class=\"col-sm-10\">\n <select class=\"form-control\" id=\"atcoder-easy-test-language\"></select>\n </div>\n </div>\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"atcoder-easy-test-input\">Standard Input</label>\n <div class=\"col-sm-10\">\n <textarea id=\"atcoder-easy-test-input\" name=\"input\" class=\"form-control\" rows=\"3\"></textarea>\n </div>\n </div>\n </div>\n <div class=\"col-xs-12 col-lg-4\">\n <details close>\n <summary>Expected Output</summary>\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"atcoder-easy-test-allowable-error-check\">Allowable Error</label>\n <div class=\"col-sm-10\">\n <div class=\"input-group\">\n <span class=\"input-group-addon\">\n <input id=\"atcoder-easy-test-allowable-error-check\" type=\"checkbox\" checked=\"checked\">\n </span>\n <input id=\"atcoder-easy-test-allowable-error\" type=\"text\" class=\"form-control\" value=\"1e-6\">\n </div>\n </div>\n </div>\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"atcoder-easy-test-output\">Expected Output</label>\n <div class=\"col-sm-10\">\n <textarea id=\"atcoder-easy-test-output\" name=\"output\" class=\"form-control\" rows=\"3\"></textarea>\n </div>\n </div>\n </details>\n </div>\n <div class=\"col-xs-12 col-md-6\">\n <div class=\"col-xs-11 col-xs-offset=1\">\n <div class=\"form-group\">\n <a id=\"atcoder-easy-test-run\" class=\"btn btn-primary\">Run</a>\n </div>\n </div>\n </div>\n <div class=\"col-xs-12 col-md-6\">\n <div class=\"col-xs-11 col-xs-offset=1\">\n <div class=\"form-group text-right\">\n <small>AtCoder Easy Test v<span id=\"atcoder-easy-test-version\"></span></small>\n <a id=\"atcoder-easy-test-setting\" class=\"btn btn-xs btn-default\">Setting</a>\n </div>\n </div>\n </div>\n </div>\n <style>\n #atcoder-easy-test-language {\n border: none;\n background: transparent;\n font: inherit;\n color: #fff;\n }\n #atcoder-easy-test-language option {\n border: none;\n color: #333;\n font: inherit;\n }\n </style>\n</form>";
  1561.  
  1562. var hStyle = "<style>\n.atcoder-easy-test-result textarea {\n font-family: monospace;\n font-weight: normal;\n}\n</style>";
  1563.  
  1564. var hRunButton = "<a class=\"btn btn-primary btn-sm\" style=\"vertical-align: top; margin-left: 0.5em\">Run</a>";
  1565.  
  1566. var hTestAndSubmit = "<a id=\"atcoder-easy-test-btn-test-and-submit\" class=\"btn btn-info btn\" style=\"margin-left: 1rem\" title=\"Ctrl+Enter\" data-toggle=\"tooltip\">Test &amp; Submit</a>";
  1567.  
  1568. var hTestAllSamples = "<a id=\"atcoder-easy-test-btn-test-all\" class=\"btn btn-default btn-sm\" style=\"margin-left: 1rem\" title=\"Alt+Enter\" data-toggle=\"tooltip\">Test All Samples</a>";
  1569.  
  1570. (async () => {
  1571. const site = await pSite;
  1572. const doc = unsafeWindow.document;
  1573. // init bottomMenu
  1574. const pBottomMenu = init();
  1575. pBottomMenu.then(bottomMenu => {
  1576. unsafeWindow.bottomMenu = bottomMenu;
  1577. });
  1578. await doneOrFail(pBottomMenu);
  1579. // external interfaces
  1580. unsafeWindow.codeRunner = codeRunner;
  1581. doc.head.appendChild(html2element(hStyle));
  1582. // interface
  1583. const atCoderEasyTest = {
  1584. config,
  1585. codeSaver,
  1586. enableButtons() {
  1587. events.trig("enable");
  1588. },
  1589. disableButtons() {
  1590. events.trig("disable");
  1591. },
  1592. runCount: 0,
  1593. runTest(title, language, sourceCode, input, output = null, options = { trim: true, split: true, }) {
  1594. this.disableButtons();
  1595. const content = new ResultTabContent();
  1596. const pTab = pBottomMenu.then(bottomMenu => bottomMenu.addTab("easy-test-result-" + content.uid, `#${++this.runCount} ${title}`, content.element, { active: true, closeButton: true }));
  1597. const pResult = codeRunner.run(language, sourceCode, input, output, options);
  1598. pResult.then(result => {
  1599. content.result = result;
  1600. if (result.status == "AC") {
  1601. pTab.then(tab => tab.color = "#dff0d8");
  1602. }
  1603. else if (result.status != "OK") {
  1604. pTab.then(tab => tab.color = "#fcf8e3");
  1605. }
  1606. }).finally(() => {
  1607. this.enableButtons();
  1608. });
  1609. return [pResult, pTab];
  1610. }
  1611. };
  1612. unsafeWindow.atCoderEasyTest = atCoderEasyTest;
  1613. // place "Easy Test" tab
  1614. {
  1615. // declare const hRoot: string;
  1616. const root = html2element(hRoot);
  1617. const E = (id) => root.querySelector(`#atcoder-easy-test-${id}`);
  1618. const eLanguage = E("language");
  1619. const eInput = E("input");
  1620. const eAllowableErrorCheck = E("allowable-error-check");
  1621. const eAllowableError = E("allowable-error");
  1622. const eOutput = E("output");
  1623. const eRun = E("run");
  1624. const eSetting = E("setting");
  1625. E("version").textContent = "2.7.0";
  1626. events.on("enable", () => {
  1627. eRun.classList.remove("disabled");
  1628. });
  1629. events.on("disable", () => {
  1630. eRun.classList.remove("enabled");
  1631. });
  1632. eSetting.addEventListener("click", () => {
  1633. settings.open();
  1634. });
  1635. // 言語選択関係
  1636. {
  1637. eLanguage.addEventListener("change", async () => {
  1638. const langSelection = config.get("langSelection", {});
  1639. langSelection[site.language.value] = eLanguage.value;
  1640. config.set("langSelection", langSelection);
  1641. });
  1642. async function setLanguage() {
  1643. const languageId = site.language.value;
  1644. while (eLanguage.firstChild)
  1645. eLanguage.removeChild(eLanguage.firstChild);
  1646. try {
  1647. const langs = await codeRunner.getEnvironment(languageId);
  1648. console.log(`language: ${langs[1]} (${langs[0]})`);
  1649. // add <option>
  1650. for (const [languageId, label] of langs) {
  1651. const option = document.createElement("option");
  1652. option.value = languageId;
  1653. option.textContent = label;
  1654. eLanguage.appendChild(option);
  1655. }
  1656. // load
  1657. const langSelection = config.get("langSelection", {});
  1658. if (languageId in langSelection) {
  1659. const prev = langSelection[languageId];
  1660. const [lang, _] = langs.find(([lang, label]) => lang == prev);
  1661. if (lang)
  1662. eLanguage.value = lang;
  1663. }
  1664. events.trig("enable");
  1665. }
  1666. catch (error) {
  1667. console.log(`language: ? (${languageId})`);
  1668. console.error(error);
  1669. const option = document.createElement("option");
  1670. option.className = "fg-danger";
  1671. option.textContent = error;
  1672. eLanguage.appendChild(option);
  1673. events.trig("disable");
  1674. }
  1675. }
  1676. site.language.addListener(() => setLanguage());
  1677. eAllowableError.disabled = !eAllowableErrorCheck.checked;
  1678. eAllowableErrorCheck.addEventListener("change", event => {
  1679. eAllowableError.disabled = !eAllowableErrorCheck.checked;
  1680. });
  1681. }
  1682. // テスト実行
  1683. function runTest(title, input, output = null) {
  1684. const options = { trim: true, split: true, };
  1685. if (eAllowableErrorCheck.checked) {
  1686. options.allowableError = parseFloat(eAllowableError.value);
  1687. }
  1688. return atCoderEasyTest.runTest(title, eLanguage.value, site.sourceCode, input, output, options);
  1689. }
  1690. function runAllCases(testcases) {
  1691. const pairs = testcases.map(testcase => runTest(testcase.title, testcase.input, testcase.output));
  1692. resultList.addResult(pairs);
  1693. return Promise.all(pairs.map(([pResult, _]) => pResult.then(result => {
  1694. if (result.status == "AC")
  1695. return Promise.resolve(result);
  1696. else
  1697. return Promise.reject(result);
  1698. })));
  1699. }
  1700. eRun.addEventListener("click", _ => {
  1701. const title = "Run";
  1702. const input = eInput.value;
  1703. const output = eOutput.value;
  1704. runTest(title, input, output || null);
  1705. });
  1706. await doneOrFail(pBottomMenu.then(bottomMenu => bottomMenu.addTab("easy-test", "Easy Test", root)));
  1707. // place "Run" button on each sample
  1708. for (const testCase of site.testCases) {
  1709. const eRunButton = html2element(hRunButton);
  1710. eRunButton.addEventListener("click", async () => {
  1711. const [pResult, pTab] = runTest(testCase.title, testCase.input, testCase.output);
  1712. await pResult;
  1713. (await pTab).show();
  1714. });
  1715. testCase.anchor.insertAdjacentElement("afterend", eRunButton);
  1716. events.on("disable", () => {
  1717. eRunButton.classList.add("disabled");
  1718. });
  1719. events.on("enable", () => {
  1720. eRunButton.classList.remove("disabled");
  1721. });
  1722. }
  1723. // place "Test & Submit" button
  1724. {
  1725. const button = html2element(hTestAndSubmit);
  1726. site.testButtonContainer.appendChild(button);
  1727. const testAndSubmit = async () => {
  1728. await runAllCases(site.testCases);
  1729. site.submit();
  1730. };
  1731. button.addEventListener("click", testAndSubmit);
  1732. events.on("testAndSubmit", testAndSubmit);
  1733. }
  1734. // place "Test All Samples" button
  1735. {
  1736. const button = html2element(hTestAllSamples);
  1737. site.testButtonContainer.appendChild(button);
  1738. const testAllSamples = () => runAllCases(site.testCases);
  1739. button.addEventListener("click", testAllSamples);
  1740. events.on("testAllSamples", testAllSamples);
  1741. }
  1742. }
  1743. // place "Restore Last Play" button
  1744. try {
  1745. const restoreButton = doc.createElement("a");
  1746. restoreButton.className = "btn btn-danger btn-sm";
  1747. restoreButton.textContent = "Restore Last Play";
  1748. restoreButton.addEventListener("click", async () => {
  1749. try {
  1750. const lastCode = await codeSaver.restore();
  1751. if (site.sourceCode.length == 0 || confirm("Your current code will be replaced. Are you sure?")) {
  1752. site.sourceCode = lastCode;
  1753. }
  1754. }
  1755. catch (reason) {
  1756. alert(reason);
  1757. }
  1758. });
  1759. site.sideButtonContainer.appendChild(restoreButton);
  1760. }
  1761. catch (e) {
  1762. console.error(e);
  1763. }
  1764. // キーボードショートカット
  1765. settings.registerFlag("useKeyboardShortcut", true, "Use Keyboard Shortcuts");
  1766. unsafeWindow.addEventListener("keydown", (event) => {
  1767. if (config.get("useKeyboardShortcut", true)) {
  1768. if (event.key == "Enter" && event.ctrlKey) {
  1769. events.trig("testAndSubmit");
  1770. }
  1771. else if (event.key == "Enter" && event.altKey) {
  1772. events.trig("testAllSamples");
  1773. }
  1774. else if (event.key == "Escape" && event.altKey) {
  1775. pBottomMenu.then(bottomMenu => bottomMenu.toggle());
  1776. }
  1777. }
  1778. });
  1779. })();
  1780. })();