Powered by GeSHi

Source code for bathl.cs

(view source code of bathl.cs as plain text)

  1. using System;
  2. using System.IO;
  3. using System.Text.RegularExpressions;
  4.  
  5. namespace RobvanderWoude
  6. {
  7. 	class BatHL
  8. 	{
  9. 		static int Main( string[] args )
  10. 		{
  11. 			#region Command Line Parsing
  12.  
  13. 			bool dispnums = true;
  14. 			bool logging = false;
  15. 			bool silent = false;
  16. 			string logfile = String.Empty;
  17. 			string source = String.Empty;
  18.  
  19. 			if ( args.Length == 0 || args.Length > 3 )
  20. 			{
  21. 				return WriteError( );
  22. 			}
  23. 			foreach ( string arg in args )
  24. 			{
  25. 				if ( arg.Substring( 0, 1 ) == "/" )
  26. 				{
  27. 					if ( arg.ToUpper( ) == "/N" )
  28. 					{
  29. 						dispnums = false;
  30. 					}
  31. 					else if ( arg.ToUpper( ) == "/S" )
  32. 					{
  33. 						silent = true;
  34. 						dispnums = false;
  35. 					}
  36. 					else if ( arg.ToUpper( ).Substring( 0, 3 ) == "/L:" )
  37. 					{
  38. 						logging = true;
  39. 						logfile = arg.Substring( 3 );
  40. 					}
  41. 					else
  42. 					{
  43. 						return WriteError( "Invalid command line argument \"" + arg + "\"" );
  44. 					}
  45. 				}
  46. 				else
  47. 				{
  48. 					if ( String.IsNullOrEmpty( source ) )
  49. 					{
  50. 						source = arg;
  51. 					}
  52. 					else
  53. 					{
  54. 						return WriteError( "Invalid command line argument \"" + arg + "\"" );
  55. 					}
  56. 				}
  57. 			}
  58.  
  59. 			if ( String.IsNullOrEmpty( source ) )
  60. 			{
  61. 				return WriteError( "Please specify a source file" );
  62. 			}
  63. 			if ( !File.Exists( source ) )
  64. 			{
  65. 				return WriteError( "Source file not found" );
  66. 			}
  67. 			if ( !String.IsNullOrEmpty( logfile ) )
  68. 			{
  69. 				if ( !Directory.Exists( Directory.GetParent( logfile ).FullName ) )
  70. 				{
  71. 					return WriteError( "Invalid path to logfile" );
  72. 				}
  73. 			}
  74.  
  75. 			#endregion Command Line Parsing
  76.  
  77. 			int parenthesis = 0;
  78. 			int rc = 1;
  79. 			string errors = String.Empty;
  80.  
  81. 			// testing...
  82. 			//Console.ResetColor( );
  83.  
  84. 			ConsoleColor bgcolor = Console.BackgroundColor;
  85. 			ConsoleColor fgcolor = Console.ForegroundColor;
  86. 			try
  87. 			{
  88. 				source = args[0];
  89. 				if ( source.IndexOfAny( "/?*".ToCharArray( ) ) > -1 )
  90. 				{
  91. 					return WriteError( );
  92. 				}
  93. 				if ( File.Exists( source ) )
  94. 				{
  95. 					rc = 0;
  96. 					int linenum = 0;
  97. 					StreamReader src = new StreamReader( source );
  98. 					while ( !src.EndOfStream )
  99. 					{
  100. 						linenum += 1;
  101. 						if ( dispnums )
  102. 						{
  103. 							// display line numbers
  104. 							Console.BackgroundColor = ConsoleColor.Gray;
  105. 							Console.ForegroundColor = ConsoleColor.DarkMagenta;
  106. 							Console.Write( "{0,4}", linenum );
  107. 							Console.BackgroundColor = bgcolor;
  108. 							Console.ForegroundColor = fgcolor;
  109. 							Console.Write( " " );
  110. 						}
  111. 						RegexOptions options = RegexOptions.IgnoreCase;
  112. 						string line = src.ReadLine( );
  113. 						string pattern = @"\(";
  114. 						int paropen = Regex.Matches( line, pattern ).Count;
  115. 						pattern = @"\)";
  116. 						int parclose = Regex.Matches( line, pattern ).Count;
  117. 						parenthesis = parenthesis + paropen - parclose;
  118. 						if ( parenthesis < 0 )
  119. 						{
  120. 							string temperr = "\nToo many closing parenthesis, starting at line ";
  121. 							if ( errors.IndexOf( temperr ) == -1 )
  122. 							{
  123. 								errors += temperr + linenum;
  124. 							}
  125. 						}
  126. 						// check for orphaned doublequotes
  127. 						pattern = "\"";
  128. 						if ( Regex.Matches( line, pattern ).Count % 2 != 0 )
  129. 						{
  130. 							errors += "\nOdd number of doublequotes in line " + linenum;
  131. 						}
  132. 						// check for orphaned singlequotes
  133. 						pattern = "'";
  134. 						if ( Regex.Matches( line, pattern ).Count % 2 != 0 )
  135. 						{
  136. 							errors += "\nOdd number of singlequotes in line " + linenum;
  137. 						}
  138. 						// check for orphaned percent signs
  139. 						pattern = "%";
  140. 						int perc = Regex.Matches( line, pattern ).Count;
  141. 						// ignore %~1 etc.
  142. 						pattern = "[^%]%~";
  143. 						perc -= Regex.Matches( line, pattern ).Count;
  144. 						if ( perc % 2 != 0 )
  145. 						{
  146. 							errors += "\nPossibly unterminated percent signs in line " + linenum;
  147. 						}
  148. 						// check for orphaned exclamation marks
  149. 						pattern = "!";
  150. 						if ( Regex.Matches( line, pattern ).Count % 2 != 0 )
  151. 						{
  152. 							errors += "\nOdd number of exclamation marks in line " + linenum;
  153. 						}
  154.  
  155. 						if ( !silent )
  156. 						{
  157. 							// highlight remarks
  158. 							pattern = @"^\s*(REM(\s|$)|::)";
  159. 							Regex regex = new Regex( pattern, options );
  160. 							if ( regex.IsMatch( line ) )
  161. 							{
  162. 								Console.ForegroundColor = ConsoleColor.DarkGreen;
  163. 								Console.WriteLine( line );
  164. 								Console.ResetColor( );
  165. 								if ( parenthesis != 0 )
  166. 								{
  167. 									pattern = @"^\s*::";
  168. 									regex = new Regex( pattern, options );
  169. 									if ( regex.IsMatch( line ) )
  170. 									{
  171. 										errors += "\nNever use \"::\" inside code blocks, use \"REM\" instead! Potential error detected in line " + linenum;
  172. 									}
  173. 								}
  174. 							}
  175. 							else
  176. 							{
  177. 								// highlight ECHOed text - may err on escaped redirection marks
  178. 								pattern = @"^(.*\b@?ECHO(?:\.|\s))(.+?)((?:&|>|\|).+)?$";
  179. 								regex = new Regex( pattern, options );
  180. 								if ( regex.IsMatch( line ) )
  181. 								{
  182. 									MatchCollection matches = Regex.Matches( line, pattern, options );
  183. 									foreach ( Match match in matches )
  184. 									{
  185. 										Console.Write( match.Groups[1].Value );
  186. 										string test = match.Groups[2].Value.Trim( ).ToLower( );
  187. 										// ignore ECHO OFF or ECHO ON, highlight any other ECHOed text
  188. 										if ( test != "off" && test != "on" )
  189. 										{
  190. 											Console.ForegroundColor = ConsoleColor.Cyan;
  191. 										}
  192. 										Console.Write( match.Groups[2].Value );
  193. 										Console.ResetColor( );
  194. 										Console.WriteLine( match.Groups[3].Value );
  195. 									}
  196. 								}
  197. 								else
  198. 								{
  199. 									// highlight doublequoted strings
  200. 									if ( line.IndexOf( '"' ) > -1 )
  201. 									{
  202. 										string part;
  203. 										int len = line.Length;
  204. 										int idx = line.IndexOf( '"' );
  205. 										// split the line into quoted and non-quoted parts
  206. 										while ( !String.IsNullOrWhiteSpace( line ) )
  207. 										{
  208. 											len = line.Length;
  209. 											idx = line.IndexOf( '"' );
  210. 											if ( idx == 0 )
  211. 											{
  212. 												if ( line.IndexOf( '"', 1 ) == -1 )
  213. 												{
  214. 													Console.ForegroundColor = ConsoleColor.Red;
  215. 													part = line;
  216. 													line = String.Empty;
  217. 													len = 0;
  218. 													idx = -1;
  219. 													errors += "\nUnterminated doublequotes in line " + linenum;
  220. 													rc = linenum;
  221. 												}
  222. 												else
  223. 												{
  224. 													Console.ForegroundColor = ConsoleColor.Yellow;
  225. 													part = line.Substring( 0, Math.Min( line.IndexOf( '"', 1 ) + 1, len ) );
  226. 													line = line.Substring( Math.Min( line.IndexOf( '"', 1 ) + 1, len ) );
  227. 													len = line.Length;
  228. 													idx = line.IndexOf( '"' );
  229. 												}
  230. 											}
  231. 											else
  232. 											{
  233. 												Console.ResetColor( );
  234. 												if ( line.IndexOf( '"', 1 ) == -1 )
  235. 												{
  236. 													part = line;
  237. 													line = String.Empty;
  238. 													len = 0;
  239. 													idx = -1;
  240. 												}
  241. 												else
  242. 												{
  243. 													part = line.Substring( 0, Math.Min( line.IndexOf( '"' ), len ) );
  244. 													line = line.Substring( Math.Min( line.IndexOf( '"' ), len ) );
  245. 													len = line.Length;
  246. 													idx = line.IndexOf( '"' );
  247. 												}
  248. 											}
  249. 											Console.Write( part );
  250. 										}
  251. 										Console.WriteLine( );
  252. 										Console.ResetColor( );
  253. 									}
  254. 									else
  255. 									{
  256. 										if ( line.ToUpper( ).IndexOf( "IF EXISTS" ) > -1 )
  257. 										{
  258. 											errors += "\n\"IF EXISTS\" (with trailing \"S\") found in line " + linenum + ", correct syntax for file existance check is \"IF EXIST\" without trailing \"S\"";
  259. 										}
  260. 										// highlight some special characters in "ordinary" code
  261. 										Console.ResetColor( );
  262. 										string[] test = new string[] { "X", "Y", "Z" };
  263. 										while ( !String.IsNullOrWhiteSpace( test[2] ) )
  264. 										{
  265. 											test = FindChars( "()'%!".ToCharArray( ), line );
  266. 											Console.Write( test[0] );
  267. 											HighlightChar( test[1] );
  268. 											line = test[2];
  269. 										}
  270. 										Console.WriteLine( line );
  271.  
  272. 									}
  273. 								}
  274. 								Console.ResetColor( );
  275. 							}
  276. 						}
  277. 					}
  278. 					src.Close( );
  279. 					// display warnings if applicable
  280. 					if ( parenthesis > 0 )
  281. 					{
  282. 						errors += "\nMissing " + parenthesis + " closing parenthesis";
  283. 					}
  284. 					if ( !String.IsNullOrEmpty( errors ) )
  285. 					{
  286. 						if ( silent )
  287. 						{
  288. 							Console.ResetColor( );
  289. 							Console.WriteLine( "File name: \"{0}\"", source );
  290. 							Console.WriteLine( );
  291. 						}
  292. 						Console.ForegroundColor = ConsoleColor.Red;
  293. 						Console.WriteLine( errors );
  294. 						Console.ResetColor( );
  295. 						Console.WriteLine( );
  296. 						rc = Math.Max( 1, rc );
  297. 						if ( logging )
  298. 						{
  299. 							StreamWriter log = new StreamWriter( logfile, true );
  300. 							log.WriteLine( "File name: \"{0}\"", source );
  301. 							log.WriteLine( errors.Replace( "\n", "\n\t" ) + "\n\n" );
  302. 							log.Close( );
  303. 						}
  304. 					}
  305. 				}
  306. 				Console.ForegroundColor = fgcolor;
  307. 				Console.BackgroundColor = bgcolor;
  308. 				return rc;
  309. 			}
  310. 			catch ( Exception e )
  311. 			{
  312. 				return WriteError( e.Message );
  313. 			}
  314. 		}
  315.  
  316.  
  317. 		public static string[] FindChars( char[] chrArr, string strIn )
  318. 		{
  319. 			int chrPos = strIn.IndexOfAny( chrArr );
  320. 			if ( chrPos > -1 )
  321. 			{
  322. 				string part1 = strIn.Substring( 0, Math.Max( 0, chrPos ) );
  323. 				string part2 = strIn.Substring( chrPos, 1 );
  324. 				string part3 = strIn.Substring( Math.Min( chrPos + 1, strIn.Length ) );
  325. 				return new string[] { part1, part2, part3 };
  326. 			}
  327. 			else
  328. 			{
  329. 				return new string[] { strIn, String.Empty, String.Empty };
  330. 			}
  331. 		}
  332.  
  333. 		// write a single character in a different color
  334. 		public static void HighlightChar( string chr )
  335. 		{
  336. 			if ( !String.IsNullOrWhiteSpace( chr ) )
  337. 			{
  338. 				ConsoleColor foregroundcolor = Console.ForegroundColor;
  339. 				ConsoleColor highlightcolor;
  340. 				switch ( chr )
  341. 				{
  342. 					case "(":
  343. 					case ")":
  344. 					case "'":
  345. 						highlightcolor = ConsoleColor.Yellow;
  346. 						break;
  347. 					case "%":
  348. 					case "!":
  349. 						highlightcolor = ConsoleColor.Green;
  350. 						break;
  351. 					default:
  352. 						highlightcolor = foregroundcolor;
  353. 						break;
  354. 				}
  355. 				Console.ForegroundColor = highlightcolor;
  356. 				Console.Write( chr );
  357. 				Console.ForegroundColor = foregroundcolor;
  358. 			}
  359. 		}
  360.  
  361.  
  362. 		#region Error Handling
  363.  
  364. 		public static int WriteError( Exception e = null )
  365. 		{
  366. 			return WriteError( e == null ? null : e.Message );
  367. 		}
  368.  
  369. 		public static int WriteError( string errorMessage )
  370. 		{
  371. 			Console.OpenStandardError( );
  372. 			if ( string.IsNullOrEmpty( errorMessage ) == false )
  373. 			{
  374. 				Console.Error.WriteLine( );
  375. 				Console.ForegroundColor = ConsoleColor.Red;
  376. 				Console.Error.Write( "ERROR: " );
  377. 				Console.ForegroundColor = ConsoleColor.White;
  378. 				Console.Error.WriteLine( errorMessage );
  379. 				Console.ResetColor( );
  380. 			}
  381.  
  382. 			/*
  383. 			BatHL,  Version 0.51 beta
  384. 			Search batch source code for unterminated quotes, parenthesis and percent signs
  385.  
  386. 			Usage:   BATHL  batchfile  [ /L:"logfile" ]  [ /N | /S ]
  387.  
  388. 			Where:   batchfile    is the source code to check and highlight
  389. 			         /L:"logfile" tells the program to log the results to a file
  390. 			         /N           tells the program Not to display line numbers
  391. 			         /S           Silent operation: no highlighting, just warnings
  392.  
  393. 			Notes:   The source code is displayed with highlighted ECHOed text,
  394. 			         comments and doublequoted strings (BatHL: Batch HighLighter).
  395. 			         A warning message will be displayed if any unterminated quotes,
  396. 			         parenthesis, or variables, or other syntax errors were found.
  397. 			         A non-zero return code means something was wrong, either on
  398. 			         the command line or in the source code. A return code higher
  399. 			         than 1 indicates the line number where an error was detected.
  400. 			         If an existing log file is specified, results will be appended.
  401. 			         If no errors are detected, nothing will be logged.
  402.  
  403. 			Written by Rob van der Woude
  404. 			http://www.robvanderwoude.com
  405. 			 */
  406.  
  407. 			Console.Error.WriteLine( );
  408. 			Console.Error.WriteLine( "BatHL,  Version 0.51 beta" );
  409. 			Console.Error.WriteLine( "Search batch source code for unterminated quotes, parenthesis and percent signs" );
  410. 			Console.Error.WriteLine( );
  411. 			Console.Error.Write( "Usage:   " );
  412. 			Console.ForegroundColor = ConsoleColor.White;
  413. 			Console.Error.WriteLine( "BATHL  batchfile  [ /L:\"logfile\" ]  [ /N | /S ]" );
  414. 			Console.ResetColor( );
  415. 			Console.Error.WriteLine( );
  416. 			Console.Error.Write( "Where:   " );
  417. 			Console.ForegroundColor = ConsoleColor.White;
  418. 			Console.Error.Write( "batchfile" );
  419. 			Console.ResetColor( );
  420. 			Console.Error.WriteLine( "    is the source code to check and highlight" );
  421. 			Console.ForegroundColor = ConsoleColor.White;
  422. 			Console.Error.Write( "         /L:\"logfile\"" );
  423. 			Console.ResetColor( );
  424. 			Console.Error.Write( " tells the program to" );
  425. 			Console.ForegroundColor = ConsoleColor.White;
  426. 			Console.Error.Write( "l" );
  427. 			Console.ResetColor( );
  428. 			Console.Error.WriteLine( "og the results to a file" );
  429. 			Console.Error.Write( "         /N" );
  430. 			Console.ResetColor( );
  431. 			Console.Error.Write( "           tells the program " );
  432. 			Console.ForegroundColor = ConsoleColor.White;
  433. 			Console.Error.Write( "N" );
  434. 			Console.ResetColor( );
  435. 			Console.Error.WriteLine( "ot to display line numbers" );
  436. 			Console.ForegroundColor = ConsoleColor.White;
  437. 			Console.Error.Write( "         /S           S" );
  438. 			Console.ResetColor( );
  439. 			Console.Error.WriteLine( "ilent operation: no highlighting, just warnings" );
  440. 			Console.Error.WriteLine( );
  441. 			Console.Error.WriteLine( "Notes:   The source code is displayed with highlighted ECHOed text," );
  442. 			Console.Error.WriteLine( "         comments and doublequoted strings (BatHL: Batch HighLighter)." );
  443. 			Console.Error.WriteLine( "         A warning message will be displayed if any unterminated quotes," );
  444. 			Console.Error.WriteLine( "         parenthesis, or variables, or other syntax errors were found." );
  445. 			Console.Error.WriteLine( "         A non-zero return code means something was wrong, either on" );
  446. 			Console.Error.WriteLine( "         the command line or in the source code. A return code higher" );
  447. 			Console.Error.WriteLine( "         than 1 indicates the line number where an error was detected." );
  448. 			Console.Error.WriteLine( "         If an existing log file is specified, results will be appended." );
  449. 			Console.Error.WriteLine( "         If no errors are detected, nothing will be logged." );
  450. 			Console.Error.WriteLine( );
  451. 			Console.Error.WriteLine( "Written by Rob van der Woude" );
  452. 			Console.Error.WriteLine( "http://www.robvanderwoude.com" );
  453. 			Console.OpenStandardOutput( );
  454. 			return 1;
  455. 		}
  456.  
  457. 		#endregion Error Handling
  458. 	}
  459. }
  460.