├── .gitignore ├── README.md ├── cf.py └── conf.py.example /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /conf.py 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | The library `lxml` is needed. Please reference 3 | [here](http://lxml.de/installation.html) for more information. 4 | 5 | If you are an Debian/Ubuntu user, simply type: 6 | 7 | $ sudo apt-get install python-lxml 8 | 9 | # Usage 10 | Using [Codeforces Problem 198A problem 11 | ](http://codeforces.com/problemset/problem/198/A) as an example. 12 | 13 | ## Donwload Sample Tests 14 | The url of this problem is 15 | . Please notice the 16 | **contest_id** is **198** and the **problem_id** is **A**. 17 | 18 | $ cf.py -c 198 -p A # download this problem 19 | $ cf.py -c 198 # download all problems in this contest 20 | 21 | There is another url which 22 | indicated the same problem. You can see the contest_id and problem_id 23 | is same, so it wouldn't be a problem. 24 | 25 | ## Running the Tests 26 | Suppose your source code is named `A.{lang}`, which `{lang}` could be 27 | `cpp`, `c`, `java` or `py` for the current version. 28 | 29 | Then, simple run `cf.py A.{lang}`, you will get the result like this: 30 | 31 | $ cf.py A.java 32 | output: 33 | 2 34 | === Case #1: AC (85 ms) === 35 | 36 | output: 37 | 2 38 | answer: 39 | 3 40 | === Case #2: WA (83 ms) === 41 | 42 | press enter to continue or to leave. 43 | output: 44 | Exception in thread "main" java.lang.Exception 45 | at A.(A.java:12) 46 | at A.main(A.java:18) 47 | answer: 48 | 0 49 | === Case #3: RE (95 ms) === 50 | 51 | press enter to continue or to leave. 52 | 53 | ## Configurations 54 | The file `conf.py' contains the compile & execute commands of support 55 | languages, so you could add more commands to support more languages 56 | easily by yourself. 57 | 58 | The section [global] in `conf.py` contains some setting about the *test 59 | file*'s name. Since the *source code*'s name and the *test file*'s name 60 | must be exactly same, you could change these settings to follow your 61 | naming convension. For example: 62 | 63 | In the default setting: 64 | 65 | PATTERN = "upper({id})" 66 | REPLACE_SPACE = "_" 67 | EXTENSION = ".xml" 68 | 69 | the filename would be 'A.xml' 70 | 71 | Or you could added the *contest id* and *problem's name*: (also notice the 72 | `replace_space`) 73 | 74 | PATTERN = "{contest}-upper({id})-lower({name})" 75 | REPLACE_SPACE = "-" 76 | EXTENSION = ".xml" 77 | 78 | the filename would be 'A-about-bacteria.xml' 79 | 80 | # About 81 | This tool is only verifiid on Linux now, but I think it could be run on 82 | other platforms, although it maybe need a little modify. 83 | 84 | Please feel free to fork and any suggesions are welcome. 85 | -------------------------------------------------------------------------------- /cf.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | 4 | from optparse import * 5 | import os.path 6 | import re 7 | import subprocess 8 | import sys 9 | import time 10 | import urllib2 11 | 12 | from lxml import etree 13 | 14 | CODEFORCES_URL = 'http://codeforces.com' 15 | EPS = 1e-6 16 | 17 | 18 | class Executer(object): 19 | def __init__(self, env, id): 20 | self.env = env 21 | self.id = id 22 | 23 | def compile(self): 24 | if len(self.env['compile']) == 0: 25 | return 0 26 | return subprocess.call(self.env['compile'].format(self.id), shell=True) 27 | 28 | def execute(self): 29 | return subprocess.Popen( 30 | self.env['execute'].format(self.id), 31 | shell=True, 32 | stdin=subprocess.PIPE, 33 | stdout=subprocess.PIPE 34 | ) 35 | 36 | 37 | def add_options(): 38 | usage = '%prog [options] [source code]' 39 | parser = OptionParser(usage=usage) 40 | parser.add_option('-c', '--contest', dest='contest_id', 41 | help="Download the specific contest. \ 42 | If the PROBLEM_ID isn't specific, \ 43 | then download all problems in the contest.") 44 | parser.add_option('-p', '--problem', dest='problem_id', 45 | help='Download the specific problem. \ 46 | The CONTEST_ID is required.') 47 | return parser.parse_args() 48 | 49 | 50 | def install_proxy(): 51 | if hasattr(conf, 'HTTP_PROXY'): 52 | proxy = urllib2.ProxyHandler({'http': conf.HTTP_PROXY}) 53 | opener = urllib2.build_opener(proxy) 54 | urllib2.install_opener(opener) 55 | 56 | 57 | def download_contest(contest_id): 58 | contest_url = '/'.join((CODEFORCES_URL, 'contest', contest_id)) 59 | contest_page = urllib2.urlopen(contest_url) 60 | tree = etree.HTML(contest_page.read()) 61 | for i in tree.xpath( 62 | ".//table[contains(@class, 'problems')]" 63 | "//td[contains(@class, 'id')]/a"): 64 | download_problem(contest_id, i.text.strip()) 65 | 66 | 67 | def download_problem(contest_id, problem_id): 68 | node_to_string = lambda node: ''.join( 69 | [node.text] + map(etree.tostring, node.getchildren())) 70 | 71 | problem_url = '/'.join( 72 | (CODEFORCES_URL, 'contest', contest_id, 'problem', problem_id)) 73 | problem_page = urllib2.urlopen(problem_url) 74 | tree = etree.HTML(problem_page.read()) 75 | 76 | title = tree.xpath( 77 | './/div[contains(@class, "problem-statement")]' 78 | '/div/div[contains(@class, "title")]')[0].text 79 | name = title[3:] 80 | 81 | filename = conf.PATTERN.format( 82 | id=problem_id, name=name, contest=contest_id) 83 | filename = re.sub( 84 | r'upper\((.*?)\)', lambda x: x.group(1).upper(), filename) 85 | filename = re.sub( 86 | r'lower\((.*?)\)', lambda x: x.group(1).lower(), filename) 87 | filename = filename.replace(' ', conf.REPLACE_SPACE) 88 | filename += conf.EXTENSION 89 | 90 | with open(filename, 'w') as f: 91 | for (input_node, answer_node) in zip( 92 | tree.xpath('.//div[contains(@class, "input")]/pre'), 93 | tree.xpath('.//div[contains(@class, "output")]/pre')): 94 | f.write('\n') 95 | f.write(node_to_string(input_node).replace('
', '\n')) 96 | f.write('\n') 97 | f.write('\n') 98 | f.write('\n') 99 | f.write(node_to_string(answer_node).replace('
', '\n')) 100 | f.write('\n') 101 | f.write('
\n') 102 | 103 | print 'contest={0!r}, id={1!r}, problem={2!r} is downloaded.'.format( 104 | contest_id, problem_id, name) 105 | 106 | 107 | def is_integer(s): 108 | try: 109 | int(s) 110 | except ValueError: 111 | return False 112 | return True 113 | 114 | 115 | def is_number(s): 116 | try: 117 | float(s) 118 | except ValueError: 119 | return False 120 | return True 121 | 122 | 123 | def floating_equal(a, b): 124 | return abs(a-b) < EPS 125 | 126 | 127 | def check_result(answer_text, output_text): 128 | answer_tokens = answer_text.split() 129 | output_tokens = output_text.split() 130 | if len(answer_tokens) != len(output_tokens): 131 | return False 132 | for answer_token, output_token in zip(answer_tokens, output_tokens): 133 | if is_integer(answer_token) and is_integer(output_token): 134 | if int(answer_token) != int(output_token): 135 | return False 136 | elif is_number(answer_token) and is_number(output_token): 137 | if not floating_equal(float(answer_token), float(output_token)): 138 | return False 139 | else: 140 | if answer_token != output_token: 141 | return False 142 | return True 143 | 144 | 145 | def handle_test(executer, case, input_text, answer_text): 146 | print 'output:' 147 | start = time.time() 148 | proc = executer.execute() 149 | proc.stdin.write(input_text) 150 | output_text = '' 151 | for output_line in iter(proc.stdout.readline, ''): 152 | print output_line, 153 | output_text += output_line 154 | proc.wait() 155 | print 156 | end = time.time() 157 | 158 | if proc.returncode != 0: 159 | result = 'RE' 160 | elif answer_text == output_text: 161 | result = 'EXACTLY' 162 | elif check_result(answer_text, output_text): 163 | result = 'AC' 164 | else: 165 | result = 'WA' 166 | 167 | if result != 'EXACTLY': 168 | print 'answer:' 169 | print answer_text 170 | 171 | print '=== Case #{0}: {1} ({2} ms) ===\n'.format( 172 | case, result, int((end-start) * 1000)) 173 | if result != 'EXACTLY': 174 | raw_input('press enter to continue or to leave.') 175 | 176 | 177 | def main(): 178 | global options, conf 179 | (options, args) = add_options() 180 | 181 | try: 182 | import conf 183 | except ImportError, e: 184 | print 'conf.py does not exist.' 185 | print 'Maybe you should copy `conf.py.example` to `conf.py`.' 186 | sys.exit(1) 187 | 188 | if options.contest_id is not None: 189 | install_proxy() 190 | if options.problem_id is not None: 191 | download_problem(options.contest_id, options.problem_id) 192 | else: 193 | download_contest(options.contest_id) 194 | sys.exit(0) 195 | 196 | if len(args) < 1 or not os.path.exists(args[0]): 197 | print 'Source code not exist!' 198 | sys.exit(1) 199 | 200 | id, lang = os.path.splitext(args[0]) 201 | executer = Executer(conf.ENV[lang], id) 202 | 203 | ret = executer.compile() 204 | 205 | if ret != 0: 206 | print '>>> failed to Compile the source code!' 207 | sys.exit(1) 208 | 209 | with open('{0}{1}'.format(id, conf.EXTENSION)) as test_file: 210 | samples = etree.fromstring( 211 | '{0}'.format(test_file.read())) 212 | nodes = samples.getchildren() 213 | for case in xrange(len(nodes)/2): 214 | input_text = nodes[case*2].text[1:-1] 215 | answer_text = nodes[case*2+1].text[1:-1] 216 | handle_test(executer, case, input_text, answer_text) 217 | 218 | if __name__ == '__main__': 219 | main() 220 | -------------------------------------------------------------------------------- /conf.py.example: -------------------------------------------------------------------------------- 1 | PATTERN = "upper({id})" 2 | REPLACE_SPACE = "_" 3 | EXTENSION = ".xml" 4 | ENV = { 5 | ".c": { 6 | "compile": "gcc -static -fno-optimize-sibling-calls -fno-strict-aliasing -fno-asm -lm -s -O2 -m32 -o {0} {0}.c", 7 | "execute": "./{0}", 8 | }, 9 | ".cpp": { 10 | "compile": "g++ -static -fno-optimize-sibling-calls -fno-strict-aliasing -lm -s -x c++ -O2 -std=c++11 -D__USE_MINGW_ANSI_STDIO=0 -m32 -o {0} {0}.cpp", 11 | "execute": "./{0}", 12 | }, 13 | ".py": { 14 | "compile": "", 15 | "execute": "python {0}.py", 16 | }, 17 | ".java": { 18 | "compile": "javac -cp '.;*' {0}.java", 19 | "execute": "java -Djava.security.manager -Djava.security.policy=java.policy -Xmx512M -Xss64M -Duser.language=en -Duser.region=US -Duser.variant=US {0}", 20 | }, 21 | ".scala": { 22 | "compile": "fsc -cp '.;*' {0}.scala", 23 | "execute": "JAVA_OPTS='-Djava.security.policy=java.policy -Xmx512M -Xss64M -Duser.language=en -Duser.region=US -Duser.variant=US' scala {0}", 24 | }, 25 | ".rb": { 26 | "compile": "", 27 | "execute": "ruby {0}.rb", 28 | }, 29 | ".go": { 30 | "compile": "go build {0}.go", 31 | "execute": "./{0}", 32 | }, 33 | ".hs": { 34 | "compile": "ghc --make -O -o {0} {0}.hs", 35 | "execute": "./{0}", 36 | }, 37 | } 38 | --------------------------------------------------------------------------------