#!/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 </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: #--------------------------------------------------------------------