#!/bin/sh

# groffer - display groff files

# Source file position: <groff-source>/contrib/groffer/groffer.sh

# Copyright (C) 2001,2002,2003,2004 Free Software Foundation, Inc.
# Written by Bernd Warken

# This file is part of groff version 1.18.1.

# groff is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.

# groff is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
# License for more details.

# You should have received a copy of the GNU General Public License
# along with groff; see the files COPYING and LICENSE in the top
# directory of the groff source.  If not, write to the Free Software
# Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

_PROGRAM_NAME='groffer';
_PROGRAM_VERSION='0.9.11';
_LAST_UPDATE='15 June 2004';


########################################################################
# Determine the shell under which to run this script from the command
# line arguments or $GROFF_OPT; if none is specified, just go on with
# the starting shell.

if test _"${_groffer_run}"_ = __; then
  # only reached during the first run of the script

  export _groffer_run;		# counter for the runs of groffer
  _groffer_run='first';

  export _PROGRAM_NAME;
  export _PROGRAM_VERSION;
  export _LAST_UPDATE;

  export GROFFER_OPT;		# option environment for groffer
  export _GROFFER_SH;		# file name of this shell script
  export _OUTPUT_FILE_NAME;	# output generated, see main_set_res..()

  export _CONFFILES;		# configuration files
  _CONFFILES="/etc/groff/groffer.conf ${HOME}/.groff/groffer.conf";

  case "$0" in
  *${_PROGRAM_NAME}*)
    _GROFFER_SH="$0";
    # was: _GROFFER_SH="/usr/bin/${_PROGRAM_NAME}";
    ;;
  *)
    echo "The ${_PROGRAM_NAME} script should be started directly." >&2
    exit 1;
    ;;
  esac;

  ###########################
  # _get_opt_shell ("$@")
  #
  # Determine whether `--shell' was specified in $GROFF_OPT or in $*;
  # if so echo its argument.
  #
  _get_opt_shell()
  {
    local i;
    local _sh;
    case " ${GROFFER_OPT} $*" in
      *\ --shell\ *|*\ --shell=*)
        (
          eval set -- "${GROFFER_OPT}" '"$@"';
          _sh='';
          for i in "$@"; do
            case "$1" in
              --shell)
                if test "$#" -ge 2; then
                  _sh="$2";
                  shift;
                fi;
                ;;
              --shell=?*)
                # delete up to first `=' character
                _sh="$(echo -n "$1" | sed -e 's/^[^=]*=//')";
                ;;
            esac;
            shift;
          done;
          echo -n "${_sh}";
        )
        ;;
    esac;
  }


  ###########################
  # _test_on_shell (<name>)
  #
  # Test whether <name> is a shell program of Bourne type (POSIX sh).
  #
  _test_on_shell()
  {
    if test "$#" -le 0 || test _"$1"_ = __; then
      return 1;
    fi;
    # do not quote $1 to allow arguments
    test _"$($1 -c 's=ok; echo -n "$s"' 2>/dev/null)"_ = _ok_;
  }

  # do the shell determination from command line and $GROFFER_OPT
  _shell="$(_get_opt_shell "$@")";

  if test _"${_shell}"_ = __; then
    # none found, so look at the `--shell' lines in configuration files
    export f;
    for f in ${_CONFFILES}; do
      if test -f $f; then
        _all="$(cat $f | sed -n -e '/^--shell[= ] *\([^ ]*\)$/s//\1/p')"
        for s in ${_all}; do
          _shell=$s;
        done;
      fi;
    done;
    unset f;
    unset s;
    unset _all;
  fi;

  # restart the script with the last found $_shell, if it is a shell
  if _test_on_shell "${_shell}"; then
    _groffer_run='second';
    # do not quote $_shell to allow arguments
    exec ${_shell} "${_GROFFER_SH}" "$@";
    exit;
  fi;

  _groffer_run='second';
  unset _shell;

fi; # end of first run

if test _"${_groffer_run}"_ != _second_;
then
  echo "$_groffer_run should be 'second' here." >&2
  exit 1
fi;

unset _groffer_run


########################################################################
# diagnostic messages
#
export _DEBUG;
_DEBUG='no';			# disable debugging information
#_DEBUG='yes';			# enable debugging information

export _DEBUG_LM;
_DEBUG_LM='no';			# disable landmark messages
#_DEBUG_LM='yes';		# enable landmark messages


########################################################################
#                       Environment Variables
########################################################################

# Environment variables that exist only for this file start with an
# underscore letter.  Global variables to this file are written in
# upper case letters, e.g. $_GLOBAL_VARIABLE; temporary variables
# start with an underline and use only lower case letters and
# underlines, e.g.  $_local_variable .

#   [A-Z]*     system variables,      e.g. $MANPATH
#   _[A-Z_]*   global file variables, e.g. $_MAN_PATH
#   _[a-z_]*   temporary variables,   e.g. $_manpath

# Due to incompatibilities of the `ash' shell, the name of loop
# variables in `for' must be single character
#   [a-z]      local loop variables,   e.g. $i


########################################################################
# read-only variables (global to this file)
########################################################################

# characters

export _BQUOTE;
export _BSLASH;
export _DQUOTE;
export _NEWLINE;
export _LBRACK;
export _LPAR;
export _RBRACK;
export _RPAR;
export _SPACE;
export _SQUOTE;
export _TAB;

_BQUOTE='`';
_BSLASH='\';
_DQUOTE='"';
_NEWLINE='
';
_LBRACK='[';
_LPAR='(';
_RBRACK=']';
_RPAR=')';
_SPACE=' ';
_SQUOTE="'";
_TAB='	';

# function return values; `0' means ok; other values are error codes
export _ALL_EXIT;
export _BAD;
export _ERROR;
export _GOOD;
export _NO;
export _OK;
export _YES;

_GOOD='0';			# return ok
_BAD='1';			# return negatively, error code `1'
_ERROR='7';			# for syntax errors; no `-1' in `ash'

_ALL_EXIT="${_GOOD} ${_BAD} ${_ERROR}"; # all exit codes (for `trap_set')

_NO="${_BAD}";
_YES="${_GOOD}";
_OK="${_GOOD}";

# quasi-functions, call with `eval'
export return_ok;
export return_good;
export return_bad;
export return_yes;
export return_no;
export return_error;
return_ok="func_pop; return ${_OK}";
return_good="func_pop; return ${_GOOD}";
return_bad="func_pop; return ${_BAD}";
return_yes="func_pop; return ${_YES}";
return_no="func_pop; return ${_NO}";
return_error="func_pop; return ${_ERROR}";


export _DEFAULT_MODES;
_DEFAULT_MODES='x,ps,tty';
export _DEFAULT_RESOLUTION;
_DEFAULT_RESOLUTION='75';

export _DEFAULT_TTY_DEVICE;
_DEFAULT_TTY_DEVICE='latin1';

# _VIEWER_* viewer programs for different modes (only X is necessary)
# _VIEWER_* a comma-separated list of viewer programs (with options)
export _VIEWER_DVI;		# viewer program for dvi mode
export _VIEWER_PS;		# viewer program for ps mode
export _VIEWER_HTML_X;		# viewer program for html mode in X
export _VIEWER_HTML_TTY;	# viewer program for html mode in tty
_VIEWER_DVI='xdvi,dvilx';
_VIEWER_PDF='xpdf,acroread';
_VIEWER_PS='gv,ghostview,gs_x11,gs';
_VIEWER_HTML='konqueror,mozilla,netscape,opera,amaya,arena,lynx';
_VIEWER_X='gxditview,xditview';

# Search automatically in standard sections `1' to `8', and in the
# traditional sections `9', `n', and `o'.  On many systems, there
# exist even more sections, mostly containing a set of man pages
# special to a specific program package.  These aren't searched for
# automatically, but must be specified on the command line.
export _MAN_AUTO_SEC;
_MAN_AUTO_SEC="'1' '2' '3' '4' '5' '6' '7' '8' '9' 'n' 'o'"

export _PROCESS_ID;		# for shutting down the program
_PROCESS_ID="$$";


############ the command line options of the involved programs
#
# The naming scheme for the options environment names is
# $_OPTS_<prog>_<length>[_<argspec>]
#
# <prog>:    program name GROFFER, GROFF, or CMDLINE (for all
#            command line options)
# <length>:  LONG (long options) or SHORT (single character options)
# <argspec>: ARG for options with argument, NA for no argument;
#            without _<argspec> both the ones with and without arg.
#
# Each option that takes an argument must be specified with a
# trailing : (colon).

# exports
export _OPTS_GROFFER_SHORT_NA;
export _OPTS_GROFFER_SHORT_ARG;
export _OPTS_GROFFER_LONG_NA;
export _OPTS_GROFFER_LONG_ARG;
export _OPTS_GROFF_SHORT_NA;
export _OPTS_GROFF_SHORT_ARG;
export _OPTS_GROFF_LONG_NA;
export _OPTS_GROFF_LONG_ARG;
export _OPTS_X_SHORT_ARG;
export _OPTS_X_SHORT_NA;
export _OPTS_X_LONG_ARG;
export _OPTS_X_LONG_NA;
export _OPTS_MAN_SHORT_ARG;
export _OPTS_MAN_SHORT_NA;
export _OPTS_MAN_LONG_ARG;
export _OPTS_MAN_LONG_NA;
export _OPTS_MANOPT_SHORT_ARG;
export _OPTS_MANOPT_SHORT_NA;
export _OPTS_MANOPT_LONG_ARG;
export _OPTS_MANOPT_LONG_NA;
export _OPTS_CMDLINE_SHORT_NA;
export _OPTS_CMDLINE_SHORT_ARG;
export _OPTS_CMDLINE_LONG_NA;
export _OPTS_CMDLINE_LONG_ARG;

###### groffer native options

_OPTS_GROFFER_SHORT_NA="'h' 'Q' 'v' 'V' 'X' 'Z'";
_OPTS_GROFFER_SHORT_ARG="'T'";

_OPTS_GROFFER_LONG_NA="'auto' 'debug' 'default' 'dvi' \
'groff' 'help' 'intermediate-output' 'html' 'man' \
'no-location' 'no-man' 'pdf' 'ps' 'rv' 'source' 'text' 'text-device' \
'title' 'tty' 'tty-device' 'version' 'whatis' 'where' 'www' 'x' 'X'";

_OPTS_GROFFER_LONG_ARG="\
'apropos' 'apropos-data' 'apropos-devel' 'apropos-progs' \
'default-modes' 'dvi-viewer' 'extension' 'fg' 'fn' 'font' \
'foreground' 'html-viewer' 'mode' 'pdf-viewer' 'ps-viewer' 'shell' \
'tty-viewer' 'www-viewer' 'x-viewer' 'X-viewer'";

##### groffer options inhereted from groff

_OPTS_GROFF_SHORT_NA="'a' 'b' 'c' 'C' 'e' 'E' 'g' 'G' 'i' 'l' 'N' 'p' \
'R' 's' 'S' 't' 'U' 'V' 'z'";
_OPTS_GROFF_SHORT_ARG="'d' 'f' 'F' 'I' 'L' 'm' 'M' 'n' 'o' 'P' 'r' \
'w' 'W'";
_OPTS_GROFF_LONG_NA="'source'";
_OPTS_GROFF_LONG_ARG="'device' 'macro-file'";

##### groffer options inhereted from the X Window toolkit

_OPTS_X_SHORT_NA="";
_OPTS_X_SHORT_ARG="";

_OPTS_X_LONG_NA="'iconic' 'rv'";

_OPTS_X_LONG_ARG="'background' 'bd' 'bg' 'bordercolor' 'borderwidth' \
'bw' 'display' 'fg' 'fn' 'font' 'foreground' 'ft', 'geometry'
'resolution' 'title' 'xrm'";

###### groffer options inherited from man

_OPTS_MAN_SHORT_NA="";
_OPTS_MAN_SHORT_ARG="";

_OPTS_MAN_LONG_NA="'all' 'ascii' 'catman' 'debug' 'ditroff' 'help' \
'local-file' 'location' 'pager' 'troff' 'update' 'version' \
'whatis' 'where'";

_OPTS_MAN_LONG_ARG="'extension' 'locale' 'manpath' \
'pager' 'preprocessor' 'prompt' 'sections' 'systems' 'troff-device'";

###### additional options for parsing $MANOPT only

_OPTS_MANOPT_SHORT_NA="'7' 'a' 'c' 'd' 'D' 'f' 'h' 'k' 'l' 't' 'u' \
'V' 'w' 'Z'";
_OPTS_MANOPT_SHORT_ARG="'e' 'L' 'm' 'M' 'p' 'P' 'r' 'S' 'T'";

_OPTS_MANOPT_LONG_NA="${_OPTS_MAN_LONG_NA} \
'apropos' 'debug' 'default' 'html' 'ignore-case' 'location-cat' \
'match-case' 'troff' 'update' 'version' 'where-cat'";

_OPTS_MANOPT_LONG_ARG="${_OPTS_MAN_LONG_NA} \
'config_file' 'encoding' 'locale'";

###### collections of command line options

_OPTS_CMDLINE_SHORT_NA="${_OPTS_GROFFER_SHORT_NA}\
${_OPTS_GROFF_SHORT_NA} ${_OPTS_X_SHORT_NA} ${_OPTS_MAN_SHORT_NA}";
_OPTS_CMDLINE_SHORT_ARG="${_OPTS_GROFFER_SHORT_ARG} \
${_OPTS_GROFF_SHORT_ARG} ${_OPTS_X_SHORT_ARG} ${_OPTS_MAN_SHORT_ARG}";

_OPTS_CMDLINE_LONG_NA="${_OPTS_GROFFER_LONG_NA} \
${_OPTS_GROFF_LONG_NA} ${_OPTS_X_LONG_NA} ${_OPTS_MAN_LONG_NA}";
_OPTS_CMDLINE_LONG_ARG="${_OPTS_GROFFER_LONG_ARG} \
${_OPTS_GROFF_LONG_ARG} ${_OPTS_MAN_LONG_ARG} ${_OPTS_X_LONG_ARG}";


########################################################################
# read-write variables (global to this file)
########################################################################

export _ADDOPTS_GROFF;		# Transp. options for groff (`eval').
export _ADDOPTS_POST;		# Transp. options postproc (`eval').
export _ADDOPTS_X;		# Transp. options X postproc (`eval').
export _DEFAULT_MODES;		# Set default modes.
export _DISPLAY_MODE;		# Display mode.
export _DISPLAY_PROG;		# Viewer program to be used for display.
export _DISPLAY_ARGS;		# X resources for the viewer program.
export _FILEARGS;		# Stores filespec parameters.
export _FUNC_STACK;		# Store debugging information.
export _REGISTERED_TITLE;	# Processed file names.
# _HAS_* from availability tests
export _HAS_COMPRESSION;	# `yes' if compression is available
export _HAS_OPTS_GNU;		# `yes' if GNU `getopt' is available
export _HAS_OPTS_POSIX;		# `yes' if POSIX `getopts' is available
# _MAN_* finally used configuration of man searching
export _MAN_ALL;		# search all man pages per filespec
export _MAN_ENABLE;		# enable search for man pages
export _MAN_EXT;		# extension for man pages
export _MAN_FORCE;		# force file parameter to be man pages
export _MAN_IS_SETUP;		# setup man variables only once
export _MAN_LANG;		# language for man pages
export _MAN_LANG_DONE;		# language dirs added to man path
export _MAN_PATH;		# search path for man pages
export _MAN_SEC;		# sections for man pages; sep. `:'
export _MAN_SEC_DONE;		# sections added to man path
export _MAN_SYS;		# system names for man pages; sep. `,'
export _MAN_SYS;		# system names added to man path
# _MANOPT_* as parsed from $MANOPT
export _MANOPT_ALL;		# $MANOPT --all
export _MANOPT_EXTENSION;	# $MANOPT --extension
export _MANOPT_LANG;		# $MANOPT --locale
export _MANOPT_PATH;		# $MANOPT --manpath
export _MANOPT_PAGER;		# $MANOPT --pager
export _MANOPT_SEC;		# $MANOPT --sections
export _MANOPT_SYS;		# $MANOPT --systems
# _OPT_* as parsed from groffer command line
export _OPT_ALL;		# display all suitable man pages.
export _OPT_APROPOS;		# call `apropos' program.
export _OPT_APROPOS_DATA;	# `apropos' for man sections 4, 5, 7
export _OPT_APROPOS_DEVEL;	# `apropos' for man sections 2, 3, 9
export _OPT_APROPOS_PROGS;	# `apropos' for man sections 1, 6, 8
export _OPT_BD;			# set border color in some modes.
export _OPT_BG;			# set background color in some modes.
export _OPT_BW;			# set border width in some modes.
export _OPT_DEBUG;		# print debugging information on stderr.
export _OPT_DEFAULT_MODES;	# `,'-list of modes when no mode given.
export _OPT_DEVICE;		# device option.
export _OPT_DISPLAY;		# set X display.
export _OPT_FG;			# set foreground color in some modes.
export _OPT_FN;			# set font in some modes.
export _OPT_GEOMETRY;		# set size and position of viewer in X.
export _OPT_ICONIC;		# -iconic option for X viewers.
export _OPT_LANG;		# set language for man pages
export _OPT_LOCATION;		# print processed file names to stderr
export _OPT_MODE;		# values: X, tty, Q, Z, ""
export _OPT_MANPATH;		# manual setting of path for man-pages
export _OPT_PAGER;		# specify paging program for tty mode
export _OPT_RESOLUTION;		# set X resolution in dpi
export _OPT_RV;			# reverse fore- and background colors.
export _OPT_SECTIONS;		# sections for man page search
export _OPT_SYSTEMS;		# man pages of different OS's
export _OPT_TITLE;		# title for gxditview window
export _OPT_TEXT_DEVICE;		# set device for tty mode.
export _OPT_V;			# groff option -V.
export _OPT_VIEWER_DVI;		# viewer program for dvi mode
export _OPT_VIEWER_PDF;		# viewer program for pdf mode
export _OPT_VIEWER_PS;		# viewer program for ps mode
export _OPT_VIEWER_HTML;	# viewer program for html mode
export _OPT_VIEWER_X;		# viewer program for x mode
export _OPT_WHATIS;		# print the one-liner man info
export _OPT_XRM;		# specify X resource.
export _OPT_Z;			# groff option -Z.
# _TMP_* temporary files
export _TMP_DIR;		# groff directory for temporary files
export _TMP_DIR_SUB;		# groffer directory for temporary files
export _TMP_CAT;		# stores concatenation of everything
export _TMP_STDIN;		# stores stdin, if any

# these variables are preset in section `Preset' after the rudim. test


########################################################################
#             Test of rudimentary shell functionality
########################################################################


########################################################################
# Test of `test'.
#
test "a" = "a" || exit 1;


########################################################################
# Test of `echo' and the `$()' construct.
#
echo -n '' >/dev/null || exit "${_ERROR}";
if test _"$(echo -n 'te' && echo -n '' && echo -n 'st')"_ != _test_; then
  exit "${_ERROR}";
fi;


########################################################################
# Test of function definitions.
#
_t_e_s_t_f_u_n_c_()
{
  return "${_OK}";
}

if _t_e_s_t_f_u_n_c_ 2>/dev/null; then
  :
else
  echo 'shell does not support function definitions.' >&2;
  exit "${_ERROR}";
fi;


########################################################################
# Preset and reset of read-write global variables
########################################################################


# For variables that can be reset by option `--default', see reset().

_FILEARGS='';

# _HAS_* from availability tests
_HAS_COMPRESSION='';
_HAS_OPTS_GNU='';
_HAS_OPTS_POSIX='';

# _TMP_* temporary files
_TMP_DIR='';
_TMP_DIR_SUB='';
_TMP_CAT='';
_TMP_STDIN='';


########################################################################
# reset ()
#
# Reset the variables that can be affected by options to their default.
#
reset()
{
  if test "$#" -ne 0; then
    error "reset() does not have arguments.";
  fi;

  _ADDOPTS_GROFF='';
  _ADDOPTS_POST='';
  _ADDOPTS_X='';
  _DISPLAY_ARGS='';
  _DISPLAY_MODE='';
  _DISPLAY_PROG='';
  _REGISTERED_TITLE='';

  # _MAN_* finally used configuration of man searching
  _MAN_ALL='no';
  _MAN_ENABLE='yes';		# do search for man-pages
  _MAN_EXT='';
  _MAN_FORCE='no';		# first local file, then search man page
  _MAN_IS_SETUP='no';
  _MAN_LANG='';
  _MAN_LANG_DONE='no';
  _MAN_PATH='';
  _MAN_SEC='';
  _MAN_SEC_DONE='no';
  _MAN_SYS='';
  _MAN_SYS_DONE='no';

  # _MANOPT_* as parsed from $MANOPT
  _MANOPT_ALL='no';
  _MANOPT_EXTENSION='';
  _MANOPT_LANG='';
  _MANOPT_PATH='';
  _MANOPT_PAGER='';
  _MANOPT_SEC='';
  _MANOPT_SYS='';

  # _OPT_* as parsed from groffer command line
  _OPT_ALL='no';
  _OPT_APROPOS='';
  _OPT_APROPOS_DATA='';
  _OPT_APROPOS_DEVEL='';
  _OPT_APROPOS_PROGS='';
  _OPT_BD='';
  _OPT_BG='';
  _OPT_BW='';
  _OPT_DEBUG='no';
  _OPT_DEFAULT_MODES='';
  _OPT_DEVICE='';
  _OPT_DISPLAY='';
  _OPT_FG='';
  _OPT_FN='';
  _OPT_GEOMETRY='';
  _OPT_ICONIC='no';
  _OPT_LANG='';
  _OPT_LOCATION='no';
  _OPT_MODE='';
  _OPT_MANPATH='';
  _OPT_PAGER='';
  _OPT_RESOLUTION='';
  _OPT_RV='no';
  _OPT_SECTIONS='';
  _OPT_SYSTEMS='';
  _OPT_TITLE='';
  _OPT_TEXT_DEVICE='';
  _OPT_V='no';
  _OPT_VIEWER_DVI='';
  _OPT_VIEWER_PDF='';
  _OPT_VIEWER_PS='';
  _OPT_VIEWER_HTML='';
  _OPT_VIEWER_X='';
  _OPT_WHATIS='no';
  _OPT_XRM='';
  _OPT_Z='no';

}

reset;


########################################################################
#          Functions for error handling and debugging
########################################################################


##############
# landmark (<text>)
#
# Print <text> to standard error as a debugging aid.
#
# Globals: $_DEBUG_LM
#
landmark()
{
  if test _"${_DEBUG_LM}"_ = _yes_; then
    echo ">>> $*" >&2;
  fi;
}

landmark "1: debugging functions";


##############
# clean_up ()
#
# Clean up at exit.
#
clean_up()
{
  if test -d "${_TMP_DIR}"; then
    rm -f "${_TMP_DIR}"/*;
    rmdir "${_TMP_DIR}";
  fi;
}


##############
# echo2 (<text>*)
#
# Output to stderr.
#
# Arguments : arbitrary text.
#
echo2()
{
  echo "$*" >&2;
}


##############
# echo2n (<text>*)
#
# Output to stderr.
#
# Arguments : arbitrary text.
#
echo2n()
{
  echo -n "$*" >&2;
}


#############
# diag (text>*)
#
# Output a diagnostic message to stderr
#
diag()
{
  echo2 '>>>>>'"$*";
}


#############
# error (<text>*)
#
# Print an error message to standard error; exit with an error condition
#
error()
{
  local i;
  local _code;
  _code="${_ERROR}";
  case "$#" in
    0) true; ;;
    1) echo2 'groffer error: '"$1"; ;;
    2)
      echo2 'groffer error: '"$1";
      _code="$2";
      ;;
    *) echo2 'groffer error: wrong number of arguments in error().'; ;;
  esac;
  if test _"${_DEBUG}"_ = _yes_; then
    func_stack_dump;
  fi;
  clean_up;
  kill "${_PROCESS_ID}" >/dev/null 2>&1;
  kill -9 "${_PROCESS_ID}" >/dev/null 2>&1;
  exit "${_code}";
}


#############
# abort (<text>*)
#
# Terminate program with error condition
#
abort()
{
  error "Program aborted.";
  exit 1;
}


#############
# func_check (<func_name> <rel_op> <nr_args> "$@")
#
# Check number of arguments and register to _FUNC_STACK.
#
# Arguments: >=3
#   <func_name>: name of the calling function.
#   <rel_op>:    a relational operator: = != < > <= >= 
#   <nr_args>:   number of arguments to be checked against <operator>
#   "$@":        the arguments of the calling function.
#
func_check()
{
  local _comp;
  local _fname;
  local _nargs;
  local _op;
  local _s;
  if test "$#" -lt 3; then
    error 'func_check() needs at least 3 arguments.';
  fi;
  _fname="$1";
  case "$3" in
    1)
      _nargs="$3";
      _s='';
      ;;
    0|[2-9])
      _nargs="$3";
      _s='s';
      ;;
    *)
      error "func_check(): third argument must be a digit.";
      ;;
  esac;
  case "$2" in
    '='|'-eq')
      _op='-eq';
      _comp='exactly';
      ;;
    '>='|'-ge')
      _op='-ge';
      _comp='at least';
      ;;
    '<='|'-le')
      _op='-le';
      _comp='at most';
      ;;
    '<'|'-lt')
      _op='-lt';
      _comp='less than';
      ;;
    '>'|'-gt')
      _op='-gt';
      _comp='more than';
      ;;
    '!='|'-ne')
      _op='-ne';
      _comp='not';
      ;;
    *) 
      error \
        'func_check(): second argument is not a relational operator.';
      ;;
  esac;
  shift 3;
  if test "$#" "${_op}" "${_nargs}"; then
    do_nothing;
  else
    error \
      "${_fname}"'() needs '"${_comp} ${_nargs}"' argument'"${_s}"'.';
  fi;
  if test _"${_DEBUG}"_ = _yes_; then
    func_push "${_fname} $*";
  fi;
}


#############
# func_pop ()
#
# Retrieve the top element from the stack.
#
# The stack elements are separated by `!'; the popped element is
# identical to the original element, except that all `!' characters
# were removed.
#
# Arguments: 1
#
func_pop()
{
  if test _"${_DEBUG}"_ = _yes_; then
    if test "$#" -ne 0; then
      error 'func_pop() does not have arguments.';
    fi;
    case "${_FUNC_STACK}" in
      '')
        error 'func_pop(): stack is empty.';
        ;;
      *!*)
        # split at first bang `!'.
        _FUNC_STACK="$(echo -n ${_FUNC_STACK} \
                       | sed -e 's/^[^!]*!//')";
        ;;
      *)
        _FUNC_STACK='';
        ;;
    esac;
  fi;
}


#############
# func_push (<element>)
#
# Store another element to stack.
#
# The stack elements are separated by `!'; if <element> contains a `!'
# it is removed first.
#
# Arguments: 1
#
func_push()
{
  local _element;
  if test _"${_DEBUG}"_ = _yes_; then
    if test "$#" -ne 1; then
      error 'func_push() needs 1 argument.';
    fi;
    case "$1" in
      *'!'*)
        # remove all bangs `!'.
        _element="$(echo -n "$1" | sed -e 's/!//g')";
        ;;
      *)
        _element="$1";
        ;;
    esac;
    if test _"${_FUNC_STACK}"_ = __; then
      _FUNC_STACK="${_element}";
    else
      _FUNC_STACK="${_element}!${_FUNC_STACK}";
    fi;
  fi;
}


#############
# func_stack_dump ()
#
# Print the content of the stack.  Ignore the arguments.
#
func_stack_dump()
{
  diag 'call stack:';
  case "${_FUNC_STACK}" in
    *!*)
      _rest="${_FUNC_STACK}";
      while test _"${_rest}"_ != __; do
        # get part before the first bang `!'.
        diag "$(echo -n "${_rest}" | sed -e 's/!.*$//')";
        # delete part before and including the first bang `!'.
        _rest="$(echo -n "${_rest}" | sed -e 's/^[^!]*!//')";
      done;
      ;;
    *)
      diag "${_FUNC_STACK}";
      ;;
  esac;
}


########################################################################
#                        System Test
########################################################################

landmark "2: system test";

# Test the availability of the system utilities used in this script.


########################################################################
# Test of `true'.
#
if true >/dev/null 2>&1; then
  true;
else
  true()
  {
    return "${_GOOD}";
  }

  false()
  {
    return "${_BAD}";
  }
fi;


########################################################################
# Test of `unset'.
#
_test='test';
if unset _test >/dev/null 2>&1 && test _"${_test}"_ = __; then
  true;
else
  unset()
  {
    for v in "$@"; do
      eval "$v"='';
    done;
  }
fi;
unset _test;

########################################################################
# Test of builtin `local'
#

_t_e_s_t_f_u_n_c_()
{
  local _test >/dev/null 2>&1 || return "${_BAD}";
}

if _t_e_s_t_f_u_n_c_; then
  :
else
  local()
  {
    if test _"$1"_ != __; then
      error "overriding global variable \`$1' with local value.";
    fi;
  }
fi;


########################################################################
# Test of global setting in functions
#
_global='outside';
_clobber='outside';

_t_e_s_t_f_u_n_c_()
{
  local _clobber;
  _global='inside';
  _clobber='inside';
}

_t_e_s_t_f_u_n_c_;
if test _"${_global}"_ != _inside_ || test _"${_clobber}"_ != _outside_;
then
  error "Cannot assign to global variables from within functions.";
fi;

unset _global;
unset _clobber;


########################################################################
# Test of function `sed'.
#

if test _"$(echo xTesTx \
           | sed -e 's/^.\([Tt]e*x*sTT*\).*$/\1/' \
           | sed -e '\|T|s|T|t|g')"_ != _test_;
then
  error 'Test of "sed" command failed.';
fi;


########################################################################
# Test of function `cat'.
#
if test _"$(echo test | cat)"_ != _test_; then
  error 'Test of "cat" command failed.';
fi;


########################################################################
# Test for compression.
#
if test _"$(echo 'test' | gzip -c -d -f - 2>/dev/null)"_ = _test_; then
  _HAS_COMPRESSION='yes';
  if echo 'test' | bzip2 -c 2>/dev/null | bzip2 -t 2>/dev/null \
     && test _"$(echo 'test' | bzip2 -c 2>/dev/null \
                            | bzip2 -d -c 2>/dev/null)"_ \
             = _test_; then
    _HAS_BZIP='yes';
  else
    _HAS_BZIP='no';
  fi;
else
  _HAS_COMPRESSION='no';
  _HAS_BZIP='no';
fi;


########################################################################
_t_e_s_t_f_u_n_c_()
{
  :
}


########################################################################
#                   Definition of normal Functions
########################################################################
landmark "3: functions";

########################################################################
# abort (<text>*)
#
# Unconditionally terminate the program with error code;
# useful for debugging.
#
# defined above


########################################################################
# apropos_run (<name>)
#
# 
apropos_run() {
  func_check apropos_run = 1 "$@";
  if apropos apropos >/dev/null 2>/dev/null; then
    apropos "$1";
  elif man --apropos man >/dev/null 2>/dev/null; then
    man --apropos "$1";
  elif man -k man >/dev/null 2>/dev/null; then
    man -k "$1";
  fi;
}


########################################################################
# base_name (<path>)
#
# Get the file name part of <path>, i.e. delete everything up to last
# `/' from the beginning of <path>.  Remove final slashes, too, to get a
# non-empty output.
#
# Arguments : 1
# Output    : the file name part (without slashes)
#
base_name()
{
  func_check base_name = 1 "$@";
  local f;
  f="$1";
  case "$f" in
    */)
      # delete all final slashes
      f="$(echo -n "$f" | sed -e '\|.*|s|//*$||')";
      ;;
  esac;
  case "$f" in
    /|'')
      eval "${return_bad}";
      ;;
    */*)
      # delete everything before and including the last slash `/'.
      echo -n "$f" | sed -e '\|.*|s|^.*//*\([^/]*\)$|\1|';
      ;;
    *)
      echo -n "$f";
      ;;
  esac;
  eval "${return_ok}";
}


########################################################################
# catz (<file>)
#
# Decompress if possible or just print <file> to standard output.
#
# gzip, bzip2, and .Z decompression is supported.
#
# Arguments: 1, a file name.
# Output: the content of <file>, possibly decompressed.
#
if test _"${_HAS_COMPRESSION}"_ = _yes_; then
  catz()
  {
    func_check catz = 1 "$@";
    case "$1" in
      '')
        error 'catz(): empty file name';
        ;;
      '-')
        error 'catz(): for standard input use save_stdin()';
        ;;
    esac;
    if obj _HAS_BZIP is_yes; then
      if bzip2 -t "$1" 2>/dev/null; then
        bzip2 -c -d "$1" 2>/dev/null;
        eval "${return_ok}";
      fi;
    fi;
    gzip -c -d -f "$1" 2>/dev/null;
    eval "${return_ok}";
  }
else
  catz()
  {
    func_check catz = 1 "$@";
    cat "$1";
    eval "${return_ok}";
  }
fi;


########################################################################
# clean_up ()
#
# Do the final cleaning up before exiting; used by the trap calls.
#
# defined above


########################################################################
# diag (<text>*)
#
# Print marked message to standard error; useful for debugging.
#
# defined above


########################################################################
landmark '4: dirname()*';
########################################################################

#######################################################################
# dirname_append (<dir> <name>)
#
# Append `name' to `dir' with clean handling of `/'.
#
# Arguments : 2
# Output    : the generated new directory name <dir>/<name>
#
dirname_append()
{
  func_check dirname_append = 2 "$@";
  local _res;
  if is_empty "$1"; then
    error "dir_append(): first argument is empty.";
  fi;
  if is_empty "$2"; then
    echo -n "$1";
  else
    dirname_chop "$1"/"$2";
  fi;
  eval "${return_ok}";
}


########################################################################
# dirname_chop (<name>)
#
# Remove unnecessary slashes from directory name.
#
# Argument: 1, a directory name.
# Output:   path without double, or trailing slashes.
#
dirname_chop()
{
  func_check dirname_chop = 1 "$@";
  local _arg;
  local _res;
  local _sep;
  # replace all multiple slashes by a single slash `/'.
  _res="$(echo -n "$1" | sed -e '\|.*|s|///*|/|g')";
  case "${_res}" in
    ?*/)
      # remove trailing slash '/';
      echo -n "${_res}" | sed -e '\|.*|s|/$||';
      ;;
    *) echo -n "${_res}"; ;;
  esac;
  eval "${return_ok}";
}


########################################################################
# do_filearg (<filearg>)
#
# Append the file, man-page, or standard input corresponding to the
# argument to the temporary file.  If this is compressed in the gzip
# or Z format it is decompressed.  A title element is generated.
#
# Argument either:
#   - name of an existing files.
#   - `-' to represent standard input (several times allowed).
#   - `man:name.(section)' the man-page for `name' in `section'.
#   - `man:name.section' the man-page for `name' in `section'.
#   - `man:name' the man-page for `name' in the lowest `section'.
#   - `name.section' the man-page for `name' in `section'.
#   - `name' the man-page for `name' in the lowest `section'.
# Globals :
#   $_TMP_STDIN, $_MAN_ENABLE, $_MAN_IS_SETUP, $_OPT_MAN
#
# Output  : none
# Return  : $_GOOD if found, ${_BAD} otherwise.
#
do_filearg()
{
  func_check do_filearg = 1 "$@";
  local _filespec;
  local i;
  _filespec="$1";
  # store sequence into positional parameters
  case "${_filespec}" in
    '')
       eval "${return_good}";
       ;;
    '-')
      register_file '-';
      eval "${return_good}";
      ;;
    */*)			# with directory part; so no man search
      set -- 'File';
      ;;
    *)
      if obj _MAN_ENABLE is_yes; then
        if obj _MAN_FORCE is_yes; then
          set -- 'Manpage' 'File';
        else
          set -- 'File' 'Manpage';
        fi;
      else
        set -- 'File';
      fi;
      ;;
  esac;
  for i in "$@"; do
    case "$i" in
      File)
        if test -f "${_filespec}"; then
          if test -r "${_filespec}"; then
            register_file "${_filespec}";
            eval "${return_good}";
          else
	    echo2 "could not read \`${_filespec}'";
            eval "${return_bad}";
          fi;
        else
          continue;
        fi;
        ;;
      Manpage)			# parse filespec as man page
        if obj _MAN_IS_SETUP is_not_yes; then
          man_setup;
        fi;
        if man_do_filespec "${_filespec}"; then
          eval "${return_good}";
        else
          continue;
	fi;
        ;;
    esac;
  done;
  eval "${return_bad}";
} # do_filearg()


########################################################################
# do_nothing ()
#
# Dummy function.
#
do_nothing()
{
  return "${_OK}";
}


########################################################################
# echo2 (<text>*)
#
# Print to standard error with final line break.
#
# defined above


########################################################################
# echo2n (<text>*)
#
# Print to standard error without final line break.
#
# defined above


########################################################################
# error (<text>*)
#
# Print error message and exit with error code.
#
# defined above


########################################################################
# func_check (<func_name> <rel_op> <nr_args> "$@")
#
# Check number of arguments and register to _FUNC_STACK.
#
# Arguments: >=3
#   <func_name>: name of the calling function.
#   <rel_op>:    a relational operator: = != < > <= >= 
#   <nr_args>:   number of arguments to be checked against <operator>
#   "$@":        the arguments of the calling function.
#
# defined above

#########################################################################
# func_pop ()
#
# Delete the top element from the function call stack.
#
# defined above


########################################################################
# func_push (<element>)
#
# Store another element to function call stack.
#
# defined above


########################################################################
# func_stack_dump ()
#
# Print the content of the stack.
#
# defined above


########################################################################
# get_first_essential (<arg>*)
#
# Retrieve first non-empty argument.
#
# Return  : `1' if all arguments are empty, `0' if found.
# Output  : the retrieved non-empty argument.
#
get_first_essential()
{
  func_check get_first_essential '>=' 0 "$@";
  local i;
  if is_equal "$#" 0; then
    eval "${return_ok}";
  fi;
  for i in "$@"; do
    if obj i is_not_empty; then
      echo -n "$i";
      eval "${return_ok}";
    fi;
  done;
  eval "${return_bad}";
}


########################################################################
landmark '5: is_*()';
########################################################################

########################################################################
# is_dir (<name>)
#
# Test whether `name' is a directory.
#
# Arguments : 1
# Return    : `0' if arg1 is a directory, `1' otherwise.
#
is_dir()
{
  func_check is_dir = 1 "$@";
  if test -d "$1" && test -r "$1"; then
    eval "${return_yes}";
  fi;
  eval "${return_no}";
}


########################################################################
# is_empty (<string>)
#
# Test whether `string' is empty.
#
# Arguments : <=1
# Return    : `0' if arg1 is empty or does not exist, `1' otherwise.
#
is_empty()
{
  func_check is_empty = 1 "$@";
  if test _"$1"_ = __; then
    eval "${return_yes}";
  fi;
  eval "${return_no}";
}


########################################################################
# is_equal (<string1> <string2>)
#
# Test whether `string1' is equal to <string2>.
#
# Arguments : 2
# Return    : `0' both arguments are equal strings, `1' otherwise.
#
is_equal()
{
  func_check is_equal = 2 "$@";
  if test _"$1"_ = _"$2"_; then
    eval "${return_yes}";
  fi;
  eval "${return_no}";
}


########################################################################
# is_file (<name>)
#
# Test whether `name' is a readable file.
#
# Arguments : 1
# Return    : `0' if arg1 is a readable file, `1' otherwise.
#
is_file()
{
  func_check is_file = 1 "$@";
  if test -f "$1" && test -r "$1"; then
    eval "${return_yes}";
  fi;
  eval "${return_no}";
}


########################################################################
# is_non_empty_file (<file_name>)
#
# Test whether `file_name' is a non-empty existing file.
#
# Arguments : <=1
# Return    :
#   `0' if arg1 is a non-empty existing file
#   `1' otherwise
#
is_non_empty_file()
{
  func_check is_empty = 1 "$@";
  if is_file "$1"; then
    if is_not_empty "$(cat "$1" | sed -e '/./q')"; then
      eval "${return_yes}";
    fi;
  fi;
  eval "${return_no}";
}


########################################################################
# is_not_dir (<name>)
#
# Test whether `name' is not a readable directory.
#
# Arguments : 1
# Return    : `0' if arg1 is a directory, `1' otherwise.
#
is_not_dir()
{
  func_check is_not_dir = 1 "$@";
  if is_dir "$1"; then
    eval "${return_no}";
  fi;
  eval "${return_yes}";
}


########################################################################
# is_not_empty (<string>)
#
# Test whether `string' is not empty.
#
# Arguments : <=1
# Return    : `0' if arg1 exists and is not empty, `1' otherwise.
#
is_not_empty()
{
  func_check is_not_empty = 1 "$@";
  if is_empty "$1"; then
    eval "${return_no}";
  fi;
  eval "${return_yes}";
}


########################################################################
# is_not_equal (<string1> <string2>)
#
# Test whether `string1' differs from `string2'.
#
# Arguments : 2
#
is_not_equal()
{
  func_check is_not_equal = 2 "$@";
  if is_equal "$1" "$2"; then
    eval "${return_no}";
  fi
  eval "${return_yes}";
}


########################################################################
# is_not_file (<filename>)
#
# Test whether `name' is a not readable file.
#
# Arguments : >=1 (empty allowed), more args are ignored
#
is_not_file()
{
  func_check is_not_file '>=' 1 "$@";
  if is_file "$1"; then
    eval "${return_no}";
  fi;
  eval "${return_yes}";
}


########################################################################
# is_not_prog (<name>)
#
# Verify that arg is a not program in $PATH.
#
# Arguments : >=1 (empty allowed)
#   more args are ignored, this allows to specify progs with arguments
#
is_not_prog()
{
  func_check is_not_prog '>=' 1 "$@";
  if where "$1" >/dev/null; then
    eval "${return_no}";
  fi;
  eval "${return_yes}";
}


########################################################################
# is_not_writable (<name>)
#
# Test whether `name' is a not a writable file or directory.
#
# Arguments : >=1 (empty allowed), more args are ignored
#
is_not_writable()
{
  func_check is_not_writable '>=' 1 "$@";
  if is_writable "$1"; then
    eval "${return_no}";
  fi;
  eval "${return_yes}";
}


########################################################################
# is_not_yes (<string>)
#
# Test whether `string' is not "yes".
#
# Arguments : 1
#
is_not_yes()
{
  func_check is_not_yes = 1 "$@";
  if is_yes "$1"; then
    eval "${return_no}";
  fi;
  eval "${return_yes}";
}


########################################################################
# is_prog (<name>)
#
# Determine whether arg is a program in $PATH
#
# Arguments : >=0 (empty allowed)
#   more args are ignored, this allows to specify progs with arguments
#
is_prog()
{
  func_check is_prog '>=' 0 "$@";
  case "$#" in
  0)
    eval "${return_no}";
    ;;
  *)
    if where "$1" >/dev/null; then
      eval "${return_yes}";
    fi;
    ;;
  esac
  eval "${return_no}";
}


########################################################################
# is_writable (<name>)
#
# Test whether `name' is a writable file or directory.
#
# Arguments : >=1 (empty allowed), more args are ignored
#
is_writable()
{
  func_check is_writable '>=' 1 "$@";
  if test -r "$1"; then
    if test -w "$1"; then
      eval "${return_yes}";
    fi;
  fi;
  eval "${return_no}";
}


########################################################################
# is_yes (<string>)
#
# Test whether `string' has value "yes".
#
# Arguments : <=1
# Return    : `0' if arg1 is `yes', `1' otherwise.
#
is_yes()
{
  func_check is_yes = 1 "$@";
  if is_equal "$1" 'yes'; then
    eval "${return_yes}";
  fi;
  eval "${return_no}";
}


########################################################################
# landmark ()
#
# Print debugging information on standard error if $_DEBUG_LM is `yes'.
#
# Globals: $_DEBUG_LM
#
# Defined in section `Debugging functions'.


########################################################################
# leave ()
#
# Clean exit without an error.
#
leave()
{
  clean_up;
  exit "${_OK}";
}


########################################################################
landmark '6: list_*()';
########################################################################
#
# `list' is an object class that represents an array or list.  Its
# data consists of space-separated single-quoted elements.  So a list
# has the form "'first' 'second' '...' 'last'".  See list_append() for
# more details on the list structure.  The array elements of `list'
# can be get by `set -- $list`.


########################################################################
# list_append (<list> <element>...)
#
# Arguments: >=2
#   <list>: a variable name for a list of single-quoted elements
#   <element>:  some sequence of characters.
# Output: none, but $<list> is set to
#   if <list> is empty:  "'<element>' '...'"
#   otherwise:           "$list '<element>' ..."
#
list_append()
{
  func_check list_append '>=' 2 "$@";
  local _element;
  local _list;
  local _name;
  _name="$1";
  eval _list='"${'$1'}"';
  shift;
  for s in "$@"; do
    case "$s" in
    *\'*)
      # escape each single quote by replacing each
      # "'" (squote) by "'\''" (squote bslash squote squote);
      # note that the backslash must be doubled in the following `sed'
      _element="$(echo -n "$s" | sed -e 's/'"${_SQUOTE}"'/&\\&&/g')";
      ;;
    '')  
      _element="";
      ;;
    *)
      _element="$s";
      ;;
    esac;
    if obj _list is_empty; then
      _list="'${_element}'";
    else
      _list="${_list} '${_element}'";
    fi;
  done;
  eval "${_name}"='"${_list}"';
  eval "${return_ok}";
}


########################################################################
# list_from_cmdline (<pre_name_of_opt_lists> [<cmdline_arg>...])
#
# Transform command line arguments into a normalized form.
#
# Options, option arguments, and file parameters are identified and
# output each as a single-quoted argument of its own.  Options and
# file parameters are separated by a '--' argument.
#
# Arguments: >=1
#   <pre_name>: common part of a set of 4 environment variable names:
#     $<pre_name>_SHORT_NA:  list of short options without an arg.
#     $<pre_name>_SHORT_ARG: list of short options that have an arg.
#     $<pre_name>_LONG_NA:   list of long options without an arg.
#     $<pre_name>_LONG_ARG:  list of long options that have an arg.
#   <cmdline_arg>...: the arguments from a command line, such as "$@",
#                     the content of a variable, or direct arguments.
#
# Output: ['-[-]opt' ['optarg']]... '--' ['filename']...
#
# Example:
#   list_from_cmdline PRE 'a b' 'c' '' 'long' -a f1 -bcarg --long=larg f2
# If $PRE_SHORT_NA, $PRE_SHORT_ARG, $PRE_LONG_NA, and $PRE_LONG_ARG are
# none-empty option lists, this will result in printing:
#     '-a' '-b' '-c' 'arg' '--long' 'larg' '--' 'f1' 'f2'
#
#   Use this function in the following way:
#     eval set -- "$(args_norm PRE_NAME "$@")";
#     while test "$1" != '--'; do
#       case "$1" in
#       ...
#       esac;
#       shift;
#     done;
#     shift;         #skip '--'
#     # all positional parameters ("$@") left are file name parameters.
#
list_from_cmdline()
{
  func_check list_from_cmdline '>=' 1 "$@";
  local _fparams;
  local _fn;
  local _short_a;
  local _short_n;
  local _long_a;
  local _long_n;
  local _result;
  _short_n="$(obj_data "$1"_SHORT_NA)";  # short options, no argument
  _short_a="$(obj_data "$1"_SHORT_ARG)"; # short options, with argument
  _long_n="$(obj_data "$1"_LONG_NA)";    # long options, no argument
  _long_a="$(obj_data "$1"_LONG_ARG)";   # long options, with argument
  if obj _short_n is_empty; then
    error 'list_from_cmdline(): no $'"$1"'_SHORT_NA options.';
  fi;
  if obj _short_a is_empty; then
    error 'list_from_cmdline(): no $'"$1"'_SHORT_ARG options.';
  fi;
  if obj _long_n is_empty; then
    error 'list_from_cmdline(): no $'"$1"'_LONG_NA options.';
  fi;
  if obj _long_a is_empty; then
    error 'list_from_cmdline(): no $'"$1"'_LONG_ARG options.';
  fi;
  shift;
  _fn='list_from_cmdline():';	 # for error messages
  if is_equal "$#" 0; then
    echo -n "'--'";
    eval "${return_ok}";
  fi;
  _fparams='';
  _result='';
  while test "$#" -ge 1; do
    _arg="$1";
    shift;
    case "$_arg" in
      --) break; ;;
      --?*)
        # delete leading '--';
        _opt="$(echo -n "${_arg}" | sed -e 's/^..//')";
        if list_has _long_n "${_opt}"; then
          # long option, no argument
          list_append _result "--${_opt}";
          continue;
        fi;
        # test on `--opt=arg'
        if string_contains "${_opt}" '='; then
          # extract option by deleting from the first '=' to the end
          _lopt="$(echo -n "${_opt}" | sed -e 's/=.*$//')";
          if list_has _long_a "${_lopt}"; then
            # get the option argument by deleting up to first `='
            _optarg="$(echo -n "${_opt}" | sed -e 's/^[^=]*=//')";
            list_append _result "--${_lopt}" "${_optarg}";
            continue;
          fi;
        fi;
        if list_has _long_a "${_opt}"; then
          # long option with argument
          if test "$#" -le 0; then
            error "${_fn} no argument for option --${_opt}."
          fi;
          list_append _result "--${_opt}" "$1";
          shift;
          continue;
        fi;
        error "${_fn} --${_opt} is not an option."
        ;;
      -?*)			# short option (cluster)
        # delete leading `-';
        _rest="$(echo -n "${_arg}" | sed -e 's/^-//')";
        while obj _rest is_not_empty; do
          # get next short option from cluster (first char of $_rest)
          _optchar="$(echo -n "${_rest}" | sed -e 's/^\(.\).*$/\1/')";
          # remove first character from ${_rest};
          _rest="$(echo -n "${_rest}" | sed -e 's/^.//')";
          if list_has _short_n "${_optchar}"; then
            list_append _result "-${_optchar}";
            continue;
          elif list_has _short_a "${_optchar}"; then
            if obj _rest is_empty; then
              if test "$#" -ge 1; then
                list_append _result "-${_optchar}" "$1";
                shift;
                continue;
              else
                error \
                  "${_fn}"' no argument for option -'"${_optchar}."
              fi;
            else		# rest is the argument
              list_append _result "-${_optchar}" "${_rest}";
              _rest='';
              continue;
            fi;
          else
            error "${_fn} unknown option -${_optchar}."
          fi;
        done;
        ;;
      *)
	# Here, $_arg is not an option, so a file parameter.
        list_append _fparams "${_arg}";

        # Ignore the strange option handling of $POSIXLY_CORRECT to
        # end option parsing after the first file name argument.  To
        # reuse it, do a `break' here if $POSIXLY_CORRECT is
        # non-empty.
        ;;
    esac;
  done;
  list_append _result '--';
  if obj _fparams is_not_empty; then
    _result="${_result} ${_fparams}";
  fi;
  if test "$#" -gt 0; then
    list_append _result "$@";
  fi;
  echo -n "$_result";
  eval "${return_ok}";
} # list_from_cmdline()


########################################################################
# list_from_split (<string> <separator>)
#
# In <string>, escape all white space characters and replace each
# <separator> by space.
#
# Arguments: 2: a <string> that is to be split into parts divided by
#               <separator>
# Output:    the resulting list string
#
list_from_split()
{
  func_check list_from_split = 2 "$@";
  local _s;

  # precede each space or tab by a backslash `\' (doubled for `sed')
  _s="$(echo -n "$1" | sed -e 's/\(['"${_SPACE}${_TAB}"']\)/\\\1/g')";

  # replace split character of string by the list separator ` ' (space).
  case "$2" in
    /)				# cannot use normal `sed' separator
      echo -n "${_s}" | sed -e '\|.*|s|'"$2"'| |g';
      ;;
    ?)				# use normal `sed' separator
      echo -n "${_s}" | sed -e 's/'"$2"'/ /g';
      ;;
    ??*)
      error 'list_from_split(): separator must be a single character.';
      ;;
  esac;
  eval "${return_ok}";
}


########################################################################
# list_get (<list>)
#
# Check whether <list> is a space-separated list of '-quoted elements.
#
# If the test fails an error is raised.
# If the test succeeds the argument is echoed.
#
# Testing criteria:
#   A list has the form "'first' 'second' '...' 'last'".  So it has a
#   leading and a final quote and the elements are separated by "' '"
#   constructs.  If these are all removed there should not be any
#   unescaped single-quotes left.  Watch out for escaped single
#   quotes; they have the form '\'' (sq bs sq sq).

# Arguments: 1
# Output: the argument <list> unchanged, if the check succeeded.
#
list_get()
{
  func_check list_get = 1 "$@";
  local _list;
  eval _list='"${'$1'}"';
  # remove leading and final space characters
  _list="$(echo -n "${_list}" | \
           sed -e 's/^['"${_SPACE}${_TAB}"']*//' | \
           sed -e 's/['"${_SPACE}${_TAB}"']*$//')";
  case "${_list}" in
  '')
    eval "${return_ok}";
    ;;
  \'*\')
    echo -n "${_list}";
    eval "${return_ok}";
    ;;
  *)
    error "list_get(): bad list: $1"
    ;;
  esac;
  eval "${return_ok}";
}


########################################################################
# list_has (<var_name> <element>)
#
# Arguments: 2
#   <var_name>: a variable name for a list of single-quoted elements
#   <element>:  some sequence of characters.
# Output:
#   if <list> is empty:  "'<element>' '...'"
#   otherwise:           "list '<element>' ..."
#
list_has()
{
  func_check list_has = 2 "$@";
  eval _list='"${'$1'}"';
  if obj _list is_empty; then
    eval "${return_no}";
  fi;
  _element="$2";
  case "$2" in
    \'*\')  _element="$2"; ;;
    *)      _element="'$2'"; ;;
  esac;
  if string_contains "${_list}" "${_element}"; then
    eval "${return_yes}";
  else
    eval "${return_no}";
  fi;
  eval "${return_ok}";
}


########################################################################
# list_has_not (<list> <element>)
#
# Arguments: 2
#   <list>:    a space-separated list of single-quoted elements.
#   <element>: some sequence of characters.
# Output:
#   if <list> is empty:  "'<element>' '...'"
#   otherwise:           "<list> '<element>' ..."
#
list_has_not()
{
  func_check list_has_not = 2 "$@";
  eval _list='"${'$1'}"';
  if obj _list is_empty; then
    eval "${return_yes}";
  fi;
  _element="$2";
  case "$2" in
    \'*\')  _element="$2"; ;;
    *)      _element="'$2'"; ;;
  esac;
  if string_contains "${_list}" "${_element}"; then
    eval "${return_no}";
  else
    eval "${return_yes}";
  fi;
  eval "${return_ok}";
}


########################################################################
landmark '7: man_*()';
########################################################################

########################################################################
# man_do_filespec (<filespec>)
#
# Print suitable man page(s) for filespec to $_TMP_CAT.
#
# Arguments : 2
#   <filespec>: argument of the form `man:name.section', `man:name',
#               `man:name(section)', `name.section', `name'.
#
# Globals   : $_OPT_ALL
#
# Output    : none.
# Return    : `0' if man page was found, `1' else.
#
# Only called from do_fileargs(), checks on $MANPATH and
# $_MAN_ENABLE are assumed.
#
man_do_filespec()
{
  func_check man_do_filespec = 1 "$@";
  local _got_one;
  local _name;
  local _prevsec;
  local _res;
  local _section;
  local _spec;
  local _string;
  local s;
  if obj _MAN_PATH is_empty; then
    eval "${return_bad}";
  fi;
  if is_empty "$1"; then
    eval "${return_bad}";
  fi;
  _spec="$1";
  _name='';
  _section='';
  case "${_spec}" in
    */*)			# not a man spec when it contains '/'
      eval "${return_bad}";
      ;;
    man:?*\(?*\))		# man:name(section)
      _name="$(echo -n "${_spec}" \
               | sed -e 's/^man:\(..*\)(\(..*\))$/\1/')";
      _section="$(echo -n "${_spec}" \
               | sed -e 's/^man:\(..*\)(\(..*\))$/\2/')";
      ;;
    man:?*.[0-9on])			# man:name.section
      _name="$(echo -n "${_spec}" \
               | sed -e 's/^man:\(..*\)\..$/\1/')";
      _section="$(echo -n "${_spec}" \
               | sed -e 's/^.*\(.\)$/\1/')";
      ;;
    man:?*)			# man:name
      _name="$(echo -n "${_spec}" | sed -e 's/^man://')";
      ;;
    ?*\(?*\))			# name(section)
      _name="$(echo -n "${_spec}" \
               | sed -e 's/^\(..*\)(\(..*\))$/\1/')";
      _section="$(echo -n "${_spec}" \
               | sed -e 's/^\(..*\)(\(..*\))$/\2/')";
      ;;
    ?*.[0-9on])			# name.section
      _name="$(echo -n "${_spec}" \
               | sed -e 's/^\(..*\)\..$/\1/')";
      _section="$(echo -n "${_spec}" \
               | sed -e 's/^.*\(.\)$/\1/')";
      ;;
    ?*)
      _name="${_filespec}";
      ;;
  esac;
  if obj _name is_empty; then
    eval "${return_bad}";
  fi;
  _got_one='no';
  if obj _section is_empty; then
    eval set -- "${_MAN_AUTO_SEC}";
    for s in "$@"; do
      if man_search_section "${_name}" "$s"; then # found
        if obj _MAN_ALL is_yes; then
          _got_one='yes';
        else
          eval "${return_good}";
        fi;
      fi;
    done;
  else
    if man_search_section "${_name}" "${_section}"; then
      eval "${return_good}";
    else
      eval "${return_bad}";
    fi;
  fi;
  if obj _MAN_ALL is_yes && is_yes "${_got_one}"; then
    eval "${return_good}";
  fi;
  eval "${return_bad}";
} # man_do_filespec()


########################################################################
# man_register_file (<file> <name> [<section>])
#
# Write a found man page file and register the title element.
#
# Arguments: 1, 2, or 3; maybe empty
# Output: none
#
man_register_file()
{
  func_check man_register_file '>=' 2 "$@";
  case "$#" in
    2|3) do_nothing; ;;
    *)
      error "man_register_file() expects 2 or 3 arguments.";
      ;;
  esac;
  if is_empty "$1"; then
    error 'man_register_file(): file name is empty';
  fi;
  to_tmp "$1";
  case "$#" in
    2)
       register_title "man:$2";
       eval "${return_ok}";
       ;;
    3)
       register_title "$2.$3";
       eval "${return_ok}";
       ;;
  esac;
  eval "${return_ok}";
}


########################################################################
# man_search_section (<name> <section>)
#
# Retrieve man pages.
#
# Arguments : 2
# Globals   : $_MAN_PATH, $_MAN_EXT
# Return    : 0 if found, 1 otherwise
#
man_search_section()
{
  func_check man_search_section = 2 "$@";
  local _dir;
  local _ext;
  local _got_one;
  local _name;
  local _prefix
  local _section;
  local d;
  local f;
  if obj _MAN_PATH is_empty; then
    eval "${return_bad}";
  fi;
  if is_empty "$1"; then
    eval "${return_bad}";
  fi;
  if is_empty "$2"; then
    eval "${return_bad}";
  fi;
  _name="$1";
  _section="$2";
  eval set -- "$(path_split "${_MAN_PATH}")";
  _got_one='no';
  if obj _MAN_EXT is_empty; then
    for d in "$@"; do
      _dir="$(dirname_append "$d" "man${_section}")";
      if obj _dir is_dir; then
        _prefix="$(dirname_append "${_dir}" "${_name}.${_section}")";
        for f in $(echo -n ${_prefix}*); do
          if obj f is_file; then
            if is_yes "${_got_one}"; then
              register_file "$f";
            elif obj _MAN_ALL is_yes; then
              man_register_file "$f" "${_name}";
            else
              man_register_file "$f" "${_name}" "${_section}";
              eval "${return_good}";
            fi;
            _got_one='yes';
          fi;
        done;
      fi;
    done;
  else
    _ext="${_MAN_EXT}";
    # check for directory name having trailing extension
    for d in "$@"; do
      _dir="$(dirname_append $d man${_section}${_ext})";
      if obj _dir is_dir; then
        _prefix="$(dirname_append "${_dir}" "${_name}.${_section}")";
        for f in ${_prefix}*; do
          if obj f is_file; then
            if is_yes "${_got_one}"; then
              register_file "$f";
            elif obj _MAN_ALL is_yes; then
              man_register_file "$f" "${_name}";
            else
              man_register_file "$f" "${_name}" "${_section}";
              eval "${return_good}";
            fi;
            _got_one='yes';
          fi;
        done;
      fi;
    done;
    # check for files with extension in directories without extension
    for d in "$@"; do
      _dir="$(dirname_append "$d" "man${_section}")";
      if obj _dir is_dir; then
        _prefix="$(dirname_append "${_dir}" \
                                  "${_name}.${_section}${_ext}")";
        for f in ${_prefix}*; do
          if obj f is_file; then
            if is_yes "${_got_one}"; then
              register_file "$f";
            elif obj _MAN_ALL is_yes; then
              man_register_file "$f" "${_name}";
            else
              man_register_file "$f" "${_name}" "${_section}";
              eval "${return_good}";
            fi;
            _got_one='yes';
          fi;
        done;
      fi;
    done;
  fi;
  if obj _MAN_ALL is_yes && is_yes "${_got_one}"; then
    eval "${return_good}";
  fi;
  eval "${return_bad}";
} # man_search_section()


########################################################################
# man_setup ()
#
# Setup the variables $_MAN_* needed for man page searching.
#
# Globals:
#   in:     $_OPT_*, $_MANOPT_*, $LANG, $LC_MESSAGES, $LC_ALL,
#           $MANPATH, $MANROFFSEQ, $MANSEC, $PAGER, $SYSTEM, $MANOPT.
#   out:    $_MAN_PATH, $_MAN_LANG, $_MAN_SYS, $_MAN_LANG, $_MAN_LANG2,
#           $_MAN_SEC, $_MAN_ALL
#   in/out: $_MAN_ENABLE
#
# The precedence for the variables related to `man' is that of GNU
# `man', i.e.
#
# $LANG; overridden by
# $LC_MESSAGES; overridden by
# $LC_ALL; this has the same precedence as
# $MANPATH, $MANROFFSEQ, $MANSEC, $PAGER, $SYSTEM; overridden by
# $MANOPT; overridden by
# the groffer command line options.
#
man_setup()
{
  func_check main_man_setup = 0 "$@";
  local _lang;

  if obj _MAN_IS_SETUP is_yes; then
    eval "${return_ok}";
  fi;
  _MAN_IS_SETUP='yes';

  if obj _MAN_ENABLE is_not_yes; then
    eval "${return_ok}";
  fi;

  # determine basic path for man pages
  _MAN_PATH="$(get_first_essential \
               "${_OPT_MANPATH}" "${_MANOPT_PATH}" "${MANPATH}")";
  if obj _MAN_PATH is_empty; then
    manpath_set_from_path;
  else
    _MAN_PATH="$(path_clean "${_MAN_PATH}")";
  fi;
  if obj _MAN_PATH is_empty; then
    if is_prog 'manpath'; then
      _MAN_PATH="$(manpath 2>/dev/null)"; # not always available
    fi;
  fi;
  if obj _MAN_PATH is_empty; then
    _MAN_ENABLE="no";
    eval "${return_ok}";
  fi;

  _MAN_ALL="$(get_first_essential "${_OPT_ALL}" "${_MANOPT_ALL}")";
  if obj _MAN_ALL is_empty; then
    _MAN_ALL='no';
  fi;

  _MAN_SYS="$(get_first_essential \
              "${_OPT_SYSTEMS}" "${_MANOPT_SYS}" "${SYSTEM}")";
  _lang="$(get_first_essential \
           "${_OPT_LANG}" "${LC_ALL}" "${LC_MESSAGES}" "${LANG}")";
  case "${_lang}" in
    C|POSIX)
      _MAN_LANG="";
      _MAN_LANG2="";
      ;;
    ?)
      _MAN_LANG="${_lang}";
      _MAN_LANG2="";
      ;;
    *)
      _MAN_LANG="${_lang}";
      # get first two characters of $_lang
      _MAN_LANG2="$(echo -n "${_lang}" | sed -e 's/^\(..\).*$/\1/')";
      ;;
  esac;
  # from now on, use only $_LANG, forget about $_OPT_LANG, $LC_*.

  manpath_add_lang_sys;		# this is very slow

  _MAN_SEC="$(get_first_essential \
              "${_OPT_SECT}" "${_MANOPT_SEC}" "${MANSEC}")";
  if obj _MAN_PATH is_empty; then
    _MAN_ENABLE="no";
    eval "${return_ok}";
  fi;

  _MAN_EXT="$(get_first_essential \
              "${_OPT_EXTENSION}" "${_MANOPT_EXTENSION}")";
  eval "${return_ok}";
} # man_setup()


########################################################################
landmark '8: manpath_*()';
########################################################################

########################################################################
# manpath_add_lang_sys ()
#
# Add language and operating system specific directories to man path.
#
# Arguments : 0
# Output    : none
# Globals:
#   in:     $_MAN_SYS: has the form `os1,os2,...', a comma separated
#             list of names of operating systems.
#           $_MAN_LANG and $_MAN_LANG2: each a single name
#   in/out: $_MAN_PATH: has the form `dir1:dir2:...', a colon
#             separated list of directories.
#
manpath_add_lang_sys()
{
  func_check manpath_add_lang_sys = 0 "$@";
  local p;
  local _mp;
  if obj _MAN_PATH is_empty; then
    eval "${return_ok}";
  fi;
  # twice test both sys and lang
  eval set -- "$(path_split "${_MAN_PATH}")";
  _mp='';
  for p in "$@"; do		# loop on man path directories
    _mp="$(_manpath_add_lang_sys_single "${_mp}" "$p")";
  done;
  eval set -- "$(path_split "${_mp}")";
  for p in "$@"; do		# loop on man path directories
    _mp="$(_manpath_add_lang_sys_single "${_mp}" "$p")";
  done;
  _MAN_PATH="$(path_chop "${_mp}")";
  eval "${return_ok}";
}


_manpath_add_lang_sys_single()
{
  # To the directory in $1 append existing sys/lang subdirectories
  # Function is necessary to split the OS list.
  #
  # globals: in: $_MAN_SYS, $_MAN_LANG, $_MAN_LANG2
  # argument: 2: `man_path' and `dir'
  # output: colon-separated path of the retrieved subdirectories
  #
  func_check _manpath_add_lang_sys_single = 2 "$@";
  local d;
  _res="$1";
  _parent="$2";
  eval set -- "$(list_from_split "${_MAN_SYS}" ',')";
  for d in "$@" "${_MAN_LANG}" "${_MAN_LANG2}"; do
    _dir="$(dirname_append "${_parent}" "$d")";
    if obj _res path_not_contains "${_dir}" && obj _dir is_dir; then
      _res="${_res}:${_dir}";
    fi;
  done;
  if path_not_contains "${_res}" "${_parent}"; then
    _res="${_res}:${_parent}";
  fi;
  path_chop "${_res}";
}

# end manpath_add_lang_sys ()


########################################################################
# manpath_set_from_path ()
#
# Determine basic search path for man pages from $PATH.
#
# Return:    `0' if a valid man path was retrieved.
# Output:    none
# Globals:
#   in:  $PATH
#   out: $_MAN_PATH
#
manpath_set_from_path()
{
  func_check manpath_set_from_path = 0 "$@";
  local _base;
  local _mandir;
  local _manpath;
  local d;
  local e;
  _manpath='';

  # get a basic man path from $PATH
  if obj PATH is_not_empty; then
    eval set -- "$(path_split "${PATH}")";
    for d in "$@"; do
      # delete the final `/bin' part
      _base="$(echo -n "$d" | sed -e '\|.*|s|//*bin/*$||')";
      for e in /share/man /man; do
        _mandir="${_base}$e";
        if test -d "${_mandir}" && test -r "${_mandir}"; then
        _manpath="${_manpath}:${_mandir}";
        fi;
      done;
    done;
  fi;

  # append some default directories
  for d in /usr/local/share/man /usr/local/man \
            /usr/share/man /usr/man \
            /usr/X11R6/man /usr/openwin/man \
            /opt/share/man /opt/man \
            /opt/gnome/man /opt/kde/man; do
    if obj _manpath path_not_contains "$d" && obj d is_dir; then
      _manpath="${_manpath}:$d";
    fi;
  done;

  _MAN_PATH="${_manpath}";
  eval "${return_ok}";
} # manpath_set_from_path()


########################################################################
landmark '9: obj_*()';
########################################################################

########################################################################
# obj (<object> <call_name> <arg>...)
#
# This works like a method (object function) call for an object.
# Run "<call_name> $<object> <arg> ...".
#
# The first argument represents an object whose data is given as first
# argument to <call_name>().
#
# Argument: >=2
#           <object>: variable name
#           <call_name>: a program or function name
#
obj()
{
  func_check obj '>=' 2 "$@";
  local func;
  local var;
  if is_empty "$2"; then
    error "obj(): function name is empty."
  else
    func="$2";
  fi;
  eval arg1='"${'$1'}"';
  shift;
  shift;
  eval "${func}"' "${arg1}" "$@"';
}


########################################################################
# obj_data (<object>)
#
# Print the data of <object>, i.e. the content of $<object>.
# For possible later extensions.
#
# Arguments: 1
#            <object>: a variable name
# Output:    the data of <object>
#
obj_data()
{
  func_check obj '=' 1 "$@";
  if is_empty "$1"; then
    error "obj_data(): object name is empty."
  fi;
  eval echo -n '"${'$1'}"';
}


########################################################################
# obj_from_output (<object> <call_name> <arg>...)
#
# Run '$<object>="$(<call_name> <arg>...)"' to set the result of a
# function call to a global variable.
#
# Arguments: >=2
#            <object>: a variable name
#            <call_name>: the name of a function or program
#            <arg>: optional argument to <call_name>
# Output:    none
#
obj_from_output()
{
  func_check obj_from_output '>=' 2 "$@";
  local result_name;
  if is_empty "$1"; then
    error "res(): variable name is empty.";
  elif is_empty "$2"; then
    error "res(): function name is empty."
  else
    result_name="$1";
  fi;
  shift;
  eval "${result_name}"'="$('"$@"')"';
}


########################################################################
# obj_set (<object> <data>)
#
# Set the data of <object>, i.e. call "$<object>=<data>".
#
# Arguments: 2
#            <object>: a variable name
#            <data>: a string
# Output::   none
#
obj_set()
{
  func_check obj_set '=' 2 "$@";
  if is_empty "$1"; then
    error "obj_set(): object name is empty."
  fi;
  eval "$1"='"$2"';
}


########################################################################
# path_chop (<path>)
#
# Remove unnecessary colons from path.
#
# Argument: 1, a colon separated path.
# Output:   path without leading, double, or trailing colons.
#
path_chop()
{
  func_check path_chop = 1 "$@";
  local _res;

  # replace multiple colons by a single colon `:'
  # remove leading and trailing colons
  echo -n "$1" | sed -e 's/:::*/:/g' |
                 sed -e 's/^:*//' |
                 sed -e 's/:*$//';
  eval "${return_ok}";
}


########################################################################
# path_clean (<path>)
#
# Remove non-existing directories from a colon-separated list.
#
# Argument: 1, a colon separated path.
# Output:   colon-separated list of existing directories.
#
path_clean()
{
  func_check path_clean = 1 "$@";
  local _arg;
  local _dir;
  local _res;
  local i;
  if is_not_equal "$#" 1; then
    error 'path_clean() needs 1 argument.';
  fi;
  _arg="$1";
  eval set -- "$(path_split "${_arg}")";
  _res="";
  for i in "$@"; do
    if obj i is_not_empty \
       && obj _res path_not_contains "$i" \
       && obj i is_dir;
    then
      case "$i" in
        ?*/) _res="${_res}$(dirname_chop "$i")"; ;;
        *)  _res="${_res}:$i";
      esac;
    fi;
  done;
  if path_chop "${_res}"; then
    eval "${return_ok}";
  else
    eval "${return_badk}";
  fi;
}


########################################################################
# path_contains (<path> <dir>)
#-
# Test whether `dir' is contained in `path', a list separated by `:'.
#
# Arguments : 2 arguments.
# Return    : `0' if arg2 is substring of arg1, `1' otherwise.
#
path_contains()
{
  func_check path_contains = 2 "$@";
  case ":$1:" in
    *":$2:"*)
      eval "${return_yes}";
      ;;
    *)
      eval "${return_no}";
      ;;
  esac;
  eval "${return_ok}";
}


########################################################################
# path_not_contains (<path> <dir>)
#-
# Test whether `dir' is not contained in colon separated `path'.
#
# Arguments : 2 arguments.
#
path_not_contains()
{
  func_check path_not_contains = 2 "$@";
  if path_contains "$1" "$2"; then
    eval "${return_no}";
  else
    eval "${return_yes}";
  fi;
  eval "${return_ok}";
}


########################################################################
# path_split (<path>)
#
# In `path' escape white space and replace each colon by a space.
#
# Arguments: 1: a colon-separated path
# Output:    the resulting list, process with `eval set --'
#
path_split()
{
  func_check path_split = 1 "$@";
  list_from_split "$1" ':';
  eval "${return_ok}";
}


########################################################################
landmark '10: register_*()';
########################################################################

########################################################################
# register_file (<filename>)
#
# Write a found file and register the title element.
#
# Arguments: 1: a file name
# Output: none
#
register_file()
{
  func_check register_file = 1 "$@";
  if is_empty "$1"; then
    error 'register_file(): file name is empty';
  fi;
  if is_equal "$1" '-'; then
    to_tmp "${_TMP_STDIN}";
    register_title '-';
  else
    to_tmp "$1";
    register_title "$(base_name "$1")";
  fi;
  eval "${return_ok}";
}


########################################################################
# register_title (<filespec>)
#
# Create title element from <filespec> and append to $_REGISTERED_TITLE
#
# Globals: $_REGISTERED_TITLE (rw)
#
register_title()
{
  func_check register_title = 1 "$@";
  local _title;
  if is_empty "$1"; then
    eval "${return_ok}";
  fi;
  _title="$(base_name "$1")";	# remove directory part
  
  # remove extension `.gz'
  _title="$(echo -n "${_title}" | sed -e 's/\.gz$//')";
  # remove extension `.Z'
  _title="$(echo -n "${_title}" | sed -e 's/\.Z$//')";

  if obj _title is_empty; then
    eval "${return_ok}";
  fi;
  _REGISTERED_TITLE="${_REGISTERED_TITLE} ${_title}";
  eval "${return_ok}";
}


########################################################################
# reset ()
#
# Reset the variables that can be affected by options to their default.
#
#
# Defined in section `Preset' after the rudimentary shell tests.


########################################################################
# save_stdin ()
#
# Store standard input to temporary file (with decompression).
#
if obj _HAS_COMPRESSION is_yes; then
  save_stdin()
  {
    local _f;
    func_check save_stdin = 0 "$@";
     _f="${_TMP_DIR}"/INPUT;
    cat >"${_f}";
    catz "${_f}" >"${_TMP_STDIN}";
    rm -f "${_f}";
    eval "${return_ok}";
  }
else
  save_stdin()
  {
    func_check save_stdin = 0 "$@";
    cat >"${_TMP_STDIN}";
    eval "${return_ok}";
  }
fi;


########################################################################
landmark '11: stack_*()';
########################################################################

########################################################################
# string_contains (<string> <part>)
#
# Test whether `part' is contained in `string'.
#
# Arguments : 2 text arguments.
# Return    : `0' if arg2 is substring of arg1, `1' otherwise.
#
string_contains()
{
  func_check string_contains = 2 "$@";
  case "$1" in
    *"$2"*)
      eval "${return_yes}";
      ;;
    *)
      eval "${return_no}";
      ;;
  esac;
  eval "${return_ok}";
}


