mdquiz 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![bundle][bundle-src]][bundle-href]
6
6
  [![License][license-src]][license-href]
7
7
 
8
- Parse markdown quiz files into structured objects. Supports YAML frontmatter, checkbox answers, code blocks, and hint blockquotes.
8
+ Parse markdown quiz files into structured objects. Supports YAML frontmatter, checkbox answers, code blocks, hint blockquotes, and per-answer explanations.
9
9
 
10
10
  ## Install
11
11
 
@@ -25,7 +25,7 @@ const md = readFileSync('question-001.md', 'utf-8')
25
25
  const question = parseQuestionFile(md, 'question-001')
26
26
 
27
27
  console.log(question.question) // "Which syntax defines a job?"
28
- console.log(question.answers) // [{ id: 'a1', text: '...', isCorrect: true }, ...]
28
+ console.log(question.answers) // [{ id: 'a1', text: '...', isCorrect: true, explanation: '...' }, ...]
29
29
  console.log(question.isMultiSelect) // false
30
30
  console.log(question.hint) // "https://docs.github.com/..."
31
31
  ```
@@ -51,6 +51,7 @@ question: "Which GitHub Actions syntax correctly defines a job that runs on Ubun
51
51
  > https://docs.github.com/en/actions/using-workflows
52
52
 
53
53
  - [x] `runs-on: ubuntu-latest`
54
+ > This is the correct syntax for specifying a runner
54
55
  - [ ] `os: ubuntu-latest`
55
56
  - [ ] `platform: ubuntu-latest`
56
57
  - [ ] `environment: ubuntu-latest`
@@ -74,6 +75,7 @@ tags: ["git", "collaboration"]
74
75
  | Blockquote | No | Hint text or URL (lines starting with `>`) |
75
76
  | Code block | No | Context code shown before answers |
76
77
  | Answers | Yes | Checkbox list: `- [x]` correct, `- [ ]` incorrect |
78
+ | Answer explanation | No | Blockquote (`>`) after an answer provides a per-answer explanation |
77
79
 
78
80
  ### Answer formats
79
81
 
@@ -89,6 +91,18 @@ Both ordered and unordered lists work:
89
91
 
90
92
  Multiple `[x]` marks make the question multi-select automatically.
91
93
 
94
+ ### Per-answer explanations
95
+
96
+ Add a blockquote (`>`) immediately after an answer to provide an explanation. Multi-line explanations are joined with newlines:
97
+
98
+ ```markdown
99
+ - [x] Correct answer
100
+ > This is why it's correct
101
+ - [ ] Wrong answer
102
+ > This is wrong because...
103
+ > Here is more detail
104
+ ```
105
+
92
106
  ## API
93
107
 
94
108
  ### `parseQuestionFile(content: string, id: string): Question`
@@ -116,6 +130,7 @@ interface AnswerOption {
116
130
  id: string
117
131
  text: string
118
132
  isCorrect: boolean
133
+ explanation?: string
119
134
  }
120
135
 
121
136
  interface ParseDirOptions {
package/dist/index.d.mts CHANGED
@@ -4,6 +4,8 @@ interface AnswerOption {
4
4
  id: string;
5
5
  text: string;
6
6
  isCorrect: boolean;
7
+ /** Optional per-answer explanation from blockquote lines after the answer */
8
+ explanation?: string;
7
9
  }
8
10
  /** A parsed quiz question */
9
11
  interface Question {
package/dist/index.mjs CHANGED
@@ -23,7 +23,7 @@ function parseQuestionFile(content, id) {
23
23
  const { data, content: body } = matter(content);
24
24
  const questionText = data.question;
25
25
  if (!questionText) throw new Error(`Missing 'question' in frontmatter for ${id}`);
26
- const lines = body.split("\n");
26
+ const lines = body.replace(/\r\n/g, "\n").split("\n");
27
27
  const answerStarts = [];
28
28
  let inFence = false;
29
29
  for (let i = 0; i < lines.length; i++) {
@@ -60,6 +60,7 @@ function parseQuestionFile(content, id) {
60
60
  const isCorrect = match[1].toLowerCase() === "x";
61
61
  let text = match[2].trim();
62
62
  const continuation = [];
63
+ const explanationLines = [];
63
64
  let contFence = false;
64
65
  for (let j = startIdx + 1; j < endIdx; j++) {
65
66
  const l = lines[j];
@@ -72,14 +73,20 @@ function parseQuestionFile(content, id) {
72
73
  continuation.push(l);
73
74
  continue;
74
75
  }
75
- if (BLOCKQUOTE_RE.test(l)) continue;
76
+ const bqMatch = l.trim().match(BLOCKQUOTE_RE);
77
+ if (bqMatch) {
78
+ explanationLines.push(bqMatch[1]);
79
+ continue;
80
+ }
76
81
  if (l.trim() === "") continue;
77
82
  continuation.push(l);
78
83
  }
79
84
  if (continuation.length > 0) text += `\n${continuation.join("\n")}`;
85
+ const explanation = explanationLines.length > 0 ? explanationLines.join("\n").trim() : void 0;
80
86
  answers.push({
81
87
  isCorrect,
82
- text
88
+ text,
89
+ explanation
83
90
  });
84
91
  }
85
92
  const correctCount = answers.filter((a) => a.isCorrect).length;
@@ -89,7 +96,8 @@ function parseQuestionFile(content, id) {
89
96
  answers: answers.map((a, i) => ({
90
97
  id: `a${i + 1}`,
91
98
  text: a.text,
92
- isCorrect: a.isCorrect
99
+ isCorrect: a.isCorrect,
100
+ ...a.explanation ? { explanation: a.explanation } : {}
93
101
  })),
94
102
  isMultiSelect: correctCount > 1,
95
103
  hint,
package/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "mdquiz",
3
3
  "type": "module",
4
- "version": "0.0.0",
5
- "packageManager": "pnpm@10.33.0",
6
- "description": "Parse markdown quiz files into structured objects. Supports YAML frontmatter, checkbox answers, code blocks, and hint blockquotes.",
4
+ "version": "0.1.0",
5
+ "description": "Parse markdown quiz files into structured objects. Supports YAML frontmatter, checkbox answers, code blocks, hint blockquotes, and per-answer explanations.",
7
6
  "author": "Aleksander Fidelus",
8
7
  "license": "MIT",
9
8
  "homepage": "https://github.com/FidelusAleksander/mdquiz#readme",
@@ -31,40 +30,38 @@
31
30
  "files": [
32
31
  "dist"
33
32
  ],
34
- "scripts": {
35
- "build": "tsdown",
36
- "dev": "tsdown --watch",
37
- "lint": "eslint",
38
- "prepublishOnly": "nr build",
39
- "release": "bumpp",
40
- "start": "tsx src/index.ts",
41
- "test": "vitest",
42
- "typecheck": "tsc",
43
- "prepare": "simple-git-hooks"
44
- },
45
33
  "dependencies": {
46
- "gray-matter": "catalog:deps"
34
+ "gray-matter": "^4.0.3"
47
35
  },
48
36
  "devDependencies": {
49
- "@antfu/eslint-config": "catalog:cli",
50
- "@antfu/ni": "catalog:cli",
51
- "@types/node": "catalog:types",
52
- "bumpp": "catalog:cli",
53
- "eslint": "catalog:cli",
54
- "lint-staged": "catalog:cli",
55
- "publint": "catalog:cli",
56
- "simple-git-hooks": "catalog:cli",
57
- "tsdown": "catalog:cli",
58
- "tsnapi": "catalog:testing",
59
- "tsx": "catalog:cli",
60
- "typescript": "catalog:cli",
61
- "vite": "catalog:cli",
62
- "vitest": "catalog:testing"
37
+ "@antfu/eslint-config": "^8.1.1",
38
+ "@antfu/ni": "^30.0.0",
39
+ "@types/node": "^25.6.0",
40
+ "bumpp": "^11.0.1",
41
+ "eslint": "^10.2.0",
42
+ "lint-staged": "^16.4.0",
43
+ "publint": "^0.3.18",
44
+ "simple-git-hooks": "^2.13.1",
45
+ "tsdown": "^0.17.3",
46
+ "tsnapi": "^0.1.1",
47
+ "tsx": "^4.21.0",
48
+ "typescript": "^6.0.2",
49
+ "vite": "^8.0.8",
50
+ "vitest": "^4.1.4"
63
51
  },
64
52
  "simple-git-hooks": {
65
53
  "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && pnpm run build && npx lint-staged"
66
54
  },
67
55
  "lint-staged": {
68
56
  "*": "eslint --fix"
57
+ },
58
+ "scripts": {
59
+ "build": "tsdown",
60
+ "dev": "tsdown --watch",
61
+ "lint": "eslint",
62
+ "release": "bumpp",
63
+ "start": "tsx src/index.ts",
64
+ "test": "vitest",
65
+ "typecheck": "tsc"
69
66
  }
70
- }
67
+ }