#!/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 "$@"