From 6e6789af2897b6a73766db33eb174e1ac7bb3f68 Mon Sep 17 00:00:00 2001 From: Syndamia Date: Tue, 31 Dec 2024 15:04:35 +0200 Subject: feat(single)!: Rewrite using buffered output This should provide a significantly simpler codebase. Implemented paragraphs, line break, headers and blockquotes. --- single.awk | 397 +++++++++++++++---------------------------------------------- 1 file changed, 99 insertions(+), 298 deletions(-) (limited to 'single.awk') diff --git a/single.awk b/single.awk index 78ae873..5ddcb94 100644 --- a/single.awk +++ b/single.awk @@ -1,34 +1,34 @@ # Status: # [.] Inline HTML -# [x] Autoescape +# [ ] Autoescape # [.] Block elements # [x] Paragraph # [x] Line break # [x] Headers (atx and setex) # [x] Blockquotes -# [x] Lists +# [ ] Lists # [ ] Code blocks -# [x] Horizontal rules -# [x] Span elements -# [x] Links -# [x] Inline -# [x] Reference -# [x] Emphasis -# [x] Code -# [x] Images -# [x] Inline -# [x] Reference +# [ ] Horizontal rules +# [ ] Span elements +# [ ] Links +# [ ] Inline +# [ ] Reference +# [ ] Emphasis +# [ ] Code +# [ ] Images +# [ ] Inline +# [ ] Reference # [ ] Other # [ ] Autolinks # [ ] Escapes # -# [.] Non-standard features -# [x] Strikethrough ~~...~~ -# [x] Superscript ^...^ -# [x] Subscript ~...~ +# [ ] Non-standard features +# [ ] Strikethrough ~~...~~ +# [ ] Superscript ^...^ +# [ ] Subscript ~...~ # [ ] Footnote [^...] (with user customizations) -# [x] Task list - [ ] (readonly) -# [.] Definition lists +# [ ] Task list - [ ] (readonly) +# [ ] Definition lists # [x] Single line # [ ] Multi-line # [ ] Fenced code blocks ```lang ``` @@ -46,94 +46,56 @@ # - emphasis inside inline code block is still converted to HTML # - backticks inside `` emphasis blocks aren't escaped -BEGIN { - prev_blankline = blankline = 1 - skipprint = 1 - newStack("bq-level") - newStack("list-level") -} - -function println(line) { - if (0 == line) line = $0 - print prev - prev = line -} - -function autoinsert(arrname, isbefore) { - if (isbefore == "") isbefore = 0 - for (confstr in arrname) { - split(confstr, conf, SUBSEP) - - ## FILENAME regex, ".*" (default) - if (2 in conf && FILENAME !~ conf[2]) { continue } - ## Text regex, ".*" (default) - if (3 in conf && text !~ conf[3]) { continue } - - if (isbefore) prev = arrname[confstr] prev - else prev = prev arrname[confstr] - - ## When, FIRST | ALL - if (conf[1] == FIRST) { delete arrname[confstr] } - } +function empty(value) { + return value == "" } { - prev_blankline = blankline - blankline = 1 + not_paragraph_p1 = not_paragraph + not_paragraph = 0 + empty0 = empty($0) + emptyP1 = empty(P1) } -/[^ \t]/ { - blankline = 0 -} +# Output is buffered on multiple lines, held in these variables: +# P2 = previous non-empty line +# P1 = previous line +# $0 = current line +# N1 = next new line (N1 is always set by the user) # Inline HTML /^[ \t]*<\/?[0-9A-Za-z]+/ { - println() - next + not_paragraph = 1 } -# Link definition -/^\[.+\]:/ { - blankline = 1 - next +# Blockquotes +blockquote_level > 0 && empty0 { + for (P1 = P1 ORS; blockquote_level > 0; --blockquote_level) { + P1 = P1 "" + } + not_paragraph_p1 = 1 } +/^>[ \t]/ { + match($0, /^[> \t]+[ \t]+/) + level = substr($0, RSTART, RLENGTH) + $0 = substr($0, RSTART + RLENGTH) + gsub(/[ \t]/, "", level) + level = length(level) -# Auto-escape & -/&/ { - ampstart = 0 - ## gsub(/&(?![[:alnum:]#]+;)/, "&") - while (match(substr($0, ampstart + 1), /&/)) { - ampstart += RSTART - if (match(substr($0, ampstart), /&[0-9A-Za-z#]+;/)) { continue } - - $0 = substr($0, 1, ampstart - 1) "&" substr($0, ampstart + 1) + for (;blockquote_level < level; ++blockquote_level) { + P1 = P1 "
" } + if (blockquote_level > level) { P2 = P2 ORS } + for (;blockquote_level > level; --blockquote_level) { + P2 = P2 "
" + } + not_paragraph = 1 } -# Auto-escape < -/[ \t]*$/ { next } -/^> / { - level = 0 - while (match($0, /^> [ \t]*/)) { - $0 = substr($0, RLENGTH + 1) - level += RSTART - while (topStack("bq-level") > level) { - println("") - autoinsert(user_BLOCKQUOTE_AFTER) +/^>$/ { $0 = ""; not_paragraph = 1 } - popStack("bq-level") - } - if (topStack("bq-level") < level) { - autoinsert(user_BLOCKQUOTE_BEFORE, 1) - println("
") - - pushStack("bq-level", level) - } - } +# Line break +/ $/ { + sub(/[ \t]* $/, "
") } # atx-style headers @@ -144,232 +106,71 @@ function autoinsert(arrname, isbefore) { gsub(/^[ \t]*#+[ \t]*/, "") gsub(/[ \t]*#+[ \t]*$/, "") - if (RLENGTH == 1) autoinsert(user_H1_BEFORE, 1) - else if (RLENGTH == 2) autoinsert(user_H2_BEFORE, 1) - else if (RLENGTH == 3) autoinsert(user_H3_BEFORE, 1) - else if (RLENGTH == 4) autoinsert(user_H4_BEFORE, 1) - else if (RLENGTH == 5) autoinsert(user_H5_BEFORE, 1) - else if (RLENGTH == 6) autoinsert(user_H6_BEFORE, 1) - autoinsert(user_H_BEFORE, 1) - - println("" $0 "") - - autoinsert(user_H_AFTER) - if (RLENGTH == 1) autoinsert(user_H1_AFTER) - else if (RLENGTH == 2) autoinsert(user_H2_AFTER) - else if (RLENGTH == 3) autoinsert(user_H3_AFTER) - else if (RLENGTH == 4) autoinsert(user_H4_AFTER) - else if (RLENGTH == 5) autoinsert(user_H5_AFTER) - else if (RLENGTH == 6) autoinsert(user_H6_AFTER) - - blankline = 1 - next + $0 = "" $0 "" + not_paragraph = 1 } } + # setex-style headers -!prev_blankline && /^[ \t]*(=|-)+[ \t]*$/ { - sub(/^

