diff --git a/onlinejudge/_implementation/command/download.py b/onlinejudge/_implementation/command/download.py index 78bb9a98..aaef6d35 100644 --- a/onlinejudge/_implementation/command/download.py +++ b/onlinejudge/_implementation/command/download.py @@ -11,6 +11,7 @@ import onlinejudge import onlinejudge._implementation.download_history +import onlinejudge._implementation.format_utils as format_utils import onlinejudge._implementation.logging as log import onlinejudge._implementation.utils as utils import onlinejudge.type @@ -19,12 +20,12 @@ import argparse -def convert_sample_to_dict(sample: onlinejudge.type.TestCase) -> dict: - data = {} - data["input"] = sample.input.data - data["output"] = sample.output.data - if sample.input.name == sample.output.name: - data["name"] = sample.input.name +def convert_sample_to_dict(sample: onlinejudge.type.TestCase) -> Dict[str, str]: + data = {} # type: Dict[str, str] + data["name"] = sample.name + data["input"] = sample.input_data.decode() + if sample.output_data is not None: + data["output"] = sample.output_data.decode() return data @@ -37,13 +38,7 @@ def download(args: 'argparse.Namespace') -> None: if args.directory is None: args.directory = pathlib.Path('test') if args.format is None: - if args.system: - if problem.get_service().get_name() == 'yukicoder': - args.format = '%b.%e' - else: - args.format = '%i.%e' - else: - args.format = 'sample-%i.%e' + args.format = '%b.%e' # get samples from the server with utils.with_cookiejar(utils.new_default_session(), path=args.cookie) as sess: @@ -65,17 +60,18 @@ def download(args: 'argparse.Namespace') -> None: for i, sample in enumerate(samples): log.emit('') log.info('sample %d', i) - for kind in ['input', 'output']: - ext = kind[:-3] - data = getattr(sample, kind).data - name = getattr(sample, kind).name + for ext in ['in', 'out']: + data = getattr(sample, ext + 'put_data') + if data is None: + continue + name = sample.name table = {} table['i'] = str(i + 1) table['e'] = ext table['n'] = name table['b'] = os.path.basename(name) table['d'] = os.path.dirname(name) - path = args.directory / utils.percentformat(args.format, table) # type: pathlib.Path + path = args.directory / format_utils.percentformat(args.format, table) # type: pathlib.Path log.status('%sput: %s', ext, name) if not args.silent: log.emit(utils.snip_large_file_content(data.rstrip(), limit=40, head=20, tail=10, bold=True)) @@ -87,7 +83,7 @@ def download(args: 'argparse.Namespace') -> None: log.warning('skipped') continue path.parent.mkdir(parents=True, exist_ok=True) - with path.open('w') as fh: + with path.open('wb') as fh: fh.write(data) log.success('saved to: %s', path) diff --git a/onlinejudge/_implementation/command/split_input.py b/onlinejudge/_implementation/command/split_input.py index c8938554..ed0f0b72 100644 --- a/onlinejudge/_implementation/command/split_input.py +++ b/onlinejudge/_implementation/command/split_input.py @@ -6,8 +6,8 @@ from typing.io import * import onlinejudge +import onlinejudge._implementation.format_utils as format_utils import onlinejudge._implementation.logging as log -import onlinejudge._implementation.utils as utils if TYPE_CHECKING: import argparse @@ -47,7 +47,7 @@ def split_input(args: 'argparse.Namespace') -> None: time.sleep(args.time) if non_block_read(proc.stdout): # if output exists index += 1 - path = utils.percentformat(args.output, {'i': str(index)}) + path = format_utils.percentformat(args.output, {'i': str(index)}) log.info('case found: %d', index) if args.header: if args.header == args.header.strip(): diff --git a/onlinejudge/_implementation/format_utils.py b/onlinejudge/_implementation/format_utils.py index 26154cfd..e5dc3d85 100644 --- a/onlinejudge/_implementation/format_utils.py +++ b/onlinejudge/_implementation/format_utils.py @@ -4,18 +4,55 @@ import pathlib import re import sys -from typing import Dict, List, Match, Optional +from typing import Dict, Generator, List, Match, Optional, Set import onlinejudge import onlinejudge._implementation.logging as log import onlinejudge._implementation.utils as utils +def percentsplit(s: str) -> Generator[str, None, None]: + for m in re.finditer('[^%]|%(.)', s): + yield m.group(0) + + +def percentformat(s: str, table: Dict[str, str]) -> str: + assert '%' not in table or table['%'] == '%' + table['%'] = '%' + result = '' + for c in percentsplit(s): + if c.startswith('%'): + result += table[c[1]] + else: + result += c + return result + + +def percentparse(s: str, format: str, table: Dict[str, str]) -> Optional[Dict[str, str]]: + table = {key: '(?P<{}>{})'.format(key, value) for key, value in table.items()} + used = set() # type: Set[str] + pattern = '' + for token in percentsplit(re.escape(format).replace('\\%', '%')): + if token.startswith('%'): + c = token[1] + if c not in used: + pattern += table[c] + used.add(c) + else: + pattern += r'(?P={})'.format(c) + else: + pattern += token + m = re.match(pattern, s) + if not m: + return None + return m.groupdict() + + def glob_with_format(directory: pathlib.Path, format: str) -> List[pathlib.Path]: table = {} table['s'] = '*' table['e'] = '*' - pattern = (glob.escape(str(directory)) + '/' + utils.percentformat(glob.escape(format).replace('\\%', '%'), table)) + pattern = (glob.escape(str(directory)) + '/' + percentformat(glob.escape(format).replace('\\%', '%'), table)) paths = list(map(pathlib.Path, glob.glob(pattern))) for path in paths: log.debug('testcase globbed: %s', path) @@ -26,7 +63,7 @@ def match_with_format(directory: pathlib.Path, format: str, path: pathlib.Path) table = {} table['s'] = '(?P.+)' table['e'] = '(?Pin|out)' - pattern = re.compile('^' + re.escape(str(directory.resolve())) + '/' + utils.percentformat(re.escape(format).replace('\\%', '%'), table) + '$') + pattern = re.compile('^' + re.escape(str(directory.resolve())) + '/' + percentformat(re.escape(format).replace('\\%', '%'), table) + '$') return pattern.match(str(path.resolve())) @@ -34,7 +71,7 @@ def path_from_format(directory: pathlib.Path, format: str, name: str, ext: str) table = {} table['s'] = name table['e'] = ext - return directory / utils.percentformat(format, table) + return directory / percentformat(format, table) def is_backup_or_hidden_file(path: pathlib.Path) -> bool: diff --git a/onlinejudge/_implementation/testcase_zipper.py b/onlinejudge/_implementation/testcase_zipper.py new file mode 100644 index 00000000..af7c6f4f --- /dev/null +++ b/onlinejudge/_implementation/testcase_zipper.py @@ -0,0 +1,62 @@ +# Python Version: 3.x +# -*- coding: utf-8 -*- +import collections +import io +import re +import zipfile +from typing import * + +import onlinejudge._implementation.format_utils +import onlinejudge._implementation.logging as log +from onlinejudge.type import * + + +class SampleZipper(object): + def __init__(self): + self.data = [] + self.dangling = None # Optional[Tuple(str, str)] + + def add(self, s: str, name: str = '') -> None: + if self.dangling is None: + if re.search('output', name, re.IGNORECASE) or re.search('出力', name): + log.warning('strange name for input string: %s', name) + self.dangling = (name, s) + else: + if re.search('input', name, re.IGNORECASE) or re.search('入力', name): + if not (re.search('output', name, re.IGNORECASE) or re.search('出力', name)): # to ignore titles like "Output for Sample Input 1" + log.warning('strange name for output string: %s', name) + index = len(self.data) + input_name, input_s = self.dangling + self.data += [TestCase('sample-{}'.format(index + 1), input_name, input_s.encode(), name, s.encode())] + self.dangling = None + + def get(self) -> List[TestCase]: + if self.dangling is not None: + log.error('dangling sample string: %s', self.dangling[1]) + return self.data + + +def extract_from_zip(zip_data: bytes, format: str, out: str = 'out') -> List[TestCase]: + table = { + 's': r'[^/]+', + 'e': r'(in|{})'.format(out), + } + names = collections.defaultdict(dict) # type: Dict[str, Dict[str, Tuple[str, bytes]]] + with zipfile.ZipFile(io.BytesIO(zip_data)) as fh: + for filename in fh.namelist(): + print(filename) + if filename.endswith('/'): + continue + m = onlinejudge._implementation.format_utils.percentparse(filename, format, table) + assert m + assert m['e'] not in names[m['s']] + names[m['s']][m['e']] = (filename, fh.read(filename)) + samples = [] # type: List[TestCase] + for name in sorted(names.keys()): + data = names[name] + if 'in' not in data or out not in data: + log.error('dangling sample found: %s', str(data)) + assert False + else: + samples += [TestCase(name, *data['in'], *data[out])] + return samples diff --git a/onlinejudge/_implementation/utils.py b/onlinejudge/_implementation/utils.py index 98fb0e64..66ea00d1 100644 --- a/onlinejudge/_implementation/utils.py +++ b/onlinejudge/_implementation/utils.py @@ -1,5 +1,4 @@ # Python Version: 3.x -# -*- coding: utf-8 -*- import contextlib import datetime import distutils.version @@ -23,7 +22,7 @@ import onlinejudge.__about__ as version import onlinejudge._implementation.logging as log -from onlinejudge.type import LabeledString, TestCase +from onlinejudge.type import * config_dir = pathlib.Path(appdirs.user_config_dir(version.__package_name__)) data_dir = pathlib.Path(appdirs.user_data_dir(version.__package_name__)) @@ -31,19 +30,6 @@ html_parser = 'lxml' -def percentformat(s: str, table: Dict[str, str]) -> str: - assert '%' not in table or table['%'] == '%' - table['%'] = '%' - result = '' - for m in re.finditer('[^%]|%(.)', s): - if m.group(1): - if m.group(1) in table: - result += table[m.group(1)] - else: - result += m.group(0) - return result - - def describe_status_code(status_code: int) -> str: return '{} {}'.format(status_code, http.client.responses[status_code]) @@ -84,29 +70,6 @@ def with_cookiejar(session: requests.Session, path: pathlib.Path = default_cooki path.chmod(0o600) # NOTE: to make secure a little bit -class SampleZipper(object): - def __init__(self): - self.data = [] - self.dangling = None - - def add(self, s: str, name: str = '') -> None: - if self.dangling is None: - if re.search('output', name, re.IGNORECASE) or re.search('出力', name): - log.warning('strange name for input string: %s', name) - self.dangling = LabeledString(name, s) - else: - if re.search('input', name, re.IGNORECASE) or re.search('入力', name): - if not (re.search('output', name, re.IGNORECASE) or re.search('出力', name)): # to ignore titles like "Output for Sample Input 1" - log.warning('strange name for output string: %s', name) - self.data += [TestCase(self.dangling, LabeledString(name, s))] - self.dangling = None - - def get(self) -> List[TestCase]: - if self.dangling is not None: - log.error('dangling sample string: %s', self.dangling[1]) - return self.data - - class FormSender(object): def __init__(self, form: bs4.Tag, url: str): assert isinstance(form, bs4.Tag) diff --git a/onlinejudge/service/anarchygolf.py b/onlinejudge/service/anarchygolf.py index 01f84e07..24a8f0ef 100644 --- a/onlinejudge/service/anarchygolf.py +++ b/onlinejudge/service/anarchygolf.py @@ -11,6 +11,7 @@ import requests import onlinejudge._implementation.logging as log +import onlinejudge._implementation.testcase_zipper import onlinejudge._implementation.utils as utils import onlinejudge.dispatch import onlinejudge.type @@ -44,7 +45,7 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L resp = utils.request('GET', self.get_url(), session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) - samples = utils.SampleZipper() + samples = onlinejudge._implementation.testcase_zipper.SampleZipper() for h2 in soup.find_all('h2'): it = self._parse_sample_tag(h2) if it is not None: diff --git a/onlinejudge/service/aoj.py b/onlinejudge/service/aoj.py index 75437d16..f08498ef 100644 --- a/onlinejudge/service/aoj.py +++ b/onlinejudge/service/aoj.py @@ -20,7 +20,7 @@ import onlinejudge._implementation.utils as utils import onlinejudge.dispatch import onlinejudge.type -from onlinejudge.type import LabeledString, TestCase +from onlinejudge.type import TestCase @utils.singleton @@ -53,10 +53,13 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L url = 'https://judgedat.u-aizu.ac.jp/testcases/samples/{}'.format(self.problem_id) resp = utils.request('GET', url, session=session) samples = [] # type: List[TestCase] - for sample in json.loads(resp.content.decode(resp.encoding)): + for i, sample in enumerate(json.loads(resp.content.decode(resp.encoding))): samples += [TestCase( - LabeledString(str(sample['serial']), sample['in']), - LabeledString(str(sample['serial']), sample['out']), + 'sample-{}'.format(i + 1), + str(sample['serial']), + sample['in'].encode(), + str(sample['serial']), + sample['out'].encode(), )] return samples @@ -85,8 +88,11 @@ def download_system_cases(self, session: Optional[requests.Session] = None) -> L log.warning("skipped due to the limitation of AOJ API") continue testcases += [TestCase( - LabeledString(header['name'], testcase['in']), - LabeledString(header['name'], testcase['out']), + header['name'], + header['name'], + testcase['in'].encode(), + header['name'], + testcase['out'].encode(), )] return testcases diff --git a/onlinejudge/service/atcoder.py b/onlinejudge/service/atcoder.py index 8a2113db..68da5de5 100644 --- a/onlinejudge/service/atcoder.py +++ b/onlinejudge/service/atcoder.py @@ -16,6 +16,7 @@ import requests import onlinejudge._implementation.logging as log +import onlinejudge._implementation.testcase_zipper import onlinejudge._implementation.utils as utils import onlinejudge.dispatch import onlinejudge.type @@ -319,7 +320,7 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L return [] # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) - samples = utils.SampleZipper() + samples = onlinejudge._implementation.testcase_zipper.SampleZipper() lang = None for pre, h3 in self._find_sample_tags(soup): s = utils.textfile(utils.dos2unix(pre.string.lstrip())) diff --git a/onlinejudge/service/codeforces.py b/onlinejudge/service/codeforces.py index f84d08b1..389a9630 100644 --- a/onlinejudge/service/codeforces.py +++ b/onlinejudge/service/codeforces.py @@ -13,6 +13,7 @@ import requests import onlinejudge._implementation.logging as log +import onlinejudge._implementation.testcase_zipper import onlinejudge._implementation.utils as utils import onlinejudge.dispatch import onlinejudge.type @@ -100,7 +101,7 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L resp = utils.request('GET', self.get_url(), session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) - samples = utils.SampleZipper() + samples = onlinejudge._implementation.testcase_zipper.SampleZipper() for tag in soup.find_all('div', class_=re.compile('^(in|out)put$')): # Codeforces writes very nice HTML :) log.debug('tag: %s', str(tag)) assert len(list(tag.children)) diff --git a/onlinejudge/service/csacademy.py b/onlinejudge/service/csacademy.py index f6512001..79f404d1 100644 --- a/onlinejudge/service/csacademy.py +++ b/onlinejudge/service/csacademy.py @@ -15,7 +15,7 @@ import onlinejudge._implementation.utils as utils import onlinejudge.dispatch import onlinejudge.type -from onlinejudge.type import LabeledString, TestCase +from onlinejudge.type import TestCase @utils.singleton @@ -93,8 +93,11 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L inname = 'Input {}'.format(test_number) outname = 'Output {}'.format(test_number) samples += [TestCase( - LabeledString(inname, example_test['input']), - LabeledString(outname, example_test['output']), + 'sample-{}'.format(test_number + 1), + inname, + example_test['input'].encode(), + outname, + example_test['output'].encode(), )] return samples diff --git a/onlinejudge/service/hackerrank.py b/onlinejudge/service/hackerrank.py index 540f56f2..3bd53d6e 100644 --- a/onlinejudge/service/hackerrank.py +++ b/onlinejudge/service/hackerrank.py @@ -17,6 +17,7 @@ import requests import onlinejudge._implementation.logging as log +import onlinejudge._implementation.testcase_zipper import onlinejudge._implementation.utils as utils import onlinejudge.dispatch import onlinejudge.type @@ -99,35 +100,13 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L def download_system_cases(self, session: Optional[requests.Session] = None) -> List[TestCase]: session = session or utils.new_default_session() - # get # example: https://www.hackerrank.com/rest/contests/hourrank-1/challenges/beautiful-array/download_testcases url = 'https://www.hackerrank.com/rest/contests/{}/challenges/{}/download_testcases'.format(self.contest_slug, self.challenge_slug) resp = utils.request('GET', url, session=session, raise_for_status=False) if resp.status_code != 200: log.error('response: %s', resp.content.decode()) return [] - # parse - with zipfile.ZipFile(io.BytesIO(resp.content)) as fh: - # list names - names = [] # type: List[str] - pattern = re.compile(r'(in|out)put/\1put(\d+).txt') - for filename in sorted(fh.namelist()): # "input" < "output" - if filename.endswith('/'): - continue - log.debug('filename: %s', filename) - m = pattern.match(filename) - assert m - if m.group(1) == 'in': - names += [m.group(2)] - # zip samples - samples = [] # type: List[TestCase] - for name in names: - inpath = 'input/input{}.txt'.format(name) - outpath = 'output/output{}.txt'.format(name) - indata = fh.read(inpath).decode() - outdata = fh.read(outpath).decode() - samples += [TestCase(LabeledString(inpath, indata), LabeledString(outpath, outdata))] - return samples + return onlinejudge._implementation.testcase_zipper.extract_from_zip(resp.content, '%eput/%eput%s.txt') def get_url(self) -> str: if self.contest_slug == 'master': diff --git a/onlinejudge/service/kattis.py b/onlinejudge/service/kattis.py index 8eacaa71..136e7b4c 100644 --- a/onlinejudge/service/kattis.py +++ b/onlinejudge/service/kattis.py @@ -13,10 +13,11 @@ import requests import onlinejudge._implementation.logging as log +import onlinejudge._implementation.testcase_zipper import onlinejudge._implementation.utils as utils import onlinejudge.dispatch import onlinejudge.type -from onlinejudge.type import LabeledString, TestCase +from onlinejudge.type import TestCase @utils.singleton @@ -60,17 +61,7 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L return [] resp.raise_for_status() # parse - with zipfile.ZipFile(io.BytesIO(resp.content)) as fh: - samples = [] # type: List[TestCase] - for filename in sorted(fh.namelist()): - log.debug('filename: %s', filename) - if filename.endswith('.in'): - inpath = filename - outpath = filename[:-3] + '.ans' - indata = fh.read(inpath).decode() - outdata = fh.read(outpath).decode() - samples += [TestCase(LabeledString(inpath, indata), LabeledString(outpath, outdata))] - return samples + return onlinejudge._implementation.testcase_zipper.extract_from_zip(resp.content, '%s.%e', out='ans') def get_url(self, contests: bool = True) -> str: if contests and self.contest_id is not None: diff --git a/onlinejudge/service/poj.py b/onlinejudge/service/poj.py index 0eaf9cd8..5a552ddb 100644 --- a/onlinejudge/service/poj.py +++ b/onlinejudge/service/poj.py @@ -13,7 +13,7 @@ import onlinejudge._implementation.utils as utils import onlinejudge.dispatch import onlinejudge.type -from onlinejudge.type import LabeledString, TestCase +from onlinejudge.type import TestCase @utils.singleton @@ -39,7 +39,7 @@ class POJProblem(onlinejudge.type.Problem): def __init__(self, problem_id: int): self.problem_id = problem_id - def download_sample_cases(self, session: Optional[requests.Session] = None) -> List[onlinejudge.type.TestCase]: + def download_sample_cases(self, session: Optional[requests.Session] = None) -> List[TestCase]: session = session or utils.new_default_session() # get resp = utils.request('GET', self.get_url(), session=session) @@ -57,7 +57,13 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L if len(in_pre.contents) == 1: assert isinstance(in_pre.contents[0], bs4.NavigableString) assert isinstance(out_pre.contents[0], bs4.NavigableString) - samples += [TestCase(LabeledString(in_p.text.strip(), in_pre.text + '\r\n'), LabeledString(out_p.text.strip(), out_pre.text + '\r\n'))] + samples += [TestCase( + 'sample', + in_p.text.strip(), + in_pre.text.encode() + b'\r\n', + out_p.text.strip(), + out_pre.text.encode() + b'\r\n', + )] else: assert len(in_pre.contents) % 2 == 0 for i in range(len(in_pre.contents) // 2): @@ -69,9 +75,13 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L assert isinstance(in_data, bs4.NavigableString) assert out_name.name == 'b' assert isinstance(out_data, bs4.NavigableString) - indata = LabeledString(in_name.text.strip(), str(in_data).strip() + '\r\n') - outdata = LabeledString(out_name.text.strip(), str(out_data).strip() + '\r\n') - samples += [TestCase(indata, outdata)] + samples += [TestCase( + 'sample-{}'.format(i + 1), + in_name.text.strip(), + str(in_data).strip().encode() + b'\r\n', + out_name.text.strip(), + str(out_data).strip().encode() + b'\r\n', + )] return samples def get_url(self) -> str: diff --git a/onlinejudge/service/toph.py b/onlinejudge/service/toph.py index 833b42e0..50e731d2 100644 --- a/onlinejudge/service/toph.py +++ b/onlinejudge/service/toph.py @@ -13,6 +13,7 @@ import requests import onlinejudge._implementation.logging as log +import onlinejudge._implementation.testcase_zipper import onlinejudge._implementation.utils as utils import onlinejudge.dispatch import onlinejudge.type @@ -91,7 +92,7 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L session = session or utils.new_default_session() resp = utils.request('GET', self.get_url(), session=session) soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) - samples = utils.SampleZipper() + samples = onlinejudge._implementation.testcase_zipper.SampleZipper() for case in soup.find('table', class_="samples").find('tbody').find_all('tr'): log.debug('case: %s', str(case)) assert len(list(case.children)) diff --git a/onlinejudge/service/yukicoder.py b/onlinejudge/service/yukicoder.py index 868b7c9f..60c1281f 100644 --- a/onlinejudge/service/yukicoder.py +++ b/onlinejudge/service/yukicoder.py @@ -11,13 +11,13 @@ import posixpath import re import urllib.parse -import zipfile from typing import * import bs4 import requests import onlinejudge._implementation.logging as log +import onlinejudge._implementation.testcase_zipper import onlinejudge._implementation.utils as utils import onlinejudge.dispatch from onlinejudge.type import * @@ -270,7 +270,7 @@ def download_sample_cases(self, session: Optional[requests.Session] = None) -> L resp = utils.request('GET', self.get_url(), session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) - samples = utils.SampleZipper() + samples = onlinejudge._implementation.testcase_zipper.SampleZipper() for pre in soup.find_all('pre'): log.debug('pre: %s', str(pre)) it = self._parse_sample_tag(pre) @@ -287,30 +287,10 @@ def download_system_cases(self, session: Optional[requests.Session] = None) -> L session = session or utils.new_default_session() if not self.get_service().is_logged_in(session=session): raise NotLoggedInError - - # get url = 'https://yukicoder.me/problems/no/{}/testcase.zip'.format(self.problem_no) resp = utils.request('GET', url, session=session) - # parse - basenames = collections.defaultdict(dict) # type: Dict[str, Dict[str, LabeledString]] - with zipfile.ZipFile(io.BytesIO(resp.content)) as fh: - for filename in sorted(fh.namelist()): # "test_in" < "test_out" - dirname = os.path.dirname(filename) - basename = os.path.basename(filename) - kind = {'test_in': 'input', 'test_out': 'output'}[dirname] - content = fh.read(filename).decode() - name = basename - if os.path.splitext(name)[1] == '.in': # ".in" extension is confusing - name = os.path.splitext(name)[0] - basenames[basename][kind] = LabeledString(name, content) - samples = [] # type: List[TestCase] - for basename in sorted(basenames.keys()): - data = basenames[basename] - if 'input' not in data or 'output' not in data or len(data) != 2: - log.error('dangling sample found: %s', str(data)) - else: - samples += [TestCase(data['input'], data['output'])] - return samples + fmt = 'test_%e/%s' + return onlinejudge._implementation.testcase_zipper.extract_from_zip(resp.content, fmt) def _parse_sample_tag(self, tag: bs4.Tag) -> Optional[Tuple[str, str]]: assert isinstance(tag, bs4.Tag) diff --git a/onlinejudge/type.py b/onlinejudge/type.py index d2a7be59..7f541d02 100644 --- a/onlinejudge/type.py +++ b/onlinejudge/type.py @@ -32,8 +32,13 @@ def from_url(self, s: str) -> Optional['Service']: pass -LabeledString = NamedTuple('LabeledString', [('name', str), ('data', str)]) -TestCase = NamedTuple('TestCase', [('input', LabeledString), ('output', LabeledString)]) +TestCase = NamedTuple('TestCase', [ + ('name', str), + ('input_name', str), + ('input_data', bytes), + ('output_name', Optional[str]), + ('output_data', Optional[bytes]), +]) LanguageId = NewType('LanguageId', str) """