/*~********************************************************************

File:	Crunch.js, v1.1

Script to crunch HTML/CSS/JS/XML

Note:	CSS and JS can either be in standalone files or embedded within
		HTML. Where HTML contains such embedded CSS and JS, this script
		expects them to be on seperate lines ...
			<script type="text/javascript">
				<!--
					// JS code
				//-->
			</script>
		... not ...
			<script type="text/javascript"><!-- // JS code //--></script>

		IE conditionals hidden in HTML comments, and the opening/closing
		HTML comment tags within <script> or <style> tags, such as those
		above, are all treated like forced comments, and included unless
		respectively -m- -j- -c- command-line switches are used.

Charles Macfarlane Harrison, jj@javajive.macfh.co.uk, May 2010

***********************************************************************

Charles Harrison's Work is licensed under a Creative Commons Licence -
Attribution-Non-Commercial-No Derivative Works 2.0 UK: England & Wales
	http://creativecommons.org/licenses/by-nc-nd/2.0/uk/

For further details, please see:
	http://www.macfh.co.uk/JavaJive/JavaJive.html

***********************************************************************

Updates:
19/10/2010	v1.2
			Bugfixes in JavaScript compression  -  regular expression
			literals and some obscure coding sequences are now handled
			properly.
13/06/2010	v1.1
			Safer Array for() loops if the Array.prototype is altered.

***********************************************************************

This script expects to be run by Windows Scripting Host, CScript.exe
This may not be installed by default on a Windows machine.  To install
WSH (you will likely need administrator rights), see ...
	http://www.microsoft.com/downloads/details.aspx?FamilyId=C717D943-7E4B-4622-86EB-95A22B832CAA

For other OSs or interpreters, search for comments which begin ...
Non WSH use:
... currently there are 13, and make appropriate alterations.

(For best layout Tabs expected every 4 columns)

**********************************************************************/

//	Non WSH use: delete or change appropriately these 6 lines
//	Create normal WSH support objects for the wider purpose of the program
//	var	wshNetwork	= new ActiveXObject( "WScript.Network" );
	var	wshShell	= new ActiveXObject( "WScript.Shell" );
	var	wshFileSys	= new ActiveXObject( "Scripting.FileSystemObject" );
	var	interpreter	= WScript.FullName;			//	The name of the script interpreter
	var	fullScript	= WScript.ScriptFullName;	//	The full path name of the script
	var	thisScript	= WScript.ScriptName;		//	The name of the script

//	Script Data

	//	Force character must not have a special meaning when placed in an RE after any of the comment starts
	var	forceChar	= "~";							//	Forces Crunch to leave a comment intact
	var	HComBeg		= "<!-"+"-";					//	Start of multiline comment in HTML
	var	HComEnd		= "-"+"->";						//	End of multiline comment in HTML
	var	HComBegRE	= new RegExp( "(" + HComBeg + ")", "g" );
	var	HComEndRE	= new RegExp( "(" + HComEnd + ")", "g" );
	var	CComBeg		= "/*";							//	Start of multiline comment in CSS & JS
	var	CComEnd		= "*/";							//	End of multiline comment in CSS & JS
	var	CComBegE	= "/\\*";						//	Start of multiline comment in CSS & JS as RE source
	var	CComEndE	= "\\*/";						//	End of multiline comment in CSS & JS as RE source
	var	CComBegRE	= new RegExp( "(" + CComBegE + ")", "g" );
	var	CComEndRE	= new RegExp( "(" + CComEndE + ")", "g" );
	var	JComBeg		= "//";							//	Start of single line comment in JS
	var	JComBegRE	= new RegExp( "(" + JComBeg + ")", "" );
	var	HStyBeg		= "<"+"style"+"[^>]*>";			//	Start of multiline style tag in HTML
	var	HStyEnd		= "<"+"/style"+">";				//	End of multiline style tag in HTML
	var	HStyBegRE	= new RegExp( HStyBeg,"im" );
	var	HStyEndRE	= new RegExp( HStyEnd,"im" );
	var	HScrBeg		= "<"+"script"+"[^>]*>";		//	Start of multiline script tag in HTML
	var	HScrEnd		= "<"+"/script"+">";			//	End of multiline script tag in HTML
	var	HScrBegRE	= new RegExp( HScrBeg,"im" );
	var	HScrEndRE	= new RegExp( HScrEnd,"im" );
	var	spaceRE		= new RegExp( "\\s+", "gm" );	//	Whitespace
	var	preExpE		= "(?:^|[\\[{(=:+,?!&|]|case|in|return|throw)\\s*";	//	Valid character sequences to precede JS expressions
	var	postExpE	= "\\s*(?:[=:,+.;?&|)}\\]]|in|$)";	//	Valid character sequences to follow JS expressions
	var	sqE			= "'(?:\\\\'|[^'])*'";			//	Single quoted string
	var	dqE			= '"(?:\\\\"|[^"])*"';			//	Double quoted string
	var	JSREE		= "\\/(?!\\*)(?:\\\\/|[^/])+\\/[gim]*";		//	JS	regular expression
	var	CSSPuncsRE	= new RegExp( "\\s*([{}:;,])\\s*", "g" );	//	CSS Punctuators
	var	JSKeysRE	= new RegExp( "\\b(break|case|delete|do|else|function|goto|in|instanceof|new|return|throw|typeof|var|void)\\b", "g" );
	var	JSIds		= "[A-Za-z0-9_$]";				//	Valid characters for JS identifiers
	var	repRE		= new RegExp( "\\$", "g" );		//	Characters needing deactivation in replacement expressions

	var	slcTag		= "'";							//	Tag for a singleline comment
	var	mlcTag		= "\"";							//	Tag for a multiline comment
	var	codTag		= "!";							//	Tag for code
	var	splTag		= "|";							//	Tag for splitting lines

	//	Character sequences that by default force a comment to be left alone
	var	Forcers		=	{
							HTML:	["[if","[IF"],	//	Microsoft's conditional comments are case insensitive
							CSS:	[HComBeg,HComEnd],
							JS:		[HComBeg,HComEnd,"<![CDATA[","]]>"],
							XML:	[]
						};

	var	EOL			= "\n";
	var	CComments	= undefined;
	var	HComments	= undefined;
	var	JComments	= undefined;

