Last active
April 20, 2021 08:00
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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