| | 1 | | using System; |
| | 2 | | using System.Collections.Generic; |
| | 3 | | using System.Diagnostics; |
| | 4 | | using System.IO; |
| | 5 | | using System.Linq; |
| | 6 | | using System.Reflection; |
| | 7 | |
|
| | 8 | | namespace Application.Helper |
| | 9 | | { |
| | 10 | | /// <summary>Helper class for Console Applications.</summary> |
| | 11 | | public class ConsoleApp |
| | 12 | | { |
| | 13 | | #region Properties |
| | 14 | |
|
| | 15 | | /// <summary>Gets the name of the current application configuration file.</summary> |
| 2 | 16 | | public string ConfigFile { get; } |
| | 17 | |
|
| | 18 | | /// <summary>Gets the application title.</summary> |
| 19 | 19 | | public string Title { get; private set; } |
| | 20 | |
|
| | 21 | | /// <summary>Indicates whether execution help has been requested.</summary> |
| 2 | 22 | | public bool HelpRequest { get; private set; } |
| | 23 | |
|
| | 24 | | /// <summary>Indicates whether a debugger is attached to the process.</summary> |
| 30 | 25 | | public bool DebugMode { get; } |
| | 26 | |
|
| | 27 | | /// <summary>Gets the total elapsed time measured.</summary> |
| 12 | 28 | | public TimeSpan ElapsedTime => _stopWatch.Elapsed; |
| | 29 | |
|
| | 30 | | /// <summary>Sets whether this is running as a unit test.</summary> |
| 41 | 31 | | public bool IsUnitTest { private get; set; } = false; |
| | 32 | |
|
| | 33 | | #endregion |
| | 34 | |
|
| | 35 | | #region Constructor |
| | 36 | |
|
| | 37 | | /// <summary>Stop watch to calculate elapse time.</summary> |
| 17 | 38 | | private readonly Stopwatch _stopWatch = new Stopwatch(); |
| | 39 | |
|
| | 40 | | private bool _started; |
| | 41 | |
|
| | 42 | | /// <summary>Creates a new instance of the ConsoleApp class.</summary> |
| | 43 | | /// <param name="configFile">Configuration file name.<br/> |
| | 44 | | /// The default is "appsettings.json"</param> |
| | 45 | | /// <param name="detectDebugMode">Detect if a debugger is attached or not.<br/> |
| | 46 | | /// The default is <see langword="true"/></param> |
| | 47 | | /// <exception cref="ArgumentException">Thrown when one of the arguments provided to a method is not valid.</excepti |
| | 48 | | /// <exception cref="TypeLoadException">Thrown when type-loading failures occur.</exception> |
| 17 | 49 | | public ConsoleApp( string configFile = "appsettings.json", bool detectDebugMode = true ) |
| 17 | 50 | | { |
| 17 | 51 | | ConfigFile = GetAppConfigFile( configFile ); |
| 17 | 52 | | Title = FormatTitle( Assembly.GetEntryAssembly() ); |
| 17 | 53 | | DebugMode = !detectDebugMode || Debugger.IsAttached; |
| 17 | 54 | | } |
| | 55 | |
|
| | 56 | | #endregion |
| | 57 | |
|
| | 58 | | #region Private Methods |
| | 59 | |
|
| | 60 | | private static string GetAppConfigFile( string configFile ) |
| 17 | 61 | | { |
| 17 | 62 | | string retValue = configFile; |
| 19 | 63 | | if( File.Exists( retValue ) ) { return retValue; } |
| | 64 | |
|
| | 65 | | // Get the name of the executable |
| 16 | 66 | | retValue = Path.GetFileName( Assembly.GetEntryAssembly().Location ); |
| | 67 | |
|
| | 68 | | // Check for XML config file |
| 16 | 69 | | retValue = Path.ChangeExtension( retValue, ".config" ); |
| 16 | 70 | | if( File.Exists( retValue ) ) { return retValue; } |
| | 71 | |
|
| | 72 | | // Check for JSON config file |
| 16 | 73 | | retValue = Path.ChangeExtension( retValue, ".json" ); |
| 16 | 74 | | if( File.Exists( retValue ) ) { return retValue; } |
| | 75 | |
|
| 16 | 76 | | return string.Empty; |
| 17 | 77 | | } |
| | 78 | |
|
| | 79 | | private static T GetAssemblyAttribute<T>( ICustomAttributeProvider assembly ) where T : Attribute |
| 36 | 80 | | { |
| 36 | 81 | | object[] attributes = assembly.GetCustomAttributes( typeof( T ), false ); |
| 36 | 82 | | return attributes.Length == 0 ? null : attributes.OfType<T>().SingleOrDefault(); |
| 36 | 83 | | } |
| | 84 | |
|
| | 85 | | private static void SetStartTime( ConsoleApp app ) |
| 16 | 86 | | { |
| 26 | 87 | | if( !app.DebugMode ) { return; } |
| | 88 | |
|
| | 89 | | // Log the start time |
| 11 | 90 | | Console.WriteLine( @"Start..: " + DateTime.Now.ToString( @"HH:mm:ss.fffffff" ) ); |
| 11 | 91 | | Console.WriteLine(); |
| 16 | 92 | | } |
| | 93 | |
|
| | 94 | | private static void SetEndTime( ConsoleApp app, bool result, bool unitTest ) |
| 13 | 95 | | { |
| 52 | 96 | | if( app._stopWatch.IsRunning ) { app.SuspendElapsed(); } |
| | 97 | |
|
| 15 | 98 | | if( !app.DebugMode ) return; |
| | 99 | |
|
| | 100 | | // Log the completion and elapsed times |
| 11 | 101 | | string exitCode = Environment.ExitCode > 0 ? " with exit code " + Environment.ExitCode : string.Empty; |
| 11 | 102 | | Console.WriteLine(); |
| 11 | 103 | | Console.WriteLine( @"Runtime: " + app.ElapsedTime ); |
| 11 | 104 | | Console.WriteLine( @"Result.: " + ( result ? "Success" : "Failed" ) + exitCode ); |
| 11 | 105 | | if( !Environment.UserInteractive ) return; |
| | 106 | |
|
| 11 | 107 | | Console.WriteLine( string.Empty ); |
| 11 | 108 | | Console.Write( @"Press any key to continue . . . " ); |
| 11 | 109 | | if( !unitTest ) { _ = Console.Read(); } |
| 13 | 110 | | } |
| | 111 | |
|
| | 112 | | private static string FormatTitle( Assembly assembly ) |
| 17 | 113 | | { |
| 17 | 114 | | AssemblyTitleAttribute titleAttr = GetAssemblyAttribute<AssemblyTitleAttribute>( assembly ); |
| 17 | 115 | | string titleStr = titleAttr?.Title.Trim() ?? string.Empty; |
| | 116 | |
|
| | 117 | | // Get the file version |
| 17 | 118 | | string versionStr = null; |
| 17 | 119 | | AssemblyFileVersionAttribute versionAttr = GetAssemblyAttribute<AssemblyFileVersionAttribute>( assembly ); |
| 68 | 120 | | if( null != versionAttr ) { versionStr = versionAttr.Version.Trim(); } |
| | 121 | |
|
| | 122 | | // If no file version get it from the full name |
| 17 | 123 | | if( null == versionStr ) { versionStr = assembly.GetName().Version.ToString(); } |
| | 124 | |
|
| 17 | 125 | | return versionStr.Length > 0 ? (titleStr + $" [Version {versionStr}]").Trim() : titleStr; |
| 17 | 126 | | } |
| | 127 | |
|
| | 128 | | private void ShowProgramInfo( ICustomAttributeProvider assembly ) |
| 1 | 129 | | { |
| | 130 | | // Add the description to the title |
| 1 | 131 | | AssemblyDescriptionAttribute descAttr = GetAssemblyAttribute<AssemblyDescriptionAttribute>( assembly ); |
| 1 | 132 | | string descStr = descAttr?.Description.Trim() ?? string.Empty; |
| 1 | 133 | | string titleStr = descStr.Length > 0 ? ( Title + $" {descStr}" ).Trim() : Title; |
| | 134 | |
|
| | 135 | | // Display the title line |
| 1 | 136 | | if( titleStr.Length > 0 ) |
| 1 | 137 | | { |
| 1 | 138 | | Console.WriteLine( titleStr ); |
| 1 | 139 | | } |
| | 140 | |
|
| | 141 | | // Display the copyright line |
| 1 | 142 | | AssemblyCopyrightAttribute copyAttr = GetAssemblyAttribute<AssemblyCopyrightAttribute>( assembly ); |
| 1 | 143 | | if( copyAttr != null && copyAttr.Copyright.Length > 0 ) |
| 1 | 144 | | { |
| 1 | 145 | | Console.WriteLine( copyAttr.Copyright ); |
| 1 | 146 | | } |
| | 147 | |
|
| 1 | 148 | | Console.WriteLine(); |
| 1 | 149 | | } |
| | 150 | |
|
| | 151 | | #endregion |
| | 152 | |
|
| | 153 | | #region Public Methods |
| | 154 | |
|
| | 155 | | /// <summary>Start the console application.</summary> |
| | 156 | | /// <returns><see langword="true"/> if the application has already been started.</returns> |
| | 157 | | public bool StartApp() |
| 19 | 158 | | { |
| | 159 | | // Check whether the application has already been started |
| 25 | 160 | | if( _started ) { return true; } |
| 16 | 161 | | _started = true; |
| | 162 | |
|
| | 163 | | // Show the application start time and start the stopwatch |
| 16 | 164 | | SetStartTime( this ); |
| 16 | 165 | | ResumeElapsed(); |
| | 166 | |
|
| 16 | 167 | | return false; |
| 19 | 168 | | } |
| | 169 | |
|
| | 170 | | /// <summary>Start the console application.</summary> |
| | 171 | | /// <param name="args">Collection of command-line arguments.</param> |
| | 172 | | public void StartApp( IReadOnlyList<string> args ) |
| 4 | 173 | | { |
| | 174 | | // Check whether the application has already been started |
| 6 | 175 | | if( StartApp() ) { return; } |
| | 176 | |
|
| | 177 | | // Check for command line help request |
| 3 | 178 | | args = args ?? new List<string>(); |
| 3 | 179 | | if( args.Count > 0 && args[0].Contains( "?" ) ) |
| 1 | 180 | | { |
| 1 | 181 | | ShowProgramInfo(); |
| | 182 | |
|
| | 183 | | // Set the flag to indicate help has been requested |
| 1 | 184 | | HelpRequest = true; |
| 1 | 185 | | } |
| 4 | 186 | | } |
| | 187 | |
|
| | 188 | | /// <summary>Output the program information to the Console.</summary> |
| | 189 | | /// <remarks>This method can be overridden in a derived class to provide specific |
| | 190 | | /// information about the program when the command line help request is made using |
| | 191 | | /// ? anywhere in the first argument.</remarks> |
| | 192 | | /// <exception cref="TypeLoadException">Thrown when type-loading failures occur.</exception> |
| | 193 | | public virtual void ShowProgramInfo() |
| 1 | 194 | | { |
| 1 | 195 | | ShowProgramInfo( Assembly.GetEntryAssembly() ); |
| 1 | 196 | | } |
| | 197 | |
|
| | 198 | | /// <summary>Stops the application and sets the exit code.</summary> |
| | 199 | | /// <param name="result">Execution result, <see langword="false"/> sets the exit code to 1.</param> |
| | 200 | | public void StopApp( bool result = true ) |
| 15 | 201 | | { |
| | 202 | | // Check whether the application has already been stopped |
| 19 | 203 | | if( !_started ) { return; } |
| 13 | 204 | | _started = false; |
| | 205 | |
|
| | 206 | | // Set the return code if it is not already set |
| 13 | 207 | | if( 0 == Environment.ExitCode ) |
| 12 | 208 | | { |
| 12 | 209 | | Environment.ExitCode = result ? 0 : 1; |
| 12 | 210 | | } |
| | 211 | |
|
| | 212 | | // Set the application stop variables |
| 13 | 213 | | SetEndTime( this, result, IsUnitTest ); |
| 15 | 214 | | } |
| | 215 | |
|
| | 216 | | /// <summary>Formats a title line to be logged.</summary> |
| | 217 | | /// <param name="text">Program title.</param> |
| | 218 | | /// <param name="maxWidth">Maximum line width, default is 80 characters.</param> |
| | 219 | | /// <returns>String containing the maximum width title.</returns> |
| | 220 | | public string FormatTitleLine( string text, int maxWidth = 80 ) |
| 4 | 221 | | { |
| 4 | 222 | | text = text ?? string.Empty; |
| 6 | 223 | | if( text.Length >= maxWidth ) { return text; } |
| | 224 | |
|
| | 225 | | // Center the title and pad width '-' characters |
| 3 | 226 | | int filler = ( maxWidth - text.Length ) / 2; |
| 3 | 227 | | string padding = new string( '-', filler ); |
| 6 | 228 | | if( text.Length + filler * 2 != maxWidth ) { text += "-"; } |
| | 229 | |
|
| 3 | 230 | | return padding + text + padding; |
| 4 | 231 | | } |
| | 232 | |
|
| | 233 | | /// <summary>Resumes measuring elapsed time for an interval.</summary> |
| | 234 | | public void ResumeElapsed() |
| 16 | 235 | | { |
| 64 | 236 | | if( !_stopWatch.IsRunning ) { _stopWatch.Start(); } |
| 16 | 237 | | } |
| | 238 | |
|
| | 239 | | /// <summary>Suspends measuring elapsed time for an interval.</summary> |
| | 240 | | public void SuspendElapsed() |
| 13 | 241 | | { |
| 52 | 242 | | if( _stopWatch.IsRunning ) { _stopWatch.Stop(); } |
| 13 | 243 | | } |
| | 244 | |
|
| | 245 | | #endregion |
| | 246 | | } |
| | 247 | | } |