From e15cc9f40ef7f9dda7ba16b2edb773179d2b9702 Mon Sep 17 00:00:00 2001 From: shmck Date: Fri, 12 Jun 2020 17:25:48 -0700 Subject: [PATCH 1/4] setup validate markdown tests Signed-off-by: shmck --- src/utils/validateMarkdown.ts | 9 +++++++++ tests/markdown.test.ts | 11 +++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/utils/validateMarkdown.ts create mode 100644 tests/markdown.test.ts diff --git a/src/utils/validateMarkdown.ts b/src/utils/validateMarkdown.ts new file mode 100644 index 0000000..d61aea2 --- /dev/null +++ b/src/utils/validateMarkdown.ts @@ -0,0 +1,9 @@ +export function validateMarkdown(md: string): boolean { + // validate title (#) + // validate description + // validate level + // validate steps + // validate codeblock formats + + return false; +} diff --git a/tests/markdown.test.ts b/tests/markdown.test.ts new file mode 100644 index 0000000..bb683be --- /dev/null +++ b/tests/markdown.test.ts @@ -0,0 +1,11 @@ +import * as T from "../typings/tutorial"; +import { validateMarkdown } from "../src/utils/validateMarkdown"; + +describe("validate markdown", () => { + it.todo("should return false if missing a summary title (#)"); + it.todo("should return false if contains multiple `#` headers"); + it.todo("should return false if missing a summary description"); + it.todo("should return false if `##` doesn't preface a level"); + it.todo("should return false if `###` doesn't preface a step"); + it.todo("should return true for valid markdown"); +}); From d4916da5503533740b347a553b5cd31fdabd9e2c Mon Sep 17 00:00:00 2001 From: shmck Date: Fri, 12 Jun 2020 17:32:49 -0700 Subject: [PATCH 2/4] setup validate markdown tests Signed-off-by: shmck --- tests/markdown.test.ts | 106 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 99 insertions(+), 7 deletions(-) diff --git a/tests/markdown.test.ts b/tests/markdown.test.ts index bb683be..6faf393 100644 --- a/tests/markdown.test.ts +++ b/tests/markdown.test.ts @@ -1,11 +1,103 @@ -import * as T from "../typings/tutorial"; import { validateMarkdown } from "../src/utils/validateMarkdown"; describe("validate markdown", () => { - it.todo("should return false if missing a summary title (#)"); - it.todo("should return false if contains multiple `#` headers"); - it.todo("should return false if missing a summary description"); - it.todo("should return false if `##` doesn't preface a level"); - it.todo("should return false if `###` doesn't preface a step"); - it.todo("should return true for valid markdown"); + it("should return false if missing a summary title (#)", () => { + const md = ` +Description. + +## L1 Put Level's title here + +> Level's summary: a short description of the level's content in one line. + +Some text that describes the level`; + expect(validateMarkdown(md)).toBe(false); + }); + + it("should return false if contains multiple `#` headers", () => { + const md1 = `# A Title +Description. + +# Another Title + +## L1 Put Level's title here + +> Level's summary: a short description of the level's content in one line. + +Some text that describes the level`; + + const md2 = `# A Title +Description. + + +## L1 Put Level's title here + +> Level's summary: a short description of the level's content in one line. + +Some text that describes the level + +# Another title +`; + expect(validateMarkdown(md1)).toBe(false); + expect(validateMarkdown(md2)).toBe(false); + }); + + it("should return false if missing a summary description", () => { + const md = `# A Title + +## L1 Put Level's title here + +> Level's summary: a short description of the level's content in one line. + +Some text that describes the level +`; + expect(validateMarkdown(md)).toBe(false); + }); + + it("should return false if `##` doesn't preface a level", () => { + const md = `# A Title + +A description + +## Put Level's title here + +> Level's summary: a short description of the level's content in one line. + +Some text that describes the level +`; + expect(validateMarkdown(md)).toBe(false); + }); + + it("should return false if `###` doesn't preface a step", () => { + const md = `# A Title + +A description + +## Put Level's title here + +> Level's summary: a short description of the level's content in one line. + +Some text that describes the level + +### A Step + +First step +`; + }); + + it("should return true for valid markdown", () => { + const md = `# Title + +Description. + +## L1 Put Level's title here + +> Level's summary: a short description of the level's content in one line. + +Some text that describes the level + +### L1S1 + +First Step`; + expect(validateMarkdown(md)).toBe(true); + }); }); From 3feef7c453cbe5709a7dbac0ca2078a6da8ef2e2 Mon Sep 17 00:00:00 2001 From: shmck Date: Fri, 12 Jun 2020 18:50:05 -0700 Subject: [PATCH 3/4] add validate markdown fn Signed-off-by: shmck --- src/utils/validateMarkdown.ts | 75 ++++++++++++++++++++++++++++++++--- tests/markdown.test.ts | 31 +++++++++++++++ 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/src/utils/validateMarkdown.ts b/src/utils/validateMarkdown.ts index d61aea2..b5ea458 100644 --- a/src/utils/validateMarkdown.ts +++ b/src/utils/validateMarkdown.ts @@ -1,9 +1,72 @@ +type Validation = { + message: string; + validate: (t: string) => boolean; +}; + +const validations: Validation[] = [ + { + message: "should start with a title", + validate: (t) => !!t.match(/^#\s.+/), + }, + { + message: "should not have multiple `#` headers", + validate: (t) => !t.match(/[\n\r]#\s/), + }, + { + message: "should have a summary description under the title", + validate: (t) => { + const [summary] = t.split(/[\n\r]##/) || [""]; + const description = summary + .split(/\n/) + .slice(1) + .filter((l) => l.length); + return !!description.length; + }, + }, + { + message: "should have a level `##` with a format of `L[0-9]+`", + validate: (t) => { + const headers = t.match(/^#{2}\s(.+)$/gm) || []; + console.log("level headers", headers); + for (const header of headers) { + if (!header.match(/^#{2}\s(L\d+)\s(.+)$/)) { + return false; + } + } + return true; + }, + }, + { + message: "should have a step `###` with a format of `L[0-9]+S[0-9]+`", + validate: (t) => { + const headers = t.match(/^#{3}\s(.+)$/gm) || []; + console.log("step headers", headers); + for (const header of headers) { + if (!header.match(/^#{3}\s(L\d+)S\d+/)) { + return false; + } + } + return true; + }, + }, +]; + +const codeBlockRegex = /```[a-z]*\n[\s\S]*?\n```/gm; + export function validateMarkdown(md: string): boolean { - // validate title (#) - // validate description - // validate level - // validate steps - // validate codeblock formats + // remove codeblocks which might contain any valid combinations + const text = md.replace(codeBlockRegex, ""); + + let valid = true; + + for (const v of validations) { + if (!v.validate(text)) { + valid = false; + // if (process.env.NODE_ENV !== "test") { + console.warn(v.message); + // } + } + } - return false; + return valid; } diff --git a/tests/markdown.test.ts b/tests/markdown.test.ts index 6faf393..9b90028 100644 --- a/tests/markdown.test.ts +++ b/tests/markdown.test.ts @@ -37,6 +37,7 @@ Some text that describes the level # Another title `; + expect(validateMarkdown(md1)).toBe(false); expect(validateMarkdown(md2)).toBe(false); }); @@ -97,6 +98,36 @@ Some text that describes the level ### L1S1 +First Step`; + expect(validateMarkdown(md)).toBe(true); + }); + + it("should ignore markdown content in codeblocks", () => { + const md = `# Title + +Description. + +\`\`\`md +# A codeblock + +Should not be a problem +\`\`\` + + +## L1 Put Level's title here + +> Level's summary: a short description of the level's content in one line. + +Some text that describes the level + +\`\`\` +## Another Level in markdown + +Should not be an issue +\`\`\` + +### L1S1 + First Step`; expect(validateMarkdown(md)).toBe(true); }); From 183a5cd76046956874b82d318ff9d15f5fab21b1 Mon Sep 17 00:00:00 2001 From: shmck Date: Fri, 12 Jun 2020 18:57:10 -0700 Subject: [PATCH 4/4] add markdown validation to build script Signed-off-by: shmck --- package.json | 4 ++-- src/build.ts | 13 +++++++++++++ src/utils/validateMarkdown.ts | 8 +++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 727b3fe..5ac564c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@coderoad/cli", - "version": "0.2.0", + "version": "0.2.1", "description": "A CLI to build the configuration file for Coderoad Tutorials", "keywords": [ "coderoad", @@ -25,7 +25,7 @@ ], "main": "bin/coderoad", "bin": { - "@coderoad/coderoad": "bin/coderoad", + "@coderoad/cli": "bin/coderoad", "coderoad": "bin/coderoad" }, "scripts": { diff --git a/src/build.ts b/src/build.ts index b129b52..150dbc0 100644 --- a/src/build.ts +++ b/src/build.ts @@ -8,6 +8,7 @@ import { getCommits, CommitLogObject } from "./utils/commits"; import skeletonSchema from "./schema/skeleton"; import tutorialSchema from "./schema/tutorial"; import { validateSchema } from "./utils/validateSchema"; +import { validateMarkdown } from "./utils/validateMarkdown"; import * as T from "../typings/tutorial"; const write = util.promisify(fs.writeFile); @@ -72,6 +73,18 @@ async function build(args: string[]) { return; } + // validate markdown loosely + try { + const isValid = validateMarkdown(_markdown); + if (!isValid) { + console.warn("Invalid markdown"); + } + } catch (e) { + console.error("Error validating markdown:"); + console.error(e.message); + return; + } + // parse yaml skeleton config let skeleton; try { diff --git a/src/utils/validateMarkdown.ts b/src/utils/validateMarkdown.ts index b5ea458..c4d13bb 100644 --- a/src/utils/validateMarkdown.ts +++ b/src/utils/validateMarkdown.ts @@ -27,7 +27,6 @@ const validations: Validation[] = [ message: "should have a level `##` with a format of `L[0-9]+`", validate: (t) => { const headers = t.match(/^#{2}\s(.+)$/gm) || []; - console.log("level headers", headers); for (const header of headers) { if (!header.match(/^#{2}\s(L\d+)\s(.+)$/)) { return false; @@ -40,7 +39,6 @@ const validations: Validation[] = [ message: "should have a step `###` with a format of `L[0-9]+S[0-9]+`", validate: (t) => { const headers = t.match(/^#{3}\s(.+)$/gm) || []; - console.log("step headers", headers); for (const header of headers) { if (!header.match(/^#{3}\s(L\d+)S\d+/)) { return false; @@ -62,9 +60,9 @@ export function validateMarkdown(md: string): boolean { for (const v of validations) { if (!v.validate(text)) { valid = false; - // if (process.env.NODE_ENV !== "test") { - console.warn(v.message); - // } + if (process.env.NODE_ENV !== "test") { + console.warn(v.message); + } } }