2222from colorama import Fore , Style
2323
2424
25+ # Parse validation mode (only for legacy problem format version)
26+ def parse_legacy_validation (mode : str ) -> set [str ]:
27+ if mode == "default" :
28+ return {mode }
29+ else :
30+ ok = True
31+ parsed = set ()
32+ for part in mode .split ():
33+ if part in ["custom" , "interactive" , "multi-pass" ] and part not in parsed :
34+ parsed .add (part )
35+ else :
36+ ok = False
37+ if "custom" not in parsed or not ok :
38+ fatal (f"Unrecognised validation mode { mode } ." )
39+ return parsed
40+
41+
2542class ProblemLimits :
2643 def __init__ (
2744 self ,
@@ -104,7 +121,12 @@ def __init__(
104121
105122
106123class ProblemSettings :
107- def __init__ (self , yamldata : dict [str , Any ], legacy_time_limit : Optional [float ] = None ):
124+ def __init__ (
125+ self ,
126+ yamldata : dict [str , Any ],
127+ problem : "Problem" ,
128+ legacy_time_limit : Optional [float ] = None ,
129+ ):
108130 assert isinstance (yamldata , dict )
109131
110132 if "name" in yamldata and isinstance (yamldata ["name" ], str ):
@@ -120,7 +142,28 @@ def __init__(self, yamldata: dict[str, Any], legacy_time_limit: Optional[float]
120142 )
121143 if not self .is_legacy () and self .problem_format_version != "2023-07-draft" :
122144 fatal (f"problem_format_version { self .problem_format_version } not supported" )
123- # TODO: also support 'type'. For now, we only support legacy 'validation'.
145+
146+ if self .is_legacy ():
147+ mode = parse_legacy_validation (parse_setting (yamldata , "validation" , "default" ))
148+ else :
149+ if "validation" in yamldata :
150+ warn (
151+ "problem.yaml: 'validation' is removed in 2023-07-draft, please use 'type' instead"
152+ )
153+ mode = set (parse_setting (yamldata , "type" , "pass-fail" ).split (" " ))
154+ self .interactive : bool = "interactive" in mode
155+ self .multi_pass : bool = "multi-pass" in mode
156+ self .custom_output : bool = (
157+ self .interactive
158+ or self .multi_pass
159+ or (
160+ "custom" in mode
161+ if self .is_legacy ()
162+ # TODO #424: output_validator should be singular, but DOMjudge does not support this yet, so this should be fixed during export.
163+ else (problem .path / "output_validators" ).exists ()
164+ )
165+ )
166+
124167 self .name : dict [str , str ] = parse_setting (yamldata , "name" , {"en" : "" })
125168 self .uuid : str = parse_setting (yamldata , "uuid" , "" )
126169 self .author : str = parse_setting (yamldata , "author" , "" )
@@ -129,7 +172,6 @@ def __init__(self, yamldata: dict[str, Any], legacy_time_limit: Optional[float]
129172 self .license : str = parse_setting (yamldata , "license" , "unknown" )
130173 self .rights_owner : str = parse_setting (yamldata , "rights_owner" , "" )
131174 self .limits = ProblemLimits (parse_setting (yamldata , "limits" , {}), self , legacy_time_limit )
132- self .validation : str = parse_setting (yamldata , "validation" , "default" )
133175 self .validator_flags : list [str ] = parse_setting (yamldata , "validator_flags" , [])
134176 self .keywords : str = parse_setting (yamldata , "keywords" , "" )
135177
@@ -279,19 +321,20 @@ def _read_settings(self):
279321 yaml_path .write_text (raw )
280322 log ("Added new UUID to problem.yaml" )
281323
282- self .settings = ProblemSettings (yaml_data , self ._get_legacy_time_limit (yaml_data ))
283- self .limits = self .settings .limits
324+ self .settings = ProblemSettings (yaml_data , self , self ._get_legacy_time_limit (yaml_data ))
284325
285- mode = parse_validation (self .settings .validation )
286- self .interactive = "interactive" in mode
287- self .multipass = "multi-pass" in mode
326+ # Aliasing fields makes life easier for us 😛
327+ self .limits : ProblemLimits = self .settings .limits
328+ self .interactive : bool = self .settings .interactive
329+ self .multi_pass : bool = self .settings .multi_pass
330+ self .custom_output : bool = self .settings .custom_output
288331
289332 # Handle dependencies...
290333 has_validation_passes = self .limits .validation_passes is not None
291- if self .multipass and not has_validation_passes :
334+ if self .multi_pass and not has_validation_passes :
292335 self .limits .validation_passes = 2
293- if not self .multipass and has_validation_passes :
294- warn ("limit: validation_passes is only used for multipass problems. SKIPPED." )
336+ if not self .multi_pass and has_validation_passes :
337+ warn ("limit: validation_passes is only used for multi_pass problems. SKIPPED." )
295338
296339 def _parse_testdata_yaml (p , path , bar ):
297340 assert path .is_relative_to (p .path / "data" )
@@ -444,14 +487,14 @@ def testcases(
444487 for f in in_paths :
445488 t = testcase .Testcase (p , f , print_warn = True )
446489 if (
447- (p .interactive or p .multipass )
490+ (p .interactive or p .multi_pass )
448491 and mode == validate .Mode .INVALID
449492 and t .root in ["invalid_answers" , "invalid_outputs" ]
450493 ):
451494 msg = ""
452495 if p .interactive :
453496 msg += " interactive"
454- if p .multipass :
497+ if p .multi_pass :
455498 msg += " multi-pass"
456499 warn (f"Found file { f } for { mode } validation in{ msg } problem. Skipping." )
457500 continue
@@ -504,7 +547,7 @@ def statement_samples(p) -> list[Path | tuple[Path, Path]]:
504547 # Non-interactive and Non-multi-pass problems should not have .interaction files.
505548 # On the other hand, interactive problems are allowed to have .{in,ans}.statement files,
506549 # so that they can emulate a non-interactive problem with on-the-fly generated input.
507- if not p .interactive and not p .multipass :
550+ if not p .interactive and not p .multi_pass :
508551 if len (interaction_paths ) != 0 :
509552 warn (
510553 f"Non-interactive/Non-multi-pass problem { p .name } should not have data/sample/*.interaction files."
@@ -672,13 +715,18 @@ def _validators(
672715 return problem ._validators_cache [key ]
673716
674717 assert hasattr (cls , "source_dirs" )
718+ # TODO #424: We should not support multiple output validators inside output_validator/.
675719 paths = [p for source_dir in cls .source_dirs for p in glob (problem .path / source_dir , "*" )]
676720
677721 # Handle default output validation
678- if cls == validate .OutputValidator and problem . settings . validation == "default" :
679- if paths :
722+ if cls == validate .OutputValidator :
723+ if problem . settings . is_legacy () and not problem . custom_output and paths :
680724 error ("Validation is default but custom output validator exists (ignoring it)" )
681- paths = [config .TOOLS_ROOT / "support" / "default_output_validator.cpp" ]
725+ paths = []
726+ if not paths :
727+ if problem .custom_output :
728+ fatal ("Problem validation type requires output_validators/" )
729+ paths = [config .TOOLS_ROOT / "support" / "default_output_validator.cpp" ]
682730
683731 # TODO: Instead of checking file contents, maybe specify this in generators.yaml?
684732 def has_constraints_checking (f ):
@@ -906,12 +954,12 @@ def validate_data(problem, mode: validate.Mode, constraints: dict | bool | None
906954 constraints = {}
907955 assert constraints is None or isinstance (constraints , dict )
908956
909- if (problem .interactive or problem .multipass ) and mode == validate .Mode .ANSWER :
957+ if (problem .interactive or problem .multi_pass ) and mode == validate .Mode .ANSWER :
910958 if (problem .path / "answer_validators" ).exists ():
911959 msg = ""
912960 if problem .interactive :
913961 msg += " interactive"
914- if problem .multipass :
962+ if problem .multi_pass :
915963 msg += " multi-pass"
916964 log (f"Not running answer_validators for{ msg } problems." )
917965 return True
@@ -925,12 +973,12 @@ def validate_data(problem, mode: validate.Mode, constraints: dict | bool | None
925973 testcases = problem .testcases (mode = mode )
926974 case validate .Mode .ANSWER :
927975 assert not problem .interactive
928- assert not problem .multipass
976+ assert not problem .multi_pass
929977 problem .validators (validate .AnswerValidator , check_constraints = check_constraints )
930978 testcases = problem .testcases (mode = mode )
931979 case validate .Mode .INVALID :
932980 problem .validators (validate .InputValidator )
933- if not problem .interactive and not problem .multipass :
981+ if not problem .interactive and not problem .multi_pass :
934982 problem .validators (validate .AnswerValidator )
935983 testcases = problem .testcases (mode = mode )
936984 case _:
0 commit comments