diff --git a/src/utils/parse.ts b/src/utils/parse.ts index 7c4fb04..c6e1a07 100644 --- a/src/utils/parse.ts +++ b/src/utils/parse.ts @@ -7,6 +7,15 @@ type TutorialFrame = { levels: T.Level[]; }; +const R = { + summary: /^#\s(?<tutorialTitle>.*)[\n\r]+(?<tutorialDescription>[^]*)/, + level: /^(#{2}\s(?<levelId>L?\d+\.?)\s(?<levelTitle>.*)[\n\r]*(>\s(?<levelSummary>.*))?[\n\r]+(?<levelContent>[^]*))/, + step: /^(#{3}\s(?<stepTitle>.*)[\n\r]+(?<stepContent>[^]*))/, + hints: /^(#{4}\sHINTS[\n\r]+([\*|\-]\s(?<hintContent>[^]*))[\n\r]+)+/, + subtasks: /^(#{4}\sSUBTASKS[\n\r]+([\*|\-]\s(?<subtaskContent>[^]*))[\n\r]+)+/, + listItem: /[\n\r]+[\*|\-]\s/, +}; + export function parseMdContent(md: string): TutorialFrame | never { let start: number = -1; const parts: any[] = []; @@ -34,9 +43,7 @@ export function parseMdContent(md: string): TutorialFrame | never { }; // Capture summary - const summaryMatch = parts - .shift() - .match(/^#\s(?<tutorialTitle>.*)[\n\r]+(?<tutorialDescription>[^]*)/); + const summaryMatch = parts.shift().match(R.summary); if (summaryMatch.groups.tutorialTitle) { mdContent.summary.title = summaryMatch.groups.tutorialTitle.trim(); } @@ -49,8 +56,7 @@ export function parseMdContent(md: string): TutorialFrame | never { // Identify each part of the content parts.forEach((section: string) => { // match level - const levelRegex = /^(#{2}\s(?<levelId>L?\d+\.?)\s(?<levelTitle>.*)[\n\r]*(>\s(?<levelSummary>.*))?[\n\r]+(?<levelContent>[^]*))/; - const levelMatch: RegExpMatchArray | null = section.match(levelRegex); + const levelMatch: RegExpMatchArray | null = section.match(R.level); if (levelMatch && levelMatch.groups) { const levelId = levelMatch.groups.levelId.replace(".", ""); @@ -77,8 +83,7 @@ export function parseMdContent(md: string): TutorialFrame | never { }; } else { // match step - const stepRegex = /^(#{3}\s(?<stepTitle>.*)[\n\r]+(?<stepContent>[^]*))/; - const stepMatch: RegExpMatchArray | null = section.match(stepRegex); + const stepMatch: RegExpMatchArray | null = section.match(R.step); if (stepMatch && stepMatch.groups) { current = { levelId: current.levelId, @@ -91,20 +96,36 @@ export function parseMdContent(md: string): TutorialFrame | never { content: stepContent.trim(), }; } else { - // parse hints from stepContent - const hintDetectRegex = /^(#{4}\sHINTS[\n\r]+([\*|\-]\s(?<hintContent>[^]*))[\n\r]+)+/; - const hintMatch = section.match(hintDetectRegex); - if (!!hintMatch) { - const hintItemRegex = /[\n\r]+[\*|\-]\s/; - const hints = section - .split(hintItemRegex) - .slice(1) // remove #### HINTS - .map((h) => h.trim()); - if (hints.length) { - mdContent.levels[current.levelIndex].steps[ - current.stepIndex - ].hints = hints; - } + const hintMatch = section.match(R.hints); + const subtaskMatch = section.match(R.subtasks); + + switch (true) { + // parse hints from stepContent + case !!hintMatch: + const hints = section + .split(R.listItem) + .slice(1) // remove #### HINTS + .map((h) => h.trim()); + if (hints.length) { + mdContent.levels[current.levelIndex].steps[ + current.stepIndex + ].hints = hints; + } + return; + // parse subtasks from stepContent + case !!subtaskMatch: + const subtasks = section + .split(R.listItem) + .slice(1) // remove #### SUBTASKS + .map((h) => h.trim()); + if (subtasks.length) { + mdContent.levels[current.levelIndex].steps[ + current.stepIndex + ].subtasks = subtasks; + } + return; + default: + console.warn(`No build parser match found for:\n${section}\n`); } } } diff --git a/tests/parse.test.ts b/tests/parse.test.ts index 28746af..478918b 100644 --- a/tests/parse.test.ts +++ b/tests/parse.test.ts @@ -541,7 +541,6 @@ The first step files: ["someFile.js"], watchers: ["someFile.js"], filter: "someFilter", - subtasks: true, }, solution: { commands: ["npm install"], @@ -579,7 +578,6 @@ The first step files: ["someFile.js"], watchers: ["someFile.js"], filter: "someFilter", - subtasks: true, }, solution: { commits: ["1gfedcba", "987654321"], @@ -631,7 +629,6 @@ The third step files: ["someFile.js"], watchers: ["someFile.js"], filter: "someFilter", - subtasks: true, }, solution: { commands: ["npm install"], @@ -645,7 +642,6 @@ The third step files: ["someFile.js"], watchers: ["someFile.js"], filter: "someFilter", - subtasks: true, }, solution: { commands: ["npm install"], @@ -666,7 +662,6 @@ The third step files: ["someFile.js"], watchers: ["someFile.js"], filter: "someFilter", - subtasks: true, }, solution: { commands: ["npm install"], @@ -709,7 +704,6 @@ The third step files: ["someFile.js"], watchers: ["someFile.js"], filter: "someFilter", - subtasks: true, }, solution: { commits: ["1fedcba", "987654321"], @@ -726,7 +720,6 @@ The third step files: ["someFile.js"], watchers: ["someFile.js"], filter: "someFilter", - subtasks: true, }, solution: { commits: ["3abcdef"], @@ -751,7 +744,6 @@ The third step files: ["someFile.js"], watchers: ["someFile.js"], filter: "someFilter", - subtasks: true, }, solution: { commits: ["5abcdef"], @@ -1419,4 +1411,75 @@ The second uninterrupted step expect(result.levels[0]).toEqual(expected.levels[0]); }); }); + describe("subtasks", () => { + it("should parse subtasks", () => { + const md = `# Subtask Demo + +A demo demonstrating how to use subtasks + +## 1. Subtask Example + +A subtask example + +### 1.1 + +Create a function \`add\` that can take a variety of params. + +#### SUBTASKS + +- Add one number +- Add two numbers +- Add three numbers +`; + const skeleton = { + levels: [ + { + id: "1", + steps: [ + { + id: "1.1", + }, + ], + }, + ], + }; + const expected = { + levels: [ + { + id: "1", + title: "Subtask Example", + summary: "A subtask example", + content: "A subtask example", + steps: [ + { + id: "1.1", + setup: { + commits: ["abcdef1"], + }, + content: + "Create a function `add` that can take a variety of params.", + solution: { + commits: ["abcdef2"], + }, + subtasks: [ + "Add one number", + "Add two numbers", + "Add three numbers", + ], + }, + ], + }, + ], + }; + const result = parse({ + text: md, + skeleton, + commits: { + "1.1:T": ["abcdef1"], + "1.1:S": ["abcdef2"], + }, + }); + expect(result.levels[0]).toEqual(expected.levels[0]); + }); + }); }); diff --git a/typings/tutorial.d.ts b/typings/tutorial.d.ts index 2b7769c..f19830b 100644 --- a/typings/tutorial.d.ts +++ b/typings/tutorial.d.ts @@ -27,7 +27,7 @@ export type Step = { content: string; setup?: StepActions; solution?: Maybe<StepActions>; - subtasks?: { [testName: string]: boolean }; + subtasks?: string[]; hints?: string[]; }; @@ -52,7 +52,7 @@ export type StepActions = { files?: string[]; watchers?: string[]; filter?: string; - subtasks?: boolean; + subtasks?: string[]; }; export interface TestRunnerArgs {