Tuesday, November 18, 2008

DOxygen and Dev Studio

So, with a little more time on my hands, I've been working on some portfolio stuff. For me, this means coding. Now at work, I had a handily little macro that I had scavenged together that added DOxygen comments to my code (bound to a keypress). Unfortunately, I left all that at work (and I don't have it backed up anywhere).

I've also had some time to play around with some CSS and jScript from Google code. This is pretty neat: http://code.google.com/p/syntaxhighlighter/ and it was very easy to add to this blog. It supports a variety of languages and looks awesome. I recommend you try it!

So, this was an excellent opportunity for me to create a new one and post it here. As well, I can discuss some of the things I ran into along my way.

It's pretty simple how I went about developing this set of macros. First off, I wanted one 'entry' function for determining what kind of comment I was making. This was pretty simple, and the method I used for determining if I'm commenting the top of a file, a class, a method or a function is pretty straightforward (potentially not the best, but I'll juice that up as time goes on). I boil it down to this:

a) if the current line is line zero, it's a file header comment.
b) if the current line contains a 'class' keyword, we're documenting a class
c) if the current line contains a '::', we're documenting a method
d) if the next line has a '{', it's a function

Like I said, it's pretty simple (read: stupid) but I'll enhance it later.

After that, it's just a matter of populating the comments with the right data.

Now, I did run into an interesting problem (which is where the majority of my day went). One thing I wanted to do was process all my method parameters and generate documentation for them. So, the process for that went like this:

1) generate an 'arguments' string. This is essentially everything between the '(' and ')'. This is a little problematic, because I need to deal with parameters that are spread across multiple lines. This was pretty easy to do. Then came stripping out the unnecessary characters (left and right trimming the '(' and ')' characters). I figured that I could do a string replace for all '(' and ')' characters with a space. Then use the Trim function in vb to strip out what I didn't want. However, I noticed that I was still having spaces at the end of my string. Or at least what I thought was spaces. It took a while for me to realize that they were non-printable characters ( specifically, there was a carriage return embedded before my ')' ... which wasn't in the source file). Anyway, there's a host of other reasons why I'd want to get rid of additional characters in my argument list, so I built a function that trimmed out additional 'fluff' characters, including tabs, line feeds, carriage returns and a few other non-printable characters). And then Trimmed the results. The code looks like this:

First, creating the argument string:
' Get all the arguments from the method
Dim args As String
Dim linenumber As Integer
selection.SelectLine()
args = selection.Text

' strip off the method return/method name (everything before the '('
Dim argsarray() As String = Split(args, "(")
Dim nextline As String
args = argsarray(1)

While InStr(args, ")") = 0
selection.LineDown()
nextline = selection.Text
args = args & nextline
End While

args = Replace(args, ")", " ")
args = TrimAll(args)
argsarray = Split(args, ",")
selection.GotoLine(linenumber)


Second, the TrimAll function:
Function TrimAll(ByVal TextIn As String) As String
Try ' Replace ALL Duplicate Characters in String with a Single Instance
Dim result As String
result = Replace(TextIn, ")", " ")
result = Replace(result, vbTab, " ")
result = Replace(result, vbNullChar, " ")
result = Replace(result, vbCr, " ")
result = Replace(result, vbLf, " ")
result = Trim(result)
TrimAll = result

Catch Exp As Exception
TrimAll = TextIn ' Oops
End Try

Return TrimAll

End Function


2) tokenize the arguments. VB has a 'split' function that breaks a string up into a string array based on a delimiter character. So, to break it down into arguments, it's a matter of splitting on a ',' character. After this, use VB's 'For Each' iterator to walk over the array of arguments and then tokenize each argument into another string array to get a list of types and variables. Simply grabbing the last array element gives me my variable name, which I can then print out.

OK. So after all that, here's the full code listing. Copy and paste into a new module. You'll probably want to change the 'Ash Matheson' to something more appropriate.
---------------------------------------cut here------------------------------------
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports EnvDTE90
Imports System.Diagnostics

Public Module Documentation
Function TrimAll(ByVal TextIn As String) As String

Try ' Replace ALL Duplicate Characters in String with a Single Instance
Dim result As String
result = Replace(TextIn, ")", " ")
result = Replace(result, vbTab, " ")
result = Replace(result, vbNullChar, " ")
result = Replace(result, vbCr, " ")
result = Replace(result, vbLf, " ")
result = Trim(result)
TrimAll = result

Catch Exp As Exception
TrimAll = TextIn ' Oops
End Try

Return TrimAll

End Function
Sub PerformDocumentation()
Try
Dim objTextDoc As TextDocument
Dim editPoint As EnvDTE.EditPoint
Dim selection As TextSelection

objTextDoc = DTE.ActiveDocument.Object("TextDocument")
editPoint = objTextDoc.StartPoint.CreateEditPoint
selection = objTextDoc.Selection
selection.StartOfLine()

If selection.ActivePoint.Line = 1 Then
GenerateFileHeader()
ElseIf selection.FindText("class") = True Then  'Class declaration
GenerateClassHeader()
ElseIf selection.FindText("::") = True Then     'Method definition
GenerateMethodHeader()
Else
selection.LineDown()
If selection.FindText("{") Then             'Some Function definition
selection.LineUp()
GenerateFunctionHeader()

End If
End If

Catch ex As Exception
Dim messagebox As System.Windows.Forms.MessageBox
messagebox.Show(ex.Message)

Finally