//	Global defaults
	var	debug		= false;
	var	overwrite	= false;

//	File data
	var	bakExt		= ".bak";
	var	logExt		= ".log";
	var	forRead		= 1;
	var	forWrite	= 2;
	var	forAppend	= 8;
	var	srcFileName	= null;
	var	dstFileName	= null;
	var	logFileName	= null;

//	==========================================================================
//	Start of processing
//	==========================================================================

//	Process command-line
	// Initialise the object heirarchy
	initObjects();

	var	forcePar	= new KeyStrPar( "F", "-F:<c>\tForce character - if first of comment, leave alone (default " + forceChar + ")" );
	var	CComsPar	= new KeyTogPar( "C", "-C±\tInclude all/no CSS comments (default forced only)" );
	var	HComsPar	= new KeyTogPar( "M", "-M±\tInclude all/no HTML comments (default forced only)" );
	var	JComsPar	= new KeyTogPar( "J", "-J±\tInclude all/no JS comments (default forced only)" );
	var	eolPar		= new KeyTogPar( "L", "-L±\tForce Unix LF (-) or Windows CRLF (+) line ends (default OS determined)" );

	var	debPar		= new KeyTogPar( "D", "-D\tDebug (creates log file with additional output)", false, debug );
	var	ovrPar		= new KeyTogPar( "O", "-O\tOverwrite existing output file without warning (default create *.bak)", false, overwrite );

	var	hlpPar		= new KeyPar	( "{0,2}(\\?|H|Help)?", "?\tShow Help", null, null, null, KeyPar.prototype.tags + "?" );

	var	srcPar		= new FileSpecPar( "<src>\tName of HTML/CSS/JS/XML source file (required)", true );
	var	dstPar		= new FileSpecPar( "<dest>\tDestination file (default backup source file)" );

	var	cmdLine		= new CmdLine(
									[ forcePar, CComsPar, HComsPar, JComsPar, eolPar, debPar, ovrPar, hlpPar, srcPar, dstPar ],
									true,
									null,
									"Crunch.js",
									"Removes formatting whitespace from *.HTML, *.CSS, *.JS, *.XML files\n",
									"Syntax:\n"
										+ "\t<interpreter> Crunch.js [options] <src> [<dest>]\n",
									"Example:\n"
										+ "\tCScript Crunch.js -D widgets.html\n"
								);

