#!/bin/sh

# groffer - display groff files

# File position: <groff-source>/contrib/groffer/groffer

# Copyright (C) 2001,2002 Free Software Foundation, Inc.
# Written by Bernd Warken <bwarken@mayn.de>

# This file is part of groff.

# 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 file COPYING.  If not, write to the
# Free Software Foundation, 59 Temple Place - Suite 330, Boston,
# MA 02111-1307, USA.

export _PROGRAM_NAME;
export _PROGRAM_VERSION;
export _LAST_UPDATE;

_PROGRAM_NAME='groffer';
_PROGRAM_VERSION='0.9.0';
_LAST_UPDATE='12 July 2002';

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


########################################################################
#                          Description
########################################################################

# Display groff files and man pages on X or tty, even when compressed.


### Usage

# Input comes from either standard input or command line parameters
# that represent either names of exisiting roff files or standardized
# specifications for man pages.  All of these can be compressed in a
# format that is decompressible by `gzip'.

# The following displaying modes are available:
# - Display formatted input with the X roff viewer `gxditview',
# - with a Prostcript viewer,
# - with a dvi viewer,
# - with a web browser.
# - Display formatted input in a text terminal using a text device.
# - Generate output for some groff device on stdout without a viewer.
# - Output only the source code without any groff processing.
# - Generate the troff intermediate output on standard output
#   without groff postprocessing.
# By default, the program tries to display with `gxditview' (1); if
# this does not work, text display (2) is used.


### Error handling

# Error handling and exit behavior is complicated by the fact that
# `exit' can only escape from the current shell; trouble occurs in
# subshells.  This was solved by sending kill signals, see
# $_PROCESS_ID and error().


### Compatibility

# This shell script is compatible to the both the GNU and the POSIX
# shell and utilities.  Care was taken to restrict the programming
# technics used here in order to achieve POSIX compatibility as far
# back as POSIX P1003.2 Draft 11.2 of September 1991.

# The only non-builtin used here is POSIX `sed'.  This script was
# tested under `bash', `ash', and `ksh'.  The speed under `ash' is
# more than double when compared to the larger shells.

# This script provides its own option parser.  It is compatible to the
# usual GNU style command line (option clusters, long options, mixing
# of options and non-option file names), except that it is not
# possible to abbreviate long option names.

# The mixing of options and file names can be prohibited by setting
# the environment variable `$POSIXLY_CORRECT' to a non-empty value.
# This enables the rather wicked POSIX behavior to terminate option
# parsing when the first non-option command line argument is found.


########################################################################
#            Survey of functions defined in this document
########################################################################

# The elements specified within paranthesis `(<>)' give hints to what
# the arguments are meant to be; the argument names are irrelevant.
# <>?     0 or 1
# <>*     arbitrarily many such arguments, incl. none
# <>+     one or more such arguments
# <>      exactly 1

# A function that starts with an underscore `_' is an internal
# function for some function.  The internal functions are defined just
# after their corresponding function; they are not mentioned in the
# following.

# abort (text>*)
# base_name (path)
# catz (<file>)
# clean_up ()
# clean_up_secondary ()
# diag (text>*)
# dirname_append (<path> [<dir...>])
# dirname_chop (<path>)
# do_filearg (<filearg>)
# do_nothing ()
# echo2 (<text>*)
# echo2n (<text>*)
# error (<text>*)
# get_first_essential (<arg>*)
# is_dir (<name>)
# is_empty (<string>)
# is_equal (<string1> <string2>)
# is_file (<name>)
# is_not_empty (<string>)
# is_not_equal (<string1> <string2>)
# is_not_file (<name>)
# is_not_prog (<name>)
# is_prog (<name>)
# is_yes (<string>)
# leave ()
# landmark (<text>)
# list_append (<list> <element>...)
# list_check (<list>)
# list_from_args (<arg>...)
# list_from_cmdline (<s_n> <s_a> <l_n> <l_n> [<cmdline_arg>...])
# list_from_split (<string> <separator>)
# list_has (<list> <element>)
# list_has_not (<list> <element>)
# list_length (<list>)
#   main_*(), see after the functions
# man_do_filespec (<filespec>)
# man_setup ()
# man_register_file (<file> [<name> [<section>]])
# man_search_section (<name> <section>)
# man_set()
# manpath_add_lang(<path> <language>)
# manpath_add_system()
# manpath_from_path ()
# normalize_args (<shortopts> <longopts> <arg>*)
# path_chop (<path>)
# path_clean (<path>)
# path_contains (<path> <dir>)
# path_not_contains (<path> <dir>)
# path_split (<path>)
# register_file (<filename>)
# register_title (<filespec>)
# reset ()
# save_stdin ()
# string_contains (<string> <part>)
# string_del_append (<string>)
# string_del_leading (<string> <regex>)
# string_del_trailing (<string> <regex>)
# string_flatten()
# string_not_contains (<string> <part>)
# string_replace_all (<string> <regex> <replace>)
# string_sed_s (<string> <regex> [<replace> [<flag>]])
# tmp_cat ()
# tmp_create (<suffix>?)
# to_tmp (<filename>)
# usage ()
# version ()
# warning (<string>)
# whatis (<filename>)
# where (<program>)


########################################################################
#                       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


########################################################################
# External environment variables

# If these variables are exported here then the `ash' shell coughs
# when calling `groff' in `main_display()'.

if test "${GROFFER_EXPORT_EXTERNALS}" = 'yes'; then

  # external system environment variables that are explicitly used
  export DISPLAY;		# Presets the X display.
  export LANG;			# For language specific man pages.
  export LC_ALL;		# For language specific man pages.
  export LC_MESSAGES;		# For language specific man pages.
  export PAGER;			# Paging program for tty mode.
  export PATH;			# Path for the programs called (: list).

  # groffer native environment variables
  export GROFFER_OPT		# preset options for groffer.

  # all groff environment variables are used, see groff(1)
  export GROFF_BIN_PATH;	# Path for all groff programs.
  export GROFF_COMMAND_PREFIX;	# '' (normally) or 'g' (several troffs).
  export GROFF_FONT_PATH;	# Path to non-default groff fonts.
  export GROFF_TMAC_PATH;	# Path to non-default groff macro files.
  export GROFF_TMPDIR;		# Directory for groff temporary files.
  export GROFF_TYPESETTER;	# Preset default device.

  # all GNU man environment variables are used, see man(1).
  export MANOPT;		# Preset options for man pages.
  export MANPATH;		# Search path for man pages (: list).
  export MANROFFSEQ;		# Ignored because of grog guessing.
  export MANSECT;		# Search man pages only in sections (:).
  export SYSTEM;		# Man pages for different OS's (, list).

fi;


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

# characters

export _APPEND			# Append to string to cover final char
export _NEWLINE;
export _SPACE;
export _TAB;

_APPEND='Z';
_NEWLINE='
';
_SPACE=' ';
_TAB='	';

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

_GOOD='0';			# return ok
_BAD='1';			# return negatively, error code `1'
_BAD2='2';			# return negatively, error code `2'
_BAD3='3';			# return negatively, error code `3'
_ERROR='255';			# for syntax errors; no `-1' in `ash'

_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 _CONFFILES;
_CONFFILES="/etc/groff/groffer.conf ${HOME}/.groff/groffer.conf";

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

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_WWW_X;		# viewer program for www mode in X
export _VIEWER_WWW_TTY;		# viewer program for www mode in tty
_VIEWER_DVI='xdvi,dvilx';
_VIEWER_PDF='xpdf,acroread';
_VIEWER_PS='gv,ghostview,gs_x11,gs';
_VIEWER_WWW='mozilla,netscape,opera,amaya,arena';
_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_MAN_SHORT_ARG;
export _OPTS_MAN_SHORT_NA;
export _OPTS_MAN_LONG_ARG;
export _OPTS_MAN_LONG_NA;
export _OPTS_GROFFER_LONG;
export _OPTS_GROFFER_SHORT;
export _OPTS_GROFF_LONG;
export _OPTS_GROFF_SHORT;
export _OPTS_CMDLINE_SHORT_NA;
export _OPTS_CMDLINE_SHORT_ARG;
export _OPTS_CMDLINE_SHORT;
export _OPTS_CMDLINE_LONG_NA;
export _OPTS_CMDLINE_LONG_ARG;
export _OPTS_CMDLINE_LONG;


###### native groffer options

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

_OPTS_GROFFER_LONG_NA="'all' 'apropos' 'ascii' 'auto' 'default' 'dvi' \
'groff' 'help' 'intermediate-output' 'local-file' 'location' 'man' \
'no-location' 'no-man' 'pdf' 'ps' 'rv' 'source' 'tty' 'tty-device' \
'version' 'whatis' 'where' 'www' 'x'";

_OPTS_GROFFER_LONG_ARG="'background' 'bd' 'bg' 'bw' 'default-modes' \
'device' 'display' 'dvi-viewer' 'extension' 'fg' 'fn' 'font' \
'foreground' 'geometry' 'locale' 'manpath' 'mode' 'pager' \
'pdf-viewer' 'ps-viewer' 'resolution' 'sections' \
'systems' 'title' 'troff-device' 'www-viewer' 'xrm' 'x-viewer'";

##### options inhereted from groff

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

###### man options (for parsing $MANOPT only)

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

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

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

###### collections of options

# groffer

_OPTS_GROFFER_LONG="${_OPTS_GROFFER_LONG_ARG} ${_OPTS_GROFFER_LONG_NA}";
_OPTS_GROFFER_SHORT=\
"${_OPTS_GROFFER_SHORT_ARG} ${_OPTS_GROFFER_SHORT_NA}";

# groff

_OPTS_GROFF_LONG="${_OPTS_GROFF_LONG_ARG} ${_OPTS_GROFF_LONG_NA}";
_OPTS_GROFF_SHORT="${_OPTS_GROFF_SHORT_ARG} ${_OPTS_GROFF_SHORT_NA}";

# all command line options

_OPTS_CMDLINE_SHORT_NA="\
${_OPTS_GROFFER_SHORT_NA} ${_OPTS_GROFF_SHORT_NA}";
_OPTS_CMDLINE_SHORT_ARG="\
${_OPTS_GROFFER_SHORT_ARG} ${_OPTS_GROFF_SHORT_ARG}";
_OPTS_CMDLINE_SHORT="${_OPTS_GROFFER_SHORT} ${_OPTS_GROFF_SHORT}";

_OPTS_CMDLINE_LONG_NA="${_OPTS_GROFFER_LONG_NA} \
${_OPTS_GROFF_LONG_NA} ${_OPTS_MAN_LONG_NA}";
_OPTS_CMDLINE_LONG_ARG="${_OPTS_GROFFER_LONG_ARG} \
${_OPTS_GROFF_LONG_ARG} ${_OPTS_MAN_LONG_ARG}";
_OPTS_CMDLINE_LONG="${_OPTS_GROFFER_LONG} ${_OPTS_GROFF_LONG}";


########################################################################
# 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;		# branch to `apropos' program.
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_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_TTY_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_WWW;		# viewer program for www mode
export _OPT_VIEWER_X;		# viewer program for x mode
export _OPT_WHATIS;		# print the one-liner man info
export _OPT_X;			# groff option -X.
export _OPT_XRM;		# specify X resource.
export _OPT_Z;			# groff option -Z.
# _TMP_* temporary files
export _TMP_DIR;		# directory for temporary files
export _TMP_CAT;		# stores concatenation of everything
export _TMP_PREFIX;		# dir and base name for temporary files
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_CAT='';
_TMP_PREFIX='';
_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.";
    return "${_ERROR}";
  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='no';
  _OPT_BD='';
  _OPT_BG='';
  _OPT_BW='';
  _OPT_DEBUG='no';
  _OPT_DEFAULT_MODES='';
  _OPT_DEVICE='';
  _OPT_DISPLAY='';
  _OPT_FG='';
  _OPT_FN='';
  _OPT_GEOMETRY='';
  _OPT_LANG='';
  _OPT_LOCATION='no';
  _OPT_MODE='';
  _OPT_MANPATH='';
  _OPT_PAGER='';
  _OPT_RESOLUTION='';
  _OPT_RV='';
  _OPT_SECTIONS='';
  _OPT_SYSTEMS='';
  _OPT_TITLE='';
  _OPT_TTY_DEVICE='';
  _OPT_V='no';
  _OPT_VIEWER_DVI='';
  _OPT_VIEWER_PDF='';
  _OPT_VIEWER_PS='';
  _OPT_VIEWER_WWW='';
  _OPT_VIEWER_X='';
  _OPT_WHATIS='no';
  _OPT_X='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()
{
  clean_up_secondary;
  rm -f "${_TMP_CAT}";
}


##############
# clean_up_secondary ()
#
# Clean up temporary files without $_TMP_CAT.
#
clean_up_secondary()
{
  rm -f "${_TMP_STDIN}";
}


##############
# 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.
#
# 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.';
        ;;
      *!*)
#        echo2 '>> pop: ('"$(echo -n "${_FUNC_STACK}" \
#                           | sed -e 's/^\([^ !]*\).*$/\1/')"')';
        _FUNC_STACK="$(echo -n ${_FUNC_STACK} \
                       | sed -e 's/^[^!]*!//')";
        ;;
      *)
        _FUNC_STACK='';
        ;;
    esac;
  fi;
}


#############
# func_push (<element>)
#
# Store another element to stack.
#
# 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
      *'!'*)
        _element="$(echo -n "$1" | sed -e 's/!//g')";
        ;;
      *)
        _element="$1";
        ;;
    esac;
#    echo2 '>> push: '"$_element";
    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
        diag "$(echo -n "${_rest}" | sed -e 's/^\([^!]*\)!.*$/\1/')";
        _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*sT\+\).*$/\1/' \
           | sed -e '\|T|s||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


