:-$

Ryan's work blog

My Links

News

The WeatherPixie
Subscribe with Bloglines
About this blog

Tools I use:

Post Categories

Article Categories

Archives

Image Galleries

Blog Stats

Personal

Projects

Random Blogs

Random other

Reference

Web comics

Work

Code Generation with Python, Cog, and Nant

We've been using C# for a couple of years now, and are getting tired of the verbosity. Especially tired of copy/pasting and changing a couple of identifiers, and I imagine many other people are, too. After seeing some of the macro capabilities of Lisp, we got jealous. After some googling and browsing, I ran across Ned Batchelder's python-based code generation tool, Cog.

Cog lets you build ad-hoc code generators in python, in your source code files. Here's an example from Ned's site:
   /*[[[cog
   import cog
   fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
   for fn in fnames:
      cog.outl("void %s();" % fn)
   ]]]*/
   void DoSomething();
   void DoAnotherThing();
   void DoLastThing();
   //[[[end]]]
   
You have some python code in a [[[cog ]]] block, which gets executed to create code in whatever desired language. This lets us write macros in python for C#.
I use Cog in concert with Nant and Visual Studio for working on C# projects. I have a nant build script to run Cog on all appropriate file types, and then compile my visual studio solution using Nant. I build the python macros in Xemacs, testing them using the interpreter built in there. It's a bit a rube-goldberg contraption, but it works pretty well.
  1. Download and install Python, Cog, Nant, and Xemacs.
  2. Make another project for the python templates/macros.
  3. Add a "default.build" to the solution (Right click on the Solution node in the tree, and choose Add Item)
  4. Also Add the cog.py file from your python/Scripts folder. The Cog install should've put it there. There's probably a better way to do this, but having the cog.py here makes the nant build file easy.
  5. Put the following XML in your default.build file:
          
    <project name="default" default="debug" basedir=".">
    
       <!-- This build target loops through and runs cog on every .cs, .js, and .xul file in the solution. -->
       <target name="codegen">
          <foreach item="File" property="filename"> 
             <in> 
                <items> 
                   <include name="**.cs" />
                   <include name="**.js" />
                   <include name="**.xul" />
                </items> 
             </in> 
             <do> 
                <exec program="python">
                   <arg value="cog.py" />
                   <arg value="-r" />
                   <arg value="${filename}" />
                </exec>
             </do> 
          </foreach>
       </target>
       
       <!-- This build target compiles the solution in "release" mode, and calls the codegen target automatically. -->
       <target name="release" description="compiles in release mode" depends="codegen" unless="${target::has-executed('release')}">
          <solution configuration="release" solutionfile="Ryan.sln" />
       </target>
       
       <!-- This build target compiles the solution in "debug" mode, and calls the codegen target automatically. -->
       <target name="debug" description="compiles the solution, then runs the tests" depends="codegen" unless="${target::has-executed('debug')}">
          <solution configuration="debug" solutionfile="Ryan.sln" />
       </target>
    </project>
    
    Change the Ryan.sln to be the name of your solution.

  6. Your solution should look something like this:

    (Mokeys and BayesianCat are the projects I'm generating with)
  7. Now, open up Xemacs, and start making files in your Templates project. Put an __init__.py in each folder in the Templates project. This specifies that folder as a package, and lets you refer to it in code elsewhere easily.
  8. When using them [[[cog]]] blocks, import your templates and go at it.
    /*[[[cog
    import cog
    from Templates.BayesianCat import Bayesian
    
    cog.outl(Bayesian.makeJS())
    
    ]]]*/
    //[[[end]]]
    
  9. Open a command prompt to your solution root, and run nant to generate and build your solution.
I think the setup I have is far from ideal, and am still figuring out good python macros, and good ways to use them. Here are a few examples.

Making some XUL
def makeUI():
    return ui.window([includes.includejs('Bayesian.js'),
                      ui.groupbox('Input', [ui.labelbox('message', 'Enter the message to be scored', True),
                                            ui.button('Score', onclick='Score();'),
                                            ui.labelbox('category', 'Create a new category'),
                                            ui.button('Add')])])   
This puts together some textboxes and buttons, using a ui module I defined elsewhere. It looks a little Lispy, and ends up being 12 lines of valid XML.
Making some C#
def fileNameWithCheck(fileName, comment='the file, with existence check on setting.'):
    return '''
protected string _%(Name)sFileName;
/// <summary>
/// %(Comment)s
/// </summary>
public string %(Name)sFileName {
   get{ return _%(Name)sFileName;}
   set {
      if(File.Exists(value)) _%(Name)sFileName = value;
      else throw new FileNotFoundException(\"The file \" + value + \" was not found!\");
   }
}''' % {'Name' :fileName, 'Comment':comment}   
   
This makes an accessor for a filename, and does the existence check against the file system.
As with everything, I may be doing it all wrong, but this jury-rig is certainly faster than pure C#. Once I figure out a way to have a nicer library of templates and macros, I'll post about it.
UPDATE: Apparently this is getting a lot of traffic, which is pretty neat, although I'm a bit nervous about the minds frequenting lambda-the-ultimate looking over my shoulder. I just wanted to add that I got a much more efficient Nant target that should be used in place of the codegen target above, and I found a decent way to use a central library of templates and macros without mucking with sys.path.

posted on Tuesday, January 25, 2005 4:12 PM