From 03a882950ead37f74ab4953dbd1199ddbc3d7f6c Mon Sep 17 00:00:00 2001 From: Syndamia Date: Wed, 1 Jan 2025 15:50:49 +0200 Subject: chore: Rename and reorganize files --- csg | 4 +- lib_stack.awk | 26 --- md-extensions/lib_stack.awk | 26 +++ md-extensions/mel.awk | 133 ++++++++++++ md-meta.awk | 60 ++++++ md-parser.awk | 491 ++++++++++++++++++++++++++++++++++++++++++++ mel.awk | 133 ------------ meta.awk | 60 ------ single.awk | 491 -------------------------------------------- 9 files changed, 712 insertions(+), 712 deletions(-) delete mode 100644 lib_stack.awk create mode 100644 md-extensions/lib_stack.awk create mode 100644 md-extensions/mel.awk create mode 100644 md-meta.awk create mode 100644 md-parser.awk delete mode 100644 mel.awk delete mode 100644 meta.awk delete mode 100644 single.awk diff --git a/csg b/csg index fc1119a..da853f5 100755 --- a/csg +++ b/csg @@ -123,7 +123,7 @@ EOF echo 'BEGIN { FILENAME=ARGV[1];FIRST="f";ALL="a"' > "$CSG_TMP_DIR/meta.begin.awk" while read -r file do - $CSG_AWK -f "$CSG_DIR/meta.awk" "$file" >> "$CSG_TMP_DIR/meta.begin.awk" & + $CSG_AWK -f "$CSG_DIR/md-meta.awk" "$file" >> "$CSG_TMP_DIR/meta.begin.awk" & done << EOF $(find "$DESTINATION" -type f -name "*.md") EOF @@ -139,7 +139,7 @@ convert_file() { $CSG_AWK \ -f "$CSG_TMP_DIR/meta.begin.awk" -f "$USERAWKDIR/user.begin.awk" \ - -f "$CSG_DIR/lib_stack.awk" -f "$CSG_DIR/single.awk" \ + -f "$CSG_DIR/md-parser.awk" \ "$file" > "$outf" rm "$file" } diff --git a/lib_stack.awk b/lib_stack.awk deleted file mode 100644 index a8b0b2a..0000000 --- a/lib_stack.awk +++ /dev/null @@ -1,26 +0,0 @@ -function newStack(name) { - __stacks[name, "length"] = 0 -} - -function topStack(name) { - return __stacks[name, __stacks[name, "length"] - 1] -} - -function lengthStack(name) { - return __stacks[name, "length"] -} - -function pushStack(name, value) { - __stacks[name, __stacks[name, "length"]++] = value -} - -function popStack(name, value) { - delete __stacks[name, --__stacks[name, "length"]] -} - -function deleteStack(name) { - for (i = 0; i < __stacks[name, "length"]; i++) { - delete __stacks[name, i] - } - delete __stacks[name, "length"] -} diff --git a/md-extensions/lib_stack.awk b/md-extensions/lib_stack.awk new file mode 100644 index 0000000..a8b0b2a --- /dev/null +++ b/md-extensions/lib_stack.awk @@ -0,0 +1,26 @@ +function newStack(name) { + __stacks[name, "length"] = 0 +} + +function topStack(name) { + return __stacks[name, __stacks[name, "length"] - 1] +} + +function lengthStack(name) { + return __stacks[name, "length"] +} + +function pushStack(name, value) { + __stacks[name, __stacks[name, "length"]++] = value +} + +function popStack(name, value) { + delete __stacks[name, --__stacks[name, "length"]] +} + +function deleteStack(name) { + for (i = 0; i < __stacks[name, "length"]; i++) { + delete __stacks[name, i] + } + delete __stacks[name, "length"] +} diff --git a/md-extensions/mel.awk b/md-extensions/mel.awk new file mode 100644 index 0000000..3359cf9 --- /dev/null +++ b/md-extensions/mel.awk @@ -0,0 +1,133 @@ +# .*html +# +# == About == +# +# MEL is the "Markdown Extensions Language" +# It's a general metalanguage (I made) for adding custom syntax to Markdown (via awk) +# The idea is it provides parsing for general constructs, and you only need to define +# the names of those constructs and with what they get replaced with +# +# Slightly based on ftags, this system allows you to easily implement the following construts: +# &(name ... &) Multiline "tag", everything inside is formatted by "name" +# &{name ... &} In practice, it just replaces "&[name" and "&]" with the appropriate HTML +# &[name ... &] All three can be used interchangeably, though it's preferrable if +# you use the matching braces and semantically, in your "language" +# square brackets are used for the largest components +# curly for structural components and normal braces for styling +# &:name Format/encapsulate everything until the end of the paragraph +# &::name Format/encapsulate everything until the end of the line +# &.name Insert standalone element as-is +# +# Though it's not decided (or implemented) yet, potentially there will also be: +# &)-( Used to simplify cases of &(name1 ... &) &(name1 ... &) &(name1 ... &) +# &}-{ into &(name1 ... &)-( ... &)-( ... &) +# &]-[ Or maybe for special cases, where rather than doing something like +# &{elementsList &(element1 ... &) &(element2 ... &) &} +# you could simply do +# &{elementsList ... &}-{ ... &}-{ ... &} +# So it will surf as a separator between direct subelements + +BEGIN { + usesMEL() + newStack("mel") +} + +function usesMEL() { + MELOPEN = "o" + MELCLOSE = "c" + MELLINEOPEN = "lo" + MELLINECLOSE = "lc" + MELINSERT = "i" + MELPARAGRAPH = "p" + MELLINE = "l" +} + +function printErr(message, column, columnPointer) { + column = length($0) - length(lineCopy) + RSTART - 5 + print "ERROR by MEL-parser.awk: " message " | Found in " FILENAME " at line " FNR " & column " column ":" > "/dev/stderr" + columnPointer = "" + for (i = 1; i < column; i++) + columnPointer = columnPointer " " + for (i = -5; i < RLENGTH; i++) + columnPointer = columnPointer "^" + print $0 "\n" columnPointer "\n" > "/dev/stderr" +} + +melInsideCode && /```/ { + melInsideCode = 0 +} +/```/ { + melInsideCode = 1 +} + +melInsideCode == 0 && /^[ \t]*(