/, "", prev) ## from paragraph logic +!emptyP1 && /^[ \t]*(=|-)+[ \t]*$/ { hlevel = 1 + ($0 ~ /-/) - - prev = "" prev "" - - if (hlevel == 1) autoinsert(user_H1_BEFORE, 1) - else autoinsert(user_H2_BEFORE, 1) - autoinsert(user_H_BEFORE, 1) - - autoinsert(user_H_AFTER) - if (hlevel == 1) autoinsert(user_H1_AFTER) - else autoinsert(user_H2_AFTER) - - blankline = 1 - next + P1 = "" substr(P1, 4) "" + $0 = "" + not_paragraph = 1 } -# Horizontal rules -/^(-|\*|_) ?(-|\*|_) ?(-|\*|_|[ \t])+$/ { - autoinsert(user_HR_BEFORE, 1) - println("


") - autoinsert(user_HR_AFTER) - blankline = 1 - next -} +# Paragraph +emptyP1 && !empty0 && !not_paragraph { $0 = "

" $0 } +!emptyP1 && empty0 && !not_paragraph_p1 { P1 = P1 "

" } -# Line break -/ $/ { - sub(/[ \t]* $/, "
") -} - - -# Code blocks -/^( |\t)[ \t]*/ { -} +# After all $0 parsing +{ + empty0 = empty($0) + emptyP1 = empty(P1) -# Check boxes Part 1/2 -/^[ \t]*- \[[ x]\][ \t]/ { - if (checkbox_level <= 0) { - println("") - autoinsert(user_L_AFTER) - autoinsert(user_UL_AFTER) - } - popStack("list-level") + else if (!empty0 && emptyP1) { + P1 = $0 } -} -/^[ \t]*(\*|\+|-|[0-9]+\.) / { - match($0, /(\*|\+|-|[0-9]+\.) /) - if (abs(topStack("list-level")) < RSTART) { - if (RLENGTH > 2) { - xl = "
    "; mult = -1 - autoinsert(user_OL_BEFORE, 1) + else { + print P2 + while (P1_repeats > 0) { + print "" + --P1_repeats } - else { - xl = "
      "; mult = 1 - autoinsert(user_UL_BEFORE, 1) + P2 = P1 + P1 = $0 + if (empty0) { + P1_repeats = 1 } - autoinsert(user_L_BEFORE, 1) - println(xl "
    • ") - pushStack("list-level", mult * (RSTART + RLENGTH - 1)) - } - else { - println("
    • ") } -} -!blankline && lengthStack("list-level") > 0 { - $0 = substr($0, abs(topStack("list-level")) + 1) -} - -# Paragraphs -prev_blankline && !blankline && lengthStack("bq-level") == 0 && lengthStack("list-level") == 0 && checkbox_level == 0 { $0 = "

      " $0 } -!prev_blankline && blankline && lengthStack("bq-level") == 0 && lengthStack("list-level") == 0 && checkbox_level == 0 { prev = prev "

      " } - -# Blockquote Part 2/2 -blankline { - while (lengthStack("bq-level") > 0) { - println("
