#!/bin/bash
# dev/docs/ref - package source reference support
set -e
lib_load 'dev/docs/rst'
######
# Reference Manual Configuration
dev_docs_ref_config_init() {
# $docs_note_types[] - List of known documentation note types
lib_setting_arrays docs_note_types
docs_note_types=( returns default example fixme todo )
docs_note_types+=( "${rst_admonitions[@]}" )
# $docs_last_line - Stores the last generated line of documentation.
# Used internally to minimize spurious whitespace.
lib_setting_vars --null docs_last_line
}
######
# Note Sections
#
# A `note section` begins with a line that starts with a known note
# keyword. The following pedantic example demonstrates a `returns` note
# section as it would appear the source code. As the name suggestions,
# this note section specifies a return value for a function:
#
# ``# Returns: Success``
#
# This note section will be rendered as:
#
# Returns: Success
#
# Each type of note section may impose additional documentation syntax
# requirements or override the format for outputting the section contents.
#
# See ``$docs_note_types[]`` for the list of built-in note types.
# docs_note_keyword() - Prints the canonical keyword of a note section
# on the current line ($1). If no known keyword is found, nothing is
# output.
docs_note_keyword() {
local line=$(str_trim "$1")
[ "${line/: /}" != "$line" ] || return 0
local -l tag=${line%%: *}
[ "${tag// /}" = "$tag" ] || return 0
echo "$tag"
}
# is_docs_note() - Returns success if line ($1) begins with a known
# note section keyword.
is_docs_note() {
local keyword="$(docs_note_keyword "$1")"
[ "$keyword" ] && in_list "$keyword" "${docs_note_types[@]}"
}
# docs_gen_file_note() - Begins a note section that starts on the given
# line ($1), formatting the output according the rules defined by the
# type of note section.
docs_gen_file_note() {
local line=$1
local keyword=${line%%: *}
local desc="${line#*: }"
docs_lines_cr
local -l ntype=$keyword
case "$ntype" in
(*) docs_gen_file_note_default "$keyword" "$desc" ;;
esac
}
# docs_gen_file_note_default() - Prints a note section using a
# default format.
docs_gen_file_note_default() {
local keyword=$1
keyword=$(str_trim "$keyword")
local indent=${1%$keyword*}
docs_lines_add "$indent$(rst_bold "$keyword"): $2"
}
######
# Feature Detection
# is_docs_section() - Returns success if the given line ($1) is the
# start of a new section.
is_docs_section() { [ "$1" = '#####' ]; }
# is_docs_symbol() - Returns success if the given line ($1) is a
# documentated symbol.
is_docs_symbol() { [ "${1/ - /}" != "$1" -a -n "$(str_trim "${1%% - *}")" ]; }
# is_docs_arg() - Returns success if the given line ($1) is a
# documentated function argument.
is_docs_arg() { [ "${1/\$[0-9\@] - /}" != "$1" -o "${1#... - }" != "$1" ]; }
######
# Source Line Processing
# docs_readline() - Reads a line into the named var ($1)
# $1 - Variable name to receive line of documentation
# Returns: If a line could be read, returns success.
docs_readline() {
local var=$1
local -n value=$var
local ignoring=false
while true; do
read $var || return
[ "$value" ] || continue
if $ignoring; then
[ "$value" != '#+++' ] || ignoring=false
continue
elif [ "$value" = '#---' ]; then
ignoring=true
continue
fi
if [ "${value#\#}" != "$value" ]; then
value=${value#\#}
#; ignore comments like this one
[ "${value:0:1}" != ';' ] || continue
value=${value##[ \t\n]}
else
value=''
fi
break
done
}
######
# Output Generation
# docs_lines_add() - Caches lines ($@) of documentation for later generation.
docs_lines_add() { docs_lines+=( "$@" ); }
# docs_lines_cr() - Caches a blank line of documentation.
docs_lines_cr() { docs_lines_add ''; }
# docs_lines_here() - Caches lines of documentation from standard input.
docs_lines_here() {
local -a lines
readarray -t lines
docs_lines_add "${lines[@]}"
}
# docs_lines_func() - Caches lines of documentation generated from
# the given command ($@).
docs_lines_func() {
min_args 1 "$@"
local tmp
file_capture_func tmp "$@"
docs_lines_here <"$tmp"
}
# docs_gen_lines() - Prints lines of documentation that have been cached
# via ``docs_lines_add()`` and friends. Sequences of multiple blank
# lines are reduced to a single line. This allows generation functions
# to be liberal when emitting whitespace between successive blocks of lines.
docs_gen_lines() {
[ ${#docs_lines[*]} -gt 0 ] || return 0
local line
for line in "${docs_lines[@]}"; do
[ "$line" -o "$docs_last_line" ] || continue
echo "$line"
docs_last_line=$line
done
docs_lines=()
}
######
# Package Reference Documentation
package_docs_ref_clean() { run rm -rf "$package_docs_refdir"; }
docs_gen_files() {
local -a files=( "$@" )
[ "$*" ] || docs_file_list files src conf libs
run_mkdir "$package_docs_refdir"
for_each docs_gen_file "${files[@]}"
}
docs_gen_file() {
local file=$1
local base=${name%.in}
local out=$(docs_filename "$package_docs_refdir" "$file" rst)
file_mkdir "$out"
local ext=''
[ "${file/conf\//}" != "$file" ] || ext='.sh'
local in="$package_gendir/${file/src\//bin\/}$ext"
local name=${file#*/}
info "$name: generating documentation..."
if ! docs_gen_file_body "$file" <"$in" >"$out"; then
error "$name: error generating documentation"
fi
}
######
# Source File Processing
docs_gen_file_body() {
local file=$1
local name=${file#*/}
local docs_cur_state=sec
local -a docs_lines
local line
docs_readline line
if [ "!/bin/bash" = "$line" ]; then
# discard shebang line, read next
docs_readline line
else
warn "$name: $line: first line should be a shebang"
fi
docs_gen_file_title "$line"
docs_gen_file_header "$name"
docs_gen_file_deps "$file"
docs_gen_file_sections "$name"
}
docs_gen_file_title() {
local line=$1
local title=${line% - *}
local desc=${line#* - }
docs_lines_func rst_title "$name" "$desc"
docs_gen_lines
}
docs_gen_file_header() {
local name=$1
local line
while docs_readline line; do
if ! is_docs_section "$line"; then
docs_lines_add "$line"
continue
fi
local -a desc
file_array_func desc docs_gen_lines
if [ "${desc[*]}" ]; then
rst_h1 "Script Overview"
for_each echo "${desc[@]}"
fi
docs_cur_state='sec'
break
done
}
docs_gen_file_deps() {
local file=$1
local name=${file#*/}
local base=${file##*/}
local svg="$base.deps.svg"
docs_lines_cr
local tmp
file_capture_func tmp docs_gen_file_deps_body "$file"
file_read_func "$tmp" docs_lines_func rst_figure "$svg" \
':align:' right ':alt:' "Dependency Graph" ':width:' "100%"
}
docs_gen_file_deps_body() {
local file=$1
local dotfile=$(docs_filename "$package_docs_refdir" "$file" deps.dot)
local name=${file#*/}
local base=${file##*/}
local dot="$base.deps.dot"
local svg="$base.deps.svg"
local pdf="$base.deps.pdf"
local rst="$base.rst"
local raw="$base.raw.html"
echo "$(rst_literal "$name"): Library Dependencies"
if [ -s "$dotfile" ]; then
cat <<GRAPH
View the full-size SVG (svg_) or PDF (pdf_) image or
graphviz source (dot_)
.. _svg: $svg
.. _pdf: $pdf
.. _dot: $dot
GRAPH
echo
fi
cat <<RST
View color source code (raw_) for this file
.. _raw: $raw
View reStructuredText (rst_) source code for this page
.. _rst: $rst
RST
}
######
# Sections/Symbols/Arguments
docs_gen_file_sections() {
local name=$1
local docs_cur_section
local docs_cur_symbol
local line
while docs_readline line; do
case "$docs_cur_state" in
(top|arg|note|sym)
docs_gen_lines
if is_docs_section "$line"; then
docs_cur_section=''
docs_cur_symbol=''
docs_cur_state='sec'
elif is_docs_arg "$line"; then
docs_gen_file_sym_arg "$line"
docs_cur_state='arg'
elif is_docs_note "$line"; then
docs_gen_file_note "$line"
docs_cur_state='note'
elif is_docs_symbol "$line"; then
docs_gen_file_sym "$line"
docs_cur_symbol="$line"
docs_cur_state='sym'
elif [ "$docs_cur_section" ]; then
docs_lines_add "$line"
elif [ "$line" ]; then
warn "ignoring: $line"
fi
;;
(sec)
line=$(rst_trim "$line")
docs_gen_file_section 1 "$line"
docs_cur_section=$line
docs_cur_state='top'
;;
(*)
error "$state: unknown generator state"
;;
esac
done
docs_gen_lines
}
docs_gen_file_section() {
min_args 2 "$@"
local level=$1
local title=$2
local desc=$3
docs_lines_cr
title=$(rst_trim "$title")
docs_lines_func rst_header_n $level "$title"
desc=$(rst_trim "$desc")
if [ "$desc" ]; then
docs_lines_func rst_reflow "$desc"
fi
}
docs_gen_file_sym() {
local title="${1%% - *}"
title=$(str_trim "$title")
title=$(rst_code "$title")
local desc="${1#* - }"
docs_lines_cr
docs_gen_file_section 2 "$title" "$desc"
}
docs_gen_file_sym_arg() {
local name="${1%% - *}"
name=$(str_trim "$name")
name=$(rst_bold "$name")
local desc="${1#* - }"
docs_lines_cr
docs_lines_add "$name - $desc"
}
######
# HTML Documentation
docs_gen_html_files() {
local -a files=( "$@" )
[ "$*" ] || docs_file_list files libs src conf
for_each docs_gen_html_file "${files[@]}"
}
docs_gen_html_file() {
local file=$1
local base=${file%.sh}
local rst=$(docs_filename "$package_docs_refdir" "$base" rst)
local tmp
tmp=$(cmd_tempfile)
{
docs_rst_to_html "$rst"
echo
docs_gen_html_page_footer
} >"$tmp"
local html=$(docs_filename "$package_docs_refdir" "$base" html)
run mv "$tmp" "$html"
}
Generated on Fri Jul 28 14:35:13 PDT 2017 by mcsh d14 v0.23.0.