//	Non WSH use: change appropriately WScript.Arguments
	if( cmdLine.scan(WScript.Arguments) && !hlpPar.getScand() )
		{
		if( forcePar.getScand() )
			forceChar = forcePar.getValue().charAt(0);

		if( CComsPar.getScand() )
			CComments = CComsPar.getValue();
		if( HComsPar.getScand() )
			HComments = HComsPar.getValue();
		if( JComsPar.getScand() )
			JComments = JComsPar.getValue();

		if( eolPar.getScand() )
			EOL = eolPar.getValue() ? "\x0D\x0A" : "\x0A";

		if( debPar.getScand() )
			debug		= debPar.getValue();
		if( ovrPar.getScand() )
			overwrite	= ovrPar.getValue();

//	Non WSH use: change appropriately wshShell.ExpandEnvironmentStrings (2)
		srcFileName = wshShell.ExpandEnvironmentStrings( srcPar.getValue() );
		if( dstPar.getScand() )
			dstFileName = wshShell.ExpandEnvironmentStrings( dstPar.getValue() );
		else
			dstFileName = srcFileName;

		//	Open the log file
//	Non WSH use: change appropriately wshFileSys.CreateTextFile
		var	logFile = null;
		if( debug)
			{
			logFileName = dstFileName.substr( 0, dstFileName.lastIndexOf(".") ) + logExt;
			logFile = wshFileSys.CreateTextFile( logFileName, true );
			}

		log( CmdLine.prototype.tUndl, logFile );
		log( interpreter + " " + fullScript + " " + cmdLine.getGood(), logFile );
		log( CmdLine.prototype.tUndl + "\n", logFile );

		var	isCSS	= new RegExp( "\\.css$","i" ).test( srcFileName );
		var	isHTML	= new RegExp( "\\.(html|htm)$","i" ).test( srcFileName );
		var	isJS	= new RegExp( "\\.js$","i" ).test( srcFileName );
		var	isXML	= new RegExp( "\\.xml$","i" ).test( srcFileName );
		var	lines	= [], inHCSS = false, inHJS = false, inHComment = false, inCComment = false, isForced = false;

		for( var i in Forcers )
			Forcers[i].push( forceChar );

//	Non WSH use: change appropriately if necessary wshFileSys.OpenTextFile, TextStream.* (2)
		//	Read the source file
		log( "Opening the source file ...", logFile );
		log( "\t\"" + srcFileName + "\"\n", logFile );
		var	srcFile = wshFileSys.OpenTextFile( srcFileName, forRead );
		while( !srcFile.AtEndOfStream )
			{
			var	line = srcFile.ReadLine().trim();
			if( debug )
				log( line, logFile );
			if( line.length )
				{
				var	rnd = "", strings = [], keys = null, re, i, I, j, J, k, l, m, n;
				do
					{
					rnd += String.fromCharCode( 65 + Math.floor(26*Math.random()) );
					re = new RegExp( rnd );
					}
				while( re.test(line) );
				re = new RegExp( preExpE + "(" + sqE + "|" + dqE + (isJS || inHJS ? "|" + JSREE : "") + ")" + postExpE );
				for( j = 0; J = line.match(re); j++ )
					if( J && J[1] )
						{
						strings.push( [rnd + j, J[1]] );
						line = line.replace( strings[j][1], " " + strings[j][0] + " " );
						re.lastIndex = -1;
if( debug )
	{
	log( strings[j][0] + ", \"" + strings[j][1] + "\"", logFile );
	log( line, logFile );
	}
						}
				if( isCSS || inHCSS )
					line = rnd + splTag + (inCComment ? mlcTag : codTag)
							+ line.replace( CComBegRE, " " + rnd + splTag + mlcTag + "$1" )
								.replace( CComEndRE, "$1" + mlcTag + rnd + splTag + codTag )
							+ " ";
				else if( isJS || inHJS )
					{
					for( i = 0, l = false, m = inCComment, n = rnd.length + 2; i < line.length; )
						{
						if( l )
							{
							line = line.substr( 0, i ) + rnd + splTag + slcTag + line.substr( i ) + " ";
							i = line.length;
							}
						else if( m )
							{
							line = line.substr( 0, i ) + rnd + splTag + mlcTag + line.substr( i );
							i += n;
							k = line.substr(i).search( CComEndE );
							if( k < 0 )
								{
								line += " ";
								i = line.length;
								}
							else
								{
								i += k + 2;
								line = line.substr( 0, i ) + mlcTag + line.substr( i );
								i ++;
								m = false;
								}
							}
						else
							{
							line = line.substr( 0, i ) + rnd + splTag + codTag + line.substr( i );
							i += n;
							k = line.substr(i).search( new RegExp("/(?:/|\\*)") );
							if( k < 0 )
								{
								line += " ";
								i = line.length;
								}
							else
								{
								i += k;
								line = line.substr( 0, i ) + " " + line.substr( i );
								i ++;
								if( line.substr(i, 2) == "//" )
									l = true;
								else
									m = true;
								}
							}
						}
					}
				else
					line = rnd + splTag + ((isCSS || inHCSS || isJS || inHJS ? inCComment : inHComment) ? mlcTag : codTag)
							+ line.replace( HComBegRE, " " + rnd + splTag + mlcTag + "$1" )
								.replace( HComEndRE, "$1" + mlcTag + rnd + splTag + codTag )
							+ " ";
				if( debug )
					log( line, logFile );
				line = line.split( rnd + splTag );
				for( i = 0, I = line.length; i < I; i++ )
					if( line[i].trim() )
						{
						var	oTag = line[i].charAt( 0 );
						var	cTag = line[i].charAt( line[i].length - 1 );
						line[i] = line[i].substr( 1, line[i].length - 2 );
						if( debug )
							log(
									"isCSS:" + isCSS
										+ ", isHTML:" + isHTML
										+ ", isJS:" + isJS
										+ ", inHCSS:" + inHCSS
										+ ", inHJS:" + inHJS
										+ ", inHComment:" + inHComment
										+ ", inCComment:" + inCComment
										+ ", isForced:" + isForced
										+ ", oTag:" + oTag
										+ ", cTag:" + cTag
										+ "\n"
									+ line[i],
									logFile
								);
						if( isCSS || inHCSS )
							{
							switch( oTag )
								{
								case mlcTag:	inCComment = cTag != mlcTag;
												line[i] = crunchComment( line[i], isForced ? isForced : CComments, Forcers.CSS, 2 );
												isForced = inCComment && (line[i].length > 0);
												break;
								case codTag:	line[i] = line[i].replace( CSSPuncsRE, "$1" );
												break;
								}
							if( inHCSS )
								{
								if( CComments == false )
									for( j = 0; j < 2; j++ )
										line[i] = line[i].replace( Forcers.CSS[j],"" );
								if( line[i].match(HStyEndRE) )
									{
									inCComment	= false;
									inHCSS	= false;
									}
								}
							}
						else if( isJS || inHJS )
							{
							switch( oTag )
								{
								case mlcTag:	inCComment = cTag != mlcTag;
												//	Deliberate fall through
								case slcTag:	line[i] = crunchComment( line[i], isForced ? isForced : JComments, Forcers.JS, 2 );
												isForced = inCComment&& (line[i].length > 0);
												break;
								case codTag:	{
												keys = line[i].match( JSKeysRE );
												if( keys )
													{
													for( j = 0, J = keys.length; j < J; j++ )
														{
														m = rnd + (strings.length + j);
														do
															{
															n = m + String.fromCharCode( 65 + Math.floor(26*Math.random()) );
															re = new RegExp( n );
															}
														while( re.test(line) );
														keys[j] = [ n, keys[j] ];
														line[i] = line[i].replace( new RegExp("\\b(" + keys[j][1] + ")\\b"), keys[j][0] );
if( debug )
	{
	log( keys[j][0] + ", \"" + keys[j][1] + "\"", logFile );
	log( line, logFile );
	}
														}
													}
												line[i] = line[i].replace( spaceRE, "" );
												if( keys )
													for( j = keys.length - 1; j >= 0; j-- )
														{
														keys[j][1] = keys[j][1].replace( repRE, "$&$&" );
														if( line[i].match(new RegExp(JSIds + keys[j][0] + JSIds)) )
															line[i] = line[i].replace( keys[j][0], " " + keys[j][1] + " " );
														else if( line[i].match(new RegExp(JSIds + keys[j][0])) )
															line[i] = line[i].replace( keys[j][0], " " + keys[j][1] );
														else if( line[i].match(new RegExp(keys[j][0] + JSIds)) )
															line[i] = line[i].replace( keys[j][0], keys[j][1] + " " );
														else
															line[i] = line[i].replace( keys[j][0], keys[j][1] );
														}
												}
												break;
								}
							if( inHJS )
								{
								if( JComments == false )
									for( j = 0; j < 2; j++ )
										line[i] = line[i].replace( Forcers.JS[j],"" );
								if( line[i].match(HScrEndRE) )
									{
									inCComment	= false;
									inHJS	= false;
									}
								}
							}
						else if( isHTML || isXML )
							{
							if( isHTML && !(inHComment && !isForced) )
								{
								if( HStyBegRE.test(line[i]) && !HStyEndRE.test(line[i]) )
									inHCSS = true;
								else if( HScrBegRE.test(line[i]) && !HScrEndRE.test(line[i]) )
									inHJS = true;
								}
							if( !inHCSS && !inHJS )
								switch( oTag )
									{
									case mlcTag:	inHComment = cTag != mlcTag;
													line[i] = crunchComment( line[i], isForced ? isForced : HComments, Forcers.HTML, 4 );
													isForced = inHComment && (line[i].length > 0);
													break;
									case codTag:	line[i] = line[i].replace( spaceRE, " " );
													break;
									}
							}
						}
					line = line.join( "" );
					if( strings )
						for( j = strings.length - 1; j >= 0; j-- )
							line = line.replace( new RegExp("\\s*" + strings[j][0] + "\\s*"), strings[j][1].replace(repRE, "$&$&") );
					line = line.trim();
					if( line.length )
						lines.push( line );
					if( debug )
						log( line, logFile );
					}
			}
//	Non WSH use: change appropriately TextStream.Close
		log( "Closing the source file ...", logFile );
		srcFile.Close();

//	Non WSH use: change appropriately wshFileSys.* (4) and directory seperator
		//	If necessary, back up any existing destination file, which might also be the source file
		if( !overwrite && wshFileSys.FileExists(dstFileName) )
			{
			bakFileName = dstFileName.substr( 0, dstFileName.lastIndexOf(".") ) + bakExt;
			if( wshFileSys.FileExists(bakFileName) )
				{
				if( debug )
					log( "Trying to delete back up file ...\n\t\"" + bakFileName + "\"", logFile );
				wshFileSys.DeleteFile( bakFileName );
				}
			if( debug )
				log( "Trying to back up existing destination file ...\n\t\"" + dstFileName + "\" -> \"" + bakFileName + "\"", logFile );
			dstFile = wshFileSys.GetFile( dstFileName );
			dstFile.Name = bakFileName.substr( dstFileName.lastIndexOf("\\") + 1 );
			}

//	Non WSH use: change appropriately wshFileSys.* (3), TextStream.Close (2), wshShell.Popup
		//	Write the destination file, which might have the same name as the source file
		log( "Creating the destination file ...\n\t\"" + dstFileName + "\"", logFile );
		var	dstFile = null;
		if( wshFileSys.FileExists(dstFileName) && ( overwrite || (wshShell.Popup("Overwrite \"" + dstFileName + "\"?", 0, "File exists!", 33) == 1) ) )
			dstFile = wshFileSys.OpenTextFile( dstFileName, forWrite);
		else
			dstFile = wshFileSys.CreateTextFile( dstFileName, false );

		for( var i = 0, I = lines.length; i < I; i++ )
			writeFile( lines[i], dstFile );

		log( "Closing the destination file ...", logFile );
		dstFile.Close();

		//	Close the log file
		if( logFile )
			logFile.Close();
		}
	else
		log	(
			cmdLine.getHelp()
				+ (cmdLine.getGood() != "" ? "\nCommand-line successfully scanned:\n" + cmdLine.getGood() : "")
				+ (cmdLine.getBad() != "" ? "\nCommand-line not scanned:\n" + cmdLine.getBad() : ""),
			logFile
			);

