#!/usr/bin/perl -w
# #######################################################################
# umenu.pl: Universal Menu System, main program, version 0.7.0
#   Copyright (C) 1999-2004 by Steve Litt (slitt@troubleshooters.com)
#   function getch_linux derived from the perlfunc man page.
#
#   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; version 2 of the License.
#
#   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., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Steve Litt, slitt@troubleshooters.com, http://www.troubleshooters.com
# #######################################################################

# #######################################################################
# #######################################################################
# #######################################################################
# HISTORY
# V0.5 Pre-alpha
#    Implements teletype terminal menu system
#    Immediate reading of user's single keystrokes (no Enter key necessary)
#    Menu-wide conf file determined by env var $UMENU_CONFIG, or if that
#        doesn't exist, defaults to /etc/umenu.conf.
#    Implements following menu-wide conf options:
#        menudir=        #dir containing menu definition files
#        scriptdir=      #writeable dir for temporary script files
#        programdir=     #directory containing umenu runnable code.
#                        #Umenu should be started in the programdir
#    Single menu filename hierarchy naming convention:
#        A given menudir can contain up to 26 main menus, a.mnu thru z.mnu.
#        Submenus of each are named after the letters the user hits to
#          navigate to that menu. In other words, if on the a menu tree
#          the user hits b and d to get to a menu, that menu's filename
#          will be abd.mnu.
#    Implements following per-menu screen properties in menu def file:
#          Menu Screen Title
#    Implements the following per-user-choice properties under each
#      menu letter record in the menu def file. Note that the letter in
#      parens is the column 1 flag -- values begin in column 3:
#          Menu letter (L)
#          Text of user selection (T)
#          Command to be run (see command notes below) (C)
#          Starting directory to switch to before running command (D)
#          prepath, the directories to add to front of path (P)
#          Stop, stop after command completion so user can view result (S)
#          Comments (#)
#    Command Implementation
#         Prompt user for arg: %%Prompt text%% where arg goes in command
#         Value of ^ means go up a level or exit if already at top.
#         Value of ~ means execute as submenu instead of command.
#
#    USES THE FOLLOWING OBJECTS:
#      OS: Interface to OS dependencies: OS.pm
#      CONFIG: Interface to menu system wide configuration as defined
#               by the configuration file at $UMENU_CONFIG or at
#               the default /etc/umenu.conf: CONFIG.pm
#      CHOICE: Interface to a single user choice, as defined by the L
#               flag in the menu definition file: CHOICE.pm
#
#    UNIMPLEMENTED:
#      Environment variable sets before execution
#      Security: System wide in conf file, choice wide in letter
#      Portability, handled by OS object:
#        Directory separator
#        Drives (Dos derived os'es)
#        del vs rm, copy vs cp, $PATH vs %PATH%, etc 
#
#Steve Litt, 2/19/1999
#End of version 0.5
# V0.6.0 Pre-alpha
#    Implements escape to main menu using the "=" keystroke.
#    Correctly implements multiletter root menus
#      = returns you to the root menu, not the single char menu.
#      Quitting from the multicharicter root terminates the menu
#        program, rather than going to a supermenu above the root
#        defined on the command line.
#    Uses the new the UMENUGLOBAL class, which is instantiated once globally
#      to supply access to all needed global variables. This class
#      uses Getopt::Long to recognize the following command line options:
#             --terminate
#             --banneroff
#             --help
#             --diag
#             --license
#
#      --terminate causes UMENU to terminate
#          after running a command, instead of going back to the
#          menu. This option is intended for those wanting to
#          use UMENU as a "Start Menu". 
#      --banneroff removes the GNU GPL banner displayed upon exit from UMENU. 
#      --help provides some limited help for UMENU.
#      --diag runs UMENU in the "diagnostic mode". This
#          option replaces the old "D" second argument of the
#          old UMENU, which no longer works. The
#          "D" as the second arg has been removed, so be
#          sure to use --diag to run in diagnostic mode. 
#      --license will display the GNU GPL license and/or
#          information about the use of that license in this
#          program, but is not implemented yet. 
#
#Steve Litt, 3/4/2002
#End of version 0.6.0
#
# V0.6.1 Pre-alpha
#   Various bug fixes
#   Implements ufork.pl method of spawning GUI programs
#     using the B parameter.
#Steve Litt, 4/6/2002
#End of version 0.6.1
#
# V 0.7.0 Pre-alpha
#   Complete rewrite using Node.pm. The troublesome OS, CONFIG,
#   CHOICE, and UMENUGLOBAL objects have been eliminated, as has
#   the error prone "operating system autodetection". Now the
#   operating system is declared in the configuration file
#   umenu.cnf. Also now declared there is the troublesome
#   getch_style variable.
#
#   This version keeps most information in node trees facilitated
#   by Node.pm, which can be downloaded from
#   http://www.troubleshooters.com/projects/Node/index.htm. The
#   directory containing Node.pm must be assigned to the nodedir
#   variable in the configuration file.
#
#   The automatic recognition of letter Q as an exit has been
#   removed so that Q can be used for actual operations. To error
#   cleanly on older menus that used Q without a C_ line, this
#   version prints the string nop (standing for No Operation)
#   before any choice letter not having a command (C_).
#   
#   Other than the preceding paragraph, this version does not
#   implement any new features. It is simply a rewrite designed to
#   be simpler to maintain, more robust to run, and configurable
#   from the umenu.cnf configuration file, thereby eliminating
#   most user changes to perl code.
#Steve Litt, 10/13/2004
#End of version 0.7.0
#END OF HISTORY
#
#MENU DEFINITION FILE SPECIFICATION:
#  MENU DEFINITION FILE STRUCTURE:
#      Each line is a separate entity.
#      Menu program must be capable of reading a line at least 512 chars long.
#      Column 1 contains a flag giving type of entity
#      Every line pertains to the closest L (letter) line above it.
#      Lines with no L line above it in the file pertains to the submenu.
#      Column 2 is reserved for future use.
#      Column 2 should be ignored by any menu program.
#      For readability it is suggested that column 2 contain an underscore.
#
#  MENU DEFINITION FILE DATA TYPES:
#     Col 1 : Col 3 and beyond
#     L :  Single letter whose choice by user invokes this choice
#            Value 'Q' is hard coded to go up a level or exit top level.
#            Value 'Q' cannot execute a command or submenu.
#            'Q' hardcode to ease of menu create and to promote uniformity.
#            Letter values (col 3) case indy, converted to uppercase.
#              But converted to lowercase for menu file naming.
#     T :  Text to be displayed for this choice, or submenu title if at top.
#     C :  Command, complete with args and %%prompt%% substitutions
#     D :  Directory to switch to before invoking command
#     P :  Prepath: directory list plus ending delimiter prepended to path
#            Value '^' goes up a level or exits top, same as letter val Q
#            Value '~' executes submenu whose final letter is L for this cmd
#     S :  1 to stop after execution, 0 (default) to immediately return to menu.
#     B :  run in Background to disconnect gui app from term that spawned it.
#     # :  Comment, specified to be ignored by this and future menu programs.
#     = :  Equal sign: used by config programs, ignored by all menu programs.
# #######################################################################

use strict;

use Getopt::Long;

# THIS SOURCE FILE IS ORGANIZED INTO THE FOLLOWING CATEGORIES
# IN THE FOLLOWING ORDER:
#
# -----   UTILITIES  -----
# -----   DIAGNOSTICS   -----
# -----   UNIX SPECIFIC ROUTINES  -----
# -----   WINDOWS SPECIFIC ROUTINES   -----
# -----   SYNTAX GUIDANCE ROUTINES   -----
# -----   SCRIPT FILE ROUTINES   -----
# -----   MENU HANDLING ROUTINES   -----
# -----   INITIALIZATION AND FINALIZATION ROUTINES   -----
# -----   MAIN LOGIC ROUTINES   -----


# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----   UTILITIES  -----
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------

# #######################################################################
# getch() implements single keystroke aquisition.
# #######################################################################
sub getch($)
	{
	my $nodeSession = shift;
	if($nodeSession->getAttribute('os') eq 'unix')
		{
		return(getch_unix($nodeSession));
		}
	elsif($nodeSession->getAttribute('os') eq 'windows')
		{
		return(getch_dos($nodeSession));
		}
	else  
		{
		return('0');
		}
	}


# #######################################################################
# Right and left trims all whitespace from a string
# #######################################################################
sub trim($)
	{
	$_[0]=~ m/\s*((\s*\S+)*)\s*/;
	return $1;
	}

# #######################################################################
# Splits a line into the first character, and the string starting at
# the third character, for use parsing UMENU lines.
# #######################################################################
sub splitline($)
	{
	my $line = shift;
	my $firstchar = '#';
	my $value = '';
	unless($line =~ m/^\s*$/)
		{
		$firstchar = uc(substr($line, 0, 1));
		$value = trim(substr($line, 2));
		$value = '' unless defined($value);
		}
	return($firstchar, $value)
	}

# #######################################################################
# Clears the screen. 25 newlines for ultimate portability, reset()
# for speed.
# #######################################################################
sub cls(){print "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";}



# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----   DIAGNOSTICS   -----
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------

# #######################################################################
# Print's attribs of a menu tree
# #######################################################################
sub diaPrintMenuTree($)
	{
	my $nodeMenu = shift;
	my $checker = $nodeMenu;
	diaPrintNodeAttributes($checker);
	$checker = $checker->getFirstChild();
	while(defined($checker))
		{
		diaPrintNodeAttributes($checker);
		if($checker->hasNextSibling())
			{
			$checker = $checker->getNextSibling();
			}
		else
			{
			last;
			}
		}
	}

# #######################################################################
# Print's attribs of a single node
# #######################################################################
sub diaPrintNodeAttributes($)
	{
	my $node = shift;
	my %attribs = $node->getAttributes();
	my @keys = sort keys %attribs;
	print "\nAttributes are:\n";
	foreach my $key (@keys)
		{
		print $key, "<=>", $attribs{$key}, "\n"
		}
	print "\n\n";
			
	}


# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----   UNIX SPECIFIC ROUTINES  -----
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------

# #######################################################################
# Makes script file on Unix systems.
# #######################################################################
sub makeScriptFileUnix($$$)
	{
	my $nodeSession = shift;
	my $nodeMenu = shift;
	my $nodeChoice = shift;
	my $scriptFilename = 'init';

	#### FIND UNIQUE FILENAME AND OPEN FOR OUTPUT ####
	$scriptFilename  = $nodeSession->getAttribute('scriptdir') . 'temp' .
		$nodeMenu->getAttribute('menustring') .
		$nodeChoice->getAttribute('L') . time . ".sh";
	
	open(SCRIPT, '>' . $scriptFilename) or return 'error';

	print SCRIPT "echo \"..... This is script $scriptFilename .....\"\n";
	print "..... This is script $scriptFilename .....\n";

	#### SWITCH TO PROPER DIRECTORY ####
	if($nodeChoice->hasAttribute('D'))
		{
		print SCRIPT "cd ", $nodeChoice->getAttribute('D'), "\n";
		}
	
	#### SET PATH ####
	if($nodeChoice->hasAttribute('P'))
		{
		print SCRIPT "PATH=", $nodeChoice->getAttribute('P');
		print SCRIPT $nodeSession->getAttribute('sep_path');
		print SCRIPT '$PATH', "\n";
		print SCRIPT 'export PATH', "\n";

		}
	
	#### SET ENVIRONMENT VARS ####
	if($nodeChoice->hasAttribute('E'))
		{
		foreach my $env (@{$nodeChoice->getAttribute('E')})
			{
			print SCRIPT $env, "\n";
			$env =~ m/^\s*([^=]+)=/;
			print SCRIPT "export ", $1, "\n";
			}
		}
	
	#### RUN THE COMMAND ####
	if($nodeChoice->hasAttribute('B') && ($nodeChoice->getAttribute('B') != 0))
		{
		print SCRIPT $nodeSession->getAttribute('programdir'), "/ufork.pl ";
		}
	print SCRIPT $nodeChoice->getAttribute('C'), "\n";

	#### RESTORE ORIGINAL ENVIRONMENT ####
	print SCRIPT "cd ", $nodeSession->getAttribute('orgdir'), "\n";

	#### CLOSE AND PREPARE SCRIPT ####
	close SCRIPT;
	chmod 0700, $scriptFilename; 
	
	
	return $scriptFilename;
	}



# #######################################################################
# The bulk of this code was taken from a the PERL man page accessible
# from the perlfunc man page on Redhat 5.1 and 5.2 distros.
# #######################################################################
sub getch_unix($)
	{
	my $nodeSession = shift;
	my($GETCH_STYLE) = $nodeSession->getAttribute('getch_style');
	$GETCH_STYLE = 1 unless defined ($GETCH_STYLE);
	my($key);
	if ($GETCH_STYLE)
		{
		system "stty cbreak </dev/tty >/dev/tty 2>&1";
		}
	else
		{
		system "stty", '-icanon', 'eol', "\001";
		}

	$key = getc(STDIN);

	if ($GETCH_STYLE)
		{
		system "stty -cbreak </dev/tty >/dev/tty 2>&1";
		}
	else
		{
		system "stty", 'icanon', 'eol', '^@'; # ASCII null
		}
	return($key);       
	}


# #######################################################################
# Sets OS dependent attribs on a UNIX, BSD or Linux system
# #######################################################################
sub setAttributesUnix($$)
	{
	my $nodeSession = shift;
	my $confLinesRef = shift;

	$nodeSession->setAttribute('sep_path',':');
	$nodeSession->setAttribute('sep_dir','/');
	$nodeSession->setAttribute('sep_cmd',';');
	$nodeSession->setAttribute('sep_drive','');
	my $pwd = `pwd`;
	chomp($pwd);
	$nodeSession->setAttribute('orgdir', $pwd);
	$nodeSession->setAttribute('programdir', $nodeSession->getAttribute('orgdir'));

	foreach my $confLine (@{$confLinesRef})
		{
		if($confLine =~ m/\s*menudir\s*=\s*(.*?)\s*$/)
			{
			my($temp) = $1;
			if($temp =~ m/(.*)\$HOME(.*)/)
				{
				$nodeSession->setAttribute('menudir', $1 . $ENV{'HOME'} . $2);
				}
			else
				{
				$nodeSession->setAttribute('menudir', $temp);
				}
			}
		if($confLine =~ m/\s*scriptdir\s*=\s*(.*?)\s*$/)
			{
			my($temp) = $1;
			if($temp =~ m/(.*)\$HOME(.*)/)
				{
				$nodeSession->setAttribute('scriptdir', $1 . $ENV{'HOME'} . $2);
				}
			else
				{
				$nodeSession->setAttribute('scriptdir', $temp);
				}
			}
		if($confLine =~ m/\s*programdir\s*=\s*(.*?)\s*$/)
			{
			my($temp) = $1;
			if($temp =~ m/(.*)\$HOME(.*)/)
				{
				$nodeSession->setAttribute('programdir', $1 . $ENV{'HOME'} . $2);
				}
			else
				{
				$nodeSession->setAttribute('programdir', $temp);
				}
			}
		if($confLine =~ m/\s*getch_style\s*=\s*(.*?)\s*$/)
			{
			$nodeSession->setAttribute('getch_style', $1);
			}

		}
	}



# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----   WINDOWS SPECIFIC ROUTINES   -----
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------


# #######################################################################
# getch_dos implements single keystroke aquisition by spawning an executable
# compiled with the following dos C code (compiled with Turbo C++ for Dos 3),
# but probably compilable in any DOS c with a conio.h implementation:
#
# #include<conio.h>
# void main(void)
# {
# printf("%c", getch());
# }
#
# #######################################################################
sub getch_dos($)
	{
# 	my $nodeSession = shift;
# 	return(`getch.exe`);
	}


# #######################################################################
# Makes script file on Windows systems. Stub, not yet written
# #######################################################################
sub makeScriptFileWindows($$$)
	{
	my $nodeSession = shift;
	my $nodeMenu = shift;
	my $nodeChoice = shift;
	my $scriptFilename = 'init';
	return $scriptFilename;
	}

# #######################################################################
# Sets OS dependent attribs on a Windows system
# #######################################################################
sub setAttributesWindows($$)
	{
	my $nodeSession = shift;
	my $confLinesRef = shift;
	}



# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----   SYNTAX GUIDANCE ROUTINES   -----
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------


# #######################################################################
# &help prints a help message.
# #######################################################################
sub help()
	{
	print "\n";
	print "Where rootmenustring is the filename of the desired\n";
	print "root menu file, but without the extension. For\n";
	print "instance, ./umenu.pl xi would present the menu\n";
	print "described by file ui.mnu.\n";
	print "\n";
	print "Options:\n";
	print "	--terminate\n";
	print "		Terminate after call to program, which is\n";
	print "		useful when UMENU is used as a \"start menu\".\n";
	print "	--diag\n";
	print "		Run UMENU in diagnostic mode. This option\n";
	print "		replaces the old \"D\" second argument.\n";
	print "	--banneroff\n";
	print "		Squelches the GNU GPL infobanner that\n";
	print "		normally prints at program termination.\n";
	print "	--help\n";
	print "		Prints usage\n";
	print "	--license\n";
	print "		Not implemented, but will print license\n";
	print "		information when implemented.\n";
	print "\n";
	}

# #######################################################################
# &usage prints a usage message.
# #######################################################################
sub usage()
	{
	#  print "\n\n";
	cls();
	print "Usage: ./umenu.pl [options] rootmenustring\n";
	print "Where rootmenustring is the filename of the desired root menu file,\n";
	print "but without the extension. For instance, ./umenu.pl xi would present\n";
	print "the menu described by file ui.mnu.\n";
	print "\n";
	print "Options:\n";
	print "   --terminate\n";
	print "      Terminate after call to program, which is\n";
	print "      useful when UMENU is used as a \"start menu\".\n";
	print "   --diag\n";
	print "      Run UMENU in diagnostic mode, replaces old \"D\" arg2.\n";
	print "   --banneroff\n";
	print "      Squelches the GNU GPL infobanner that prints at program termination.\n";
	print "   --help\n";
	print "      Prints usage\n";
	print "   --license\n";
	print "      Not implemented, but will print license information when implemented.\n";
	print "\n";
	print "Within UMENU: press a key on the menu to run a command or submenu,\n";
	print "or press = to escape to the root menu.\n";
	print "\n";
	}

# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----   SCRIPT FILE ROUTINES   -----
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------


# #######################################################################
# OS independent script file maker calls OS specific script file maker
# #######################################################################
sub makeScriptFile($$$)
	{
	my $nodeSession = shift;
	my $nodeMenu = shift;
	my $nodeChoice = shift;
	if($nodeSession->getAttribute('os') eq 'unix')
		{
		return makeScriptFileUnix($nodeSession, $nodeMenu, $nodeChoice);
		}
	elsif($nodeSession->getAttribute('os') eq 'linux')
		{
		return makeScriptFileUnix($nodeSession, $nodeMenu, $nodeChoice);
		}
	elsif($nodeSession->getAttribute('os') eq 'bsd')
		{
		return makeScriptFileUnix($nodeSession, $nodeMenu, $nodeChoice);
		}
	elsif($nodeSession->getAttribute('os') eq 'windows')
		{
		return makeScriptFileWindows($nodeSession, $nodeMenu, $nodeChoice);
		}
	}

# #######################################################################
# Runs the script
# #######################################################################
sub runScriptFile($)
	{
	my $scriptFileName = shift;
	system($scriptFileName);
	}

# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----   MENU HANDLING ROUTINES   -----
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------


# #######################################################################
# Returns node of choice matching keystroke
# #######################################################################
sub getChoiceNode($$)
	{
	my $nodeMenu = shift;
	my $keystroke = shift;
	my $nodeChoice = $nodeMenu->getFirstChild();
	while(defined($nodeChoice))
		{
		last if(lc($nodeChoice->getAttribute('L')) eq lc($keystroke));
		$nodeChoice = $nodeChoice->getNextSibling();
		}
	return $nodeChoice;
	}

# #######################################################################
# Displays a menu
# #######################################################################

sub displayMenu($$)
	{
	my $nodeSession = shift;
	my $nodeMenu = shift;
	cls();
	print "             ";
	print "UMENU 0.7.0 menu system, ";
	print "(C) 1999-2004 by Steve Litt.\n";
	print "                         ";
	print "NO WARRANTY! See GPL license.\n\n\n\n";
	print "*****  ", $nodeMenu->getAttribute('T'), "  *****        [";
	print $nodeMenu->getAttribute('menustring'), "]\n\n";

	my $checker = $nodeMenu->getFirstChild();
	while(defined($checker))
		{
		my $command = $checker->getAttribute('C');
		if(!defined $command)
			{
			print "nop  ";
			}
		elsif($command eq '~')
			{
			print "...  ";
			}
		else
			{
			print "     ";
			}
		print $checker->getAttribute('L'), ": ";
		print $checker->getAttribute('T');
		$checker = $checker->getNextSibling();
		print "\n";
		}
	print "\n";
	}


# #######################################################################
# Acquire a keystroke
# #######################################################################
sub acquireKeystroke($$)
	{
	my $nodeSession = shift;
	my $nodeMenu = shift;

	#### DETERMINE ACCEPTABLE CHOICE STRING ####
	my $acceptableChoices = "";
	my $checker = $nodeMenu->getFirstChild();
	while(defined($checker))
		{
		$acceptableChoices .= $checker->getAttribute('L');
		$checker = $checker->getNextSibling();
		}
	$acceptableChoices .= '=';
	
	#### ACQUIRE KEYSTROKE ####
	my $keystroke = '~';
	while(1)
		{
		print "Your choice please==>";
		$keystroke = uc(getch($nodeSession));
		last if(length($keystroke) > 0 && ((index $acceptableChoices, $keystroke) > -1));
		print "                       \r";
		print "($acceptableChoices) \007\007";
		}
	print "                                    \r";
	return lc($keystroke);
	}
	

# #######################################################################
# promptedArgumentSubstitution() prompts the user for any run time args
# defined in the command string, and returns the argument string
# with the run time args filled in.
# #######################################################################
sub promptedArgumentSubstitution($)
	{
	my($argString) = shift;
	unless($argString =~ m/%.%.+%%/) {return($argString);}

	##### IF YOU GOT THIS FAR, THERE ARE ARGS #####
	my(@matches) = ($argString =~ /(%.%.+?%%)/g);
	@matches = sort(@matches);

	my($sub);
	my($match);
	print "\n\n";
	foreach $match (@matches)
		{
		$match =~ m/%.%(.+)%%/;
		print "$1==>";
		$sub = <STDIN>;
		chomp($sub);
		$argString =~ s/$match/$sub/;
		}
	return($argString);  
	}

# #######################################################################
# Parses a UMENU menu definition file into a node tree
# #######################################################################
sub parseMenuFile($$)
	{
	my $nodeSession = shift;
	my $menustring = lc(shift);
	my $menufname = $nodeSession->getAttribute('menudir') . $nodeSession->getAttribute('sep_dir') . $menustring . ".mnu";
	my $nodeMenu = Node->new('menu', 'menu', 'menu');
	$nodeMenu->setAttribute('menustring', $menustring);
	my $flag = '#';
	open FILE, '<' . $menufname or die "Cannot open menu file >$menufname< for read: Terminating...\n";
	my $key;
	my $value;
	my $choiceno = 0;
	my $nodeChoice;

	#### FIND THE FIRST L LINE ####
	while(<FILE>)
		{
		my $line = $_;
		chomp($line);
		($key, $value) = splitline($line);
		last if $key eq 'L';
		if(($key ne '#') && ($key ne ' ') && ($key ne "\t"))
			{
			$nodeMenu->setAttribute($key, $value);
			}
		}

	#### CREATE NODES FOR EACH CHOICE ####
	while(<FILE>)
		{
		if($key eq 'L')
			{
			$choiceno++;
			$nodeChoice = Node->new('choice', 'choice', $choiceno);
			$nodeChoice->setAttribute('L', $value);
			$nodeMenu->insertLastChild($nodeChoice);
			}
		my $line = $_;
		chomp($line);
		($key, $value) = splitline($line);

		unless (($key eq '#') or ($key eq 'L'))
			{
			if($key eq 'C')
				{
				if($nodeChoice->hasAttribute('C'))
					{
					$nodeChoice->setAttribute('C', 
						$nodeChoice->getAttribute('C') .
						" " . $value);
					}
				else
					{
					$nodeChoice->setAttribute('C', $value)
					}

				}
			elsif($key eq 'E')
				{
				unless($nodeChoice->hasAttribute('E'))
					{
					$nodeChoice->setAttribute('E', [])
					}
				push(@{$nodeChoice->getAttribute('E')}, $value);

				}
			else
				{
				$nodeChoice->setAttribute($key, $value);
				}
			}
		}
	close(FILE);
	return($nodeMenu);
	}


# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----   INITIALIZATION AND FINALIZATION ROUTINES   -----
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------



# #######################################################################
# The following BEGIN routine imports Node.pm, looking in the usual
# places after looking at any directories identified in umenu.cnf as
# nodedir=value. Multiple nodedir=value lines are prepended in the
# module path, in the order they appear in the conf file.
# #######################################################################
sub loadNodeModule()
	{
	my($conffile) = $ENV{'UMENU_CONFIG'};
	$conffile = "./umenu.cnf" unless defined($conffile);
	print "Using config file $conffile.\n";
	open CONF, '<' . $conffile or die "FATAL ERROR: Could not open config file $conffile.";
	my @lines = <CONF>;
	close CONF;
	foreach my $line (@lines)
		{
		chomp $line;
		if($line =~ m/^\s*nodedir\s*=\s*([^\s]*)/)
			{
			my $dir = $1;
			if($dir =~ m/(.*)\$HOME(.*)/)
				{
				$dir = $1 . $ENV{'HOME'} . $2;
				}
			push @main::nodedirs, ($dir);
			}
		}
	if(defined(@main::nodedirs))
		{
		unshift @INC, @main::nodedirs;
		}
	require Node;
	import Node;
	}


# #######################################################################
# Initializes the program, mostly by placing config info into
# $nodeSession, which is then passed everywhere.
# #######################################################################
sub initialize()
	{
	my %optctl = ("terminate"=>0, "banneroff"=>0, "license"=>0, "help"=>0, "diag"=>0);
	&GetOptions(\%optctl, "terminate", "banneroff", "license", "help", "diag");

	if(@ARGV != 1) {usage();exit;}
	if($optctl{'help'}) {usage();exit;}

	my $nodeSession = Node->new("session", "session", "session");
	$nodeSession->setAttribute('menustring_org', $ARGV[0]);

	$nodeSession->setAttribute('terminate', $optctl{'terminate'});

	$nodeSession->setAttribute('banneroff', $optctl{'banneroff'});
	$nodeSession->setAttribute('license', $optctl{'license'});
	$nodeSession->setAttribute('diag', $optctl{'diag'});

	##### GET CONFIG INFO, STARTING WITH 
	#### DETERMINE AND STORE CONFIG FILE LOCATION ####
	my($conffile) = $ENV{'UMENU_CONFIG'};
	unless(defined($conffile)) {$conffile = '';}
	unless(length($conffile) > 1) { $conffile = "./umenu.cnf"; }
	print "Using config file $conffile.\n";
	$nodeSession->setAttribute('conffile', $conffile);

	#### LOAD CONFIG FILE INTO LINE ARRAY ####
	open CONF, '<' . $conffile or die "FATAL ERROR: Could not open config file $conffile.";
	my @conflines = <CONF>;
	close CONF;

	#### FIND OPERATING SYSTEM ####
	$nodeSession->setAttribute('os', 'init');
	foreach my $confline (@conflines)
		{
		chomp($confline);
		if($confline =~ m/\s*os\s*=\s*(.*?)\s*$/)
			{
			my $osTemp = lc($1);
			unless(($osTemp eq 'windows') || ($osTemp eq 'linux') || ($osTemp eq 'unix') || ($osTemp eq 'bsd'))
				{
				die "Bad operating system >$osTemp< in conf file $conffile\n";
				}
			unless($nodeSession->getAttribute('os') eq 'init')
				{
				die "ERROR: Multiple OS lines in conf file $conffile\n";
				}
			$osTemp = 'unix' unless($osTemp eq 'windows');
			$nodeSession->setAttribute('os', $osTemp);
			}
		}
	
	if ($nodeSession->getAttribute('os') eq 'init')
		{
		$nodeSession->setAttribute('os', 'unix');
		}

	
	#### SET OTHER CONFIG ATTRIBUTES, ACCORDING TO OS ####
	if($nodeSession->getAttribute('os') eq 'unix')
		{
		setAttributesUnix($nodeSession, \@conflines);
		}
	else
		{
		setAttributesWindows($nodeSession, \@conflines);
		}
  

	#### TEST FOR VIABILITY OF THIS SESSION ####  
	unless(defined($nodeSession->getAttribute('menudir')))
		{
		die "FATAL ERROR: No menu definition directory entry in config file " . $nodeSession->setAttribute('conffile') . ".\n";
		}
	unless(defined($nodeSession->getAttribute('scriptdir')))
		{
		die "FATAL ERROR: No script write directory entry in config file  " . $nodeSession->setAttribute('conffile') . ".\n";
		}
	unless(defined($nodeSession->getAttribute('programdir')))
		{
		die "FATAL ERROR: No UMENU program directory entry in config file "  . $nodeSession->getAttribute('conffile') . ".\n";
		}

	my $fname = $nodeSession->getAttribute('scriptdir') . "test.tst";
	open FILE, '>' . $fname or die "FATAL ERROR: Cannot write test file [$fname].";
	close FILE;
	unlink $fname;
	return $nodeSession;
	}	


