diff --git a/catmd.sh b/catmd.sh new file mode 100755 index 0000000..fadee5e --- /dev/null +++ b/catmd.sh @@ -0,0 +1,340 @@ +#!/bin/bash + +DEBUG=: +#DEBUG=echo + +#INDENT_SPACE_SHORT=2 +#INDENT_SPACE=4 +#INDENT_TAB=2 +#rex_nonemptyline_indent='^([ ]{'$INDENT_SPACE_SHORT'}|[ ]{'$INDENT_SPACE'}|[\ ]{'$INDENT_TAB'})[[:graph:]]+' # for $INDENT_SPACE spaces or $INDENT_TAB tabs + +rex_emptyline='^$' +rex_nonemptyline='^([[:blank:]]*)([[:graph:]]+.*)' +rex_header='^[[:blank:]]*(#+)[[:blank:]]*(.*)' +rex_list_start='^([[:blank:]]{0,1})([*+-]{1} +|[[:digit:]]+\.)(.*)'; flag_list=0 +rex_list_level='^([[:blank:]]{2,})([*+-]{1} +|[[:digit:]]+\.)(.*)' +rex_list="$rex_list_start" +rex_codeblock='^[[:blank:]]*(```)(.*)' +rex_codeinline_start='([^`]*)(`{1,2})(.*)' +rex_codeinline_space='(^[[:blank:]]{4})(.*)' +rex_codeinline1='([^`]*)(`)(.*)' +rex_codeinline2='(``)(.*)$' # first part of $line: ${line%"${BASH_REMATCH[0]}"} +rex_codeinline="$rex_codeinline_start" +rex_quote='^([[:blank:]]*>)(.*)'; flag_quote=0 +rex_emphasis_start='([^*_]*)([*_]{1,2})([^ ].*)' +rex_emphasis_end='([^*_]*)([*_]{1,2})(.*)' +rex_emphasis="$rex_emphasis_start" +rex_link='([^[]*\[+)([^]]*)(\]+ *[\([])([^]\)]*)([]\)].*)' +rex_autolink='([^<]*<)([^>]*)(>)(.*)' + +LINE_INDENT= +LINE_INDENT_LEVEL=0 +PARSE_STATE=0 +PARSE_FIX=0 +FIX_STATE=0 +UNFIX_STATE=0 + +# Next parsted line is directed directly to the fixed state +fix() { + PARSE_FIX=1 + FIX_STATE=$PARSE_STATE + [ ! -z "$1" ] && PARSE_STATE=$1 +} +isfix() { [ $PARSE_FIX -ne 0 ] && [ $FIX_STATE -eq $PARSE_STATE ]; return $?; } +# Unfix and/or stop parsing for this line +unfix() { + UNFIX_STATE=1 + PARSE_FIX=0 + FIX_STATE=0 +} +isunfix() { [ $UNFIX_STATE -ne 0 ]; return $?; } +# Decides when to got the next parser stop +# Called usually like: fall || break +fall() { + if isunfix; then + $DEBUG -n "U]" + UNFIX_STATE=0 + PARSE_STATE=0 + return 1 + fi + # Fall to next PARSE_STATE if not fixed + if [ $PARSE_FIX -eq 0 ]; then + ((PARSE_STATE++)) + return 0 + fi + if [ $PARSE_STATE -ne $FIX_STATE ]; then + PARSE_STATE=$FIX_STATE + return 0 + else + $DEBUG -n "B]" + return 1 + fi +} + +parseline() { + local line="$1" + local tocheck="$1" + local format_prefix= + local format_suffix= + + [ $PARSE_FIX -eq 0 ] && PARSE_STATE=0 + + while : ; do + $DEBUG -n "$PARSE_STATE]" + case "$PARSE_STATE" in + 0) ## Empty line ## + if [[ "$tocheck" =~ $rex_emptyline ]]; then + # End quote + [ $flag_quote -ne 0 ] && flag_quote=0 && unfix + + # TODO? Catch rouge formates with empty lines outside codeblocks + echo -ne "$FO_RESET" + format_prefix="" + format_suffix="" + fi + fall || break;; + 1) ## Non empty line ## + if [[ "$tocheck" =~ $rex_nonemptyline ]]; then + # Detect indent TODO detect levels of indent + local tab=$(printf '\t') + case "${BASH_REMATCH[1]}" in + "${tab}${tab}") + LINE_INDENT="${BASH_REMATCH[1]}" + $DEBUG -n "2t]";; + ' ') + LINE_INDENT="${BASH_REMATCH[1]}" + $DEBUG -n "4s]";; + ' ') + LINE_INDENT="${BASH_REMATCH[1]}" + $DEBUG -n "2s]";; + *) + LINE_INDENT= + esac + # List ending + if [ $flag_list -ne 0 ] && [ -z "$LINE_INDENT" ]; then + rex_list="$rex_list_start" + flag_list=0 + fi + fi + fall || break;; + 2) ## List ## + if [[ "$tocheck" =~ $rex_list ]]; then + local indent= + ((flag_list++)) + rex_list="$rex_list_level" + [ $flag_list -gt 1 ] && indent="$LINE_INDENT" #"${BASH_REMATCH[1]}" + printf "%s%b%s%b" "$indent" "$FO_LISTITEM" "${BASH_REMATCH[2]}" "$FO_RESET" + tocheck="${BASH_REMATCH[3]}" + line="$tocheck" + elif [[ "$tocheck" =~ $rex_list_start ]]; then + flag_list=1 + rex_list="$rex_list_start" + continue + fi + fall || break + ;; + 3) ## Header ## + if [[ "$tocheck" =~ $rex_header ]]; then + format_prefix="${FO_HEADLINE}" + format_suffix="${FO_RESET}" + unfix + fi + fall || break;; + 4) ## Quotes + if [[ "$tocheck" =~ $rex_quote ]] || isfix; then + # TODO continue parsing quoted string + fix 0 + flag_quote=1 + format_prefix="${FO_QUOTE}" + format_suffix="${FO_RESET}" + break + else + fall || break + fi + ;; + 5) ## Multiline code blocks ## + if [[ "$tocheck" =~ $rex_codeblock ]]; then + if isfix; then + format_suffix="${FO_RESET}" + unfix + else + format_prefix="${FO_CODEBLOCK}" + fix + fi + fi + fall || break;; + 6) ## Inline code blocks ## + if [[ "$tocheck" =~ $rex_codeinline ]]; then + if isfix; then + local lazym="${tocheck%"${BASH_REMATCH[0]}"}" + if [ -z "$lazym" ]; then + printf "%s%b" "${BASH_REMATCH[1]}${BASH_REMATCH[2]}" "${FO_RESET}" + tocheck="${BASH_REMATCH[3]}" + else + printf "%s%b" "${lazym}${BASH_REMATCH[1]}" "${FO_RESET}" + tocheck="${BASH_REMATCH[2]}" + fi + rex_codeinline="$rex_codeinline_start" + unfix + else + if [ '``' == "${BASH_REMATCH[2]}" ]; then + rex_codeinline="$rex_codeinline2" + else + rex_codeinline="$rex_codeinline1" + fi + printf "%s%b%s" "${BASH_REMATCH[1]}" "${FO_CODEBLOCK}" "${BASH_REMATCH[2]}" + tocheck="${BASH_REMATCH[3]}" + fix + fi + line="$tocheck" + elif [[ "$tocheck" =~ $rex_codeinline_space ]]; then + # TODO check for list indentation + format_suffix="$FO_RESET" + printf "%b%s" "$FO_CODEBLOCK" "${BASH_REMATCH[2]}" + line= + break + elif isfix; then + # Open inline block at end of line + fall || break + fi + fall;; + 7) ## Inline emphasis start + if [[ "$tocheck" =~ $rex_emphasis ]]; then + local fo_emphasis=$FO_EMPHASIS1 + if isfix; then + printf "%s%b" "${BASH_REMATCH[1]}${BASH_REMATCH[2]}" "${FO_RESET}" + rex_emphasis="$rex_emphasis_start" + unfix + else + if [ '**' == "${BASH_REMATCH[2]}" ] || [ '__' == "${BASH_REMATCH[2]}" ]; then + fo_emphasis=$FO_EMPHASIS2 + fi + printf "%s%b%s" "${BASH_REMATCH[1]}" "${fo_emphasis}" "${BASH_REMATCH[2]}" + rex_emphasis="$rex_emphasis_end" + fix + fi + tocheck="${BASH_REMATCH[3]}" + line="$tocheck" + elif isfix; then + # Open inline block at end of line + fall || break + fi + fall;; + 8) ## Links within one line ## + if [[ "$tocheck" =~ $rex_link ]]; then + #echo -n "m]" + #echo "${BASH_REMATCH[0]}" + #echo "${BASH_REMATCH[1]} ! ${BASH_REMATCH[2]} ! ${BASH_REMATCH[3]} ! ${BASH_REMATCH[4]} ! ${BASH_REMATCH[5]}" + printf "%s%b%s%b%s%b%s%b%s" "${BASH_REMATCH[1]}" "${FO_LINKTEXT}" "${BASH_REMATCH[2]}" "${FO_RESET}" "${BASH_REMATCH[3]}" "${FO_LINKURL}" \ + "${BASH_REMATCH[4]}" "${FO_RESET}" + tocheck="${BASH_REMATCH[5]}" + line="$tocheck" + [ ! -z "$tocheck" ] && fix + elif isfix; then + unfix + fall || break + fi + fall;; + 9) ## Auto-links within one line ## + if [[ "$tocheck" =~ $rex_autolink ]]; then + #echo -n "m]" + #echo "${BASH_REMATCH[0]}" + #echo "${BASH_REMATCH[1]} ! ${BASH_REMATCH[2]} ! ${BASH_REMATCH[3]} ! ${BASH_REMATCH[4]} ! ${BASH_REMATCH[5]}" + printf "%s%b%s%b%s" "${BASH_REMATCH[1]}" "${FO_LINKURL}" "${BASH_REMATCH[2]}" "${FO_RESET}" "${BASH_REMATCH[3]}" + tocheck="${BASH_REMATCH[4]}" + line="$tocheck" + [ ! -z "$tocheck" ] && fix + elif isfix; then + unfix + fall || break + fi + fall;; + *) ## Regular text + $DEBUG -n "e]$tocheck" + break;; + esac + done + + printf "%b%s%b" "$format_prefix" "$line" "$format_suffix" + printf "\n" +} + +catmd() { + local line= + for arg in "$@"; do + case "$1" in + -*) + # Ignore args for now + shift;; + esac + done + local file="$1" + + while IFS='' read -r line; do + parseline "$line" + done <"${file:-/dev/stdin}" + echo -en "$FO_RESET" +} + +## Font formating +FO_BLINK=$(tput blink) # '\033[5m' +FO_BOLD=$(tput bold) # '\033[1m' +FO_SO=$(tput smso) # '\033[7m' +FO_SO_E=$(tput rmso) # '\033[27m' +FO_UL=$(tput smul) # '\033[4m' +FO_UL_E=$(tput smul) # '\033[24m' +FO_INVIS=$(tput invis) # '\033[8m' +## Rareley supported +FO_DIM=$(tput dim) +FO_REV=$(tput rev) +FO_RESET=$(tput sgr0) + +## Colorcodes +## +## '\033[Xm' - Basic 8 colors +## X = 30..37 foreground +## = 40..47 background +## = Black, Red, Green, Yellow, Blue, Magenta, Cyan, Light gray +## = 39 = default foreground +## = 49 = default background +## - Basic "high contrast" colors +## X = 90...97 foreground +## = 100..107 background +## = Dark grey, Light red, green, yellow, blue, magenta, cyan, white +## - Format +## X: 0 = reset, 1 = bold, 2 = dim, +## 4 = underline, 5 = slow blink, +## 7 = Reverse, 8 = hidden +## 20+X: Reset of formating +## e.g. 21 = reset bold +## '\033[38;5;Xm' - xterm-256 foreground colors X = 0..255 +## '\033[48;5;Xm' - xterm-256 foreground colors X = 0..255 +COB_BLACK='\033[40m' +COB_LIGHTGRAY='\033[100m' +COF_BLACK='\033[0;30m' +COF_DARKGRAY='\033[1;30m' +COF_RED='\033[0;31m' +COF_LIGHTRED='\033[1;31m' +COF_GREEN='\033[0;32m' +COF_LIGHTGREEN='\033[1;32m' +COF_ORANGE='\033[0;33m' +COF_YELLOW='\033[1;33m' +COF_BLUE='\033[0;34m' +COF_LIGHTBLUE='\033[1;34m' +COF_PURPLE='\033[0;35m' +COF_LIGHTPURPLE='\033[1;35m' +COF_CYAN='\033[0;36m' +COF_LIGHTCYAN='\033[1;36m' +COF_LIGHTGRAY='\033[0;37m' +COF_WHITE='\033[1;37m' + +FO_HEADLINE="$FO_BOLD$FO_SO" +FO_LISTITEM="$COF_ORANGE" +FO_EMPHASIS1="${COF_LIGHTGRAY}" +FO_EMPHASIS2="${COF_WHITE}" +FO_LINKTEXT="${COF_LIGHTGRAY}" +FO_LINKURL="${FO_UL}" +FO_CODEBLOCK="$COF_LIGHTGRAY$COB_LIGHTGRAY" +FO_QUOTE='\033[96;100m' + +catmd "$@"