In an earlier post I demonstrated how to center a block of text in a terminal. Here we continue to build on that script to create a slide deck.

“/usr/share/figlet/fonts”

#!/bin/bash

main() {

  [[ -t 2  ]] \
    || ERX "this script must be executed in a terminal"

  [[ -f ${deckfile:=$*} ]] \
    || ERX "not a valid deck file: $*"
  
  _tw=$(tput cols)   # terminal width
  _th=$(tput lines)  # terminal height
  _fg="$(tput sgr0)" # default foreground

  _tmpdir=$(mktemp -d)
  mapfile -t slides <<< "$(formatfile "$deckfile")"
  
  while :; do for slide in "${slides[@]}"; do
    
    if [[ $slide =~ ^(@@(.+)@@[[:space:]]+)?(<[[:space:]]+)?(.+)$ ]]
    then

      page=""

      options=${BASH_REMATCH[2]:-}
      fromfile=${BASH_REMATCH[3]:-}
      text=${BASH_REMATCH[4]:-}
      
      [[ -n $fromfile ]] && [[ $text =~ ^~ ]] \
        && text="$HOME${text:1}"

      parse_options "$options"

      if [[ -z $fromfile ]]; then
        mapfile -d _ -t lines <<< "$text"
        for line in "${lines[@]}"; do
          block=$(figlet ${font:+-f ${font}} -w "$_tw" "$line")
          block=$(h_center "$block")
          page+=$(printf "\n%s" "$block")
        done

      elif [[ -f ${text} ]]; then
        page=$(< "$text")
        page=$(h_center "${page}")

      else
        continue
      fi
      
    fi

    if ((lolcat==1)); then
      v_center "$page" | lolcat -f
    else
      v_center "$page"
    fi
    
  done done | less -r
}

formatfile() {

  local file=$1
  
  awk -v tmpdir="$_tmpdir" '
    { skip = 0 }
    /^@@[^@]+@@\s*$/ || /_$/ {
      if (lastline ~ /@$/)
        lastline=lastline " "
      lastline=lastline $0 ; skip = 1
    }
    
    blockread {
      if (/^```$/) {
        $0 = "< " blockfile
        blockread = !blockread
      }
      else
        print >> blockfile
    }

    /^```$/ { 
      blockread = !blockread
      blockfile = tmpdir "/" i++ 
    }

    skip != 1 && /^[^#]/ && !/@@$/ && !blockread {
      
      if (lastline ~ /@$/)
        lastline=lastline " "

      print lastline $0
      lastline=""
      skip = 0
    }
  '  "$file"
}

parse_options() {

  local optstr="$1"

  foreground="$_fg"
  font=""
  lolcat=0

  [[ -n $optstr ]] && {
    mapfile -d : -t ao <<< "$optstr"
    for o in "${ao[@]}"; do
      k="${o%=*}" v="${o#*=}"
      case "$k" in
        c|color  ) foreground="$(tput setaf ${v})" ;;
        f|font   ) font="${v}"                     ;;
        l|lolcat ) lolcat=1                        ;;
        * ) continue                               ;;
      esac
    done
  }
}

h_center() {
  local block="$1"
  local block_width blank_columns indentation

  block_width=$(wc -L <<< "$block")

  blank_columns=$((_tw - block_width))
  indentation=$(printf "%$((blank_columns/2))s" " ")

  sed "s/^/${indentation}/g" <<< "$block"
}

v_center() {
  local block="$1"
  local block_height blank_lines vpad1 vpad2

  block_height=$(wc -l <<< "$block")

  blank_lines=$((_th - block_height))
  vpad1=$((blank_lines/2))
  vpad2=$((_th - (block_height + vpad1) ))

  vpad1=$(printf "%${vpad1}s" " ")
  vpad2=$(printf "%${vpad2}s" " ")

  vpad1=${vpad1// /$'\n'}
  vpad2=${vpad2// /$'\n'}

  printf '%s' \
    "$vpad1" "$foreground" "$block" "$foreground" "$vpad2"
}

set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR

ERM(){

  local mode

  getopts xr mode
  case "$mode" in
    x ) urg=critical ; prefix='[ERROR]: '   ;;
    r ) urg=low      ; prefix='[WARNING]: ' ;;
    * ) urg=normal   ; mode=m ;;
  esac
  shift $((OPTIND-1))

  msg="${prefix}$*"

  if [[ -t 2 ]]; then
    echo "$msg" >&2
  else
    notify-send -u "$urg" "$msg"
  fi

  [[ $mode = x ]] && exit 77
}

ERX() { ERM -x "$*" ;}
ERR() { ERM -r "$*" ;}

function DEATH {
  rm -rf "$_tmpdir"
}

trap DEATH EXIT

main "$@"