#!/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; indent_list= 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})([[:blank:]]*)(.*)' 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='([^<]*<)([^>]*)(>)(.*)' TEXT_INDENT=0 LIST_INDENT_LAST= LINE_INDENT= LINE_INDENT_N= LINE_EMPTY_N= 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 ((LINE_EMPTY_N++)) # End quote [ $flag_quote -ne 0 ] && flag_quote=0 && unfix # End text indent [ $TEXT_INDENT -ne 0 ] && TEXT_INDENT=0 # 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 LINE_INDENT_N=${#BASH_REMATCH[1]} LINE_INDENT="${BASH_REMATCH[1]}" LINE_EMPTY_N=0 # List ending if [ $flag_list -ne 0 ] && [ $LINE_INDENT_N -le 1 ] && [ $TEXT_INDENT -eq 0 ]; then rex_list="$rex_list_start" flag_list=0 indent_list= LIST_INDENT_LAST= fi fi fall || break;; 2) ## List ## if [[ "$tocheck" =~ $rex_list ]]; then indent_list[$flag_list]="$LINE_INDENT" LIST_INDENT_LAST="$LINE_INDENT" if (($flag_list > 0 )); then local level=$((${#LINE_INDENT} - ${#indent_list[$flag_list - 1]})) #echo -n $level #if (($level > 1 )); then # echo -n ">]" #elif (($level < -1 )); then # echo -n "<]" #else # echo -n "=]" #fi fi # Detect indent TODO detect levels of indent #local tab=$(printf '\t') #case "$LINE_INDENT" 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 ((flag_list++)) rex_list="$rex_list_level" local indent= [ $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 # Start of new list flag_list=0 indent_list= LIST_INDENT_LAST= rex_list="$rex_list_start" continue elif [ $flag_list -ne 0 ] && [ $LINE_INDENT_N -le 1 ] && [ $TEXT_INDENT -ne 0 ]; then printf "%-${TEXT_INDENT}s" " " 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) ## Code block started with spaces if [[ "$tocheck" =~ $rex_codeinline_space ]]; then if [ $LINE_INDENT_N -eq 4 ] && [ $flag_list -ne 0 ]; then # First line of text paragraph inside a list item # signaling indentation for the following lines TEXT_INDENT=$LINE_INDENT_N else # Code block started with 4 spaces TODO 2 tabs if [ $flag_list -ne 0 ] && [ $LINE_INDENT_N -ge 8 ]; then # Indent code line according to list level printf "%-4s%s" " " "$LIST_INDENT_LAST" # Remove 4 leading spaces printf "%b%s" "$FO_CODEBLOCK" "${BASH_REMATCH[2]#' '}" else # Print code related spaces printf "%b%s" "$FO_CODEBLOCK" "${BASH_REMATCH[2]}" fi format_suffix="$FO_RESET" printf "%b%s" "$FO_CODEBLOCK" "${BASH_REMATCH[3]}" line= unfix fi fi fall || break;; 7) ## Inline code blocks ## if [[ "$tocheck" =~ $rex_codeinline ]]; then if isfix; then local lazym="${tocheck%"${BASH_REMATCH[0]}"}" if [ -z "$lazym" ]; then printf "%b%s%b" "$FO_CODEBLOCK" "${BASH_REMATCH[1]}${BASH_REMATCH[2]}" "${FO_RESET}" tocheck="${BASH_REMATCH[3]}" else printf "%b%s%b" "$FO_CODEBLOCK" "${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 isfix; then # Open inline block at end of line format_suffix="$FO_RESET" fall || break fi fall;; 8) ## 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;; 9) ## Links within one line ## if [[ "$tocheck" =~ $rex_link ]]; then 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;; 10) ## Auto-links within one line ## if [[ "$tocheck" =~ $rex_autolink ]]; then 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() { for arg in "$@"; do case "$1" in -) # treat - as input stream break;; -*) # Ignore args for now shift;; esac done local line= local myargs=("$@") # Use standard input if now files are present [ $# -eq 0 ] && myargs[0]="/dev/stdin" for file in "${myargs[@]}"; do [ "$file" == "-" ] && file="/dev/stdin" while IFS='' read -r line; do parseline "$line" done <"${file}" # If last line in a file has no newline, read ends but still populates $line [[ $line ]] && parseline "$line" echo -en "$FO_RESET" done } ## Font formating FO_RESET=$(tput sgr0) 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) ## 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 "$@"