Skip to content

Commit e12dc25

Browse files
committed
Initial commit
0 parents  commit e12dc25

37 files changed

+1745
-0
lines changed

.coveragerc

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[run]
2+
source = limejudge

.gitignore

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
env/
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
*.egg-info/
24+
.installed.cfg
25+
*.egg
26+
27+
# PyInstaller
28+
# Usually these files are written by a python script from a template
29+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
30+
*.manifest
31+
*.spec
32+
33+
# Installer logs
34+
pip-log.txt
35+
pip-delete-this-directory.txt
36+
37+
# Unit test / coverage reports
38+
htmlcov/
39+
.tox/
40+
.coverage
41+
.coverage.*
42+
.cache
43+
nosetests.xml
44+
coverage.xml
45+
*,cover
46+
.hypothesis/
47+
48+
# Translations
49+
*.mo
50+
*.pot
51+
52+
# Django stuff:
53+
*.log
54+
local_settings.py
55+
56+
# Flask instance folder
57+
instance/
58+
59+
# Scrapy stuff:
60+
.scrapy
61+
62+
# Sphinx documentation
63+
docs/_build/
64+
65+
# PyBuilder
66+
target/
67+
68+
# IPython Notebook
69+
.ipynb_checkpoints
70+
71+
# pyenv
72+
.python-version
73+
74+
# celery beat schedule file
75+
celerybeat-schedule
76+
77+
# dotenv
78+
.env
79+
80+
# virtualenv
81+
venv/
82+
ENV/
83+
84+
# Spyder project settings
85+
.spyderproject
86+
87+
# Rope project settings
88+
.ropeproject

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2015 Yutao Yuan
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.

MANIFEST.in

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include LICENSE
2+
recursive-include tests *.py

README.rst

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Simple judge tool for OI contests
2+
=================================
3+
4+
LimeJudge is a simple OI judge for Linux.
5+
6+
You will need Python 3.4+, PyYAML, docopt and termcolor to run this
7+
program.
8+
9+
Warning: This program doesn't have any sandbox. You should only run it
10+
with trusted code.
11+
12+
It doesn't work well with programs that use multiple threads or spawn
13+
other processes.

limejudge/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""Simple OI judge tool."""
2+
3+
4+
# Version information. The following line must be in this format. It is
5+
# read in setup.py to get the version string.
6+
__version__ = '0.0.9'

limejudge/__main__.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""__main__ wrapper for limejudge."""
2+
3+
import sys
4+
5+
from limejudge.cmdline import main
6+
7+
8+
if __name__ == '__main__':
9+
sys.exit(main())

limejudge/anscompare.py

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""Compare program output and provided answer."""
2+
3+
import subprocess
4+
5+
6+
def compare(comp_type, infile, outfile, ansfile, full_score):
7+
"""Compare answers using comparison type comp_type.
8+
9+
infile is the input file.
10+
outfile is the contestant's output file.
11+
ansfile is the standard output file.
12+
full_score is the maximum score.
13+
"""
14+
if comp_type == 'no-extra-ws':
15+
if _compare_no_extra_ws(outfile, ansfile):
16+
return full_score
17+
else:
18+
return 0
19+
elif comp_type.startswith('special-judge:'):
20+
spj_name = comp_type[len('special-judge:'):]
21+
return _compare_spj(spj_name, infile, outfile, ansfile,
22+
full_score)
23+
else:
24+
raise ValueError('Unknown comparison type: ' + repr(comp_type))
25+
26+
27+
def _compare_no_extra_ws(out1, out2):
28+
"""Compare file out1 and out2. Return true if two files match.
29+
30+
This function ignores trailing whitespace in every line and trailing
31+
blank lines.
32+
"""
33+
try:
34+
with open(out1) as f1, open(out2) as f2:
35+
return _compare_files_no_extra_ws(f1, f2)
36+
except FileNotFoundError:
37+
return False
38+
39+
40+
def _compare_files_no_extra_ws(f1, f2):
41+
while True:
42+
l1 = f1.readline()
43+
l2 = f2.readline()
44+
if l1 == '' or l2 == '':
45+
# One of the files ends
46+
break
47+
if l1.rstrip() != l2.rstrip():
48+
return False
49+
while l1 != '' or l2 != '':
50+
if l1.rstrip() != '' or l2.rstrip() != '':
51+
# One of the files has a non-empty line
52+
return False
53+
l1 = f1.readline()
54+
l2 = f2.readline()
55+
return True
56+
57+
58+
def _compare_spj(spj_name, infile, outfile, ansfile, full_score):
59+
proc = subprocess.Popen([spj_name, infile, outfile, ansfile,
60+
str(full_score)],
61+
stdout=subprocess.PIPE)
62+
proc_out, _ = proc.communicate()
63+
retcode = proc.poll()
64+
if retcode:
65+
return 0
66+
return int(proc_out)

limejudge/cmdline.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Simple judge tool for OI contests.
2+
3+
Usage:
4+
limejudge <command> [<args>...]
5+
limejudge --help
6+
limejudge --version
7+
8+
Available commands:
9+
autocreate Create a contest configuration file automatically.
10+
resourcetest Test the time and memory a program uses.
11+
runjudge Judge the contest.
12+
13+
Options:
14+
-h, --help Print this help message and exit.
15+
--version Print version information and exit.
16+
"""
17+
18+
import sys
19+
20+
from docopt import docopt
21+
22+
from limejudge import __version__
23+
from limejudge.commands import UnknownCommandError, run_command_by_name
24+
25+
26+
def main(argv=None):
27+
if argv is None:
28+
argv = sys.argv[1:]
29+
args = docopt(__doc__, argv=argv,
30+
version='limejudge ' + __version__,
31+
options_first=True)
32+
cmd_argv = [args['<command>']] + args['<args>']
33+
try:
34+
return run_command_by_name(args['<command>'], cmd_argv)
35+
except UnknownCommandError as err:
36+
print(str(err), file=sys.stderr)
37+
return 1

limejudge/commands/__init__.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Subcommands for limejudge."""
2+
3+
4+
class UnknownCommandError(ValueError):
5+
"""Command name is unknown."""
6+
7+
8+
def run_command_by_name(name, argv=None):
9+
if name == 'autocreate':
10+
from limejudge.commands.autocreate import main
11+
elif name == 'resourcetest':
12+
from limejudge.commands.resourcetest import main
13+
elif name == 'runjudge':
14+
from limejudge.commands.runjudge import main
15+
else:
16+
raise UnknownCommandError('Unknown command name: ' + repr(name))
17+
return main(argv)

limejudge/commands/autocreate.py

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"""Create a contest configuration file automatically.
2+
3+
Usage:
4+
limejudge autocreate [options]
5+
limejudge autocreate --help
6+
7+
Options:
8+
-c, --contest=<file> Write contest configuration to this file.
9+
[default: contest.yaml]
10+
-f, --force Force writing contest configuration even if
11+
the file already exists.
12+
--cflags=<flags> Use extra C compiler flags for this contest.
13+
--cxxflags=<flags> Use extra C++ compiler flags for this contest.
14+
-h, --help Print this help message and exit.
15+
"""
16+
17+
import os
18+
import sys
19+
from glob import glob
20+
21+
import yaml
22+
from docopt import docopt
23+
24+
25+
def ask_resource_limits(probname):
26+
time_limit = input(
27+
'What is the time limit (in s) for "' + probname + '"? ')
28+
time_limit = float(time_limit)
29+
if int(time_limit) == time_limit:
30+
time_limit = int(time_limit)
31+
memory_limit = input(
32+
'What is the memory limit (in MB) for "' + probname + '"? ')
33+
memory_limit = int(memory_limit) * 1024 * 1024
34+
return {
35+
'time-limit': time_limit,
36+
'memory-limit': memory_limit,
37+
}
38+
39+
40+
def find_contestants(basedir):
41+
source_dir = os.path.join(basedir, 'source')
42+
contestants = []
43+
for name in sorted(os.listdir(source_dir)):
44+
if os.path.isdir(os.path.join(source_dir, name)):
45+
contestants.append({
46+
'name': name,
47+
'path': os.path.join('source', name),
48+
})
49+
return contestants
50+
51+
52+
def find_testcases(basedir, probname):
53+
resource_limits = ask_resource_limits(probname)
54+
prob_dir = os.path.join(basedir, 'data', probname)
55+
has_in = {os.path.splitext(os.path.basename(x))[0]
56+
for x in glob(os.path.join(prob_dir, '*.in'))
57+
if os.path.isfile(x)}
58+
has_out = {os.path.splitext(os.path.basename(x))[0]
59+
for x in glob(os.path.join(prob_dir, '*.out'))
60+
if os.path.isfile(x)}
61+
testcases = []
62+
prob_reldir = os.path.join('data', probname)
63+
for testdata in sorted(has_in & has_out):
64+
testcases.append({
65+
'input-file': os.path.join(prob_reldir, testdata + '.in'),
66+
'output-file': os.path.join(prob_reldir, testdata + '.out'),
67+
'resource-limits': resource_limits,
68+
})
69+
if not testcases:
70+
return []
71+
testcase_cnt = len(testcases)
72+
full_score_each = 100 // testcase_cnt
73+
for testcase in testcases:
74+
testcase['full-score'] = full_score_each
75+
return testcases
76+
77+
78+
def find_problems(basedir):
79+
data_dir = os.path.join(basedir, 'data')
80+
problems = []
81+
for name in sorted(os.listdir(data_dir)):
82+
if os.path.isdir(os.path.join(data_dir, name)):
83+
testcases = find_testcases(basedir, name)
84+
problems.append({
85+
'type': 'normal',
86+
'name': name,
87+
'input-file': name + '.in',
88+
'output-file': name + '.out',
89+
'source-path': name,
90+
'testcases': testcases,
91+
})
92+
return problems
93+
94+
95+
def main(argv=None):
96+
if argv is None:
97+
argv = sys.argv[1:]
98+
args = docopt(__doc__, argv=argv)
99+
if os.path.exists(args['--contest']):
100+
if os.path.isdir(args['--contest']):
101+
print('Not overwriting directory.', file=sys.stderr)
102+
return 1
103+
elif not args['--force']:
104+
print('Not overwriting existing file.', file=sys.stderr)
105+
return 1
106+
cwd = os.getcwd()
107+
contest_data = {
108+
'metadata': {'title': os.path.basename(cwd)},
109+
'contestants': find_contestants(cwd),
110+
'problems': find_problems(cwd),
111+
}
112+
compiler_flags = {}
113+
if args['--cflags'] is not None:
114+
compiler_flags['c'] = args['--cflags'].strip()
115+
if args['--cxxflags'] is not None:
116+
compiler_flags['cpp'] = args['--cxxflags'].strip()
117+
if compiler_flags:
118+
for problem in contest_data['problems']:
119+
problem['compiler-flags'] = compiler_flags
120+
with open(args['--contest'], 'w') as fout:
121+
fout.write(yaml.safe_dump(contest_data))

0 commit comments

Comments
 (0)