Introduction
In this article of this series, I�m going to show you a simple VS.NET Add-In client I wrote for the TaHoGen template engine. (If you�re not familiar with TaHoGen, you can think of it as an Open Source implementation of a CodeSmith-compatible code generation engine.) Furthermore, we�re going to take it a step further by allowing the user to output his/her templates directly into the VS.NET Code Editor windows.
Background
One of the things that I�ve always wanted to do is to be able to run a CodeSmith template and send the output of that template directly to the code window. Unfortunately, the current version of CodeSmith (version 3.0 at the time of this writing) doesn�t support that out of the box. Secondly, its source code is proprietary, leaving Open Source tool developers like me with little or no options to work with. It�s either you buy a license to use it in your products, or you can�t use it at all (except from within CodeSmith itself).
For me, both options were unacceptable, and that was why the TaHoGen engine was born. The difference between TaHoGen and CodeSmith in this regard is simple: as long as you use TaHoGen in another project licensed under the terms of the GNU Public License, I will never, ever, charge you a cent for it. If you decide to include it in a commercial product, however, then, that�s a different story�
So, now that we as a developer community have our own Open Source CodeSmith-style compatible code generation engine (yeah, I know, that�s quite a mouthful), it�s time to make something useful out of it�let�s get started!
What you�re going to need before we get started
In addition to the TaHoGen Core Libraries from the previous article, you�re going to need:
- Phil Wright�s MagicLibrary. You can get it from here.
- MutantDesign�s Managed AddIns Library & AddIn Manager. Normally, you can find these libraries included as part of the TestDriven.NET library. (For your convenience, I�ve included a copy of the MSI installer that I used as a part of this add-in, in the section above.)
Using the client
Once you�ve installed the two libraries that I mentioned earlier (namely the ManagedAddIns library) and installed the add-in itself, you should get an option in your Tools menu in VS.NET that looks like this:
After you click on that option in the menu, you should see a dialog similar to the following appear:
Once you choose the File->Open option on the menu, a standard OpenFileDialog will ask you where you installed the add-in. Once you�ve selected where the file (TaHoGen.VSAddIn.exe) is located, the only thing you have to do is right click on the TaHoGen.VSAddIn list item and click on Connect. You should then see something like this:
This should be pretty familiar to those of you used to using the CodeSmith Explorer interface. (For now, don�t worry if you don�t see anything listed on the tree view just yet�we�ll get to that in a bit.) Double-clicking on a single template in the tree view will compile that template, and the add-in will automatically show you the properties for that template:
In this case, I double-clicked on the CSHashtable.cst template, and these were the properties that automatically came up. If you click on the Generate button, what will happen is that the add-in will run the template and send its output to the active code editor window in the VS.NET IDE. So, for example, if I wanted to generate a new strongly-typed collection (or hashtable, in this case), all I have to do is choose File->New->File�->New Text Document from the VS.NET IDE menu and click on the Generate button to dump the output of the template into the new file, and save and add that file as part of my project. It can�t get any simpler than that.
The rest of the options on this tab are fairly self-explanatory, so let�s move on to the next section.
Viewing the Output
There might be times where you need to see the diagnostic output of the code generation engine. For example, if a template fails to compile, we need to know exactly why it failed so we can go about fixing the compilation error(s). With that in mind, the add-in sends all of its messages to the output window to make templates easier to debug:
The add-in adds two new output window panes to the output window:
- TaHoGen Build Output. This window will report any compile errors if they occur, in addition to any other error messages TaHoGen will display if something goes wrong.
- TaHoGen Generated Template Source. This window shows you what the code generator you created would have looked like if you were to write it by hand. (This is useful for debugging code generators with additional code written in some script blocks in the template markup.)
Editing Template Files from within Visual Studio .NET
The add-in makes it easy to edit your template files without having to leave the confines of the VS.NET IDE. All you have to do is right click on any template file, choose the Edit option from the context menu, and you can edit the template file from within the IDE, as seen from this image:
In addition, if you make any changes to your template files within the IDE and need to rebuild, all you have to do is right click on the selected template, and choose the Build option.
Adding and Removing Template Directories
The Options tab lets you choose the directories the add-in should use to recursively search for (both TaHoGen and CodeSmith) template files. For example, here�s how it would look if you decided to add a few sample templates from the CodeSmith installation folder:
Once you select a directory, the add-in will update the treeview in the Templates tab with the new list of templates, and you�ll be able to use them just as you would be able to use any other template.
Using the code
Despite its functionality, the add-in itself is no more than a thousand lines of (mostly) user interface code. I won�t bore you with all the details since most of it is boilerplate code; instead, we�ll just pick up where we left off in the previous article, and I�ll show you a few notable portions of the code so you can see what�s going on behind the scenes.
Compiling the Template
As I mentioned in the previous article, you can compile a single template using the following lines of code:
string sourceText = ���;
CompilerCallback callback = new CompilerCallback();
�
Assembly templateAssembly = TemplateCompiler.Compile(sourceText,
"", false, callback);
�
In this particular case, the add-in is reading the contents of a template file and sending its contents to the compiler. The TemplateCompiler
class has the following signature:
public static Assembly Compile(string text)
public static Assembly Compile(string text, bool addDebugSymbols)
public static Assembly Compile(string text, string outputFileName,
bool addDebugSymbols)
public static Assembly Compile(string text, string outputFileName,
bool addDebugSymbols,
ICompilerCallback compilerCallback)
�
The first three parameters are self-explanatory. The fourth parameter, however, needs a little more explanation. The ICompilerCallback
interface is useful if you need to show the results of a compilation to the user. It is defined as follows:
public interface ICompilerCallback
{
void BeginCompile(CompilerArgs args);
void EndCompile(CompilerArgs args);
}
The CompilerArgs
class, in turn, is defined as:
public class CompilerArgs
{
private string _source;
private CompilerErrorCollection _errors = new CompilerErrorCollection();
public CompilerArgs(string source, CompilerErrorCollection errors)
{
_source = source;
if (errors != null)
_errors.AddRange(errors);
}
public string CompiledCode
{
get { return _source; }
}
public CompilerErrorCollection Errors
{
get { return _errors; }
}
}
Getting the results from the compiler
In order to get the results from the compiler, I had to define my own class implementation of ICompilerCallback
:
using System;
using System.CodeDom.Compiler;
using System.Reflection;
using TaHoGen;
namespace TaHoGen.VSAddIn
{
public class CompilerCallback : ICompilerCallback
{
private string _templateCode;
#region ICompilerCallback Members
public void BeginCompile(CompilerArgs args)
{
Console.WriteLine("----------------- Compile Started ----------------");
Console.WriteLine();
}
public void EndCompile(CompilerArgs args)
{
_templateCode = args.CompiledCode;
foreach(CompilerError error in args.Errors)
{
string msg = string.Format("{0}({1}, {2}): error {3}: {4}",
error.FileName,
error.Line,
error.Column,
error.ErrorNumber,
error.ErrorText);
Console.WriteLine(msg);
}
Console.WriteLine("Build Complete - {0} Errors, 0 Warnings",
args.Errors.Count);
Console.WriteLine();
Console.WriteLine("---------------------- Done ----------------------");
Console.WriteLine();
string text = args.Errors.Count > 0 ? "Build Failed" :
"Build Succeeded";
Console.WriteLine(text);
}
#endregion
public string GeneratedTemplateCode
{
get { return _templateCode; }
}
}
}
As you can see, the implementation of the class is very straightforward. The CompilerCallback
class marks the beginning and end of the compilation process, reports any compilation errors if they occur, and saves the generated template code so that we can display it to the end user once it�s done compiling. The only thing that seems to be out of the ordinary here is the calls to Console.WriteLine()
� how do you get its output to show up on the output window in VS.NET?
Redirecting the Console Output
It turns out that the Console
class has a single static method named SetOut()
that allows us to redirect its output to any object derived from System.IO.TextWriter
. In this case, we�re redirecting the console output to a StringWriter
class so that we can dump its contents to an output window once the template finishes compiling:
StreamReader reader = new StreamReader(fileName);
string sourceText = reader.ReadToEnd();
StringWriter buildOutput = new StringWriter();
Console.SetOut(buildOutput);
CompilerCallback callback = new CompilerCallback();
Assembly templateAssembly = TemplateCompiler.Compile(sourceText,
"", false, callback);
�
_buildOutputPane.OutputString(buildOutput.ToString());
Showing the Compiled Template Code
Next, we need to show the generated template code:
�
_generatedSourcePane.OutputString(callback.GeneratedTemplateCode);
�
�and that�s all there is to sending text to an output window in VS.NET.
Adding Extra Output Panes
If you�re wondering how I added those extra output window panes, here�s the code I used to do it:
private static OutputWindowPane CreatePane(string paneName)
{
Window window =
ManagedAddIns.DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput);
OutputWindow outputWindow = window.Object as OutputWindow;
OutputWindowPane outputPane = null;
outputPane = null;
OutputWindowPanes panes = outputWindow.OutputWindowPanes;
for(int i = 1; i <= panes.Count; i++)
{
outputPane = panes.Item(i);
if (outputPane.Name == paneName)
return outputPane;
}
OutputWindowPane newPane = outputWindow.OutputWindowPanes.Add(paneName);
return newPane;
}
Attaching the Template to the PropertyGrid
Wiring a template object to a PropertyGrid
is equally just as simple. Once the template has been instantiated, the only thing left to do is to attach it to the property grid so that the user can set the template properties:
�
object template = Activator.CreateInstance(templateType);
_template = template as ITextGenerator;
propertyGrid1.SelectedObject = _template;
�
Sending the Template Output to the Active Code Editor Window
Lastly, the add-in sends the output of the template to the current code window using the following code:
TextSelection selection =
ManagedAddIns.DTE.ActiveDocument.Selection as TextSelection;
Debug.Assert(selection != null);
StringTarget target = new StringTarget();
target.Attach(_template);
target.Write();
selection.MoveTo(selection.CurrentLine, 1, false);
selection.Text = target.ToString();
Conclusion
Hopefully this article shows that you don�t need to write a complex client for TaHoGen in order for it to be useful. If you�ve read both of my articles on TaHoGen up to this point (including this one) and studied the sample code, then that�s all you�ll really need to know in order for you to get started. Good Luck!
Points of Interest
What surprised me about writing a simple COM-based add-in like this one was the amount of trouble it gave me when I had to deploy it on other people�s machines; it was a nightmare. Fortunately, I came across MutantDesign�s Managed AddIns which really made it easy to write a decent add-in in just a couple hundred lines of code. My hat is off to MutantDesign!
Special Thanks
- Phil Wright. Thanks for your effort in writing MagicLibrary!
- MutantDesign. I truly have to thank you guys for making this add-in easier to write. You have no idea how much trouble you saved me.
History
- 9/12/2005 - First release.