xref: /freebsd/contrib/kyua/doc/manbuild.sh (revision d4eeb02986980bf33dd56c41ceb9fc5f180c0d47)
1#! /bin/sh
2# Copyright 2014 The Kyua Authors.
3# All rights reserved.
4#
5# Redistribution and use in source and binary forms, with or without
6# modification, are permitted provided that the following conditions are
7# met:
8#
9# * Redistributions of source code must retain the above copyright
10#   notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright
12#   notice, this list of conditions and the following disclaimer in the
13#   documentation and/or other materials provided with the distribution.
14# * Neither the name of Google Inc. nor the names of its contributors
15#   may be used to endorse or promote products derived from this software
16#   without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30# \file doc/manbuild.sh
31# Generates a manual page from a source file.
32#
33# Input files can have __VAR__-style patterns in them that are replaced
34# with the values provided by the caller via the -v VAR=VALUE flag.
35#
36# Input files can also include other files using the __include__ directive,
37# which takes a relative path to the file to include plus an optional
38# collection of additional variables to replace in the included file.
39
40
41# Name of the running program for error reporting purposes.
42Prog_Name="${0##*/}"
43
44
45# Prints an error message and exits.
46#
47# Args:
48#   ...: The error message to print.  Multiple arguments are joined with a
49#       single space separator.
50err() {
51    echo "${Prog_Name}: ${*}" 1>&2
52    exit 1
53}
54
55
56# Invokes sed(1) translating input variables to expressions.
57#
58# Args:
59#   ...: List of var=value pairs to replace.
60#
61# Returns:
62#   True if the operation succeeds; false otherwise.
63sed_with_vars() {
64    local vars="${*}"
65
66    set --
67    for pair in ${vars}; do
68        local var="$(echo "${pair}" | cut -d = -f 1)"
69        local value="$(echo "${pair}" | cut -d = -f 2-)"
70        set -- "${@}" -e"s&__${var}__&${value}&g"
71    done
72
73    if [ "${#}" -gt 0 ]; then
74        sed "${@}"
75    else
76        cat
77    fi
78}
79
80
81# Generates the manual page reading from stdin and dumping to stdout.
82#
83# Args:
84#   include_dir: Path to the directory containing the include files.
85#   ...: List of var=value pairs to replace in the manpage.
86#
87# Returns:
88#   True if the generation succeeds; false otherwise.
89generate() {
90    local include_dir="${1}"; shift
91
92    while :; do
93        local read_ok=yes
94        local oldifs="${IFS}"
95        IFS=
96        read -r line || read_ok=no
97        IFS="${oldifs}"
98        [ "${read_ok}" = yes ] || break
99
100        case "${line}" in
101            __include__*)
102                local file="$(echo "${line}" | cut -d ' ' -f 2)"
103                local extra_vars="$(echo "${line}" | cut -d ' ' -f 3-)"
104                # If we fail to output the included file, just leave the line as
105                # is.  validate_file() will later error out.
106                [ -f "${include_dir}/${file}" ] || echo "${line}"
107                generate <"${include_dir}/${file}" "${include_dir}" \
108                    "${@}" ${extra_vars} || echo "${line}"
109                ;;
110
111            *)
112                echo "${line}"
113                ;;
114        esac
115    done | sed_with_vars "${@}"
116}
117
118
119# Validates that the manual page has been properly generated.
120#
121# In particular, this checks if any directives or common replacement patterns
122# have been left in place.
123#
124# Returns:
125#   True if the manual page is valid; false otherwise.
126validate_file() {
127    local filename="${1}"
128
129    if grep '__[A-Za-z0-9]*__' "${filename}" >/dev/null; then
130        return 1
131    else
132        return 0
133    fi
134}
135
136
137# Program entry point.
138main() {
139    local vars=
140
141    while getopts :v: arg; do
142        case "${arg}" in
143            v)
144                vars="${vars} ${OPTARG}"
145                ;;
146
147            \?)
148                err "Unknown option -${OPTARG}"
149                ;;
150        esac
151    done
152    shift $((${OPTIND} - 1))
153
154    [ ${#} -eq 2 ] || err "Must provide input and output names as arguments"
155    local input="${1}"; shift
156    local output="${1}"; shift
157
158    trap "rm -f '${output}.tmp'" EXIT HUP INT TERM
159    generate "$(dirname "${input}")" ${vars} \
160        <"${input}" >"${output}.tmp" \
161        || err "Failed to generate ${output}"
162    if validate_file "${output}.tmp"; then
163        :
164    else
165        err "Failed to generate ${output}; some patterns were left unreplaced"
166    fi
167    mv "${output}.tmp" "${output}"
168}
169
170
171main "${@}"
172