def main(args: Optional[List[str]] = None) -> None: log.addHandler(log.logging.StreamHandler(sys.stderr)) log.setLevel(log.logging.INFO) is_updated = update_checking.run() parser = get_parser() namespace = parser.parse_args(args=args) try: run_program(namespace, parser=parser) except NotImplementedError as e: log.debug('\n' + traceback.format_exc()) log.error('NotImplementedError') log.info( 'The operation you specified is not supported yet. Pull requests are welcome.' ) log.info( 'see: https://github.com/kmyk/online-judge-tools/blob/master/CONTRIBUTING.md' ) if not is_updated: log.info('hint: try updating the version of online-judge-tools') sys.exit(1) except Exception as e: log.debug('\n' + traceback.format_exc()) log.error(str(e)) if not is_updated: log.info('hint: try updating the version of online-judge-tools') sys.exit(1)
def exec_command(command_str: str, *, stdin: Optional[IO[Any]] = None, input: Optional[bytes] = None, timeout: Optional[float] = None, gnu_time: Optional[str] = None) -> Tuple[Dict[str, Any], subprocess.Popen]: if input is not None: assert stdin is None stdin = subprocess.PIPE # type: ignore if gnu_time is not None: context = tempfile.NamedTemporaryFile(delete=True) # type: Any else: context = contextlib.ExitStack() # TODO: we should use contextlib.nullcontext() if possible with context as fh: command = shlex.split(command_str) if gnu_time is not None: command = [gnu_time, '-f', '%M', '-o', fh.name, '--'] + command if os.name == 'nt': # HACK: without this encoding and decoding, something randomly fails with multithreading; see https://github.com/kmyk/online-judge-tools/issues/468 command = command_str.encode().decode() # type: ignore begin = time.perf_counter() # We need kill processes called from the "time" command using process groups. Without this, orphans spawn. see https://github.com/kmyk/online-judge-tools/issues/640 preexec_fn = None if gnu_time is not None and os.name == 'posix': preexec_fn = os.setsid try: proc = subprocess.Popen(command, stdin=stdin, stdout=subprocess.PIPE, stderr=sys.stderr, preexec_fn=preexec_fn) except FileNotFoundError: log.error('No such file or directory: %s', command) sys.exit(1) except PermissionError: log.error('Permission denied: %s', command) sys.exit(1) answer = None # type: Optional[bytes] try: answer, _ = proc.communicate(input=input, timeout=timeout) except subprocess.TimeoutExpired: pass finally: if preexec_fn is not None: try: os.killpg(os.getpgid(proc.pid), signal.SIGTERM) except ProcessLookupError: pass else: proc.terminate() end = time.perf_counter() memory = None # type: Optional[float] if gnu_time is not None: with open(fh.name) as fh1: reported = fh1.read() log.debug('GNU time says:\n%s', reported) if reported.strip() and reported.splitlines()[-1].isdigit(): memory = int(reported.splitlines()[-1]) / 1000 info = { 'answer': answer, # Optional[byte] 'elapsed': end - begin, # float, in second 'memory': memory, # Optional[float], in megabyte } return info, proc
def run() -> bool: """ :returns: :any:`True` if they are updated. """ try: is_updated = run_for_package(package_name=version.__package_name__, current_version=version.__version__) is_api_updated = run_for_package( package_name=api_version.__package_name__, current_version=api_version.__version__) return is_updated and is_api_updated except Exception as e: log.error('failed to check update: %s', e) return True
def get_latest_version_from_pypi(package_name: str) -> str: pypi_url = 'https://pypi.org/pypi/{}/json'.format(package_name) version_cache_path = user_cache_dir / "pypi.json" update_interval = 60 * 60 * 8 # 8 hours # load cache cache = {} # type: Dict[str, Any] if version_cache_path.exists(): try: log.debug('load the cache for update checking: %s', str(version_cache_path)) with version_cache_path.open() as fh: cache = json.load(fh) if time.time() < cache[package_name]['time'] + update_interval: return cache[package_name]['version'] except Exception as e: log.warning('failed to load the cache in update checking: %s', e) # get try: resp = request('GET', pypi_url, session=requests.Session()) data = json.loads(resp.content.decode()) value = data['info']['version'] except requests.RequestException as e: log.error(str(e)) value = '0.0.0' # ignore since this failure is not important cache[package_name] = { 'time': int( time.time() ), # use timestamp because Python's standard datetime library is too weak to parse strings 'version': value, } # store cache log.debug('store the cache for update checking: %s', str(version_cache_path)) version_cache_path.parent.mkdir(parents=True, exist_ok=True) with version_cache_path.open('w') as fh: json.dump(cache, fh) return value
def construct_relationship_of_files(paths: List[pathlib.Path], directory: pathlib.Path, format: str) -> Dict[str, Dict[str, pathlib.Path]]: tests = collections.defaultdict(dict) # type: Dict[str, Dict[str, pathlib.Path]] for path in paths: m = match_with_format(directory, format, path.resolve()) if not m: log.error('unrecognizable file found: %s', path) sys.exit(1) name = m.groupdict()['name'] ext = m.groupdict()['ext'] assert ext not in tests[name] tests[name][ext] = path for name in tests: if 'in' not in tests[name]: assert 'out' in tests[name] log.error('dangling output case: %s', tests[name]['out']) sys.exit(1) if not tests: log.error('no cases found') sys.exit(1) log.info('%d cases found', len(tests)) return tests
def submit(args: 'argparse.Namespace') -> None: # guess url history = onlinejudge_command.download_history.DownloadHistory() if args.file.parent.resolve() == pathlib.Path.cwd(): guessed_urls = history.get(directory=pathlib.Path.cwd()) else: log.warning('cannot guess URL since the given file is not in the current directory') guessed_urls = [] if args.url is None: if len(guessed_urls) == 1: args.url = guessed_urls[0] log.info('guessed problem: %s', args.url) else: log.error('failed to guess the URL to submit') log.info('please manually specify URL as: $ oj submit URL FILE') sys.exit(1) # parse url problem = dispatch.problem_from_url(args.url) if problem is None: sys.exit(1) # read code with args.file.open('rb') as fh: code = fh.read() # type: bytes format_config = { 'dos2unix': args.format_dos2unix or args.golf, 'rstrip': args.format_dos2unix or args.golf, } code = format_code(code, **format_config) # report code log.info('code (%d byte):', len(code)) log.emit(utils.make_pretty_large_file_content(code, limit=30, head=10, tail=10, bold=True)) with utils.new_session_with_our_user_agent(path=args.cookie) as sess: # guess or select language ids language_dict = {language.id: language.name for language in problem.get_available_languages(session=sess)} # type: Dict[LanguageId, str] matched_lang_ids = None # type: Optional[List[str]] if args.language in language_dict: matched_lang_ids = [args.language] else: if args.guess: kwargs = { 'language_dict': language_dict, 'cxx_latest': args.guess_cxx_latest, 'cxx_compiler': args.guess_cxx_compiler, 'python_version': args.guess_python_version, 'python_interpreter': args.guess_python_interpreter, } matched_lang_ids = guess_lang_ids_of_file(args.file, code, **kwargs) if not matched_lang_ids: log.info('failed to guess languages from the file name') matched_lang_ids = list(language_dict.keys()) if args.language is not None: log.info('you can use `--no-guess` option if you want to do an unusual submission') matched_lang_ids = select_ids_of_matched_languages(args.language.split(), matched_lang_ids, language_dict=language_dict) else: if args.language is None: matched_lang_ids = None else: matched_lang_ids = select_ids_of_matched_languages(args.language.split(), list(language_dict.keys()), language_dict=language_dict) # report selected language ids if matched_lang_ids is not None and len(matched_lang_ids) == 1: args.language = matched_lang_ids[0] log.info('chosen language: %s (%s)', args.language, language_dict[LanguageId(args.language)]) else: if matched_lang_ids is None: log.error('language is unknown') log.info('supported languages are:') elif len(matched_lang_ids) == 0: log.error('no languages are matched') log.info('supported languages are:') else: log.error('Matched languages were not narrowed down to one.') log.info('You have to choose:') for lang_id in sorted(matched_lang_ids or language_dict.keys()): log.emit('%s (%s)', lang_id, language_dict[LanguageId(lang_id)]) sys.exit(1) # confirm guessed_unmatch = ([problem.get_url()] != guessed_urls) if guessed_unmatch: samples_text = ('samples of "{}'.format('", "'.join(guessed_urls)) if guessed_urls else 'no samples') log.warning('the problem "%s" is specified to submit, but %s were downloaded in this directory. this may be mis-operation', problem.get_url(), samples_text) if args.wait: log.status('sleep(%.2f)', args.wait) time.sleep(args.wait) if not args.yes: if guessed_unmatch: problem_id = problem.get_url().rstrip('/').split('/')[-1].split('?')[-1] # this is too ad-hoc key = problem_id[:3] + (problem_id[-1] if len(problem_id) >= 4 else '') sys.stdout.write('Are you sure? Please type "{}" '.format(key)) sys.stdout.flush() c = sys.stdin.readline().rstrip() if c != key: log.info('terminated.') return else: sys.stdout.write('Are you sure? [y/N] ') sys.stdout.flush() c = sys.stdin.read(1) if c.lower() != 'y': log.info('terminated.') return # submit try: submission = problem.submit_code(code, language_id=LanguageId(args.language), session=sess) except NotLoggedInError: log.failure('login required') sys.exit(1) except SubmissionError: log.failure('submission failed') sys.exit(1) # show result if args.open: browser = webbrowser.get() log.status('open the submission page with browser') opened = browser.open_new_tab(submission.get_url()) if not opened: log.failure('failed to open the url. please set the $BROWSER envvar')