Skip to content

Commit a443462

Browse files
committed
Program comparer
1 parent 08171f6 commit a443462

File tree

15 files changed

+258
-28
lines changed

15 files changed

+258
-28
lines changed

cyaron/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
from .consts import *
1313
from .vector import Vector
1414
from .polygon import Polygon
15+
from .compare import Compare
1516
from random import randint, randrange, uniform, choice, random
1617

cyaron/compare.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from cyaron import IO
2+
from cyaron.consts import *
3+
from cyaron.graders import CYaRonGraders
4+
import subprocess
5+
6+
7+
class Compare:
8+
@staticmethod
9+
def __compare_two(name, content, std, grader):
10+
(result, info) = CYaRonGraders.invoke(grader, content, std)
11+
12+
info = info if info is not None else ""
13+
status = "Correct" if result else "!!!INCORRECT!!!"
14+
print("%s: %s %s" % (name, status, info))
15+
16+
@staticmethod
17+
def __process_file(file):
18+
if isinstance(file, IO):
19+
file.flush_buffer()
20+
file.output_file.seek(0)
21+
return file.output_filename, file.output_file.read()
22+
else:
23+
with open(file, "r") as f:
24+
return file, f.read()
25+
26+
@staticmethod
27+
def output(*args, **kwargs):
28+
if len(args) == 0:
29+
raise Exception("You must specify some files to compare.")
30+
31+
if "std" not in kwargs:
32+
raise Exception("You must specify a std.")
33+
(_, std) = Compare.__process_file(kwargs["std"])
34+
35+
grader = kwargs.get("grader", DEFAULT_GRADER)
36+
37+
for file in args:
38+
(file_name, content) = Compare.__process_file(file)
39+
Compare.__compare_two(file_name, content, std, grader)
40+
41+
@staticmethod
42+
def program(*args, **kwargs):
43+
if len(args) == 0:
44+
raise Exception("You must specify some programs to compare.")
45+
46+
if "input" not in kwargs:
47+
raise Exception("You must specify an input.")
48+
input = kwargs['input']
49+
if not isinstance(input, IO):
50+
raise Exception("Input must be an IO instance.")
51+
input.flush_buffer()
52+
input.input_file.seek(0)
53+
54+
std = None
55+
if "std" not in kwargs and "std_program" not in kwargs:
56+
raise Exception("You must specify a std or a std_program.")
57+
else:
58+
if "std_program" in kwargs:
59+
std = subprocess.check_output(kwargs['std_program'], shell=True, stdin=input.input_file).decode('ascii')
60+
else:
61+
(_, std) = Compare.__process_file(kwargs["std"])
62+
63+
grader = kwargs.get("grader", DEFAULT_GRADER)
64+
65+
for program_name in args:
66+
content = subprocess.check_output(program_name, shell=True, stdin=input.input_file)
67+
Compare.__compare_two(program_name, content, std, grader)
68+
69+

cyaron/consts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@
1919
NUMBERS = string.digits
2020
SENTENCE_SEPARATORS = ',,,,,,,;;:' # 70% ',' 20% ';' 10% ':'
2121
SENTENCE_TERMINATORS = '....!' # 80% '.' 20% '!'
22+
23+
DEFAULT_GRADER = "NOIPStyle"

cyaron/graders/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .graderregistry import CYaRonGraders
2+
3+
from .fulltext import fulltext
4+
from .noipstyle import noipstyle

cyaron/graders/fulltext.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import hashlib
2+
from .graderregistry import CYaRonGraders
3+
4+
@CYaRonGraders.grader("FullText")
5+
def fulltext(content, std):
6+
content_hash = hashlib.sha256(content).hexdigest()
7+
std_hash = hashlib.sha256(std).hexdigest()
8+
return True, None if content_hash == std_hash else False, "Hash mismatch: read %s, expected %s" % (content_hash, std_hash)
9+

cyaron/graders/graderregistry.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class GraderRegistry:
2+
_registry = dict()
3+
4+
def grader(self, name):
5+
def wrapper(func):
6+
self._registry[name] = func
7+
return func
8+
9+
return wrapper
10+
11+
def invoke(self, name, content, std):
12+
return self._registry[name](content, std)
13+
14+
def check(self, name):
15+
return name in self._registry
16+
17+
18+
CYaRonGraders = GraderRegistry()

cyaron/graders/noipstyle.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from ..utils import *
2+
from .graderregistry import CYaRonGraders
3+
4+
5+
@CYaRonGraders.grader("NOIPStyle")
6+
def noipstyle(content, std):
7+
content_lines = strtolines(content.replace('\r\n', '\n'))
8+
std_lines = strtolines(std.replace('\r\n', '\n'))
9+
if len(content_lines) != len(std_lines):
10+
return False, 'Too many or too few lines.'
11+
12+
for i in range(len(content_lines)):
13+
if std_lines[i] != content_lines[i]:
14+
for j in range(min(len(std_lines[i]), len(content_lines[i]))):
15+
if std_lines[i][j] != content_lines[i][j]:
16+
return (False, 'On line %d column %d, read %s, expected %s.'
17+
% (i + 1, j + 1, content_lines[i][j:j + 5], std_lines[i][j:j + 5]))
18+
if len(std_lines[i]) > len(content_lines[i]):
19+
return False, 'Too short on line %d.' % i
20+
if len(std_lines[i]) < len(content_lines[i]):
21+
return False, 'Too long on line %d.' % i
22+
23+
return True, None

cyaron/io.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from .utils import *
22
import subprocess
3+
import tempfile
4+
import os
35

46

57
class IO(object):
@@ -23,29 +25,37 @@ def __init__(self, *args, **kwargs):
2325
"""
2426
if len(args) == 0:
2527
if not "file_prefix" in kwargs:
26-
raise Exception("You must specify either two file names or file_prefix.")
27-
28-
if "data_id" in kwargs:
29-
filename_prefix = "%s%d" % (kwargs["file_prefix"], kwargs["data_id"])
28+
self.file_flag = 0
29+
(fd, self.input_filename) = tempfile.mkstemp()
30+
os.close(fd)
31+
(fd, self.output_filename) = tempfile.mkstemp()
32+
os.close(fd)
3033
else:
31-
filename_prefix = kwargs["file_prefix"]
34+
self.file_flag = 2
35+
if "data_id" in kwargs:
36+
filename_prefix = "%s%d" % (kwargs["file_prefix"], kwargs["data_id"])
37+
else:
38+
filename_prefix = kwargs["file_prefix"]
3239

33-
input_suffix = kwargs.get("input_suffix", ".in")
34-
output_suffix = kwargs.get("output_suffix", ".out")
35-
disable_output = kwargs.get("disable_output", False)
36-
self.input_filename = filename_prefix + input_suffix
37-
self.output_filename = filename_prefix + output_suffix if not disable_output else None
40+
input_suffix = kwargs.get("input_suffix", ".in")
41+
output_suffix = kwargs.get("output_suffix", ".out")
42+
disable_output = kwargs.get("disable_output", False)
43+
self.input_filename = filename_prefix + input_suffix
44+
self.output_filename = filename_prefix + output_suffix if not disable_output else None
3845
elif len(args) == 1:
46+
self.file_flag = 1
3947
self.input_filename = args[0]
40-
self.output_filename = None
48+
(fd, self.output_filename) = tempfile.mkstemp()
49+
os.close(fd)
4150
elif len(args) == 2:
51+
self.file_flag = 2
4252
self.input_filename = args[0]
4353
self.output_filename = args[1]
4454
else:
4555
raise Exception("Invalid argument count")
4656

47-
self.input_file = open(self.input_filename, 'w')
48-
self.output_file = open(self.output_filename, 'w') if self.output_filename else None
57+
self.input_file = open(self.input_filename, 'w+')
58+
self.output_file = open(self.output_filename, 'w+') if self.output_filename else None
4959
self.is_first_char = dict()
5060
print("Processing %s" % self.input_filename)
5161

@@ -56,6 +66,10 @@ def __del__(self):
5666
try:
5767
self.input_file.close()
5868
self.output_file.close()
69+
if self.file_flag <= 1:
70+
os.remove(self.output_filename)
71+
if self.file_flag == 0:
72+
os.remove(self.input_filename)
5973
except Exception:
6074
pass
6175

@@ -69,6 +83,10 @@ def __exit__(self, exc_type, exc_val, exc_tb):
6983
try:
7084
self.input_file.close()
7185
self.output_file.close()
86+
if self.file_flag <= 1:
87+
os.remove(self.output_filename)
88+
if self.file_flag == 0:
89+
os.remove(self.input_filename)
7290
except Exception:
7391
pass
7492

@@ -118,7 +136,7 @@ def output_gen(self, shell_cmd):
118136
with open(self.input_filename, 'r') as f:
119137
self.output_file.write(subprocess.check_output(shell_cmd, shell=True, stdin=f).decode('ascii'))
120138

121-
self.input_file = open(self.input_filename, 'a')
139+
self.input_file = open(self.input_filename, 'a+')
122140
print(self.output_filename, " done")
123141

124142
def output_write(self, *args, **kwargs):
@@ -138,3 +156,6 @@ def output_writeln(self, *args, **kwargs):
138156
args = list(args)
139157
args.append("\n")
140158
self.output_write(*args, **kwargs)
159+
160+
def flush_buffer(self):
161+
self.input_file.flush()

cyaron/output_capture.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from contextlib import contextmanager
2+
from StringIO import StringIO
3+
import sys
4+
5+
@contextmanager
6+
def captured_output():
7+
new_out, new_err = StringIO(), StringIO()
8+
old_out, old_err = sys.stdout, sys.stderr
9+
try:
10+
sys.stdout, sys.stderr = new_out, new_err
11+
yield sys.stdout, sys.stderr
12+
finally:
13+
sys.stdout, sys.stderr = old_out, old_err

cyaron/tests/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .sequence_test import TestSequence
22
from .io_test import TestIO
33
from .str_test import TestString
4-
from .polygon_test import TestPolygon
4+
from .polygon_test import TestPolygon
5+
from .compare_test import TestCompare

0 commit comments

Comments
 (0)