Skip to content

Commit 2b6eea2

Browse files
Move comparing to a service so we can reuse it (and test it)
1 parent 78ccfa8 commit 2b6eea2

10 files changed

+437
-370
lines changed

webapp/src/Command/AbstractCompareCommand.php

Lines changed: 28 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,28 @@
22

33
namespace App\Command;
44

5+
use App\Service\Compare\AbstractCompareService;
6+
use App\Service\Compare\Message;
7+
use App\Service\Compare\MessageType;
58
use Symfony\Component\Console\Command\Command;
69
use Symfony\Component\Console\Input\InputArgument;
710
use Symfony\Component\Console\Input\InputInterface;
811
use Symfony\Component\Console\Output\OutputInterface;
912
use Symfony\Component\Console\Style\SymfonyStyle;
1013
use Symfony\Component\Serializer\SerializerInterface;
1114

15+
/**
16+
* @template T
17+
*/
1218
abstract class AbstractCompareCommand extends Command
1319
{
14-
public function __construct(protected readonly SerializerInterface $serializer)
15-
{
20+
/**
21+
* @param AbstractCompareService<T> $compareService
22+
*/
23+
public function __construct(
24+
protected readonly SerializerInterface $serializer,
25+
protected AbstractCompareService $compareService
26+
) {
1627
parent::__construct();
1728
}
1829

@@ -26,57 +37,13 @@ protected function configure(): void
2637
protected function execute(InputInterface $input, OutputInterface $output): int
2738
{
2839
$style = new SymfonyStyle($input, $output);
29-
$messages = [];
30-
$success = true;
31-
if (!file_exists($input->getArgument('file1'))) {
32-
$this->addMessage($messages, $success, 'error', sprintf('File "%s" does not exist', $input->getArgument('file1')));
33-
}
34-
if (!file_exists($input->getArgument('file2'))) {
35-
$this->addMessage($messages, $success, 'error', sprintf('File "%s" does not exist', $input->getArgument('file2')));
36-
}
37-
if (!$success) {
38-
return $this->displayMessages($style, $messages);
39-
}
40-
41-
$this->compare($messages, $success, $input->getArgument('file1'), $input->getArgument('file2'));
40+
$messages = $this->compareService->compareFiles($input->getArgument('file1'), $input->getArgument('file2'));
4241

4342
return $this->displayMessages($style, $messages) ?? Command::SUCCESS;
4443
}
4544

4645
/**
47-
* @param array<array{type: string, message: string, source: ?string, target: ?string}> $messages
48-
*/
49-
abstract protected function compare(
50-
array &$messages,
51-
bool &$success,
52-
string $file1,
53-
string $file2,
54-
): void;
55-
56-
/**
57-
* @param array<array{type: string, message: string, source: ?string, target: ?string}> $messages
58-
*/
59-
protected function addMessage(
60-
array &$messages,
61-
bool &$success,
62-
string $type,
63-
string $message,
64-
?string $source = null,
65-
?string $target = null,
66-
): void {
67-
$messages[] = [
68-
'type' => $type,
69-
'message' => $message,
70-
'source' => $source,
71-
'target' => $target,
72-
];
73-
if ($type === 'error') {
74-
$success = false;
75-
}
76-
}
77-
78-
/**
79-
* @param array<array{type: string, message: string, source: ?string, target: ?string}> $messages
46+
* @param Message[] $messages
8047
*/
8148
protected function displayMessages(SymfonyStyle $style, array $messages): ?int
8249
{
@@ -89,22 +56,22 @@ protected function displayMessages(SymfonyStyle $style, array $messages): ?int
8956
$rows = [];
9057
$counts = [];
9158
foreach ($messages as $message) {
92-
if (!isset($counts[$message['type']])) {
93-
$counts[$message['type']] = 0;
59+
if (!isset($counts[$message->type->value])) {
60+
$counts[$message->type->value] = 0;
9461
}
95-
$counts[$message['type']]++;
62+
$counts[$message->type->value]++;
9663
$rows[] = [
97-
$this->formatMessage($message['type'], $message['type']),
98-
$this->formatMessage($message['type'], $message['message']),
99-
$this->formatMessage($message['type'], $message['source'] ?? ''),
100-
$this->formatMessage($message['type'], $message['target'] ?? ''),
64+
$this->formatMessage($message->type, $message->type->value),
65+
$this->formatMessage($message->type, $message->message),
66+
$this->formatMessage($message->type, $message->source ?? ''),
67+
$this->formatMessage($message->type, $message->target ?? ''),
10168
];
10269
}
10370
$style->table($headers, $rows);
10471

10572
$style->newLine();
10673
foreach ($counts as $type => $count) {
107-
$style->writeln($this->formatMessage($type, sprintf('Found %d %s(s)', $count, $type)));
74+
$style->writeln($this->formatMessage(MessageType::from($type), sprintf('Found %d %s(s)', $count, $type)));
10875
}
10976

11077
if (isset($counts['error'])) {
@@ -117,13 +84,13 @@ protected function displayMessages(SymfonyStyle $style, array $messages): ?int
11784
return null;
11885
}
11986

120-
protected function formatMessage(string $level, string $message): string
87+
protected function formatMessage(MessageType $level, string $message): string
12188
{
12289
$colors = [
123-
'error' => 'red',
124-
'warning' => 'yellow',
125-
'info' => 'green',
90+
MessageType::ERROR->value => 'red',
91+
MessageType::WARNING->value => 'yellow',
92+
MessageType::INFO->value => 'green',
12693
];
127-
return sprintf('<fg=%s>%s</>', $colors[$level], $message);
94+
return sprintf('<fg=%s>%s</>', $colors[$level->value], $message);
12895
}
12996
}

webapp/src/Command/CompareAwardsCommand.php

Lines changed: 10 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,80 +3,23 @@
33
namespace App\Command;
44

55
use App\DataTransferObject\Award;
6+
use App\Service\Compare\AwardCompareService;
67
use Symfony\Component\Console\Attribute\AsCommand;
7-
use Symfony\Component\Serializer\Exception\ExceptionInterface;
8+
use Symfony\Component\Serializer\SerializerInterface;
89

10+
/**
11+
* @extends AbstractCompareCommand<Award[]>
12+
*/
913
#[AsCommand(
1014
name: 'compare:awards',
1115
description: 'Compare awards between two files'
1216
)]
1317
class CompareAwardsCommand extends AbstractCompareCommand
1418
{
15-
protected function compare(
16-
array &$messages,
17-
bool &$success,
18-
string $file1,
19-
string $file2,
20-
): void {
21-
try {
22-
/** @var Award[] $awards1 */
23-
$awards1 = $this->serializer->deserialize(file_get_contents($file1), Award::class . '[]', 'json');
24-
} catch (ExceptionInterface $e) {
25-
$this->addMessage($messages, $success, 'error', sprintf('Error deserializing file "%s": %s', $file1, $e->getMessage()));
26-
}
27-
try {
28-
/** @var Award[] $awards2 */
29-
$awards2 = $this->serializer->deserialize(file_get_contents($file2), Award::class . '[]', 'json');
30-
} catch (ExceptionInterface $e) {
31-
$this->addMessage($messages, $success, 'error', sprintf('Error deserializing file "%s": %s', $file2, $e->getMessage()));
32-
}
33-
34-
if (!$success || !isset($awards1) || !isset($awards2)) {
35-
return;
36-
}
37-
38-
/** @var array<string,Award> $awards1Indexed */
39-
$awards1Indexed = [];
40-
foreach ($awards1 as $award) {
41-
$awards1Indexed[$award->id] = $award;
42-
}
43-
44-
/** @var array<string,Award> $awards2Indexed */
45-
$awards2Indexed = [];
46-
foreach ($awards2 as $award) {
47-
$awards2Indexed[$award->id] = $award;
48-
}
49-
50-
foreach ($awards1Indexed as $awardId => $award) {
51-
if (!isset($awards2Indexed[$awardId])) {
52-
if (!$award->teamIds) {
53-
$this->addMessage($messages, $success, 'info', sprintf('Award "%s" not found in second file, but has no team ID\'s in first file', $awardId));
54-
} else {
55-
$this->addMessage($messages, $success, 'error', sprintf('Award "%s" not found in second file', $awardId));
56-
}
57-
} else {
58-
$award2 = $awards2Indexed[$awardId];
59-
if ($award->citation !== $award2->citation) {
60-
$this->addMessage($messages, $success, 'warning', sprintf('Award "%s" has different citation', $awardId), $award->citation, $award2->citation);
61-
}
62-
$award1TeamIds = $award->teamIds;
63-
sort($award1TeamIds);
64-
$award2TeamIds = $award2->teamIds;
65-
sort($award2TeamIds);
66-
if ($award1TeamIds !== $award2TeamIds) {
67-
$this->addMessage($messages, $success, 'error', sprintf('Award "%s" has different team ID\'s', $awardId), implode(', ', $award->teamIds), implode(', ', $award2->teamIds));
68-
}
69-
}
70-
}
71-
72-
foreach ($awards2Indexed as $awardId => $award) {
73-
if (!isset($awards1Indexed[$awardId])) {
74-
if (!$award->teamIds) {
75-
$this->addMessage($messages, $success, 'info', sprintf('Award "%s" not found in first file, but has no team ID\'s in second file', $awardId));
76-
} else {
77-
$this->addMessage($messages, $success, 'error', sprintf('Award "%s" not found in first file', $awardId));
78-
}
79-
}
80-
}
19+
public function __construct(
20+
SerializerInterface $serializer,
21+
AwardCompareService $compareService
22+
) {
23+
parent::__construct($serializer, $compareService);
8124
}
8225
}

webapp/src/Command/CompareResultsCommand.php

Lines changed: 10 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -3,117 +3,23 @@
33
namespace App\Command;
44

55
use App\DataTransferObject\Result;
6+
use App\Service\Compare\ResultsCompareService;
67
use Symfony\Component\Console\Attribute\AsCommand;
7-
use Symfony\Component\Serializer\Encoder\CsvEncoder;
8-
use Symfony\Component\Serializer\Exception\ExceptionInterface;
8+
use Symfony\Component\Serializer\SerializerInterface;
99

10+
/**
11+
* @extends AbstractCompareCommand<Result[]>
12+
*/
1013
#[AsCommand(
1114
name: 'compare:results',
1215
description: 'Compare results between two files'
1316
)]
1417
class CompareResultsCommand extends AbstractCompareCommand
1518
{
16-
17-
protected function compare(
18-
array &$messages,
19-
bool &$success,
20-
string $file1,
21-
string $file2,
22-
): void {
23-
$results1Contents = file_get_contents($file1);
24-
if (!str_starts_with($results1Contents, "results\t1")) {
25-
$this->addMessage($messages, $success, 'error', sprintf("File \"%s\" does not start with \"results\t1\"", $file1));
26-
}
27-
$results2Contents = file_get_contents($file2);
28-
if (!str_starts_with($results2Contents, "results\t1")) {
29-
$this->addMessage($messages, $success, 'error', sprintf("File \"%s\" does not start with \"results\t1\"", $file2));
30-
}
31-
32-
if (!$success) {
33-
return;
34-
}
35-
36-
$results1Contents = substr($results1Contents, strpos($results1Contents, "\n") + 1);
37-
$results2Contents = substr($results2Contents, strpos($results2Contents, "\n") + 1);
38-
39-
// Prefix both files with a fake header, so we can deserialize them
40-
$results1Contents = "team_id\trank\taward\tnum_solved\ttotal_time\tlast_time\tgroup_winner\n" . $results1Contents;
41-
$results2Contents = "team_id\trank\taward\tnum_solved\ttotal_time\tlast_time\tgroup_winner\n" . $results2Contents;
42-
43-
try {
44-
/** @var Result[] $results1 */
45-
$results1 = $this->serializer->deserialize($results1Contents, Result::class . '[]', 'csv', [
46-
CsvEncoder::DELIMITER_KEY => "\t",
47-
]);
48-
} catch (ExceptionInterface $e) {
49-
$this->addMessage($messages, $success, 'error', sprintf('Error deserializing file "%s": %s', $file1, $e->getMessage()));
50-
}
51-
52-
try {
53-
/** @var Result[] $results2 */
54-
$results2 = $this->serializer->deserialize($results2Contents, Result::class . '[]', 'csv', [
55-
CsvEncoder::DELIMITER_KEY => "\t",
56-
]);
57-
} catch (ExceptionInterface $e) {
58-
$this->addMessage($messages, $success, 'error', sprintf('Error deserializing file "%s": %s', $file2, $e->getMessage()));
59-
}
60-
61-
if (!$success || !isset($results1) || !isset($results2)) {
62-
return;
63-
}
64-
65-
// Sort results for both files: first by num_solved, then by total_time
66-
usort($results1, fn(
67-
Result $a,
68-
Result $b
69-
) => $a->numSolved === $b->numSolved ? $a->totalTime <=> $b->totalTime : $b->numSolved <=> $a->numSolved);
70-
usort($results2, fn(
71-
Result $a,
72-
Result $b
73-
) => $a->numSolved === $b->numSolved ? $a->totalTime <=> $b->totalTime : $b->numSolved <=> $a->numSolved);
74-
75-
/** @var array<string,Result> $results1Indexed */
76-
$results1Indexed = [];
77-
foreach ($results1 as $result) {
78-
$results1Indexed[$result->teamId] = $result;
79-
}
80-
81-
/** @var array<string,Result> $results2Indexed */
82-
$results2Indexed = [];
83-
foreach ($results2 as $result) {
84-
$results2Indexed[$result->teamId] = $result;
85-
}
86-
87-
foreach ($results1 as $result) {
88-
if (!isset($results2Indexed[$result->teamId])) {
89-
$this->addMessage($messages, $success, 'error', sprintf('Team "%s" not found in second file', $result->teamId));
90-
} else {
91-
$result2 = $results2Indexed[$result->teamId];
92-
if ($result->rank !== $result2->rank) {
93-
$this->addMessage($messages, $success, 'error', sprintf('Team %s has different rank', $result->teamId), (string)$result->rank, (string)$result2->rank);
94-
}
95-
if ($result->award !== $result2->award) {
96-
$this->addMessage($messages, $success, 'error', sprintf('Team %s has different award', $result->teamId), $result->award, $result2->award);
97-
}
98-
if ($result->numSolved !== $result2->numSolved) {
99-
$this->addMessage($messages, $success, 'error', sprintf('Team %s has different num_solved', $result->teamId), (string)$result->numSolved, (string)$result2->numSolved);
100-
}
101-
if ($result->totalTime !== $result2->totalTime) {
102-
$this->addMessage($messages, $success, 'error', sprintf('Team %s has different total_time', $result->teamId), (string)$result->totalTime, (string)$result2->totalTime);
103-
}
104-
if ($result->lastTime !== $result2->lastTime) {
105-
$this->addMessage($messages, $success, 'error', sprintf('Team %s has different last_time', $result->teamId), (string)$result->lastTime, (string)$result2->lastTime);
106-
}
107-
if ($result->groupWinner !== $result2->groupWinner) {
108-
$this->addMessage($messages, $success, 'warning', sprintf('Team %s has different group_winner', $result->teamId), (string)$result->groupWinner, (string)$result2->groupWinner);
109-
}
110-
}
111-
}
112-
113-
foreach ($results2 as $result) {
114-
if (!isset($results1Indexed[$result->teamId])) {
115-
$this->addMessage($messages, $success, 'error', sprintf('Team %s not found in first file', $result->teamId));
116-
}
117-
}
19+
public function __construct(
20+
SerializerInterface $serializer,
21+
ResultsCompareService $compareService
22+
) {
23+
parent::__construct($serializer, $compareService);
11824
}
11925
}

0 commit comments

Comments
 (0)