|

)?&[{[()\]}:.]/ { + newLine = "" + lineCopy = $0 + paragraphed = 0 + append = "" + while (match(lineCopy, /&(((::|[[{(:.])[^[:space:].:;[{(<>)}\]&]+)|[\]})])/)) { + RSTART += 1 + RLENGTH -= 1 + + # Inside closing tag + if (RLENGTH == 1) { + replaceWith = MEL[topStack("mel"), MELCLOSE] + + if (replaceWith == "") + printErr("Missing closing tag for \"" topStack("mel") "\"") + + popStack("mel") + } + # Inside insert + else if (substr(lineCopy, RSTART, 1) == ".") { + name = substr(lineCopy, RSTART + 1, RLENGTH - 1) + + replaceWith = MEL[name, MELINSERT] + + if (replaceWith == "") + printErr("Unknown insertion tag \"" name "\"") + } + # Inside one line + else if (substr(lineCopy, RSTART, 2) == "::") { + name = substr(lineCopy, RSTART + 2, RLENGTH - 2) + + replaceWith = MEL[name, MELLINEOPEN] + append = append MEL[name, MELLINECLOSE] + + if (replaceWith == "") + printErr("Missing beginning for line tag \"" name "\"") + else if (MEL[name, MELLINECLOSE] == "") + printErr("Missing ending for line tag \"" name "\"") + else if (replaceWith == "" && MEL[name, MELLINECLOSE] == "") + printErr("Unknown single line tag \"" name "\"") + } + # Inside paragraph + else if (substr(lineCopy, RSTART, 1) == ":") { + name = substr(lineCopy, RSTART + 1, RLENGTH - 1) + + replaceWith = " " MEL[name, MELPARAGRAPH] + + if (replaceWith == " ") + printErr("Unknown paragraph tag \"" name "\"") + + RSTART -= 1 + RLENGTH += 1 + paragraphed = 1 + } + # Inside opening tag + else { + name = substr(lineCopy, RSTART + 1, RLENGTH - 1) + + replaceWith = MEL[name, MELOPEN] + pushStack("mel", name) + + if (replaceWith == "") + printErr("Unknown opening tag \"" name "\"") + } + newLine = newLine substr(lineCopy, 0, RSTART - 6) replaceWith + lineCopy = substr(lineCopy, RSTART + RLENGTH) + } + $0 = newLine lineCopy append + if (paragraphed == 0 && newLine != "") + gsub(/(

|<\/p>)/, "") +} diff --git a/md-meta.awk b/md-meta.awk new file mode 100644 index 0000000..fea34c3 --- /dev/null +++ b/md-meta.awk @@ -0,0 +1,60 @@ +# No character classes, since they aren't supported on every system +function emit(ind, val) { + print "meta[\"" FILENAME "\", \"" ind "\"]=\"" val "\"" +} +function erit(id, href, title) { + print "reference[\"" id "\", \"href\"]=\"" href "\"" + print "reference[\"" id "\", \"title\"]=\"\\\"" title "\\\"\"" +} + +title != "" && stopdesc == 0 && /^[A-Za-z]/ { + startedGathering = 1 + # Take only anchor text + gsub(/\]\([^)]*\)/, "") + gsub(/\[/, "") + # Append line + if (desc == "") desc = $0 + else desc = desc " " $0 + # Trim line to 200 characters + if (length(desc) > 200) { + desc = substr(desc, 1, 197) "..." + stopdesc = 1 + } +} +stopdesc == 0 && startedGathering == 1 && /^[ \t]*$/ { stopdesc = 1 } + +{ + words += NF +} + +title == "" && /^# / { + sub(/^#[ \t]*/, "") + sub(/[ \t#]*$/, "") + # sub(/.*/, titlef) + title = $0 +} + +/^\[[^^].*\]:/ { + match($0, /^\[.+\]:/) + id = substr($0, 2, RLENGTH - 3) + + href = substr($0, RLENGTH + 1) + reftitle = "" + if (match(href, /[ \t]+["'(].*[)'"]$/)) { + reftitle = substr(href, RSTART) + href = substr(href, 1, RSTART - 1) + + sub(/^[ \t]["'(]*/, "", reftitle) + sub(/[)'"][ \t]*$/, "", reftitle) + } + sub(/^[ \t]*?[ \t]*$/, "", href) + + erit(id, href, reftitle) +} + +END { + emit("title", title) + emit("description", desc) + emit("words", words) +} diff --git a/md-parser.awk b/md-parser.awk new file mode 100644 index 0000000..50e7f60 --- /dev/null +++ b/md-parser.awk @@ -0,0 +1,491 @@ +# Almost the entire canonical Markdown specification is supported +# https://daringfireball.net/projects/markdown/syntax +# Notable behavioural exceptions are: +# - Block-level HTML elements needn't be separated with blank lines +# - Lists are identation-aware. List items begin and continue after the same +# identation with which the list beginning (e.g. "* " or "5. ") was written. +# The benefit is that all Markdown is parsed as normal inside an item. +# +# [.] Non-standard features +# [x] Strikethrough ~~...~~ +# [x] Superscript ^...^ +# [x] Subscript ~...~ +# [ ] Footnote [^...] (with user customizations) +# [x] Task list - [ ] (readonly) +# [ ] Definition lists +# [ ] Single line +# [ ] Multi-line +# [x] Fenced code blocks ```lang ``` +# [ ] Tables +# [x] Links +# [x] Link destination ( inside anchors) +# [x] Nested links +# +# No character classes have been used, since they aren't supported on every system + +function empty(value) { + return value == "" +} + +function PNappnd(value) { + if (emptyP1) P2 = P2 ORS value + else P1 = P1 ORS value +} + +function _next() { + __buffered_print() + not_paragraph = 1 + next +} + +function sub_match(replacement) { + $0 = substr($0, 1, RSTART - 1) replacement substr($0, RSTART + RLENGTH) +} + +function char(string, ind) { + if (1 > ind || ind > length(string)) { + return ""; + } + return substr(string, ind, 1) +} + +function backup_match() { + backup_rstart = RSTART + backup_rlength = RLENGTH +} +function restore_match() { + RSTART = backup_rstart + RLENGTH = backup_rlength +} + +BEGIN { + ## This is used to convert email autolinks into a series of hex values + ## Escaped output of: awk 'BEGIN {for (i = 32; i <= 126; ++i){ printf("%c", i) } }' + ascii = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" + for (i = 1; i < 97; ++i) { + char_to_dec[char(ascii, i)] = 31 + i + } + + ## We do character escaping by substituting the escape with some + ## "useless" characters, which we're confident won't appear in actual text. + ## These are from decimal value 14 to 28 inclusive. They're blank characters, + ## related to shifting, device control and data transmission. + escapable = "\\`*_{}[]()#+-.!" + for (i = 1; i < 16; ++i) { + escapes[char(escapable, i)] = sprintf("%c", 13 + i) + } +} + +{ + not_paragraph_p1 = not_paragraph + not_paragraph = 0 + empty0 = empty($0) + emptyP1 = empty(P1) + list_started = 0 +} + +# Output is buffered on multiple lines, held in these variables: +# P2 = last non-empty line before the previous 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]+/ { + match($0, /^[ \t]*/) + if (last_html || RLENGTH - 2 * list_level < 4) { + last_html = 1 + _next() + } +} +{ + last_html = 0 +} + +# Fenced code blocks +fenced_codeblock { + $0 = " " $0 +} + +# Code blocks (ending) +inside_codeblock && (/ ```/ || ! /^( |\t|[ \t]*$)/) { + PNappnd("") + inside_codeblock = 0 + emptyP1 = 1 + not_paragraph = 1 + if (fenced_codeblock) { + fenced_codeblock = 0 + fenced_class = "" + next + } +} + +# Link definition +!inside_codeblock && /^\[[^\]]+\]:[ \t]+.*/ { + next +} + +# Escapes +!inside_codeblock && /\\[-\\`*_{}\[\]()#+.!]/ { + while (match($0, /\\[-\\`*_{}\[\]()#+.!]/)) { + sub_match(escapes[char($0, RSTART + 1)]) + } +} + +# Horizontal rule +/^(---+|- - -( -)*|\*\*\*+|\* \* \*( \*)*|___+|_ _ _( _)*)$/ { + $0 = "


