Update to generate_readme.sh

This commit is contained in:
labbots
2020-06-07 04:02:36 +01:00
parent 53ba142a5f
commit f4ebe6afcb

View File

@@ -1,13 +1,111 @@
#!/usr/bin/env bash
README="README.md"
_setup() {
INVALID_CHARS="'[]/?!:\`.,()*\";{}+=<>~$|#@&–—"
_usage() {
printf "
Script to autogenerate markdown based on bash source code.\n
The script generates table of contents and bashdoc and update it the given markdown template.\n
Usage:\n %s [options.. ]\n
Options:\n
-f | --file <filename.md> - Relative or absolute path to the README.md file.
-p | --sh-dir <folderpath> - path to the bash script source folder to generate shdocs.\n
-l | --toc-level <number> - Minimum level of header to print in Table of Contents.\n
-d | --toc-depth <number> - Maximum depth of tree to print in Table of Contents.\n
-h | --help - Display usage instructions.\n" "${0##*/}"
exit 0
}
_setup_arguments() {
unset MINLEVEL MAXLEVEL SCRIPT_FILE SOURCE_MARKDOWN SOURCE_SCRIPT_DIR
MINLEVEL=1
MAXLEVEL=3
SCRIPT_FILE=$(basename "${0}")
rm "$README"
cp readme-template.md "$README"
SCRIPT_FILE="${0##*/}"
SOURCE_MARKDOWN="../README.md"
SOURCE_SCRIPT_DIR="../src"
SHORTOPTS="hp:f:m:d:-:"
while getopts "${SHORTOPTS}" OPTION; do
case "${OPTION}" in
-)
_check_longoptions() { { [[ -z "$1" ]] && printf '%s: --%s: option requires an argument\nTry '"%s -h/--help"' for more information.\n' "${0##*/}" "${OPTARG}" "${0##*/}" && exit 1; } || :; }
case "${OPTARG}" in
help)
_usage
;;
file)
_check_longoptions "${!OPTIND}"
SOURCE_MARKDOWN="${!OPTIND}" && OPTIND=$((OPTIND + 1))
;;
toc-level)
_check_longoptions "${!OPTIND}"
MINLEVEL="${!OPTIND}" && OPTIND=$((OPTIND + 1))
;;
toc-depth)
_check_longoptions "${!OPTIND}"
MAXLEVEL="${!OPTIND}" && OPTIND=$((OPTIND + 1))
;;
sh-dir)
_check_longoptions "${!OPTIND}"
SOURCE_SCRIPT_DIR="${!OPTIND}" && OPTIND=$((OPTIND + 1))
;;
'')
_usage
;;
*)
printf '%s: --%s: Unknown option\nTry '"%s -h/--help"' for more information.\n' "${0##*/}" "${OPTARG}" "${0##*/}" && exit 1
;;
esac
;;
h)
_usage
;;
f)
SOURCE_MARKDOWN="${OPTARG}"
;;
m)
MINLEVEL="${OPTARG}"
;;
d)
MAXLEVEL="${OPTARG}"
;;
p)
SOURCE_SCRIPT_DIR="${OPTARG}"
;;
:)
printf '%s: -%s: option requires an argument\nTry '"%s -h/--help"' for more information.\n' "${0##*/}" "${OPTARG}" "${0##*/}" && exit 1
;;
?)
printf '%s: -%s: Unknown option\nTry '"%s -h/--help"' for more information.\n' "${0##*/}" "${OPTARG}" "${0##*/}" && exit 1
;;
esac
done
shift "$((OPTIND - 1))"
if [[ -w "${SOURCE_MARKDOWN}" ]]; then
declare src_file src_extension
src_file="${SOURCE_MARKDOWN##*/}"
src_extension="${src_file##*.}"
if [[ "${src_extension,,}" != "md" ]]; then
printf "Provided file %s is not a markdown.\n" "${src_file}" && exit 1
fi
else
printf "Provided file %s does not exist or no enough permission to access it.\n" "${SOURCE_MARKDOWN}" && exit 1
fi
if [[ ! -d "${SOURCE_SCRIPT_DIR}" ]]; then
printf "Provided directory for bash script files %s does not exist.\n" "${SOURCE_SCRIPT_DIR}" && exit 1
fi
re='^[0-9]+$'
if ! [[ "${MINLEVEL}" =~ $re ]] || ! [[ "${MAXLEVEL}" =~ $re ]]; then
echo "error: Not a number" >&2
exit 1
fi
if [[ "${MINLEVEL}" -gt "${MAXLEVEL}" ]]; then
printf "Minimum level for TOC cannot be greater than the depth of TOC to be printed.\n" && exit 1
fi
}
_setup_tempfile() {
@@ -20,56 +118,64 @@ _setup_tempfile() {
_generate_shdoc() {
declare file
file="$(realpath $1)"
./shdoc.awk < $file >> "$2"
./shdoc.awk < "${file}" >> "$2"
}
_insert_shdoc_to_file() {
declare shdoc_tmp_file="$1"
declare dest_file="$2"
declare shdoc_tmp_file source_markdown start_shdoc info_shdoc end_shdoc
shdoc_tmp_file="$1"
source_markdown="$2"
declare start_shdoc="<!-- START ${SCRIPT_FILE} generated SHDOC please keep comment here to allow auto update -->"
declare info_shdoc="<!-- DO NOT EDIT THIS SECTION, INSTEAD RE-RUN ${SCRIPT_FILE} TO UPDATE -->"
declare end_shdoc="<!-- END ${SCRIPT_FILE} generated SHDOC please keep comment here to allow auto update -->"
start_shdoc="<!-- START ${SCRIPT_FILE} generated SHDOC please keep comment here to allow auto update -->"
info_shdoc="<!-- DO NOT EDIT THIS SECTION, INSTEAD RE-RUN ${SCRIPT_FILE} TO UPDATE -->"
end_shdoc="<!-- END ${SCRIPT_FILE} generated SHDOC please keep comment here to allow auto update -->"
sed -i "1s/^/$info_shdoc\n/" "$shdoc_tmp_file"
sed -i "1s/^/${info_shdoc}\n/" "${shdoc_tmp_file}"
if grep --color=always -Pzl "(?s)$start_shdoc.*\n.*$end_shdoc" $dest_file &> /dev/null; then
if grep --color=always -Pzl "(?s)${start_shdoc}.*\n.*${end_shdoc}" $source_markdown &> /dev/null; then
# src https://stackoverflow.com/questions/2699666/replace-delimited-block-of-text-in-file-with-the-contents-of-another-file
sed -i -ne "/$start_shdoc/ {p; r $shdoc_tmp_file" -e ":a; n; /$end_shdoc/ {p; b}; ba}; p" $dest_file
echo -e "\nUpdated shdoc content to $dest_file successfully\n"
sed -i -ne "/${start_shdoc}/ {p; r ${shdoc_tmp_file}" -e ":a; n; /${end_shdoc}/ {p; b}; ba}; p" "${source_markdown}"
echo -e "Updated shdoc content to ${source_markdown} successfully\n"
else
printf "%s\n" "${start_shdoc}" >> "${dest_file}"
cat "${shdoc_tmp_file}" >> "${dest_file}"
printf "%s\n" "${end_shdoc}" >> "${dest_file}"
echo -e "\nCreated shdoc content to $dest_file successfully\n"
{
printf "%s\n" "${start_shdoc}"
cat "${shdoc_tmp_file}"
printf "%s\n" "${end_shdoc}"
} >> "${source_markdown}"
echo -e "Created shdoc content to ${source_markdown} successfully\n"
fi
}
_process_sh_files() {
declare shdoc_tmp_file=$(_setup_tempfile)
find ../src -name '*.sh' -print0 | sort -z |
declare shdoc_tmp_file source_script_dir source_markdown
source_markdown="${1}"
source_script_dir="${2}"
shdoc_tmp_file=$(_setup_tempfile)
find "${source_script_dir}" -name '*.sh' -print0 | sort -z |
while IFS= read -r -d '' line; do
_generate_shdoc "$line" "$shdoc_tmp_file"
_generate_shdoc "${line}" "${shdoc_tmp_file}"
done
_insert_shdoc_to_file "$shdoc_tmp_file" "$README"
rm "$shdoc_tmp_file"
_insert_shdoc_to_file "${shdoc_tmp_file}" "${source_markdown}"
rm "${shdoc_tmp_file}"
}
_generate_toc() {
declare line level title anchor output counter temp_output
declare line level title anchor output counter temp_output invalid_chars
invalid_chars="'[]/?!:\`.,()*\";{}+=<>~$|#@&–—"
while IFS='' read -r line || [[ -n "${line}" ]]; do
level="$(echo "${line}" | sed -E 's/(#+).*/\1/; s/#/ /g; s/^ //')"
title="$(echo "${line}" | sed -E 's/^#+ //')"
[[ "${title}" = "Table of Contents" ]] && continue
while IFS='' read -r line || [[ -n "$line" ]]; do
level="$(echo "$line" | sed -E 's/(#+).*/\1/; s/#/ /g; s/^ //')"
title="$(echo "$line" | sed -E 's/^#+ //')"
# tr does not do OK the lowercase for non ascii chars, add sed to pipeline -> src https://stackoverflow.com/questions/13381746/tr-upper-lower-with-cyrillic-text
anchor="$(echo "$title" | tr '[:upper:] ' '[:lower:]-' | sed 's/[[:upper:]]*/\L&/' | tr -d "$INVALID_CHARS")"
anchor="$(echo "${title}" | tr '[:upper:] ' '[:lower:]-' | sed 's/[[:upper:]]*/\L&/' | tr -d "${invalid_chars}")"
# check new line introduced is not duplicated, if is duplicated, introduce a number at the end
# copying doctoc behavior
temp_output=$output"$level- [$title](#$anchor)\n"
counter=1
while true; do
@@ -86,7 +192,7 @@ _generate_toc() {
# grep: filter header candidates to be included in toc
# sed: remove the ignored headers (case: minlevel greater than one) to avoid unnecessary spacing later in level variable assignment
done <<< "$(grep -E "^#{${MINLEVEL},${MAXLEVEL}} " "$1" | tr -d '\r' | sed "s/^#\{$(($MINLEVEL - 1))\}//g")"
done <<< "$(grep -E "^#{${MINLEVEL},${MAXLEVEL}} " "${1}" | tr -d '\r' | sed "s/^#\{$((${MINLEVEL} - 1))\}//g")"
# when in toc we have two `--` quit one
output="$(echo "$output" | sed 's/--*/-/g')"
@@ -97,63 +203,68 @@ _generate_toc() {
_insert_toc_to_file() {
declare toc_text="$2"
declare source_markdown toc_text start_toc info_toc end_toc utext_ampersand utext_slash
source_markdown="${1}"
toc_text="${2}"
# inspired in doctoc lines
declare start_toc="<!-- START ${SCRIPT_FILE} generated TOC please keep comment here to allow auto update -->"
declare info_toc="<!-- DO NOT EDIT THIS SECTION, INSTEAD RE-RUN ${SCRIPT_FILE} TO UPDATE -->"
declare end_toc="<!-- END ${SCRIPT_FILE} generated TOC please keep comment here to allow auto update -->"
start_toc="<!-- START ${SCRIPT_FILE} generated TOC please keep comment here to allow auto update -->"
info_toc="<!-- DO NOT EDIT THIS SECTION, INSTEAD RE-RUN ${SCRIPT_FILE} TO UPDATE -->"
end_toc="<!-- END ${SCRIPT_FILE} generated TOC please keep comment here to allow auto update -->"
toc_block="$start_toc\n$info_toc\n## Table of Contents\n\n$toc_text\n$end_toc"
# temporary replace of '/' (confused with separator of substitutions) and '&' (confused with match regex symbol) to run the special sed command
utext_ampersand="id8234923000230gzz"
utext_slash="id9992384923423gzz"
toc_block="$(echo "$toc_block" | sed "s,\&,$utext_ampersand,g")"
toc_block="$(echo "$toc_block" | sed "s,\/,$utext_slash,g")"
toc_block="$(echo "${toc_block}" | sed "s,\&,${utext_ampersand},g")"
toc_block="$(echo "${toc_block}" | sed "s,\/,${utext_slash},g")"
# search multiline toc block -> https://stackoverflow.com/questions/2686147/how-to-find-patterns-across-multiple-lines-using-grep/2686705
# grep color for debugging -> https://superuser.com/questions/914856/grep-display-all-output-but-highlight-search-matches
if grep --color=always -Pzl "(?s)$start_toc.*\n.*$end_toc" $1 &> /dev/null; then
if grep --color=always -Pzl "(?s)${start_toc}.*\n.*${end_toc}" "${source_markdown}" &> /dev/null; then
# src https://askubuntu.com/questions/533221/how-do-i-replace-multiple-lines-with-single-word-in-fileinplace-replace
sed -i ":a;N;\$!ba;s/$start_toc.*$end_toc/$toc_block/g" $1
echo -e "\nUpdated TOC content in $1 succesfully\n"
sed -i ":a;N;\$!ba;s/$start_toc.*$end_toc/$toc_block/g" "${source_markdown}"
echo -e "Updated TOC content in ${source_markdown} succesfully\n"
else
sed -i 1i"$toc_block" "$1"
echo -e "\nCreated TOC in $1 succesfully\n"
sed -i 1i"$toc_block" "${source_markdown}"
echo -e "Created TOC in ${source_markdown} succesfully\n"
fi
# undo symbol replacements
sed -i "s,$utext_ampersand,\&,g" $1
sed -i "s,$utext_slash,\/,g" $1
sed -i "s,${utext_ampersand},\&,g" "${source_markdown}"
sed -i "s,${utext_slash},\/,g" "${source_markdown}"
}
_process_toc() {
declare toc_temp_file=$(_setup_tempfile)
declare toc_temp_file source_markdown
source_markdown="${1}"
sed '/```/,/```/d' "$README" > "$toc_temp_file"
toc_temp_file=$(_setup_tempfile)
sed '/```/,/```/d' "${source_markdown}" > "${toc_temp_file}"
declare level=$MINLEVEL
while [[ $(grep -E "^#{$level} " "$toc_temp_file" | wc -l) -le 1 ]]; do
while [[ $(grep -E "^#{$level} " "${toc_temp_file}" | wc -l) -le 1 ]]; do
level=$(($level + 1))
done
if [[ $MINLEVEL -ne $level ]]; then
echo -e "\nnote: detected all headers (maybe except 1) in level $level, switching to that level of headers to fill table of contents"
fi
MINLEVEL=$level
toc_text=$(_generate_toc "$toc_temp_file")
rm "$toc_temp_file"
_insert_toc_to_file "$README" "$toc_text"
MINLEVEL=${level}
toc_text=$(_generate_toc "${toc_temp_file}")
rm "${toc_temp_file}"
_insert_toc_to_file "${source_markdown}" "${toc_text}"
}
main() {
_setup
_process_sh_files
_process_toc
trap 'exit "$?"' INT TERM && trap 'exit "$?"' EXIT
set -o errexit -o noclobber -o pipefail
_setup_arguments "${@}"
_process_sh_files "${SOURCE_MARKDOWN}" "${SOURCE_SCRIPT_DIR}"
_process_toc "${SOURCE_MARKDOWN}" "${MINLEVEL}" "${MAXLEVEL}"
}
main "$@"
main "${@}"