From 2911e3a74dad0608d6a06c92cc55c5dbc15fd7c7 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Tue, 12 Oct 2021 13:05:00 +0200 Subject: [PATCH 1/4] code quality, fixes for multiple code blocks --- src/bin/md2pango | 155 ++++++++++++++++++++++++++++------------------- src/md2pango.js | 155 ++++++++++++++++++++++++++++------------------- 2 files changed, 186 insertions(+), 124 deletions(-) diff --git a/src/bin/md2pango b/src/bin/md2pango index de9f2ce..0e00713 100755 --- a/src/bin/md2pango +++ b/src/bin/md2pango @@ -13,23 +13,23 @@ let sub_h1, sub_h2, sub_h3 // m2p_sections defines how to detect special markdown sections. // These expressions scan the full line to detect headings, lists, and code. const m2p_sections = [ - sub_h1 = { name: H1, re: /^(#\s+)(.*)(\s*)$/, sub: "$2" }, - sub_h2 = { name: H2, re: /^(##\s+)(.*)(\s*)$/, sub: "$2" }, + sub_h1 = { name: H1, re: /^(#\s+)(.*)(\s*)$/, sub: "$2" }, + sub_h2 = { name: H2, re: /^(##\s+)(.*)(\s*)$/, sub: "$2" }, sub_h3 = { name: H3, re: /^(###\s+)(.*)(\s*)$/, sub: "$2" }, - { name: UL, re: /^(\s*[\*\-]\s)(.*)(\s*)$/, sub: " • $2" }, + { name: UL, re: /^(\s*[\*\-]\s)(.*)(\s*)$/, sub: " • $2" }, { name: OL, re: /^(\s*[0-9]+\.\s)(.*)(\s*)$/, sub: " $1$2" }, - { name: CODE, re: /^```[a-z_]*$/, sub: "" }, + { name: CODE, re: /^```[a-z_]*$/, sub: "" }, ] // m2p_styles defines how to replace inline styled text const m2p_styles = [ { name: BOLD, re: /(^|[^\*])(\*\*)(.*)(\*\*)/g, sub: "$1$3" }, { name: BOLD, re: /(\*\*)(.*)(\*\*)([^\*]|$)/g, sub: "$3$4" }, - { name: EMPH, re: /(^|[^\*])(\*)(.*)(\*)/g, sub: "$1$3" }, - { name: EMPH, re: /(\*)(.*)(\*)([^\*]|$)/g, sub: "$3$4" }, - { name: PRE, re: /(`)([^`]*)(`)/g, sub: "$2" }, - { name: LINK, re: /(!)?(\[)(.*)(\]\()(.+)(\))/g, sub: "$3" }, - { name: LINK, re: /(!)?(\[)(.*)(\]\(\))/g, sub: "$3" }, + { name: EMPH, re: /(^|[^\*])(\*)(.*)(\*)/g, sub: "$1$3" }, + { name: EMPH, re: /(\*)(.*)(\*)([^\*]|$)/g, sub: "$3$4" }, + { name: PRE, re: /(`)([^`]*)(`)/g, sub: "$2" }, + { name: LINK, re: /(!)?(\[)(.*)(\]\()(.+)(\))/g, sub: "$3" }, + { name: LINK, re: /(!)?(\[)(.*)(\]\(\))/g, sub: "$3" }, ] const re_comment = /^\s*\s*$/ @@ -45,7 +45,7 @@ const m2p_escapes = [ [//, ''], [/&/g, '&'], [//g, '>'], + [/>/g, '>'], ] const code_color_span = "" @@ -59,103 +59,133 @@ const pad = (lines, start=1, end=1) => { function convert(text) { let lines = text.split('\n') - let code = false - let out = [] - let pre = [] + + // Indicates if the current line is within a code block + let is_code = false + let code_lines = [] + + let output = [] let color_span_open = false let tt_must_close = false const try_close_span = () => { if (color_span_open) { - out.push('') + output.push('') color_span_open = false } } + const try_open_span = () => { if (!color_span_open) { - out.push('') + output.push('') color_span_open = false } } - for (const line of lines) { // first parse color macros in non-code texts - if(!code) { + if (!is_code) { let colors = line.match(re_color) - if (colors || line.match(re_reset)) try_close_span() + if (colors || line.match(re_reset)) { + try_close_span() + } + if (colors) { try_close_span() - if(color_span_open) close_span() + if (color_span_open) { + close_span() + } + let fg = colors[2] == 'fg'? colors[3] : colors[5] == 'fg'? colors[6] : '' let bg = colors[2] == 'bg'? colors[3] : colors[5] == 'bg'? colors[6] : '' let attrs = '' - if(fg != '') { attrs += ` foreground='${fg}'`} - if(bg != '') { attrs += ` background='${bg}'`} - if (attrs != '') { - out.push(``) + + if (fg != '') { + attrs += ` foreground='${fg}'` + } + + if (bg != '') { + attrs += ` background='${bg}'` + } + + if (attrs != '') { + output.push(``) color_span_open = true } } } + // all macros processed, lets remove remaining comments - if (line.match(re_comment)) continue + if (line.match(re_comment)) { + continue + } - // escape all non-verbatim text - let result = code? line : escape_line(line) + // is this line an opening statement of a code block let code_start = false - let match = null - for (sec of m2p_sections) { - if (match = line.match(sec.re)) { - switch (sec.name) { - case CODE: - if (!code) { - code_start=true - if (color_span_open) { - // cannot color - result = '' - tt_must_close = false - } else { - result = code_color_span + '' - tt_must_close = true - } + + // escape all non-verbatim text + let result = is_code ? line : escape_line(line) + + for ({ re, sub, name } of m2p_sections) { + if (line.match(re)) { + if (name === CODE) { + if (!is_code) { + // haven't been inside a code block, so ``` indicates + // that it is starting now + code_start = true + is_code = true + + if (color_span_open) { + // cannot color + result = '' + tt_must_close = false + } else { + result = code_color_span + '' + tt_must_close = true } - else { - out.push(...pad(pre).map(escape_line)) - result='' - if (tt_must_close) { - result += '' - tt_must_close = false - } + } else { + // the code block ends now + is_code = false + output.push(...pad(code_lines).map(escape_line)) + code_lines = [] + result = '' + if (tt_must_close) { + result += '' + tt_must_close = false } - code=!code - break - default: - if (code) result = line - else result = line.replace(sec.re, sec.sub) - break + } + } else { + if (is_code) { + result = line + } else { + result = line.replace(re, sub) + } } - break } } - if (code && !code_start) { - pre.push(result) + + if (is_code && !code_start) { + code_lines.push(result) continue } + if (line.match(re_h1line)) { - out.push(`# ${out.pop()}`.replace(sub_h1.re, sub_h1.sub)) + output.push(`# ${output.pop()}`.replace(sub_h1.re, sub_h1.sub)) continue } + if (line.match(re_h2line)) { - out.push(`## ${out.pop()}`.replace(sub_h2.re, sub_h2.sub)) + output.push(`## ${output.pop()}`.replace(sub_h2.re, sub_h2.sub)) continue } + // all other text can be styled for (const style of m2p_styles) { result = result.replace(style.re, style.sub) } + // all raw urls can be linked if possible - let uri = result.match(re_uri) // look for any URI + let uri = result.match(re_uri) // look for any URI let href = result.match(re_href) // and for URIs in href='' let atag = result.match(re_atag) // and for URIs in href = href && href[1] == uri @@ -163,11 +193,12 @@ function convert(text) { if (uri && (href || atag)) { result = result.replace(uri, `${uri}`) } - out.push(result) + + output.push(result) } try_close_span() - return out.join('\n') + return output.join('\n') } const readFile = (f) => { diff --git a/src/md2pango.js b/src/md2pango.js index d42eae9..655085b 100755 --- a/src/md2pango.js +++ b/src/md2pango.js @@ -12,23 +12,23 @@ let sub_h1, sub_h2, sub_h3 // m2p_sections defines how to detect special markdown sections. // These expressions scan the full line to detect headings, lists, and code. const m2p_sections = [ - sub_h1 = { name: H1, re: /^(#\s+)(.*)(\s*)$/, sub: "$2" }, - sub_h2 = { name: H2, re: /^(##\s+)(.*)(\s*)$/, sub: "$2" }, + sub_h1 = { name: H1, re: /^(#\s+)(.*)(\s*)$/, sub: "$2" }, + sub_h2 = { name: H2, re: /^(##\s+)(.*)(\s*)$/, sub: "$2" }, sub_h3 = { name: H3, re: /^(###\s+)(.*)(\s*)$/, sub: "$2" }, - { name: UL, re: /^(\s*[\*\-]\s)(.*)(\s*)$/, sub: " • $2" }, + { name: UL, re: /^(\s*[\*\-]\s)(.*)(\s*)$/, sub: " • $2" }, { name: OL, re: /^(\s*[0-9]+\.\s)(.*)(\s*)$/, sub: " $1$2" }, - { name: CODE, re: /^```[a-z_]*$/, sub: "" }, + { name: CODE, re: /^```[a-z_]*$/, sub: "" }, ] // m2p_styles defines how to replace inline styled text const m2p_styles = [ { name: BOLD, re: /(^|[^\*])(\*\*)(.*)(\*\*)/g, sub: "$1$3" }, { name: BOLD, re: /(\*\*)(.*)(\*\*)([^\*]|$)/g, sub: "$3$4" }, - { name: EMPH, re: /(^|[^\*])(\*)(.*)(\*)/g, sub: "$1$3" }, - { name: EMPH, re: /(\*)(.*)(\*)([^\*]|$)/g, sub: "$3$4" }, - { name: PRE, re: /(`)([^`]*)(`)/g, sub: "$2" }, - { name: LINK, re: /(!)?(\[)(.*)(\]\()(.+)(\))/g, sub: "$3" }, - { name: LINK, re: /(!)?(\[)(.*)(\]\(\))/g, sub: "$3" }, + { name: EMPH, re: /(^|[^\*])(\*)(.*)(\*)/g, sub: "$1$3" }, + { name: EMPH, re: /(\*)(.*)(\*)([^\*]|$)/g, sub: "$3$4" }, + { name: PRE, re: /(`)([^`]*)(`)/g, sub: "$2" }, + { name: LINK, re: /(!)?(\[)(.*)(\]\()(.+)(\))/g, sub: "$3" }, + { name: LINK, re: /(!)?(\[)(.*)(\]\(\))/g, sub: "$3" }, ] const re_comment = /^\s*\s*$/ @@ -44,7 +44,7 @@ const m2p_escapes = [ [//, ''], [/&/g, '&'], [//g, '>'], + [/>/g, '>'], ] const code_color_span = "" @@ -58,103 +58,133 @@ const pad = (lines, start=1, end=1) => { function convert(text) { let lines = text.split('\n') - let code = false - let out = [] - let pre = [] + + // Indicates if the current line is within a code block + let is_code = false + let code_lines = [] + + let output = [] let color_span_open = false let tt_must_close = false const try_close_span = () => { if (color_span_open) { - out.push('') + output.push('') color_span_open = false } } + const try_open_span = () => { if (!color_span_open) { - out.push('') + output.push('') color_span_open = false } } - for (const line of lines) { // first parse color macros in non-code texts - if(!code) { + if (!is_code) { let colors = line.match(re_color) - if (colors || line.match(re_reset)) try_close_span() + if (colors || line.match(re_reset)) { + try_close_span() + } + if (colors) { try_close_span() - if(color_span_open) close_span() + if (color_span_open) { + close_span() + } + let fg = colors[2] == 'fg'? colors[3] : colors[5] == 'fg'? colors[6] : '' let bg = colors[2] == 'bg'? colors[3] : colors[5] == 'bg'? colors[6] : '' let attrs = '' - if(fg != '') { attrs += ` foreground='${fg}'`} - if(bg != '') { attrs += ` background='${bg}'`} - if (attrs != '') { - out.push(``) + + if (fg != '') { + attrs += ` foreground='${fg}'` + } + + if (bg != '') { + attrs += ` background='${bg}'` + } + + if (attrs != '') { + output.push(``) color_span_open = true } } } + // all macros processed, lets remove remaining comments - if (line.match(re_comment)) continue + if (line.match(re_comment)) { + continue + } - // escape all non-verbatim text - let result = code? line : escape_line(line) + // is this line an opening statement of a code block let code_start = false - let match = null - for (sec of m2p_sections) { - if (match = line.match(sec.re)) { - switch (sec.name) { - case CODE: - if (!code) { - code_start=true - if (color_span_open) { - // cannot color - result = '' - tt_must_close = false - } else { - result = code_color_span + '' - tt_must_close = true - } + + // escape all non-verbatim text + let result = is_code ? line : escape_line(line) + + for ({ re, sub, name } of m2p_sections) { + if (line.match(re)) { + if (name === CODE) { + if (!is_code) { + // haven't been inside a code block, so ``` indicates + // that it is starting now + code_start = true + is_code = true + + if (color_span_open) { + // cannot color + result = '' + tt_must_close = false + } else { + result = code_color_span + '' + tt_must_close = true } - else { - out.push(...pad(pre).map(escape_line)) - result='' - if (tt_must_close) { - result += '' - tt_must_close = false - } + } else { + // the code block ends now + is_code = false + output.push(...pad(code_lines).map(escape_line)) + code_lines = [] + result = '' + if (tt_must_close) { + result += '' + tt_must_close = false } - code=!code - break - default: - if (code) result = line - else result = line.replace(sec.re, sec.sub) - break + } + } else { + if (is_code) { + result = line + } else { + result = line.replace(re, sub) + } } - break } } - if (code && !code_start) { - pre.push(result) + + if (is_code && !code_start) { + code_lines.push(result) continue } + if (line.match(re_h1line)) { - out.push(`# ${out.pop()}`.replace(sub_h1.re, sub_h1.sub)) + output.push(`# ${output.pop()}`.replace(sub_h1.re, sub_h1.sub)) continue } + if (line.match(re_h2line)) { - out.push(`## ${out.pop()}`.replace(sub_h2.re, sub_h2.sub)) + output.push(`## ${output.pop()}`.replace(sub_h2.re, sub_h2.sub)) continue } + // all other text can be styled for (const style of m2p_styles) { result = result.replace(style.re, style.sub) } + // all raw urls can be linked if possible - let uri = result.match(re_uri) // look for any URI + let uri = result.match(re_uri) // look for any URI let href = result.match(re_href) // and for URIs in href='' let atag = result.match(re_atag) // and for URIs in href = href && href[1] == uri @@ -162,11 +192,12 @@ function convert(text) { if (uri && (href || atag)) { result = result.replace(uri, `${uri}`) } - out.push(result) + + output.push(result) } try_close_span() - return out.join('\n') + return output.join('\n') } const readFile = (f) => { From 65b3a6999fdba2effd9ec80dab85c5f3af129695 Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Tue, 12 Oct 2021 13:44:59 +0200 Subject: [PATCH 2/4] improved test, removing trailing whitespaces --- .gitignore | 2 ++ Makefile | 2 +- src/bin/md2pango | 4 ++++ src/md2pango.js | 4 ++++ tests/expected.pango | 42 ++++++++++++++++++++++++++++++++++++++++ test.md => tests/test.md | 0 test.sh => tests/test.sh | 9 +++++---- 7 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 tests/expected.pango rename test.md => tests/test.md (100%) rename test.sh => tests/test.sh (69%) diff --git a/.gitignore b/.gitignore index 757a3d2..5ea06fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +tests/result.pango +.idea .vscode .bin dist diff --git a/Makefile b/Makefile index 5f23c26..092d59d 100644 --- a/Makefile +++ b/Makefile @@ -8,5 +8,5 @@ src/bin/md2pango: src/md2pango.js cat src/md2pango.js >> $@ TESTS=all -test: ; ./test.sh $(TESTS) +test: ; ./tests/test.sh $(TESTS) reuse: ; reuse addheader src/*.js -y 2021 -l MIT -c 'Uwe Jugel' diff --git a/src/bin/md2pango b/src/bin/md2pango index 0e00713..819dfb7 100755 --- a/src/bin/md2pango +++ b/src/bin/md2pango @@ -198,6 +198,10 @@ function convert(text) { } try_close_span() + + // remove trailing newlines + output = output.map(line => line.replace(/ +$/, '')) + return output.join('\n') } diff --git a/src/md2pango.js b/src/md2pango.js index 655085b..02f44f8 100755 --- a/src/md2pango.js +++ b/src/md2pango.js @@ -197,6 +197,10 @@ function convert(text) { } try_close_span() + + // remove trailing newlines + output = output.map(line => line.replace(/ +$/, '')) + return output.join('\n') } diff --git a/tests/expected.pango b/tests/expected.pango new file mode 100644 index 0000000..8a10226 --- /dev/null +++ b/tests/expected.pango @@ -0,0 +1,42 @@ +T1 + + + + <html> + <tag> + </tag> + </html> + + + + + # default colored + --> more + <-- code + + +T2 + + 1. first + 2. second + 3. third + +T3 + + • a + • b + • c + +full link: https://example.com +named link: link +URI: https://example.com + +bold emph inline code example +bold code +bold emph +bold-emph +emph bold emph + + +Red T2 + diff --git a/test.md b/tests/test.md similarity index 100% rename from test.md rename to tests/test.md diff --git a/test.sh b/tests/test.sh similarity index 69% rename from test.sh rename to tests/test.sh index 057023a..3f658fc 100755 --- a/test.sh +++ b/tests/test.sh @@ -8,10 +8,11 @@ fail() { echo -e "[FAIL] $*"; exit 1; } ok() { echo -e "[OK] $*\n"; } test_md2pango() { - lines=$(node src/md2pango.js *.md | wc -l) && - test "$lines" -gt 150 || - fail "unexpected pango output, got $lines lines, expected > 150" - ok "Output looks good! ($lines lines)" + set -e + node src/md2pango.js tests/test.md > tests/result.pango + diff "tests/result.pango" "tests/expected.pango" || + fail "tests/result.pango does not match tests/expected.pango" + ok "Output looks good!" } test_lint() { From e72929c36dc2d393c57c362a11d30be88748c20f Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Tue, 12 Oct 2021 16:16:09 +0200 Subject: [PATCH 3/4] typo --- src/md2pango.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/md2pango.js b/src/md2pango.js index 02f44f8..a731323 100755 --- a/src/md2pango.js +++ b/src/md2pango.js @@ -198,7 +198,7 @@ function convert(text) { try_close_span() - // remove trailing newlines + // remove trailing whitespaces output = output.map(line => line.replace(/ +$/, '')) return output.join('\n') From e0547214f2a758988c9265a0b2a0c32cc4a7654c Mon Sep 17 00:00:00 2001 From: sezanzeb Date: Wed, 13 Oct 2021 23:08:01 +0200 Subject: [PATCH 4/4] fixed path to test.md in demos --- demos/gtk3-app.js | 2 +- demos/gtk4-app.js | 2 +- src/bin/md2pango | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/demos/gtk3-app.js b/demos/gtk3-app.js index 6062899..0b01b9d 100755 --- a/demos/gtk3-app.js +++ b/demos/gtk3-app.js @@ -11,7 +11,7 @@ const file = GLib.path_get_basename(args[0]) const dir = GLib.path_get_dirname(args[0]) const root = GLib.path_get_dirname(dir) -const test_md = path(root, 'test.md') +const test_md = path(root, 'tests/test.md') const readme_md = path(root, 'README.md') const contrib_md = path(root, 'CONTRIBUTING.md') const src = path(root, 'src') diff --git a/demos/gtk4-app.js b/demos/gtk4-app.js index 799b23b..5a291f9 100755 --- a/demos/gtk4-app.js +++ b/demos/gtk4-app.js @@ -11,7 +11,7 @@ const file = GLib.path_get_basename(args[0]) const dir = GLib.path_get_dirname(args[0]) const root = GLib.path_get_dirname(dir) -const test_md = path(root, 'test.md') +const test_md = path(root, 'tests/test.md') const readme_md = path(root, 'README.md') const contrib_md = path(root, 'CONTRIBUTING.md') const src = path(root, 'src') diff --git a/src/bin/md2pango b/src/bin/md2pango index 819dfb7..1426514 100755 --- a/src/bin/md2pango +++ b/src/bin/md2pango @@ -199,7 +199,7 @@ function convert(text) { try_close_span() - // remove trailing newlines + // remove trailing whitespaces output = output.map(line => line.replace(/ +$/, '')) return output.join('\n')