Summary Files Commits Branches @commit @diff @stats
path: root / single.awk
diff options
context:
space:
mode:
Commit6e6789af2897b6a73766db33eb174e1ac7bb3f68 (patch)
Author
 Syndamia <kamen@syndamia.com>
2024-12-31 15:04:35 +0200
Committer
 Syndamia <kamen@syndamia.com>
2024-12-31 15:04:35 +0200
Files56c39828eb8c58b0d09df8fa49546bb900463684 /single.awk
csg-6e6789af2897b6a73766db33eb174e1ac7bb3f68.tar
csg-6e6789af2897b6a73766db33eb174e1ac7bb3f68.tar.gz
csg-6e6789af2897b6a73766db33eb174e1ac7bb3f68.zip
Parent5a53c0bfc65d8966ee0874939dfe449e372330f7 (diff)
feat(single)!: Rewrite using buffered output
This should provide a significantly simpler codebase. Implemented paragraphs, line break, headers and blockquotes.
1 files changed, 99 insertions, 298 deletions
-rw-r--r--single.awk397
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 "</blockquote>"
+ }
+ 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:]#]+;)/, "&amp;")
- while (match(substr($0, ampstart + 1), /&/)) {
- ampstart += RSTART
- if (match(substr($0, ampstart), /&[0-9A-Za-z#]+;/)) { continue }
-
- $0 = substr($0, 1, ampstart - 1) "&amp;" substr($0, ampstart + 1)
+ for (;blockquote_level < level; ++blockquote_level) {
+ P1 = P1 "<blockquote>"
}
+ if (blockquote_level > level) { P2 = P2 ORS }
+ for (;blockquote_level > level; --blockquote_level) {
+ P2 = P2 "</blockquote>"
+ }
+ not_paragraph = 1
}
-# Auto-escape <
-/</ {
- gsub(/</, "\\&lt;")
-}
-
-# Blockquote Part 1/2
-/^>[ \t]*$/ { next }
-/^> / {
- level = 0
- while (match($0, /^> [ \t]*/)) {
- $0 = substr($0, RLENGTH + 1)
- level += RSTART
- while (topStack("bq-level") > level) {
- println("</blockquote>")
- autoinsert(user_BLOCKQUOTE_AFTER)
+/^>$/ { $0 = ""; not_paragraph = 1 }
- popStack("bq-level")
- }
- if (topStack("bq-level") < level) {
- autoinsert(user_BLOCKQUOTE_BEFORE, 1)
- println("<blockquote>")
-
- pushStack("bq-level", level)
- }
- }
+# Line break
+/ $/ {
+ sub(/[ \t]* $/, "</br>")
}
# 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("<h" RLENGTH ">" $0 "</h" RLENGTH ">")
-
- 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 = "<h" RLENGTH ">" $0 "</h" RLENGTH ">"
+ not_paragraph = 1
}
}
+
# setex-style headers
-!prev_blankline && /^[ \t]*(=|-)+[ \t]*$/ {
- sub(/^<p>/, "", prev) ## from paragraph logic
+!emptyP1 && /^[ \t]*(=|-)+[ \t]*$/ {
hlevel = 1 + ($0 ~ /-/)
-
- prev = "<h" hlevel ">" prev "</h" hlevel ">"
-
- 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 = "<h" hlevel ">" substr(P1, 4) "</h" hlevel ">"
+ $0 = ""
+ not_paragraph = 1
}
-# Horizontal rules
-/^(-|\*|_) ?(-|\*|_) ?(-|\*|_|[ \t])+$/ {
- autoinsert(user_HR_BEFORE, 1)
- println("<hr>")
- autoinsert(user_HR_AFTER)
- blankline = 1
- next
-}
+# Paragraph
+emptyP1 && !empty0 && !not_paragraph { $0 = "<p>" $0 }
+!emptyP1 && empty0 && !not_paragraph_p1 { P1 = P1 "</p>" }
-# Line break
-/ $/ {
- sub(/[ \t]* $/, "</br>")
-}
-
-
-# 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("<ul class=\"checkboxed\">")
- checkbox_level = 1
+ if (empty0 && emptyP1) {
+ ++P1_repeats
}
- match($0, /- \[[ x]\][ \t]/)
- if (substr($0, RSTART + 3, 1) == "x") checked = " checked=\"checked\""
- else checked = ""
- $0 = "<li><input type=\"checkbox\" disabled=\"disabled\"" checked "><label>" substr($0, RSTART + RLENGTH) "</label></li>"
-}
-
-# Unordered lists
-function abs(x) { return ((x < 0) ? -x : x) }
-!blankline && lengthStack("list-level") > 0 {
- match($0, /^ */)
- indent = RLENGTH
- match($0, /(\*|\+|-|[0-9]+\.) /)
- if (RLENGTH == -1) RLENGTH = 0
- ## indent + RLENGTH = length(" * " or " 1. ")
- while (indent + RLENGTH < abs(topStack("list-level"))) {
- if (topStack("list-level") < 0) {
- println("</ol>")
- autoinsert(user_L_AFTER)
- autoinsert(user_OL_AFTER)
- }
- else {
- println("</ul>")
- 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 = "<ol>"; mult = -1
- autoinsert(user_OL_BEFORE, 1)
+ else {
+ print P2
+ while (P1_repeats > 0) {
+ print ""
+ --P1_repeats
}
- else {
- xl = "<ul>"; mult = 1
- autoinsert(user_UL_BEFORE, 1)
+ P2 = P1
+ P1 = $0
+ if (empty0) {
+ P1_repeats = 1
}
- autoinsert(user_L_BEFORE, 1)
- println(xl "<li>")
- pushStack("list-level", mult * (RSTART + RLENGTH - 1))
- }
- else {
- println("</li><li>")
}
-}
-!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 = "<p>" $0 }
-!prev_blankline && blankline && lengthStack("bq-level") == 0 && lengthStack("list-level") == 0 && checkbox_level == 0 { prev = prev "</p>" }
-
-# Blockquote Part 2/2
-blankline {
- while (lengthStack("bq-level") > 0) {
- println("</blockquote>")
- autoinsert(user_BLOCKQUOTE_AFTER)
- popStack("bq-level")
- }
-}
-
-# Check boxes Part 2/2
-checkbox_level > 0 && ! /^<li><input type="checkbox" disabled="disabled"/ {
- println("</ul>")
- 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 = "<img src=\"" href "\" alt=\"" inner_anchor "\" title=" title ">"
- else elem = "<a href=\"" href "\" title=" title ">" inner_anchor "</a>"
-
- $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 <em> is on the inside for long chains
- ## E.g. *****a***** will be the same as ****_a_****
- surround("___" , "<strong><em>", "</em></strong>", 3, "oneshot")
- surround("\\*\\*\\*", "<strong><em>", "</em></strong>", 3, "oneshot")
-
- surround("__" , "<strong>", "</strong>")
- surround("_" , "<em>" , "</em>")
- surround("\\*\\*", "<strong>", "</strong>", 2)
- surround("\\*" , "<em>" , "</em>", 1)
- surround("~~" , "<s>" , "</s>")
- surround("~" , "<sub>" , "</sub>")
- surround("\\^" , "<sup>" , "</sup>", 1)
- surround("``" , "<code>" , "</code>")
- surround("`" , "<code>" , "</code>")
-}
-
-# Definition lists
-!prev_blankline && /^: / {
- sub(/^<p>/, "", prev)
- sub(/^: /, "")
- prev = "<dl><dt>" ORS prev ORS "</dt><dd>" ORS $0 ORS "</dd></dl>"
- blankline = 1
- next
-}
-
-{
- println()
-}
-
END {
- print prev
- if (!blankline && lengthStack("bq-level") == 0) { print "</p>" }
- ## Flush stacks
- while (lengthStack("bq-level") > 0) {
- print "</blockquote>"
- popStack("bq-level")
- }
- while (lengthStack("list-level") > 0) {
- if (topStack("list-level") < 0) print "</ol>"
- else print "</ul>"
- popStack("list-level")
+ print P2
+ while (P1_repeats > 0) {
+ print ""
+ --P1_repeats
}
+ if (!empty(P1)) { print P1 }
+ print
+ if (!empty(N1)) { print N1 }
}