diff --git a/app/lib/__tests__/parse-results-text.test.ts b/app/lib/__tests__/parse-results-text.test.ts new file mode 100644 index 0000000..380115b --- /dev/null +++ b/app/lib/__tests__/parse-results-text.test.ts @@ -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" }, + ]); + }); + }); +}); diff --git a/app/lib/parse-results-text.ts b/app/lib/parse-results-text.ts new file mode 100644 index 0000000..a30b4ae --- /dev/null +++ b/app/lib/parse-results-text.ts @@ -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; +}