ede/doc/asciidoc/a2x
2008-09-04 10:02:41 +00:00

588 lines
16 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# a2x - convert Asciidoc text file to PDF, XHTML, HTML Help, manpage
# or plain text
#
# Copyright (C) 2006 Stuart Rackham. Free use of this software is granted
# under the terms of the GNU General Public License (GPL).
#
VERSION=1.0.0
BASENAME=$(basename "$0")
REALNAME="$0"
if [ ! -e "$REALNAME" ]; then
REALNAME=$(which "$REALNAME")
fi
REALNAME="$(readlink -f "$REALNAME")"
GLOBAL_CONF_DIR=/etc/asciidoc
#--------------------------------------------------------------------
# Constants.
#--------------------------------------------------------------------
# These are mostly related to command options and are set by parse_options().
ASCIIDOC_OPTS=
COPY=no
DESTINATION_DIR=
DOCTYPE=
DRY_RUN=no
FORMAT=xhtml
ICONS=no
ICONS_DIR=./images/icons
SKIP_ASCIIDOC=no
SRC_DIR=
SRC_FILE=
SRC_NAME= # Source file name sans path and file name extension.
STYLESHEET=./docbook-xsl.css
VERBOSE_2=no
VERBOSE=no
XSLTPROC_OPTS=
#--------------------------------------------------------------------
# General purpose functions
#--------------------------------------------------------------------
# Write $1 to stderr with backslash-escaped characters and no trailing newline.
function write_console()
{
echo -ne "$1" >&2
}
# Write newline to stderr.
function newline()
{
echo >&2
}
# Write $1 message to stderr.
function console_msg()
{
echo "$BASENAME: $1" >&2
}
# Write $1 to stderr if verbose or dry-run options set.
function verbose_msg()
{
if isyes "$VERBOSE" || isyes "$DRY_RUN"; then
console_msg "$1"
fi
}
# Return 0 if $1 is interpreted as an affirmative string.
function isyes()
{
case "$1" in
y|Y|yes|YES|Yes|true|TRUE|True) return 0;;
esac
return 1
}
# Log message $1 and exit with status $2 (default 1).
function quit()
{
local err tmp
err=${2:-1}
if [ $err -ne 0 ]; then
tmp="$VERBOSE"
VERBOSE=yes # Force console error exit message.
console_msg "failed: $1"
VERBOSE="$tmp"
else
console_msg "$1"
fi
cleanup
exit $err
}
# Execute the $1 command in the current shell subject to VERBOSE, DRY_RUN shell
# variables.
function execute_command()
{
if isyes "$VERBOSE" || isyes "$DRY_RUN"; then
console_msg "eval: $1"
fi
if isyes "$DRY_RUN"; then
return 0
else
eval $1
return $?
fi
}
# Same as execute_command() but if error occurs prints optional $2 message and
# exits program.
function execute_command_2()
{
local msg
execute_command "$1"
if [ $? -ne 0 ]; then
if [ -n "$2" ]; then
msg="$2"
else
msg="$1"
fi
quit "$msg"
fi
}
# Return 127 if $1 is not in search path else return 0.
function require()
{
if ! which "$1" >/dev/null 2>&1; then
quit "cannot find required program: $1" 127
fi
}
# Join path $1 to path $2.
function join()
{
if [ -n "$1" ]; then
echo "$1/$2"
else
echo "$2"
fi
}
# Echo the total size in bytes of file name arguments.
function file_size()
{
echo $(du -cb "$@" | tail -1 | awk '{print $1}')
}
#--------------------------------------------------------------------
# Application specific functions
#--------------------------------------------------------------------
# Trap interrupts.
function set_trap()
{
# By convention exit code is 128 + signal number.
trap "newline; quit 'exiting: SIGINT' 130" SIGINT
trap "newline; quit 'exiting: SIGQUIT' 131" SIGQUIT
trap "quit 'exiting: SIGHUP' 129" SIGHUP
trap "quit 'exiting: SIGTERM' 143" SIGTERM
}
# Called at program exit.
function cleanup()
{
if [ "$(pwd)" != "$PWD" ]; then
execute_command "cd \"$PWD\""
fi
}
# Print help summary.
function help()
{
cat <<EOF
synopsis:
$BASENAME [OPTIONS] FILE
options:
--asciidoc-opts=ASCIIDOC_OPTS asciidoc(1) options
--copy copy icons or HTML stylesheet
-D, --destination-dir=PATH output directory (defaults to FILE directory)
-d, --doctype=DOCTYPE article, manpage, book
-f, --format=FORMAT chunked,htmlhelp,manpage,odt,pdf,text,xhtml
-h, --help print command syntax summary
--icons use admonition and navigation icons
--icons-dir=PATH admonition and navigation icon directory
-n, --dry-run don't do anything just print the commands
-s, --skip-asciidoc skip asciidoc(1) execution
--stylesheet=PATH target HTML CSS stylesheet file name.
--version print program version to stdout
-v, --verbose print operational details to stderr
--xsltproc-opts=XSLTPROC_OPTS xsltproc(1) options
EOF
}
# Print full path name of file $1 searching first in the directory containing
# the asciidoc executable and then in the global configuration directory.
function conf_file()
{
local result dir
# First look in same directory as asciidoc executable.
dir="$(dirname "$REALNAME")"
if [ ! -f "$dir/$1" -a -d $GLOBAL_CONF_DIR ]; then
dir=$GLOBAL_CONF_DIR
fi
result="$dir/$1"
echo $result
}
#--------------------------------------------------------------------
# Process command-line arguments $@
#--------------------------------------------------------------------
function parse_options()
{
if [ -z "$*" ]; then
help; exit 0
fi
require "getopt"
getopt -T >/dev/null
if [ $? -ne 4 ]; then
quit "enhanced getopt(1) required"
fi
short_opts="d:D:f:hnsv"
long_opts="asciidoc-opts:,destination-dir:,doctype:,help,icons-dir:,dry-run,format:,copy,icons,skip-asciidoc,stylesheet:,version,verbose,xsltproc-opts:"
args=$(getopt -o $short_opts -l $long_opts -n $BASENAME -- "$@" 2>/dev/null)
if [ $? -ne 0 ]; then
quit "invalid command options, run: a2x --help"
fi
eval set -- "$args" # Set positional variables.
while true ; do
case "$1" in
--asciidoc-opts)
ASCIIDOC_OPTS=$2
shift 2 ;;
--copy)
COPY=yes;
shift ;;
-d|--doctype)
DOCTYPE=$2
shift 2 ;;
-D|--destination-dir)
DESTINATION_DIR=$2
shift 2 ;;
-f|--format)
FORMAT=$2
shift 2 ;;
-h|--help)
help; exit 0 ;;
--icons)
ICONS=yes;
shift ;;
--icons-dir)
ICONS_DIR=$2
shift 2 ;;
-n|--dry-run)
DRY_RUN=yes;
shift ;;
-s|--skip-asciidoc)
SKIP_ASCIIDOC=yes;
shift ;;
--stylesheet)
STYLESHEET=$2
shift 2 ;;
--version)
echo "$BASENAME $VERSION" ; exit 0 ;;
-v|--verbose)
if isyes "$VERBOSE"; then
VERBOSE_2=yes
else
VERBOSE=yes
fi
shift ;;
--xsltproc-opts)
XSLTPROC_OPTS=$2
shift 2 ;;
--)
shift; break ;;
*)
quit "unrecognized option: $1" ;;
esac
done
if isyes "$DRY_RUN"; then
VERBOSE=yes
fi
if [ $# -eq 0 ]; then
quit "source file not specified"
fi
if [ $# -ne 1 ]; then
quit "only one source file allowed"
fi
if [ ! -r "$1" ]; then
quit "source file not found: $1"
fi
SRC_FILE=$1
SRC_DIR=$(dirname "$1")
SRC_NAME=$1
SRC_NAME=${SRC_NAME##*/} # Strip path.
SRC_NAME=${SRC_NAME%.*} # Strip extension.
}
#--------------------------------------------------------------------
# Validate program options.
#--------------------------------------------------------------------
function validate_options()
{
case "$FORMAT" in
chunked|htmlhelp|manpage|odt|pdf|text|xhtml) ;;
*) quit "illegal format: $FORMAT" ;;
esac
if [ -z "$DOCTYPE" ]; then
if [ "$FORMAT" = "manpage" ]; then
DOCTYPE=manpage
else
DOCTYPE=article
fi
fi
case "$DOCTYPE" in
article|book|manpage) ;;
*) quit "illegal doctype: $DOCTYPE" ;;
esac
if [ -z "$ICONS_DIR" ]; then
quit "icons directory not specified"
fi
if [[ "$ICONS_DIR" == /* ]]; then
quit "icons directory must be relative: $ICONS_DIR"
fi
ICONS_DIR=${ICONS_DIR%*/} # Strip trailing backslash.
if [ ! -z "$DESTINATION_DIR" ]; then
if [ ! -d "$DESTINATION_DIR" ]; then
quit "destination directory not found: $DESTINATION_DIR"
fi
else
DESTINATION_DIR="$SRC_DIR"
fi
if [ -z "$STYLESHEET" ]; then
quit "stylesheet cannot be blank"
fi
if [[ "$STYLESHEET" == /* ]]; then
quit "stylesheet path must be relative: $STYLESHEET"
fi
}
# Conditionally copy distribution stylesheet and admonition and navigation
# icons to destination directory $1.
function copy_stylesheet_and_icons()
{
if isyes $COPY; then
copy_stylesheet "$1"
if isyes $ICONS; then
copy_icons "$1/$ICONS_DIR"
fi
fi
}
# Copy distribution stylesheet to destination directory $1.
function copy_stylesheet()
{
local src dst
src=$(conf_file stylesheets/docbook-xsl.css)
if [ ! -r "$src" ]; then
quit "file not found: $src"
fi
dst="$1/$STYLESHEET"
# Check we're not trying to copy the file onto itself.
if [[ "$src" -ef "$dst" ]]; then
return
fi
execute_command_2 "cp -u \"$src\" \"$dst\""
}
# Copy distribution admonition and navigation icons to destination directory
# $1.
function copy_icons()
{
local src dst
dst="$1"
# Set source icons directory.
src=$(conf_file images/icons/home.png)
if [ ! -r "$src" ]; then
quit "file not found: $src"
fi
src=$(dirname "$src")
# Check we're not trying to copy the file onto itself.
if [[ "$src" -ef "$dst" ]]; then
return
fi
if [ -e "$dst" ]; then
if [ ! -d "$dst" ]; then
quit "icon destination must be a directory: $dst"
fi
else
execute_command_2 "mkdir -p \"$dst\""
fi
execute_command_2 "cp -rfu \"$src/\"* \"$dst\""
}
#--------------------------------------------------------------------
# Format conversion functions.
#--------------------------------------------------------------------
# Convert AsciiDoc $SRC_FILE to DocBook XML if it is newer than the
# XML output file. $1 has additional asciidoc(1) options.
function to_docbook()
{
local xml
xml="$SRC_DIR/$SRC_NAME.xml"
if isyes $SKIP_ASCIIDOC; then
if [ ! -r "$xml" ]; then
quit "file not found: $xml"
fi
return
fi
require "asciidoc"
execute_command_2 "asciidoc $ASCIIDOC_OPTS $1 -b docbook \"$SRC_FILE\""
}
function to_xhtml()
{
require "xsltproc"
local xsl xml html
xsl=$(conf_file docbook-xsl/xhtml.xsl)
if [ ! -r "$xsl" ]; then
quit "file not found: $xsl"
fi
to_docbook
xml=$(readlink -f "$SRC_DIR/$SRC_NAME.xml")
html="$SRC_NAME.html"
copy_stylesheet_and_icons "$DESTINATION_DIR"
execute_command_2 "cd \"$DESTINATION_DIR\""
execute_command_2 "xsltproc $XSLTPROC_OPTS --nonet \
\"$xsl\" \"$xml\" >\"$html\""
execute_command_2 "cd - >/dev/null"
}
function to_chunked()
{
require "xsltproc"
local chunkdir xsl xml hhp chm
case "$FORMAT" in
chunked)
chunkdir="$DESTINATION_DIR/$SRC_NAME.chunked"
xsl=chunked.xsl
;;
htmlhelp)
chunkdir="$DESTINATION_DIR/$SRC_NAME.htmlhelp"
hhp="$SRC_NAME.hhp"
chm="$SRC_NAME.chm"
XSLTPROC_OPTS="$XSLTPROC_OPTS \
--stringparam htmlhelp.hhp \"$hhp\"
--stringparam htmlhelp.chm \"$chm\""
xsl=htmlhelp.xsl
;;
esac
xsl=$(conf_file docbook-xsl/$xsl)
if [ ! -r "$xsl" ]; then
quit "file not found: $xsl"
fi
to_docbook
xml=$(readlink -f "$SRC_DIR/$SRC_NAME.xml")
if [ ! -d "$chunkdir" ]; then
execute_command_2 "mkdir \"$chunkdir\""
fi
execute_command_2 "rm -f \"$chunkdir/*.html\""
copy_stylesheet_and_icons "$chunkdir"
execute_command_2 "cd \"$DESTINATION_DIR\""
execute_command_2 "xsltproc $XSLTPROC_OPTS --nonet \
--stringparam base.dir \"$(basename "$chunkdir")/\" \
\"$xsl\" \"$xml\""
execute_command_2 "cd - >/dev/null"
}
function to_manpage()
{
require "xsltproc"
local xsl xml
xsl=$(conf_file docbook-xsl/manpage.xsl)
if [ ! -r "$xsl" ]; then
quit "file not found: $xsl"
fi
to_docbook "-d manpage"
xml=$(readlink -f "$SRC_DIR/$SRC_NAME.xml")
execute_command_2 "cd \"$DESTINATION_DIR\""
execute_command_2 "xsltproc $XSLTPROC_OPTS --nonet \
\"$xsl\" \"$xml\""
execute_command_2 "cd - >/dev/null"
}
function to_pdf()
{
require "xsltproc"
require "fop.sh"
local xsl xml fo pdf
xsl=$(conf_file docbook-xsl/fo.xsl)
if [ ! -r "$xsl" ]; then
quit "file not found: $xsl"
fi
xml="$SRC_DIR/$SRC_NAME.xml"
fo="$SRC_DIR/$SRC_NAME.fo"
pdf="$DESTINATION_DIR/$SRC_NAME.pdf"
to_docbook
execute_command_2 "xsltproc $XSLTPROC_OPTS --nonet \
\"$xsl\" \"$xml\" >\"$fo\""
execute_command_2 "fop.sh \"$fo\" \"$pdf\""
}
function to_odt()
{
require "docbook2odf"
local xml odt opts
xml="$SRC_DIR/$SRC_NAME.xml"
odt="$DESTINATION_DIR/$SRC_NAME.odt"
opts="--force"
if ! isyes $VERBOSE; then
opts="$opts --quiet"
fi
to_docbook
execute_command_2 "docbook2odf $opts --input-file \"$xml\" --output-file \"$odt\""
}
function to_text()
{
require "asciidoc"
require "lynx"
local html text conf
html="$SRC_DIR/$SRC_NAME.html"
text="$DESTINATION_DIR/$SRC_NAME.text"
conf=$(conf_file text.conf)
execute_command_2 "asciidoc $ASCIIDOC_OPTS -f "$conf" -b html4 \
-o - \"$SRC_FILE\" | lynx -dump -stdin >\"$text\""
}
#--------------------------------------------------------------------
# Main
#--------------------------------------------------------------------
PWD=`pwd`
set_trap
parse_options "$@"
validate_options
ASCIIDOC_OPTS="--doctype=$DOCTYPE $ASCIIDOC_OPTS"
if isyes $VERBOSE_2; then
ASCIIDOC_OPTS="$ASCIIDOC_OPTS --verbose"
XSLTPROC_OPTS="$XSLTPROC_OPTS --verbose"
fi
case "$FORMAT" in
xhtml|chunked|htmlhelp)
XSLTPROC_OPTS="$XSLTPROC_OPTS \
--stringparam html.stylesheet \"$STYLESHEET\""
;;
esac
if isyes $ICONS; then
XSLTPROC_OPTS="$XSLTPROC_OPTS --stringparam callout.graphics 1 \
--stringparam navig.graphics 0 \
--stringparam admon.textlabel 0 \
--stringparam admon.graphics 1 \
--stringparam admon.graphics.path \"$ICONS_DIR/\" \
--stringparam callout.graphics.path \"$ICONS_DIR/callouts/\" \
--stringparam navig.graphics.path \"$ICONS_DIR/\""
else
XSLTPROC_OPTS="$XSLTPROC_OPTS --stringparam callout.graphics 0 \
--stringparam navig.graphics 0 \
--stringparam admon.textlabel 1 \
--stringparam admon.graphics 0"
fi
case "$FORMAT" in
chunked|htmlhelp) to_chunked;;
manpage) to_manpage;;
odt) to_odt;;
pdf) to_pdf;;
text) to_text;;
xhtml) to_xhtml;;
esac
cleanup
#--------------------------------------------------------------------
# vim: set et ts=4 sw=4 sts=4:
#--------------------------------------------------------------------