The addition of colour support to the .NET console in 2.0 made me happy. A lot of the coding I do these days is console based: I've written a bunch of utilities that trawl through the big (~1.3 million LOC) code base our vendor's delivering, generating reports that highlight possible issues. Tasteful, understated use of colour in the output reports really improves their usability. Sure, I want a console version of the <blink/>and <marquee/> tags, but colour will keep me quiet for a while.
The problem comes when you want to archive coloured text from a console window. If you launch a console app from a command prompt and redirect its output to a text file (e.g. "myapp.exe > myappoutput.txt"), all you get is a plain text file; the colour information is lost. The same happens when you mark a region in the console window and copy to the clipboard; your text makes it to the clipboard, but the colouring is left behind.
The solution's pretty simple. I wrote a thin wrapper around System.Console which adds a new feature: the ability to echo console output to an HTML file, with formatting and colouring preserved.
So now when my console apps use colour (a little something like this):
I get HTML export with colouring for free:
Simple, but effective. Sure, I'm a little ashamed I'm not dynamically injecting instructions into an IL stream or anything, but on the other hand, this works. And it's multi-coloured. Top that.
Sample code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExtendedConsole;
namespace ExtendedConsoleDemo
{
class Program
{
// This member and the Console property getter are named so that existing static
// calls to System.Console will instead be compiled into instance calls to this
// HtmlExportConsole. No need to do it this way, of course.
static HtmlExportConsole _console = new HtmlExportConsole("consoleLog.html");
static HtmlExportConsole Console
{
get
{
return _console;
}
}
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Here's some red");
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("yellow");
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("... green");
}
}
}
HtmlExportConsole
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Web;
namespace ExtendedConsole
{
/// <summary>
/// This class is a thin wrapper around System.Console that adds the ability to echo your console output to an HTML file with formatting and colouring preserved. Not all methods
/// on System.Console are supported at the moment. The simplest approach is just to add a thin wrapper for each method you need, as you need it (just as I have with KeyAvailable).
///
/// When retrofitting an existing console app to use HtmlExportConsole instead of System.Console, I just declare a local getter of type HtmlExportConsole named "Console" - this
/// local property effectively hides the System.Console static class, so that my existing calls, e.g. Console.Write(), get routed to HtmlExportConsole.Write() instead.
///
/// To enable/disable output to an HTML file, set the bool EnableFileOut property.
/// </summary>
public class HtmlExportConsole : IDisposable
{
ConsoleColor _foregroundColor;
ConsoleColor? _lastWrittenConsoleColor;
StreamWriter _exportStreamWriter;
public bool EnableConsoleOut { get; set; }
public bool EnableFileOut { get; set; }
public bool KeyAvailable
{
get
{
return Console.KeyAvailable;
}
}
public HtmlExportConsole(string htmlOutputFilePath)
{
_foregroundColor = Console.ForegroundColor;
_lastWrittenConsoleColor = null;
_exportStreamWriter = new StreamWriter(htmlOutputFilePath);
_exportStreamWriter.AutoFlush = true;
EnableConsoleOut = true;
EnableFileOut = true;
_exportStreamWriter.Write("<html><head><style>BODY { font-family: Lucida Console; background-color: black; }</style></head><body>");
}
public ConsoleColor ForegroundColor
{
set
{
Console.ForegroundColor = value;
_foregroundColor = value;
}
}
public void Write(ConsoleColor foregroundColor, string value)
{
_foregroundColor = foregroundColor;
Write(value);
}
public void Write(string value)
{
InternalWrite(value, false);
}
private void InternalWrite(string value, bool lineBreakAfter)
{
if (EnableConsoleOut)
{
Console.ForegroundColor = _foregroundColor;
Console.Write(value + (lineBreakAfter ? Environment.NewLine : ""));
}
if (EnableFileOut)
{
if (_lastWrittenConsoleColor != _foregroundColor)
{
if (_lastWrittenConsoleColor != null)
{
_exportStreamWriter.Write(String.Format(@"</font>"));
}
_exportStreamWriter.Write(String.Format(@"<font color=""{0}"">", _foregroundColor));
}
_exportStreamWriter.Write(HttpUtility.HtmlEncode(value).Replace(" ", " ") + (lineBreakAfter ? "<br>" + Environment.NewLine : ""));
_lastWrittenConsoleColor = _foregroundColor;
}
}
public void WriteLine()
{
WriteLine("");
}
public void WriteLine(ConsoleColor foregroundColor, string value)
{
_foregroundColor = foregroundColor;
WriteLine(value);
}
public void WriteLine(string value)
{
InternalWrite(value, true);
}
#region IDisposable Members
public void Dispose()
{
if (EnableFileOut)
{
_exportStreamWriter.Write("</body></html>");
}
_exportStreamWriter.Dispose();
}
#endregion
}
}