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.
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!
In addition to the TaHoGen Core Libraries from the previous article, you’re going to need:
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.
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:
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.
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.
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.
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:
TemplateCompiler
// (not shown)
…
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:
ICompilerCallback
public interface ICompilerCallback
{
void BeginCompile(CompilerArgs args);
void EndCompile(CompilerArgs args);
}
The CompilerArgs class, in turn, is defined as:
CompilerArgs; }
}
}
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)
{
// Save the compiled code so that
// we can show it to the user later
_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?
CompilerCallback
Console.WriteLine()
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:
Console
SetOut()
System.IO.TextWriter
StringWriter
StreamReader reader = new StreamReader(fileName);
string sourceText = reader.ReadToEnd();
// The StringWriter will store the output of the compiler
StringWriter buildOutput = new StringWriter();
Console.SetOut(buildOutput);
// Compile the template
CompilerCallback callback = new CompilerCallback();
Assembly templateAssembly = TemplateCompiler.Compile(sourceText,
"", false, callback);
…
// Send the results to the output window
_buildOutputPane.OutputString(buildOutput.ToString());
Next, we need to show the generated template code:
…
// Show the compiled code
_generatedSourcePane.OutputString(callback.GeneratedTemplateCode);
…
…and that’s all there is to sending text to an output window in VS.NET.
If you’re wondering how I added those extra output window panes, here’s the code I used to do it:
/// <summary>
/// Creates a special pane in the output window of the VS.NET IDE.
/// </summary>
/// <param name="paneName">The name of the pane to create.</param>
/// <returns>An OutputWindowPane.</returns>
private static OutputWindowPane CreatePane(string paneName)
{
// We’re interested only in output windows
Window window =
ManagedAddIns.DTE.Windows.Item(EnvDTE.Constants.vsWindowKindOutput);
OutputWindow outputWindow = window.Object as OutputWindow;
OutputWindowPane outputPane = null;
outputPane = null;
OutputWindowPanes panes = outputWindow.OutputWindowPanes;
// Reuse the existing pane (if it exists)
for(int i = 1; i <= panes.Count; i++)
{
outputPane = panes.Item(i);
if (outputPane.Name == paneName)
return outputPane;
}
// Otherwise, we’re going to create a new pane
OutputWindowPane newPane = outputWindow.OutputWindowPanes.Add(paneName);
return newPane;
}
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:
PropertyGrid
…
// Instantiate the template type
object template = Activator.CreateInstance(templateType);
_template = template as ITextGenerator;
// Once we're done compiling, we
// need to give the user a chance to
// set the properties for the template.
propertyGrid1.SelectedObject = _template;
…
Lastly, the add-in sends the output of the template to the current code window using the following code:
// Output the generated code at the current selection
TextSelection selection =
ManagedAddIns.DTE.ActiveDocument.Selection as TextSelection;
Debug.Assert(selection != null);
// Save the output of the template
// when it runs
StringTarget target = new StringTarget();
target.Attach(_template);
// Run the template itself
target.Write();
// Send the template output to
// the current selection
selection.MoveTo(selection.CurrentLine, 1, false);
selection.Text = target.ToString();
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!
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!
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.
A list of licenses authors might use can be found here
Dave Cross wrote:Where's part 1?
CodeTemplate
General News Suggestion Question Bug Answer Joke Praise Rant Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.