#! /usr/bin/perl
#
# directomatic Perl Foomatic filter script for spooler-less printing
#
# Copyright 2001 Grant Taylor <gtaylor@picante.com>
#              & Till Kamppeter <till.kamppeter@gmx.net>
#
#  This program 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 of the License, or (at your option)
#  any later version.
#
#  This program 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 this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#

my $configpath = "/etc/foomatic";
my $enscriptcommand = "";
my $execpath = "/usr/local/bin:/usr/bin:/bin";
my $logfile = "/tmp/directomatic.log";
my $debug = 0;

# Read config file if present
%conf = readConfFile("$configpath/filter.conf");

# Determine which filter to use for non-PostScript files to be converted
# to PostScript

my @enscriptcommands =
  ('a2ps -1 @@--medium=@@PAGESIZE@@ @@--center-title=@@JOBTITLE@@ -o - 2>/dev/null',
   'enscript -G @@-M @@PAGESIZE@@ @@-b "Page $%|@@JOBTITLE@@ -p- 2>/dev/null',
   'mpage -o -1 @@-b @@PAGESIZE@@ @@-H -h @@JOBTITLE@@ -P- -');

if (defined(%conf) and $conf{textfilter})
{
    $enscriptcommand = $conf{textfilter};
    $enscriptcommand eq 'a2ps' and $enscriptcommand = $enscriptcommands[0];
    $enscriptcommand eq 'enscript' and $enscriptcommand = $enscriptcommands[1];
    $enscriptcommand eq 'mpage' and $enscriptcommand = $enscriptcommands[2];
}

$ENV{'PATH'} = $execpath;

# Set debug mode
$debug = $conf{debug} if defined(%conf) and defined $conf{debug};

# Where to send debugging log output to
if ($debug) {
    # Grotesquely unsecure; use for debugging only
    open LOG, ">$logfile";
    $logh = *LOG;

    use IO::Handle;
    $logh->autoflush(1);
} else {
    # Through away the debugging info
    open LOG, ">/dev/null";
    $logh = *LOG;
}


# Flush everything immediately.
$|=1;
$SIG{PIPE}='IGNORE';

my $domversion='$Revision: 2.3 $';
#'# Fix emacs syntax highlighting
print $logh "Directomatic backend version $domversion running...\n";
print $logh "$0: called with arguments: '",join("','",@ARGV),"'\n";

# Read the command line arguments

# We use the library Getopt::Long here, so that we can have more than
# one "-o" option on one command line.

use Getopt::Long;
Getopt::Long::Configure("no_ignore_case", "pass_through");
GetOptions("P=s" => \$opt_P,         # which Printer definition file?
           "d=s" => \$opt_d,         # which printer definition file
	                             # (Destination)?
           "o=s" => \@opt_o,         # printing Options
	   "J=s" => \$opt_J);        # Job title

# We get the definition filename as the "-P" or "-d" argument.

if (!defined($opt_P) && (defined($opt_d))) {
    $opt_P = $opt_d;
}

if (!defined($opt_P)) {
    # No printer definition file selected, check whether we have a default
    # printer defined.

    for my $conf_file ((".config",
			"$ENV{'HOME'}/.foomatic/direct/.config",
			"/etc/foomatic/direct/.config")) {
	if (open CONFIG, "< $conf_file") {
	    while (my $line = <CONFIG>) {
		chomp $line;
		if ($line =~ /^default\s*:\s*([^:\s]+)\s*$/) {
		    $opt_P = $1;
		    last;
		}
	    }
	    close CONFIG;
	}
	if (defined($opt_P)) {
	    last;
	}
    }
}

if (!defined($opt_P)) {
    die "No printer definition (option \"-P <name>\") specified!";
}

my $def_file = $opt_P;
my @opts = @opt_o;

my $system_default_path = "/etc/foomatic/direct/";
my $user_default_path = "$ENV{'HOME'}/.foomatic/direct/";

# If the file does not exist or if it is unreadable, try to add an
# extension
if (! -r $def_file) {
    if (-r "${def_file}.foo") { # current dir
	$def_file = "${def_file}.foo";
    } elsif (-r "${def_file}.lom") {
	$def_file = "${def_file}.lom";
    } elsif (-r "$user_default_path${def_file}.foo") { # user dir
	$def_file = "$user_default_path${def_file}.foo";
    } elsif (-r "$user_default_path${def_file}.lom") {
	$def_file = "$user_default_path${def_file}.lom";
    } elsif (-r "$system_default_path${def_file}.foo") { # system dir
	$def_file = "$system_default_path${def_file}.foo";
    } elsif (-r "$system_default_path${def_file}.lom") {
	$def_file = "$system_default_path${def_file}.lom";
    } else {
	die "printer definition file $def_file does not exist or is unreadable!";
    }
}
print $logh "$0: printer def file=$def_file\n";

open PPD, "$def_file" || do {
    print $logh "$0: error opening $def_file.\n";
    die "unable to open printer definition file $def_file";
};
my @datablob = <PPD>;
close PPD;

# Which files do we want to print?
my $filelist;
if ($#ARGV < 0) {
    # No file specified, print fron standard input
    @filelist = ("<STDIN>");
} else {
    @filelist = @ARGV;
}

# Check file list
my $file;
for $file (@filelist) {
    if ($file ne "<STDIN>") {
	if ($file =~ /^-/) {
	    die "Invalid argument: $file";
	} elsif (! -r $file) {
	    die "File $file does not exist/is not readable";
	}
    }
}

# Determine with which command non-PostScript files are converted to PostScript
if ($enscriptcommand eq "") {
    for my $c (@enscriptcommands) {
	($c =~ m/^\s*(\S+)\s+/) || ($c = m/^\s*(\S+)$/);
	$command = $1;
	for (split(':', $ENV{'PATH'})) {
	    if (-x "$_/$command") {
		$enscriptcommand = $c;
		last;
	    }
	}
	if ($enscriptcommand ne "") {
	    last;
	}
    }
    if ($enscriptcommand eq "") {
	$enscriptcommand = "echo \"Cannot convert file to PostScript!\" 1>&2";
    }
}

# From here on we have to repeat all the rest of the program for every file
# to print

for $file (@filelist) {

    print $logh "
================================================

File: $file

================================================

";

    # If we do not print standard input, open the file to print
    if ($file ne "<STDIN>") {
	close STDIN;
	open STDIN, "< $file" ||
	    die "Cannot open $file";
    }

    # OK, we have the datablob
    eval join('',@datablob) || do {
	print $logh "$0: unable to evaluate datablob\n";
	die "error in datablob eval";
    };

    $dat = $VAR1;

    # First, for arguments with a default, stick the default in as the userval.
    for $arg (@{$dat->{'args'}}) {
	if ($arg->{'default'}) {
	    $arg->{'userval'} = $arg->{'default'};
	}
    }



    # Do we get options from within the job postscript?  We might from
    # a classical ppd-grokking postscript generating application (with
    # the # PPD-O-Matic PPD file).  In that case, we should have
    # stuffed # something we can extract into the postscript stream
    # (ie, the # standard PPD mechanism) and parsed it out here.
    # (Structured # comments are probably ideal for this purpose?)
    # When we get there, # be careful to let command-line options
    # override job contents.

    ## Next, examine the postscript job itself for traces of
    ## command-line and pjl options.  Sometimes these don't show up in
    ## the CUPS filter ## 'options' argument!

    # Examination strategy: read some lines from STDIN.  Put those
    # lines onto the stack @examined_stuff, which will be stuffed into
    # # Ghostscript/whatever later on.

    print $logh "Seaerching job for option settings ...\n";
    my $maxlines = 1000;            # how many lines to examine?
    my $more_stuff = 1;             # there is more stuff in stdin.
    my $linect = 0;                 # how many lines have we examined?
    @examined_stuff = ();           # Make sure that there does not
                                    # remain anything from the previous
                                    # file.
    do {
	my $line;
	if ($line=<STDIN>) {
	    if ($linect == 0) {
		# Line zero should be postscript leader
		if ($line !~ m/^.?%!/) { # There can be a Windows control
		                         # char before "%!"
		    # The input file is a text file!
		    $maxlines = 1; # Bail out of the option search loop
		}
	    } else {
		if (($line =~ m/\%\%BeginFeature:\s+\*?([^\s=]+)\s+(\S.*)$/) ||
		    ($line =~ m/\%\%\s*FoomaticOpt:\s*([^\s=]+)\s*=\s*(\S.*)$/)) {
		    my ($option, $value) = ($1, $2);

		    # OK, we have an option.  If it's not a
		    # *ostscript-style option (ie, it's command-line or
		    # PJL) then we should note that fact, since the
		    # attribute-to-filteroption passing in CUPS is kind of
		    # funky, especially wrt boolean options.

		    print $logh "Found: $line";
		    if ($arg=argbyname($option)) {
			print $logh "   Option: $option=$value";
			if ($arg->{'style'} ne 'G') {
			    print $logh " --> Setting option\n";
			    if ($arg->{'type'} eq 'bool') {
				# Boolean options are 1 or 0
				if ($value eq 'True') {
				    $arg->{'userval'} = 1;
				} elsif ($value eq 'False') {
				    $arg->{'userval'} = 0;
				} else {
				    warn "job contained boolean option",
				    " with neither True nor False value!?";
				}
			    } elsif (($arg->{'type'} eq 'enum') ||
				     ($arg->{'type'} eq 'int') ||
				     ($arg->{'type'} eq 'float')) {
				# enum options go as the value, unless
				# they were Unknown...
				# Same with numerical options, they can
				# appear here when the client has used the
				# Adobe-compliant PPD-O-MATIC PPD file.

				if (lc($value) eq 'unknown') {
				    $arg->{'userval'} = undef;
				} else {
				    $arg->{'userval'} = $value;
				}
			    }
			} else {
			    # it is a postscript style option, presuemably
			    # all applied for us and such...
			    print $logh " --> Option will be set by PostScript interpreter\n";
			}
		    } else {
			# This option is unknown to us.  WTF?
			warn "unknown option $option=$value found in the job";
		    }

		}
	    }

	    # Push the line onto the stack for later spitting up...
	    push (@examined_stuff, $line);
	    $linect++;

	} else {
	    # EOF!
	    $more_stuff = 0;
	}

    } while (($linect < $maxlines) and ($more_stuff != 0));

    # Get command line options

    print $logh "$0: options: '", join(" ", @opts), "'\n";

    # Everything below this point was once identical to cupsomatic.
    # Now it's subtly different and mangled.  I really ought to
    # combine # scripts, or modularize, or something...

    optionproc: for (@opts) {
	print $logh "$0: pondering option `$_'\n";

	if (lc($_) eq 'docs') {
	    $do_docs = 1;
	    last;
	}

	my $arg;
	if ((m!(.+)=(.+)!) || (m!(.+):(.+)!)) {
	    # "gpr" separates option and value with a ":" and not with a
	    # "=", seems that it is programmed against a special backend.

	    my ($aname, $avalue) = ($1, $2);

	    # Standard arguments?
	    # media=x,y,z
	    # sides=one|two-sided-long|short-edge

	    # handled by cups for us?
	    # page-ranges=
	    # page-set=
	    # number-up=

	    # brightness= gamma= these probably collide with printer-
	    # specific options.  Hmm.  CUPS has a stupid design for option
	    # handling; everything gets all muddled together.

	    # Rummage around in the media= option for known media, source, etc types.
	    # We ought to do something sensible to make the common manual
	    # boolean option work when specified as a media= tray thing.
	    #
	    # Note that this fails miserably when the option value is in
	    # fact a number; they all look alike.  It's unclear how many
	    # drivers do that.  We may have to standardize the verbose
	    # names to make them work as selections, too.

	    if ($aname =~ m!^media$!i) {
		my @values = split(',',$avalue);
		for (@values) {
		    if ($dat->{'args_byname'}{'PageSize'}
			and $val=valbyname($dat->{'args_byname'}{'PageSize'},$_)) {
			$dat->{'args_byname'}{'PageSize'}{'userval'} =
			    $val->{'value'};
		    } elsif ($dat->{'args_byname'}{'MediaType'}
			     and $val=valbyname($dat->{'args_byname'}{'MediaType'},$_)) {
			$dat->{'args_byname'}{'MediaType'}{'userval'} =
			    $val->{'value'};
		    } elsif ($dat->{'args_byname'}{'InputSlot'}
			     and $val=valbyname($dat->{'args_byname'}{'InputSlot'},$_)) {
			$dat->{'args_byname'}{'InputSlot'}{'userval'} =
			    $val->{'value'};
		    } elsif (lc($_) eq 'manualfeed') {
			# Special case for our typical boolean manual
			# feeder option if we didn't match an InputSlot above
			if (defined($dat->{'args_byname'}{'ManualFeed'})) {
			    $dat->{'args_byname'}{'ManualFeed'}{'userval'} = 1;
			}
		    } else {
			print $logh "$0: unknown media= component $_.\n";
		    }
		}

	    } elsif ($aname =~ m!^sides$!i) {
		# Handle the standard duplex option, mostly
		if ($avalue =~ m!^two-sided!i) {
		    if (defined($dat->{'args_byname'}{'Duplex'})) {
			$dat->{'args_byname'}{'Duplex'}{'userval'} = '1';
		    }
		}

		# We should handle the other half of this option - the
		# BindEdge bit.  Also, are there well-known ipp/cups
		# options for Collate and StapleLocation?  These may be
		# here...

	    } else {
		# Various non-standard printer-specific options
		if ($arg=argbyname($aname)) {
		    $arg->{'userval'} = $avalue;
		} else {
		    print $logh "$0: unknown option $aname\n";
		}
	    }
	} elsif (m!no(.+)!i) {
	    # standard bool args:
	    # landscape; what to do here?
	    # duplex; we should just handle this one OK now?

	    if ($arg=argbyname($1)) {
		$arg->{'userval'} = 0;
	    } else {
		print $logh "$0: unknown option $1\n";
	    }
	} elsif (m!(.+)!) {
	    if ($arg=argbyname($1)) {
		$arg->{'userval'} = 1;
	    } else {
		print $logh "$0: unknown option $1\n";
	    }
	}
    }


    #### Everything below here ought to be generic for any printing
    #### system?  It just uses the $dat structure, with user values
    #### filled in, and turns postscript into printer data.


    # Construct the proper command line.
    $commandline = $dat->{'cmd'};
    @pjlprepend = ();
    @pjlappend = ();
  argument:
    for $arg (sort { $a->{'order'} <=> $b->{'order'} }
              @{$dat->{'args'}}) {

        my $name = $arg->{'name'};
        my $spot = $arg->{'spot'};
        my $varname = $arg->{'varname'};
        my $cmd = $arg->{'proto'};
        my $comment = $arg->{'comment'};
        my $type = $arg->{'type'};
        my $cmdvar = "";
        my $userval = $arg->{'userval'};

        if ($type eq 'bool') {

            # If true, stick the proto into the command line
            if (defined($userval) && $userval == 1) {
                $cmdvar = $cmd;
            }

        } elsif ($type eq 'int' or $type eq 'float') {

            # If defined, process the proto and stick the result into
            # the command line or postscript queue.
            if (defined($userval)) {
                my $min = $arg->{'min'};
                my $max = $arg->{'max'};
                if ($userval >= $min and $userval <= $max) {
		    my $sprintfcmd = $cmd;
		    $sprintfcmd =~ s!\%([^s])!\%\%$1!g;
		    $cmdvar = sprintf($sprintfcmd,
                                      ($type eq 'int'
                                       ? sprintf("%d", $userval)
                                       : sprintf("%f", $userval)));
                } else {
                    print $logh "Value $userval for $name is out of range $min<=x<=$max.\n";
                }
            }

        } elsif ($type eq 'enum') {

            # If defined, stick the selected value into the proto and
            # thence into the commandline
            if (defined($userval)) {
                my $val;
                if ($val=valbyname($arg,$userval)) {
		    my $sprintfcmd = $cmd;
		    $sprintfcmd =~ s!\%([^s])!\%\%$1!g;
		    $cmdvar = sprintf($sprintfcmd,
                                      (defined($val->{'driverval'})
                                       ? $val->{'driverval'}
                                       : $val->{'value'}));
                } else {
                    # User gave unknown value?
                    print $logh "Value $userval for $name is not a valid choice.\n";
                }
            }

        } else {

            print $logh "unknown type for argument $name!?\n";
            # die "evil type!?";

        }

        if ($arg->{'style'} eq 'G') {
            # Place this Postscript command onto the prepend queue.
            push (@prepend, "$cmdvar\n") if $cmdvar;
            print $logh "prepending: $cmdvar\n";

        } elsif ($arg->{'style'} eq 'J') {
	    # put PJL commands onto PJL stack...
	    if (defined($dat->{'pjl'})) {
		push (@pjlprepend, "\@PJL $cmdvar\n") if $cmdvar;
	    }
        } elsif ($arg->{'style'} eq 'C') {
            # command-line argument

            # Insert the processed argument in the commandline
            # just before the spot marker.
            $commandline =~ s!\%$spot!$cmdvar\%$spot!;
        }

    }


    ### Tidy up after computing option statements for all of P, J, and C
    ### types:

    ## C type finishing
    # Pluck out all of the %n's from the command line prototype
    my @letters = qw/A B C D E F G H I J K L M Z/;
    for $spot (@letters) {
	# Remove the letter marker from the commandline
	$commandline =~ s!\%$spot!!;
    }

    ## J type finishing
    # Compute the proper stuff to say around the job
    if (defined($dat->{'pjl'})) {

	# Stick beginning of job cruft on the front of the pjl stuff...
	unshift (@pjlprepend,
		 "\033%-12345X\@PJL JOB NAME=\"DIRECTOMATIC\"\n");

	# Arrange for PJL EOJ command at end of job
	push (@pjlappend,
	      "\33%-12345X\@PJL RESET\n\@PJL EOJ\n");

	print $logh "PJL: ", @pjlprepend, "<job data>\n", @pjlappend;
    }


    # Insert the page size into the $enscriptcommand
    if ($enscriptcommand =~ /\@\@([^@]+)\@\@PAGESIZE\@\@/) {
	my $optstr = ((($arg = argbyname('PageSize')))
		      ? $1 . $arg->{'userval'}
		      : "");
	$enscriptcommand =~ s/\@\@([^@]+)\@\@PAGESIZE\@\@/$optstr/;
    }

    # Insert the job title into the $enscriptcommand
    if ($enscriptcommand =~ /\@\@([^@]+)\@\@JOBTITLE\@\@/) {
	if ($do_docs) {
	    $opt_J = "Documentation for the $dat->{'make'} $dat->{'model'}";
	}
	my $titlearg = $1;
	my ($arg, $optstr);
	($arg = $opt_J) =~ s/\"/\\\"/g;
	if (($titlearg =~ /\"/) || $arg) {
	    $optstr = $titlearg . ($titlearg =~ /\"/ ? '' : '"') .
		($arg ? "$arg\"" : '"');
	} else {
	    $optstr = "";
	}
	$enscriptcommand =~ s/\@\@([^@]+)\@\@JOBTITLE\@\@/$optstr/;
    }

    # Debugging printout of all option values
    if ($debug) {
	for $arg (@{$dat->{'args'}}) {
	    my ($name, $val) = ($arg->{'name'}, $arg->{'userval'});
	    print $logh "Final value for option $name is $val\n";
	}
    }

    # Now print the darned thing!
    if (! $do_docs) {
	# Run the proper command line.
	print $logh "$0: running: $commandline\n";

	# OK.  Examine the input to see if it is text or Postscript
	if ($examined_stuff[0] =~ m/^(.?)%!/) { # optional stupid Windows control-char
	    # The job is Postscript...
	    print $logh "$0: postscript job line1=>$examined_stuff[0]<\n";

	    # get a handle on | commandline | us pjlstuffing | postpipe
	    my ($driverhandle, $driverpid) = getdriverhandle();

	    # Now spew the job into the driver
	    print $driverhandle @examined_stuff;
	    if ($debug != 0) {
		open DRIVERINPUT, "> /tmp/prnjob"
		    or die "error opening /tmp/prnjob";
		print DRIVERINPUT @examined_stuff;
	    }
	    if ($more_stuff) {
		while (<STDIN>) {
		    print $driverhandle $_;
		    if ($debug != 0) {
			print DRIVERINPUT $_;
		    }
		}
	    }

	    print $logh "closing $driverhandle\n";
	    close $driverhandle
		or die "Error closing pipe to $commandline";
	    print $logh "closed $driverhandle\n";
	    if ($debug != 0) {
		close DRIVERINPUT
		    or die "error closing /tmp/prnjob";
	    }

	    # Wait for driver child
	    waitpid($driverpid, 0);
	    print $logh "\nFile $file finished\n\n\n";

	} else {
	    # The job is ascii, we guess.
	    print $logh "$0: ascii job\n";

	    # Implemented:
	    # directomatic | $enscriptcommand | getdriverhandle()..
	    #       KID1^

	    # plus an optional | $postpipe on the end, handled by KID3

	    my $pid, $sleep_count=0;
	    do {
		$pid = open(KID1, "|-");
		unless (defined $pid) {
		    warn "cannot fork: $!";
		    die "bailing out" if $sleep_count++ > 6;
		    sleep 10;
		}
	    } until defined $pid;

	    if ($pid) {
		# parent; write the job data into KID1 aka $enscriptcommand

		print KID1 @examined_stuff;
		#print $logh "printing: @examined_stuff";
		if ($more_stuff) {
		    while (<STDIN>) {
			print KID1 $_;
			#print $logh "printing: $_";
		    }
		}
		close KID1;

		print $logh "root process done writing job data in\n";

		# Wait for enscript child
		waitpid($pid, 0);
		print $logh "\nFile $file finished\n\n\n";

	    } else {

		my ($driverhandle, $driverpid) = getdriverhandle();

		print $logh "setting STDOUT to be $driverhandle and spawning $enscriptcommand\n";

		open (STDOUT, ">&$driverhandle")
		    or die "Couldn't dup driverhandle";
		system "$enscriptcommand"
		    and die "Couldn't exec $enscriptcommand";

		close STDOUT;
		close $driverhandle;

		# Wait for driver child
		waitpid($driverpid, 0);
		print $logh "KID1 finished\n";
		exit(0);
	    }
	}
    } else {
	print $logh "$0: printing docs\n";

	# Redirect STDERR to $logh, so that standard error of GhostScript
	# goes to /dev/null when not in debug mode
	close STDERR;
	open STDERR, ">&$logh" || do {
	    open STDERR;
	    die "Couldn't dup $logh";
	};
	$commandline = "| $enscriptcommand | ( echo \"@prepend\"; cat ) | ( $commandline ) $postpipe";
	# Put STDERR back to its old state
	close STDERR;
	open STDERR;
	print $logh "Piping doc page(s) into: $commandline\n";
	close $logh;
	open PRINTER, $commandline || die "unable to run $commandline";
	select PRINTER;

	my ($make, $model, $driver)
	    = ($dat->{'make'}, $dat->{'model'}, $dat->{'driver'});

	# Read out the program name with which we were called, but discard the path

	$0 =~ m!/([^/]+)\s*$!;
	my $progname = $1;

	print "Invokation summary for your $make $model printer as driven by
the $driver driver.

Specify each option with a -o argument to $progname ie
% $progname -P $opt_P -o duplex -o two=2 -o three=3

The following options are available for this printer:

";

	for $arg (@{$dat->{'args'}}) {
	    my ($name,
		$required,
		$type,
		$comment,
		$spot,
		$default) = ($arg->{'name'},
			     $arg->{'required'},
			     $arg->{'type'},
			     $arg->{'comment'},
			     $arg->{'spot'},
			     $arg->{'default'});

	    my $reqstr = ($required ? " required" : "n optional");
	    print "Option `$name':\n  A$reqstr $type argument.\n  $comment\n";
	    print "  This option corresponds to a PJL command.\n" if ($arg->{'style'} eq 'J');

	    if ($type eq 'bool') {
		if (defined($default)) {
		    my $defstr = ($default ? "True" : "False");
		    print "  Default: $defstr\n";
		}
		print "  Example: `$name'\n";
	    } elsif ($type eq 'enum') {
		print "  Possible choices:\n";
		my $exarg;
		for (@{$arg->{'vals'}}) {
		    my ($choice, $comment) = ($_->{'value'}, $_->{'comment'});
		    print "   o $choice: $comment\n";
		    $exarg=$choice;
		}
		if (defined($default)) {
		    print "  Default: $default\n";
		}
		print "  Example: $name=$exarg\n";
	    } elsif ($type eq 'int' or $type eq 'float') {
		my ($max, $min) = ($arg->{'max'}, $arg->{'min'});
		my $exarg;
		if (defined($max)) {
		    print "  Range: $min <= x <= $max\n";
		    $exarg=$max;
		}
		if (defined($default)) {
		    print "  Default: $default\n";
		    $exarg=$default;
		}
		if (!$exarg) { $exarg=0; }
		print "  Example: $name=$exarg\n";
	    }

	    print "\n";
	}

	select STDOUT;
	close PRINTER;

	# Print the documentation only once, evwn if there are more files
	# specified on the command line
	exit (0);
    }

    close STDIN;

}

# All files printed

exit(0);

# return glob ref to "| commandline | self(pjlstuffer) | $postpipe"
# also return driver pid.  must wait on diver pid
# ugly, we use $commandline, $postpipe, @prepend, @pjlprepend, @pjlappend globals
sub getdriverhandle {

    use IO::Handle;
    pipe KID3_IN, KID3;
    KID3->autoflush(1);

    my $pid3 = fork();
    if (!defined($pid3)) {
        print $logh "$0: cannot fork for kid3!\n";
        die "can't fork for kid3\n";
    }
    if ($pid3) {

        # we are the parent; return a glob to the filehandle
        close KID3_IN;
        print KID3 @prepend;
        print $logh "$0: prepended:\n", @prepend;

        KID3->flush();
        return ( *KID3, $pid3 );

    } else {
        close KID3;

        pipe KID4_IN, KID4;
	KID4->autoflush(1);
        my $pid2 = fork();
        if (!defined($pid2)) {
            print $logh "$0: cannot fork for kid4!\n";
            die "can't fork for kid4\n";
        }

        if ($pid2) {
            # parent, child of primary task; we are |commandline|
            close KID4_IN;

            print $logh "gs  PID pid2=$pid2\n";

            close STDIN                or die "Couldn't close STDIN in $pid2";
            open (STDIN, "<&KID3_IN")  or die "Couldn't dup KID3_IN";
            close STDOUT               or die "Couldn't close STDOUT in $pid2";
            open (STDOUT, ">&KID4")    or die "Couldn't dup KID4";
	    if ($debug) {
		open (STDERR, ">&$logh")
		    or die "Couldn't dup logh to stderr";
	    }

	    # Massage commandline to execute foomatic-gswrapper
	    my $havewrapper = 0;
	    for (split(':', $ENV{'PATH'})) {
		if (-x "$_/foomatic-gswrapper") {
		    $havewrapper=1;
		    last;
		}
	    }
	    if ($havewrapper) {
		$commandline =~ s!^\s*gs !foomatic-gswrapper !;
		$commandline =~ s!(\|\s*)gs !\|foomatic-gswrapper !;
		$commandline =~ s!(;\s*)gs !; foomatic-gswrapper !;
	    }

	    # Redirect STDERR to $logh, so that standard error of
	    # GhostScript goes to /dev/null when not in debug mode
	    close STDERR;
	    open STDERR, ">&$logh" || do {
		open STDERR;
		die "Couldn't dup $logh";
	    };
	    # Actually run the thing...
            if (system "$commandline") {
		open STDERR;
		die "Couldn't exec $commandline";
	    };
	    # Put STDERR back to its old state
	    close STDERR;
	    open STDERR;
	    close STDOUT;
	    close KID4;
	    close STDIN;
	    close KID3_IN;
	    # Wait for postpipe/output child
	    waitpid($pid2, 0);
	    print $logh "KID3 finished\n";
	    exit(0);
        } else {
            # child, trailing task on the pipe; we write pjl stuff
            close KID4;
            close KID3_IN;

            my $fileh = *STDOUT;
            if ($postpipe) {
                open PIPE,$postpipe
                    or "die cannot open postpipe $postpipe";
                $fileh = *PIPE;
            }

            # wrap the PJL around the job data, if there are any
            # options specified...
	    if ( @pjlprepend > 1 ) {
		print $fileh @pjlprepend;
	    }
            while (<KID4_IN>) {
                print $fileh $_;
            }
	    if ( @pjlprepend > 1 ) {
		print $fileh @pjlappend;
	    }

            close $fileh or die "error closing $fileh";
	    close KID4_IN;

            print $logh "tail process done writing data to $fileh\n";

	    print $logh "KID4 finished\n";
	    exit(0);
        }
    }
}

# Find an argument by name in a case-insensitive way
sub argbyname {
    my $name = @_[0];

    my $arg;
    for $arg (@{$dat->{'args'}}) {
        return $arg if (lc($name) eq lc($arg->{'name'}));
    }

    return undef;
}

sub valbyname {
    my ($arg,$name) = @_;

    my $val;
    for $val (@{$arg->{'vals'}}) {
        return $val if (lc($name) eq lc($val->{'value'}));
    }

    return undef;
}

sub readConfFile {
    my ($file) = @_;

    my %conf;
    # Read config file if present
    if (open CONF, "< $file") {
	while (<CONF>)
	{
	    $conf{$1}="$2" if (m/^\s*([^\#\s]\S*)\s*:\s*(.*)\s*$/);
	}
	close CONF;
    }

    return %conf;
}

# Emacs tabulator/indentation

### Local Variables:
### tab-width: 8
### perl-indent-level: 4
### End:
