Files
catmd/catmd.sh

353 lines
10 KiB
Bash
Executable File

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