version/0.2
[shelmfish.git] / helpm2pod
blobaaf526f7ec8e1759b205c9c68daa700d7c58f68b
1 #!/bin/sh
2 # Copyright © 2014 Géraud Meyer <graud@gmx.com>
4 # This program is free software; you can redistribute it and/or modify it under
5 # the terms of the GNU General Public License version 2 as published by the
6 # Free Software Foundation.
8 # This program is distributed in the hope that it will be useful, but WITHOUT
9 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
11 # details.
13 # You should have received a copy of the GNU General Public License along with
14 # this program. If not, see <http://www.gnu.org/licenses/>.
16 PROGRAM_NAME="helpm2pod"
17 PROGRAM_VERSION="0.2"
18 # valid help text example
19 help_message () {
20 cat <<HelpMessage
21 NAME
22 $PROGRAM_NAME - convert a specially formatted help text to POD
23 USAGE
24 $PROGRAM_NAME -h
25 $PROGRAM_NAME [<options>] [--] [<file> [<name>.<section>[.<helpm>]]]
26 DESCRIPTION
27 $PROGRAM_NAME converts a HelpMessage to POD. The text is read from the
28 given <file> or from standard input if a dash (-) or nothing is given.
30 It can also further process the POD document with pod2man(1) to obtain a
31 man(7) document. In that case the filename is used to guess the section
32 and the manual name. An additional optional argument allows to override
33 the filename used for that purpose or to give one if the standard input is
34 used.
36 See helpmessage(5) for a description of the HelpMessage format.
38 The programs helpm4sh(1) and gitparseopt2helpm(1) can be used to obtain a
39 HelpMessage.
40 OPTIONS
41 The options are parsed by getopt(1).
42 -h, --help
43 Print a help message and exit.
44 -p, --pod
45 Convert the HelpMessage to POD; this is the default.
46 -m, --man
47 Convert the HelpMessage to man(7). If stdout is a tty, view the
48 manual in a pager.
49 EXIT STATUS
50 If invalid lines are detected, 1 is returned. If there are no errors, 0 is
51 returned.
52 EXAMPLES
53 To convert the help output of this program to a manual page:
54 $ helpm2pod -h |helpm2pod |pod2man -n HELPM2POD >helpm2pod.1
55 To view the same manual in the pager:
56 $ helpm2pod -h |helpm2pod --man - HELPM2POD
57 AUTHOR
58 helpm2pod was written by G.raud Meyer.
59 SEE ALSO
60 helpmessage(5), perlpod(1), pod2man(1), man(7), helpm2text(1)
61 HelpMessage
64 # command line parsing
65 GETOPT_OPT=hpmV
66 GETOPT_LOPT=help,help-man,pod,man,version
67 if getopt -T >/dev/null
68 then CL=`getopt $GETOPT_OPT $*`
69 else CL=`getopt -o $GETOPT_OPT -l $GETOPT_LOPT -- "$@"`
71 if [ $? -ne 0 ]
72 then echo >&2 "$PROGRAM_NAME: syntax error"; exit 255
74 if getopt -T >/dev/null
75 then set -- $CL
76 else eval set -- "$CL"
78 MAN=
79 while [ $# -gt 0 ]
81 case $1 in
82 --help|-h) help_message; exit 0 ;;
83 --help-man) "$0" -h |"$0" --man -; exit ;;
84 --pod|-p) MAN= ;;
85 --man|-m) MAN=yes ;;
86 --version|-V) echo "$PROGRAM_NAME version $PROGRAM_VERSION"; exit ;;
87 --) shift; break ;;
88 *) break ;;
89 esac
90 shift
91 done
92 if [ $# -gt 2 ]
93 then echo >&2 "$PROGRAM_NAME: error: too many arguments"; exit 255
95 [ $# -gt 0 ] || set -- -
96 if [ $# -gt 0 ] && expr "$1" : '[a-zA-Z_][a-zA-Z_0-9]*=' >/dev/null
97 then set -- ./"$1"
99 FILE=$1
100 [ $# -ge 2 ] && FILE=$2
101 # attempt to guess the manual name and the section from the first filename
102 if [ -n "$MAN" ]
103 then
104 FILE=${FILE##*/}; SECTION=${FILE##*.}
105 if [ x"$SECTION" = x"$FILE" ]
106 then
107 SECTION= # no section/extension found
108 else
110 expr "$SECTION" : '[Hh]\([Ee][Ll][Pp]\)\{0,1\}[Mm]\(essage\)\{0,1\}' >/dev/null ||
111 expr "$SECTION" : '[Tt][Ee]\{0,1\}[Xx][Tt]' >/dev/null
112 then
113 FILE=${FILE%."$SECTION"} # get rid of the extension
114 SECTION=${FILE##*.}
115 if [ x"$SECTION" = x"$FILE" ]
116 then
117 SECTION= # no section found
120 if expr x"$SECTION" : x'[0-9][a-zA-Z]\{0,\}' >/dev/null
121 then
122 FILE=${FILE%."$SECTION"} # get rid of the section
123 else
124 SECTION=
127 [ x"$FILE" = x"-" ] && FILE=
130 process () {
131 # section is set to the type:
132 # - use: synopsis, name have one synopsis per line
133 # - opt: options which is a list with additional formatting
134 # - def: default for other sections
135 awk -v PROGRAM_NAME="$PROGRAM_NAME" 'BEGIN { ORS="" }
136 # header
137 FNR == 1 && $0 ~ /^[# ]|^\/\// { next }
138 # comments
139 /^#|^\/\// { next }
141 function sep(n) {
142 if (_sep < n) _sep=n; if (n < 0 && _sep < -n) _sep=-n
143 # do print the blank lines and reset the counter
144 if (n <= 0) {
145 while (_blank-- > 0) { _sep--; print "\n" }; _blank=0
146 while (_sep-- > 0) print "\n"; _sep=0
148 return _sep
150 function line(arg) { sep(0); print(arg "\n") }
151 function sline(arg) { sep(-1); print(arg "\n"); sep(1) }
152 # end of list
153 !/^( *|\t)\t/ && list { list=0; sline("=back") }
154 # input blank lines are kept
155 /(^|^\t)$/ { _blank++; next } # remember spacing blank lines
156 # may be verbatim space or an invalid line
157 /^[ \t]*$/ { sub(/^\t/, ""); line($0); next }
158 # section
159 /^[^\t ]/ {
160 sep(-2); print "=head1 "
161 if (tolower($1) ~ /^usage:?$|^synopsis/) { section="use"; print "SYNOPSIS" }
162 else if (tolower($1) ~ /^options/) { section="opt"; print "OPTIONS" }
163 else if (tolower($1) ~ /^[a-z]/) { section="def"; sub(/:$/,""); print toupper($0) }
164 print "\n"; sep(1); next
166 # subsection
167 /^ *[^\t]/ {
168 sep(2); sline("=head2 " $0); next
171 function style(l, cmd) {
172 cmd = "printf \"%s\\n\" " sqesc(l) "| LC_ALL=C sed -r" \
173 " -e '\''s/(^|[^A-Z])(<[_a-zA-Z0-9-]{1,}>)/\\1I\\2/g'\''" \
174 " -e '\''s/(^|[^a-zA-Z0-9_-])\\*([a-zA-Z0-9_-]{1,})\\*([^a-zA-Z0-9_-]|$)/\\1B<\\2>\\3/g'\''"
175 cmd | getline l; close(cmd)
176 return l
178 function item(l, cmd) {
179 cmd = "printf \"%s\\\\n\" " sqesc(l) "| LC_ALL=C sed -r" \
180 " -e '\''s/(^|[^a-zA-Z])(<[_a-zA-Z0-9-]{1,}>)/\\1I\\2/g'\''"
181 if (section ~ /^opt/)
182 cmd = cmd " -e '\''s/(^|[^a-zA-Z0-9_-])-([a-zA-Z0-9]|-[a-zA-Z0-9_-]{1,})/\\1B<-\\2>/g'\''"
183 else
184 cmd = cmd " -e '\''s/(^|[^a-zA-Z0-9_-])\\*([a-zA-Z0-9_-]{1,})\\*([^a-zA-Z0-9_-]|$)/\\1B<\\2>\\3/g'\''"
185 cmd | getline l; close(cmd)
186 return l
189 # Usage/Synopsis
190 /^\t/ && section == "use" {
191 sep(-1)
192 if ($1 ~ /^[-a-zA-Z0-9_+.=]+$/) print "B<" $1 ">"
193 else print $1
194 $1=""; gsub(/<[-a-zA-Z_]+>/,"I&"); line($0); sep(1); next
196 # Verbatim in list
197 !/^\t\t[ \t]/ && list { if (verbatim) sep(1); verbatim=0 }
198 /^\t\t[ \t]/ && list {
199 if (!verbatim) { verbatim=1; sep(1) }
200 sub(/^\t\t/,""); line($0); next
202 # List/Option description
203 /^\t\t/ && list {
204 sub(/^\t\t/, ""); line(style($0)); next
206 # List/Option item
207 /^ *\t/ {
208 if (!list) {
209 list=1; sline("=over 4")
211 sep(-1); print "=item "
212 sub(/^ *\t/, ""); line(item($0)); sep(1); next
214 # Verbatim
215 !/^\t[ \t]/ { if (verbatim) sep(1); verbatim=0 }
216 /^\t[ \t]/ {
217 if (!verbatim) { verbatim=1; sep(1) }
218 sub(/^\t/,""); line($0); next
220 # Other
221 /^\t[^ \t]/ {
222 sub(/^\t/,""); line(style($0)); next
224 # Detect unrecognized skipped lines
225 { invalid++; _blank=0 }
226 # List
227 END {
228 if (list) { sep(-1); print "=back\n" }
229 if (invalid) {
230 print PROGRAM_NAME ": error: " invalid " invalid lines skipped\n" > "/dev/fd/2"
231 exit 1
234 function sqesc(s) {
235 gsub(/'/, \"'\\\\''\", s); return \"'\" s \"'\"
236 }" "${1--}"
238 # main
239 set -e
240 (set -o pipefail) 2>/dev/null && set -o pipefail
241 if [ -n "$MAN" ] && [ -t 1 ]
242 then
243 process "$@" |pod2man -u -c "" ${FILE:+-n "$FILE"} ${SECTION:+-s "$SECTION"} |
244 nroff -man |${PAGER:-more}
245 elif [ -n "$MAN" ]
246 then
247 process "$@" |pod2man -u -c "" ${FILE:+-n "$FILE"} ${SECTION:+-s "$SECTION"}
248 else
249 process "$@"