# #######################################################################
# printEndingMessage() prints the ending message
# #######################################################################

sub printEndingMessage()
{
print " __________________________________________________________________________\n";
print "| umenu.pl: Universal Menu System, main program, version 0.7.0             |\n";
print "|  Copyright (C) 1999-2004 by Steve Litt (slitt\@troubleshooters.com)       |\n";
print "|  GNU General Public License version 2. NO WARRANTY!                      |\n";
print "|                                                                          |\n";
print "|  You should have received a copy of the GNU General Public License       |\n";
print "|  along with this program; if not, write to the Free Software             |\n";
print "|  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.               |\n";
print "|__________________________________________________________________________|\n";
print "\n\n\n\n\n\n\n\n\n";
print "Thank You for using the Universal Menu System!\n";
print "\n\n\n\n";
}

# #######################################################################
# finalize() wraps up the program
# menu, accepts a user keystroke, and handles the keystroke appropriately.
# #######################################################################
sub finalize($)
	{
	my $nodeSession = shift;
	printEndingMessage() unless ($nodeSession->getAttribute('banneroff'));
	}	

# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----   MAIN LOGIC ROUTINES   -----
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------


# #######################################################################
# This loop repeatedly parses a menu file, displays a menu, displays a
# menu, accepts a user keystroke, and handles the keystroke appropriately.
# #######################################################################
sub mainloop($)
	{

	#### INITIALIZE THE LOOP ####
	my $nodeSession = shift;
	my $menustringOrg = $nodeSession->getAttribute('menustring_org');
	my $menustring = $menustringOrg;

	#### MAIN LOOP -- EACH ITERATION RUNS ONE MENU ####
	while(length($menustringOrg) <= length($menustring))
		{
		#### RUN MENU BASED ON MENU FILE AND ACQUIRE KEYSTROKE ####
		my $nodeMenu = parseMenuFile($nodeSession, $menustring);
		displayMenu($nodeSession, $nodeMenu);
		my $keystroke = acquireKeystroke($nodeSession, $nodeMenu);

		#### HANDLE THE USER'S CHOICE ####
		if($keystroke eq '=')  # User wants to jump to top level
			{
			$menustring = $menustringOrg; # Jump to top level menu
			}
		else
			{
			my $nodeChoice = getChoiceNode($nodeMenu, $keystroke);
			my $command = $nodeChoice->getAttribute('C');

			if($command =~ m/^\^/)   # Choice denotes quit
				{
				$menustring = substr($menustring, 0, -1); # remove last char
				}
			elsif($command =~ m/^~/) # Choice denotes submenu
				{
				$menustring .= $keystroke;  # append submenu's character to menu string
				}
			else        # this is a command to be run
				{
				$nodeChoice->setAttribute('C', 
					promptedArgumentSubstitution($nodeChoice->getAttribute('C'))
					);
				my $scriptFileName = makeScriptFile($nodeSession, $nodeMenu, $nodeChoice);
				runScriptFile($scriptFileName);
				if(($nodeChoice->hasAttribute('S')) && ($nodeChoice->getAttribute('S') ne "0"))
					{
					print "\n";
					print "\n";
					print "Hit any key to return to menu system==>";
					getch($nodeSession);
					print "\n";
					}
				unless (($nodeSession->hasAttribute('diag')) && ($nodeSession->getAttribute('diag') ne "0"))
					{
					unlink $scriptFileName;
					}
				if (($nodeSession->hasAttribute('terminate')) && ($nodeSession->getAttribute('terminate') ne "0"))
					{
					$menustring = "";   #### this terminates the loop, ending the session
					}

				}
			}
		}
	}	


sub main()
	{
	loadNodeModule();
	my $nodeSession = initialize();
	mainloop($nodeSession);
	finalize($nodeSession);
	}

main();
