YAMG'�Yet Another Macro Generator

Environment: .NET

As experienced software developers, we have great disdain for doing mindless work. As previous articles have pointed out, the VS.NET Macro IDE is a terrific means of automating development tasks.

Frequently, both when refactoring code or writing new systems, I need to generate a large number of similar classes, or for some other reason, need to perform a similar change repeatedly. For example, take the following declaration in a VB module:

Private objData as DataSet

Say that you need to make this module-level variable specific on a per-session basis. If you needed to do that, you might replace the variable with the following code:

Private m_objDataCol as Collection
Public Property objData As DataSet
  Get
    Dim lfoo As Object
      If m_objDataCol Is Nothing Then
         m_objDataCol = New Collection()
      End If
   Try
      lfoo = m_objDataCol.Item(HttpContext.Current.Session. _
                               SessionID)
      Return m_objDataCol.Item(HttpContext.Current.Session. _
                               SessionID)
    Catch aexc As ArgumentException
      m_objDataCol.Add(New DataSet, HttpContext.Current.Session. _
                       SessionID)
     Return m_objDataCol.Item(HttpContext.Current.Session.SessionID)
    End Try
  End Get
  Set(ByVal Value As DataSet)
    Dim lfoo As Object
    If m_objDataCol Is Nothing Then
       m_objDataCol = New Collection()
    End If
    Try
      lfoo = m_objDataCol.Item(HttpContext.Current.Session. _
                               SessionID)
      m_objDataCol.Remove(HttpContext.Current.Session.SessionID)
      m_objDataCol.Add(Value, HttpContext.Current.Session. _
                       SessionID)
    Catch aexc As ArgumentException
      m_objDataCol.Add(Value, HttpContext.Current.Session. _
                       SessionID)
     End Try
  End Set
End Property

The problem is, of course, you end up doing a lot of cut and paste coding in the event that you have many variables to "sessionize" this way (by converting into properties).

For this instance of the issue, the solution is to build a macro that automatically "sessionizes" a module variable. The macro to do this is as follows:

Public Sub MakeGlobalVarSessionVar()

  Dim objTD As TextDocument = ActiveDocument.Object
  If Split(objTD.Selection.Text, " ").GetUpperBound(0) >= 3 Then
    'Class name is usually the 4th entry in the selection
    '(good enough for now)
    Dim lsCName As String = Split(objTD.Selection.Text, " ")(3)
    'Variable name is usually the 2nd entry in the selection
    '(again, good enough for now)
    Dim lsVName As String = Split(objTD.Selection.Text, " ")(1)
    Dim lsNewCode As String
    Dim lsbNewCode As New StringBuilder()
    lsbNewCode.Append("  Private m_{V}Col as Collection" & vbCrLf)
    lsbNewCode.Append("  Public Property {V} As {C}" & vbCrLf)
    lsbNewCode.Append("    Get" & vbCrLf)
    lsbNewCode.Append("      Dim lfoo As Object" & vbCrLf)
    lsbNewCode.Append("      If m_{V}Col Is Nothing Then" & vbCrLf)
    lsbNewCode.Append("         m_{V}Col = New Collection()" &
                                           vbCrLf)
    lsbNewCode.Append("      End If" & vbCrLf)
    lsbNewCode.Append("      Try" & vbCrLf)
    lsbNewCode.Append("        lfoo = m_{V}Col.Item(HttpContext.
                                      Current.Session.SessionID)" &
                                      vbCrLf)
    lsbNewCode.Append("        Return m_{V}Col.Item(HttpContext.
                                      Current.Session.SessionID)" &
                                      vbCrLf)
    lsbNewCode.Append("      Catch aexc As ArgumentException" &
                                           vbCrLf)
    lsbNewCode.Append("        m_{V}Col.Add(New {C}, HttpContext.
                                    Current.Session.SessionID)" &
                                    vbCrLf)
    lsbNewCode.Append("        Return m_{V}Col.Item(HttpContext.
                                      Current.Session.SessionID)" &
                                      vbCrLf)
    lsbNewCode.Append("      Catch exc As Exception" & vbCrLf)
    lsbNewCode.Append("        StoreError(exc)" & vbCrLf)
    lsbNewCode.Append("      End Try" & vbCrLf)
    lsbNewCode.Append("    End Get" & vbCrLf)
    lsbNewCode.Append("    Set(ByVal Value As {C})" & vbCrLf)
    lsbNewCode.Append("      Dim lfoo As Object" & vbCrLf)
    lsbNewCode.Append("      If m_{V}Col Is Nothing Then" & vbCrLf)
    lsbNewCode.Append("         m_{V}Col = New Collection()" &
                                           vbCrLf)
    lsbNewCode.Append("      End If" & vbCrLf)
    lsbNewCode.Append("      Try" & vbCrLf)
    lsbNewCode.Append("        lfoo = m_{V}Col.Item(HttpContext.
                                      Current.Session.SessionID)" &
                                      vbCrLf)
    lsbNewCode.Append("        m_{V}Col.Remove(HttpContext.Current.
                                    Session.SessionID)" & vbCrLf)
    lsbNewCode.Append("        m_{V}Col.Add(Value, HttpContext.
                                    Current.Session.SessionID)" &
                                    vbCrLf)
    lsbNewCode.Append("      Catch aexc As ArgumentException" &
                                        vbCrLf)
    lsbNewCode.Append("        m_{V}Col.Add(Value, HttpContext.
                                    Current.Session.SessionID)" &
                                    vbCrLf)
    lsbNewCode.Append("      Catch exc As Exception" & vbCrLf)
    lsbNewCode.Append("        StoreError(exc)" & vbCrLf)
    lsbNewCode.Append("      End Try" & vbCrLf)
    lsbNewCode.Append("    End Set" & vbCrLf)
    lsbNewCode.Append("  End Property" & vbCrLf)
    lsbNewCode.Replace("{V}", lsVName)
    lsbNewCode.Replace("{C}", lsCName)
    objTD.ReplacePattern(objTD.Selection.Text, lsbNewCode.ToString)
  End If