End Try
End Sub
Sub GenerateMethodHeader()
DTE.UndoContext.Open("MethodHeader")
Try
Dim selection As TextSelection
selection = DTE.ActiveDocument.Selection

' Get all the arguements from the method
Dim args As String
Dim linenumber As Integer
linenumber = selection.ActivePoint.Line
selection.SelectLine()

args = selection.Text

' strip off the method return/method name (everything before the '('
Dim argsarray() As String = Split(args, "(")
Dim nextline As String
args = argsarray(1)
While InStr(args, ")") = 0
selection.LineDown()
nextline = selection.Text
args = args & nextline
End While
args = Replace(args, ")", " ")
args = TrimAll(args)

argsarray = Split(args, ",")

selection.GotoLine(linenumber)
selection.StartOfLine()
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// *********************************************************************"
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// @brief "
linenumber = selection.ActivePoint.Line ' This is where we want to return to
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// "
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// "
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// @return "

' now for our args
For Each arguement As String In argsarray
Dim tokenArray() As String
Dim character As Char

selection.NewLine()
selection.StartOfLine()

tokenArray = Split(Trim(arguement), " ")
selection.Text = "/// @param " & tokenArray(tokenArray.Length - 1)

Next

selection.NewLine()
selection.StartOfLine()
selection.Text = "/// *********************************************************************"
selection.NewLine()

selection.GotoLine(linenumber)

Catch ex As Exception
Dim messagebox As System.Windows.Forms.MessageBox
messagebox.Show(ex.Message)

Finally
DTE.UndoContext.Close()
End Try

End Sub
Sub GenerateFunctionHeader()
DTE.UndoContext.Open("FunctionHeader")
Try
Dim selection As TextSelection
selection = DTE.ActiveDocument.Selection

' Get all the arguements from the method
Dim args As String
Dim linenumber As Integer
selection.SelectLine()
args = selection.Text

' strip off the method return/method name (everything before the '('
Dim argsarray() As String = Split(args, "(")
Dim nextline As String
args = argsarray(1)
While InStr(args, ")") = 0
selection.LineDown()
nextline = selection.Text
args = args & nextline
End While
args = Replace(args, ")", " ")
args = TrimAll(args)

argsarray = Split(args, ",")

selection.NewLine()
selection.StartOfLine()
selection.Text = "/// *********************************************************************"
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// @brief "
linenumber = selection.ActivePoint.Line ' This is where we want to return to
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// "
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// "
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// @return "
' now for our args
For Each arguement As String In argsarray
Dim tokenArray() As String
Dim character As Char

selection.NewLine()
selection.StartOfLine()

tokenArray = Split(Trim(arguement), " ")
selection.Text = "/// @param " & tokenArray(tokenArray.Length - 1)

Next
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// *********************************************************************"

selection.GotoLine(linenumber)

Catch ex As Exception

Finally
DTE.UndoContext.Close()
End Try

End Sub
Sub GenerateClassHeader()
DTE.UndoContext.Open("ClassHeader")
Try
Dim selection As TextSelection
Dim linenumber As Integer

selection = DTE.ActiveDocument.Selection

selection.StartOfLine()
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// *********************************************************************"
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// @brief "
linenumber = selection.ActivePoint.Line ' This is where we want to return to
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// "
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// "
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// *********************************************************************"
selection.NewLine()

selection.GotoLine(linenumber)
selection.EndOfLine()

Catch ex As Exception
Finally
DTE.UndoContext.Close()
End Try
End Sub
Sub GenerateFileHeader()
DTE.UndoContext.Open("Generate File Header")
Try
DTE.ActiveDocument.Selection.StartOfDocument()
Dim CurrentDate
CurrentDate = System.DateTime.Now.ToShortDateString()
Dim CurrentTime
CurrentTime = System.DateTime.Now.ToShortTimeString()
Dim Author = "Ash Matheson"
Dim FileType
Dim selection As TextSelection
selection = DTE.ActiveDocument.Selection

selection.Text = "/// Time-stamp: <@(#)" & ActiveDocument.Name & "   " & CurrentDate & " - " & CurrentTime & "   " & Author & ">"
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// *********************************************************************"
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// @file   : " & ActiveDocument.Name
selection.NewLine()
selection.StartOfLine()
selection.Text = "///"
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// @author " & Author
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// @date  " & CurrentDate
selection.NewLine()
selection.StartOfLine()
selection.Text = "///"
selection.NewLine()
selection.StartOfLine()
If InStr(ActiveDocument.Name, ".h") <> 0 Then
FileType = "Declaration of class "
Else
FileType = "Implementation of methods for class "
End If
selection.Text = "///  Purpose : " & FileType
selection.NewLine()
selection.StartOfLine()
selection.Text = "///"
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// @brief "
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// *********************************************************************"
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// Version History:"
selection.NewLine()
selection.StartOfLine()
selection.Text = "///"
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// V 0.10  " & CurrentDate & "  BN : First Revision"
selection.NewLine()
selection.StartOfLine()
selection.Text = "///"
selection.NewLine()
selection.StartOfLine()
selection.Text = "/// *********************************************************************"
selection.NewLine()
selection.StartOfLine()
selection.NewLine()
selection.NewLine()

Catch ex As Exception
Dim messagebox As System.Windows.Forms.MessageBox
messagebox.Show(ex.Message)

Finally
DTE.UndoContext.Close()
End Try

End Sub

End Module

---------------------------------------cut here------------------------------------
linkage, for downloading: link

No comments: