Some shells set the line background for complete terminal width instead of only the available characters to print
405 lines
12 KiB
Bash
Executable File
405 lines
12 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; 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
|
|
unfix
|
|
else
|
|
fix
|
|
fi
|
|
format_prefix="${FO_CODEBLOCK}"
|
|
format_suffix="${FO_RESET}"
|
|
elif isfix; then
|
|
format_prefix="${FO_CODEBLOCK}"
|
|
format_suffix="${FO_RESET}"
|
|
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 "$@"
|