") - autoinsert(user_BLOCKQUOTE_AFTER) - popStack("bq-level") - } -} - -# Check boxes Part 2/2 -checkbox_level > 0 && ! /^
  • ") - checkbox_level = 0 -} - -# Links -/!?\[[^\]]*\] ?[(\[][^)\]]*[\])]/ { - isimg = 0 - while (match($0, /!?\[[^\]]*\] ?[(\[]/)) { - if (substr($0, RSTART, 1) == "!") { - isimg = 1 - RSTART++ - RLENGTH-- - } - astart = RSTART ## astart = indexof("[") - has_space = substr($0, RSTART + RLENGTH - 2, 1) == " " - inner_anchor = substr($0, RSTART + 1, RLENGTH - 3 - has_space) ## ...[xxxxx](...)... - - aend = RSTART + RLENGTH - 1 - match(substr($0, astart), /\] ?[(\[][^)\]]*[\])]/) - aend += RLENGTH - 2 - has_space ## aend = indexof(")") - href = substr($0, astart + RSTART + 1 + has_space, RLENGTH - 3 - has_space) ## ...[...](xxxxx)... - title = "\"\"" - if (substr($0, astart + RSTART + has_space, 1) == "[") { ## is reference link - if (href == "") href = inner_anchor - title = reference[href, "title"] - href = reference[href, "href"] + if (!empty(N1)) { + if (emptyP1) { + P1 = N1 } - else if (match(href, /[ \t]"[^"]*"[ \t]*$/)) { ## has title - title = substr(href, RSTART + 1, RLENGTH - 1) - href = substr(href, 1, RSTART - 1) + else { + print P2 + while (P1_repeats > 0) { + print "" + --P1_repeats + } + P2 = P1 + P1 = N1 + N1 = "" } - sub(/[ \t]+/, "", href) - - if (isimg) elem = "\""" - else elem = "" inner_anchor "" - - $0 = substr($0, 1, astart - 1 - isimg) elem substr($0, aend + 1) - isimg = 0 } } -# Emphasis -function surround(brace, opening, closing, blen, single_surround) { - if (blen == "") { blen = length(brace) } - - while (match($0, brace "[^" brace "]+" brace)) { - elem = opening substr($0, RSTART + blen, RLENGTH - 2 * blen) closing - $0 = substr($0, 1, RSTART - 1) elem substr($0, RSTART + RLENGTH) - - if (single_surround != "") { return; } - } -} -{ - ## Make sure is on the inside for long chains - ## E.g. *****a***** will be the same as ****_a_**** - surround("___" , "", "", 3, "oneshot") - surround("\\*\\*\\*", "", "", 3, "oneshot") - - surround("__" , "", "") - surround("_" , "" , "") - surround("\\*\\*", "", "", 2) - surround("\\*" , "" , "", 1) - surround("~~" , "" , "") - surround("~" , "" , "") - surround("\\^" , "" , "", 1) - surround("``" , "" , "") - surround("`" , "" , "") -} - -# Definition lists -!prev_blankline && /^: / { - sub(/^

    /, "", prev) - sub(/^: /, "") - prev = "

    " ORS prev ORS "
    " ORS $0 ORS "
    " - blankline = 1 - next -} - -{ - println() -} - END { - print prev - if (!blankline && lengthStack("bq-level") == 0) { print "

    " } - ## Flush stacks - while (lengthStack("bq-level") > 0) { - print "" - popStack("bq-level") - } - while (lengthStack("list-level") > 0) { - if (topStack("list-level") < 0) print "" - else print "" - popStack("list-level") + print P2 + while (P1_repeats > 0) { + print "" + --P1_repeats } + if (!empty(P1)) { print P1 } + print + if (!empty(N1)) { print N1 } } -- cgit v1.3.1-23-gd82d