//	Non WSH use: change appropriately WScript.Quit
	WScript.Quit();


//	Crunch a comment
function crunchComment( comment, status, forcers, index )
	{
	result = comment;
	switch( status )
		{
		case true:		break;

		case undefined:	for( var i = 0, I = forcers.length, found = false; (i < I) && !found; i++ )
							found = comment.substr(index, forcers[i].length) == forcers[i];
						if( found )
							break;
						//	Deliberate fall through
		case false:		result = "";
						break;
		}
	return result;
	}


//	==========================================================================
//	End of processing
//	==========================================================================


//	==========================================================================
//	Start of functions and objects
//	==========================================================================

//	Non WSH use: change appropriately wshFileSys.GetFileName
//	Initialise the command-line object heirarchy
//
//	Note:	MUST be called before creating any *Par (parameter) or CmdLine (command-line) objects.
//
function initObjects()
	{
//	Extend the Array object
	Array.prototype.toString = arrString;

//	Extend the String object
	String.prototype.trim = strTrim;

//	Extend the Number object
	Number.prototype.pad = pad;

	FileSpecPar.prototype	= new Param;
	KeyPar.prototype		= new Param;
	KeyPar.prototype.tags	= "-";				//	Windows users may prefer "-/"
	KeyPar.prototype.cases	= false;
	KeyStrPar.prototype		= new KeyPar;
	KeyLstPar.prototype		= new KeyStrPar;
	KeyTogPar.prototype		= new KeyStrPar;
	KeyTogPar.prototype.vals = "-+01FT";
	KeyIntPar.prototype		= new KeyStrPar;
	KeyFltPar.prototype		= new KeyIntPar;
	CmdLine.prototype.tTitl	= thisScript;
	CmdLine.prototype.tSyn	= "Syntax:\n" + wshFileSys.GetFileName(interpreter) + " " + thisScript + " <parameter(s)>\n";
	CmdLine.prototype.tErr	= "Command-Line Error - Required parameter(s) missing or wrong syntax or value:\n";
	CmdLine.prototype.tUndl = "============================================================================";
	CmdLine.prototype.tPars = "Parameters:";
	}

//	A set of command-line parameters
//
//	Parameters:
//
//		aPars	An array of objects each derived from Param and holding one recognised command-line parameter
//		bHelp	Boolean for whether to give help when errors are encountered scanning script arguments
//		sHelp	Either: A full (console) page of help for the user constructed by the programmer ...
//		... 	Or:		Null, in which case console help will be constructed from the parameters' help and:
//		STitle	Program title
//		sDesc	Program description
//		sSyntax	A line describing the command-line syntax
//		sEg		An example command line
//		sPars	An optional header for the list of help-lines for the individual command-line parameters
//
function CmdLine( aPars, bHelp, sHelp, sTitle, sDesc, sSyntax, sEg, sPars )
	{
	this.pars		= aPars ? aPars : null;
	this.bHelpful	= bHelp ? bHelp : false;
	this.help		= sHelp ? sHelp : null;
	if(	sTitle )
		this.tTitl	= sTitle;
	this.desc		= sDesc ? sDesc : null;
	if(	sSyntax )
		this.tSyn	= sSyntax;
	this.eg			= sEg;
	if(	sPars )
		this.tPars	= sPars;
	this.goodScans	= "";
	this.badScans	= "";
	this.getGood	= gGood;
	this.getBad		= gBad;
	this.scan		= scanCmdLine;
	this.getHelp	= fHelp;
	}

function scanCmdLine( args )
	{
	var	result	= true;
	var	subResult;
	var	msg		= this.tErr;
	for( var a = 0, A = args.length; a < A; a++ )
		{
//	Non WSH use: change appropriately to args[a]
		thisArg	= args(a);
		subResult = false;
		for( var p = 0, P = this.pars.length; p < P; p++ )
			{
			if( debug )
				log( "Parameter " + p + ": " + (this.pars[p].getHelp() ? this.pars[p].getHelp() : "") );
			if( this.pars[p].scan( thisArg ) )
				{
				subResult = true;
				this.goodScans += (this.goodScans != "" ? " " : "") + thisArg;
				if( debug )
					log( "Argument $" + a  + " " + thisArg + " scanned as " + this.pars[p].getValue() );
				break;
				}
			}
		if( (!subResult) && (this.bHelpful || debug) )
			{
			this.badScans += (this.badScans != "" ? " " : "") + thisArg;
			if( debug )
				log( "Argument $" + (a+1)  + " " + thisArg + " not scanned" );
			}
		result &= subResult;
		}
	subResult = true;
	for( p = 0, P = this.pars.length; p < P; p++ )
		if( this.pars[p].getReqd() && !this.pars[p].getScand() )
			{
			msg += this.pars[p].getHelp() + "\n";
			subResult = false;
			}
	if( (!subResult) && (this.bHelpful || debug) )
		log( msg + "\n" );
	result &= subResult;
	return result;
	}

function fHelp()
	{
	var	result = null;
	if( this.help != null )
		result = this.help;
	else
		{
		result = ( this.tTitl ? this.tTitl + "\n" + this.tUndl.substr(0, this.tTitl.length) + "\n\n" : "" )
					+ ( this.desc ? this.desc + "\n" : "" )
					+ ( this.tSyn ? this.tSyn + "\n" : "" )
					+ ( this.eg ? this.eg + "\n" : "" )
					+ ( this.tPars && this.pars && this.pars.length ? this.tPars + "\n" : "" );
		for( var p = 0, P = this.pars.length; p < P; p++ )
			if( this.pars[p].getHelp() )
				result += this.pars[p].getHelp() + "\n";
		}
	return result;
	}

function gGood()
	{
	return this.goodScans;
	}

function gBad()
	{
	return this.badScans;
	}

//	Base object for all command-line parameters
//
//	Parameters:
//
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		oValue	The initial value, defaults to null
//
function Param( sHelp, bReqd, oValue )
	{
	this.help		= sHelp ? sHelp : null;
	this.reqd		= bReqd ? true : false;
	this.scand		= false;
	this.value		= oValue ? oValue : null;
	this.getHelp	= gHelp;
	this.getReqd	= gReqd;
	this.getScand	= gScand;
	this.getValue	= gVal;
	this.setValue	= sVal;
	this.addListen	= aList;
	this.delListen	= dList;
	this.listeners	= new Array(0);
	}

function gHelp()
	{
	return this.help;
	}

function gReqd()
	{
	return this.reqd;
	}

function gScand()
	{
	return this.scand;
	}

function gVal()
	{
	return this.value;
	}

function sVal( nValue )
	{
	var	oValue = this.value;
	this.value = nValue;
	if( nValue != oValue )
		for( var l = 0, L = this.listeners.length; l < L; l++ )
			this.listeners[l]( nValue, oValue );
	return oValue;
	}

function aList( fListener )
	{
	this.listeners.push( fListener );
	}

function dList( fListener )
	{
	for( var l = this.listeners.length - 1; l >= 0; l-- )
		if( this.listeners[l] == fListener )
			this.listeners.splice( l , 1 );
	}

//	A file spec command-line parameter object
//
//	Format:	<filespec>
//
//	Note:	Untagged/unkeyed string parameters such as FileSpecPars must follow all other parameter types
//			in the aPars array passed to the CmdLine constructor.
//
//	Parameters:
//
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		sValue	The initial value, defaults to null
//		bStrict	Whether the parser should perform strict scanning, default is false
//				(would accept legal filenames that look like other parameters, such as -A)
//
function FileSpecPar( sHelp, bReqd, sValue, bStrict )
	{
	//	Object inheritance
	this.base	= Param;
	this.base( sHelp, bReqd, sValue );

	this.strict	= bStrict ? true : false;
	this.scan	= scanFileSpec;
	}

function scanFileSpec( aValue )
	{
	var	result 	= false;
	var	re 		= new RegExp( "[" + KeyPar.prototype.tags + "]" );
	if( !this.scand )
		{
		if( this.strict || !re.test(aValue.substr(0, 1)) )
			{
			this.setValue( aValue );
			this.scand	= true;
			result = true;
			}
		}
	return result;
	}

//	A keyed command-line parameter to toggle (switch or logically NOT) a boolean value
//
//	Format: [-]<key>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		bValue	The initial value, defaults to false
//		bCase	Boolean for whether the key is case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//
function KeyPar( sKey, sHelp, bReqd, bValue, bCase, sTag )
	{
	//	Object inheritance
	this.base	= Param;
	this.base( sHelp, bReqd, bValue ? bValue : false );

	this.key	= sKey;
	if( bCase )
		this.cases = true;
	if( sTag )
		this.tags	= sTag;
	this.scan	= scanKey;
	this.re		= new RegExp("^[" + this.tags + "]" + this.key + "$", this.cases ? "g" : "ig" );
	}

function scanKey( aValue )
	{
	var	result = false;
	if( (!this.scand) && this.re.test(aValue) )
		{
		this.setValue( !this.getValue() );
		this.scand	= true;
		result = true;
		}
	return result;
	}

//	A keyed command-line string parameter
//
//	Format: [-]<key>:<string>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		sValue	The initial value, defaults to null
//		bCase	Boolean for whether the key is case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//
function KeyStrPar( sKey, sHelp, bReqd, sValue, bCase, sTag )
	{
	//	Object inheritance
	this.base	= KeyPar;
	this.base( sKey, sHelp, bReqd, sValue ? sValue : null, bCase, sTag );

	this.scan	= scanKeyStr;
	this.re		= new RegExp("^[" + this.tags + "]" + this.key + ":?(.+)$", this.cases ? "g" : "ig" );
	}

function scanKeyStr( aValue )
	{
	var	result = false;
	if( !this.scand )
		{
		try
			{
			this.setValue( this.re.exec(aValue)[1] );
			this.scand	= true;
			result = true;
			}
		catch(e)
			{
			if( debug )
				log( "scanKeyStr - exception caught: 0x" + ((e.number+0xFFFFFFFF)%0xFFFFFFFF).toString(16).toUpperCase() + "\n\t" + e.description );
			}
		}
	return result;
	}

//	A keyed command-line parameter to set a boolean value
//
//	Format: [-]<key>:<-+01>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		bValue	The initial value, defaults to false
//		bCase	Boolean for whether the key and the toggle characters are case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//		sVals	String of allowable toggle characters, should be in pairs "<False><True>", as in default "-+01FT"
//
function KeyTogPar( sKey, sHelp, bReqd, bValue, bCase, sTag, sVals )
	{
	//	Object inheritance
	this.base	= KeyStrPar;
	this.base( sKey, sHelp, bReqd, bValue ? bValue : false, bCase, sTag );

	if( sVals )
		this.vals = sVals;
	this.scan	= scanKeyTog;
	this.re		= new RegExp("^[" + this.tags + "]" + this.key + ":?([" + this.vals + "])?$", this.cases ? "g" : "ig" );
	}

function scanKeyTog( aValue )
	{
	var	result = false;
	if( !this.scand )
		{
		try
			{
			var	tog = this.re.exec(aValue);
			if( tog != null )
				{
				tog = tog[tog.length - 1];
				switch( tog ? this.vals.indexOf( this.cases ? tog : tog.toUpperCase() ) % 2 : -1 )
					{
					case -1:	this.setValue( !this.getValue() );
								this.scand	= true;
								result = true;
								break;

					case 0:		this.setValue( false );
								this.scand	= true;
								result = true;
								break;

					case 1:		this.setValue( true );
								this.scand	= true;
								result = true;
								break;

					default:	break;
					}
				}
			}
		catch(e)
			{
			if( debug )
				log( "scanKeyTog - exception caught: 0x" + ((e.number+0xFFFFFFFF)%0xFFFFFFFF).toString(16).toUpperCase() + "\n\t" + e.description );
			}
		}
	return result;
	}

//	A keyed command-line parameter to choose from a list of allowed strings
//
//	Format: [-]<key>:<string>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		sVals	Array of allowed string choices
//		bReqd	Boolean for whether the parameter must be present, default is false
//		bValue	The initial value, defaults to false
//		bCase	Boolean for whether the key and the string values are case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//
function KeyLstPar( sKey, sHelp, sVals, bReqd, bValue, bCase, sTag )
	{
	//	Object inheritance
	this.base	= KeyStrPar;
	this.base( sKey, sHelp, bReqd, bValue ? bValue : false, bCase, sTag );

	if( sVals )
		this.vals = sVals;
	this.scan	= scanKeyLst;
	this.re		= new RegExp("^[" + this.tags + "]" + this.key + ":?(" + this.vals.join("|") + ")$", this.cases ? "g" : "ig" );
	}

function scanKeyLst( aValue )
	{
	var	result = false;
	if( !this.scand )
		{
		try
			{
			this.setValue( this.re.exec(aValue)[1] );
			this.scand	= true;
			result = true;
			}
		catch(e)
			{
			if( debug )
				log( "scanKeyStr - exception caught: 0x" + ((e.number+0xFFFFFFFF)%0xFFFFFFFF).toString(16).toUpperCase() + "\n\t" + e.description );
			}
		}
	return result;
	}

//	A keyed command-line bounded integer parameter
//
//	Format: [-]<key>:<decimal>|B<binary>|O<octal>|H<hexadecimal>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		iLower	The lower bound, defaults to javascript's smallest integer -2^52
//		iUpper	The upper bound, defaults to javascript's largest integer +2^52
//		iValue	The initial value, defaults to null
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		bCase	Boolean for whether the key, B, O, H are case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//
function KeyIntPar( sKey, iLower, iUpper, iValue, sHelp, bReqd, bCase, sTag )
	{
	//	Object inheritance
	this.base	= KeyStrPar;
	this.base( sKey, sHelp, bReqd, iValue, bCase, sTag );

	this.lower		= iLower != null ? iLower : -Math.pow(2,52);
	this.upper		= iUpper != null ? iUpper : Math.pow(2,52);
	this.getLower	= gLower;
	this.setLower	= sLower;
	this.getUpper	= gUpper;
	this.setUpper	= sUpper;
	this.scan		= scanKeyInt;
	this.re			= new RegExp("^[" + this.tags + "]" + this.key + ":?(([-+]?[0-9]+)|(B[-+]?[0-1]+)|(O[-+]?[0-7]+)|(H[-+]?[0-9A-F]+))$", this.cases ? "g" : "ig" );
	}

function scanKeyInt( aValue )
	{
	var	result = false;
	if( !this.scand )
		{
		try
			{
			var	base;
			var	numb = this.re.exec(aValue)[1];
			switch( numb.charAt(0).toUpperCase() )
				{
				default:	base = 10;
							break;

				case "B":	numb = numb.substr( 1 );
							base = 2;
							break;

				case "O":	numb = numb.substr( 1 );
							base = 8;
							break;

				case "H":	numb = numb.substr( 1 );
							base = 16;
							break;
				}
			numb = parseInt( numb, base );
			if( (numb >= this.lower) && (numb <= this.upper) )
				{
				this.setValue( numb );
				this.scand	= true;
				result = true;
				}
			}
		catch(e)
			{
			if( debug )
				log( "scanKeyInt - exception caught: 0x" + ((e.number+0xFFFFFFFF)%0xFFFFFFFF).toString(16).toUpperCase() + "\n\t" + e.description );
			}
		}
	return result;
	}

function gLower()
	{
	return this.lower;
	}

function sLower( nLower )
	{
	var	oLower = this.lower;
	this.lower = nLower;
	return oLower;
	}

function gUpper()
	{
	return this.upper;
	}

function sUpper( nUpper )
	{
	var	oUpper = this.upper;
	this.upper = nUpper;
	return oUpper;
	}

//	A keyed command-line bounded floating point parameter accepting a number in decimal or scientific format
//
//	Format: [-]<key>:<fp decimal>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		iLower	The lower bound, defaults to javascript's smallest number -2^52
//		iUpper	The upper bound, defaults to javascript's largest number +2^52
//		iValue	The initial value, defaults to null
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		bCase	Boolean for whether the key and the E denoting an exponent are case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//
function KeyFltPar( sKey, iLower, iUpper, iValue, sHelp, bReqd, bCase, sTag )
	{
	//	Object inheritance
	this.base	= KeyIntPar;
	this.base( sKey, iLower, iUpper, iValue, sHelp, bReqd, bCase, sTag );

	this.scan		= scanKeyFlt;
	this.re			= new RegExp("^[" + this.tags + "]" + this.key + ":?([-+]?[.0-9]+)(?:E([-+]?[.0-9]+))?$", this.cases ? "g" : "ig" );
	}

function scanKeyFlt( aValue )
	{
	var	result = false;
	if( !this.scand )
		{
		try
			{
			var	comps = this.re.exec(aValue);
			var	numb = comps[2] ? parseFloat( comps[1] )*Math.pow( 10, parseFloat(comps[2]) ) : parseFloat( comps[1] );
			if( (numb >= this.lower) && (numb <= this.upper) )
				{
				this.setValue( numb );
				this.scand	= true;
				result = true;
				}
			}
		catch(e)
			{
			if( debug )
				log( "scanKeyFlt - exception caught: 0x" + ((e.number+0xFFFFFFFF)%0xFFFFFFFF).toString(16).toUpperCase() + "\n\t" + e.description );
			}
		}
	return result;
	}

//	Convert Array to string
//	(redefinition for Array class)
function arrString()
	{
	var	result = "[";
	for( var i = 0, I = this.length; i < I; i++ )
		result += (i ? ", " : " " ) + i + ": " + this[i];
	result += "]";
	return result;
	}

//	Strip leading and trailing spaces
//	(extension to String class)
function strTrim()
	{
    return this.replace(/(^\s+)|(\s+$)/g, "");
	}

//	Pad number
//	(extension to Number class)
function pad( width )
	{
	var	sign	= "";
	var	result	= "" + this + "";
	if( (result[0] == '+') || (result[0] == '-') )
		{
		sign	= result[0];
		result	= result.substr( 1 );
		}
	while( (result.indexOf(".") >= 0 ? result.indexOf(".") : result.length) < width )
		result = "0" + result;
	return sign + result;
	}

//	Non WSH use: change appropriately TextStream.Write
//	Output to destination file
function writeFile( _msg, _dstfile )
	{
	if( _dstfile != null )
		_dstfile.Write( _msg + EOL );
	return;
	}

//	Non WSH use: change appropriately WScript.Echo, TextStream.WriteLine
//	Output logging messages
function log( _msg, _logfile )
	{
	WScript.Echo( _msg );
	if( _logfile != null )
		_logfile.WriteLine( _msg );
	return;
	}

//	==========================================================================
//	End of functions and objects
//	==========================================================================


