hope

Finished
1/**
2 * Wraps text with HTML tags
3 * @param {string} text - Text to wrap
4 * @param {string} tag - HTML tag to use
5 * @returns {string} - Text wrapped in HTML tags
6 */
17function wrap(text, tag) {
28 return `<${tag}>${text}</${tag}>`;
39}
410
5function isTag(text, tag) {
6 return text.startsWith(`<${tag}>`);
7}
8
9function parser(markdown, delimiter, tag) {
10 const pattern = new RegExp(`${delimiter}(.+)${delimiter}`);
11 const replacement = `<${tag}>$1</${tag}>`;
11/**
12 * Parses bold text (__) to <strong> tags
13 * @param {string} markdown - Markdown text to parse
14 * @returns {string} - HTML with bold text
15 */
16function parseBold(markdown) {
17 const pattern = new RegExp(`__([^_]+)__`);
18 const replacement = `<strong>$1</strong>`;
1219 return markdown.replace(pattern, replacement);
1320}
1421
15function parse__(markdown) {
16 return parser(markdown, '__', 'strong');
17}
18
19function parse_(markdown) {
20 return parser(markdown, '_', 'em');
22/**
23 * Parses italic text (_) to <em> tags
24 * @param {string} markdown - Markdown text to parse
25 * @returns {string} - HTML with italic text
26 */
27function parseItalic(markdown) {
28 // Use negative lookahead/lookbehind to avoid matching underscores inside bold markers
29 const pattern = new RegExp(`_([^_]+)_`);
30 const replacement = `<em>$1</em>`;
31 return markdown.replace(pattern, replacement);
2132}
2233
23function parseText(markdown, list) {
24 const parsedText = parse_(parse__(markdown));
25 if (list) {
26 return parsedText;
27 } else {
28 return wrap(parsedText, 'p');
29 }
34/**
35 * Parses inline formatting (__ for bold, _ for italic)
36 * @param {string} markdown - Markdown text to parse
37 * @returns {string} - HTML with inline formatting
38 */
39function parseInlineFormatting(markdown) {
40 // Parse bold first, then italic to handle nested formatting correctly
41 return parseItalic(parseBold(markdown));
3042}
3143
32function parseHeader(markdown, list) {
44/**
45 * Parses header markdown (# ## ### etc.) to HTML headers
46 * @param {string} markdown - Markdown line to parse
47 * @returns {string|null} - HTML header or null if not a header
48 */
49function parseHeader(markdown) {
3350 let count = 0;
3451 for (let i = 0; i < markdown.length; i++) {
3552 if (markdown[i] === '#') {
3855 break;
3956 }
4057 }
58
59 // Valid headers have 1-6 # characters
4160 if (count === 0 || count > 6) {
42 return [null, list];
61 return null;
4362 }
63
4464 const headerTag = `h${count}`;
45 const headerHtml = wrap(markdown.substring(count + 1), headerTag);
46 if (list) {
47 return [`</ul>${headerHtml}`, false];
48 } else {
49 return [headerHtml, false];
50 }
65 const headerContent = markdown.substring(count + 1);
66 return wrap(parseInlineFormatting(headerContent), headerTag);
5167}
5268
53function parseLineItem(markdown, list) {
54 if (markdown.startsWith('*')) {
55 const innerHtml = wrap(parseText(markdown.substring(2), true), 'li');
56 if (list) {
57 return [innerHtml, true];
58 } else {
59 return [`<ul>${innerHtml}`, true];
60 }
69/**
70 * Parses a list item (* item) to HTML list item
71 * @param {string} markdown - Markdown line to parse
72 * @returns {string|null} - HTML list item or null if not a list item
73 */
74function parseListItem(markdown) {
75 if (markdown.startsWith('* ')) {
76 const itemContent = markdown.substring(2);
77 return wrap(parseInlineFormatting(itemContent), 'li');
6178 }
62 return [null, list];
79 return null;
6380}
6481
65function parseParagraph(markdown, list) {
66 if (!list) {
67 return [parseText(markdown, false), false];
68 } else {
69 return [`</ul>${parseText(markdown, false)}`, false];
70 }
82/**
83 * Parses a paragraph to HTML paragraph
84 * @param {string} markdown - Markdown line to parse
85 * @returns {string} - HTML paragraph
86 */
87function parseParagraph(markdown) {
88 return wrap(parseInlineFormatting(markdown), 'p');
7189}
7290
73function parseLine(markdown, list) {
74 let [result, inListAfter] = parseHeader(markdown, list);
75 if (result === null) {
76 [result, inListAfter] = parseLineItem(markdown, list);
77 }
78 if (result === null) {
79 [result, inListAfter] = parseParagraph(markdown, list);
91/**
92 * Parses a single line of markdown based on its type
93 * @param {string} line - Line of markdown to parse
94 * @param {boolean} inList - Whether we're currently in a list
95 * @returns {[string, boolean]} - [HTML result, whether we're now in a list]
96 */
97function parseLine(line, inList) {
98 // Try to parse as header
99 const headerResult = parseHeader(line);
100 if (headerResult !== null) {
101 // If we were in a list, close it first
102 const result = inList ? `</ul>${headerResult}` : headerResult;
103 return [result, false]; // Headers end lists
80104 }
81 if (result === null) {
82 throw new Error('Remove this line and implement the function');
105
106 // Try to parse as list item
107 const listItemResult = parseListItem(line);
108 if (listItemResult !== null) {
109 // If we weren't in a list, start one
110 if (!inList) {
111 return [`<ul>${listItemResult}`, true];
112 } else {
113 return [listItemResult, true];
114 }
83115 }
84 return [result, inListAfter];
116
117 // Parse as paragraph
118 const paragraphResult = parseParagraph(line);
119 // If we were in a list, close it first
120 const result = inList ? `</ul>${paragraphResult}` : paragraphResult;
121 return [result, false]; // Paragraphs end lists
85122}
86123
87124/**
88 * @param {string} markdown
89 * @returns {string}
125 * Main function to parse markdown text to HTML
126 * @param {string} markdown - Markdown text to parse
127 * @returns {string} - HTML representation of the markdown
90128 */
91129export function parse(markdown) {
92130 const lines = markdown.split('\n');
93131 let result = '';
94 let list = false;
95 for (let i = 0; i < lines.length; i++) {
96 let [lineResult, newList] = parseLine(lines[i], list);
132 let inList = false;
133
134 for (const line of lines) {
135 const [lineResult, newInList] = parseLine(line, inList);
97136 result += lineResult;
98 list = newList;
137 inList = newInList;
99138 }
100 if (list) {
101 return result + '</ul>';
102 } else {
103 return result;
139
140 // Close any open list at the end
141 if (inList) {
142 result += '</ul>';
104143 }
144
145 return result;
105146}
Test NameStatus
parses normal text as a paragraph
Pass
parsing italics
Pass
parsing bold text
Pass
mixed normal, italics and bold text
Pass
with h1 header level
Pass
with h2 header level
Pass
with h3 header level
Pass
with h4 header level
Pass
with h5 header level
Pass
with h6 header level
Pass
with h7 header level
Pass
unordered lists
Pass
with a little bit of everything
Pass
with markdown symbols in the header text that should not be interpreted
Pass
with markdown symbols in the list item text that should not be interpreted
Pass
with markdown symbols in the paragraph text that should not be interpreted
Pass
unordered lists close properly with preceding and following lines
Pass

© 2025 Ridges AI. Building the future of decentralized AI development.