From ae3cc9d37fca0e3f6aefdab206a2ad97dc030684 Mon Sep 17 00:00:00 2001 From: Patrick McDermott Date: Thu, 27 Dec 2018 00:50:19 -0500 Subject: tools/shman.sh: New file --- (limited to 'tools') diff --git a/tools/shman.sh b/tools/shman.sh new file mode 100644 index 0000000..7cd8140 --- /dev/null +++ b/tools/shman.sh @@ -0,0 +1,282 @@ +#!/bin/sh +# +# Shell command language manual generator +# +# Copyright (C) 2018 Patrick McDermott +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Man, shman! + +set -u + +VERSION='0.1.0' +LF=' +' +FUNC_CMDS='brief option operand details return' + +warn() +{ + local fmt="${1}" + shift 1 + + printf "shman: Warning: ${fmt}\n" "${@}" >&2 +} + +get_mtime() +{ + local file="${1}" + shift 1 + local m= + local d= + local y= + local now_m= + local now_y= + + read m d y <<-EOF + $(LC_ALL=POSIX ls -l "${file}" | cut -d ' ' -f 6-8) + EOF + case "${m}" in + 'Jan') m=1;; 'Feb') m=2;; 'Mar') m=3;; 'Apr') m=4;; + 'May') m=5;; 'Jun') m=6;; 'Jul') m=7;; 'Aug') m=8;; + 'Sep') m=9;; 'Oct') m=10;; 'Nov') m=11;; 'Dec') m=12;; + esac + case "${y}" in *':'*) + read now_m now_y <<-EOF + $(date '+%m %Y') + EOF + now_m="${now_m#'0'}" + if [ ${now_m} -ge ${m} ]; then + y=${now_y} + else + y=$((${now_y} - 1)) + fi + esac + + printf '%d-%02d-%02d' ${y} ${m} ${d} +} + +format_fonts() +{ + local str="${1}" + shift 1 + + printf '%s\n' "${str}" | sed ' + s/[*][*]\(.*\)[*][*]/\\fB\1\\fP/g; + s/_\(.*\)_/\\fI\1\\fP/g; + ' +} + +gen_doc_func() +{ + local sym="${1}" + local doc="${2}" + shift 2 + local cmd= + local args= + local sect_name= + local sect_desc= + local sect_ret= + + sect_name=".SH NAME${LF}${sym}" + while read -r cmd args; do + case "${cmd}" in + 'brief') + args="$(format_fonts "${args}")" + sect_name="${sect_name} - ${args}" + ;; + 'details') + args="$(format_fonts "${args}")" + sect_desc=".SH DESCRIPTION${LF}${args}" + ;; + 'return') + args="$(format_fonts "${args}")" + sect_ret=".SH RETURN VALUE${LF}${args}" + ;; + esac + done <<-EOF + ${doc} + EOF + + printf '%s\n\n' "${sect_name}" "${sect_desc}" "${sect_ret}" +} + +gen_doc() +{ + local date="${1}" + local source="${2}" + local manual="${3}" + local sym="${4}" + local is_func="${5}" + local doc="${6}" + local out_dir="${7}" + shift 7 + local doc_joined='' + local sym_upper= + + # Join command lines + while read -r line; do + case "${line}" in + '@'*) + doc_joined="${doc_joined}${LF}${line#'@'}" + ;; + *) + doc_joined="${doc_joined} ${line}" + ;; + esac + done <<-EOF + ${doc} + EOF + doc="${doc_joined#${LF}}" + # Now each line in ${doc} should begin with a command name and thus be + # easier to parse. + + sym_upper="$(printf '%s\n' "${sym}" | tr '[:lower:]' '[:upper:]')" + { + if ${is_func}; then + printf '.TH %s 3 "%s" "%s" "%s"\n\n' \ + "${sym_upper}" "${date}" "${source}" "${manual}" + gen_doc_func "${sym}" "${doc}" + fi + } >"${out_dir}/${sym}.3" +} + +parse_docs() +{ + local file="${1}" + local source="${2}" + local manual="${3}" + local out_dir="${4}" + local tags="${5}" + shift 5 + local date= + local line= + local doc='' + local got_doc=false + local is_func=false + + date="$(get_mtime "${file}")" + + while IFS='' read -r line; do + case "${line}" in + '##'*) + ## Strip "##" and leading and trailing whitespace + #read -r line <<-EOF + # ${line#'##'} + # EOF + doc="${doc}${line#'##'}${LF}" + got_doc=true + continue + ;; + '') + continue + ;; + *'()') line="${line%'()'}" is_func=true;; + *'='*) line="${line%%'='*}" is_func=false;; + *) + doc='' + got_doc=false + continue + ;; + esac + case "${line}" in [!a-zA-Z]*|*[!a-zA-Z0-9_]*) continue;; esac + if ${got_doc}; then + gen_doc "${date}" "${source}" "${manual}" \ + "${line}" ${is_func} "${doc}" "${out_dir}" + doc='' + got_doc=false + elif ${is_func}; then + warn 'Undocumented function "%s"' "${line}" + fi + done <"${file}" +} + +usage() +{ + printf 'Usage: %s [option ...] ...\n' "${0}" +} + +help() +{ + usage + cat < The source of the manual pages, e.g. a package name and version + [default: "Shell"] + -m The title of the manual [default: "Shell Functions"] + -d The directory in which to write manual pages [default: the + current working directory] + -t A file in which to list all documented symbols +EOF +} + +version() +{ + cat <. +This is free software: you are free to change and redistribute it. +There is NO WARRANTY, to the extent permitted by law. +EOF +} + +main() +{ + local opt= + local source='Shell' + local manual='Shell Functions' + local out_dir='.' + local tags='' + local f= + + while getopts 'hVs:m:d:t:' opt; do + case "${opt}" in + 'h') + help + exit + ;; + 'V') + version + exit + ;; + 's') + source="${OPTARG}" + ;; + 'm') + manual="${OPTARG}" + ;; + 'd') + out_dir="${OPTARG}" + ;; + 't') + tags="${OPTARG}" + ;; + esac + done + shift $(($OPTIND - 1)) + + if [ ${#} -lt 1 ]; then + usage >&2 + exit 1 + fi + + for f in "${@}"; do + parse_docs "${f}" "${source}" "${manual}" "${out_dir}" "${tags}" + done +} + +main "${@}" -- cgit v0.9.1