#!/bin/awk -f # HAT TRICK # Copyright (C) 2022 Case Duckworth # BEGIN { # Configuration DEFAULT_CONFIG_MODE = "config" config_initialize() config_parse(ENVIRON["HT_CONFIG"] ? ENVIRON["HT_CONFIG"] : "ht.conf") # State DEFTAG = CONFIG["default_tag"] DEFATTR = CONFIG["default_attr"] TAG = DEFTAG ATTR = DEFATTR } # Handle raw sections $0 ~ CONFIG["raw_delim"] { RAW = ! RAW if (RAW) { buflush() bufpush(CONFIG["raw_beg"], -1) } else { if (substr(BUFFER, length(BUFFER)) == "\n") { BUFFER = substr(BUFFER, 1, length(BUFFER) - 1) } bufpush(CONFIG["raw_end"], -1) print BUFFER BUFFER = "" } next } # Shell expansion escape hatch { gsub(/\$\$/, "$\a", $0) gsub(/\$[^\a]/, "\\\\&", $0) gsub(/\$\a/, "$", $0) } RAW { bufpush(html_escape($0)) next } # Comments $0 ~ ("^" COMMENT_DELIM) { next } # HTML escape hatch /^[ \t]*")) { parent = substr(BLOCK_TYPES[bt], 1, RSTART - 1) child = substr(BLOCK_TYPES[bt], RSTART + RLENGTH) } else { parent="" child="" } if (parent) { split(parent, pa, FS) split(child, bl, FS) if (! IN_PARENT) { IN_PARENT = pa[1] } TAG = IN_PARENT ATTR = "" for (i = 2; i <= length(pa); i++) { ATTR = ATTR (ATTR ? " " : "") pa[i] } bufpush("<" child ">" $0 "") next # XXX: This is messy. } else { split(BLOCK_TYPES[bt], bl, FS) if (IN_PARENT) { bufpush("") IN_PARENT = "" } if (! BUFFER) { TAG = bl[1] for (b = 2; b <= length(bl); b++) { ATTR = ATTR (ATTR ? " " : "") bl[b] } } else { $0 = "<" BLOCK_TYPES[bt] ">" $0 "" } } } } # Loop through LINE_TYPES for (lt in LINE_TYPES) { if (match($0, "^" lt "[ \t]*")) { $0 = substr($0, RSTART + RLENGTH) # Escape & in $0 so awk doesn't choke gsub(/&/, "\\\\&", $0) # Expand the template templ = LINE_TYPES[lt] while (match(templ, /\$[0-9-]+/)) { if (substr(templ, RSTART + 1, 1) == "-") { # Up to $field f = "" n = substr(templ, RSTART + 2, RLENGTH - 2) for (i = 1; i <= n; i++) { f = f (f ? " " : "") $i } sub(/\$[0-9-]+/, f, templ) } else if (substr(templ, RSTART + RLENGTH - 1, 1) == "-") { # $Field to end f = "" n = substr(templ, RSTART + 1, RLENGTH - 2) for (i = n; i <= NF; i++) { f = f (f ? " " : "") $i } sub(/\$[0-9-]+/, f, templ) } else { sub(/\$[0-9]+/, $(substr(templ, RSTART + 1, RLENGTH - 1)), templ) } } $0 = templ } } # Escape backslash and ` gsub(/`/, "\\`", $0) # Push to buffer bufpush($0, sep) } # Blank lines end blocks /^$/ { if (HTML) { html_end() } if (! RAW) { buflush() } } # Clean up END { if (HTML) { html_end() } else if (RAW) { bufpush(CONFIG["raw_end"], -1) print BUFFER } else { buflush() } } ### Buffer-y functions function buflush() { buftrim() if (BUFFER) { if (TAG) { TAG_BEG = "<" TAG (ATTR ? " " ATTR : "") ">" TAG_END = "" } print TAG_BEG BUFFER TAG_END BUFFER = "" TAG = DEFTAG ATTR = DEFATTR IN_PARENT = "" } } function bufpush(text, separator) { if (! separator) { separator = "\n" } if (separator == -1) { separator = "" } BUFFER = BUFFER text (separator ? separator : "") } function buftrim() { if (match(BUFFER, "\n+$")) { BUFFER = substr(BUFFER, 1, RSTART - 1) } } ### Config functions function config_initialize() { COMMENT_DELIM = ";" CONFIG["raw_delim"] = "```" CONFIG["raw_beg"] = "
"
	CONFIG["raw_end"] = "
" CONFIG["default_tag"] = "p" CONFIG["default_attr"] = "" LINE_TYPES["@"] = "$2" LINE_TYPES["`"] = "$0" BLOCK_TYPES["#"] = "h1" BLOCK_TYPES["##"] = "h2" BLOCK_TYPES["###"] = "h3" BLOCK_TYPES["-"] = "ul>li" BLOCK_TYPES["%"] = "ol>li" } function config_parse(file) { mode = DEFAULT_CONFIG_MODE while ((getline < file) > 0) { if (match($0, /^#/) || ! $0) { continue } if (match($0, /^\\/)) { $0 = substr($0, 2) } if (match($0, /\[[^\]]+\]/)) { mode = substr($0, RSTART + 1, RLENGTH - 2) continue } else { var = $1 val = "" for (i = 2; i <= NF; i++) { val = val (val ? " " : "") $i } if (mode == "config") { CONFIG[var] = val } else if (mode == "block") { BLOCK_TYPES[var] = val } else if (mode == "line") { LINE_TYPES[var] = val } } } } ### Other functions function html_end() { buftrim() print BUFFER BUFFER = "" HTML = 0 } function html_escape(text) { # Sanitize HTML gsub(/&/, "\\&", text) gsub(//, "\\>", text) return text }