########################################################################
# base_name (<path>)
#
# Get the file name part of <path>, i.e. delete everything up to last
# `/' from the beginning of <path>.
#
# Arguments : 1
# Output    : the file name part (without slashes)
#
base_name()
{
  func_check base_name = 1 "$@";
  string_sed_s "$1" '^.*/\([^/]*\)$' '\1';
  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 is_yes "${_HAS_BZIP}"; 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


########################################################################
# clean_up_secondary ()
#
# Do the second but final cleaning up.
#
# 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;
  _res="$(string_replace_all "$1" '//\+' '/')";
  case "${_res}" in
    ?*/) string_del_trailing "${_res}" '/'; ;;
    *) 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 positional parameters
  case "${_filespec}" in
    '')
       eval "${return_good}";
       ;;
    '-')
      register_file '-';
      eval "${return_good}";
      ;;
    */*)			# with directory part; so no man search
      set -- 'File';
      ;;
    *)
      if is_yes "${_MAN_ENABLE}"; then
        if is_yes "${_OPT_MAN}"; 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 is_not_yes "${_MAN_IS_SETUP}"; 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 test "$#" -eq 0; then
    eval "${return_ok}";
  fi;
  for i in "$@"; do
    if is_not_empty "$i"; 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 is_not_empty "$1" && test -d "$1" && test -r "$1"; then
    eval "${return_yes}";
  else
    eval "${return_no}";
  fi;
  eval "${return_ok}";
}


########################################################################
# 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 -z "$1"; then
    eval "${return_yes}";
  else
    eval "${return_no}";
  fi;
  eval "${return_ok}";
}


########################################################################
# 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}";
  else
    eval "${return_no}";
  fi;
  eval "${return_ok}";
}


########################################################################
# 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 is_not_empty "$1" && test -f "$1" && test -r "$1"; then
    eval "${return_yes}";
  else
    eval "${return_no}";
  fi;
  eval "${return_ok}";
}


########################################################################
# 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}";
  else
    eval "${return_yes}";
  fi;
  eval "${return_ok}";
}


########################################################################
# 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}";
  else
    eval "${return_yes}";
  fi;
  eval "${return_ok}";
}


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


########################################################################
# 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}";
  else
    eval "${return_yes}";
  fi;
  eval "${return_ok}";
}


########################################################################
# 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}";
  else
    eval "${return_yes}";
  fi;
  eval "${return_ok}";
}


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


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


########################################################################
# 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 test "$1" = 'yes'; then
    eval "${return_yes}";
  else
    eval "${return_no}";
  fi;
  eval "${return_ok}";
}


########################################################################
# 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_append (<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_append()
{
  func_check list_append '>=' 2 "$@";
  local _element;
  local _res;
  _res="$1";
  shift;
  for s in "$@"; do
    # escape each single quote.
    case "$s" in
      *\'*)
        _element="$(echo -n "$s" | sed -e s/\'/'&\\&&'/g)";
        ;;
      *)
        _element="$s";
        ;;
    esac;
    _res="${_res} '${_element}'";
  done;
  echo -n "${_res}";
  eval "${return_ok}";
}


########################################################################
# list_check (<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 single-quotes left.  Watch out for "\'".
#
# Arguments: 1
# Output: the argument <list> unchanged, it the check succeeded.
#
list_check()
{
  func_check list_check = 1 "$@";
  local _list;
  if is_empty "$1"; then
    eval "${return_ok}";
  fi;
  case "$1" in
    \'*\') _list="$1"; ;;
    *)
      error "list_check() bad list: $1"
      ;;
  esac;
  _list="$(echo -n "${_list}" \
           | sed -e s/^"[']"'\(.*\)'"[']"'$/\1/' \
           | sed -e s/"[']"'[\]'"['][']"'//g' \
           | sed -e 's/\([^\]\)'"'"'  *'"'"'/\1/g')"; # ' (for emacs)
  case "${_list}" in
    *\'*)
      error "list_check() bad list: ${_list}"
      ;;
  esac;
  echo -n "$1";
  eval "${return_ok}";
}


########################################################################
# list_element_from_arg (<arg>)
#
# Arguments: 1
#   <arg>: some sequence of characters (also single quotes allowed).
# Output: the list element generated from <arg>.
#
list_element_from_arg()
{
  func_check list_element_from_arg = 1 "$@";
  local _res;
  echo -n "'";
  # replace each single quote "'" by "'\''". 
  echo -n "$1" | sed -e 's/'\''/&\\&&/g'; # ' for emacs
  echo -n "'";
  eval "${return_ok}";
}


########################################################################
# list_from_args (<arg>...)
#
# Generate a space-separated list of single-quoted elements from args.
#
# Arguments:
#   <arg>: some sequence of characters.
# Output: "'<arg>' '...'"
#
list_from_args()
{
  func_check list_from_args '>=' 1 "$@";
  local _list;
  _list="";
  for s in "$@"; do
    _list="$(list_append "${_list}" "$s")";
  done;
  echo -n "${_list}";
  eval "${return_ok}";
}


########################################################################
# list_from_cmdline (<s_n> <s_a> <l_n> <l_n> [<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: >=4
#   <s_n>: space-separated list of short options without an arg.
#   <s_a>: space-separated list of short options that have an arg.
#   <l_n>: space-separated list of long options without an arg.
#   <l_a>: space-separated list of long options that have an arg.
#   <cmdline_arg>...: the arguments from the command line (by "$@").
#
# Globals: $POSIXLY_CORRECT (only kept for compatibility).
#
# Output: ['-[-]opt' ['optarg']]... '--' ['filename']...
#
# Example:
#     list_normalize 'a b' 'c' '' 'long' -a f1 -bcarg --long=larg f2
#   will result in printing:
#     '-a' '-b' '-c' 'arg' '--long' 'larg' '--' 'f1' 'f2'
#   If $POSIXLY_CORRECT is not empty, the result will be:
#     '-a' '--' 'f1' '-bcarg' '--long=larg' 'f2'
#
#   Rationale:
#     In POSIX, the first non-option ends the option processing.
#     In GNU mode (default), non-options are sorted behind the options.
#
#   Use this function only in the following way:
#     eval set -- "$(args_norm '...' '...' '...' '...' "$@")";
#     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 '>=' 4 "$@";
  local _fparams;
  local _result;
  if is_empty "${FUNCNAME}"; then
    local FUNCNAME;
    FUNCNAME='list_from_cmdline';
  fi;
  _short_n="$(list_check "$1")"; # short options, no argument
  _short_a="$(list_check "$2")"; # short options with argument
  _long_n="$(list_check "$3")";	 # long options, no argument
  _long_a="$(list_check "$4")";	 # long options with argument
  shift 4;
  if test "$#" -eq 0; then
    echo -n "'--'";
    eval "${return_ok}";
  fi;
  _fparams='';
  _result='';
  while test "$#" -ge 1; do
    _arg="$1";
    shift;
    case "$_arg" in
      --) break; ;;
      --?*)
        _opt="$(string_del_leading "${_arg}" "--")";
        if list_has "$_long_n" "${_opt}"; then
          # long option, no argument
          _result="$(list_append "${_result}" "--${_opt}")";
          continue;
        fi;
        if list_has "${_long_a}" "${_opt}"; then
          # long option with argument
          _result="$(list_append "${_result}" "--${_opt}")";
          if test "$#" -le 0; then
            error "${FUNCNAME}(): no argument for option --${_opt}."
          fi;
          _result="$(list_append "${_result}" "$1")";
          shift;
          continue;
        fi;
        # test on `--opt=arg'
        if string_contains "${_opt}" "="; then
          _lopt="$(string_del_trailing "${_opt}" '=.*')";
          if list_has "${_long_a}" "${_lopt}"; then
            _optarg="$(string_del_leading "${_opt}" "${_lopt}=")";
            _result="$(list_append "${_result}" \
                                   "--${_lopt}" "${_optarg}")";
            continue;
          fi;
        fi;
        error "${FUNCNAME}(): --${_opt} is not an option."
        ;;
      -?*)			# short option (cluster)
        _rest="$(string_del_leading "${_arg}" "-")";
        while is_not_empty "${_rest}"; do
          _optchar="$(string_get_leading "${_rest}" '.')";
          _rest="$(string_del_leading "${_rest}" '.')";
          if list_has "${_short_n}" "${_optchar}"; then
            _result="$(list_append "${_result}" "-${_optchar}")";
            continue;
          elif list_has "${_short_a}" "${_optchar}"; then
            _rest="$(string_del_leading "${_rest}"  "${_optchar}")";
            if is_empty "${_rest}"; then
              if test "$#" -ge 1; then
                _result="$(list_append "${_result}" \
                                          "-${_optchar}" "$1")";
                shift;
                continue;
              else
                error \
                  "${FUNCNAME}(): no argument for option -${_optchar}."
              fi;
            else		# rest is the argument
              _result="$(list_append "${_result}" \
                                     "-${_optchar}" "${_rest}")";
              _rest='';
              continue;
            fi;
          else
            error "${FUNCNAME}(): unknown option -${_optchar}."
          fi;
        done;
        ;;
      *)
	# Here, $_arg is not an option, so a file parameter.
        # When $POSIXLY_CORRECT is set this ends option parsing;
        # otherwise, the argument is stored as a file parameter and
        # option processing is continued.
        _fparams="$(list_append "${_fparams}" "${_arg}")";
	if is_not_empty "$POSIXLY_CORRECT"; then
          break;
        fi;
        ;;
    esac;
  done;
  _result="$(list_append "${_result}" "--")";
  if is_not_empty "${_fparams}"; then
    _result="${_result} ${_fparams}";
  fi;
  if test "$#" -gt 0; then
    _result="$(list_append "${_result}" "$@")";
  fi;
  echo -n "$_result";
  eval "${return_ok}";
} # list_from_cmdline()


########################################################################
# list_from_lists (<list1> <list2>...)
#
# Generate a list from the concatenation of the lists in the arguments.
#
# Arguments: >=2
#   <list*>: string of space-separated single-quoted elements.
# Output: "'<element1_of_list1>' ..."
#
list_from_lists()
{
  func_check list_from_lists '>=' 2 "$@";
  _list='';
  echo -n "$*";
  eval "${return_ok}";
}


########################################################################
# list_from_split (<string> <separator>)
#
# In <string> escape white space and replace each <separator> by space.
#
# Arguments: 2: a <string> that is to be split into parts divided by
#               <separator>
# Output:    the resulting string
#
list_from_split()
{
  func_check list_from_split = 2 "$@";
  string_replace_all \
    "$(string_replace_all \
       "$1" \
       '\(['"${_SPACE}${_TAB}"']\)' \
       '\\\1')" \
    "$2" \
    ' ';
  eval "${return_ok}";
}


########################################################################
# list_has (<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()
{
  func_check list_has = 2 "$@";
  if is_empty "$1"; then
    eval "${return_no}";
  fi;
  _list="$1";
  _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 "$@";
  if is_empty "$1"; then
    eval "${return_yes}";
  fi;
  _list="$1";
  _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}";
}


########################################################################
# list_length (<list>)
#
# Arguments: 1
#   <list>:    a space-separated list of single-quoted elements.
# Output: the number of elements in <list>
#
list_length()
{
  func_check list_length = 1 "$@";
  eval set -- "$1";
  echo -n "$#";
  eval "${return_ok}";
}


########################################################################
# list_prepend (<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:           "'<element>' ... <list>"
#
list_prepend()
{
  func_check list_prepend '>=' 2 "$@";
  local _res;
  _res="$1";
  shift;
  for s in "$@"; do
    # escape each single quote.
    _element="$(echo -n "$s" | sed -e 's/'\''/&\\&&/g')";
    _res="'${_element}' ${_res}";
  done;
  echo -n "${_res}";
  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 is_empty "${MANPATH}"; then
    eval "${return_bad}";
  fi;
  if is_empty "$1"; then
    eval "${return_bad}";
  fi;
  _spec="$1";
  _name='';
  _section='';
  case "${_spec}" in
    man:?*\(?*\))		# man:name(section)
      _string="$(string_del_leading "${_spec}" 'man:')";
      _string="$(string_del_trailing "${_string}" ')')";
      _name="$(string_del_trailing "${_string}" '(.\+')";
      _section="$(string_del_leading "${_string}" "${_name}"'(')";
      ;;
    man:?*.?*)			# man:name.section
      _string="$(string_del_leading "${_spec}" 'man:')";
      _name="$(string_del_trailing "${_string}" '\.[^.]*')";
      _section="$(string_del_leading "${_string}" "${_name}"'\.')";
      ;;
    man:?*)			# man:name
      _name="$(string_del_leading "${_spec}" 'man:')";
      ;;
    ?*\(?*\))			# name(section)
      _string="$(string_del_trailing "${_spec}" ')')";
      _name="$(string_del_trailing "${_string}" '(.\+')";
      _section="$(string_del_leading "${_string}" "${_name}"'(')";
      ;;
    ?*.?*)			# name.section
      _name="$(string_del_trailing "${_spec}" '\.[^.]\+')";
      _section="$(string_del_leading "${_spec}" "${_name}"'\.')";
      ;;
    ?*)
      _name="${_filespec}";
      ;;
  esac;
  if is_empty "${_name}"; then
    eval "${return_bad}";
  fi;
  _got_one='no';
  if is_empty "${_section}"; then
    eval set -- "${_MAN_AUTO_SEC}";
    for s in "$@"; do
      if man_search_section "${_name}" "$s"; then # found
        if is_yes "${_MAN_ALL}"; 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 is_yes "${_MAN_ALL}" && 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 is_empty "${_MAN_PATH}"; 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 is_empty "${_MAN_EXT}"; then
    for d in "$@"; do
      _dir="$(dirname_append "$d" "man${_section}")";
      if is_dir "${_dir}"; then
        _prefix="$(dirname_append "${_dir}" "${_name}.${_section}")";
        for f in $(echo -n ${_prefix}*); do
          if is_file "$f"; then
            if is_yes "${_got_one}"; then
              register_file "$f";
            elif is_yes "${_MAN_ALL}"; 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 is_dir "${_dir}"; then
        _prefix="$(dirname_append "${_dir}" "${_name}.${_section}")";
        for f in ${_prefix}*; do
          if is_file "$f"; then
            if is_yes "${_got_one}"; then
              register_file "$f";
            elif is_yes "${_MAN_ALL}"; 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 is_dir "${_dir}"; then
        _prefix="$(dirname_append "${_dir}" \
                                  "${_name}.${_section}${_ext}")";
        for f in ${_prefix}*; do
          if is_file "$f"; then
            if is_yes "${_got_one}"; then
              register_file "$f";
            elif is_yes "${_MAN_ALL}"; 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 is_yes "${_MAN_ALL}" && 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 is_yes "${_MAN_IS_SETUP}"; then
    eval "${return_ok}";
  fi;
  _MAN_IS_SETUP='yes';

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

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

  _MAN_ALL="$(get_first_essential "${_OPT_ALL}" "${_MANOPT_ALL}")";
  if is_empty "${_MAN_ALL}"; 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}";
      _MAN_LANG2="$(string_get_leading "${_lang}" '..')";
      ;;
  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 is_empty "${_MAN_PATH}"; 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 is_empty "${_MAN_PATH}"; 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
  #
  local d;
#  if test "$#" -ne 2; then
#    error "manpath_add_system_single() needs 2 arguments.";
#  fi;
  _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 path_not_contains "${_res}" "${_dir}" && is_dir "${_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 is_not_empty "${PATH}"; then
    eval set -- "$(path_split "${PATH}")";
    for d in "$@"; do
      _base="$(string_del_trailing "$d" '/\+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 path_not_contains "${_manpath}" "$d" && is_dir "$d"; then
      _manpath="${_manpath}:$d";
    fi;
  done;

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


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

########################################################################
# 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;

#  _res="$1";
#  _res="$(string_flatten "${_res}" ':')";
#  _res="$(string_del_leading "${_res}" ':')";
#  _res="$(string_del_trailing "${_res}" ':')";
#  echo -n "${_res}";

  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 test "$#" -ne 1; then
    error 'path_clean() needs 1 argument.';
  fi;
  _arg="$1";
  eval set -- "$(path_split "${_arg}")";
  _res="";
  for i in "$@"; do
    if is_not_empty "$i" \
       && path_not_contains "${_res}" "$i" \
       && is_dir "$i";
    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}";
}


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


########################################################################
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
  _title="$(string_del_trailing "${_title}" '\.gz')"; # remove .gz
  _title="$(string_del_trailing "${_title}" '\.Z')";  # remove .Z
  if is_empty "${_title}"; then
    eval "${return_ok}";
  fi;
  _REGISTERED_TITLE="${_REGISTERED_TITLE} ${_title}";
  eval "${return_ok}";
}


########################################################################
# save_stdin ()
#
# Store standard input to temporary file (with decompression).
#
if test "${_HAS_COMPRESSION}" = 'yes'; then
  save_stdin()
  {
    local _f;
    func_check save_stdin = 0 "$@";
    _f="$(tmp_create)";
    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_del_append (<string>)
#
# Delete $_APPEND from the end of <string>, if any.
#
# As this is needed within string_sed_s() this function must use `sed'.
#
# Arguments: <string>: arbitrary sequence of characters.
# Globals: in: $_APPEND
# Output: the shortened string.
# Return: $_GOOD if successful, $_BAD if no replace took place.
#
string_del_append()
{
  func_check string_del_append = 1 "$@";
  case "$1" in
    *"$_APPEND")
      echo -n "$1" | sed -e 's/'"$_APPEND"'$//'
      eval "${return_good}";
      ;;
    *)
      eval "${return_bad}";
      ;;
  esac;
  eval "${return_ok}";
}


########################################################################
# string_del_leading (<string> <regex>)
#
# Delete the beginning <regex> of <string>, if any.
#
# Arguments: 2
#   <string>: arbitrary sequence of characters.
#   <regex>: is a BRE like in `sed';
#   Do not worry about the address delimiter, the program escapes them.
# Output: the replaced string.
#
string_del_leading()
{
  func_check string_del_leading = 2 "$@";
  local _del;
  local _result;
  local _string;
  _string="$1";
  _del="$2";
  if is_empty "${_string}"; then
    if is_empty "${_del}"; then
      eval "${return_good}";
    else
      eval "${return_bad}";
    fi;
  fi;
  if is_empty "${_del}"; then
    echo -n "${_string}";
    eval "${return_ok}";
  fi;
  _result="$(string_sed_s "${_string}" '^'"${_del}" '')";
  echo -n "${_result}";
  if is_equal "${_result}" "${_string}"; then
    eval "${return_bad}";
  else
    eval "${return_ok}";
  fi;
  eval "${return_ok}";
}


########################################################################
# string_del_trailing (<string> <regex>)
#
# Delete the final <regex> of <string>, if any.
#
# Arguments: 2
#   <string>: arbitrary sequence of characters.
#   <regex>: is a BRE like in `sed';
#   Do not worry about the address delimiter, the program escapes them.
# Output: the replaced string.
#
string_del_trailing()
{
  func_check string_del_trailing = 2 "$@";
  local _del;
  local _result;
  local _string;
  _string="$1";
  _del="$2";
  if is_empty "${_string}"; then
    if is_empty "${_del}"; then
      eval "${return_ok}";
    else
      eval "${return_bad}";
    fi;
  fi;
  if is_empty "${_del}"; then
    echo -n "${_string}";
    eval "${return_good}";
  fi;
  _result="$(string_sed_s "${_string}" "${_del}"'$' '')";
  echo -n "${_result}";
  if is_equal "${_result}" "${_string}"; then
    eval "${return_bad}";
  else
    eval "${return_ok}";
  fi;
  eval "${return_ok}";
}


########################################################################
# string_flatten (<string> <char>)
#
# Reduce multiple occurences of character <char> in <string> to one.
#
# Arguments: 2
#   <string>: arbitrary sequence of characters.
#   <char>: a character, or escaped character for sed.
#   Do not worry about the address delimiter, the program escapes them.
# Output: the retrieved string.
#
string_flatten()
{
  func_check string_flatten = 2 "$@";
  _string="$1";
  _char="$2";
  string_replace_all "${_string}" "${_char}${_char}\+" "${_char}";
  eval "${return_ok}";
}


########################################################################
# string_get_before (<string> <regex>)
#
# Get the beginning <regex> of <string>, if any.
#
# Arguments: 2
#   <string>: arbitrary sequence of characters.
#   <regex>: is a BRE like in `sed';
#   Do not worry about the address delimiter, the program escapes them.
# Output: the retrieved string.
#
string_get_before()
{
  func_check string_get_before = 2 "$@";
  local _del;
  local _regex;
  local _res;
  local _string;
  if is_empty "$1"; then
    if is_empty "$2"; then
      eval "${return_ok}";
    else
      eval "${return_bad}";
    fi;
  fi;
  if is_empty "$2"; then
    echo -n "$1";
    eval "${return_ok}";
  fi;
  _res="$(string_sed_s "$1" "$2"'.*$' '')";
  echo -n "${_res}";
  if is_equal "${_res}" "$1"; then
    eval "${return_bad}";
  else
    eval "${return_ok}";
  fi;
  eval "${return_ok}";
}


########################################################################
# string_get_leading (<string> <regex>)
#
# Get the beginning <regex> of <string>, if any.
#
# Arguments: 2
#   <string>: arbitrary sequence of characters.
#   <regex>: is a BRE like in `sed';
#   Do not worry about the address delimiter, the program escapes them.
# Output: the retrieved string.
#
string_get_leading()
{
  func_check string_get_leading = 2 "$@";
  local _del;
  local _result;
  local _string;
  _string="$1";
  _get="$2";
  if is_empty "${_string}"; then
    if is_empty "${_get}"; then
      eval "${return_ok}";
    else
      eval "${return_bad}";
    fi;
  fi;
  if is_empty "${_get}"; then
    echo -n "${_string}";
    eval "${return_ok}";
  fi;
  _result="$(string_sed_s "${_string}" '^\('"${_get}"'\).*$' '\1')";
  echo -n "${_result}";
  if is_equal "${_result}" "${_string}"; then
    eval "${return_bad}";
  else
    eval "${return_ok}";
  fi;
  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}";
}


########################################################################
# string_replace_all (<string> <regex> <replace>)
#
# Replace <regex> by <replace> in <string>. Interface to `sed s'.
#
# Arguments: 3
#   <regex>: is a BRE like in `sed';
#   <replace>: like the last element in `sed s', honors \1, etc.
#   <string>: no special characters, no restrictions.
#   Do not worry about the address delimiter, the program escapes them.
# Output: the replaced string.
# Return: `1', if no replace; `0' otherwise.
#
string_replace_all()
{
  func_check string_replace_all = 3 "$@";
  string_sed_s "$@" 'g';
  eval "${return_ok}";
}


########################################################################
# string_sed_s (<string> <regex> [<replace> [<flag>]])
#
# Feed command `sed s' independently of delimiter.
#
# Equivalent to:
# echo -n <string> | sed -e '/<regex>/s//<replace>/<flag>';
#
# Arguments: do not worry about the deliniter character `/'.
#            2: <replace>='', <flag>=''
#            3: <flag>=''
#            4: with sed flag, e.g. `g' for global
# Output: the resulting string.
#
string_sed_s()
{
  func_check string_sed_s '>=' 2 "$@";
  local _flag;
  local _regex;
  local _replace;
  local _string;
  case "$#" in
    2)
      _replace='';
      _flag='';
      ;;
    3)
      _replace="$(_string_sed_s_esc_slash "$3")";
      _flag='';
      ;;
    4)
      _replace="$(_string_sed_s_esc_slash "$3")";
      _flag="$4";
      ;;
    *)
      error "string_sed_s() needs 2, 3, or 4 arguments.";
      ;;
  esac;
  _string="$1";
  _regex="$(_string_sed_s_esc_slash "$2")";
  if is_empty "${_string}"; then
    eval "${return_ok}";
  fi;
  if is_empty "${_string}"; then
    error "string_sed_s(): empty regular expression";
  fi;
  echo -n "${_string}" | \
    eval sed -e \'s/"${_regex}"/"${_replace}"/"${_flag}"\';
  eval "${return_ok}";
} # string_sed_s()


_string_sed_s_esc_slash()
{
  # Replace each slash `/' by `\/', but not within `[]'.
  # Global: $_APPEND
  local _beginning;
  local _bracketed;
  local _end;
  local _rest;
  local _result;
  local _str;
#  if test "$#" -ne 1; then
#    error '_string_sed_s_esc_slash() requires 1 argument.';
#  fi;
  if is_empty "$1"; then
    return "${_GOOD}";
  fi;
  if string_not_contains "$1" '/'; then
    echo -n "$1";
    return "${_GOOD}";
  fi;
  _rest="$1""${_APPEND}";
  _result='';
  while true; do
    if string_not_contains "${_rest}" '/'; then
      string_del_append "${_result}${_rest}";
      return "${_GOOD}";
    fi;
    if string_not_contains "${_rest}" '['; then
      string_del_append \
        "${_result}$(_string_sed_s_esc_slash_unbracketed "${_rest}")";
      return "${_GOOD}";
    fi;
    # split at first bracket.
    _beginning="$(echo -n "${_rest}" | sed -e '/^\([^[]*\).*$/s//\1/')";
    _rest="$(echo -n "${_rest}" | sed -e 's/^[^[]*//')";
    if is_not_empty "${_beginning}"; then
      _str="$(_string_sed_s_esc_slash_unbracketed "${_beginning}")";
      _result="${_result}${_str}";
    fi;
    if string_not_contains "${_rest}" '/'; then
      string_del_append "${_result}${_rest}";
      return "${_GOOD}";
    fi;
    case "${_rest}" in
      \[\]*\]*)			# `[]...]' construct
        _bracketed="$(echo -n "${_rest}" | \
                      sed -e 's/^\(\[\][^]]*\]\).*$/\1/')";
        _rest="$(echo -n "${_rest}" | \
                      sed -e 's/^\(\[\][^]]*\]\)\(.*\)$/\2/')";
        ;;
      \[^\]*\]*)		# `[^]...]' construct
        _bracketed="$(echo -n "${_rest}" | \
                      sed -e 's/^\(\[^\][^]]*\]\).*$/\1/')";
        _rest="$(echo -n "${_rest}" | \
                      sed -e 's/^\(\[^\][^]]*\]\)\(.*\)$/\2/')";
        ;;
      \[*\]*)			# `[...]' construct
        _bracketed="$(echo -n "${_rest}" | \
                      sed -e 's/^\(\[[^]]*\]\).*$/\1/')";
        _rest="$(echo -n "${_rest}" | \
                      sed -e 's/^\(\[[^]]*\]\)\(.*\)$/\2/')";
        ;;
      *)
        error \
          '_string_sed_s_esc_slash(): $_rest must start with a bracket';
        ;;
    esac;
    _result="$(string_del_trailing "${_result}${_bracketed}" \
                                   "$_APPEND")";
  done;
  return "${_BAD}";
} # _string_sed_s_esc_slash_line()


_string_sed_s_esc_slash_unbracketed()
{
  # Do the escaping of slashes in strings that do not contain a bracket.
  #
  # Argument: 1, may not contain a `[' nor line breaks.
  # Output:   precede each slash in the argument by a backslash.
  # Return:   1, if argument has a `['; 0 otherwise.
  #
#   if test "$#" -ne 1; then
#    error \
#      '_string_sed_s_esc_slash_unbracketed() needs 1 argument).';
#  fi;
#  if string_contains "$1" '['; then
#    error "_string_sed_s_esc_slash(): no bracket allowed in argument.";
#  fi;
  case "$1" in
    */*)
      echo -n "$1" | sed -e '\|/|s|/|\\/|g';
      return "${_OK}";
      ;;
    *)
      echo -n "$1";
      return "${_OK}";
      ;;
  esac;
} # _string_sed_s_esc_slash_unbracketed()


########################################################################
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;
  _tmp="${_TMP_PREFIX}${_PROCESS_ID}$1";
  echo -n >"${_tmp}";
  echo -n "${_tmp}";
  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 is_yes "${_OPT_LOCATION}"; then
      echo2 "$1";
    fi;
    if is_yes "${_OPT_WHATIS}"; 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}";
}


########################################################################
# usage ()
#
# print usage information to stderr
#
usage()
{
  func_check usage = 0 "$@";
  local _header;
  local _gap;
  _header="Usage: ${_PROGRAM_NAME}";
  _gap="$(string_replace_all "${_header}" '\.' ' ')";
  echo2;
  version;
  cat >&2 <<EOF
Copyright (C) 2001 Free Software Foundation, Inc.
This is free software licensed under the GNU General Public License.

EOF

  echo2 "${_header} [options] [file] [-] [[man:]manpage.x]";
  echo2 "${_gap} [[man:]manpage(x)] [[man:]manpage]...";

  cat >&2 <<EOF

Display roff files, standard input, and/or Unix manual pages with
in a X window viewer or in a text pager.
"-" stands for including standard input.
"manpage" is the name of a man page, "x" its section.
All input is decompressed on-the-fly (by gzip).

-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.

All other short options are interpreted as "groff" formatting
parameters and are transferred unmodified.  The following groff
options imply groff mode (groffer viewing disabled):

-X               display with "gxditview" using groff -X.
-V               display the groff execution pipe instead of formatting.
-Z --ditroff --intermediate-output
                 generate groff intermediate output without 
                 post-processing and viewing like groff -Z.

The most important long options are

--auto-modes=mode1,mode2,...
                 set sequence of automatically tried modes.
--bg             set background color (not for all modes).
--default        reset effects of option processing so far.
--display        set the X device when displaying in X.
--dpi=res        set resolution to "res" ("75" (default) or "100").
--dvi            display in a viewer for TeX device independent format.
--dvi-viewer     choose the viewer program for dvi mode.
--extension=ext  restrict man pages to section suffix.
--fg             set foreground color (not for all modes).
--geometry=geom  set the window size and position when displaying in X.
--groff          process like groff, disable viewing features.
--local-file     same as --no-man.
--locale=lang    preset the language for man pages.
--location       print file locations additionally to standard error.
--man            check file parameters first whether they are man pages.
--mode=auto|dvi|groff|pdf|ps|source|tty|www|x
                 choose display mode.
--no-location    disable former call to "--location".
--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.
--systems=os1,os2,...
                 search man pages for different operating systems.
--title='text'   set the title of the viewer window (not in all modes.
--tty            force paging on text terminal even when in X.
--www            display in a web browser.
--www-viewer     choose the web browser for www mode.
--x              display in an X roff viewer.
--x-viewer       choose viewer program for x mode.
--xrm='resource' set X resouce.

EOF
  eval "${return_ok}";
}


########################################################################
# version ()
#
# print version information to stderr
#
version()
{
  echo2 "${_PROGRAM_NAME} ${_PROGRAM_VERSION} of ${_LAST_UPDATE}";
  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>)
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='^\.[ 	]*';
  echo '.br';
  echo "$1: ";
    echo '.br';
  echo -n '  ';
  _res="$(catz "$1" | sed -e '/'"${_dot}"'TH /p
d')";
  if is_not_empty "${_res}"; then	# traditional man style
    catz "$1" | sed -e '1,/'"${_dot}"'SH/d' \
              | sed -e '1,/'"${_dot}"'SH/p
d' \
              | sed -e '/'"${_dot}"'SH/d';
    eval "${return_ok}";
  fi;
  _res="$(catz "$1" | grep "${_dot}"'Dd ')";
  if is_not_empty "${_res}"; then	# BSD doc style
    catz "$1" |  sed -e  '/'"${_dot}"'Nd /p
d' \
              | sed -e '2q' \
              | sed -e 's/'"${_dot}"'Nd *\(.*\)$/\1/';
    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 is_empty "${_arg}"; 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 clean_up  2>/dev/null || true;

  for f in ${_CONFFILES}; do
    if is_file "$f"; then
      . "$f";
    fi;
  done;

  # determine temporary directory
  for d in "${GROFF_TMPDIR}" "${TMPDIR}" "${TMP}" "${TEMP}" \
           "${TEMPDIR}" "${HOME}"'/tmp' '/tmp' "${HOME}" '.';
  do
    if test "$d" != ""; then
      if test -d "$d" && test -r "$d" && test -w "$d"; then
        _TMP_DIR="$d";
        break;
      fi;
    fi;
  done;
  unset d;
  if test "${_TMP_DIR}" = ""; then
    error "Couldn't find a directory for storing temorary files.";
  fi;
  _TMP_PREFIX="${_TMP_DIR}/${_PROGRAM_NAME}";

  _TMP_CAT="$(tmp_create)";
  _TMP_STDIN="$(tmp_create i)";
  eval "${return_ok}";
} # main_init()


########################################################################
# main_parse_MANOPT ()
#
# Parse $MANOPT.
#
# Globals:
#   in: $MANOPT, $_OPTS_MAN_*
#   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='';
  # feed in $MANOPT
  eval set -- "$(list_from_cmdline \
    "${_OPTS_MAN_SHORT_NA}" "${_OPTS_MAN_SHORT_ARG}" \
    "${_OPTS_MAN_LONG_NA}" "${_OPTS_MAN_LONG_ARG}" \
    "${MANOPT}")";
  until test "$#" -le 0 || is_equal "$1" '--'; do
    _opt="$1";
    shift;
    case "${_opt}" in
      -7|--ascii)
        _list="$(list_append "${_list}" '--ascii')";
        ;;
      -a|--all)
        _list="$(list_append "${_list}" '--all')";
        ;;
      -c|--catman)
        do_nothing;
        shift;
        ;;
      -d|--debug)
        _list="$(list_append "${_list}" '--debug')";
        ;;
      -D|--default)
        # undo all man options so far
        _list='';
        ;;
      -e|--extension)
        _list="$(list_append "${_list}" '--extension')";
        shift;
        ;;
      -f|--whatis)
        _list="$(list_append "${_list}" '--whatis')";
        shift;
        ;;
      -h|--help)
        do_nothing;
        shift;
        ;;
      -k|--apropos)
        _list="$(list_append "${_list}" '--apropos')";
        shift;
        ;;
      -l|--local-file)
        _list="$(list_append "${_list}" '--local-file')";
        ;;
      -L|--locale)
        _list="$(list_append "${_list}" '--locale' "$1")";
        shift;
        ;;
      -m|--systems)
        _list="$(list_append "${_list}" '--systems' "$1")";
        shift;
        ;;
      -M|--manpath)
        _list="$(list_append "${_list}" '--manpath' "$1")";
        shift;
        ;;
      -p|--preprocessor)
        do_nothing;
        shift;
        ;;
      -P|--pager)
        _list="$(list_append "${_list}" '--pager' "$1")";
        shift;
        ;;
      -r|--prompt)
        do_nothing;
        shift;
        ;;
      -S|--sections)
        _list="$(list_append "${_list}" '--sections' "$1")";
        shift;
        ;;
      -t|--troff)
        do_nothing;
        shift;
        ;;
      -T|--device)
        _list="$(list_append "${_list}" '-T' "$1")";
        shift;
        ;;
      -u|--update)
        do_nothing;
        shift;
        ;;
      -V|--version)
        do_nothing;
        ;;
      -w|--where|--location)
        _list="$(list_append "${_list}" '--location')";
        ;;
      -Z|--ditroff)
        _list="$(list_append "${_list}" '-Z' "$1")";
        shift;
        ;;
      # ignore all other options
    esac;
  done;
  GROFFER_OPT="$(list_from_lists "${_list}" "${GROFFER_OPT}")";
  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_SHORT_NA" "$_OPTS_CMDLINE_SHORT_ARG" \
   "$_OPTS_CMDLINE_LONG_NA" "$_OPTS_CMDLINE_LONG_ARG" \
   "$@")";

# 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";
        shift;
        ;;
      -v|--version)
        version;
        leave;
        ;;
      -V)
        _OPT_V='yes';
        ;;
      -X)
        _OPT_X='yes';
        ;;
      -Z|--ditroff|--intermediate-output)
				# groff intermediate output
        _OPT_Z='yes';
        ;;
      -?)
        _optchar="$(string_del_leading "${_opt}" '-')";
        if list_has "${_OPTS_GROFF_SHORT_NA}" "${_optchar}";
        then
          _ADDOPTS_GROFF="$(list_append "${_ADDOPTS_GROFF}" \
                                        "${_opt}")";
        elif list_has "${_OPTS_GROFF_SHORT_ARG}" "${_optchar}";
        then
          _ADDOPTS_GROFF="$(list_append "${_ADDOPTS_GROFF}" \
                                        "${_opt}" "$1")";
           shift;
        else
          error "Unknown option : \`$1'";
        fi;
        ;;
      --all)
          _OPT_ALL="yes";
          ;;
      --ascii)
        _ADDOPTS_GROFF="$(list_append "${_ADDOPTS_GROFF}" \
                                      '-mtty-char')";
        ;;
      --apropos)
        _OPT_APROPOS="yes";
        ;;
      --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)		# sequence of modes in auto mode; arg
        _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';
        ;;
      --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|'')		# the default automatic mode
	    _mode='';
            ;;
          groff)		# pass input to plain groff
            _mode='groff';
            ;;
          dvi)			# display with xdvi viewer
            _mode='dvi';
            ;;
          pdf)			# display with PDF viewer
            _mode='pdf';
            ;;
          ps)			# display with Postscript viewer
            _mode='ps';
            ;;
          X|x)			# output on X roff viewer
            _mode='x';
            ;;
          tty)			# output on terminal
            _mode='tty';
            ;;
          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;
        ;;
      --systems)		# man pages for different OS's, arg
        # argument is a comma-separated list
        _OPT_SYSTEMS="$1";
        shift;
        ;;
      --title)			# title for X viewers; arg
        _OPT_TITLE="$1";
        shift;
        ;;
      --tty)
        _OPT_MODE="tty";
        ;;
      --tty-device)		# device for tty mode; arg
        _OPT_TTY_DEVICE="$1";
        shift;
        ;;
      --whatis)
        _OPT_WHATIS='yes';
        ;;
      --www)			# display with web browser
        _OPT_MODE='www';
        ;;
      --www-viewer)		# viewer program for www mode; arg
        _OPT_VIEWER_WWW="$1";
        shift;
        ;;
      --x)
        _OPT_MODE='x';
        ;;
      --xrm)			# pass X resource string, arg;
        _OPT_XRM="$(list_append "${_OPT_XRM}" "$1")";
        shift;
        ;;
      --x-viewer)		# viewer program for x mode; arg
        _OPT_VIEWER_X="$1";
        shift;
        ;;
      *)
        error "error on argument parsing : \`$*'";
        ;;
    esac;
  done;
  shift;			# remove `--' argument

  if test "${_DEBUG}" != 'yes'; then
    if test "${_OPT_DEBUG}" = 'yes'; then
      _DEBUG='yes';
    fi;
  fi;

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


########################################################################
# 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 is_yes "${_OPT_APROPOS}"; then
    apropos "$@";
    _code="$?";
    clean_up;
    exit "${_code}";
  fi;

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

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

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

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

      if is_empty "${_OPT_DEFAULT_MODES}"; then
        _modes="${_DEFAULT_MODES}";
      else
        _modes="${_OPT_DEFAULT_MODES}";
      fi;
      ;;
    tty)
      _DISPLAY_MODE='tty';
      eval "${return_ok}";
      ;;
    *)				# display mode was given
      if is_empty "${DISPLAY}"; 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
      tty)
        _DISPLAY_MODE='tty';
        eval "${return_ok}";
        ;;
      x)
        if is_not_empty "${_OPT_VIEWER_X}"; then
          _viewers="${_OPT_VIEWER_X}";
        else
          _viewers="${_VIEWER_X}";
        fi;
        _viewer="$(_get_first_prog "${_viewers}")";
        if test "$?" -ne 0; then
          continue;
        fi;
        _DISPLAY_PROG="${_viewer}";
        _DISPLAY_MODE='x';
        eval "${return_ok}";
        ;;
      dvi)
        if is_not_empty "${_OPT_VIEWER_DVI}"; then
          _viewers="${_OPT_VIEWER_DVI}";
        else
          _viewers="${_VIEWER_DVI}";
        fi;
        _viewer="$(_get_first_prog "${_viewers}")";
        if test "$?" -ne 0; then
          continue;
        fi;
        _DISPLAY_PROG="${_viewer}";
        _DISPLAY_MODE="dvi";
        eval "${return_ok}";
        ;;
      pdf)
        if is_not_empty "${_OPT_VIEWER_PDF}"; then
          _viewers="${_OPT_VIEWER_PDF}";
        else
          _viewers="${_VIEWER_PDF}";
        fi;
        _viewer="$(_get_first_prog "${_viewers}")";
        if test "$?" -ne 0; then
          continue;
        fi;
        _DISPLAY_PROG="${_viewer}";
        _DISPLAY_MODE="pdf";
        eval "${return_ok}";
        ;;
      ps)
        if is_not_empty "${_OPT_VIEWER_PS}"; then
          _viewers="${_OPT_VIEWER_PS}";
        else
          _viewers="${_VIEWER_PS}";
        fi;
        _viewer="$(_get_first_prog "${_viewers}")";
        if test "$?" -ne 0; then
          continue;
        fi;
        _DISPLAY_PROG="${_viewer}";
        _DISPLAY_MODE="ps";
        eval "${return_ok}";
        ;;
      www)
        if is_not_empty "${_OPT_VIEWER_WWW}"; then
          _viewers="${_OPT_VIEWER_WWW}";
        else
          _viewers="${_VIEWER_WWW}";
        fi;
        _viewer="$(_get_first_prog "${_viewers}")";
        if test "$?" -ne 0; then
          continue;
        fi;
        _DISPLAY_PROG="${_viewer}";
        _DISPLAY_MODE="www";
        eval "${return_ok}";
        ;;
    esac;
  done;
  error "no suitable display mode found.";
}

_get_first_prog()
{
  local i;
  if test "$#" -eq 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 is_empty "$i"; 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;
  clean_up_secondary;
  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.
#
landmark '18: main_set_resources()';
main_set_resources()
{
  func_check main_set_resources = 0 "$@";
  local _prog;			# viewer program
  local _rl;			# resource list
  _rl='';
  if is_empty "${_DISPLAY_PROG}"; then
    eval "${return_ok}";
  fi;
  set -- ${_DISPLAY_PROG};
  _prog="$(base_name "$1")";
  if is_not_empty "${_OPT_BD}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        _rl="$(list_append "$_rl" '-bd' "${_OPT_BD}")";
        ;;
    esac;
  fi;
  if is_not_empty "${_OPT_BG}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        _rl="$(list_append "$_rl" '-bg' "${_OPT_BG}")";
        ;;
      xpdf)
        _rl="$(list_append "$_rl" '-papercolor' "${_OPT_BG}")";
        ;;
    esac;
  fi;
  if is_not_empty "${_OPT_BW}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        _rl="$(list_append "$_rl" '-bw' "${_OPT_BW}")";
        ;;
    esac;
  fi;
  if is_not_empty "${_OPT_FG}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        _rl="$(list_append "$_rl" '-fg' "${_OPT_FG}")";
        ;;
    esac;
  fi;
  if is_not_empty "${_OPT_FN}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        _rl="$(list_append "$_rl" '-fn' "${_OPT_FN}")";
        ;;
    esac;
  fi;
  if is_not_empty "${_OPT_GEOMETRY}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi|xpdf)
        _rl="$(list_append "$_rl" '-geometry' "${_OPT_GEOMETRY}")";
        ;;
    esac;
  fi;
  if is_empty "${_OPT_RESOLUTION}"; then
    case "${_prog}" in
      gxditview|xditview)
        _rl="$(list_append "$_rl" \
               '-resolution' "${_DEFAULT_RESOLUTION}")";
        ;;
      xpdf)
        case "${_DEFAULT_RESOLUTION}" in
          75)
            _rl="$(list_append "$_rl" '-z' '2')";
            ;;
          100)
            _rl="$(list_append "$_rl" '-z' '3')";
            ;;
        esac;
        ;;
    esac;
  else
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        _rl="$(list_append "$_rl" '-resolution' "${_OPT_RESOLUTION}")";
        ;;
      xpdf)
        case "${_OPT_RESOLUTION}" in
          75)
            _rl="$(list_append "$_rl" '-z' '2')";
            ;;
          100)
            _rl="$(list_append "$_rl" '-z' '3')";
            ;;
        esac;
        ;;
    esac;
  fi;
  if is_not_empty "${_OPT_RV}"; then
    case "${_prog}" in
      ghostview|gv|gxditview|xditview|xdvi)
        _rl="$(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
          _rl="$(list_append "$_rl" '-xrm' "$i")";
        done;
        ;;
    esac;
  fi;
  _title="$(get_first_essential \
                "${_OPT_TITLE}" "${_REGISTERED_TITLE}")";
  if is_not_empty "${_title}"; then
    case "${_prog}" in
      gxditview|xditview)
        _rl="$(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_PREFIX, $_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;

  # Some display programs have trouble with empty input.  
  # This is avoided by feeding a space character in this case.
  if is_empty "$(tmp_cat | sed -e '/./q')"; then
    echo ' ' > "${_TMP_CAT}";
  fi;

  case "${_DISPLAY_MODE}" in
    groff)
      _ADDOPTS_GROFF="${_ADDOPTS_GROFF} ${_ADDOPTS_POST}";
      if is_not_empty "${_OPT_DEVICE}"; then
        _ADDOPTS_GROFF="${_ADDOPTS_GROFF} -T${_OPT_DEVICE}";
      fi;
      _groggy="$(tmp_cat | eval grog "${_options}")";
      trap "" EXIT 2>/dev/null || true;
      # start a new shell program to get another process ID.
      sh -c '
        set -e;
        _PROCESS_ID="$$";
        _modefile="${_TMP_DIR}/${_PROGRAM_NAME}${_PROCESS_ID}";
        rm -f "${_modefile}";
        mv "${_TMP_CAT}" "${_modefile}";
        rm -f "${_TMP_CAT}";
        cat "${_modefile}" | \
        (
          clean_up()
          {
            rm -f "${_modefile}";
          }
          trap clean_up EXIT 2>/dev/null || true;
          eval "${_groggy}" "${_ADDOPTS_GROFF}";
        ) &'
      ;;
    tty)
      case "${_OPT_DEVICE}" in
        '')
          _device="$(get_first_essential \
                     "${_OPT_TTY_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})";
      _pager='';
      for p in "${_OPT_PAGER}" "${PAGER}" "${_MANOPT_PAGER}" \
               'less' 'more' 'cat'; do
        if is_prog "$p"; then
          _pager="$p";
          break;
        fi;
      done;
      if is_empty "${_pager}"; then
        error 'no pager program found for tty mode';
      fi;
      tmp_cat | eval "${_groggy}" "${_addopts}" | \
                eval "${_pager}";
      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;
      ;;
    pdf)
      case "${_OPT_DEVICE}" in
        ''|ps)
          do_nothing;
          ;;
        *)
          warning \
            "wrong device for ${_DISPLAY_MODE} mode: ${_OPT_DEVICE}";
          ;;
      esac;
      _groggy="$(tmp_cat | grog -Tps)";
      trap "" EXIT 2>/dev/null || true;
      # start a new shell program to get another process ID.
      sh -c '
        set -e;
        _PROCESS_ID="$$";
        _psfile="${_TMP_DIR}/${_PROGRAM_NAME}${_PROCESS_ID}";
        _modefile="${_TMP_DIR}/${_PROGRAM_NAME}${_PROCESS_ID}.pdf";
        rm -f "${_psfile}";
        rm -f "${_modefile}";
        cat "${_TMP_CAT}" | \
        eval "${_groggy}" "${_ADDOPTS_GROFF}" > "${_psfile}";
        gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \
           -sOutputFile="${_modefile}" -c save pop -f "${_psfile}";
        rm -f "${_psfile}";
        rm -f "${_TMP_CAT}";
        (
          clean_up()
          {
            rm -f "${_modefile}";
          }
          trap clean_up EXIT 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;
      ;;
    www)
      case "${_OPT_DEVICE}" in
        ''|html) do_nothing; ;;
        *)
          warning \
            "wrong device for ${_DISPLAY_MODE} mode: ${_OPT_DEVICE}";
          ;;
      esac;
      _groggy="$(tmp_cat | grog -Thtml)";
      _do_display;
      ;;
    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()
{
  trap "" EXIT 2>/dev/null || true;
  # start a new shell program to get another process ID.
  sh -c '
    set -e;
    _PROCESS_ID="$$";
    _modefile="${_TMP_DIR}/${_PROGRAM_NAME}${_PROCESS_ID}";
    rm -f "${_modefile}";
    cat "${_TMP_CAT}" | \
      eval "${_groggy}" "${_ADDOPTS_GROFF}" > "${_modefile}";
    rm -f "${_TMP_CAT}";
    (
      clean_up()
      {
        rm -f "${_modefile}";
      }
      trap clean_up EXIT 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 "$@";
