Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
4176b2d
Add more types
mzuenni Oct 20, 2025
0e404e1
rework default solution
mzuenni Oct 21, 2025
4dc7f70
more typing
mzuenni Oct 21, 2025
9ab565a
Introduce config.Args class
mzuenni Oct 22, 2025
0a1ae16
fix use of bool variable and moved deprecation warning
mzuenni Oct 22, 2025
95699b1
enable strict
mzuenni Oct 22, 2025
ea1b02b
ignore code not used for bt
mzuenni Oct 22, 2025
95846fd
made parsing more lenient
mzuenni Oct 22, 2025
bf8e291
Update handling of default solution
mzuenni Oct 23, 2025
3a465df
fix
mzuenni Oct 23, 2025
a4fe580
use generator for counting testcases
mzuenni Oct 23, 2025
6b496d9
use ProgressBar
mzuenni Oct 23, 2025
974b0d7
cleanup
mzuenni Oct 23, 2025
228dfa8
empty keys should be None
mzuenni Oct 23, 2025
98507d9
Cleanup & fix tests
mzuenni Oct 23, 2025
e7b50ef
disallow star imports
mzuenni Oct 24, 2025
08563a2
Add doc/workflow.md
RagnarGrootKoerkamp Oct 29, 2025
17f26d4
remove * import
mzuenni Oct 24, 2025
3611cc7
Improve typing
mzuenni Oct 24, 2025
6ccf053
print => eprint to get rid of all file=sys.stderr
mzuenni Oct 24, 2025
7355dd8
removed unused file argument
mzuenni Oct 24, 2025
3a9752c
Code cleanup to reduce line wrapping
mzuenni Oct 24, 2025
5dbe71c
less flickering in verdicts :)
mzuenni Oct 24, 2025
e48d03a
use error instead of difference in wording
mzuenni Oct 24, 2025
be0ea54
Use PrintBar instead of 'message()' fn
mzuenni Oct 26, 2025
62cd22c
remove unreachable code
mzuenni Oct 27, 2025
fb453d0
fix interactive multipass logic
mzuenni Oct 27, 2025
0fe1ab4
Ruff sorts imports
mzuenni Oct 27, 2025
0f0eafa
automatically organize imports
mzuenni Oct 27, 2025
c805e4d
Warn if output validator ever crashes
mzuenni Oct 28, 2025
d5550fb
Review fixes/comments
RagnarGrootKoerkamp Oct 29, 2025
c6bfc97
Update testcase.py
mzuenni Oct 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
remove * import
  • Loading branch information
mzuenni authored and RagnarGrootKoerkamp committed Oct 29, 2025
commit 17f26d468cba5128a76f611b378bf9fb7e2df4bb
10 changes: 9 additions & 1 deletion bin/check_testing_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@
import parallel
from program import Program
from run import Submission
from util import *
from util import (
command_supports_memory_limit,
default_exec_code_map,
ensure_symlink,
error,
ExecResult,
ExecStatus,
ProgressBar,
)

if TYPE_CHECKING: # Prevent circular import: https://stackoverflow.com/a/39757388
from problem import Problem
Expand Down
4 changes: 1 addition & 3 deletions bin/constraints.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
import validate
from colorama import Fore, Style
from problem import Problem

# Local imports
from util import *
from util import error, log, warn

"""DISCLAIMER:

Expand Down
2 changes: 1 addition & 1 deletion bin/contest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path
from typing import cast, Any, Optional, TYPE_CHECKING

from util import *
from util import error, fatal, log, read_yaml, read_yaml_settings, verbose

if TYPE_CHECKING:
import requests
Expand Down
74 changes: 47 additions & 27 deletions bin/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,33 @@
from typing import Any, Optional

import config
import util

from contest import *
from contest import call_api, call_api_get_json, contest_yaml, get_contests
from latex import PdfType
from problem import Problem
from util import (
ask_variable_bool,
drop_suffix,
ensure_symlink,
error,
fatal,
glob,
has_ryaml,
has_substitute,
inc_label,
log,
message,
MessageType,
normalize_yaml_value,
parse_yaml,
PrintBar,
read_yaml,
ryaml_filter,
substitute,
verbose,
warn,
write_yaml,
)
from validate import InputValidator, AnswerValidator, OutputValidator
from visualize import InputVisualizer, OutputVisualizer

Expand Down Expand Up @@ -63,7 +85,7 @@ def build_samples_zip(problems: list[Problem], output: Path, languages: list[str

attachments_dir = problem.path / "attachments"
if (problem.interactive or problem.multi_pass) and not attachments_dir.is_dir():
util.error(
error(
f"{problem.settings.type_name()} problem {problem.name} does not have an attachments/ directory."
)
continue
Expand All @@ -82,13 +104,13 @@ def build_samples_zip(problems: list[Problem], output: Path, languages: list[str
if attachments_dir.is_dir():
for f in attachments_dir.iterdir():
if f.is_dir():
util.error(f"{f} directory attachments are not yet supported.")
error(f"{f} directory attachments are not yet supported.")
elif f.is_file() and f.exists():
if f.name.startswith("."):
continue # Skip dotfiles
destination = outputdir / f.name
if destination in contents:
util.error(
error(
f"Cannot overwrite {destination} from attachments/"
+ f" (sourced from {contents[destination]})."
+ "\n\tDo not include samples in attachments/,"
Expand All @@ -97,13 +119,13 @@ def build_samples_zip(problems: list[Problem], output: Path, languages: list[str
else:
contents[destination] = f
else:
util.error(f"Cannot include broken file {f}.")
error(f"Cannot include broken file {f}.")

if contents:
for destination, source in contents.items():
zf.write(source, destination)
else:
util.error(f"No attachments or samples found for problem {problem.name}.")
error(f"No attachments or samples found for problem {problem.name}.")

zf.close()
print("Wrote zip to samples.zip", file=sys.stderr)
Expand Down Expand Up @@ -158,25 +180,23 @@ def build_problem_zip(problem: Problem, output: Path) -> bool:

def add_file(path: Path, source: Path) -> None:
if source.stat().st_size >= config.ICPC_FILE_LIMIT * 1024**2:
util.warn(
f"{path} is too large for the ICPC Archive (limit {config.ICPC_FILE_LIMIT}MiB)!"
)
warn(f"{path} is too large for the ICPC Archive (limit {config.ICPC_FILE_LIMIT}MiB)!")
path = export_dir / path
path.parent.mkdir(parents=True, exist_ok=True)
ensure_symlink(path, source)

# Include all files beside testcases
for pattern, required in files:
# Only include hidden files if the pattern starts with a '.'.
paths = list(util.glob(problem.path, pattern, include_hidden=True))
paths = list(glob(problem.path, pattern, include_hidden=True))
if required and len(paths) == 0:
util.error(f"No matches for required path {pattern}.")
error(f"No matches for required path {pattern}.")
for f in paths:
if f.is_file() and not f.name.startswith("."):
add_file(f.relative_to(problem.path), f)

def add_testcase(in_file: Path) -> None:
base_name = util.drop_suffix(in_file, [".in", ".in.statement", ".in.download"])
base_name = drop_suffix(in_file, [".in", ".in.statement", ".in.download"])
for ext in config.KNOWN_DATA_EXTENSIONS:
f = base_name.with_suffix(ext)
if f.is_file():
Expand All @@ -185,21 +205,21 @@ def add_testcase(in_file: Path) -> None:
# Include all sample test cases and copy all related files.
samples = problem.download_samples()
if len(samples) == 0:
util.error("No samples found.")
error("No samples found.")
for in_file, _ in samples:
add_testcase(in_file)

# Include all secret test cases and copy all related files.
pattern = "data/secret/**/*.in"
paths = util.glob(problem.path, pattern)
paths = glob(problem.path, pattern)
if len(paths) == 0:
util.error(f"No secret test cases found in {pattern}.")
error(f"No secret test cases found in {pattern}.")
for f in paths:
if f.is_file():
if f.with_suffix(".ans").is_file():
add_testcase(f)
else:
util.warn(f"No answer file found for {f}, skipping.")
warn(f"No answer file found for {f}, skipping.")

# handle languages (files and yaml have to be in sync)
yaml_path = export_dir / "problem.yaml"
Expand Down Expand Up @@ -231,13 +251,13 @@ def add_testcase(in_file: Path) -> None:
]
for pattern in constants_supported:
for f in export_dir.glob(pattern):
if f.is_file() and util.has_substitute(f, config.CONSTANT_SUBSTITUTE_REGEX):
if f.is_file() and has_substitute(f, config.CONSTANT_SUBSTITUTE_REGEX):
text = f.read_text()
text = util.substitute(
text = substitute(
text,
problem.settings.constants,
pattern=config.CONSTANT_SUBSTITUTE_REGEX,
bar=util.PrintBar("Zip"),
bar=PrintBar("Zip"),
)
f.unlink()
f.write_text(text)
Expand All @@ -257,7 +277,7 @@ def add_testcase(in_file: Path) -> None:
if not file.exists():
continue
if out.exists():
util.warn(f"can't add {path} (already exists).")
warn(f"can't add {path} (already exists).")
file.unlink()
continue
out.parent.mkdir(parents=True, exist_ok=True)
Expand Down Expand Up @@ -289,7 +309,7 @@ def add_testcase(in_file: Path) -> None:
# change source:
if problem.settings.source:
if len(problem.settings.source) > 1:
util.warn(f"Found multiple sources, using '{problem.settings.source[0].name}'.")
warn(f"Found multiple sources, using '{problem.settings.source[0].name}'.")
yaml_data["source"] = problem.settings.source[0].name
yaml_data["source_url"] = problem.settings.source[0].url
# limits.time_multipliers -> time_multiplier / time_safety_margin
Expand Down Expand Up @@ -338,15 +358,15 @@ def add_testcase(in_file: Path) -> None:
f.unlink()
f.write_text(t)
else:
util.error(f"{f}: no name set for language {lang}.")
error(f"{f}: no name set for language {lang}.")

# rename statement dirs
if (export_dir / "statement").exists():
(export_dir / "statement").rename(export_dir / "problem_statement")
for d in ["solution", "problem_slide"]:
if not (export_dir / d).is_dir():
continue
for f in list(util.glob(problem.path, f"{d}/*")):
for f in list(glob(problem.path, f"{d}/*")):
if f.is_file():
out = Path("problem_statement") / f.relative_to(problem.path / d)
if out.exists():
Expand Down Expand Up @@ -503,7 +523,7 @@ def export_contest(cid: Optional[str]) -> str:
fatal(parse_yaml(r.text)["message"])
r.raise_for_status()

new_cid = util.normalize_yaml_value(yaml.load(r.text, Loader=yaml.SafeLoader), str)
new_cid = normalize_yaml_value(yaml.load(r.text, Loader=yaml.SafeLoader), str)
assert isinstance(new_cid, str)

log(f"Uploaded the contest to contest_id {new_cid}.")
Expand Down Expand Up @@ -672,7 +692,7 @@ def export_contest_and_problems(problems: list[Problem], languages: list[str]) -
if config.args.contest_id:
cid = config.args.contest_id
else:
cid = util.normalize_yaml_value(contest_yaml().get("contest_id"), str)
cid = normalize_yaml_value(contest_yaml().get("contest_id"), str)
assert isinstance(cid, str)
if cid is not None and cid != "":
log(f"Reusing contest id {cid} from contest.yaml")
Expand Down Expand Up @@ -708,7 +728,7 @@ def get_problem_id(problem: Problem) -> Optional[str]:
nonlocal ccs_problems
for p in ccs_problems:
if problem.name in [p.get("short_name"), p.get("id"), p.get("externalid")]:
pid = util.normalize_yaml_value(p.get("id"), str)
pid = normalize_yaml_value(p.get("id"), str)
assert isinstance(pid, str)
return pid
return None
Expand Down
12 changes: 11 additions & 1 deletion bin/fuzz.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@
from typing import Any, Optional, TextIO

import parallel
from util import *
from util import (
error,
fatal,
has_ryaml,
message,
MessageType,
ProgressBar,
read_yaml,
ryaml_get_or_add,
write_yaml,
)
from run import Run, Submission
from testcase import Testcase
from validate import OutputValidator, Mode
Expand Down
27 changes: 26 additions & 1 deletion bin/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,32 @@
from verdicts import Verdict
from problem import Problem

from util import *
from util import (
combine_hashes,
combine_hashes_dict,
ensure_symlink,
error,
ExecResult,
ExecStatus,
fatal,
get_basedirs,
glob,
hash_file_content,
hash_string,
has_ryaml,
is_relative_to,
log,
message,
MessageType,
path_size,
ProgressBar,
read_yaml,
ryaml_get_or_add,
shorten_path,
substitute,
warn,
write_yaml,
)


if has_ryaml:
Expand Down
12 changes: 11 additions & 1 deletion bin/interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@

import config
import validate
from util import *
from util import (
error,
ExecResult,
ExecStatus,
exec_command,
is_bsd,
is_windows,
limit_setter,
PrintBar,
ProgressBar,
)
from verdicts import Verdict

if TYPE_CHECKING:
Expand Down
29 changes: 27 additions & 2 deletions bin/problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import threading

from collections.abc import Callable, Sequence
from colorama import Fore, Style
from pathlib import Path
from typing import Any, Final, Literal, Optional, overload, TypeVar, TYPE_CHECKING

Expand All @@ -22,8 +23,32 @@
import validator_tests
import verdicts
import visualize
from util import *
from colorama import Fore, Style
from util import (
BAR_TYPE,
combine_hashes_dict,
drop_suffix,
error,
fatal,
generate_problem_uuid,
glob,
hash_file_content,
has_ryaml,
is_relative_to,
is_uuid,
log,
message,
parse_yaml,
PrintBar,
ProgressBar,
read_yaml,
read_yaml_settings,
resolve_path_argument,
ryaml_get_or_add,
substitute,
verbose,
warn,
write_yaml,
)


if has_ryaml:
Expand Down
19 changes: 18 additions & 1 deletion bin/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,24 @@
from typing import Any, Final, Optional, TypeVar, TYPE_CHECKING

import config
from util import *
from util import (
combine_hashes,
copy_and_substitute,
ensure_symlink,
error,
ExecResult,
ExecStatus,
exec_command,
fatal,
glob,
hash_file,
has_substitute,
ProgressBar,
read_yaml,
strip_newline,
warn,
write_yaml,
)

if TYPE_CHECKING: # Prevent circular import: https://stackoverflow.com/a/39757388
from problem import Problem
Expand Down
Loading