Skip to content

Instantly share code, notes, and snippets.

@fonic
Last active April 20, 2021 08:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fonic/028f53885ef8d83868642ce0441f8d3f to your computer and use it in GitHub Desktop.
Save fonic/028f53885ef8d83868642ce0441f8d3f to your computer and use it in GitHub Desktop.
generate-tag.sh - Generate file tag interactively (comment block to be used as file header)
#!/usr/bin/env bash
# -------------------------------------------------------------------------
# -
# Generate Tag (gentag) -
# -
# Created by Fonic <https://github.com/fonic> -
# Date: 01/09/18 - 04/20/21 -
# -
# -------------------------------------------------------------------------
# -------------------------------------
# -
# Globals -
# -
# -------------------------------------
# Tag styles
STYLES=(
"Shell #"
"Shell ##"
"C/C++ //"
"C/C++ /***"
"C/C++ /*"
"C/C++ /*--"
"C/C++ /***\\"
"Windows Batch (.bat/.cmd) REM"
"Assembler ;"
"HTML <!--***-->"
)
# Tag prefixes/postfixes
# Format: <head-pre>|<head-post>|<body-pre>|<body-post>|<foot-pre>|<foot-post>|<border>
# TODO: change to '<pre>|<fill>|<post>' for head + body + foot; remove '<border>'
# TODO: when done, also change how pfixes are printed, e.g.: '/*' '*' '*\'
PFIXES=(
"# ||# ||# ||-"
"## ||## ||## ||-"
"// ||// ||// ||-"
"/*|| * || *|*/|*"
"/*|| * || */||"
"/*|| * || *|*/|-"
"/*|\|* ||\|*/|*"
"REM ||REM ||REM ||-"
"; ||; ||; ||-"
"<!--||* ||*|-->|*"
)
# Tag types
TYPES=("Created" "Modified" "Customized" "Configured" "Tagged" "<custom>")
# Authors
AUTHORS=("Fonic <https://github.com/fonic>" "<custom>")
# Defaults
DEFAULT_STYLE=0
DEFAULT_WIDTH=75
DEFAULT_TITLE=""
DEFAULT_TYPE=0
DEFAULT_AUTHOR=0
[[ -n "${1+set}" ]] && DEFAULT_DATE="$(date "+%D" -r "$1")" || DEFAULT_DATE="$(date "+%D")"
DEFAULT_ADDCOMMENT="n"
DEFAULT_COMMENT=""
# -------------------------------------
# -
# Functions -
# -
# -------------------------------------
# NOTE:
# Abstracted console and file output in case we want/need to switch stdout
# and stderr for some reason in the future
# Echo to console [$@: arguments]
function econs() {
echo "$@" >&2
}
# Echo to file [$@: arguments]
function efile() {
echo "$@" >&1
}
# Read from console [$@: arguments]
function rcons() {
read "$@" >&2
}
# Run nano on console [$@: arguments]
function ncons() {
nano "$@" >&2
}
# Signal trap handler
function abort() {
trap - EXIT INT HUP
econs -e "\r\e[2K> aborted"
econs
exit 1
}
# Prompt user to answer yes-no question [$1: question, $2: default answer (y/Y/yes/YES/1 or n/N/no/NO/0)]
# NOTE:
# Using readline here (-e) since this seems to be the only way for CTRL+D to be recognized
# when using '-n' option of read. Drawback: '-e -n 1' will always echo key+newline, i.e.
# option '-s' seems to be ignored
function read_yesno() {
local input
econs -e "\e[1m$1?\e[0m [y/n]"
while true; do
econs -en "\r\e[2K> "
rcons -e -n 1 -s input || exit
[[ -n "${input}" ]] && econs -en "\e[A\r\e[2K> " || econs -en "\r\e[2K> "
[[ -z "${input}" ]] && input="$2"
case "${input}" in
y|Y|yes|YES|1)
econs -e "yes"
econs
return 0
;;
n|N|no|NO|0)
econs -e "no"
econs
return 1
;;
esac
econs -n "${input}"
sleep 0.25s
done
}
# Prompt user to input line [$1: target variable, $2: default value, $3: prompt]
function read_line() {
local input
econs -e "\e[1m$3:\e[0m"
while [[ -z "${input+set}" ]]; do
econs -en "\r\e[2K"
rcons -r -p "> " input || exit
[[ -z "${input}" ]] && input="$2"
[[ -z "${input+set}" ]] && sleep 0.25s
econs -en "\e[A"
done
econs -en "\r\e[2K> "
[[ -z "${input}" ]] && econs "(none)" || econs "${input}"
econs
eval "$1=\"${input}\""
}
# Prompt user to choose from options [$1: target variable, $2: default value, $3: prompt, $4: options variable (array/dict)]
# NOTE:
# - returns key of chosen option, i.e. index for options variable
# - works for both arrays and dicts, as those are treated equally by Bash (arrays are dicts with keys 0..n)
function read_choose() {
# Local variables
local i ilen input
eval "local keys=(\"\${!$4[@]}\")"
eval "local values=(\"\${$4[@]}\")"
# Print options
econs -e "\e[1m$3:\e[0m"
for ((i=0; i < ${#keys[@]}; i++)); do
econs "${i}: ${values[i]}"
done
# Are there options to choose from?
if (( ${#keys[@]} == 0 || ${#values[@]} == 0 )); then
econs -e "\e[1;31mError:\e[0m variable '$4' does not contain any options"
econs
eval "unset \"$1\""
return 1
fi
# Index string length (i.e. how many digits to read from user)
# NOTE: -1 since starting from 0
ilen=$(( ${#keys[@]} - 1 ))
ilen=${#ilen}
# Read user input
while true; do
econs -en "\r\e[2K> "
rcons -e -n ${ilen} -s input || exit
[[ -n "${input}" ]] && econs -en "\e[A\r\e[2K> " || econs -en "\r\e[2K> "
[[ -z "${input}" ]] && input="$2"
case "${input}" in # https://stackoverflow.com/a/3951175/1976617
""|*[!0-9]*) ;; # input is not int
*) # input is int
if (( ${input} >= 0 && ${input} < ${#keys[@]} )); then
#econs -e "\r\e[2K> ${input} (${values[input]})"
econs -en "\r\e[2K> "
econs "${input} (${values[input]})"
econs
break
fi
;;
esac
econs -n "${input}"
sleep 0.25s
done
# Return user's choice
eval "$1=\"${keys[input]}\""
return 0
}
# Generate tag header line [no params]
function generate_head() {
local str="${tag_pfixes[0]}"
for ((i=0; i < ${tag_width}-${#tag_pfixes[0]}-${#tag_pfixes[1]}; i++)); do
str+="${tag_pfixes[6]}"
done
str+="${tag_pfixes[1]}"
efile "${str}"
}
# Generate tag body line [$*: text]
function generate_body() {
local str="${tag_pfixes[2]}"
str+="$*"
for ((i=${#str}; i < ${tag_width}-${#tag_pfixes[6]}-${#tag_pfixes[3]}; i++)); do
str+=" "
done
str+="${tag_pfixes[6]}${tag_pfixes[3]}"
efile "${str}"
}
# Generate tag footer line [no params]
function generate_foot() {
local str="${tag_pfixes[4]}"
for ((i=0; i < ${tag_width}-${#tag_pfixes[4]}-${#tag_pfixes[5]}; i++)); do
str+="${tag_pfixes[6]}"
done
str+="${tag_pfixes[5]}"
efile "${str}"
}
# -------------------------------------
# -
# Main -
# -
# -------------------------------------
# Set traps
trap "abort" EXIT INT HUP
# Print title
econs
econs -e "\e[1m--==[ Generate Tag (gentag) ]==--\e[0m"
econs
# Read tag parameters
read_choose tag_style "${DEFAULT_STYLE}" "Choose style" STYLES
econs -e "\e[1mStyle pfixes:\e[0m"
readarray -d "|" -t tag_pfixes <<< "${PFIXES[tag_style]}"
i=$(( ${#tag_pfixes[@]} - 1 ))
tag_pfixes[i]="${tag_pfixes[i]::-1}"
for ((i=0; i < ${#tag_pfixes[@]}; i++)); do
econs "${i}: '${tag_pfixes[i]}'"
done
econs
read_line tag_width "${DEFAULT_WIDTH}" "Enter width"
read_line tag_title "${DEFAULT_TITLE}" "Enter title"
read_choose tag_typei "${DEFAULT_TYPE}" "Choose type" TYPES
tag_type="${TYPES[tag_typei]}"
[[ "${tag_type}" == "<custom>" ]] && read_line tag_type "" "Enter type"
read_choose tag_authori "${DEFAULT_AUTHOR}" "Choose author" AUTHORS
tag_author="${AUTHORS[tag_authori]}"
[[ "${tag_author}" == "<custom>" ]] && read_line "tag_author" "" "Enter author"
read_line tag_date "${DEFAULT_DATE}" "Enter date"
if read_yesno "Add comment" "${DEFAULT_ADDCOMMENT}"; then
# Comment
max_len=$((${tag_width}-${#tag_pfixes[2]}-${#tag_pfixes[6]}-${#tag_pfixes[3]}-2))
ml_hint="# Maxlen: ${max_len}"
for ((i=${#ml_hint}; i < ${max_len}-3; i++)); do ml_hint+=" "; done
ml_hint+="->|"
tempfile="$(mktemp)"
echo "${ml_hint}" > "${tempfile}"
ncons +2 "${tempfile}" # open nano on second line
tag_comment="$(tail -n +2 "${tempfile}" 2>/dev/null)" # cut first line (i.e. max length hint)
rm "${tempfile}" 2>/dev/null
# Auto-width
IFS=$'\n'
while read -r line; do
(( ${#line} > ${max_len} )) && max_len=${#line}
done <<< "${tag_comment}"
unset IFS
new_width=$((${max_len}+${#tag_pfixes[2]}+${#tag_pfixes[6]}+${#tag_pfixes[3]}+2))
if (( ${new_width} > ${tag_width} )); then
econs -e "\e[1mAuto-width:\e[0m"
econs "> Adjusting width from ${tag_width} to ${new_width}"
econs
tag_width=${new_width}
fi
fi
# Generate tag
econs -e "\e[1mGenerated tag:\e[0m"
[[ ! -t 1 ]] && econs "(redirected)"
generate_head
generate_body ""
if [[ "${tag_title}" != "" ]]; then
generate_body "${tag_title}"
generate_body ""
fi
generate_body "${tag_type} by ${tag_author}"
generate_body "Date: ${tag_date}"
generate_body ""
if [[ "${tag_comment}" != "" ]]; then
IFS=$'\n'
while read -r line; do
generate_body "${line}"
done <<< "${tag_comment}"
unset IFS
generate_body ""
fi
generate_foot
# Exit gracefully
econs
trap - EXIT INT HUP
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment