#!/bin/bash
# doc/rst - reStructuredText file support
# The rst library provides support for reStructuredText file generation.
# The following document was referenced often during implementation:
#
#   http://http://docutils.sourceforge.net/rst.html

set -e


######
# Native dependencies

doc_rst_client_packages=( python-docutils )


######
# Configuration

doc_rst_config_init() {
	lib_setting_vars --null rst_title

	lib_setting_arrays rst_header_chars
	rst_header_chars=( "^" ":" "=" "~" "-" "_" "." )

	lib_setting_vars rst_col_indent rst_col_limit rst_col_inc
	rst_col_indent=0
	rst_col_limit=72
	rst_col_inc=2

	lib_setting_vars rst_flow_hanging
	rst_flow_hanging=false

	lib_setting_vars rst_enum_start rst_enum_index
	rst_enum_start=1
	rst_enum_index=0

	######
	# Block Directive Settings

	# $rst_code_line_start - If not `null`, this setting both (a)
	# specifies the starting number to use when prefixing the lines
	# of a code block and (b) enables line numbering in a code block.
	# Default: `null`
	lib_setting_vars --null rst_code_line_start

	# $rst_admonitions[] - List of supported admonitions
	# Default: ``attention caution danger error hint important
	# note tip warning admonition``
	lib_setting_arrays -ro rst_admonitions
	rst_admonitions=(
			attention caution danger error hint
			important note tip warning admonition
		)

	# $rst_image_element - Name of the image directive to generate
	# Default: ``image``
	lib_setting_vars rst_image_element
	rst_image_element=image

	# $rst_image_strict - If set to ``true``, invalid image options
	# generate an error; otherwise, they are ignored.
	# Default: ``image``
	lib_setting_vars rst_image_strict
	rst_image_strict=true

	######
	# HTML Settings

	# $rst_html_stylesheets[] - List of HTML stylesheets
	lib_setting_arrays rst_html_stylesheets
}

doc_rst_lib_init() {
	local level
	for level in $(seq 1 7); do
		eval "rst_h$level() { rst_header_n $level \"\$*\"; }"
	done
}


######
# Text reflowing

# rst_indent - Increments indent and calls another generation function
rst_indent() {
	local rst_col_indent=$((rst_col_indent + $rst_col_inc))
	func_trace "indent=$rst_col_indent limit=$rst_col_limit"
	run "$@"
}

rst_indent_str() { printf "%${rst_col_indent}s" ''; }

# rst_reflow - Rewraps text with current indent level and column limit
rst_reflow() {
	local -a text

	local indent=$(rst_indent_str)
	local line="$indent"
	while [ ${#*} -gt 0 ]; do
		if $rst_flow_hanging && [ ${#text[*]} -gt 0 ]; then
			local rst_flow_hanging=false
			rst_indent rst_reflow "$@"
			return
		fi
		local part=$1
		shift
		part=${part##[\t\n ]}
		part=${part%%[\t\n ]}

		if [ -z "$part" ]; then
			text+=( "$line" )
			[ -z "${line## }" ] || echo
			line="$indent"
			continue
		fi

		line="$line${line:+ }$part"

		local next=''
		local i=$rst_col_limit
		while ! [ $rst_col_limit -lt ${#line} ]; do
			if [ "${line[$i]}" != ' ' ]; then
				i=$((i - 1))
				continue
			fi
			# save last word for later
			next="${line:$((i + 1))}${next:+ }$next"
			part=${line:0:$i}
			break
		done

		# do not emit until we spill
		[ "$next" ] || continue

		text+=( "$line" )
		line=$next
	done
	[ "${line## }" ] && text+=( "$line" )

	echo "${text[*]}"
}

rst_reflow() { echo "$*"; }

rst_trim() {
	local var="$*"
	var="${var#"${var%%[![:space:]]*}"}"
	var="${var%"${var##*[![:space:]]}"}"
	echo -n "$var"
}

######
# Document

rst_title() {
	has_args 2 "$@"
	rst_header '$' "$1"
	rst_header '%' "$2"
}


######
# Headers

rst_header_n() {
	has_args 2 "$@"
	local level=$1
	local str=$2
	local char=${rst_header_chars[$((level - 1))]}
	rst_header "$char" "$str"
}

rst_header() {
	has_args 2 "$@"
	local char=$1

	local str=$2
	echo "$str"

	local bar=$(printf "%${#str}s" '')
	echo "${bar// /$char}"

	echo
}


######
# Blocks

rst_p() { rst_reflow "$@"; echo; }

rst_block_literal() {
	rst_reflow "$1::"
	shift
	rst_indent rst_reflow "$@"
}

rst_block_quote() {
	rst_indent rst_reflow "$@"
}


######
# Inline

rst_italic() { echo ":emphasis:\`$*\`"; }
rst_bold() { echo ":strong:\`$*\`"; }
rst_subscript() { echo ":subscript:\`$*\`"; }
rst_superscript() { echo ":superscript:\`$*\`"; }
rst_title_ref() { echo ":title:\`$*\`"; }

rst_literal() { echo ":literal:\`$*\`"; }
rst_code() { echo ":code:\`$*\`"; }
rst_math() { echo ":math:\`$*\`"; }
rst_pep() { echo ":PEP:\`$*\`"; }
rst_rfc() { echo ":RFC:\`$*\`"; }


######
# Lists

rst_bullet_item() {
	local rst_flow_hanging=true
	rst_reflow " - $1"
}
rst_bullet_items() {
	local rst_indent=$((rst_indent + 2))
	for_each rst_bullet_item "$@"
}

rst_enum_item() {
	local rst_flow_hanging=true
	rst_reflow "$rst_enum_index. $@"
	$((rst_enum_index++))
}
rst_enum_items() {
	local rst_enum_index=$rst_enum_start
	for_each rst_enum_item "$@"
}

rst_def_item() {
	echo "$1"
	rst_reflow "$2"
}
rst_def_items() { for_each_pair rst_def_item "$@"; }

rst_option_item() {
	echo "$1"
	rst_indent rst_reflow "$2"
}
rst_option_items() { for_each_pair rst_option_item "$@"; }


######
# Directives

# rst_directive() - Generates a directive of the given type ($1) and
# using the given data ($2).
#
# $1 - The type of directive to emit (e.g. ``image``).
# $2 - Directive data (optional).  The format depends on the type of
#      directive ($1).
# $@ - Directive body
rst_directive() {
	min_args 2 "$@"
	local kind=$1
	local data=$2
	shift 2

	echo ".. $kind::${data:+ }$data"
	for_each_pair rst_directive_opt_print "${rst_directive_opts[@]}"
	[ "$*" ] || return

	[ -z "${rst_directive_opts[*]}" ] || echo
	for_each rst_directive_line "$@"
	echo
}

# rst_directive_line() - Prints a single line ($1) of a directive body,
# indented to align with the directive name.
rst_directive_line() { [ "$1" ] && echo "   $1" || echo; }


######
# Directive Options

# rst_is_directive_opt() - Checks whether a string ($1) is a directive option.
# Returns: Success if the string is a valid directive option
rst_is_directive_opt() { [ "${1#:}" != "$1" -a "${1%:}" != "$1" ]; }

# rst_directive_opt_add() - Adds a directive option ($1) and value ($@)
# pair to ``$rst_directive_opts[]``.
# $1 - Option name
# $@ - Option values (optional)
rst_directive_opt_add() {
	min_args 1 "$@"
	local opt=$1
	shift
	rst_directive_opts+=( ":$opt:" "$*" )
}

# rst_directive_opt_print() - Prints a directive option ($1) and value ($2).
rst_directive_opt_print() { echo "   $1 $2"; }


######
# Topic, Sidebar, Code

# rst_topic() - Generates a topic directive
# $1 - Topic Title
# $@ - Topic Body
rst_topic() {
	min_args 1 "$@"
	rst_directive 'topic' "$@"
}

# rst_sidebar() - Generates a `sidebar`.
# $1 - Title
# $2 - Subtitle (may be a `null` string)
# $@ - Sidebar body
rst_sidebar() {
	min_args 2 "$@"
	local title=$1
	local subtitle=$2
	shift 2

	local -a opts=()
	[ -z "$subtitle" ] || opts+=( ':subtitle:' "$subtitle" )

	local -a rst_directive_opts=( "${opts[@]}" )
	rst_directive 'sidebar' "$title" "$@"
}

# rst_block_code() - Generates a `code` block.
# $1 - Code language (see ``dev/tool/pygmentize``)
# $@ - Code block body
rst_block_code() {
	min_args 2 "$@"
	local language=$1
	[ -z "$rst_block_code_line_start" ] \
		|| opts+=( ":number-lines:" "$rst_block_code_line_start" )
	local rst_directive_opts=( "${opts[@]}" )
	rst_directive 'code' "$language" "$@"
}


######
# Admonisions
#
# For a list of supported admonisions, see ``$rst_admonitions[]``.

# rst_is_admonition() - Checks whether a string ($1) is a valid
# keyword.
rst_is_admonition() {
	in_list "$kind" "${rst_admonitions[@]}" \
		|| error "$1: invalid admonition"
}

# rst_admonition() - Produces the given type ($1) of admonition directive.
# $1 - Type of Admonition.  See ``$rst_admonitions[]``.
rst_admonition() {
	local kind=$1
	rst_is_admonition "$kind"
	rst_directive "$kind" "" "$@"
}


######
# Images and Figures

# rst_image() - Generates an image directive for the given url ($1) and
# image options ($@).  The following options are supported:
#
# - ``:alt:``: Alternative image text.
# - ``:align:``: Conrols the alignment of the image.
# - ``:height:``, ``width``: Control the dimensions of the image.
# - ``:scale:``: Control the scale of the image (0-100).
# - ``:target:``: Makes the image into a hyperlink reference.
#
# The type of directive generated is controlled by the value of
# ``$rst_image_element``.  The default value of ``image`` generates
# canonical image directives.
#
# If ``$rst_image_strict`` is ``true``, an invalid option will produce
# an ``error``.  Otherwise, option processing will end.  The invalid
# option, along with any other options, will be emitted as part of the
# directive body.
#
# Note: The value of ``$rst_image_strict`` does not impact validation
# of an option's values.  If an invalid value is given for the ``scale``
# or ``align`` options, an ``error`` will always be produced.
#
# The ``rst_figure()`` function overrides these two settings and
# calls this function.
#
# $1 - The URL location of the image
# $@ - Image options (optional)
rst_image() {
	local url=$1
	shift

	local -a opts=()
	while [ "$*" ]; do
		local opt=$1
		rst_is_directive_opt "$opt" || break
		opt=${opt#:}
		opt=${opt%:}

		local val=$2
		case "$opt" in
		(alt|height|width|target)
			opts+=( ":$opt:" "$val" )
			shift
			;;
		(scale)
			[ "$val" -ge 0 -a "$val" -le 100 ] \
				|| rst_image_error "$opt" "$val"
			opts+=( ":$opt:" "$val %" )
			shift
			;;
		(align)
			rst_is_image_align "$val" \
				|| rst_image_error "$opt" "$val"
			opts+=( ":$opt:" "$val" )
			shift
			;;
		(*)
			! $rst_image_strict || rst_image_error '' "$opt"
			break
			;;
		esac
		shift
	done

	local -a rst_directive_opts=( "${opts[@]}" )
	rst_directive "$rst_image_element" "$url" "$@"
}

# rst_is_image_align() - Checks whether a string ($1) is a valid
# image alignment option value.
# $1 - Align option value
# Returns: Success if the value is valid.
rst_is_image_align() {
	local val=$1
	case "$val" in
	(top|middle|bottom|left|center|right) true ;;
	(*) false ;;
	esac
}

# rst_image_error() - Reports an error found when parsing an image
# option ($1) or option value ($2).
# $1 - The option that generated the error (optional)
# $2 - The value of the option
# $3 - The type of option error (optional)
rst_image_error() {
	error "$2: unknown image${1+: }$1${1+: }option${3:+ }$3"
}

# rst_figure() - Generates a `figure`.  See the ``rst_image()`` function
# for usage information.
rst_figure() {
	local rst_image_element='figure'
	local rst_image_strict=false
	rst_image "$@"
}


######
# TODO: Tables


######
# Rendering

rst_to_html() {
	local -a opts
	if [ "${rst_html_css[*]}" ]; then
		local IFS=','
		opts+=( "--stylesheet=${rst_html_css[*]}" )
	fi
	run rst2html "${opts[@]}" "$@"
}
rst_to_latex() { run rst2latex "$@"; }
rst_to_pdf() {
	rst_to_latex "$1" >"$tmp";
	latex_to_pdf "$tmp" "$2"
}

View the Developer Guide Index

View the Reference Manual Index


Generated on Fri Jul 28 14:35:35 PDT 2017 by mcsh d14 v0.23.0.