End Sub

This macro works by replacing a simple declaration with the more elaborate code requried to "sessionize" the variable using a property.

Whereas this solution is good for this particular case, there are many cases where coding really consists of parameterized replacement or parameterized generation of code. In particular, database-driven classes come to mind (for example, where the classes only differ by name and data fields). For such cases, I have designed a macro generator that writes the boilerplate macro code for code replacement macros:

'******************************************************************
    '     YAMG() (c) 2003, Aaron Erickson
    '
    '     VS.NET Macro that allows a user to easily write code
    '     generation macros
    '
    '     This Macro creates a new macro template that generates
    '     the code selected in the IDE
    '     It prompts for a Macro name.  The user can then add code
    '     to figure out the macro
    '     parameters and code replacement rules.
    '
    '     Use this macro when you encounter a situation where you
    '     have found a prototype class that is expected to spawn
    '     many similar classes
    '     In such a case:
    '          Write and test a class template
    '          Use this macro to generate the macro that writes
    '          a class based on this template
    '          Modify the macro to take in parameters from
    '          a selection, inputbox, or other means
    '          Run the generatedmacro as many times as required
    '          to generate code, or entire classes
    
'******************************************************************
    Public Sub YAMG()
        Dim objMacroDoc As TextDocument = 
            DTE.MacrosIDE.ActiveDocument.Object
        Dim objTD As TextDocument = DTE.ActiveDocument.Object
        Dim lsLinesArray() As String = 
            objTD.Selection.Text.Split(vbCrLf.ToCharArray())
        Dim ll As Long
        Dim lep As EditPoint
        Dim lsb As New StringBuilder()
        Dim lsMacroName As String = InputBox("What should the name
                                              of this macro be?")
        lep = objMacroDoc.StartPoint.CreateEditPoint()
        lep.FindPattern("Public Module")
        lep.LineDown(2)
        lsb.Append("    'TODO: Automatically Generated Macro from
                        'WriteTemplateMacro" & vbCrLf)
        lsb.Append("    Public Sub " & lsMacroName & "()" & vbCrLf)
        lsb.Append("        Dim objTD as TextDocument = 
                                ActiveDocument.Object" & vbCrLf)
        lsb.Append("        Dim lsbNewCode As New StringBuilder()"
                                & vbCrLf)
        lsb.Append("        'TODO: Put in parameter var
                            'declarations here" & vbCrLf)
        lsb.Append("        'For example:" & vbCrLf)
        lsb.Append("        'Dim lsVName As String = 
                            '    Split(objTD.Selection.Text, "" "")
                            '    (1)" & vbCrLf)
        For ll = 0 To lsLinesArray.GetUpperBound(0) Step 2
            lsb.Append("        lsbNewCode.Append(""" &
                                lsLinesArray(ll).Replace("""",
                                                         """""") &
                                  """ & vbCrLf)" & vbCrLf)
        Next ll
        lsb.Append("        'TODO: Put in parameter replacement
                            'commands here" & vbCrLf)
        lsb.Append("        'For example:" & vbCrLf)
        lsb.Append("        'lsbNewCode.Replace(""{V}"", lsVName)"
                            '& vbCrLf)
        lsb.Append("        If objTD.Selection.Text.Length > 0
                               Then" & vbCrLf)
        lsb.Append("            objTD.ReplacePattern(objTD.
                                      Selection.Text,
                                      lsbNewCode.ToString)" &
                                      vbCrLf)
        lsb.Append("        Else" & vbCrLf)
        lsb.Append("            objTD.Selection.ActivePoint().
                                      CreateEditPoint().
                                      Insert(lsbNewCode.ToString)"
                                      & vbCrLf)
        lsb.Append("        End If" & vbCrLf)
        lsb.Append("    End Sub" & vbCrLf)
        lep.Insert(lsb.ToString())
    End Sub

This macro makes it possible to easily write the replacement macro I wrote above, with the only coding task being to, within the generated macro, make changes to determine the macro parameters and apply the appropriate code replacements (comments are generated to help with this).

This macro could easily be extended to be able to generate class generators in VS.NET, by adding code to create and name the new class (easily available within the DTE object model). While I have just discovered this (and in fact, only started playing around with macros in VS.NET yesterday), I am sure there are numerous uses for this type of "macro-macro."



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds