#!/opt/perl/bin/perl -w
# Rough conversion of Apollo fmt files (and maybe "compose" files) to HTML
#
# This code copyright 1999-2003 by
# D. W. Eaton, Artronic Development, Phoenix, AZ -- dwe@arde.com
#
# This software is made freely available under the provisions of the Perl
# "Artistic" license:  http://language.perl.com/misc/Artistic.html
#
# This code is not supported and is not warranteed to perform any particular
# function. Contact dwe@arde.com for aditional information.
# If you find bugs or make enhancements, it would be appreciated if you
# sent them on to the author at dwe@arde.com.
#
# This application is intended to help "bridge" Apollo Domain/OS
# users and documentation to UNIX and the Web.
#
# This code does not handle:
#   control characters other than "."
#   multiple blank lines
#   word vs continuous underline (always continuous)
#   defined macros
#   "source" inputs (includes from other files)
#   page header/footers
#   certain directives within pre-formatted areas
#   right justification
#   column-setting directives
#   tabs and tab characters
#   escape character directive
#   number registers
#   mark characters
#
# Lines which start with whitespace, followed by the single character
# "o", "*", or "-" and more whitespace are "assumed" to be bulleted lists.
# The character noted is removed and replaced with an HTML "<li>".
# If needed, a "<ul>" is supplied. Any line directive is assumed to end
# the list, and a "</ul>" is supplied. The same is true of numbers, but
# "<ol>" is used. If this is not what you wanted, you will need to edit
# the results.
#
# The output may need hand modification to be correct, but it should
# give you a good start.
#
# Logical revision history:
#  1.2 -  DWE Provide options and help
#  1.1 -  DWE improve what gets processed
#  1.0 -  DWE Initial version
#
# Pragmas
use strict;
#
BEGIN
{
 # Could set module libs here, for example:
 # unshift (@INC, '/opt/template/lib/utilities');
}

# Environment
use Getopt::Long;
# In some environments, modules exist to define common items, use it:
##use Constants;

# Define local data
# Variables:
my (
$all,
$arg1,
$bakext,
$cmd,
$defaultSTDOUT,
$doout,
$ercf_noinfile,
$ercf_noinread,
$ercf_noopmake,
$ercf_opexist,
$ercf_ok,
$ercf_user,
$false,
$headnum,
$HTMLheader,
$HTMLtrailer,
$inputext,
$junk,
$line,
$linecnt,
$lineno,
$lines,
$new,
$numargs,
$opt_compose,
$opt_help,
$opt_hidedefine,
$opt_in,
$opt_keep,
$opt_nowrap,
$opt_out,
$opt_outdir,
$opt_quiet,
$opt_replace,
$opt_showerror,
$opt_syntax,
$opt_verbose,
$opt_version,
$opt_veryverbose,
$opt_veryveryverbose,
$opt_wraplen,
$ordlist,
$outleaf,
$outline,
$outlinecnt,
$outpext,
$outpfile,
$plastout,
$pref,
$progname,
$replacedefault,
$status,
$transformspecials,
$true,
$unordlist,
$userid,
$versn,
$wrap_len
);
# Arrays:
my (
@content,
@opts,
@inpfile
);
# Hashes:
my (
%ermcf,
%multilineend,
%opt
);

