├── acedit
├── __init__.py
├── install_entry.py
├── main.py
└── util.py
├── MANIFEST.in
├── setup.cfg
├── images
├── demo.gif
└── logo.png
├── .gitignore
├── requirements.txt
├── LICENSE
├── setup.py
├── README.rst
└── README.md
/acedit/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.txt
2 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | description-file = README.md
--------------------------------------------------------------------------------
/images/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderick14/ACedIt/HEAD/images/demo.gif
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/coderick14/ACedIt/HEAD/images/logo.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | dist/
3 | build/
4 | venv/
5 | *.egg-info
6 | *.egg
7 | constants.json
8 | .python-version
9 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | beautifulsoup4==4.6.0
2 | certifi==2019.11.28
3 | chardet==3.0.4
4 | idna==2.7
5 | requests==2.20.0
6 | terminaltables==3.1.0
7 | urllib3==1.24.3
8 |
--------------------------------------------------------------------------------
/acedit/install_entry.py:
--------------------------------------------------------------------------------
1 | import json
2 | import os
3 | from setuptools.command.install import install
4 |
5 |
6 | class InstallEntry(install):
7 |
8 | def run(self):
9 |
10 | default_site = 'codeforces'
11 | cache_dir = os.path.join(os.path.expanduser('~'), '.cache', 'ACedIt')
12 |
13 | from acedit.main import supported_sites
14 |
15 | for site in supported_sites:
16 | # create cache directory structure
17 | if not os.path.isdir(os.path.join(cache_dir, site)):
18 | os.makedirs(os.path.join(cache_dir, site))
19 |
20 | data = {'default_site': default_site.strip(
21 | ), 'default_contest': None, 'cachedir': cache_dir}
22 | with open(os.path.join(cache_dir, 'constants.json'), 'w') as f:
23 | f.write(json.dumps(data, indent=2))
24 | install.run(self)
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Deep Bhattacharyya
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 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | try:
2 | from setuptools import setup
3 | except ImportError:
4 | from distutils.core import setup
5 |
6 | import sys
7 |
8 | from acedit.install_entry import InstallEntry
9 |
10 | with open('requirements.txt', 'r') as f:
11 | requirements = [line.strip() for line in f.readlines()]
12 |
13 | with open('README.rst', 'rb') as f:
14 | long_description = f.read().decode('utf-8')
15 |
16 | extra = {}
17 | if sys.version_info >= (3,):
18 | extra['use_2to3'] = True
19 |
20 | setup(
21 | name='ACedIt',
22 |
23 | packages=['acedit'],
24 |
25 | version='1.2.1',
26 |
27 | description='Download and test against sample test cases from any competitive programming website',
28 |
29 | long_description=long_description,
30 |
31 | author='Deep Bhattacharyya',
32 |
33 | author_email='bhattacharyya.rick14@gmail.com',
34 |
35 | python_requires='>=3.5',
36 |
37 | install_requires=requirements,
38 |
39 | entry_points={
40 | 'console_scripts': ['acedit=acedit.main:main']
41 | },
42 |
43 | zip_safe=False,
44 |
45 | url='https://github.com/coderick14/ACedIt',
46 |
47 | keywords=['sample test cases', 'downloader', 'competitive programming'],
48 |
49 | cmdclass={
50 | 'install': InstallEntry,
51 | 'develop': InstallEntry,
52 | },
53 |
54 | classifiers=[
55 | 'Operating System :: POSIX :: Linux',
56 | 'Operating System :: MacOS',
57 | 'License :: OSI Approved :: MIT License',
58 | 'Programming Language :: Python :: 3.5',
59 | 'Topic :: Education',
60 | ],
61 |
62 | license='MIT License'
63 | )
64 |
--------------------------------------------------------------------------------
/acedit/main.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import acedit.util as util
3 |
4 |
5 | supported_sites = ['codeforces', 'codechef', 'hackerrank', 'spoj', 'atcoder']
6 |
7 |
8 | def validate_args(args):
9 | """
10 | Method to check valid combination of flags
11 | """
12 |
13 | if args['default_site'] is not None or args['default_contest'] is not None:
14 | return
15 |
16 | if args['clear_cache']:
17 | return
18 |
19 | if not args['site'] == 'spoj' and args['contest'] is None:
20 | print('Please specify contest code or set a default contest.')
21 | sys.exit(0)
22 |
23 | if args['source']:
24 | return
25 |
26 | if args['site'] == 'spoj' and args['problem'] is None:
27 | print('Please specify a problem code for Spoj.')
28 | sys.exit(0)
29 |
30 |
31 | def main():
32 | args = util.Utilities.parse_flags(supported_sites)
33 | validate_args(args)
34 |
35 | try:
36 | if args['default_site']:
37 | # set default site
38 | util.Utilities.set_constants('default_site', args['default_site'])
39 |
40 | elif args['default_contest']:
41 | # set default contest
42 | util.Utilities.set_constants('default_contest', args['default_contest'])
43 |
44 | elif args['clear_cache']:
45 | # clear cached test cases
46 | util.Utilities.clear_cache(args['site'])
47 |
48 | elif args['source']:
49 | # run code
50 | util.Utilities.run_solution(args)
51 |
52 | elif args['problem'] is not None:
53 | # fetch single problem
54 | util.Utilities.download_problem_testcases(args)
55 |
56 | elif args['contest']:
57 | # fetch all problems for the contest
58 | util.Utilities.download_contest_testcases(args)
59 |
60 | else:
61 | print('Invalid combination of flags.')
62 |
63 | except KeyboardInterrupt:
64 | # Clean up files here
65 | util.Utilities.handle_kbd_interrupt(
66 | args['site'], args['contest'], args['problem'])
67 |
68 |
69 | if __name__ == '__main__':
70 | main()
71 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | A command line tool to run your code against sample test cases. Without leaving the terminal :)
2 |
3 | Supported sites
4 | ^^^^^^^^^^^^^^^
5 |
6 | - Codeforces
7 | - Codechef
8 | - Spoj
9 | - Hackerrank
10 | - Atcoder
11 |
12 | Supported languages
13 | ^^^^^^^^^^^^^^^^^^^
14 |
15 | - C
16 | - C++
17 | - Python
18 | - Java
19 | - Ruby
20 | - Haskell
21 |
22 | Installation
23 | ^^^^^^^^^^^^
24 |
25 | Build from source
26 | '''''''''''''''''
27 |
28 | - ``git clone https://github.com/coderick14/ACedIt``
29 | - ``cd ACedIt``
30 | - ``python setup.py install``
31 |
32 | As a Python package
33 | '''''''''''''''''''
34 |
35 | ::
36 |
37 | pip install --user ACedIt
38 |
39 | Usage
40 | ^^^^^
41 |
42 | ::
43 |
44 | usage: acedit [-h] [-s {codeforces,codechef,hackerrank,spoj}] [-c CONTEST]
45 | [-p PROBLEM] [-f] [--run SOURCE_FILE]
46 | [--set-default-site {codeforces,codechef,hackerrank,spoj}]
47 | [--set-default-contest DEFAULT_CONTEST]
48 |
49 | optional arguments:
50 | -h, --help show this help message and exit
51 | -s {codeforces,codechef,hackerrank,spoj}, --site {codeforces,codechef,hackerrank,spoj}
52 | The competitive programming platform, e.g. codeforces,
53 | codechef etc
54 | -c CONTEST, --contest CONTEST
55 | The name of the contest, e.g. JUNE17, LTIME49, COOK83
56 | etc
57 | -p PROBLEM, --problem PROBLEM
58 | The problem code, e.g. OAK, PRMQ etc
59 | -f, --force Force download the test cases, even if they are cached
60 | --run SOURCE_FILE Name of source file to be run
61 | --set-default-site {codeforces,codechef,hackerrank,spoj}
62 | Name of default site to be used when -s flag is not
63 | specified
64 | --set-default-contest DEFAULT_CONTEST
65 | Name of default contest to be used when -c flag is not
66 | specified
67 | --clear-cache Clear cached test cases for a given site. Takes
68 | default site if -s flag is omitted
69 |
70 | During installation, the default site is set to ``codeforces``. You
71 | can change it anytime using the above mentioned flags.
72 |
73 |
74 | Examples
75 | ^^^^^^^^
76 |
77 | - Fetch test cases for a single problem
78 |
79 | ::
80 |
81 | acedit -s codechef -c AUG17 -p CHEFFA
82 |
83 | - Fetch test cases for all problems in a contest
84 |
85 | ::
86 |
87 | acedit -s codechef -c AUG17
88 |
89 | - Force download test cases, even when they are cached
90 |
91 | ::
92 |
93 | acedit -s codeforces -c 86 -p D -f
94 |
95 | - Test your code (when default-site and default-contest is set and filename is same as problem_code)
96 |
97 | ::
98 |
99 | acedit --run D.cpp
100 |
101 | ::
102 |
103 | acedit --run CHEFFA.py
104 |
105 | **Since your filename is same as problem code, there's no need for the -p flag.**
106 |
107 | - Test your code (specifying contest and problem codes explicitly)
108 |
109 | ::
110 |
111 | acedit --run solve.cpp -c 835 -p D
112 |
113 | ::
114 |
115 | acedit --run test.py -s codechef -c AUG17 -p CHEFFA
116 |
117 | Note :
118 | ''''''
119 |
120 | - The working directory structure mentioned in the previous versions is no longer required and supported.
121 |
122 | - There might be some issues with Spoj, as they have widely varying DOM trees for different problems. Feel free to contribute on this. Or anything else that you can come up with :)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/coderick14/ACedIt/issues)
2 | [](https://github.com/coderick14/ACedIt)
3 | [](https://opensource.org/licenses/MIT)
4 |
5 | 
6 |
7 | A command line tool to run your code against sample test cases. Without leaving the terminal :)
8 |
9 | #### Demo
10 | 
11 |
12 | #### Supported sites
13 | + Codeforces (including gyms)
14 | + Codechef
15 | + Spoj
16 | + Hackerrank
17 | + AtCoder
18 |
19 | #### Supported Languages
20 | + C
21 | + C++
22 | + Python
23 | + Java
24 | + Ruby
25 | + Haskell
26 |
27 | #### Requirements
28 | + python3.5
29 | + coreutils (macOS only)
30 |
31 | #### Installation
32 | ##### Build from source
33 | + `git clone https://github.com/coderick14/ACedIt`
34 | + `cd ACedIt`
35 | + `python setup.py install`
36 |
37 | ##### As a Python package
38 | ```
39 | pip install --user ACedIt
40 | ```
41 |
42 | #### Usage
43 | ```
44 | usage: acedit [-h] [-s {codeforces,codechef,hackerrank,spoj}] [-c CONTEST]
45 | [-p PROBLEM] [-f] [--run SOURCE_FILE]
46 | [--set-default-site {codeforces,codechef,hackerrank,spoj}]
47 | [--set-default-contest DEFAULT_CONTEST] [--clear-cache]
48 |
49 | optional arguments:
50 | -h, --help show this help message and exit
51 | -s {codeforces,codechef,hackerrank,spoj}, --site {codeforces,codechef,hackerrank,spoj}
52 | The competitive programming platform, e.g. codeforces,
53 | codechef etc
54 | -c CONTEST, --contest CONTEST
55 | The name of the contest, e.g. JUNE17, LTIME49, COOK83
56 | etc
57 | -p PROBLEM, --problem PROBLEM
58 | The problem code, e.g. OAK, PRMQ etc
59 | -f, --force Force download the test cases, even if they are cached
60 | --run SOURCE_FILE Name of source file to be run
61 | --set-default-site {codeforces,codechef,hackerrank,spoj}
62 | Name of default site to be used when -s flag is not
63 | specified
64 | --set-default-contest DEFAULT_CONTEST
65 | Name of default contest to be used when -c flag is not
66 | specified
67 | --clear-cache Clear cached test cases for a given site. Takes
68 | default site if -s flag is omitted
69 |
70 | ```
71 | During installation, the default site is set to `codeforces`. You can change it anytime using the above mentioned flags.
72 |
73 | #### Examples
74 | + Fetch test cases for a single problem
75 | ```
76 | acedit -s codechef -c AUG17 -p CHEFFA
77 | ```
78 | + Fetch test cases for all problems in a contest
79 | ```
80 | acedit -s codechef -c AUG17
81 | ```
82 | + Force download test cases, even when they are cached
83 | ```
84 | acedit -s codeforces -c 86 -p D -f
85 | ```
86 | + Test your code (when default-site and default-contest is set and filename is same as problem_code)
87 | ```
88 | acedit --run D.cpp
89 | ```
90 | ```
91 | acedit --run CHEFFA.py
92 | ```
93 | **Since your filename is same as problem code, there's no need for the `-p` flag.**
94 | + Test your code (specifying contest and problem codes explicitly)
95 | ```
96 | acedit --run solve.cpp -c 835 -p D
97 | ```
98 | ```
99 | acedit --run test.py -s codechef -c AUG17 -p CHEFFA
100 | ```
101 |
102 | ##### Note :
103 | + The working directory structure mentioned in the previous versions is no longer required and supported.
104 |
105 | + There might be some issues with Spoj, as they have widely varying DOM trees for different problems. Feel free to contribute on this. Or anything else that you can come up with :)
106 |
107 | ##### Contributors
108 | + [Lakshmanaram](https://github.com/lakshmanaram)
109 | + [Igor Kolobov](https://github.com/Igorjan94)
110 | + [Mayuko Kori](https://github.com/kotapiku)
111 |
--------------------------------------------------------------------------------
/acedit/util.py:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 | import sys
3 | import json
4 | import re
5 | import os
6 | import functools
7 | import platform
8 | import threading
9 | try:
10 | from bs4 import BeautifulSoup as bs
11 | import requests as rq
12 | from argparse import ArgumentParser
13 | except:
14 | err = """
15 | You haven't installed the required dependencies.
16 | Run 'python setup.py install' to install the dependencies.
17 | """
18 | print(err)
19 | sys.exit(0)
20 |
21 |
22 | class Utilities:
23 |
24 | cache_dir = os.path.join(os.path.expanduser('~'), '.cache', 'ACedIt')
25 | colors = {
26 | 'GREEN': '\033[92m',
27 | 'YELLOW': '\033[93m',
28 | 'RED': '\033[91m',
29 | 'ENDC': '\033[0m',
30 | 'BOLD': '\033[1m',
31 | }
32 |
33 | @staticmethod
34 | def parse_flags(supported_sites):
35 | """
36 | Utility function to parse command line flags
37 | """
38 |
39 | parser = ArgumentParser()
40 |
41 | parser.add_argument('-s', '--site',
42 | dest='site',
43 | choices=supported_sites,
44 | help='The competitive programming platform, e.g. codeforces, codechef etc')
45 |
46 | parser.add_argument('-c', '--contest',
47 | dest='contest',
48 | help='The name of the contest, e.g. JUNE17, LTIME49, COOK83 etc')
49 |
50 | parser.add_argument('-p', '--problem',
51 | dest='problem',
52 | help='The problem code, e.g. OAK, PRMQ etc')
53 |
54 | parser.add_argument('-f', '--force',
55 | dest='force',
56 | action='store_true',
57 | help='Force download the test cases, even if they are cached')
58 |
59 | parser.add_argument('--run',
60 | dest='source_file',
61 | help='Name of source file to be run')
62 |
63 | parser.add_argument('--set-default-site',
64 | dest='default_site',
65 | choices=supported_sites,
66 | help='Name of default site to be used when -s flag is not specified')
67 |
68 | parser.add_argument('--set-default-contest',
69 | dest='default_contest',
70 | help='Name of default contest to be used when -c flag is not specified')
71 |
72 | parser.add_argument('--clear-cache',
73 | dest='clear_cache',
74 | action='store_true',
75 | help='Clear cached test cases for a given site. Takes default site if -s flag is omitted')
76 |
77 | parser.set_defaults(force=False, clear_cache=False)
78 |
79 | args = parser.parse_args()
80 |
81 | flags = {}
82 |
83 | if args.site is None or args.contest is None:
84 | import json
85 | site, contest = None, None
86 | try:
87 | with open(os.path.join(Utilities.cache_dir, 'constants.json'), 'r') as f:
88 | data = f.read()
89 | data = json.loads(data)
90 | site = data.get(
91 | 'default_site', None) if args.site is None else args.site
92 | contest = data.get(
93 | 'default_contest', None) if args.contest is None else args.contest
94 | except:
95 | pass
96 |
97 | flags['site'] = site
98 | flags['contest'] = contest if not site == 'spoj' else None
99 | else:
100 | flags['site'] = args.site
101 | flags['contest'] = args.contest
102 |
103 | flags['problem'] = args.problem
104 | flags['force'] = args.force
105 | flags['clear_cache'] = args.clear_cache
106 | flags['source'] = args.source_file
107 | flags['default_site'] = args.default_site
108 | flags['default_contest'] = args.default_contest
109 |
110 | return flags
111 |
112 | @staticmethod
113 | def set_constants(key, value):
114 | """
115 | Utility method to set default site and contest
116 | """
117 | with open(os.path.join(Utilities.cache_dir, 'constants.json'), 'r+') as f:
118 | data = f.read()
119 | data = json.loads(data)
120 | data[key] = value
121 | f.seek(0)
122 | f.write(json.dumps(data, indent=2))
123 | f.truncate()
124 |
125 | print('Set %s to %s' % (key, value))
126 |
127 | @staticmethod
128 | def check_cache(site, contest, problem):
129 | """
130 | Method to check if the test cases already exist in cache
131 | If not, create the directory structure to store test cases
132 | """
133 |
134 | if problem is None:
135 | if not os.path.isdir(os.path.join(Utilities.cache_dir, site, contest)):
136 | os.makedirs(os.path.join(Utilities.cache_dir, site, contest))
137 | return False
138 |
139 | # Handle case for SPOJ specially as it does not have contests
140 | contest = '' if site == 'spoj' else contest
141 |
142 | if os.path.isdir(os.path.join(Utilities.cache_dir, site, contest, problem)):
143 | return True
144 | else:
145 | os.makedirs(os.path.join(Utilities.cache_dir, site,
146 | contest, problem))
147 | return False
148 |
149 | @staticmethod
150 | def clear_cache(site):
151 | """
152 | Method to clear cached test cases
153 | """
154 |
155 | confirm = input(
156 | 'Remove entire cache for site %s? (y/N) : ' % (site))
157 | if confirm == 'y':
158 | from shutil import rmtree
159 | try:
160 | rmtree(os.path.join(Utilities.cache_dir, site))
161 | except:
162 | print('Some error occured. Try again.')
163 | return
164 | os.makedirs(os.path.join(Utilities.cache_dir, site))
165 | print('Done.')
166 |
167 | @staticmethod
168 | def store_files(site, contest, problem, inputs, outputs):
169 | """
170 | Method to store the test cases in files
171 | """
172 |
173 | # Handle case for SPOJ specially as it does not have contests
174 | contest = '' if site == 'spoj' else contest
175 |
176 | for i, inp in enumerate(inputs):
177 | filename = os.path.join(
178 | Utilities.cache_dir, site, contest, problem, 'Input' + str(i))
179 | with open(filename, 'w') as handler:
180 | handler.write(inp)
181 |
182 | for i, out in enumerate(outputs):
183 | filename = os.path.join(
184 | Utilities.cache_dir, site, contest, problem, 'Output' + str(i))
185 | with open(filename, 'w') as handler:
186 | handler.write(out)
187 |
188 | @staticmethod
189 | def download_problem_testcases(args):
190 | """
191 | Download test cases for a given problem
192 | """
193 | if args['site'] == 'codeforces':
194 | platform = Codeforces(args)
195 | elif args['site'] == 'codechef':
196 | platform = Codechef(args)
197 | elif args['site'] == 'spoj':
198 | platform = Spoj(args)
199 | elif args['site'] == 'atcoder':
200 | platform = AtCoder(args)
201 | else:
202 | platform = Hackerrank(args)
203 |
204 | is_in_cache = Utilities.check_cache(
205 | platform.site, platform.contest, platform.problem)
206 |
207 | if not args['force'] and is_in_cache:
208 | print('Test cases found in cache...')
209 | sys.exit(0)
210 |
211 | platform.scrape_problem()
212 |
213 | @staticmethod
214 | def download_contest_testcases(args):
215 | """
216 | Download test cases for all problems in a given contest
217 | """
218 | if args['site'] == 'codeforces':
219 | platform = Codeforces(args)
220 | elif args['site'] == 'codechef':
221 | platform = Codechef(args)
222 | elif args['site'] == 'hackerrank':
223 | platform = Hackerrank(args)
224 | elif args['site'] == 'atcoder':
225 | platform = AtCoder(args)
226 |
227 | Utilities.check_cache(
228 | platform.site, platform.contest, platform.problem)
229 |
230 | platform.scrape_contest()
231 |
232 | @staticmethod
233 | def input_file_to_string(path, num_cases):
234 | """
235 | Method to return sample inputs as a list
236 | """
237 | inputs = []
238 |
239 | for i in range(num_cases):
240 | with open(os.path.join(path, 'Input' + str(i)), 'r') as fh:
241 | inputs += [fh.read()]
242 |
243 | return inputs
244 |
245 | @staticmethod
246 | def cleanup(num_cases, basename, extension):
247 | """
248 | Method to clean up temporarily created files
249 | """
250 | for i in range(num_cases):
251 | if os.path.isfile('temp_output' + str(i)):
252 | os.remove('temp_output' + str(i))
253 |
254 | if extension == 'java':
255 | os.system('rm ' + basename + '*.class')
256 | if extension == 'cpp':
257 | os.system('rm ' + basename)
258 |
259 | @staticmethod
260 | def handle_kbd_interrupt(site, contest, problem):
261 | """
262 | Method to handle keyboard interrupt
263 | """
264 | from shutil import rmtree
265 | print('Cleaning up...')
266 |
267 | # Handle case for SPOJ specially as it does not have contests
268 | contest = '' if site == 'spoj' else contest
269 |
270 | if problem is not None:
271 | path = os.path.join(Utilities.cache_dir, site, contest, problem)
272 | if os.path.isdir(path):
273 | rmtree(path)
274 | else:
275 | path = os.path.join(Utilities.cache_dir, site, contest)
276 | if os.path.isdir(path):
277 | rmtree(path)
278 |
279 | print('Done. Exiting gracefully.')
280 |
281 | @staticmethod
282 | def run_solution(args):
283 | """
284 | Method to run and test the user's solution against sample cases
285 | """
286 | problem = args['source']
287 |
288 | extension = problem.split('.')[-1]
289 | problem = problem.split('.')[0]
290 | basename = problem.split('/')[-1]
291 | problem_path = os.path.join(os.getcwd(), problem)
292 |
293 | if not os.path.isfile(problem_path + '.' + extension):
294 | print('ERROR : No such file')
295 | sys.exit(0)
296 |
297 | problem_code = args['problem'] if args['problem'] else basename
298 | contest_code = '' if args['site'] == 'spoj' else args['contest']
299 |
300 | testcases_path = os.path.join(Utilities.cache_dir, args[
301 | 'site'], contest_code, problem_code)
302 |
303 | if os.path.isdir(testcases_path):
304 | num_cases = len(os.listdir(testcases_path)) // 2
305 | results, expected_outputs, user_outputs = [], [], []
306 |
307 | if extension in ['c', 'cpp', 'java', 'py', 'hs', 'rb']:
308 |
309 | # Compiler flags taken from http://codeforces.com/blog/entry/79
310 | compiler = {
311 | 'hs': 'ghc --make -O -dynamic -o ' + basename,
312 | 'py': None,
313 | 'rb': None,
314 | 'c': 'gcc -static -DONLINE_JUDGE -fno-asm -lm -s -O2 -o ' + basename,
315 | 'cpp': 'g++ -static -DONLINE_JUDGE -lm -s -x c++ -O2 -std=c++14 -o ' + basename,
316 | 'java': 'javac -d .'
317 | }[extension]
318 |
319 | execute_command = {
320 | 'py': 'python \'' + problem_path + '.' + extension + '\'',
321 | 'rb': 'ruby \'' + problem_path + '.' + extension + '\'',
322 | 'hs': './' + basename,
323 | 'c': './' + basename,
324 | 'cpp': './' + basename,
325 | 'java': 'java -DONLINE_JUDGE=true -Duser.language=en -Duser.region=US -Duser.variant=US ' + basename
326 | }[extension]
327 |
328 | if compiler is None:
329 | compile_status = 0
330 | else:
331 | compile_status = os.system(
332 | compiler + ' \'' + problem_path + '.' + extension + '\'')
333 |
334 | if compile_status == 0:
335 |
336 | # Compiled successfully
337 | timeout_command = 'timeout' if platform.system() == 'Linux' else 'gtimeout'
338 | for i in range(num_cases):
339 | status = os.system(timeout_command + ' 2s ' + execute_command + ' < ' + os.path.join(
340 | testcases_path, 'Input' + str(i)) + ' > temp_output' + str(i))
341 |
342 | with open(os.path.join(testcases_path, 'Output' + str(i)), 'r') as out_handler:
343 | expected_output = out_handler.read().strip().split('\n')
344 | expected_output = '\n'.join(
345 | [line.strip() for line in expected_output])
346 | expected_outputs += [expected_output]
347 |
348 | if status == 31744:
349 | # Time Limit Exceeded
350 | results += [Utilities.colors['BOLD'] + Utilities.colors[
351 | 'YELLOW'] + 'TLE' + Utilities.colors['ENDC']]
352 | user_outputs += ['']
353 |
354 | elif status == 0:
355 | # Ran successfully
356 | with open('temp_output' + str(i), 'r') as temp_handler:
357 | user_output = temp_handler.read().strip().split('\n')
358 | user_output = '\n'.join(
359 | [line.strip() for line in user_output])
360 | user_outputs += [user_output]
361 |
362 | if expected_output == user_output:
363 | # All Correct
364 | results += [Utilities.colors['BOLD'] + Utilities.colors[
365 | 'GREEN'] + 'AC' + Utilities.colors['ENDC']]
366 | else:
367 | # Wrong Answer
368 | results += [Utilities.colors['BOLD'] + Utilities.colors[
369 | 'RED'] + 'WA' + Utilities.colors['ENDC']]
370 |
371 | else:
372 | # Runtime Error
373 | results += [Utilities.colors['BOLD'] +
374 | Utilities.colors['RED'] + 'RTE' + Utilities.colors['ENDC']]
375 | user_outputs += ['']
376 | else:
377 | # Compilation error occurred
378 | message = Utilities.colors['BOLD'] + Utilities.colors[
379 | 'RED'] + 'Compilation error. Not run against test cases' + Utilities.colors['ENDC'] + '.'
380 | print(message)
381 | sys.exit(0)
382 |
383 | else:
384 | print('Supports only C, C++, Python, Java, Ruby and Haskell as of now.')
385 | sys.exit(0)
386 |
387 | from terminaltables import AsciiTable
388 | table_data = [['Serial No', 'Input',
389 | 'Expected Output', 'Your Output', 'Result']]
390 |
391 | inputs = Utilities.input_file_to_string(testcases_path, num_cases)
392 |
393 | for i in range(num_cases):
394 |
395 | row = [
396 | i + 1,
397 | inputs[i],
398 | expected_outputs[i],
399 | user_outputs[i] if any(sub in results[i]
400 | for sub in ['AC', 'WA']) else 'N/A',
401 | results[i]
402 | ]
403 |
404 | table_data.append(row)
405 |
406 | table = AsciiTable(table_data)
407 |
408 | print(table.table)
409 |
410 | # Clean up temporary files
411 | Utilities.cleanup(num_cases, basename, extension)
412 |
413 | else:
414 | print('Test cases not found locally...')
415 |
416 | args['problem'] = problem_code
417 | args['force'] = True
418 | args['source'] = problem + '.' + extension
419 |
420 | Utilities.download_problem_testcases(args)
421 |
422 | print('Running your solution against sample cases...')
423 | Utilities.run_solution(args)
424 |
425 | @staticmethod
426 | def get_html(url):
427 | """
428 | Utility function get the html content of an url
429 | """
430 | sys.setrecursionlimit(10000)
431 | MAX_TRIES = 3
432 | try:
433 | for try_count in range(MAX_TRIES):
434 | r = rq.get(url)
435 | if r.status_code == 200:
436 | break
437 | if try_count >= MAX_TRIES:
438 | print('Could not fetch content. Please try again.')
439 | sys.exit(0)
440 | except Exception as e:
441 | print('Please check your internet connection and try again.')
442 | sys.exit(0)
443 | return r
444 |
445 |
446 | class Platform:
447 | """
448 | Base class for platforms
449 | """
450 | def __init__(self, args):
451 | self.site = args['site']
452 | self.contest = args['contest']
453 | self.force_download = args['force']
454 | self.responses = []
455 | self.lock = threading.Lock()
456 |
457 | def get_problem_name(self, response):
458 | return response.url.split('/')[-1]
459 |
460 | def build_problem_url(self):
461 | raise NotImplementedError
462 |
463 | def parse_html(self):
464 | raise NotImplementedError
465 |
466 | def scrape_problem(self):
467 | """
468 | Method to scrape a single problem
469 | """
470 | contest = '' if self.site == 'spoj' else self.contest
471 | print('Fetching problem %s-%s from %s...' % (contest, self.problem, self.site))
472 | req = Utilities.get_html(self.build_problem_url())
473 | inputs, outputs = self.parse_html(req)
474 | Utilities.store_files(self.site, self.contest,
475 | self.problem, inputs, outputs)
476 | print('Done.')
477 |
478 | def fetch_html(self, link):
479 | r = rq.get(link)
480 | with self.lock:
481 | self.responses += [r]
482 |
483 |
484 | def handle_batch_requests(self, links):
485 | """
486 | Method to send simultaneous requests to all problem pages
487 | """
488 | threads = [threading.Thread(target=self.fetch_html, args=(link,)) for link in links]
489 |
490 | for t in threads:
491 | t.start()
492 |
493 | for t in threads:
494 | t.join()
495 |
496 | failed_requests = []
497 |
498 | for response in self.responses:
499 | if response is not None and response.status_code == 200:
500 | inputs, outputs = self.parse_html(response)
501 | self.problem = self.get_problem_name(response)
502 | Utilities.check_cache(self.site, self.contest, self.problem)
503 | Utilities.store_files(
504 | self.site, self.contest, self.problem, inputs, outputs)
505 | else:
506 | failed_requests += [response.url]
507 |
508 | return failed_requests
509 |
510 | def scrape_contest(self):
511 | """
512 | Method to scrape all problems from a given contest
513 | """
514 | print('Checking problems available for contest %s-%s...' % (self.site, self.contest))
515 | req = Utilities.get_html(self.build_contest_url())
516 | links = self.get_problem_links(req)
517 |
518 | print('Found %d problems..' % (len(links)))
519 |
520 | if not self.force_download:
521 | cached_problems = os.listdir(os.path.join(
522 | Utilities.cache_dir, self.site, self.contest))
523 | links = [link for link in links if link.split(
524 | '/')[-1] not in cached_problems]
525 |
526 | failed_requests = self.handle_batch_requests(links)
527 | if len(failed_requests) > 0:
528 | self.handle_batch_requests(failed_requests)
529 |
530 |
531 | class Codeforces(Platform):
532 | """
533 | Class to handle downloading of test cases from Codeforces
534 | """
535 |
536 | def __init__(self, args):
537 | self.problem = args['problem']
538 | super(Codeforces, self).__init__(args)
539 |
540 | def parse_html(self, req):
541 | """
542 | Method to parse the html and get test cases
543 | from a codeforces problem
544 | """
545 | soup = bs(req.text, 'html.parser')
546 |
547 | inputs = soup.findAll('div', {'class': 'input'})
548 | outputs = soup.findAll('div', {'class': 'output'})
549 |
550 | if len(inputs) == 0 or len(outputs) == 0:
551 | print('Problem not found..')
552 | Utilities.handle_kbd_interrupt(
553 | self.site, self.contest, self.problem)
554 | sys.exit(0)
555 |
556 | repls = ('
', '\n'), ('
', '\n'), ('', '')
557 |
558 | formatted_inputs, formatted_outputs = [], []
559 |
560 | for inp in inputs:
561 | pre = inp.find('pre').decode_contents()
562 | pre = functools.reduce(lambda a, kv: a.replace(*kv), repls, pre)
563 | pre = re.sub('<[^<]+?>', '', pre)
564 | formatted_inputs += [pre]
565 |
566 | for out in outputs:
567 | pre = out.find('pre').decode_contents()
568 | pre = functools.reduce(lambda a, kv: a.replace(*kv), repls, pre)
569 | pre = re.sub('<[^<]+?>', '', pre)
570 | formatted_outputs += [pre]
571 |
572 | # print 'Inputs', formatted_inputs
573 | # print 'Outputs', formatted_outputs
574 |
575 | return formatted_inputs, formatted_outputs
576 |
577 | def get_problem_links(self, req):
578 | """
579 | Method to get the links for the problems
580 | in a given codeforces contest
581 | """
582 | soup = bs(req.text, 'html.parser')
583 |
584 | table = soup.find('table', {'class': 'problems'})
585 |
586 | if table is None:
587 | print('Contest not found..')
588 | Utilities.handle_kbd_interrupt(
589 | self.site, self.contest, self.problem)
590 | sys.exit(0)
591 |
592 | links = ['http://codeforces.com' +
593 | td.find('a')['href'] for td in table.findAll('td', {'class': 'id'})]
594 |
595 | return links
596 |
597 | def build_problem_url(self):
598 | contest_type = 'contest' if int(self.contest) <= 100000 else 'gym'
599 | return 'http://codeforces.com/%s/%s/problem/%s' % (contest_type, self.contest, self.problem)
600 |
601 | def build_contest_url(self):
602 | contest_type = 'contest' if int(self.contest) <= 100000 else 'gym'
603 | return 'http://codeforces.com/%s/%s' % (contest_type, self.contest)
604 |
605 |
606 | class Codechef(Platform):
607 | """
608 | Class to handle downloading of test cases from Codechef
609 | """
610 |
611 | def __init__(self, args):
612 | self.problem = args['problem']
613 | super(Codechef, self).__init__(args)
614 |
615 | def _extract(self, data, marker):
616 | data_low = data.lower()
617 | extracts = []
618 | idx = data_low.find(marker, 0)
619 |
620 | while not idx == -1:
621 | start = data_low.find('```', idx)
622 | end = data_low.find('```', start + 3)
623 | extracts += [data[start + 3:end]]
624 | idx = data_low.find(marker, end)
625 |
626 | return [extract.strip() for extract in extracts]
627 |
628 | def parse_html(self, req):
629 | """
630 | Method to parse the html and get test cases
631 | from a codechef problem
632 | """
633 | try:
634 | data = str(json.loads(req.text)['body'])
635 | except (KeyError, ValueError):
636 | print('Problem not found..')
637 | Utilities.handle_kbd_interrupt(
638 | self.site, self.contest, self.problem)
639 | sys.exit(0)
640 |
641 | inputs = self._extract(data, 'example input')
642 | outputs = self._extract(data, 'example output')
643 |
644 | return inputs, outputs
645 |
646 | def get_problem_links(self, req):
647 | """
648 | Method to get the links for the problems
649 | in a given codechef contest
650 | """
651 | soup = bs(req.text, 'html.parser')
652 |
653 | table = soup.find('table', {'class': 'dataTable'})
654 |
655 | if table is None:
656 | print('Contest not found..')
657 | Utilities.handle_kbd_interrupt(
658 | self.site, self.contest, self.problem)
659 | sys.exit(0)
660 |
661 | links = [div.find('a')['href']
662 | for div in table.findAll('div', {'class': 'problemname'})]
663 | links = ['https://codechef.com/api/contests/' + self.contest +
664 | '/problems/' + link.split('/')[-1] for link in links]
665 |
666 | return links
667 |
668 | def build_problem_url(self):
669 | return 'https://codechef.com/api/contests/%s/problems/%s' % (self.contest, self.problem)
670 |
671 | def build_contest_url(self):
672 | return 'https://codechef.com/%s' % self.contest
673 |
674 |
675 | class Spoj(Platform):
676 | """
677 | Class to handle downloading of test cases from Spoj
678 | """
679 |
680 | def __init__(self, args):
681 | self.problem = args['problem'].upper()
682 | super(Spoj, self).__init__(args)
683 |
684 | def parse_html(self, req):
685 | """
686 | Method to parse the html and get test cases
687 | from a spoj problem
688 | """
689 | soup = bs(req.text, 'html.parser')
690 |
691 | test_cases = soup.findAll('pre')
692 |
693 | if test_cases is None or len(test_cases) == 0:
694 | print('Problem not found..')
695 | Utilities.handle_kbd_interrupt(
696 | self.site, self.contest, self.problem)
697 | sys.exit(0)
698 |
699 | formatted_inputs, formatted_outputs = [], []
700 |
701 | input_list = [
702 | '(.|\n|\r)*Input:?:?',
703 | 'Output:?(.|\n|\r)*'
704 | ]
705 |
706 | output_list = [
707 | '(.|\n|\r)*Output:?:?',
708 | '
'
709 | ]
710 |
711 | input_regex = re.compile('(%s)' % '|'.join(input_list))
712 | output_regex = re.compile('(%s)' % '|'.join(output_list))
713 |
714 | for case in test_cases:
715 | inp = input_regex.sub('', str(case))
716 | out = output_regex.sub('', str(case))
717 |
718 | inp = re.sub('<[^<]+?>', '', inp)
719 | out = re.sub('<[^<]+?>', '', out)
720 |
721 | formatted_inputs += [inp.strip()]
722 | formatted_outputs += [out.strip()]
723 |
724 | # print 'Inputs', formatted_inputs
725 | # print 'Outputs', formatted_outputs
726 |
727 | return formatted_inputs, formatted_outputs
728 |
729 | def build_problem_url(self):
730 | return 'http://spoj.com/problems/%s' % self.problem
731 |
732 |
733 | class Hackerrank(Platform):
734 | """
735 | Class to handle downloading of test cases from Hackerrank
736 | """
737 |
738 | def __init__(self, args):
739 | self.problem = '-'.join(args['problem'].split()
740 | ).lower() if args['problem'] is not None else None
741 | super(Hackerrank, self).__init__(args)
742 |
743 | def parse_html(self, req):
744 | """
745 | Method to parse the html and get test cases
746 | from a hackerrank problem
747 | """
748 |
749 | try:
750 | data = json.loads(req.text)
751 | soup = bs(data['model']['body_html'], 'html.parser')
752 | except (KeyError, ValueError):
753 | print('Problem not found..')
754 | Utilities.handle_kbd_interrupt(
755 | self.site, self.contest, self.problem)
756 | sys.exit(0)
757 |
758 | input_divs = soup.findAll('div', {'class': 'challenge_sample_input'})
759 | output_divs = soup.findAll('div', {'class': 'challenge_sample_output'})
760 |
761 | inputs = [input_div.find('pre') for input_div in input_divs]
762 | outputs = [output_div.find('pre') for output_div in output_divs]
763 |
764 | regex_list = [
765 | '()?',
766 | '()?
'
767 | ]
768 |
769 | regex = re.compile('(%s)' % '|'.join(regex_list))
770 |
771 | formatted_inputs, formatted_outputs = [], []
772 |
773 | for inp in inputs:
774 | spans = inp.findAll('span')
775 | if len(spans) > 0:
776 | formatted_input = '\n'.join(
777 | [span.decode_contents() for span in spans])
778 | else:
779 | formatted_input = regex.sub('', str(inp))
780 |
781 | formatted_inputs += [formatted_input.strip()]
782 |
783 | for out in outputs:
784 | spans = out.findAll('span')
785 | if len(spans) > 0:
786 | formatted_output = '\n'.join(
787 | [span.decode_contents() for span in spans])
788 | else:
789 | formatted_output = regex.sub('', str(out))
790 |
791 | formatted_outputs += [formatted_output.strip()]
792 |
793 | # print 'Inputs', formatted_inputs
794 | # print 'Outputs', formatted_outputs
795 |
796 | return formatted_inputs, formatted_outputs
797 |
798 | def get_problem_links(self, req):
799 | """
800 | Method to get the links for the problems
801 | in a given hackerrank contest
802 | """
803 |
804 | try:
805 | data = json.loads(req.text)
806 | data = data['models']
807 | except (KeyError, ValueError):
808 | print('Contest not found..')
809 | Utilities.handle_kbd_interrupt(
810 | self.site, self.contest, self.problem)
811 | sys.exit(0)
812 |
813 | links = ['https://www.hackerrank.com/rest/contests/' + self.contest +
814 | '/challenges/' + problem['slug'] for problem in data]
815 |
816 | return links
817 |
818 | def build_problem_url(self):
819 | return 'https://www.hackerrank.com/rest/contests/%s/challenges/%s' % (self.contest, self.problem)
820 |
821 | def build_contest_url(self):
822 | 'https://www.hackerrank.com/rest/contests/%s/challenges' % self.contest
823 |
824 |
825 | class AtCoder(Platform):
826 | """
827 | Class to handle downloading of test cases from atcoder
828 | """
829 |
830 | def __init__(self, args):
831 | self.problem = args['problem']
832 | super(AtCoder, self).__init__(args)
833 |
834 | def parse_html(self, req):
835 | """
836 | Method to parse the html and get test cases
837 | from a atcoder problem
838 | """
839 | soup = bs(req.text, 'html.parser')
840 |
841 | inouts= soup.findAll('div', {'class': 'part'})
842 |
843 | repls = ('
', '\n'), ('
', '\n'), ('', '')
844 |
845 | formatted_inputs, formatted_outputs = [], []
846 |
847 | inouts = filter((lambda x: x.find('section') and x.find('section').find('h3')), inouts)
848 |
849 | for inp in inouts:
850 | if inp.find('section').find('h3').text[:3] == "入力例":
851 | pre = inp.find('pre').decode_contents()
852 | pre = functools.reduce(lambda a, kv: a.replace(*kv), repls, pre)
853 | pre = re.sub('<[^<]+?>', '', pre)
854 | pre = pre.replace("&", "&")
855 | pre = pre.replace("<", "<")
856 | pre = pre.replace(">", ">")
857 | formatted_inputs += [pre]
858 | if inp.find('section').find('h3').text[:3] == "出力例":
859 | pre = inp.find('pre').decode_contents()
860 | pre = functools.reduce(lambda a, kv: a.replace(*kv), repls, pre)
861 | pre = re.sub('<[^<]+?>', '', pre)
862 | pre = pre.replace("&", "&")
863 | pre = pre.replace("<", "<")
864 | pre = pre.replace(">", ">")
865 | formatted_outputs += [pre]
866 |
867 | return formatted_inputs, formatted_outputs
868 |
869 | def get_problem_links(self, req):
870 | """
871 | Method to get the links for the problems
872 | in a given atcoder contest
873 | """
874 | soup = bs(req.text, 'html.parser')
875 |
876 | table = soup.find('tbody')
877 |
878 | if table is None:
879 | print('Contest not found..')
880 | Utilities.handle_kbd_interrupt(
881 | self.site, self.contest, self.problem)
882 | sys.exit(0)
883 |
884 | links = ['http://beta.atcoder.jp' +
885 | td.find('a')['href'] for td in soup.findAll('td', {'class': 'text-center no-break'})]
886 |
887 | return links
888 |
889 | def get_problem_name(self, response):
890 | """
891 | Method to get the names for the problems
892 | in a given atcoder contest
893 | """
894 | soup = bs(response.text, 'html.parser')
895 | return soup.find('title').get_text()[0].lower()
896 |
897 | def build_problem_url(self):
898 | return 'https://beta.atcoder.jp/contests/%s/tasks/%s' % (self.contest, self.problem)
899 |
900 | def build_contest_url(self):
901 | return 'https://beta.atcoder.jp/contests/%s/tasks/' % self.contest
902 |
903 |
--------------------------------------------------------------------------------