" + not_paragraph = 1 +} + +# Blockquotes +blockquote_level > 0 && empty0 { + if (inside_codeblock) { + P1 = P1 ORS "" + inside_codeblock = 0 + } + for (P1 = P1 ORS; blockquote_level > 0; --blockquote_level) { + P1 = P1 "" + } + not_paragraph_p1 = 1 +} +/^>[ \t]/ { + match($0, /^[> \t]*> /) + level = substr($0, RSTART, RLENGTH) + $0 = substr($0, RSTART + RLENGTH) + gsub(/[ \t]/, "", level) + level = length(level) + + for (;blockquote_level < level; ++blockquote_level) { + P1 = P1 "
" + } + if (blockquote_level > level) { + if (inside_codeblock) { + P2 = P2 ORS "" + inside_codeblock = 0 + } + P2 = P2 ORS + } + for (;blockquote_level > level; --blockquote_level) { + P2 = P2 "
" + } + not_paragraph = 1 +} +/^>$/ { $0 = ""; not_paragraph = 1 } + +# Lists +function end_list() { + if (list_level == 0) return + PNappnd("") + --list_level +} +function start_list(level, type, shift) { + ## Every two columns we define a new level + level = int((level + 2) / 2) + ## If list definition is the first line inside a code block + if (level - list_level >= 3) return + + ## Make list_level = level - 1 + while (list_level < level - 1) { + lists[++list_level] = "" + } + ## Make list_level = level + while (list_level > level) { + end_list() + } + + if (list_level == level && lists[list_level] != type) { + end_list() + } + if (list_level < level || lists[list_level] != type) { + P1 = P1 ORS "<" (type == "." ? "o" : "u") "l>
  • " + lists[level] = type + lists_shift[level] = shift + list_level = level + } + else { + PNappnd("
  • ") + P1 = P1 ORS "
  • " + } + not_paragraph = 1 + list_started = 1 +} +!inside_codeblock && /^[ \t]*[*+-] / { ## Unordered list + match($0, /^[ \t]*[*+-] /) + level = RLENGTH - 2 + type = char($0, RSTART + RLENGTH - 2) + match($0, /^[^*+-]*[*+-] /) + shift = RLENGTH + sub_match("") + + start_list(level, type, shift) +} +!inside_codeblock && /^[ \t]*[0-9]+\. / { ## Ordered list + match($0, /^[ \t]*/) + level = RLENGTH + type = "." + match($0, /^[^.]+\. /) + shift = RLENGTH + sub_match("") + + start_list(level, type, shift) +} +list_level > 0 && !list_started && /[^ \t]/ { ## Non-list line + match($0, /^[ \t]*/) + level = int((RLENGTH + 2) / 2) + not_paragraph = 1 + sub("^[ \t]{1," lists_shift[list_level] "}", "") + + while (list_level >= level && emptyP1) { + end_list() + not_paragraph = 0 + } +} + +# Checklists +list_started && /^\[[x ]\][ \t]/ { + checked = (char($0, 2) == "x") ? " checked=\"checked\"" : ""; + $0 = "" +} + +# Fenced code blocks +!inside_codeblock && /^[ \t]*```/ { + fenced_codeblock = 1 + + match(P1, /```[^ \t]*/) + if (RLENGTH > 3) { + fenced_class = " class=\"language-" tolower(substr(P1, RSTART + 3, RLENGTH - 3)) "\"" + } + + next +} + +# Code blocks +/^( |\t)/ { + ## Autoescape & and < + for (i = 1; match(substr($0, i), /&/); i += RSTART) { + RSTART += i - 1 + sub_match("&") + } + gsub(//, "\\>") + + if (fenced_codeblock) { + $0 = substr($0, 5) + } + if (!inside_codeblock) { + $0 = "
    " $0
    +		inside_codeblock = 1
    +	}
    +
    +	_next()
    +}
    +
    +# Automatic links
    +/<[^ \t<>]+>/ {
    +	## URL: 
    +	while (match($0, /<[A-Za-z][-A-Za-z0-9+.]*:[^ \t<>]+>/)) {
    +		href = substr($0, RSTART + 1, RLENGTH - 2)
    +		sub_match("" href "")
    +	}
    +
    +	## Email addresses, with support for quoted local part "..."@...
    +	## and ip address literal ...@[...]
    +	while (match($0, /<([^ \t<>]+|".+")@([-A-Za-z0-9]+\.[^ \t<>]+|\[[0-9A-Fa-f.:]+\])>/)) {
    +		href = substr($0, RSTART + 1, RLENGTH - 2)
    +		href_masked = ""
    +		## The original markdown blogpost calls for convertions of all
    +		## email characters into their hex values. Allegedly, this fools
    +		## many autoharvesting spam bots.
    +		for (i = length(href); i > 0; --i) {
    +			href_masked = sprintf("&#x%X;", char_to_dec[char(href, i)]) href_masked
    +		}
    +		## href="mailto:..."
    +		sub_match("" href_masked "")
    +	}
    +}
    +
    +# Autoescape &
    +/&/ {
    +	for (i = 1; match(substr($0, i), /&/); i += RSTART) {
    +		RSTART += i - 1
    +		if (substr($0, RSTART) ~ /&[A-Za-z0-9#]+;/) { continue }
    +		sub_match("&")
    +	}
    +}
    +# Autoescape <
    +/ < / {
    +	gsub(/ < /, "\\<")
    +}
    +
    +# Line break
    +/  $/ {
    +	sub(/[ \t]*  $/, "
    ") +} + +# atx-style headers +/^[ \t]*#+/ { + match($0, /#+/) + if (RLENGTH <= 6) { + ## Remove prefix/suffix + gsub(/^[ \t]*#+[ \t]*/, "") + gsub(/[ \t]*#+[ \t]*$/, "") + + $0 = "" $0 "" + not_paragraph = 1 + } +} + +# setex-style headers +!emptyP1 && /^[ \t]*(=|-)+[ \t]*$/ { + hlevel = 1 + ($0 ~ /-/) + P1 = "" substr(P1, 4) "" + $0 = "" + not_paragraph = 1 +} + +# Emphasis +function surround(brace, left, right, blen) { + if (blen == "") { blen = length(brace) } + if (match($0, brace (brace == "``" ? "." : "[^" brace "]") "+" brace)) { + elem = left substr($0, RSTART + blen, RLENGTH - 2*blen) right + if (brace == "``") { gsub("`", "\\`", elem) } + sub_match(elem) + return 1; + } + return 0 +} +function gsurround(brace, left, right, blen) { + while (surround(brace, left, right, blen)) {} + + if (blen == 1) { + gsub("\\\\", "", brace) + ## Surrounding a single character span with spaces should make it a + ## literal char. This is inelegant but functional. + gsub(" " left " ", " " brace " ") + gsub(" " right " ", " " brace " ") + } +} + +{ + ## Make sure is on the inside for long chains + ## E.g. *****a***** will be the same as ****_a_**** + surround("___" , "", "") + surround("\\*\\*\\*", "", "", 3) + + gsurround("__" , "", "") + gsurround("_" , "" , "") + gsurround("\\*\\*", "", "", 2) + gsurround("\\*" , "" , "", 1) + gsurround("``" , "" , "") + gsurround("`" , "" , "") + + gsurround("~~" , "" , "") + gsurround("~" , "" , "") + gsurround("\\^", "" , "", 1) +} + +# Links +/!?\[[^\]]*\] ?[(\[][^)\]]*[\])]/ { + while (match($0, /!?\[[^\]]*\] ?[(\[][^)\]]*[\])]/)) { + link = substr($0, RSTART, RLENGTH) + backup_match() + + ## ](xxxxx) + match(link, /\] ?[(\[][^)\]]*[\])]$/) + has_space = char(link, RSTART + 1) == " " + is_img = char(link, 1) == "!" + + ## ...[xxxxx](...)... + text = substr(link, 2 + is_img, RSTART - 2 - is_img) + ## ...[...](xxxxx)... + href = substr(link, RSTART + has_space + 2, RLENGTH - has_space - 3) + + title = "\"\"" + if (char(link, RSTART + RLENGTH - 1) == "]") { ## is reference link + if (href == "") href = text # [...][] + ## [...]: ... "xxxxx" + title = reference[href, "title"] + ## [...]: xxxxx "..." + href = reference[href, "href"] + } + else if (match(href, /[ \t]"[^"]*"[ \t]*$/)) { ## has title + ## ...[...](... "xxxxx")... + title = substr(href, RSTART + 1, RLENGTH - 1) + ## ...[...](xxxxx "...")... + href = substr(href, 1, RSTART - 1) + } + + if (is_img) elem = "\""" + else elem = "" text "" + + restore_match() + sub_match(elem) + } +} + +# Paragraph +emptyP1 && !empty0 && !not_paragraph { $0 = "

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

    " } + +# After all $0 parsing +{ + __buffered_print() +} +function __buffered_print() { + empty0 = empty($0) + emptyP1 = empty(P1) + + if (empty0 && emptyP1) { + ++P1_repeats + } + else if (!empty0 && emptyP1) { + P1 = $0 + } + else { + print P2 + while (P1_repeats > 0) { + print "" + --P1_repeats + } + P2 = P1 + P1 = $0 + if (empty0) { + P1_repeats = 1 + } + } + + if (!empty(N1)) { + if (emptyP1) { + P1 = N1 + } + else { + print P2 + while (P1_repeats > 0) { + print "" + --P1_repeats + } + P2 = P1 + P1 = N1 + N1 = "" + } + } + + ## Fix escaped characters + for (i = 1; i < 16; ++i) { + esc = char(escapable, i) + gsub(escapes[esc], esc, P2) + } +} + +END { + print P2 + while (P1_repeats > 0) { + print "" + --P1_repeats + } + if (!empty(P1)) { print P1 } + print + if (!empty(N1)) { print N1 } +} diff --git a/mel.awk b/mel.awk deleted file mode 100644 index 3359cf9..0000000 --- a/mel.awk +++ /dev/null @@ -1,133 +0,0 @@ -# .*html -# -# == About == -# -# MEL is the "Markdown Extensions Language" -# It's a general metalanguage (I made) for adding custom syntax to Markdown (via awk) -# The idea is it provides parsing for general constructs, and you only need to define -# the names of those constructs and with what they get replaced with -# -# Slightly based on ftags, this system allows you to easily implement the following construts: -# &(name ... &) Multiline "tag", everything inside is formatted by "name" -# &{name ... &} In practice, it just replaces "&[name" and "&]" with the appropriate HTML -# &[name ... &] All three can be used interchangeably, though it's preferrable if -# you use the matching braces and semantically, in your "language" -# square brackets are used for the largest components -# curly for structural components and normal braces for styling -# &:name Format/encapsulate everything until the end of the paragraph -# &::name Format/encapsulate everything until the end of the line -# &.name Insert standalone element as-is -# -# Though it's not decided (or implemented) yet, potentially there will also be: -# &)-( Used to simplify cases of &(name1 ... &) &(name1 ... &) &(name1 ... &) -# &}-{ into &(name1 ... &)-( ... &)-( ... &) -# &]-[ Or maybe for special cases, where rather than doing something like -# &{elementsList &(element1 ... &) &(element2 ... &) &} -# you could simply do -# &{elementsList ... &}-{ ... &}-{ ... &} -# So it will surf as a separator between direct subelements - -BEGIN { - usesMEL() - newStack("mel") -} - -function usesMEL() { - MELOPEN = "o" - MELCLOSE = "c" - MELLINEOPEN = "lo" - MELLINECLOSE = "lc" - MELINSERT = "i" - MELPARAGRAPH = "p" - MELLINE = "l" -} - -function printErr(message, column, columnPointer) { - column = length($0) - length(lineCopy) + RSTART - 5 - print "ERROR by MEL-parser.awk: " message " | Found in " FILENAME " at line " FNR " & column " column ":" > "/dev/stderr" - columnPointer = "" - for (i = 1; i < column; i++) - columnPointer = columnPointer " " - for (i = -5; i < RLENGTH; i++) - columnPointer = columnPointer "^" - print $0 "\n" columnPointer "\n" > "/dev/stderr" -} - -melInsideCode && /```/ { - melInsideCode = 0 -} -/```/ { - melInsideCode = 1 -} - -melInsideCode == 0 && /^[ \t]*(

    |

    )?&[{[()\]}:.]/ { - newLine = "" - lineCopy = $0 - paragraphed = 0 - append = "" - while (match(lineCopy, /&(((::|[[{(:.])[^[:space:].:;[{(<>)}\]&]+)|[\]})])/)) { - RSTART += 1 - RLENGTH -= 1 - - # Inside closing tag - if (RLENGTH == 1) { - replaceWith = MEL[topStack("mel"), MELCLOSE] - - if (replaceWith == "") - printErr("Missing closing tag for \"" topStack("mel") "\"") - - popStack("mel") - } - # Inside insert - else if (substr(lineCopy, RSTART, 1) == ".") { - name = substr(lineCopy, RSTART + 1, RLENGTH - 1) - - replaceWith = MEL[name, MELINSERT] - - if (replaceWith == "") - printErr("Unknown insertion tag \"" name "\"") - } - # Inside one line - else if (substr(lineCopy, RSTART, 2) == "::") { - name = substr(lineCopy, RSTART + 2, RLENGTH - 2) - - replaceWith = MEL[name, MELLINEOPEN] - append = append MEL[name, MELLINECLOSE] - - if (replaceWith == "") - printErr("Missing beginning for line tag \"" name "\"") - else if (MEL[name, MELLINECLOSE] == "") - printErr("Missing ending for line tag \"" name "\"") - else if (replaceWith == "" && MEL[name, MELLINECLOSE] == "") - printErr("Unknown single line tag \"" name "\"") - } - # Inside paragraph - else if (substr(lineCopy, RSTART, 1) == ":") { - name = substr(lineCopy, RSTART + 1, RLENGTH - 1) - - replaceWith = " " MEL[name, MELPARAGRAPH] - - if (replaceWith == " ") - printErr("Unknown paragraph tag \"" name "\"") - - RSTART -= 1 - RLENGTH += 1 - paragraphed = 1 - } - # Inside opening tag - else { - name = substr(lineCopy, RSTART + 1, RLENGTH - 1) - - replaceWith = MEL[name, MELOPEN] - pushStack("mel", name) - - if (replaceWith == "") - printErr("Unknown opening tag \"" name "\"") - } - newLine = newLine substr(lineCopy, 0, RSTART - 6) replaceWith - lineCopy = substr(lineCopy, RSTART + RLENGTH) - } - $0 = newLine lineCopy append - if (paragraphed == 0 && newLine != "") - gsub(/(

    |<\/p>)/, "") -} diff --git a/meta.awk b/meta.awk deleted file mode 100644 index fea34c3..0000000 --- a/meta.awk +++ /dev/null @@ -1,60 +0,0 @@ -# No character classes, since they aren't supported on every system -function emit(ind, val) { - print "meta[\"" FILENAME "\", \"" ind "\"]=\"" val "\"" -} -function erit(id, href, title) { - print "reference[\"" id "\", \"href\"]=\"" href "\"" - print "reference[\"" id "\", \"title\"]=\"\\\"" title "\\\"\"" -} - -title != "" && stopdesc == 0 && /^[A-Za-z]/ { - startedGathering = 1 - # Take only anchor text - gsub(/\]\([^)]*\)/, "") - gsub(/\[/, "") - # Append line - if (desc == "") desc = $0 - else desc = desc " " $0 - # Trim line to 200 characters - if (length(desc) > 200) { - desc = substr(desc, 1, 197) "..." - stopdesc = 1 - } -} -stopdesc == 0 && startedGathering == 1 && /^[ \t]*$/ { stopdesc = 1 } - -{ - words += NF -} - -title == "" && /^# / { - sub(/^#[ \t]*/, "") - sub(/[ \t#]*$/, "") - # sub(/.*/, titlef) - title = $0 -} - -/^\[[^^].*\]:/ { - match($0, /^\[.+\]:/) - id = substr($0, 2, RLENGTH - 3) - - href = substr($0, RLENGTH + 1) - reftitle = "" - if (match(href, /[ \t]+["'(].*[)'"]$/)) { - reftitle = substr(href, RSTART) - href = substr(href, 1, RSTART - 1) - - sub(/^[ \t]["'(]*/, "", reftitle) - sub(/[)'"][ \t]*$/, "", reftitle) - } - sub(/^[ \t]*?[ \t]*$/, "", href) - - erit(id, href, reftitle) -} - -END { - emit("title", title) - emit("description", desc) - emit("words", words) -} diff --git a/single.awk b/single.awk deleted file mode 100644 index 50e7f60..0000000 --- a/single.awk +++ /dev/null @@ -1,491 +0,0 @@ -# Almost the entire canonical Markdown specification is supported -# https://daringfireball.net/projects/markdown/syntax -# Notable behavioural exceptions are: -# - Block-level HTML elements needn't be separated with blank lines -# - Lists are identation-aware. List items begin and continue after the same -# identation with which the list beginning (e.g. "* " or "5. ") was written. -# The benefit is that all Markdown is parsed as normal inside an item. -# -# [.] Non-standard features -# [x] Strikethrough ~~...~~ -# [x] Superscript ^...^ -# [x] Subscript ~...~ -# [ ] Footnote [^...] (with user customizations) -# [x] Task list - [ ] (readonly) -# [ ] Definition lists -# [ ] Single line -# [ ] Multi-line -# [x] Fenced code blocks ```lang ``` -# [ ] Tables -# [x] Links -# [x] Link destination ( inside anchors) -# [x] Nested links -# -# No character classes have been used, since they aren't supported on every system - -function empty(value) { - return value == "" -} - -function PNappnd(value) { - if (emptyP1) P2 = P2 ORS value - else P1 = P1 ORS value -} - -function _next() { - __buffered_print() - not_paragraph = 1 - next -} - -function sub_match(replacement) { - $0 = substr($0, 1, RSTART - 1) replacement substr($0, RSTART + RLENGTH) -} - -function char(string, ind) { - if (1 > ind || ind > length(string)) { - return ""; - } - return substr(string, ind, 1) -} - -function backup_match() { - backup_rstart = RSTART - backup_rlength = RLENGTH -} -function restore_match() { - RSTART = backup_rstart - RLENGTH = backup_rlength -} - -BEGIN { - ## This is used to convert email autolinks into a series of hex values - ## Escaped output of: awk 'BEGIN {for (i = 32; i <= 126; ++i){ printf("%c", i) } }' - ascii = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~" - for (i = 1; i < 97; ++i) { - char_to_dec[char(ascii, i)] = 31 + i - } - - ## We do character escaping by substituting the escape with some - ## "useless" characters, which we're confident won't appear in actual text. - ## These are from decimal value 14 to 28 inclusive. They're blank characters, - ## related to shifting, device control and data transmission. - escapable = "\\`*_{}[]()#+-.!" - for (i = 1; i < 16; ++i) { - escapes[char(escapable, i)] = sprintf("%c", 13 + i) - } -} - -{ - not_paragraph_p1 = not_paragraph - not_paragraph = 0 - empty0 = empty($0) - emptyP1 = empty(P1) - list_started = 0 -} - -# Output is buffered on multiple lines, held in these variables: -# P2 = last non-empty line before the previous 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]+/ { - match($0, /^[ \t]*/) - if (last_html || RLENGTH - 2 * list_level < 4) { - last_html = 1 - _next() - } -} -{ - last_html = 0 -} - -# Fenced code blocks -fenced_codeblock { - $0 = " " $0 -} - -# Code blocks (ending) -inside_codeblock && (/ ```/ || ! /^( |\t|[ \t]*$)/) { - PNappnd("

    ") - inside_codeblock = 0 - emptyP1 = 1 - not_paragraph = 1 - if (fenced_codeblock) { - fenced_codeblock = 0 - fenced_class = "" - next - } -} - -# Link definition -!inside_codeblock && /^\[[^\]]+\]:[ \t]+.*/ { - next -} - -# Escapes -!inside_codeblock && /\\[-\\`*_{}\[\]()#+.!]/ { - while (match($0, /\\[-\\`*_{}\[\]()#+.!]/)) { - sub_match(escapes[char($0, RSTART + 1)]) - } -} - -# Horizontal rule -/^(---+|- - -( -)*|\*\*\*+|\* \* \*( \*)*|___+|_ _ _( _)*)$/ { - $0 = "
    " - not_paragraph = 1 -} - -# Blockquotes -blockquote_level > 0 && empty0 { - if (inside_codeblock) { - P1 = P1 ORS "" - inside_codeblock = 0 - } - for (P1 = P1 ORS; blockquote_level > 0; --blockquote_level) { - P1 = P1 "" - } - not_paragraph_p1 = 1 -} -/^>[ \t]/ { - match($0, /^[> \t]*> /) - level = substr($0, RSTART, RLENGTH) - $0 = substr($0, RSTART + RLENGTH) - gsub(/[ \t]/, "", level) - level = length(level) - - for (;blockquote_level < level; ++blockquote_level) { - P1 = P1 "
    " - } - if (blockquote_level > level) { - if (inside_codeblock) { - P2 = P2 ORS "" - inside_codeblock = 0 - } - P2 = P2 ORS - } - for (;blockquote_level > level; --blockquote_level) { - P2 = P2 "
    " - } - not_paragraph = 1 -} -/^>$/ { $0 = ""; not_paragraph = 1 } - -# Lists -function end_list() { - if (list_level == 0) return - PNappnd("
  • ") - --list_level -} -function start_list(level, type, shift) { - ## Every two columns we define a new level - level = int((level + 2) / 2) - ## If list definition is the first line inside a code block - if (level - list_level >= 3) return - - ## Make list_level = level - 1 - while (list_level < level - 1) { - lists[++list_level] = "" - } - ## Make list_level = level - while (list_level > level) { - end_list() - } - - if (list_level == level && lists[list_level] != type) { - end_list() - } - if (list_level < level || lists[list_level] != type) { - P1 = P1 ORS "<" (type == "." ? "o" : "u") "l>
  • " - lists[level] = type - lists_shift[level] = shift - list_level = level - } - else { - PNappnd("
  • ") - P1 = P1 ORS "
  • " - } - not_paragraph = 1 - list_started = 1 -} -!inside_codeblock && /^[ \t]*[*+-] / { ## Unordered list - match($0, /^[ \t]*[*+-] /) - level = RLENGTH - 2 - type = char($0, RSTART + RLENGTH - 2) - match($0, /^[^*+-]*[*+-] /) - shift = RLENGTH - sub_match("") - - start_list(level, type, shift) -} -!inside_codeblock && /^[ \t]*[0-9]+\. / { ## Ordered list - match($0, /^[ \t]*/) - level = RLENGTH - type = "." - match($0, /^[^.]+\. /) - shift = RLENGTH - sub_match("") - - start_list(level, type, shift) -} -list_level > 0 && !list_started && /[^ \t]/ { ## Non-list line - match($0, /^[ \t]*/) - level = int((RLENGTH + 2) / 2) - not_paragraph = 1 - sub("^[ \t]{1," lists_shift[list_level] "}", "") - - while (list_level >= level && emptyP1) { - end_list() - not_paragraph = 0 - } -} - -# Checklists -list_started && /^\[[x ]\][ \t]/ { - checked = (char($0, 2) == "x") ? " checked=\"checked\"" : ""; - $0 = "" -} - -# Fenced code blocks -!inside_codeblock && /^[ \t]*```/ { - fenced_codeblock = 1 - - match(P1, /```[^ \t]*/) - if (RLENGTH > 3) { - fenced_class = " class=\"language-" tolower(substr(P1, RSTART + 3, RLENGTH - 3)) "\"" - } - - next -} - -# Code blocks -/^( |\t)/ { - ## Autoescape & and < - for (i = 1; match(substr($0, i), /&/); i += RSTART) { - RSTART += i - 1 - sub_match("&") - } - gsub(//, "\\>") - - if (fenced_codeblock) { - $0 = substr($0, 5) - } - if (!inside_codeblock) { - $0 = "
    " $0
    -		inside_codeblock = 1
    -	}
    -
    -	_next()
    -}
    -
    -# Automatic links
    -/<[^ \t<>]+>/ {
    -	## URL: 
    -	while (match($0, /<[A-Za-z][-A-Za-z0-9+.]*:[^ \t<>]+>/)) {
    -		href = substr($0, RSTART + 1, RLENGTH - 2)
    -		sub_match("" href "")
    -	}
    -
    -	## Email addresses, with support for quoted local part "..."@...
    -	## and ip address literal ...@[...]
    -	while (match($0, /<([^ \t<>]+|".+")@([-A-Za-z0-9]+\.[^ \t<>]+|\[[0-9A-Fa-f.:]+\])>/)) {
    -		href = substr($0, RSTART + 1, RLENGTH - 2)
    -		href_masked = ""
    -		## The original markdown blogpost calls for convertions of all
    -		## email characters into their hex values. Allegedly, this fools
    -		## many autoharvesting spam bots.
    -		for (i = length(href); i > 0; --i) {
    -			href_masked = sprintf("&#x%X;", char_to_dec[char(href, i)]) href_masked
    -		}
    -		## href="mailto:..."
    -		sub_match("" href_masked "")
    -	}
    -}
    -
    -# Autoescape &
    -/&/ {
    -	for (i = 1; match(substr($0, i), /&/); i += RSTART) {
    -		RSTART += i - 1
    -		if (substr($0, RSTART) ~ /&[A-Za-z0-9#]+;/) { continue }
    -		sub_match("&")
    -	}
    -}
    -# Autoescape <
    -/ < / {
    -	gsub(/ < /, "\\<")
    -}
    -
    -# Line break
    -/  $/ {
    -	sub(/[ \t]*  $/, "
    ") -} - -# atx-style headers -/^[ \t]*#+/ { - match($0, /#+/) - if (RLENGTH <= 6) { - ## Remove prefix/suffix - gsub(/^[ \t]*#+[ \t]*/, "") - gsub(/[ \t]*#+[ \t]*$/, "") - - $0 = "" $0 "" - not_paragraph = 1 - } -} - -# setex-style headers -!emptyP1 && /^[ \t]*(=|-)+[ \t]*$/ { - hlevel = 1 + ($0 ~ /-/) - P1 = "" substr(P1, 4) "" - $0 = "" - not_paragraph = 1 -} - -# Emphasis -function surround(brace, left, right, blen) { - if (blen == "") { blen = length(brace) } - if (match($0, brace (brace == "``" ? "." : "[^" brace "]") "+" brace)) { - elem = left substr($0, RSTART + blen, RLENGTH - 2*blen) right - if (brace == "``") { gsub("`", "\\`", elem) } - sub_match(elem) - return 1; - } - return 0 -} -function gsurround(brace, left, right, blen) { - while (surround(brace, left, right, blen)) {} - - if (blen == 1) { - gsub("\\\\", "", brace) - ## Surrounding a single character span with spaces should make it a - ## literal char. This is inelegant but functional. - gsub(" " left " ", " " brace " ") - gsub(" " right " ", " " brace " ") - } -} - -{ - ## Make sure is on the inside for long chains - ## E.g. *****a***** will be the same as ****_a_**** - surround("___" , "", "") - surround("\\*\\*\\*", "", "", 3) - - gsurround("__" , "", "") - gsurround("_" , "" , "") - gsurround("\\*\\*", "", "", 2) - gsurround("\\*" , "" , "", 1) - gsurround("``" , "" , "") - gsurround("`" , "" , "") - - gsurround("~~" , "" , "") - gsurround("~" , "" , "") - gsurround("\\^", "" , "", 1) -} - -# Links -/!?\[[^\]]*\] ?[(\[][^)\]]*[\])]/ { - while (match($0, /!?\[[^\]]*\] ?[(\[][^)\]]*[\])]/)) { - link = substr($0, RSTART, RLENGTH) - backup_match() - - ## ](xxxxx) - match(link, /\] ?[(\[][^)\]]*[\])]$/) - has_space = char(link, RSTART + 1) == " " - is_img = char(link, 1) == "!" - - ## ...[xxxxx](...)... - text = substr(link, 2 + is_img, RSTART - 2 - is_img) - ## ...[...](xxxxx)... - href = substr(link, RSTART + has_space + 2, RLENGTH - has_space - 3) - - title = "\"\"" - if (char(link, RSTART + RLENGTH - 1) == "]") { ## is reference link - if (href == "") href = text # [...][] - ## [...]: ... "xxxxx" - title = reference[href, "title"] - ## [...]: xxxxx "..." - href = reference[href, "href"] - } - else if (match(href, /[ \t]"[^"]*"[ \t]*$/)) { ## has title - ## ...[...](... "xxxxx")... - title = substr(href, RSTART + 1, RLENGTH - 1) - ## ...[...](xxxxx "...")... - href = substr(href, 1, RSTART - 1) - } - - if (is_img) elem = "\""" - else elem = "" text "" - - restore_match() - sub_match(elem) - } -} - -# Paragraph -emptyP1 && !empty0 && !not_paragraph { $0 = "

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

    " } - -# After all $0 parsing -{ - __buffered_print() -} -function __buffered_print() { - empty0 = empty($0) - emptyP1 = empty(P1) - - if (empty0 && emptyP1) { - ++P1_repeats - } - else if (!empty0 && emptyP1) { - P1 = $0 - } - else { - print P2 - while (P1_repeats > 0) { - print "" - --P1_repeats - } - P2 = P1 - P1 = $0 - if (empty0) { - P1_repeats = 1 - } - } - - if (!empty(N1)) { - if (emptyP1) { - P1 = N1 - } - else { - print P2 - while (P1_repeats > 0) { - print "" - --P1_repeats - } - P2 = P1 - P1 = N1 - N1 = "" - } - } - - ## Fix escaped characters - for (i = 1; i < 16; ++i) { - esc = char(escapable, i) - gsub(escapes[esc], esc, P2) - } -} - -END { - 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