# These should come from "Constants" utility module:
$true  = 1;  # truth values
$false = 0;
$all   = 2; # for Getopt ignore case
use vars ('$true','$false','$all');
#
# Program configuration 'constants':
$versn = '1.2 - 26 APR 2003';         # Code version and modify date
#
# Exit status codes:
$ercf_ok = 0;$ermcf{$ercf_ok} = 'Exit OK';
$ercf_noinfile = 1;$ermcf{$ercf_noinfile} = 'Can\'t find input file';
$ercf_noinread = 2;$ermcf{$ercf_noinread} = 'Can\'t read input file';
$ercf_noopmake = 3;$ermcf{$ercf_noopmake} = 'Can\'t create new output file';
$ercf_opexist = 4;$ermcf{$ercf_opexist} = 'Output file already exists';
$ercf_user = 99;$ermcf{$ercf_user} = 'User syntax error';
#
$| = $true; # Don't buffer output
#
# Some more-like configuration items:
# Default for the --replace option ($true or $false)
# $false requires the user to supply --replace to overwrite output
# $true always overwrites output without asking for confirm
$replacedefault = $true; # Default for the --replace option
# Default for the --out option ($true or $false)
# $false allows output as described elsewhere
# $true always writes to STDOUT unless user specifies an output
$defaultSTDOUT = $false;
#
$inputext = 'ci'; # Default input name extension for fmt processing
$outpext = 'html'; # Default output name extension
$bakext = 'bak'; # Extension to use for backup of input file (if needed)
#
# Script-specific configuration values:
$wrap_len = 78;  # default max line length
$transformspecials = $true;   # 'true' to do transform of special characters, etc.
# Define stock HTML header info (you will need to edit TITLE after conversion):
$HTMLheader = "<!doctype HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
$HTMLheader .= "<html><head>\n";
$HTMLheader .= "<title>converted</title>\n";
$HTMLheader .= "<meta name=\"Generator\" CONTENT=\"fmt2html $versn\">\n";
$HTMLheader .= "</head>\n";
$HTMLheader .= "<body bgcolor=\"#FFFFFF\" link=\"#0000FF\" vlink=\"#330099\" ";
$HTMLheader .= "alink=\"#FF0000\" text=\"#000000\">\n";
# Define HTML trailer:
$HTMLtrailer = "</body></html>\n";
#
# Constants:
# Define termination to miltiline commands
%multilineend = (
 ".bd", "</strong>",
 ".ce", "</p>",
 "Xol", "<\/ol>",
 "Xul", "<\/ul>",
 ".cu", "<\/u>",
 ".ul", "<\/u>");
# ---- end normal configuration items --------------------
#
# Script-specific initialization:
$plastout = $false; # <p> was the last output if true
$pref = $false;  # Processing preformatted text if true
$ordlist = $false;  # Processing ordered list if true
$unordlist = $false;  # Processing unordered list if true
#
# Try to determine user name
$userid = getlogin || getpwuid ($<) || $ENV{'USER'} || $ENV{'LOGNAME'};

if (! defined ($userid))
{
 die "Can't figure out the user name!\n";
}
#
chomp ($progname = `basename $0`); # Get our leaf name
#
# Process command line
$opt_compose = 'compose';
$opt_help = 'help';
$opt_hidedefine = 'hidedefine';
$opt_in = 'in';
$opt_keep = 'keep';
$opt_nowrap = 'nowrap';
$opt_out = 'out';
$opt_outdir = 'outdir';
$opt_quiet = 'quiet';
$opt_replace = 'replace';
$opt_showerror = 'showerror';
$opt_syntax = 'syntax';
$opt_verbose = 'verbose';
$opt_version = 'version';
$opt_veryverbose = 'veryverbose';
$opt_veryveryverbose = 'veryveryverbose';
$opt_wraplen = 'wraplen';
#
# option defaults
$opt{$opt_compose}     = $false;
$opt{$opt_help}        = $false;
$opt{$opt_hidedefine}  = $false;
$opt{$opt_in}          = '';
$opt{$opt_keep}        = $false;
$opt{$opt_nowrap}      = $false;
$opt{$opt_out}         = '';
$opt{$opt_outdir}      = '';
$opt{$opt_quiet}       = $false;
$opt{$opt_replace}     = $replacedefault;
$opt{$opt_showerror}   = $false;
$opt{$opt_syntax}      = $false;
$opt{$opt_verbose}     = $false;
$opt{$opt_version}     = $false;
$opt{$opt_veryverbose} = $false;
$opt{$opt_veryveryverbose} = $false;
$opt{$opt_wraplen}     = $wrap_len;
#
# Initialize accepted options:
@opts = qw(
  compose|c
  help|h
  hidedefine|hd
  in=s
  keep|k
  nowrap|now
  out=s
  outdir|outd=s
  quiet|q
  replace|r
  showerror|shoerr|se|e
  syntax
  verbose|v
  version
  veryverbose|vv
  veryveryverbose|vvv
  wraplen=s
);
#
# Process command-line
$Getopt::Long::bundling = $true;  # perl 5.003 and earlier will complain about this
$Getopt::Long::ignorecase = $all;
GetOptions (\%opt, @opts);
#
# Check supplied options:
if ($opt{$opt_veryveryverbose})
{
 # Force veryverbose (if it was not already called out)
 $opt{$opt_veryverbose} = $true;
}
#
if ($opt{$opt_veryverbose})
{
 # Force verbose (if it was not already called out)
 $opt{$opt_verbose} = $true;
}
#
$numargs = scalar (@ARGV); # Number of non-option arguments left
if ($opt{$opt_version})
{
 print STDERR "$progname v$versn\n";
 exit ($ercf_user); # Quit after showing version
}
#
if ($opt{$opt_help} || $opt{$opt_syntax})
{
 &syntax_message ();
 exit ($ercf_user);
}
if ($opt{$opt_showerror})
{
 &show_error_codes(%ermcf); # Sho error codes
 exit ($ercf_user);
}
#
# ----------
#
# Check rest of arguments
#
# Look for an input file name
# Get non-option arguments (one at most):
if ($numargs == 1)
{
 if ($opt{$opt_in})
 {
  # Argh, we can't tollerate multiple input designations
  print STDERR "ERROR: use positional or specific input designation, use --$opt_help for assistance\n";
  exit ($ercf_user);
 }
 $arg1 = shift (@ARGV); # Get file ID
 $opt{$opt_in} = $arg1; # Default this as the input
}
elsif ($numargs > 1)
{
 # Too many arguments specified
 print STDERR "ERROR: Too many arguments specified ($numargs), use --$opt_help for assistance\n";
 exit ($ercf_user);
}
#
# Check input file
if ($opt{$opt_in})
{
 # See if the specified input file exists
 if (! -f "$opt{$opt_in}")
 {
  $opt{$opt_in} .= '.' . "$inputext";
  if (!-f "$opt{$opt_in}")
  {
   print STDERR "ERROR: Input file '$opt{$opt_in}' not found, use --$opt_help for assistance\n";
   exit ($ercf_user);
  }
  else
  {
   print "Input file is '$opt{$opt_in}', extension added\n" if ($opt{$opt_verbose});
  }
 }
 else
 {
  print "Input file is '$opt{$opt_in}'\n" if ($opt{$opt_verbose});
 }
}
else
{
 # Cannot continue without some sort of input
 print STDERR "ERROR: No input file has been defined, use --$opt_help for assistance\n";
 exit ($ercf_user);
}
#
# Check output path
if ($opt{$opt_outdir})
{
 # See if the specified output directory exists
 if (! -d "$opt{$opt_outdir}")
 {
  print STDERR "ERROR: Specified output directory not found, use --$opt_help for assistance\n";
  exit ($ercf_user);
 }
 else
 {
  print "Output directory is '$opt{$opt_outdir}'\n" if ($opt{$opt_verbose});
 }
}
else
{
 # default it to current working dir if none
 $opt{$opt_outdir} = '.';
 print "Defaulting output directory to '$opt{$opt_outdir}'\n" if ($opt{$opt_verbose});
}
# Determine output leaf name
if ($opt{$opt_out})
{
 $outleaf = $opt{$opt_out}; # Use what the user supplied
}
else
{
 if ($defaultSTDOUT)
 {
  $opt{$opt_out} = 'STDOUT'; # Force output to STDOUT if so desired
  $outleaf = 'STDOUT';
 }
 else
 {
  # Derive output leaf name from input name
  $outleaf = $opt{$opt_in};
  $outleaf =~ s/^.*\///; # Isolate leaf
  $outleaf =~ s/\.$inputext$//; # Strip off default extension
  if ($outleaf !~ /\.$outpext$/)
  {
   $outleaf .= ".$outpext"; # Add default out extension if not there
  }
 }
 if ($opt{$opt_verbose})
 {
  print "Defaulting output to '$outleaf'\n";
 }
}
#
# Specific initialization following option gathering:
# (add your initialization items here)
#
# - - - - - - - - - - - - - -
# Main body of the code
#
if ($opt{$opt_verbose})
{
 # Show stuff if we want to
}
#
# Do the real work
($status,@inpfile) = &readfile("$opt{$opt_in}"); # Read input file
if ($status)
{
 print STDERR "ERROR: Unable to read input file '$opt{$opt_in}' ($status)\n$!\n";
 exit ($ercf_noinread);
}
#
# - - - input file read - - -
#
# Process file content
$lineno = 0; # No input lines yet
$outlinecnt = 0; # No output lines yet
undef @content; # No output yet
$linecnt = 0; # no multi-lines yet
#
# Put out stock HTML header info (you will need to edit TITLE):
$outlinecnt = &pushout(\@content,$outlinecnt,$HTMLheader); # Add this to output
#
# Spin through each line of the input file:
foreach $line (@inpfile)
{
 chomp ($line);
 $lineno++; # Bump input line number
 $outline = ''; # No output line yet
 # Echo input if we really need to:
 print "[$lineno] $line\n" if ($opt{$opt_veryveryverbose});
 #
 # - - do the work - -
 # If they want to process compose, then get rid of that crud first:

  if ($opt{$opt_compose})
  {
   # Process each line for compose commands first
   $line =~ s/^\.\*/.# /;   # comment
   $line =~ s/^\.alb/.ju/;
   $line =~ s/^\.brb/.brf/i;
   $line =~ s/^\.brf/.br/i;
   $line =~ s/^\.brn\s+\d+/.br/i;
   $line =~ s/^\.brn/.br/i;
   $line =~ s/^\.brp/.bp/i;
   $line =~ s/^\.fin/.fi/i;
   $line =~ s/^\.fif/.nf/i;
   $line =~ s/^\.cb/.# IGNORE: cb/i;
   $line =~ s/^\.un */.ti -/i;
   # (note: .ifi Par handled below if needed)
   $line =~ s/^\.spb/.sp/i;
   $line =~ s/^\.SP/.sp/i;
   $line =~ s/^\.IN/.in/i;
  }

  if ($transformspecials)
  {
   # Process each line
   # Do some "stock" character conversions:
   $line =~ s/\r/\n/g ;   # convert any returns to newlines

   # Protect special HTML characters
   $line =~ s/\&/\&amp;/g ;
   $line =~ s/\</\&lt;/g ;
   $line =~ s/\>/\&gt;/g ;
   $line =~ s/\"/\&quot;/g ;

   # Check for continuation lines:
   if ($linecnt)
   {
    if ($opt{$opt_veryverbose})
    {
     print STDERR "[$lineno]: Multi-lines left for '$cmd' = $linecnt\n";
    }
    $linecnt--;
    if ($linecnt <= 0)
    {
     # if this was end of the counted lines, then drop in end of multiline command
     $line =~ s/$/$multilineend{$cmd}/;
    }
   }

   # HTMLize compose heading commands (or a frequently used fmt extension for heading)
   if ($line =~ /^\.ifi Par/i ||
       $line =~ /^\.par/i)
   {
    if ($line =~ /^\.ifi Par/i)
    {
     $line =~ s/^\.ifi Par\s+//i; # Remove command
    }
    else
    {
     $line =~ s/^\.par\s+//i; # Remove command
    }
    $line =~ s/\s+$//; # Trim trailing spaces
    if ($line =~ /^\&quot;\d/ &&
        $line =~ /\&quot;$/)
    {
     # Must be a heading
     $line =~ s/^\&quot;(.+)\&quot;/$1/; # trim quotes
     $headnum = $line;
     $headnum =~ s/^(\d+).*$/$1/; # Isolate heading number
     $headnum++; # bump it by one (HTML starts at H1, not H0)
     $line =~ s/^(\d+)//; # remove number from old command
     $line = "<h$headnum>$line<\/h$headnum>"; # Build new line
    }
    else
    {
     # damn, some sort of insert
     $line = '<strong>FIX INSERT:</strong> ' . $line; # Add an indicator
    }
   }
   if ($line =~ /^\.srv/)
   {
    if ($opt{$opt_hidedefine})
    {
     $line =~ s/^\.srv/<!-- FIX DEFINED:/; # comment out user define
     $line =~ s/$/ -->/;
    }
    else
    {
     $line =~ s/^\.srv/<strong>FIX DEFINED:<\/strong>/g; # show user define
    }
   }
   $line =~ s/^\.br/<br>/g ;    # Break line
   $line =~ s/^\.ti.*$/<br>/ ;  # Treat temp indent as a break line (ignore distance)
   $line =~ s/^\.bp.*$/<hr>/ ;  # HR for page breaks (ignore next page number field)
   if ($line =~ /^\.sp/)
   {
    $line =~ s/^\.sp.*$/<p>/ ;   # "space n lines" -> para (ignore how many)
    ($unordlist,$ordlist,$line) = &stoplists($unordlist,$ordlist,$line); # Terminate any outstanding lists
   }

   if ($line =~ /^\.in/)
   {
    $line =~ s/^\.in.*$/<p>/ ;   # "temp indent" -> para (ignore how many)
    ($unordlist,$ordlist,$line) = &stoplists($unordlist,$ordlist,$line); # Terminate any outstanding lists
   }

   # Handle pre-formatted text areas
   if ($line =~ /^\.nf/)
   {
    $line =~ s/^\.nf/<pre>/g ; # Preformatted
    $pref = $true;
   }
   if ($line =~ /^\.fif/)
   {
    $line =~ s/^\.fif/<pre>/g ; # Preformatted
    $pref = $true;
   }
   if ($line =~ /^\.fin/)
   {
    $line =~ s/^\.fin/<\/pre>/g ; # End Preformatted
    $pref = $false;
   }
   if ($line =~ /^\.fi/)
   {
    $line =~ s/^\.fi/<\/pre>/g ; # End Preformatted
    $pref = $false;
   }

   # Character "styles"
   $line =~ s/\{\!/<strong>/g;   # Make bold be STRONG
   $line =~ s/\!\}/<\/strong>/g;
   $line =~ s/\{_/<u>/g;         # Make underline
   $line =~ s/_\}/<\/u>/g;

   # Invent paras as needed:
   if (!$pref)
   {
    $line =~ s/^ *$/<p>/;        # Make blank lines into paras
    if ($line eq "<p>")
    {
     ($unordlist,$ordlist,$line) = &stoplists($unordlist,$ordlist,$line); # Terminate any outstanding lists
    }
   }

   # Handle commands that apply to multiple lines:
   if ($line =~ /^\.bd/ ||
       $line =~ /^\.ce/ ||
       $line =~ /^\.cu/ ||
       $line =~ /^\.ul/)
   {
    # Initialize this line count (assumes these are not imbedded)
    ($cmd,$linecnt,$junk) = split (" ",$line,3);
    if (! $linecnt)
    {
     # Default to 1 line
     $linecnt = 1;
    }
    if ($junk)
    {
     # Hmm, what was this stuff?
     print STDERR "[$lineno]: Found '$junk' at end of line\n";
    }
    $line = $cmd;
    $line =~ s/^\.bd/<strong>/g;   # Bold start
    $line =~ s/^\.cu/<u>/g;   # Underline
    $line =~ s/^\.ul/<u>/g;   # Underline words
    if ($pref)
    {
     # Oops, must ignore centering
     $linecnt = 0;
     print STDERR "[$lineno]: Ignoring centering, inside a preformatted block\n"
    }
    else
    {
     if ($line =~ /^\.ce/)
     {
      # Start centering
      $line =~ s/^\.ce/<p align="CENTER">/;   # Center
      ($unordlist,$ordlist,$line) = &stoplists($unordlist,$ordlist,$line); # Terminate any outstanding lists
     }
    }
   }

   if ($line =~ /^\.so/)
   {
    # "source" or included directives are not implemented:
    print STDERR "[$lineno]: ERROR - external file not included:\n";
    print STDERR "  $line\n";
   }

   if (! $pref)
   {
    # Skip this processing if in pre-formatted block:
    if ($line =~ /^\s*[o|-|\*]\s+/)
    {
     # This looks like an "unordered list" ... assume so and deal with it:
     # Swap character for tag:
     $line =~ s/^\s*[o|-|\*]\s+/ <li>/;
     if (! $unordlist)
     {
      # Start the unordered list since we were not doing one:
      $line =~ s/^/<ul>\n/;
      $unordlist = $true;
     }
     if ($ordlist)
     {
      # Terminate open ordered list first:
      $line =~ s/^/<\/ol>\n/;
      $ordlist = $false;
     }
    }
    elsif ($line =~ /^\s+\d+\.*\s+/)
    {
     # This looks like an "ordered list" ... assume so and deal with it:
     # Swap character for tag:
     $line =~ s/^\s+(\d+.*)\s+/ <li><!-- $1 -->/;
     if (! $ordlist)
     {
      # Start the unordered list since we were not doing one:
      $line =~ s/^/<ol>\n/;
      $ordlist = $true;
     }
     if ($unordlist)
     {
      # Terminate open unordered list first:
      $line =~ s/^/<\/ul>\n/;
      $unordlist = $false;
     }
    }
   }

   # Get rid of all other dot commands that we don't recognize and process:
   if ($line =~ /^\./)
   {
    next;
   }
  }

 # See if we should outoput this
 $doout = $true; # Assume we will process normally
 if ($line =~ /<p>$/)
 {
  if ($plastout)
  {
   $doout = $false; # Then don't duplicate another <p>
  }
  $plastout = $true; # Outputting a <p>, try not to do multiples
 }
 else
 {
  $plastout = $false; # Not a <p> being output, always do this
 }
 if ($doout)
 {
  # Build up the new output line:
  $new = $line;
  if (! $opt{$opt_nowrap} && ! $pref)
  {
   # Wrap long lines if not preformatted:
   $new = &word_wrap ($line);
  }
  if ($opt{$opt_veryverbose})
  {
   # Show input line number on lines if debug:
   $new = "[$lineno]: $new";
   $outlinecnt = &pushout(\@content,$outlinecnt,$new); # Add this to output
  }
  else
  {
   if ($pref)
   {
    # If pre-formatted, always showline
    $outlinecnt = &pushout(\@content,$outlinecnt,$new); # Add this to output
   }
   elsif ($new)
   {
    # But normally only show lines with content
    $outlinecnt = &pushout(\@content,$outlinecnt,$new); # Add this to output
   }
  }
 }
 #
 # - - end of the work - -
 #
}

