mirror of
https://github.com/rwxrob/dot
synced 2024-11-16 21:25:29 +00:00
431 lines
7.3 KiB
Plaintext
431 lines
7.3 KiB
Plaintext
|
#!/usr/bin/env bash
|
||
|
|
||
|
# Originally written by https://github.com/deyloop/dot
|
||
|
|
||
|
# Formats Markdown tables so that they are readable in text form
|
||
|
# Parses the table in order to print the formatted form.
|
||
|
#
|
||
|
# Supposed to be used as an UNIX filter, where the unformatted table
|
||
|
# should be sent to `stdin` and the formatted output will be sent to
|
||
|
# `stdout`.
|
||
|
# If a parsing error occures, prints the unformatted table unchanged,
|
||
|
# and exits with code 42
|
||
|
#
|
||
|
# In order to debug, look at `__fail`
|
||
|
|
||
|
# Does all the parsing in pure bash, in a single process. Printing uses
|
||
|
# a few subshells as that was convenient.
|
||
|
|
||
|
# TODO: support escaping pipe characters
|
||
|
# TODO: skipping to just tables when whole markdown document is passed
|
||
|
# TODO: allow filename to be passed as argument
|
||
|
|
||
|
|
||
|
#-------------------------------------------------------------------------
|
||
|
# The Parsing Grammar used:
|
||
|
#
|
||
|
# table -> newline headingrow delimrow tablebody
|
||
|
#
|
||
|
# headingrow -> pipe? headings pipe? newline
|
||
|
# headings -> content pipe headingsB
|
||
|
# headingsB -> content / content pipe headingsB
|
||
|
# content -> [^pipe]*
|
||
|
#
|
||
|
# delimrow -> pipe? delims pipe? newline
|
||
|
# delims -> delim pipe delimsB
|
||
|
# delimsB -> delim / delim pipe delimsB
|
||
|
# delim -> noalign / leftalign / centeralign / rightalign
|
||
|
# noalign -> dash dash dashes
|
||
|
# leftalign -> colon dash dashes
|
||
|
# centeralign -> colon dashes colon
|
||
|
# rightalign -> dash dashes colon
|
||
|
#
|
||
|
# tablebody -> nothing / tablerow tablebody
|
||
|
# tablerow -> pipe? body pipe? newline
|
||
|
# body -> content pipe bodyB
|
||
|
# bodyB -> content / content pipe bodyB
|
||
|
#
|
||
|
# dashes -> dash / dash dashes
|
||
|
# pipe -> '|'
|
||
|
# dash -> '-'
|
||
|
# colon -> ':'
|
||
|
#-------------------------------------------------------------------------
|
||
|
|
||
|
|
||
|
fmttable() {
|
||
|
local in=/dev/stdin
|
||
|
|
||
|
local buf
|
||
|
buf="$(<"$in")"
|
||
|
buf+=$'\n'
|
||
|
|
||
|
local i=0
|
||
|
local c
|
||
|
|
||
|
local -a contents
|
||
|
local col=0
|
||
|
local row=0
|
||
|
local totalcols=0
|
||
|
local -a colwidths
|
||
|
local -a colaligns
|
||
|
|
||
|
# parsing
|
||
|
_parse_table
|
||
|
|
||
|
# print the headings
|
||
|
printf "|"
|
||
|
for (( x=0; x<totalcols; x++ )); do
|
||
|
local w="${colwidths[$x]}"
|
||
|
printf "%-${w}s|" "${contents[$x]}"
|
||
|
done
|
||
|
printf "\n"
|
||
|
|
||
|
# print the delims
|
||
|
printf "|"
|
||
|
for (( x=0; x<${totalcols}; x++ )); do
|
||
|
local con
|
||
|
case ${colaligns[$x]} in
|
||
|
left)
|
||
|
con=":$(for ((i=0; i<$((${colwidths[$x]}-1)); i++)); do
|
||
|
printf "-";
|
||
|
done)"
|
||
|
;;
|
||
|
right)
|
||
|
con="$(for ((i=0; i<$((${colwidths[$x]}-1)); i++)); do
|
||
|
printf "-";
|
||
|
done):"
|
||
|
;;
|
||
|
center)
|
||
|
con=":$(for ((i=0; i<$((${colwidths[$x]}-2)); i++)); do
|
||
|
printf "-";
|
||
|
done):"
|
||
|
;;
|
||
|
none)
|
||
|
con="$(for ((i=0; i<${colwidths[$x]}; i++)); do
|
||
|
printf "-";
|
||
|
done)"
|
||
|
;;
|
||
|
esac
|
||
|
local w="${colwidths[$x]}"
|
||
|
printf "%-${w}s|" "$con"
|
||
|
done
|
||
|
printf "\n"
|
||
|
|
||
|
# print the body
|
||
|
for (( y=2; y<row; y++ )); do
|
||
|
printf "|"
|
||
|
for (( x=0; x<totalcols; x++ )); do
|
||
|
local w="${colwidths[$x]}"
|
||
|
local a=""
|
||
|
[[ ${colaligns[$x]} != right ]] && a="-"
|
||
|
printf "%${a}${w}s|" "${contents[$y*$totalcols+$x]}"
|
||
|
done
|
||
|
printf "\n"
|
||
|
done
|
||
|
}
|
||
|
|
||
|
__fail(){
|
||
|
printf "%s" "$buf"
|
||
|
echo
|
||
|
echo "$row:$col - $1"
|
||
|
exit 42
|
||
|
}
|
||
|
|
||
|
_parse_table() {
|
||
|
_parse_headingrow
|
||
|
_parse_delimrow
|
||
|
_parse_tablebody
|
||
|
}
|
||
|
|
||
|
|
||
|
_parse_headingrow(){
|
||
|
_nextchar
|
||
|
[[ $c != '|' ]] && _putback
|
||
|
|
||
|
_parse_headings
|
||
|
|
||
|
_nextchar
|
||
|
[[ $c != '|' ]] && _putback
|
||
|
|
||
|
_parse_newline
|
||
|
|
||
|
totalcols=$col
|
||
|
row=$((row+1))
|
||
|
}
|
||
|
|
||
|
_advance_col() {
|
||
|
shopt -s extglob
|
||
|
|
||
|
local trimed="${contents[$row*$totalcols+$col]}"
|
||
|
trimed="${trimed##+([[:space:]])}"
|
||
|
trimed="${trimed%%+([[:space:]])}"
|
||
|
contents[$row*$totalcols+$col]="$trimed"
|
||
|
|
||
|
local colw="${#trimed}"
|
||
|
local w="${colwidths[$col]}"
|
||
|
if [[ $colw -gt $w ]]; then colwidths[$col]="$colw"; fi
|
||
|
|
||
|
col=$((col+1))
|
||
|
}
|
||
|
|
||
|
_parse_headings() {
|
||
|
|
||
|
_parse_content
|
||
|
_advance_col
|
||
|
|
||
|
_parse_pipe
|
||
|
_parse_headingsB
|
||
|
|
||
|
}
|
||
|
|
||
|
_parse_content() {
|
||
|
_nextchar
|
||
|
if [[ $c = '|' || $c = $'\n' ]]; then
|
||
|
_putback
|
||
|
else
|
||
|
contents[$row*$totalcols+$col]+="$c"
|
||
|
|
||
|
_parse_content
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
_parse_pipe() {
|
||
|
_nextchar
|
||
|
[[ $c != '|' ]] && __fail "expected pipe, found '$c'"
|
||
|
}
|
||
|
|
||
|
_parse_headingsB() {
|
||
|
_parse_content
|
||
|
_advance_col
|
||
|
|
||
|
_nextchar
|
||
|
if [[ $c = '|' ]]; then
|
||
|
_nextchar
|
||
|
if [[ $c = $'\n' ]]; then
|
||
|
_putback
|
||
|
_putback
|
||
|
else
|
||
|
_putback
|
||
|
_putback
|
||
|
_parse_pipe
|
||
|
_parse_headingsB
|
||
|
fi
|
||
|
else
|
||
|
if [[ $c = $'\n' ]]; then
|
||
|
_putback
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
}
|
||
|
|
||
|
_parse_newline() {
|
||
|
_nextchar
|
||
|
[[ $c != $'\n' ]] && __fail "expected newline, found '$c'"
|
||
|
}
|
||
|
|
||
|
_parse_delimrow(){
|
||
|
col=0
|
||
|
_nextchar
|
||
|
[[ $c != '|' ]] && _putback
|
||
|
|
||
|
_parse_delims
|
||
|
|
||
|
_nextchar
|
||
|
[[ $c != '|' ]] && _putback
|
||
|
|
||
|
_parse_newline
|
||
|
|
||
|
row=$((row+1))
|
||
|
}
|
||
|
|
||
|
_parse_delims() {
|
||
|
_parse_delim
|
||
|
col=$((col+1))
|
||
|
|
||
|
_parse_pipe
|
||
|
|
||
|
_parse_delimsB
|
||
|
}
|
||
|
|
||
|
_parse_delimsB() {
|
||
|
_parse_delim
|
||
|
col=$((col+1))
|
||
|
|
||
|
_nextchar
|
||
|
if [[ $c = '|' ]]; then
|
||
|
_nextchar
|
||
|
if [[ $c = $'\n' ]]; then
|
||
|
_putback
|
||
|
_putback
|
||
|
else
|
||
|
_putback
|
||
|
_putback
|
||
|
_parse_pipe
|
||
|
_parse_delimsB
|
||
|
fi
|
||
|
else
|
||
|
if [[ $c = $'\n' ]]; then
|
||
|
_putback
|
||
|
fi
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
_parse_delim() {
|
||
|
local remainingbuf=${buf:$i}
|
||
|
local lookahead=${remainingbuf%%\|*}
|
||
|
|
||
|
if [[ $lookahead =~ ^[[:space:]]*:-+:[[:space:]]*$ ]]; then
|
||
|
colaligns[$col]="center"
|
||
|
_parse_centeralign
|
||
|
return
|
||
|
fi
|
||
|
if [[ $lookahead =~ ^[[:space:]]*:--+[[:space:]]*$ ]]; then
|
||
|
colaligns[$col]="left"
|
||
|
_parse_leftalign
|
||
|
return
|
||
|
fi
|
||
|
if [[ $lookahead =~ ^[[:space:]]*---+[[:space:]]*$ ]]; then
|
||
|
colaligns[$col]="none"
|
||
|
_parse_noalign
|
||
|
return
|
||
|
fi
|
||
|
if [[ $lookahead =~ ^[[:space:]]*--+:[[:space:]]*$ ]]; then
|
||
|
colaligns[$col]="right"
|
||
|
_parse_rightalign
|
||
|
return
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
_parse_skipspaces() {
|
||
|
_nextchar
|
||
|
if [[ $c = ' ' ]]; then
|
||
|
_parse_skipspaces
|
||
|
else
|
||
|
_putback
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
_parse_noalign() {
|
||
|
_parse_skipspaces
|
||
|
_parse_dash
|
||
|
_parse_dash
|
||
|
_parse_dashes
|
||
|
_parse_skipspaces
|
||
|
}
|
||
|
|
||
|
_parse_leftalign() {
|
||
|
_parse_skipspaces
|
||
|
_parse_colon
|
||
|
_parse_dash
|
||
|
_parse_dashes
|
||
|
_parse_skipspaces
|
||
|
}
|
||
|
|
||
|
_parse_centeralign(){
|
||
|
_parse_skipspaces
|
||
|
_parse_colon
|
||
|
_parse_dashes
|
||
|
_parse_colon
|
||
|
_parse_skipspaces
|
||
|
}
|
||
|
|
||
|
_parse_rightalign() {
|
||
|
_parse_skipspaces
|
||
|
_parse_dash
|
||
|
_parse_dashes
|
||
|
_parse_colon
|
||
|
_parse_skipspaces
|
||
|
}
|
||
|
|
||
|
_parse_dash() {
|
||
|
_nextchar
|
||
|
[[ $c != '-' ]] && __fail "expected dash, found '$c'"
|
||
|
}
|
||
|
|
||
|
_parse_dashes() {
|
||
|
_parse_dash
|
||
|
|
||
|
_nextchar
|
||
|
if [[ $c = '-' ]]; then
|
||
|
_putback
|
||
|
_parse_dashes
|
||
|
else
|
||
|
_putback
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
_parse_colon() {
|
||
|
_nextchar
|
||
|
[[ $c != ':' ]] && __fail "expected colon, fount '$c'"
|
||
|
}
|
||
|
|
||
|
_parse_tablebody(){
|
||
|
local bi=$((${#buf}-1))
|
||
|
if [[ $i -lt $bi ]]; then
|
||
|
_parse_tablerow
|
||
|
_parse_tablebody
|
||
|
else
|
||
|
return
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
_parse_tablerow() {
|
||
|
col=0
|
||
|
_nextchar
|
||
|
[[ $c != '|' ]] && _putback
|
||
|
|
||
|
_parse_body
|
||
|
|
||
|
_nextchar
|
||
|
[[ $c != '|' ]] && _putback
|
||
|
|
||
|
row=$((row+1))
|
||
|
_parse_newline
|
||
|
}
|
||
|
|
||
|
_parse_body() {
|
||
|
_parse_content
|
||
|
_advance_col
|
||
|
|
||
|
_parse_pipe
|
||
|
_parse_bodyB
|
||
|
}
|
||
|
|
||
|
_parse_bodyB() {
|
||
|
_parse_content
|
||
|
_advance_col
|
||
|
|
||
|
_nextchar
|
||
|
if [[ $c = '|' ]]; then
|
||
|
_nextchar
|
||
|
if [[ $c = $'\n' ]]; then
|
||
|
_putback
|
||
|
_putback
|
||
|
else
|
||
|
_putback
|
||
|
_putback
|
||
|
_parse_pipe
|
||
|
_parse_bodyB
|
||
|
fi
|
||
|
else
|
||
|
if [[ $c = $'\n' ]]; then
|
||
|
_putback
|
||
|
fi
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
_nextchar() {
|
||
|
if [[ $i -lt "${#buf}" ]]; then
|
||
|
c="${buf:$i:1}"
|
||
|
i=$((i+1))
|
||
|
else
|
||
|
__fail "Unexpectedly reached EOF"
|
||
|
fi
|
||
|
}
|
||
|
|
||
|
_putback() {
|
||
|
i=$((i-1))
|
||
|
}
|
||
|
|
||
|
fmttable
|