########################################################################
# string_not_contains (<string> <part>)
#
# Test whether `part' is not substring of `string'.
#
# Arguments : 2 text arguments.
# Return    : `0' if arg2 is substring of arg1, `1' otherwise.
#
string_not_contains()
{
  func_check string_not_contains = 2 "$@";
  if string_contains "$1" "$2"; then
    eval "${return_no}";
  else
    eval "${return_yes}";
  fi;
  eval "${return_ok}";
}


########################################################################
landmark '12: tmp_*()';
########################################################################

########################################################################
# tmp_cat ()
#
# output the temporary cat file (the concatenation of all input)
#
tmp_cat()
{
  cat "${_TMP_CAT}";
}


########################################################################
# tmp_create (<suffix>?)
#
# create temporary file
#
# It's safe to use the shell process ID together with a suffix to
# have multiple temporary files.
#
# Output : name of created file
#
tmp_create()
{
  func_check tmp_create '<=' 1 "$@";
  local _tmp;
  # the output file does not have `,' as first character
  _tmp="${_TMP_DIR}/,$1";
  echo -n >"${_tmp}";
  echo -n "${_tmp}";		# output file name
  eval "${return_ok}";
}


########################################################################
# to_tmp (<filename>)
#
# print file (decompressed) to the temporary cat file
#
to_tmp()
{
  func_check to_tmp = 1 "$@";
  if is_file "$1"; then
    if obj _OPT_LOCATION is_yes; then
      echo2 "$1";
    fi;
    if obj _OPT_WHATIS is_yes; then
      what_is "$1" >>"${_TMP_CAT}";
    else
      catz "$1" >>"${_TMP_CAT}";
    fi;
  else
    error "to_tmp(): could not read file \`$1'.";
  fi;
  eval "${return_ok}";
}


########################################################################
# trap_clean ()
#
# disable trap on all exit codes ($_ALL_EXIT)
#
# Arguments: 0
# Globals:   $_ALL_EXIT
#
trap_clean()
{
  func_check trap_clean = 0 "$@";
  local i;
  for i in ${_ALL_EXIT}; do
    trap "" "$i" 2>/dev/null || true;
  done;
  eval "${return_ok}";
}


########################################################################
# trap_set (<functionname>)
#
# call function on all exit codes ($_ALL_EXIT)
#
# Arguments: 1 (name of a shell function)
# Globals:   $_ALL_EXIT
#
trap_set()
{
  func_check trap_set = 1 "$@";
  local i;
  for i in ${_ALL_EXIT}; do
    trap "$1" "$i" 2>/dev/null || true;
  done;
  eval "${return_ok}";
}


########################################################################
# usage ()
#
# print usage information to stderr; for groffer option --help.
#
usage()
{
  func_check usage = 0 "$@";
  echo;
  version;
  echo 'Usage: '"${_PROGRAM_NAME}"' [option]... [filespec]...';
  cat <<EOF

Display roff files, standard input, and/or Unix manual pages with a X
Window viewer or in several text modes.  All input is decompressed
on-the-fly with all formats that gzip can handle.

"filespec" is one of
  "filename"       name of a readable file
  "-"              for standard input
  "man:name.n"     man page "name" in section "n"
  "man:name"       man page "name" in first section found
  "name.n"         man page "name" in section "n"
  "name"           man page "name" in first section found
and some more (see groffer(1) for details).

-h --help        print this usage message.
-Q --source      output as roff source.
-T --device=name pass to groff using output device "name".
-v --version     print version information.
-V               display the groff execution pipe instead of formatting.
-X --X --x       display with "gxditview" using groff -X.
-Z --ditroff --intermediate-output
                 generate groff intermediate output without 
                 post-processing and viewing, like groff -Z.
All other short options are interpreted as "groff" formatting options.

The most important groffer long options are

--apropos=name   start man's "apropos" program for "name".
--apropos-data=name
                 "apropos" for "name" in man's data sections 4, 5, 7.
--apropos-devel=name
                 "apropos" for "name" in development sections 2, 3, 9.
--apropos-progs=name
                 "apropos" for "name" in man's program sections 1, 6, 8.
--auto           choose mode automatically from the default mode list.
--default        reset all options to the default value.
--default-modes=mode1,mode2,...
                 set sequence of automatically tried modes.
--dvi            display in a viewer for TeX device independent format.
--dvi-viewer     choose the viewer program for dvi mode.
--groff          process like groff, disable viewing features.
--help           display this helping output.
--html --www     display in a web browser.
--html-viewer    choose the web browser for www mode.
--man            check file parameters first whether they are man pages.
--mode=auto|dvi|groff|html|pdf|ps|source|text|tty|www|x|X
                 choose display mode.
--no-man         disable man-page facility.
--pager=program  preset the paging program for tty mode.
--pdf            display in a PDF viewer.
--pdf-viewer     choose the viewer program for pdf mode.
--ps             display in a Postscript viewer.
--ps-viewer      choose the viewer program for ps mode.
--shell          specify shell under which to run this program.
--text           output in a text device without a pager.
--tty            display with a pager on text terminal even when in X.
--www-viewer     same as --html-viewer
--x-viewer       choose viewer program for x mode (X mode).
--X-viewer       same as "--xviewer".

The usual X Windows toolkit options transformed into GNU long options
--background=color, --bd=size, --bg=color, --bordercolor=color,
--borderwidth=size, --bw=size, --display=Xdisplay, --fg=color,
--fn=font, --font=font, --foreground=color, --geometry=geom, --iconic,
--resolution=dpi, --rv, --title=text, --xrm=resource

Long options of GNU "man"
 --all, --ascii, --ditroff, --extension=suffix, --locale=language,
--local-file=name, --location, --manpath=dir1:dir2:...,
--sections=s1:s2:..., --systems=s1,s2,..., --whatis, --where, ...

EOF
  eval "${return_ok}";
}


########################################################################
# version ()
#
# print version information to stderr
#
version()
{
  func_check version = 0 "$@";
  echo2 "${_PROGRAM_NAME} ${_PROGRAM_VERSION} of ${_LAST_UPDATE}";
  # also display groff's version, but not the called subprograms
  groff -v 2>&1 | sed -e '/^ *$/q' | sed -e '1s/^/is part of /' >&2;  
}


########################################################################
# warning (<string>)
#
# Print warning to stderr
#
warning()
{
  echo2 "warning: $*";
}


########################################################################
# what_is (<filename>)
#
# Interpret <filename> as a man page and display its `whatis'
# information as a fragment written in the groff language.
#
what_is()
{
  func_check what_is = 1 "$@";
  local _res;
  local _dot;
  if is_not_file "$1"; then
    error "what_is(): argument is not a readable file."
  fi;
  _dot='^\.['"${_SPACE}${_TAB}"']*';
  echo '.br';
  echo "$1: ";
    echo '.br';
  echo -n '  ';
  # grep the line containing `.TH' macro, if any
  _res="$(catz "$1" | sed -e '/'"${_dot}"'TH /p
d')";
  if obj _res is_not_empty; then	# traditional man style
    # get the text between the first and the second `.SH' macro, by
    # - delete up to first .SH;
    # - of this, print everything up to next .SH, and delete the rest;
    # - of this, delete the final .SH line;
    catz "$1" | sed -e '1,/'"${_dot}"'SH/d' \
              | sed -e '1,/'"${_dot}"'SH/p
d' \
              | sed -e '/'"${_dot}"'SH/d';
    eval "${return_ok}";
  fi;
  # grep the line containing `.Dd' macro, if any
  _res="$(catz "$1" | sed -e '/'"${_dot}"'Dd /p
d')";
  if obj _res is_not_empty; then	# BSD doc style
    # get the text between the first and the second `.Nd' macro, by
    # - delete up to first .Nd;
    # - of this, print everything up to next .Nd, and delete the rest;
    # - of this, delete the final .Nd line;
    catz "$1" | sed -e '1,/'"${_dot}"'Nd/d' \
              | sed -e '1,/'"${_dot}"'Nd/p
d' \
              | sed -e '/'"${_dot}"'Nd/d';
    eval "${return_ok}";
  fi;
  echo 'is not a man page.';
  eval "${return_bad}";
}


########################################################################
# where (<program>)
#
# Output path of a program if in $PATH.
#
# Arguments : >=1 (empty allowed)
#   more args are ignored, this allows to specify progs with arguments
# Return    : `0' if arg1 is a program in $PATH, `1' otherwise.
#
where()
{
  func_check where '>=' 1 "$@";
  local _file;
  local _arg;
  local p;
  _arg="$1";
  if obj _arg is_empty; then
    eval "${return_bad}";
  fi;
  case "${_arg}" in
    /*)
      if test -f "${_arg}" && test -x "${_arg}"; then
        eval "${return_ok}";
      else
        eval "${return_bad}";
      fi;
      ;;
  esac;
  eval set -- "$(path_split "${PATH}")";
  for p in "$@"; do
    case "$p" in
      */) _file=${p}${_arg}; ;;
      *)  _file=${p}/${_arg}; ;;
    esac;
    if test -f "${_file}" && test -x "${_file}"; then
      echo -n "${_file}";
      eval "${return_ok}";
    fi;
  done;
  eval "${return_bad}";
}


########################################################################
#                              main
########################################################################

# The main area contains the following parts:
# - main_init(): initialize temporary files and set exit trap
# - parse $MANOPT
# - main_parse_args(): argument parsing
# - determine display mode
# - process filespec arguments
# - setup X resources
# - do the displaying

# These parts are implemented as functions, being defined below in the
# sequence they are called in the main() function.


#######################################################################
# main_init ()
#
# set exit trap and create temporary files
#
# Globals: $_TMP_CAT, $_TMP_STDIN
#
landmark '13: main_init()';
main_init()
{
  func_check main_init = 0 "$@";
  # call clean_up() on any signal
  trap_set clean_up;

  # determine temporary directory
  umask 000;
  _TMP_DIR='';
  for d in "${GROFF_TMPDIR}" "${TMPDIR}" "${TMP}" "${TEMP}" \
           "${TEMPDIR}" "${HOME}"'/tmp' '/tmp' "${HOME}" '.';
  do
    if is_not_empty "$d"; then
      if obj d is_dir && obj d is_writable; then
	_TMP_DIR="`mktemp -d ${d}/${_PROGRAM_NAME}.XXXXXX`";
	if test $? = 0; then  
          break;
        else
	  _TMP_DIR='';
	  continue;
	fi
      fi;
      if obj _TMP_DIR is_not_writable; then
	_TMP_DIR='';
	continue;
      fi;
    fi;
  done;
  unset d;
  if obj _TMP_DIR is_empty; then
    error "Couldn't create a directory for storing temporary files.";
  fi;

  _TMP_CAT="$(tmp_create groffer_cat)";
  _TMP_STDIN="$(tmp_create groffer_input)";

  # groffer configuration files
  for f in ${_CONFFILES}; do
    if obj f is_file; then
      echo '_groffer_opt=""' >>${_TMP_CAT};
      # collect the lines starting with a minus
      cat "$f" | sed -e \
        's/^[	 ]*\(-.*\)$/_groffer_opt="${_groffer_opt} \1"'/ \
        >>${_TMP_CAT};
      # prepend the collected information to $GROFFER_OPT
      echo 'GROFFER_OPT="${_groffer_opt} ${GROFFER_OPT}"' >>${_TMP_CAT};
    fi;
  done;
  . "${_TMP_CAT}";
  _TMP_CAT="$(tmp_create groffer_cat)";

  eval "${return_ok}";
} # main_init()


########################################################################
# main_parse_MANOPT ()
#
# Parse $MANOPT to retrieve man options, but only if it is a non-empty
# string; found man arguments can be overwritten by the command line.
#
# Globals:
#   in: $MANOPT, $_OPTS_MANOPT_*
#   out: $_MANOPT_*
#   in/out: $GROFFER_OPT
#
landmark '14: main_parse_MANOPT()';
main_parse_MANOPT()
{
  func_check main_parse_MANOPT = 0 "$@";
  local _opt;
  local _list;
  _list='';
  if obj MANOPT is_not_empty; then
    MANOPT="$(echo -n "${MANOPT}" | \
      sed -e 's/^'"${_SPACE}${_SPACE}"'*//')";
  fi;
  if obj MANOPT is_empty; then
    eval "${return_ok}";
  fi;
  # add arguments in $MANOPT by mapping them to groffer options
  eval set -- "$(list_from_cmdline _OPTS_MANOPT "${MANOPT}")";
  until test "$#" -le 0 || is_equal "$1" '--'; do
    _opt="$1";
    shift;
    case "${_opt}" in
      -7|--ascii)
        list_append _list '--ascii';
        ;;
      -a|--all)
        list_append _list '--all';
        ;;
      -c|--catman)
        do_nothing;
        shift;
        ;;
      -d|--debug)
        list_append _list '--debug';
        ;;
      -D|--default)
        # undo all man options so far
        _list='';
        ;;
      -e|--extension)
        list_append _list '--extension';
        shift;
        ;;
      -f|--whatis)
        list_append _list '--whatis';
        shift;
        ;;
      -h|--help)
        do_nothing;
        shift;
        ;;
      -k|--apropos)
	# groffer's --apropos takes an argument, but man's does not, so
        do_nothing;
        shift;
        ;;
      -l|--local-file)
        list_append _list '--local-file';
        ;;
      -L|--locale)
        list_append _list '--locale' "$1";
        shift;
        ;;
      -m|--systems)
        list_append _list '--systems' "$1";
        shift;
        ;;
      -M|--manpath)
        list_append _list '--manpath' "$1";
        shift;
        ;;
      -p|--preprocessor)
        do_nothing;
        shift;
        ;;
      -P|--pager|--tty-viewer)
        list_append _list '--pager' "$1";
        shift;
        ;;
      -r|--prompt)
        do_nothing;
        shift;
        ;;
      -S|--sections)
        list_append _list '--sections' "$1";
        shift;
        ;;
      -t|--troff)
        do_nothing;
        shift;
        ;;
      -T|--device)
        list_append _list '-T' "$1";
        shift;
        ;;
      -u|--update)
        do_nothing;
        shift;
        ;;
      -V|--version)
        do_nothing;
        ;;
      -w|--where|--location)
        list_append _list '--location';
        ;;
      -Z|--ditroff)
        list_append _list '-Z' "$1";
        shift;
        ;;
      # ignore all other options
    esac;
  done;
  # append the 2 lists in $_list and $GROFFER_OPT to $GROFFER_OPT
  if obj GROFFER_OPT is_empty; then
    GROFFER_OPT="${_list}";
  elif obj _list is_not_empty; then
    GROFFER_OPT="${_list} ${GROFFER_OPT}";
  fi;
  eval "${return_ok}";
} # main_parse_MANOPT()


