feat: add qualifying results text parser with tests
This commit is contained in:
parent
6c824f9365
commit
6988b8c1c0
2 changed files with 147 additions and 0 deletions
96
app/lib/__tests__/parse-results-text.test.ts
Normal file
96
app/lib/__tests__/parse-results-text.test.ts
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { parseResultsText } from "../parse-results-text";
|
||||||
|
|
||||||
|
describe("parseResultsText", () => {
|
||||||
|
describe("dot separator", () => {
|
||||||
|
it("parses '1. Player Name'", () => {
|
||||||
|
expect(parseResultsText("1. Gerwyn Price")).toEqual([
|
||||||
|
{ placement: 1, rawName: "Gerwyn Price" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("comma separator", () => {
|
||||||
|
it("parses '1, Player Name'", () => {
|
||||||
|
expect(parseResultsText("1, Gerwyn Price")).toEqual([
|
||||||
|
{ placement: 1, rawName: "Gerwyn Price" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("space-only separator", () => {
|
||||||
|
it("parses '1 Player Name'", () => {
|
||||||
|
expect(parseResultsText("1 Gerwyn Price")).toEqual([
|
||||||
|
{ placement: 1, rawName: "Gerwyn Price" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("T-prefix ties", () => {
|
||||||
|
it("parses 'T3. Player Name' as placement 3", () => {
|
||||||
|
expect(parseResultsText("T3. Luke Littler")).toEqual([
|
||||||
|
{ placement: 3, rawName: "Luke Littler" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses 'T3, Player Name' as placement 3", () => {
|
||||||
|
expect(parseResultsText("T3, Luke Littler")).toEqual([
|
||||||
|
{ placement: 3, rawName: "Luke Littler" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses two T-prefix lines with same rank as both placement 3", () => {
|
||||||
|
const input = "T3. Luke Littler\nT3. Michael van Gerwen";
|
||||||
|
expect(parseResultsText(input)).toEqual([
|
||||||
|
{ placement: 3, rawName: "Luke Littler" },
|
||||||
|
{ placement: 3, rawName: "Michael van Gerwen" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("repeated-rank ties", () => {
|
||||||
|
it("parses two lines with the same rank number as both tied", () => {
|
||||||
|
const input = "3. Luke Littler\n3. Michael van Gerwen";
|
||||||
|
expect(parseResultsText(input)).toEqual([
|
||||||
|
{ placement: 3, rawName: "Luke Littler" },
|
||||||
|
{ placement: 3, rawName: "Michael van Gerwen" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("blank lines and whitespace", () => {
|
||||||
|
it("ignores blank lines", () => {
|
||||||
|
const input = "1. Gerwyn Price\n\n2. Luke Littler";
|
||||||
|
expect(parseResultsText(input)).toEqual([
|
||||||
|
{ placement: 1, rawName: "Gerwyn Price" },
|
||||||
|
{ placement: 2, rawName: "Luke Littler" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("trims leading and trailing whitespace from names", () => {
|
||||||
|
expect(parseResultsText("1. Gerwyn Price ")).toEqual([
|
||||||
|
{ placement: 1, rawName: "Gerwyn Price" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("multi-line mixed formats", () => {
|
||||||
|
it("parses a realistic paste with multiple formats", () => {
|
||||||
|
const input = [
|
||||||
|
"1. Gerwyn Price",
|
||||||
|
"2, Luke Littler",
|
||||||
|
"T3. Michael van Gerwen",
|
||||||
|
"T3. Peter Wright",
|
||||||
|
"",
|
||||||
|
"5 Damon Heta",
|
||||||
|
].join("\n");
|
||||||
|
expect(parseResultsText(input)).toEqual([
|
||||||
|
{ placement: 1, rawName: "Gerwyn Price" },
|
||||||
|
{ placement: 2, rawName: "Luke Littler" },
|
||||||
|
{ placement: 3, rawName: "Michael van Gerwen" },
|
||||||
|
{ placement: 3, rawName: "Peter Wright" },
|
||||||
|
{ placement: 5, rawName: "Damon Heta" },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
51
app/lib/parse-results-text.ts
Normal file
51
app/lib/parse-results-text.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
export interface ParsedLine {
|
||||||
|
placement: number;
|
||||||
|
rawName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a pasted results text into placement + name pairs.
|
||||||
|
*
|
||||||
|
* Supported formats per line:
|
||||||
|
* 1. Name (dot separator)
|
||||||
|
* 1, Name (comma separator)
|
||||||
|
* 1 Name (space separator)
|
||||||
|
* T3. Name (T-prefix tie, dot separator)
|
||||||
|
* T3, Name (T-prefix tie, comma separator)
|
||||||
|
* T3 Name (T-prefix tie, space separator)
|
||||||
|
*
|
||||||
|
* Repeated rank numbers across consecutive lines are both emitted
|
||||||
|
* with the same placement (treated as ties automatically).
|
||||||
|
* Blank lines are ignored.
|
||||||
|
*/
|
||||||
|
export function parseResultsText(text: string): ParsedLine[] {
|
||||||
|
const results: ParsedLine[] = [];
|
||||||
|
|
||||||
|
for (const rawLine of text.split("\n")) {
|
||||||
|
const line = rawLine.trim();
|
||||||
|
if (!line) continue;
|
||||||
|
|
||||||
|
// Try to match: optional T, digits, optional separator (. or ,), then whitespace and the name
|
||||||
|
// Pattern 1: "1. Name", "1, Name", "T3. Name", "T3, Name"
|
||||||
|
const match = line.match(/^[Tt]?(\d+)[.,]\s+(.+)$/);
|
||||||
|
if (match) {
|
||||||
|
results.push({
|
||||||
|
placement: parseInt(match[1], 10),
|
||||||
|
rawName: match[2].trim(),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern 2: "1 Name", "T3 Name" (space-only separator)
|
||||||
|
const spaceMatch = line.match(/^[Tt]?(\d+)\s+(.+)$/);
|
||||||
|
if (spaceMatch) {
|
||||||
|
results.push({
|
||||||
|
placement: parseInt(spaceMatch[1], 10),
|
||||||
|
rawName: spaceMatch[2].trim(),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue