Skip to content

Commit b51ee1b

Browse files
committed
Allow sequence of arrays for problem types
See: https://www.kattis.com/problem-package-format/spec/2025-09.html#type
1 parent 9b0a0bf commit b51ee1b

3 files changed

Lines changed: 126 additions & 3 deletions

File tree

webapp/src/Entity/Problem.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,10 @@ public function setTypesAsString(array $types): Problem
331331
return $this;
332332
}
333333

334-
public function getTypesAsString(): string
334+
/**
335+
* @return list<string>
336+
*/
337+
public function getTypesAsStringArray(): array
335338
{
336339
$typeConstants = $this->getTypes();
337340
$typeStrings = [];
@@ -341,7 +344,12 @@ public function getTypesAsString(): string
341344
}
342345
$typeStrings[] = $this->typesToString[$type];
343346
}
344-
return implode(', ', $typeStrings);
347+
return $typeStrings;
348+
}
349+
350+
public function getTypesAsString(): string
351+
{
352+
return implode(', ', $this->getTypesAsStringArray());
345353
}
346354

347355
/**

webapp/src/Service/ImportProblemService.php

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,27 @@ public static function parseTestCaseGroupMeta(string $fileContent, string $name,
11991199
return $testcaseGroup;
12001200
}
12011201

1202+
/**
1203+
* Allow to import the types both as arrays and as strings. Due
1204+
* to the recursion to make parsing of the strings easier this now
1205+
* also works for arrays inside arrays.
1206+
*
1207+
* @param string|array<mixed, mixed> $input
1208+
* @return string[]
1209+
*/
1210+
private static function parseTypes(string|array $input): array
1211+
{
1212+
$final = [];
1213+
if (is_array($input)) {
1214+
foreach ($input as $possibleType) {
1215+
$final = array_merge($final, self::parseTypes($possibleType));
1216+
}
1217+
} else {
1218+
$final = array_merge($final, preg_split("/[\s,;]+/", $input));
1219+
}
1220+
return $final;
1221+
}
1222+
12021223
/**
12031224
* Returns true iff the yaml could be parsed correctly.
12041225
*
@@ -1235,7 +1256,7 @@ public static function parseYaml(bool|string $problemYaml, array &$messages, str
12351256

12361257
$validationMode = 'default';
12371258
if (isset($yamlData['type'])) {
1238-
$types = explode(' ', $yamlData['type']);
1259+
$types = self::parseTypes($yamlData['type']);
12391260
// Validation happens later when we set the properties.
12401261
$yamlProblemProperties['typesAsString'] = $types;
12411262
if (in_array('interactive', $types)) {

webapp/tests/Unit/Service/ImportProblemServiceTest.php

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,53 @@ public function testTypesYamlTest(): void
133133
}
134134
}
135135

136+
#[DataProvider('provideAlternativeTypeNotations')]
137+
public function testTypesStringWithAlternativeChars(string $separator): void
138+
{
139+
$expectedTypes = ['pass-fail', 'interactive'];
140+
$typesAsString = implode($separator, $expectedTypes);
141+
$specVersion = 'draft';
142+
$yaml = <<<YAML
143+
name: test
144+
problem_format_version: $specVersion
145+
type: $typesAsString
146+
YAML;
147+
148+
$messages = [];
149+
$validationMode = 'xxx';
150+
$problem = new Problem();
151+
152+
$ret = ImportProblemService::parseYaml($yaml, $messages, $validationMode, PropertyAccess::createPropertyAccessor(), $problem);
153+
$messageString = var_export($messages, true);
154+
$this->assertTrue($ret, 'Parsing failed for type: ' . $typesAsString . ', messages: ' . $messageString);
155+
$problemTypes = $problem->getTypesAsStringArray();
156+
$this->assertEquals(
157+
$expectedTypes, $problemTypes,
158+
'Found: "' . implode(' ', $problemTypes) . '" vs Expected: "' . implode(' ', $expectedTypes) . '"'
159+
);
160+
}
161+
162+
/**
163+
* @param string[] $expectedTypes
164+
*/
165+
#[DataProvider('provideAlternativeArrayNotations')]
166+
public function testTypesSequenceStrings(string $yaml, array $expectedTypes): void
167+
{
168+
$messages = [];
169+
$validationMode = 'xxx';
170+
$problem = new Problem();
171+
$typesAsString = implode(', ', $expectedTypes);
172+
173+
$ret = ImportProblemService::parseYaml($yaml, $messages, $validationMode, PropertyAccess::createPropertyAccessor(), $problem);
174+
$messageString = var_export($messages, true);
175+
$this->assertTrue($ret, 'Parsing failed for type: ' . $typesAsString . ', messages: ' . $messageString);
176+
$problemTypes = $problem->getTypesAsStringArray();
177+
$this->assertEquals(
178+
$expectedTypes, $problemTypes,
179+
'Found: "' . implode(' ', $problemTypes) . '" vs Expected: "' . implode(' ', $expectedTypes) . '"'
180+
);
181+
}
182+
136183
public function testUnknownProblemType(): void
137184
{
138185
$yaml = <<<YAML
@@ -721,4 +768,51 @@ public static function problemSpecVersionProvider(): Generator
721768
yield ['icpc-legacy'];
722769
yield ['2025-09'];
723770
}
771+
772+
773+
public static function provideAlternativeTypeNotations(): Generator
774+
{
775+
yield ["\t"];
776+
yield [", "];
777+
yield ["; "];
778+
}
779+
780+
public static function provideAlternativeArrayNotations(): Generator
781+
{
782+
$specVersion = 'draft';
783+
$yamlBasic = <<<YAML
784+
name: test
785+
problem_format_version: $specVersion
786+
YAML;
787+
$simpleArray = <<<YAML
788+
$yamlBasic
789+
type:
790+
- pass-fail
791+
YAML;
792+
$mappedArray = <<<YAML
793+
$yamlBasic
794+
type:
795+
pass-fail: pass-fail
796+
YAML;
797+
$oneLineArray = <<<YAML
798+
$yamlBasic
799+
type: [pass-fail]
800+
YAML;
801+
$malformedArray = <<<YAML
802+
$yamlBasic
803+
type: [pass-fail, [multi-pass, interactive]]
804+
YAML;
805+
$combinedStringArray = <<<YAML
806+
$yamlBasic
807+
type:
808+
- pass-fail
809+
- multi-pass, interactive
810+
YAML;
811+
812+
foreach ([$simpleArray, $mappedArray, $oneLineArray] as $yamlFile) {
813+
yield [$yamlFile, ['pass-fail']];
814+
}
815+
yield [$malformedArray, ['pass-fail', 'multi-pass', 'interactive']];
816+
yield [$combinedStringArray, ['pass-fail', 'multi-pass', 'interactive']];
817+
}
724818
}

0 commit comments

Comments
 (0)