├── .gitignore ├── LICENSE ├── Main.java ├── README.md ├── main.cc ├── main.go ├── main.kt ├── main.rs └── parse.py /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | Past/ 3 | [0-9][0-9][0-9] 4 | .vscode/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jonathan Hao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Main.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.io.*; 3 | 4 | public class Main { 5 | private static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("DEBUG", "false")); 6 | 7 | private static void debug(Object... args) { 8 | if (DEBUG) { 9 | System.err.print("DEBUG: "); 10 | for (Object arg : args) { 11 | System.err.print(arg + " "); 12 | } 13 | System.err.println(); 14 | } 15 | } 16 | 17 | public static void main(String[] args) throws IOException { 18 | Scanner sc = new Scanner(System.in); 19 | // code goes here ... 20 | 21 | sc.close(); 22 | } 23 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Codeforces Parser v1.5.1 2 | ================= 3 | 4 | Summary 5 | ------- 6 | 7 | [Codeforces](http://codeforces.com/) is a website for _competitive programming_. It holds contests, so-called **Codeforces Rounds**, about every week. 8 | 9 | This is a python program that parses the sample tests from the contest problem pages. For each problem, it generates the sample input/output files and a shell script to run sample tests. 10 | 11 | You can also find this article here, [http://codeforces.com/blog/entry/10416](http://codeforces.com/blog/entry/10416) 12 | 13 | ### Installation 14 | 15 | * Arch Linux (AUR): https://aur.archlinux.org/packages/codeforces-parser-git/ (note that parse.py is renamed codeforces-parser) 16 | 17 | ### Example: 18 | `./parse.py contest_number (e.g. ./parse.py 513)` 19 | 20 | Where `512` is the contest number, not the round number! Check the URL of the contest on your browser, that is the number you are supposed to use. 21 | 22 | ### Effect: 23 | 24 | ##### What will happen, for example, if `./parse.py 512` is executed? 25 | 26 | 1. Directories `512/A`, `512/B`, `512/C`, `512/D` and so on are created depending on the contest number of problems. 27 | 2. For each problem, `main.cc` is copied and renamed to the problem letter to the corresponding directory. **You can put the path of your usual template in `parse.py:20`**. 28 | 3. Problem page is downloaded from Codeforces website, and parsed. Sample input/output files are generated, e.g. `input1`, `output1`, `input2`, `output2` and so on. You can create your own test cases after that, just keep the same naming format as others test cases. 29 | 4. A script `test.sh` is generated. You can use it to compile and run the sample tests after you finish coding. Just run `./test.sh` in the problem directory. 30 | 31 | ##### What will happen if `./test.sh` is executed? 32 | 33 | 1. Compilation: `g++ -g -std=c++0x -Wall $DBG main.cc`. **You can change the compile options in `parse.py:21`**. Variable $DBG is set to -DDEBUG if you start "./test.sh -d", otherwise it is empty. This allows for compilation with and without debug macros. 34 | 2. Run each sample tests on your program (`a.out`), and check the output by `diff`. If it's correct, print **Accepted**, or else print the sample test that went wrong. 35 | 3. Please note that for problems with multiple correct answers it might say that your output is incorrect. 36 | 37 | ### Collaborators and Versions: 38 | 39 | ##### List of CodeForces Collaborators: 40 | + [johnathan79717](http://codeforces.com/profile/johnathan79717) 41 | + [brunoja](http://codeforces.com/profile/brunoja) 42 | + [Matthias Kauer (mini addition)] 43 | If you have any suggestions and/or bugs drop a message! 44 | 45 | ##### Versions Changes: 46 | + **1.5.1:** 47 | Minor bug fixes related to Python 2 vs Python 3. 48 | Makes the template file to use the proper language extension. 49 | + **1.5:** 50 | Added debug flag (-d) to enable DEBUG macro (read above for details). 51 | Fixed problems parsing for problem names that are not called A, B, etc. Such as A1, A2.. 52 | + **1.4.1:** 53 | Minor fixes, such as typos, bugs and special characters handling. 54 | + **1.4:** 55 | Changed how the parser gets the problems. During the competitions the page is slightly different. 56 | Fixed some invalid character on input and output causing the script to crash. 57 | Forcing a new line on the input/output if there is none. 58 | Fixed some line number information in this README file. 59 | + **1.3:** 60 | Some minor fixes and code organizing. Also fixed some typos. 61 | Removed the _sample_ from default input and output files. 62 | + **1.2:** 63 | Fixed some typos and constants. 64 | Fetching contest info, printing contest name and problem names. 65 | The contest may now have more or less than 5 problems, it will auto detect. 66 | The script will now generate the template with the problem letter. 67 | Fixed test cases fetching. The script was stopping for escaped html characters, such as '<'. 68 | Fixed script to work with python 3. 69 | + **1.1:** 70 | Cleaner generation of the test script, now it auto detects the test cases, making you able to create your own cases. 71 | Echo color output, for accepted we get a green message, otherwise it is red. 72 | Added the time measurement for running the test cases. 73 | For the runtime error case, it now outputs the input case. 74 | Created some constants, such as compile options. These user modifiable constants should be easily spotted at the first lines of the python script. 75 | + **1.0:** Initial Version. 76 | 77 | ##### Todo, Bugs & Troubleshootings: 78 | 79 | + In OS X it is necessary to install the `gnu-time` to measure time. 80 | + This parser currently works only on Unix OSes. If you want to add Windows/Other support let us know. 81 | -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | #ifdef DEBUG 5 | #define debug(args...) \ 6 | { \ 7 | dbg, args; \ 8 | cerr << endl; \ 9 | } 10 | #else 11 | #define debug(args...) // Just strip off all debug tokens 12 | #endif 13 | 14 | struct debugger { 15 | template debugger &operator,(const T &v) { 16 | cerr << v << " "; 17 | return *this; 18 | } 19 | } dbg; 20 | 21 | int main(void) { 22 | // ... 23 | return 0; 24 | } 25 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | var DEBUG = "N" 10 | 11 | func debug(args ...interface{}) { 12 | if DEBUG != "N" { 13 | fmt.Fprint(os.Stderr, "DEBUG: ") 14 | fmt.Fprintln(os.Stderr, args...) 15 | } 16 | } 17 | 18 | func main() { 19 | scanner := bufio.NewScanner(os.Stdin) 20 | scanner.Scan() 21 | // code goes here 22 | 23 | } 24 | -------------------------------------------------------------------------------- /main.kt: -------------------------------------------------------------------------------- 1 | var _debug = false; 2 | fun debug(vararg vals: T): Unit { 3 | if (!_debug) { return } 4 | for (v in vals) { System.err.print(v); System.err.print(" ") } 5 | System.err.println() 6 | } 7 | 8 | fun main(args: Array) { 9 | if (args.size > 0 && args[0] == "-d") { 10 | _debug = true; 11 | } 12 | 13 | val n = readLine()!!.toInt() 14 | val (a, b) = readLine()!!.split(' ').map(String::toLong) 15 | } 16 | -------------------------------------------------------------------------------- /main.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, BufRead, BufReader, Write}; 2 | 3 | #[allow(unused_macros)] 4 | macro_rules! debug { 5 | ($($arg::tt)*) => { 6 | #[cfg(debug_assertions)] 7 | eprintln!($($arg)*); 8 | }; 9 | } 10 | 11 | fn main() { 12 | let stdin = io::stdin(); 13 | let mut lines = stdin.lock().lines(); 14 | // code goes here ... 15 | } 16 | 17 | #[allow(dead_code)] 18 | fn read_line() -> String { 19 | let stdin = io::stdin(); 20 | let mut line = String::new(); 21 | stdin.read_line(&mut line).unwrap(); 22 | line.trim().to_string() 23 | } 24 | 25 | #[allow(dead_code)] 26 | fn read_nums() -> Vec 27 | where 28 | T::Err: std::fmt::Debug, 29 | { 30 | read_line() 31 | .split_whitespace() 32 | .map(|s| s.parse().unwrap()) 33 | .collect() 34 | } 35 | -------------------------------------------------------------------------------- /parse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from urllib.request import urlopen, Request 3 | from html.parser import HTMLParser 4 | from sys import argv 5 | from subprocess import call 6 | from functools import partial, wraps 7 | import os 8 | from pathlib import Path 9 | 10 | import re 11 | import html 12 | import time 13 | import argparse 14 | import platform 15 | import logging 16 | 17 | ########################### 18 | # User modifiable constants 19 | ########################### 20 | language_params = { 21 | "c++17": { 22 | "TEMPLATE": "main.cc", 23 | "DEBUG_FLAGS": "-DDEBUG", 24 | "COMPILE_CMD": "g++ -g -std=c++17 -Wall $DBG", 25 | "RUN_CMD": "./a.out", 26 | }, 27 | "c++14": { 28 | "TEMPLATE": "main.cc", 29 | "DEBUG_FLAGS": "-DDEBUG", 30 | "COMPILE_CMD": "g++ -g -std=c++14 -Wall $DBG", 31 | "RUN_CMD": "./a.out", 32 | }, 33 | "go": { 34 | "TEMPLATE": "main.go", 35 | "COMPILE_CMD": "go build $DBG -o a.out", 36 | "DEBUG_FLAGS": '''"-ldflags '-X=main.DEBUG=Y'"''', 37 | "RUN_CMD": "./a.out", 38 | }, 39 | "kotlin": { 40 | "TEMPLATE": "main.kt", 41 | "COMPILE_CMD": "kotlinc -include-runtime -d out.jar", 42 | "DEBUG_FLAGS": "-d", 43 | "RUN_CMD": "java -jar out.jar $DBG", 44 | }, 45 | "java": { 46 | "TEMPLATE": "Main.java", 47 | "DEBUG_FLAGS": "-DDEBUG=true", 48 | "COMPILE_CMD": "javac $DBG", 49 | "RUN_CMD": "java -DDEBUG=$DBG Main", 50 | }, 51 | "rust": { 52 | "TEMPLATE": "main.rs", 53 | "DEBUG_FLAGS": "--cfg debug_assertions", 54 | "COMPILE_CMD": "rustc $DBG -o a.out", 55 | "RUN_CMD": "./a.out", 56 | }, 57 | } 58 | 59 | headers = { 60 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' 61 | } 62 | 63 | SAMPLE_INPUT = "input" 64 | SAMPLE_OUTPUT = "output" 65 | MY_OUTPUT = "my_output" 66 | 67 | # Do not modify these! 68 | VERSION = ( 69 | "CodeForces Parser v1.5.1: https://github.com/johnathan79717/codeforces-parser" 70 | ) 71 | RED_F = "\033[31m" 72 | GREEN_F = "\033[32m" 73 | BOLD = "\033[1m" 74 | NORM = "\033[0m" 75 | if platform.system() == "Darwin": 76 | TIME_CMD = r'`which gtime` -o time.out -f "(%es)"' 77 | else: 78 | TIME_CMD = r'`which time` -o time.out -f "(%es)"' 79 | TIME_AP = r"`cat time.out`" 80 | 81 | 82 | # Problems parser. 83 | class CodeforcesProblemParser(HTMLParser): 84 | 85 | def __init__(self, folder): 86 | HTMLParser.__init__(self) 87 | self.folder = folder 88 | self.num_tests = 0 89 | self.testcase = None 90 | self.start_copy = False 91 | self.end_line = False 92 | 93 | def handle_starttag(self, tag, attrs): 94 | if tag == "div": 95 | attr_dict = dict(attrs) 96 | if attr_dict.get("class") == "input": 97 | self.num_tests += 1 98 | if self.testcase: 99 | self.testcase.close() 100 | self.testcase = open( 101 | f"{self.folder}/{SAMPLE_INPUT}{self.num_tests}", "wb" 102 | ) 103 | elif attr_dict.get("class") == "output": 104 | if self.testcase: 105 | self.testcase.close() 106 | self.testcase = open( 107 | f"{self.folder}/{SAMPLE_OUTPUT}{self.num_tests}", "wb" 108 | ) 109 | elif tag == "pre": 110 | if self.testcase is not None: 111 | self.start_copy = True 112 | 113 | def handle_endtag(self, tag): 114 | if tag == "br": 115 | if self.start_copy: 116 | self.testcase.write("\n".encode("utf-8")) 117 | self.end_line = True 118 | if tag == "pre": 119 | if self.start_copy: 120 | if not self.end_line: 121 | self.testcase.write("\n".encode("utf-8")) 122 | self.testcase.close() 123 | self.testcase = None 124 | self.start_copy = False 125 | 126 | def handle_entityref(self, name): 127 | if self.start_copy: 128 | self.testcase.write(html.unescape(("&%s;" % name)).encode("utf-8")) 129 | 130 | def handle_data(self, data): 131 | if self.start_copy: 132 | self.testcase.write(data.strip("\n").encode("utf-8")) 133 | self.end_line = False 134 | 135 | def __del__(self): 136 | if self.testcase: 137 | self.testcase.close() 138 | 139 | 140 | # Contest parser. 141 | class CodeforcesContestParser(HTMLParser): 142 | 143 | def __init__(self, contest): 144 | HTMLParser.__init__(self) 145 | self.contest = contest 146 | self.start_contest = False 147 | self.start_problem = False 148 | self.name = "" 149 | self.problem_name = "" 150 | self.problems = [] 151 | self.problem_names = [] 152 | 153 | def handle_starttag(self, tag, attrs): 154 | attr_dict = dict(attrs) 155 | 156 | # More robust contest name detection 157 | if (tag == "a" and 158 | self.name == "" and 159 | attr_dict.get("href") == f"/contest/{self.contest}"): 160 | self.start_contest = True 161 | 162 | elif tag == "option": 163 | value = attr_dict.get("value", "") 164 | # Look for problem identifiers (A, B, C, etc.) 165 | if re.match(r'^[A-Z][0-9]?$', value): 166 | self.problems.append(value) 167 | self.start_problem = True 168 | 169 | def handle_endtag(self, tag): 170 | if tag == "a" and self.start_contest: 171 | self.start_contest = False 172 | elif self.start_problem: 173 | self.problem_names.append(self.problem_name) 174 | self.problem_name = "" 175 | self.start_problem = False 176 | 177 | def handle_data(self, data): 178 | if self.start_contest: 179 | self.name = data 180 | elif self.start_problem: 181 | self.problem_name += data 182 | 183 | 184 | # Parses each problem page. 185 | def parse_problem(folder, contest, problem): 186 | url = f"https://codeforces.com/contest/{contest}/problem/{problem}" 187 | req = Request(url, headers=headers) 188 | try: 189 | with urlopen(req, timeout=10) as response: 190 | html = response.read() 191 | parser = CodeforcesProblemParser(folder) 192 | parser.feed(html.decode("utf-8")) 193 | logger.info(f"Successfully parsed problem {problem}") 194 | return parser.num_tests 195 | except Exception as e: 196 | logger.error(f"Error parsing problem {problem}: {e}") 197 | return 0 198 | 199 | 200 | # Parses the contest page. 201 | def parse_contest(contest): 202 | url = f"https://codeforces.com/contest/{contest}" 203 | req = Request(url, headers=headers) 204 | try: 205 | html = urlopen(req).read() 206 | parser = CodeforcesContestParser(contest) 207 | parser.feed(html.decode("utf-8")) 208 | return parser 209 | except Exception as e: 210 | print(f"Error parsing contest {contest}: {e}") 211 | return None 212 | 213 | 214 | # Generates the test script. 215 | def generate_test_script(folder, language, num_tests, problem): 216 | param = language_params[language] 217 | 218 | with open(folder + "test.sh", "w") as test: 219 | test.write( 220 | ( 221 | "#!/bin/bash\n" 222 | 'DBG=""\n' 223 | 'while getopts ":d" opt; do\n' 224 | " case $opt in\n" 225 | " d)\n" 226 | ' echo "-d was selected; compiling in DEBUG mode!" >&2\n' 227 | " DBG=" + param["DEBUG_FLAGS"] + "\n" 228 | " ;;\n" 229 | r" \?)" + "\n" 230 | ' echo "Invalid option: -$OPTARG" >&2\n' 231 | " ;;\n" 232 | " esac\n" 233 | "done\n" 234 | "\n" 235 | "if ! " + param["COMPILE_CMD"] + " {0}.{1}; then\n" 236 | " exit\n" 237 | "fi\n" 238 | "INPUT_NAME=" + SAMPLE_INPUT + "\n" 239 | "OUTPUT_NAME=" + SAMPLE_OUTPUT + "\n" 240 | "MY_NAME=" + MY_OUTPUT + "\n" 241 | "rm -R $MY_NAME* &>/dev/null\n" 242 | ).format(problem, param["TEMPLATE"].split(".")[1]) 243 | ) 244 | 245 | # Fixed: Use INPUT_NAME instead of the literal string 246 | script_content = ( 247 | "for test_file in $INPUT_NAME*\n" 248 | "do\n" 249 | " i=${{#INPUT_NAME}}\n" # Fixed: Use INPUT_NAME variable 250 | " test_case=${{test_file:$i}}\n" 251 | " if ! {1} {3} < $INPUT_NAME$test_case > $MY_NAME$test_case; then\n" 252 | ' echo "{4}{5}Sample test #$test_case: Runtime Error{6} {2}"\n' 253 | " echo ========================================\n" 254 | ' echo "Sample Input #$test_case"\n' 255 | " cat $INPUT_NAME$test_case\n" 256 | " else\n" 257 | " if diff --brief --ignore-space-change --ignore-blank-lines $MY_NAME$test_case $OUTPUT_NAME$test_case; then\n" 258 | ' echo "{4}{7}Sample test #$test_case: Accepted{6} {2}"\n' 259 | " else\n" 260 | ' echo "{4}{5}Sample test #$test_case: Wrong Answer{6} {2}"\n' 261 | " echo ========================================\n" 262 | ' echo "Sample Input #$test_case"\n' 263 | " cat $INPUT_NAME$test_case\n" 264 | " echo ========================================\n" 265 | ' echo "Sample Output #$test_case"\n' 266 | " cat $OUTPUT_NAME$test_case\n" 267 | " echo ========================================\n" 268 | ' echo "My Output #$test_case"\n' 269 | " cat $MY_NAME$test_case\n" 270 | " echo ========================================\n" 271 | " fi\n" 272 | " fi\n" 273 | "done\n" 274 | ).format( 275 | SAMPLE_INPUT, # {0} - Not used in this part 276 | TIME_CMD, # {1} 277 | TIME_AP, # {2} 278 | param["RUN_CMD"], # {3} 279 | BOLD, # {4} 280 | RED_F, # {5} 281 | NORM, # {6} 282 | GREEN_F, # {7} 283 | ) 284 | 285 | test.write(script_content) 286 | 287 | call(["chmod", "+x", folder + "test.sh"]) 288 | 289 | 290 | def main(): 291 | print(VERSION) 292 | parser = argparse.ArgumentParser(description="Parse Codeforces contest problems") 293 | parser.add_argument( 294 | "--language", 295 | "-l", 296 | default="c++17", 297 | choices=list(language_params.keys()), 298 | help="The programming language you want to use" 299 | ) 300 | parser.add_argument("contest", help="Contest number", type=int) 301 | args = parser.parse_args() 302 | 303 | contest = args.contest 304 | language = args.language 305 | 306 | # Validate contest number 307 | if contest <= 0: 308 | print("Contest number must be positive") 309 | return 310 | 311 | # Find contest and problems. 312 | print(f"Parsing contest {contest} for language {language}, please wait...") 313 | content = parse_contest(contest) 314 | if content is None: 315 | print("Failed to parse contest. Check if the contest number is correct.") 316 | return 317 | 318 | if not content.problems: 319 | print("No problems found in this contest.") 320 | return 321 | 322 | print(f"{BOLD}{GREEN_F}*** Round name: {content.name} ***{NORM}") 323 | print(f"Found {len(content.problems)} problems!") 324 | 325 | # Find problems and test cases. 326 | TEMPLATE = language_params[language]["TEMPLATE"] 327 | for index, problem in enumerate(content.problems): 328 | problem_name = content.problem_names[index] if index < len(content.problem_names) else "Unknown" 329 | print(f"Downloading Problem {problem}: {problem_name}...") 330 | 331 | folder = Path(f"{contest}-{language}") / problem 332 | folder.mkdir(parents=True, exist_ok=True) 333 | 334 | template_src = Path(TEMPLATE) 335 | template_dst = folder / f"{problem}.{TEMPLATE.split('.')[1]}" 336 | 337 | if template_src.exists() and not template_dst.exists(): 338 | import shutil 339 | shutil.copy2(template_src, template_dst) 340 | 341 | num_tests = parse_problem(str(folder) + "/", contest, problem) 342 | print(f"{num_tests} sample test(s) found.") 343 | 344 | if num_tests > 0: 345 | generate_test_script(str(folder) + "/", language, num_tests, problem) 346 | print("=" * 40) 347 | time.sleep(1) 348 | 349 | print("Use ./test.sh to run sample tests in each directory.") 350 | 351 | 352 | if __name__ == "__main__": 353 | logging.basicConfig( 354 | level=logging.INFO, 355 | format='%(asctime)s - %(levelname)s - %(message)s' 356 | ) 357 | logger = logging.getLogger(__name__) 358 | main() 359 | --------------------------------------------------------------------------------