# Put out HTML trailer:
$outlinecnt = &pushout(\@content,$outlinecnt,$HTMLtrailer); # Add this to output

#
# - - - now write output file - - -
#
if (@content)
{
 $outlinecnt = scalar(@content); # Determine lines of output
}
else
{
 $outlinecnt = 0; # no output
}
if ($outlinecnt)
{
 if ($opt{$opt_verbose})
 {
  print "There are $outlinecnt lines of output (not counting multiple head/foot or wrapped lines)\n";
 }
 # Write output
 if ("$opt{$opt_outdir}" eq '.')
 {
  $outpfile = "$outleaf"; # Use simple filename
 }
 else
 {
  $outpfile = "$opt{$opt_outdir}/$outleaf"; # Make outname in its dir
 }
 if (! -f $outpfile ||
     $opt{$opt_replace})
 {
  if ("$opt{$opt_in}" eq "$outpfile")
  {
   # oops, output is the same as the input, protect input against bad writes
   if ($opt{$opt_verbose})
   {
    print "Renaming input '$opt{$opt_in}' to '$opt{$opt_in}.$bakext' temporarily\n";
   }
   rename ("$opt{$opt_in}", "$opt{$opt_in}.$bakext") ;
  }
  ($status,$lines) = &writefile("$outpfile",@content); 
  if (! $status)
  {
   exit ($ercf_noopmake);
  }
  elsif ("$opt{$opt_in}" eq "$outpfile")
  {
   if (! $opt{$opt_keep})
   {
    # Wrote output OK, but had saved input and we don't need to
    if ($opt{$opt_verbose})
    {
     print "Deleting input file backup '$opt{$opt_in}.$bakext'\n";
    }
    unlink ("$opt{$opt_in}.$bakext") ;
   }
   elsif ($opt{$opt_verbose})
   {
    print "Kept input file as '$opt{$opt_in}.$bakext'\n";
   }
  }
 }
 else
 {
  print STDERR "ERROR: Output file '$outpfile' exists, use --$opt_replace to overwrite\n";
  exit ($ercf_opexist);
 }
}
#
# - - - - - - - - - - - - - -
#
exit ($ercf_ok); # Whew, must be OK
###
# - - - - - - - - - - - subroutines - - - - - - -
# Local subroutines
# ----------
# Insert strategically-placed newlines to accomplish word-wrap
# $new = &word_wrap ($message);
sub word_wrap
{
 my ($message) = @_ ;
 my ($start_col, $col, $sep, $lensep) ;

 $start_col = 0 ;
 $message =~ /^([\* \t]*)/ ;
 $sep = "\n$1" ;
 $lensep = length($sep);

 # Strip off all whitespace stuff at end (no need to wrap that)
 $message =~ s/[\s\r\n]*$//;

 if (! $pref)
 {
  $message =~ s/([\.\?])  +/$1 /g; # Shrink multi spaces after sentence to one
 }

 # (if there is a long phrase that can't wrap ... leave it, wrap rest)
 while (($col = $start_col + $opt{$opt_wraplen}) <= length ($message))
 {
  --$col while ($col > $start_col && substr ($message, $col, 1) !~ /\s/) ;
  $col = $start_col + $opt{$opt_wraplen} if $col <= $start_col ;
  substr ($message, $col, 1) = substr ($message, $col, 1) . $sep ;
  $start_col = $col + $lensep;
  if ($opt{$opt_veryverbose})
  {
   print "$col: $message";
  }
 }
 return ($message) ;
}
# - - - - - - - - -
# End any started lists
# ($unordlist,$ordlist,$line) = &stoplists($unordlist,$ordlist,$line);
sub stoplists
{
 my ($unordlist,$ordlist,$line) = @_;

 if ($unordlist)
 {
  # Terminate open unordered list first:
  $line =~ s/^/<\/ul>\n/;
  $unordlist = $false;
 }
 if ($ordlist)
 {
  # Terminate open ordered list first:
  $line =~ s/^/<\/ol>\n/;
  $ordlist = $false;
 }
 return ($unordlist,$ordlist,$line);
}
# ----------
# Standard subroutines
# ----------
# Add string to output array
# $outlinecnt = &pushout(\@content,$outlinecnt,"string");
sub pushout
{
 my ($contref,$outlinecnt,$outline) = @_;

 push (@$contref,$outline); # Add this to the output file
 $outlinecnt++; # Bump output lines
 return ($outlinecnt);
}
# ----------
# Read the designated file into @tmp
# ($status,@tmp) = &readfile("pathname");
# Where $status = 0 if OK
sub readfile
{
 my ($pathname) = @_;
 my (@tmp);

 # Open the input file and read its contents
 if (! open (FN, "<$pathname"))
 {
  print STDERR  "$ermcf{$ercf_user} file \"$pathname\" ($!)-- skipped\n" unless ($opt{$opt_quiet});
  return ($ercf_user,@tmp);
 }
 @tmp = <FN>;
 close (FN);
 # -- all of file read now
 return ($ercf_ok,@tmp);
}
# ----------
#-----------------------
# Write contents to identified file
# (or just write to STDOUT if that is the "$filename")
#  @content = array of the content lines
#  ($status,$lines) = &writefile("$filename",@content);
#  $status  = TRUE if read was good
#  $lines   = number of lines in the results
sub writefile
{
 my ($filename,@content) = @_;
 my ($line,$msg);
 my ($status,$lineno);

 $status = $false;
 $lineno = 0;
 $msg = '';

 if ($filename eq 'STDOUT')
 {
  # Just write this to STDOUT:
  foreach $line (@content)
  {
   chomp ($line);
   print "$line\n";
   $lineno++; # Bump line count
  }
 }
 else
 {
  # Now write the file
  if (!open (WRITEFL, ">$filename"))
  {
   $msg = "ERROR: unable to open $filename\n$!\n";
   print STDERR "$msg\n"; # Dispatch this error
  }
  else
  {
   foreach $line (@content)
   {
    chomp ($line);
    print WRITEFL "$line\n";
    $lineno++; # Bump line count
   }
   close (WRITEFL);
   $status = $true; # Show we had a good write
  }
 }
 return ($status,$lineno);
}
# ----------
# Print syntax message
sub syntax_message
{
 my ($progname);
 my ($key);
 my ($toolid);

 chomp ($progname = `basename $0`);
 if (! $opt{$opt_syntax})
 {
  print STDERR  qq+
Version $versn

Name: $progname - Convert from Apollo "fmt" to HTML
   This program makes a rough attempt to convert an input file in
   Apollo "fmt" word processing format (or possibly in "compose" format)
   to HTML. The conversion is not full and fool-proof, so users probably
   will need to review and tweak the output before publishing the page.
   It also would be a good idea to run the results through an HTML
   validator such as "weblint".+;
 }
 if (! $opt{$opt_syntax})
 {
  print STDERR  qq+

Syntax:

+;
 }
 # Always show this line:
 print STDERR "   $progname  [ options ]  [filename]\n";
 if ($opt{$opt_syntax} && $opt{$opt_verbose})
 {
  print STDERR  qq+
   Operational options:
   --$opt_compose,--$opt_hidedefine,--$opt_in=f,--$opt_keep,--$opt_nowrap,--$opt_out=f,--$opt_outdir=d,--$opt_wraplen=X+;
if (! $replacedefault)
{
  print STDERR  qq+,--$opt_replace+;
}
  print STDERR  qq+
   Assistance options:
   --$opt_help,--$opt_quiet,--$opt_showerror,--$opt_syntax,--$opt_verbose,--$opt_version,--$opt_veryverbose,--$opt_veryveryverbose
+;
 }
 if (! $opt{$opt_syntax})
 {
  print STDERR  qq+
The positional argument 'filename' is optional, and may be replaced with
a specific optional argument below.+;
 }
 if (! $opt{$opt_syntax})
 {
  print STDERR  qq+
If present, the positional argument is assumed to be:

 *  filename from which to read the input data
    If the specified file does not exist and does not end with the default
    extension ".$inputext", then that extension will be added and input
    will be attempted from the resulting extended name

Only one positional argument is permitted.+;
 }
 if (! $opt{$opt_syntax})
 {
  print STDERR  qq+

Where 'options' are:
+;

  print STDERR  qq+
   --$opt_compose, -c
       Process some commands from the "compose" word processing language
       as well as any from the "fmt" word processing language
   --$opt_hidedefine, --hd
       Hide user command definitions encountered in the input file in HTML
       comments (default is to show them in strong font)
   --$opt_in=filename
       Name of the input file, if not provided by a positional argument
   --$opt_keep, -k
       Keep the backed up input file if it was needed.
       If the output file name is the same as the input file name, then the
       input file is saved under the same name, but with extension '.$bakext'
       before the output is written. If the output is written correctly,
       then the backup file is deleted unless option --$opt_keep is specified
   --$opt_nowrap, --now
       No wrapping of output lines
       (default is to wrap lines not in a pre-formatted block of text)
   --$opt_out=filename
       Use this argument as the name of the output file+;
if ($defaultSTDOUT)
{
  print STDERR  qq+
       (if output to the default STDOUT is not desired)+;
}
else
{
  print STDERR  qq+
       (if filename is STDOUT, the output is written to STDOUT, not to a file)
       (default is to use the input file name plus the extension '$outpext')+;
}
  print STDERR  qq+
   --$opt_outdir=directory, --outd=directory
       Place the resulting output file(s) in the specified directory
       (default is to put them into the current working directory)+;
if ($defaultSTDOUT)
{
  print STDERR  qq+
       (requires --$opt_out or output is written to file STDOUT in this dir)+;
}
if (! $replacedefault)
{
 # Only show this if it is not defaulted to "true"
  print STDERR  qq+
   --$opt_replace, -r
       Replace an existing output file with the new data
       (default is to not over write an existing output file)+;
}
  print STDERR  qq+
   --$opt_wraplen=X
       Length of line at which wrapping takes place
       (default is $opt{$opt_wraplen} characters)

  Help and assistance options:

   --$opt_help, -h
       Print this help message
       (use --$opt_verbose or --$opt_veryverbose for extended help)
   --$opt_quiet, -q
       Quiet mode--don't show advisory info or warnings unless verbose
       is also selected (errors still are shown)
   --$opt_showerror, --shoerr, --se, -e
       Show brief descriptions of error exit codes from this routine
   --$opt_syntax
       Only show the syntax line of this help, not all the other info
       (use with --$opt_verbose for slightly more syntax info)
   --$opt_verbose, -v
       Verbose mode, show some diagnostics as well as the input path read
       and the output path written (or show more --help info)
   --$opt_version
       Display the version number of this routine
   --$opt_veryverbose, --vv
       Very verbose mode (implies --$opt_verbose), show more information
       in addition to all --$opt_verbose info and put input line numbers
       on the output lines (to assist with debug)
   --$opt_veryveryverbose, --vvv
       Very very verbose mode (implies --$opt_veryverbose), show more information
       in addition to all --$opt_veryverbose info
+;
 }
 if (! $opt{$opt_syntax})
 {
  print STDERR  qq+
Additional information:

 1) The HTML doccument "title" is set to "converted" and should be set to
    a proper title before posting.
 2) If the input contains a directive to include another file(s), that is
    ignored and simply highlighted in the output. It is the responsibility
    of the user to paste such files into the input before processing.
 3) Incorrect input file tagging may result in an incorrectly tagged HTML
    file. The user should check the results with an HTML validator before
    posting it. The tool "weblint" might suffice, or the W3C validator
    found at http://validator.w3.org/ may be used.
+;
 }

 if ($opt{$opt_veryverbose} && ! $opt{$opt_syntax})
 {
  &show_error_codes(%ermcf); # Sho error codes
 }
 if (! $opt{$opt_syntax})
 {
  print STDERR  qq+
EXAMPLES

 Process input file inputfilename.$inputext and place the results in inputfilename.$outpext
   $progname inputfilename.$inputext
   $progname --in=inputfilename
   $progname inputfilename

 Process input file inputfilename.$inputext and place the results in outfilename.ext
   $progname inputfilename --out=outfilename.ext

 Process input file inputfilename.$inputext, place the results in inputfilename.$outpext,
 and hide visible warnings for user defined commands in the output
   $progname inputfilename.$inputext --$opt_hidedefine
+;
 }
}

# ----------
# Sho error codes
#  &show_error_codes(%ermcf);
# where: %ermcf is a hash of the error exits and reasons
sub show_error_codes
{
 my (%ermcf) = @_;
 my ($key);

 print STDERR  qq+
Error exit codes:
+;
 foreach $key (sort keys %ermcf)
 {
  printf STDERR ("%5s  =  %s\n",$key,$ermcf{$key});
 }
}
# ----------