########################################################################
# main_parse_args (<command_line_args>*)
#
# Parse arguments; process options and filespec parameters
#
# Arguments: pass the command line arguments unaltered.
# Globals:
#   in:  $_OPTS_*
#   out: $_OPT_*, $_ADDOPTS, $_FILEARGS
#
landmark '15: main_parse_args()';
main_parse_args()
{
  func_check main_parse_args '>=' 0 "$@";
  local _arg;
  local _code;
  local _dpi;
  local _longopt;
  local _mode;
  local _opt;
  local _optchar;
  local _optarg;
  local _opts;
  local _string;

  eval set -- "${GROFFER_OPT}" '"$@"';

  eval set -- "$(list_from_cmdline _OPTS_CMDLINE "$@")";

# By the call of `eval', unnecessary quoting was removed.  So the
# positional shell parameters ($1, $2, ...) are now guaranteed to
# represent an option or an argument to the previous option, if any;
# then a `--' argument for separating options and
# parameters; followed by the filespec parameters if any.

# Note, the existence of arguments to options has already been checked.
# So a check for `$#' or `--' should not be done for arguments.

  until test "$#" -le 0 || is_equal "$1" '--'; do
    _opt="$1";			# $_opt is fed into the option handler
    shift;
    case "${_opt}" in
      -h|--help)
        usage;
        leave;
        ;;
      -Q|--source)		# output source code (`Quellcode').
        _OPT_MODE='source';
        ;;
      -T|--device|--troff-device) # device; arg
        _OPT_DEVICE="$1";
        _check_device_with_mode;
        shift;
        ;;
      -v|--version)
        version;
        leave;
        ;;
      -V)
        _OPT_V='yes';
        ;;
      -Z|--ditroff|--intermediate-output) # groff intermediate output
        _OPT_Z='yes';
        ;;
      -X|--X|--x)
        _OPT_MODE=x;
        ;;
      -?)
        # delete leading `-'
        _optchar="$(echo -n "${_opt}" | sed -e 's/^.//')";
        if list_has _OPTS_GROFF_SHORT_NA "${_optchar}";
        then
          list_append _ADDOPTS_GROFF "${_opt}";
        elif list_has _OPTS_GROFF_SHORT_ARG "${_optchar}";
        then
          list_append _ADDOPTS_GROFF "${_opt}" "$1";
          shift;
        else
          error "Unknown option : \`$1'";
        fi;
        ;;
      --all)
          _OPT_ALL="yes";
          ;;
      --apropos)		# run `apropos'
	apropos_run "$1";
        _code="$?";
        clean_up;
        exit "${_code}";
        ;;
      --apropos-data)		# run `apropos' for data sections
	apropos_run "$1" | grep '^[^(]*([457][^)]*)';
        _code="$?";
        clean_up;
        exit "${_code}";
        ;;
      --apropos-devel)		# run `apropos' for development sections
	apropos_run "$1" | grep '^[^(]*([239][^)]*)';
        _code="$?";
        clean_up;
        exit "${_code}";
        ;;
      --apropos-progs)		# run `apropos' for program sections
	apropos_run "$1" | grep '^[^(]*([168][^)]*)';
        _code="$?";
        clean_up;
        exit "${_code}";
        ;;
      --ascii)
        list_append _ADDOPTS_GROFF '-mtty-char';
        if obj _mode is_empty; then
          _mode='text';
        fi;
        ;;
      --auto)			# the default automatic mode
        _mode='';
        ;;
      --bd)			# border color for viewers, arg;
        _OPT_BD="$1";
        shift;
        ;;
      --bg|--backgroud)		# background color for viewers, arg;
        _OPT_BG="$1";
        shift;
        ;;
      --bw)			# border width for viewers, arg;
        _OPT_BW="$1";
        shift;
        ;;
      --default)		# reset variables to default
        reset;
        ;;
      --default-modes)		# sequence of modes in auto mode; arg
        _OPT_DEFAULT_MODES="$1";
        shift;
        ;;
      --debug)			# buggy, only for development
        _OPT_DEBUG='yes';
        ;;
      --display)		# set X display, arg
        _OPT_DISPLAY="$1";
        shift;
        ;;
      --dvi)
        _OPT_MODE='dvi';
        ;;
      --dvi-viewer)		# viewer program for dvi mode; arg
        _OPT_VIEWER_DVI="$1";
        shift;
        ;;
      --extension)		# the extension for man pages, arg
        _OPT_EXTENSION="$1";
        shift;
        ;;
      --fg|--foreground)	# foreground color for viewers, arg;
        _OPT_FG="$1";
        shift;
        ;;
      --fn|--font)		# set font for viewers, arg;
        _OPT_FN="$1";
        shift;
        ;;
      --geometry)		# window geometry for viewers, arg;
        _OPT_GEOMETRY="$1";
        shift;
        ;;
      --groff)
        _OPT_MODE='groff';
        ;;
      --html|--www)		# display with web browser
        _OPT_MODE=html;
        ;;
      --html-viewer|--www-viewer) # viewer program for html mode; arg
        _OPT_VIEWER_HTML="$1";
        shift;
        ;;
      --iconic)			# start viewers as icons
        _OPT_ICONIC='yes';
        ;;
      --locale)			# set language for man pages, arg
        # argument is xx[_territory[.codeset[@modifier]]] (ISO 639,...)
        _OPT_LANG="$1";
        shift;
        ;;
      --local-file)		# force local files; same as `--no-man'
        _MAN_FORCE='no';
        _MAN_ENABLE='no';
        ;;
      --location|--where)	# print file locations to stderr
        _OPT_LOCATION='yes';
        ;;
      --man)			# force all file params to be man pages
        _MAN_ENABLE='yes';
        _MAN_FORCE='yes';
        ;;
      --manpath)		# specify search path for man pages, arg
        # arg is colon-separated list of directories
        _OPT_MANPATH="$1";
        shift;
        ;;
      --mode)			# display mode
        _arg="$1";
        shift;
        case "${_arg}" in
          auto|'')		# search mode automatically among default
	    _mode='';
            ;;
          groff)		# pass input to plain groff
            _mode='groff';
            ;;
          html|www)		# display with a web browser
            _mode='html';
            ;;
          dvi)			# display with xdvi viewer
            _mode='dvi';
            ;;
          pdf)			# display with PDF viewer
            _mode='pdf';
            ;;
          ps)			# display with Postscript viewer
            _mode='ps';
            ;;
          text)			# output on terminal
            _mode='text';
            ;;
          tty)			# output on terminal
            _mode='tty';
            ;;
          X|x)			# output on X roff viewer
            _mode='x';
            ;;
          Q|source)		# display source code
            _mode="source";
            ;;
	  *)
            error "unknown mode ${_arg}";
            ;;
        esac;
        _OPT_MODE="${_mode}";
        ;;
      --no-location)		# disable former call to `--location'
        _OPT_LOCATION='yes';
        ;;
      --no-man)			# disable search for man pages
        # the same as --local-file
        _MAN_FORCE="no";
        _MAN_ENABLE="no";
        ;;
      --pager)			# set paging program for tty mode, arg
        _OPT_PAGER="$1";
        shift;
        ;;
      --pdf)
        _OPT_MODE='pdf';
        ;;
      --pdf-viewer)		# viewer program for ps mode; arg
        _OPT_VIEWER_PDF="$1";
        shift;
        ;;
      --ps)
        _OPT_MODE='ps';
        ;;
      --ps-viewer)		# viewer program for ps mode; arg
        _OPT_VIEWER_PS="$1";
        shift;
        ;;
      --resolution)		# set resolution for X devices, arg
        _arg="$1";
        shift;
        case "${_arg}" in
          75|75dpi)
            _dpi=75;
            ;;
          100|100dpi)
            _dpi=100;
            ;;
          *)
            error "only resoutions of 75 or 100 dpi are supported";
            ;;
        esac;
        _OPT_RESOLUTION="${_dpi}";
        ;;
      --rv)
        _OPT_RV='yes';
        ;;
      --sections)		# specify sections for man pages, arg
        # arg is colon-separated list of section names
        _OPT_SECTIONS="$1";
        shift;
        ;;
      --shell)
        # already done during the first run; so ignore the argument
        shift;
        ;;
      --systems)		# man pages for different OS's, arg
        # argument is a comma-separated list
        _OPT_SYSTEMS="$1";
        shift;
        ;;
      --text)			# text mode without pager
        _OPT_MODE=text;
        ;;
      --title)			# title for X viewers; arg
        _OPT_TITLE="$1";
        shift;
        ;;
      --tty)			# tty mode, text with pager
        _OPT_MODE=tty;
        ;;
      --text-device|--tty-device) # device for tty mode; arg
        _OPT_TEXT_DEVICE="$1";
        shift;
        ;;
      --whatis)
        _OPT_WHATIS='yes';
        ;;
      --xrm)			# pass X resource string, arg;
        list_append _OPT_XRM "$1";
        shift;
        ;;
      --x-viewer|--X-viewer)	# viewer program for x mode; arg
        _OPT_VIEWER_X="$1";
        shift;
        ;;
      *)
        error 'error on argument parsing : '"\`$*'";
        ;;
    esac;
  done;
  shift;			# remove `--' argument

  if obj _DEBUG is_not_yes; then
    if obj _OPT_DEBUG is_yes; then
      _DEBUG='yes';
    fi;
  fi;

  # Remaining arguments are file names (filespecs).
  # Save them to list $_FILEARGS
  if is_equal "$#" 0; then	# use "-" for standard input
    set -- '-';
  fi;
  _FILEARGS='';
  list_append _FILEARGS "$@";
  if list_has _FILEARGS '-'; then
    save_stdin;
  fi;
  # $_FILEARGS must be retrieved with `eval set -- "$_FILEARGS"'
  eval "${return_ok}";
} # main_parse_args()

# Called from main_parse_args() because double `case' is not possible.
# Globals: $_OPT_DEVICE, $_OPT_MODE
_check_device_with_mode()
{
  func_check _check_device_with_mode = 0 "$@";
  case "${_OPT_DEVICE}" in
    dvi)
      _OPT_MODE=dvi;
      eval "${return_ok}";
      ;;
    html)
      _OPT_MODE=html;
      eval "${return_ok}";
      ;;
    lbp|lj4)
      _OPT_MODE=groff;
      eval "${return_ok}";
      ;;
    ps)
      _OPT_MODE=ps;
      eval "${return_ok}";
      ;;
    ascii|cp1047|latin1|utf8)
      if obj _OPT_MODE is_not_equal text; then
        _OPT_MODE=tty;		# default text mode
      fi;
      eval "${return_ok}";
      ;;
    X*)
      _OPT_MODE=x;
      eval "${return_ok}";
      ;;
    *)				# unknown device, go to groff mode
      _OPT_MODE=groff;
      eval "${return_ok}";
      ;;
  esac;
  eval "${return_error}";
}


########################################################################
# main_set_mode ()
#
# Determine the display mode.
#
# Globals:
#   in:  $DISPLAY, $_OPT_MODE, $_OPT_DEVICE
#   out: $_DISPLAY_MODE
#

# _get_first_prog (<proglist>)
#
# Retrieve first argument that represents an existing program in $PATH.
# Local function for main_set_mode().
#
# Arguments: 1; a comma-separated list of commands (with options),
#               like $_VIEWER_*.
#
# Return  : `1' if none found, `0' if found.
# Output  : the argument that succeded.
#
landmark '16: main_set_mode()';
main_set_mode()
{
  func_check main_set_mode = 0 "$@";
  local m;
  local _modes;
  local _viewer;
  local _viewers;

  # handle apropos
  if obj _OPT_APROPOS is_not_empty; then
    apropos "${_OPT_APROPOS}";
    _code="$?";
    clean_up;
    exit "${_code}";
  fi;
  if obj _OPT_APROPOS_DATA is_not_empty; then
    apropos "$@" | grep '^[^(]*([457])';
    _code="$?";
    clean_up;
    exit "${_code}";
  fi;
  if obj _OPT_APROPOS_DEVEL is_not_empty; then
    apropos "$@" | grep '^[^(]*([239])';
    _code="$?";
    clean_up;
    exit "${_code}";
  fi;
  if obj _OPT_APROPOS_PROGS is_not_empty; then
    apropos "$@" | grep '^[^(]*([168])';
    _code="$?";
    clean_up;
    exit "${_code}";
  fi;

  # set display
  if obj _OPT_DISPLAY is_not_empty; then
    DISPLAY="${_OPT_DISPLAY}";
  fi;

  if obj _OPT_V is_yes; then
    _DISPLAY_MODE='groff';
    list_append _ADDOPTS_GROFF '-V';
  fi;
  if obj _OPT_Z is_yes; then
    _DISPLAY_MODE='groff';
    list_append _ADDOPTS_GROFF '-Z';
  fi;
  if obj _OPT_MODE is_equal 'groff'; then
    _DISPLAY_MODE='groff';
  fi;
  if obj _DISPLAY_MODE is_equal 'groff'; then
    eval "${return_ok}";
  fi;

  if obj _OPT_MODE is_equal 'source'; then
    _DISPLAY_MODE='source';
    eval "${return_ok}";
  fi;

  case "${_OPT_MODE}" in
    '')				# automatic mode
      case "${_OPT_DEVICE}" in
        X*)
          if obj DISPLAY is_empty; then
            error "no X display found for device ${_OPT_DEVICE}";
          fi;
          _DISPLAY_MODE='x';
          eval "${return_ok}";
          ;;
        ascii|cp1047|latin1|utf8)
          if obj _DISPLAY_MODE is_not_equal 'text'; then
            _DISPLAY_MODE='tty';
          fi;
          eval "${return_ok}";
          ;;
      esac;
      if obj DISPLAY is_empty; then
        _DISPLAY_MODE='tty';
        eval "${return_ok}";
      fi;

      if obj _OPT_DEFAULT_MODES is_empty; then
        _modes="${_DEFAULT_MODES}";
      else
        _modes="${_OPT_DEFAULT_MODES}";
      fi;
      ;;
    text)
      _DISPLAY_MODE='text';
      eval "${return_ok}";
      ;;
    tty)
      _DISPLAY_MODE='tty';
      eval "${return_ok}";
      ;;
    *)				# display mode was given
      if obj DISPLAY is_empty; then
        error "you must be in X Window for ${_OPT_MODE} mode.";
      fi;
      _modes="${_OPT_MODE}";
      ;;
  esac;

  # only viewer modes are left
  eval set -- "$(list_from_split "${_modes}" ',')";
  while test "$#" -gt 0; do
    m="$1";
    shift;
    case "$m" in
      text)
        _DISPLAY_MODE='text';
        eval "${return_ok}";
        ;;
      tty)
        _DISPLAY_MODE='tty';
        eval "${return_ok}";
        ;;
      x)
        if obj _OPT_VIEWER_X is_not_empty; then
          _viewers="${_OPT_VIEWER_X}";
        else
          _viewers="${_VIEWER_X}";
        fi;
        _viewer="$(_get_first_prog "${_viewers}")";
        if is_not_equal "$?" 0; then
          continue;
        fi;
        _DISPLAY_PROG="${_viewer}";
        _DISPLAY_MODE='x';
        eval "${return_ok}";
        ;;
      dvi)
        if obj _OPT_VIEWER_DVI is_not_empty; then
          _viewers="${_OPT_VIEWER_DVI}";
        else
          _viewers="${_VIEWER_DVI}";
        fi;
        _viewer="$(_get_first_prog "${_viewers}")";
        if is_not_equal "$?" 0; then
          continue;
        fi;
        _DISPLAY_PROG="${_viewer}";
        _DISPLAY_MODE="dvi";
        eval "${return_ok}";
        ;;
      pdf)
        if obj _OPT_VIEWER_PDF is_not_empty; then
          _viewers="${_OPT_VIEWER_PDF}";
        else
          _viewers="${_VIEWER_PDF}";
        fi;
        _viewer="$(_get_first_prog "${_viewers}")";
        if is_not_equal "$?" 0; then
          continue;
        fi;
        _DISPLAY_PROG="${_viewer}";
        _DISPLAY_MODE="pdf";
        eval "${return_ok}";
        ;;
      ps)
        if obj _OPT_VIEWER_PS is_not_empty; then
          _viewers="${_OPT_VIEWER_PS}";
        else
          _viewers="${_VIEWER_PS}";
        fi;
        _viewer="$(_get_first_prog "${_viewers}")";
        if is_not_equal "$?" 0; then
          continue;
        fi;
        _DISPLAY_PROG="${_viewer}";
        _DISPLAY_MODE="ps";
        eval "${return_ok}";
        ;;
      html)
        if obj _OPT_VIEWER_HTML is_not_empty; then
          _viewers="${_OPT_VIEWER_HTML}";
        else
          _viewers="${_VIEWER_HTML}";
        fi;
        _viewer="$(_get_first_prog "${_viewers}")";
        if is_not_equal "$?" 0; then
          continue;
        fi;
        _DISPLAY_PROG="${_viewer}";
        _DISPLAY_MODE=html;
        eval "${return_ok}";
        ;;
    esac;
  done;
  error "no suitable display mode found.";
}

_get_first_prog()
{
  local i;
  if is_equal "$#" 0; then
    error "_get_first_prog() needs 1 argument.";
  fi;
  if is_empty "$1"; then
    return "${_BAD}";
  fi;
  eval set -- "$(list_from_split "$1" ',')";
  for i in "$@"; do
    if obj i is_empty; then
      continue;
    fi;
    if is_prog "$(get_first_essential $i)"; then
      echo -n "$i";
      return "${_GOOD}";
    fi;
  done;
  return "${_BAD}";
} # main_set_mode()


#######################################################################
# main_do_fileargs ()
#
# Process filespec arguments in $_FILEARGS.
#
# Globals:
#   in: $_FILEARGS (process with `eval set -- "$_FILEARGS"')
#
landmark '17: main_do_fileargs()';
main_do_fileargs()
{
  func_check main_do_fileargs = 0 "$@";
  local _exitcode;
  local _filespec;
  local _name;
  _exitcode="${_BAD}";
  eval set -- "${_FILEARGS}";
  unset _FILEARGS;
  # temporary storage of all input to $_TMP_CAT
  while test "$#" -ge 2; do
    # test for `s name' arguments, with `s' a 1-char standard section
    _filespec="$1";
    shift;
    case "${_filespec}" in
      '')
        continue;
        ;;
      '-')
        if register_file '-'; then
          _exitcode="${_GOOD}";
        fi;
        continue;
        ;;
      ?)
        if list_has_not _MAN_AUTO_SEC "${_filespec}"; then
          if do_filearg "${_filespec}"; then
            _exitcode="${_GOOD}";
          fi;
          continue;
        fi;
        _name="$1";
        case "${_name}" in
          */*|man:*|*\(*\)|*."${_filespec}")
            if do_filearg "${_filespec}"; then
              _exitcode="${_GOOD}";
            fi;
            continue;
            ;;
        esac;
        if do_filearg "man:${_name}(${_filespec})"; then
          _exitcode="${_GOOD}";
          shift;
          continue;
        else
          if do_filearg "${_filespec}"; then
            _exitcode="${_GOOD}";
          fi;
          continue;
        fi;
        ;;
      *)
        if do_filearg "${_filespec}"; then
          _exitcode="${_GOOD}";
        fi;
        continue;
        ;;
    esac;
  done;				# end of `s name' test
  while test "$#" -gt 0; do
    _filespec="$1";
    shift;
    if do_filearg "${_filespec}"; then
      _exitcode="${_GOOD}";
    fi;
  done;
  rm -f "${_TMP_STDIN}";
  if is_equal "${_exitcode}" "${_BAD}"; then
    eval "${return_bad}";
  fi;
  eval "${return_ok}";
} # main_do_fileargs()


########################################################################
# main_set_resources ()
#
# Determine options for setting X resources with $_DISPLAY_PROG.
# 
# Globals: $_DISPLAY_PROG, $_OUTPUT_FILE_NAME
#
landmark '18: main_set_resources()';
main_set_resources()
{
  func_check main_set_resources = 0 "$@";
  local _prog;			# viewer program
  local _rl;			# resource list
  local n;
  _title="$(get_first_essential \
                "${_OPT_TITLE}" "${_REGISTERED_TITLE}")";
  _OUTPUT_FILE_NAME='';
  set -- ${_title};
  until is_equal "$#" 0; do 
    n="$1";
    case "$n" in
    '')
      continue;
      ;;
    ,*)
      n="$(echo -n "$1" | sed -e 's/^,,*//')";
      ;;
    esac
    if obj n is_empty; then
      continue;
    fi;
    if obj _OUTPUT_FILE_NAME is_not_empty; then
      _OUTPUT_FILE_NAME="${_OUTPUT_FILE_NAME},";
    fi;
    _OUTPUT_FILE_NAME="${_OUTPUT_FILE_NAME}$n";
    shift;
  done;
  case "${_OUTPUT_FILE_NAME}" in
  '')
    _OUTPUT_FILE_NAME='-';
    ;;
  ,*)
    error "$_OUTPUT_FILE_NAME starts with a comma.";
    ;;
  esac;
  _OUTPUT_FILE_NAME="${_TMP_DIR}/${_OUTPUT_FILE_NAME}";

  if obj _DISPLAY_PROG is_empty; then # for example, for groff mode
    _DISPLAY_ARGS='';
    eval "${return_ok}";
  fi;

  set -- ${_DISPLAY_PROG};
  _prog="$(base_name "$1")";
  _rl='';
  if obj _OPT_BD is_not_empty; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        list_append _rl '-bd' "${_OPT_BD}";
        ;;
    esac;
  fi;
  if obj _OPT_BG is_not_empty; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        list_append _rl '-bg' "${_OPT_BG}";
        ;;
      xpdf)
        list_append _rl '-papercolor' "${_OPT_BG}";
        ;;
    esac;
  fi;
  if obj _OPT_BW is_not_empty; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        _list_append _rl '-bw' "${_OPT_BW}";
        ;;
    esac;
  fi;
  if obj _OPT_FG is_not_empty; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        list_append _rl '-fg' "${_OPT_FG}";
        ;;
    esac;
  fi;
  if is_not_empty "${_OPT_FN}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        list_append _rl '-fn' "${_OPT_FN}";
        ;;
    esac;
  fi;
  if is_not_empty "${_OPT_GEOMETRY}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi|xpdf)
        list_append _rl '-geometry' "${_OPT_GEOMETRY}";
        ;;
    esac;
  fi;
  if is_empty "${_OPT_RESOLUTION}"; then
    _OPT_RESOLUTION="${_DEFAULT_RESOLUTION}";
    case "${_prog}" in
      gxditview|xditview)
        list_append _rl '-resolution' "${_DEFAULT_RESOLUTION}";
        ;;
      xpdf)
        case "${_DEFAULT_RESOLUTION}" in
          75)
            # 72dpi is '100'
            list_append _rl '-z' '104';
            ;;
          100)
            list_append _rl '-z' '139';
            ;;
        esac;
        ;;
    esac;
  else
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        list_append _rl '-resolution' "${_OPT_RESOLUTION}";
        ;;
      xpdf)
        case "${_OPT_RESOLUTION}" in
          75)
            list_append _rl '-z' '104';
            # '100' corresponds to 72dpi
            ;;
          100)
            list_append _rl '-z' '139';
            ;;
        esac;
        ;;
    esac;
  fi;
  if is_yes "${_OPT_ICONIC}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        list_append _rl '-iconic';
        ;;
    esac;
  fi;
  if is_yes "${_OPT_RV}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        list_append _rl '-rv';
        ;;
    esac;
  fi;
  if is_not_empty "${_OPT_XRM}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi|xpdf)
        eval set -- "{$_OPT_XRM}";
        for i in "$@"; do
          list_append _rl '-xrm' "$i";
        done;
        ;;
    esac;
  fi;
  if is_not_empty "${_title}"; then
    case "${_prog}" in
      gxditview|xditview)
        list_append _rl '-title' "${_title}";
        ;;
    esac;
  fi;
  _DISPLAY_ARGS="${_rl}"; 

  eval "${return_ok}";
} # main_set_resources


########################################################################
# main_display ()
#
# Do the actual display of the whole thing.
#
# Globals:
#   in: $_DISPLAY_MODE, $_OPT_DEVICE,
#       $_ADDOPTS_GROFF, $_ADDOPTS_POST, $_ADDOPTS_X,
#       $_REGISTERED_TITLE, $_TMP_CAT,
#       $_OPT_PAGER $PAGER $_MANOPT_PAGER
#
landmark '19: main_display()';
main_display()
{
  func_check main_display = 0 "$@";
  local p;
  local _addopts;
  local _device;
  local _groggy;
  local _modefile;
  local _options;
  local _pager;
  local _title;
  export _addopts;
  export _groggy;
  export _modefile;

  if obj _TMP_CAT is_non_empty_file; then
    _modefile="${_OUTPUT_FILE_NAME}";
  else
    clean_up;
    eval "${return_ok}";
  fi;
  case "${_DISPLAY_MODE}" in
    groff)
      _ADDOPTS_GROFF="${_ADDOPTS_GROFF} ${_ADDOPTS_POST}";
      if obj _OPT_DEVICE is_not_empty; then
        _ADDOPTS_GROFF="${_ADDOPTS_GROFF} -T${_OPT_DEVICE}";
      fi;
      _groggy="$(tmp_cat | eval grog "${_options}")";
      trap_clean;
      # start a new shell program to get another process ID.
      sh -c '
        set -e;
        test -f "${_modefile}" && rm -f "${_modefile}";
        mv "${_TMP_CAT}" "${_modefile}";
        cat "${_modefile}" | \
        (
          clean_up()
          {
            if test -d "${_TMP_DIR}"; then
              rm -f "${_TMP_DIR}"/* || true;
              rmdir "${_TMP_DIR}";
            fi;
          }
          trap clean_up 0 2>/dev/null || true;
          eval "${_groggy}" "${_ADDOPTS_GROFF}";
        ) &'
      ;;
    text|tty)
      case "${_OPT_DEVICE}" in
        '')
          _device="$(get_first_essential \
                     "${_OPT_TEXT_DEVICE}" "${_DEFAULT_TTY_DEVICE}")";
          ;;
        ascii|cp1047|latin1|utf8)
          _device="${_OPT_DEVICE}";
          ;;
        *)
          warning \
            "wrong device for ${_DISPLAY_MODE} mode: ${_OPT_DEVICE}";
          ;;
      esac;
      _addopts="${_ADDOPTS_GROFF} ${_ADDOPTS_POST}";
      _groggy="$(tmp_cat | grog -T${_device})";
      if obj _DISPLAY_MODE is_equal 'text'; then
        tmp_cat | eval "${_groggy}" "${_addopts}";
      else
        _pager='';
        for p in "${_OPT_PAGER}" "${PAGER}" "${_MANOPT_PAGER}" \
                 'less -r -R' 'more' 'pager' 'cat'; do
        if is_prog $p; then   # no "" for is_prog() allows args for $p
          _pager="$p";
          break;
        fi;
        done;
        if obj _pager is_empty; then
          error 'no pager program found for tty mode';
        fi;
        tmp_cat | eval "${_groggy}" "${_addopts}" | \
                  eval "${_pager}";
      fi;
      clean_up;
      ;;

    #### viewer modes

    dvi)
      case "${_OPT_DEVICE}" in
        ''|dvi) do_nothing; ;;
        *)
          warning \
            "wrong device for ${_DISPLAY_MODE} mode: ${_OPT_DEVICE}";
          ;;
      esac;
      _groggy="$(tmp_cat | grog -Tdvi)";
      _do_display;
      ;;
    html)
      case "${_OPT_DEVICE}" in
        ''|html) do_nothing; ;;
        *)
          warning \
            "wrong device for ${_DISPLAY_MODE} mode: ${_OPT_DEVICE}";
          ;;
      esac;
      _modefile="${_modefile}".html
      _groggy="$(tmp_cat | grog -Thtml)";
      _do_display;
      ;;
    pdf)
      case "${_OPT_DEVICE}" in
        ''|ps)
          do_nothing;
          ;;
        *)
          warning \
            "wrong device for ${_DISPLAY_MODE} mode: ${_OPT_DEVICE}";
          ;;
      esac;
      _modefile="${_modefile}"
      _groggy="$(tmp_cat | grog -Tps)";
      trap_clean;
      # start a new shell program to get another process ID.
      sh -c '
        set -e;
        _psfile="${_modefile}.ps";
        _modefile="${_modefile}.pdf";
        test -f "${_psfile}" && rm -f "${_psfile}";
        test -f "${_modefile}" && rm -f "${_modefile}";
        cat "${_TMP_CAT}" | \
          eval "${_groggy}" "${_ADDOPTS_GROFF}" > "${_psfile}";
        gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
           -sOutputFile="${_modefile}" -c save pop -f "${_psfile}";
        test -f "${_psfile}" && rm -f "${_psfile}";
        test -f "${_TMP_CAT}" && rm -f "${_TMP_CAT}";
        (
          clean_up() {
            rm -f "${_modefile}";
            if test -d "${_TMP_DIR}"; then
              rm -f "${_TMP_DIR}"/* || true;
              rmdir "${_TMP_DIR}";
            fi;
          }
          trap clean_up 0 2>/dev/null || true;
          eval "${_DISPLAY_PROG}" ${_DISPLAY_ARGS} "${_modefile}";
        ) &'
      ;;
    ps)
      case "${_OPT_DEVICE}" in
        ''|ps)
          do_nothing;
          ;;
        *)
          warning \
            "wrong device for ${_DISPLAY_MODE} mode: ${_OPT_DEVICE}";
          ;;
      esac;
      _groggy="$(tmp_cat | grog -Tps)";
      _do_display;
      ;;
    source)
      tmp_cat;
      clean_up;
      ;;
    x)
      case "${_OPT_DEVICE}" in
        '')
          _groggy="$(tmp_cat | grog -Z)";
          ;;
        X*|ps)
          _groggy="$(tmp_cat | grog -T"${_OPT_DEVICE}" -Z)";
          ;;
        *)
          warning \
            "wrong device for ${_DISPLAY_MODE} mode: ${_OPT_DEVICE}";
          _groggy="$(tmp_cat | grog -Z)";
          ;;
      esac;
      _do_display;
      ;;
    *)
      error "unknown mode \`${_DISPLAY_MODE}'";
      ;;
  esac;
  eval "${return_ok}";
} # main_display()

_do_display()
{
  func_check _do_display = 0 "$@";
  trap_clean;
  # start a new shell program for another process ID and better
  # cleaning-up of the temporary files.
  sh -c '
    set -e;
    test -f "${_modefile}" && rm -f "${_modefile}";
    cat "${_TMP_CAT}" | \
      eval "${_groggy}" "${_ADDOPTS_GROFF}" > "${_modefile}";
    rm -f "${_TMP_CAT}";
    (
      clean_up() {
        if test -d "${_TMP_DIR}"; then
          rm -f "${_TMP_DIR}"/* || true;
          rmdir "${_TMP_DIR}";
        fi;
      }
      trap clean_up 0 2>/dev/null || true;
      eval "${_DISPLAY_PROG}" ${_DISPLAY_ARGS} "${_modefile}";
    ) &'
}


########################################################################
# main (<command_line_args>*)
#
# The main function for groffer.
#
# Arguments:
#
main()
{
  func_check main '>=' 0 "$@";
  # Do not change the sequence of the following functions!
  main_init;
  main_parse_MANOPT;
  main_parse_args "$@";
  main_set_mode;
  main_do_fileargs;
  main_set_resources;
  main_display;
  eval "${return_ok}";
}

landmark '20: end of function definitions';

########################################################################

main "$@";
