![]() |
Languages »
C# »
Attributes
Intermediate
Make NDoc compile the code examples contained in your documentation using NLiterateBy Jonathan de HalleuxAn utility that merges and recompiles the examples in your documentation using NDoc. |
C#, Windows, .NET 1.0, .NET 1.1VS.NET2003, Dev
|
|
Advanced Search |
|
|
|
||||||||||||||||

NLiterate is a tool that allows to write C# documentation in the Literate Programming Style (LPS). It merges, compiles and runs the code snippets contained in the comments, keeping the documentation and the source code synchronized. NLiterate comes as a custom documenter for NDoc (see [3]), so you easily use it through the NDoc GUI.
Disclaimer: The intention of NLiterate is not to replace the C# code, far from that. It mainly is a tool to automate the compilation of code examples contained in the documentation.
The literate programming style developed by the mighty D.Knuth consists of writing source code and documentation together in the same file. A tool then automatically converts the file into both a pure source code file and into a documentation file with pretty-printed source code. The literate programming style makes it easier to ensure that the code examples in the book/documentation really compile and run and that they stay consistent with the text.
The source code for each example is broken up into parts. Parts can include references to other parts. An example often starts with a part that provides an outline for the entire computation, which is then followed by other parts that fill in the details.
For that task, D.Knuth has developed the WEB language (later on CWEB, FWEB) which was targeted for C (source code) and TeX (word processor).
"The structure of a software program may be thought of as a web that is made up of many interconnected pieces. To document such a program, we want to explain each individual part of the web and how it relates to its neighbors. The typographic tools provided by TEX give us an opportunity to explain the local structure of each part by making that structure visible, and the programming tools provided by Pascal make it possible for us to specify the algorithms formally and unambiguously. By combining the two, we can develop a style of programming that maximizes our ability to perceive the structure of a complex piece of software, and at the same time the documented programs can be mechanically translated into a working software system that matches the documentation." Extracted from The WEB System of Structured Documentation User Manual (see [1]).
A large piece of this introduction is extracted from the Boost Graph Library User Guide (see [2]). For further information on Literate Programming, refer to the Literate Programming Official Page (see [1]).
C# documentation lets you integrate snippets of code in the documentation using the code tag. Ex:
/// <remarks>
/// This methods does something:
/// <code>
/// MyClass c = new MyClass();
/// c.SomeMethod();
/// </code>
/// </remarks>
On one hand, examples take an important place in a good documentation. A lot of users prefer to learn from examples rather from a "literal" description of the method (think about MSDN doc). On the other, examples in the documentation are difficult to maintain: they are not recompiled, not run and not tested. When the number of examples becomes large, it will take more and more time to test each of the examples.
An out-of-sync example will make users lose time and will do more harm than nothing. Therefore, the maintenance problem of examples is a critical one.
NLiterate applies LPS to source code contained in C# comments. Therefore, it lets the documenter write interconnected snippets using the LPS style and it provides an automated tool to compile and execute those snippets.
In this example, we are going to build a simple "Hello World" code example. We suppose that this sample is used as an example for an imaginary XString class.
Let's start from the following empty documentation:
/// <example> /// </example> public XString {...}
First, we write the Hello World sample:
/// <example>
/// <para>
/// <c>HelloWorld</c> is a classic piece of code.
/// </para>
/// <code id="helloworld" title="HelloWorld" compilable="true" language="cs">
/// using System;
/// public class HelloWorld
/// {
/// public static void Main(string[] argv)
/// {
/// Console.WriteLine("Hello Word");
/// }
/// }
/// </code>
There are several remarks on the code above:
id identifies the section, title is the title of the snippet, compilable tells NLiterate that this snippet should be compiled, that it is a root snippet, and language gives the programming language (cs for C#, vb for Visual Basic). Sometimes, examples can become quite long and it would be interesting to "split up" the example in different parts that you could explain separately. For example, we would like to separate the body of the HelloWorld.Main method from the main code snippet:
/// <example>
/// <para>
/// <c>HelloWorld</c> is a classic piece of code.
/// </para>
/// <code id="helloworld" title="HelloWorld" compilable="true" language="cs">
/// using System;
/// public class HelloWorld
/// {
/// public static void Main(string[] argv)
/// {
/// {[mainbody Implementation of Main]}
/// }
/// }
/// </code>
/// <para>
/// The implementation of Main can be added later:
/// <code id="mainbody" title="HelloWorld.Main">
/// Console.WriteLine("Hello World");
/// </code>
/// </para>
Note that in the first snippet, we have replaced the body of the Main method by a reference to another snippet:
{[ref-id title...]}
This snippet will be merged in place of the reference in the previous snippet. You can continue like this to describe and build a tree of code snippet in your documentation.
By default, NLiterate adds System.dll and the declaring assembly to the list of referenced assemblies by the compiler. If you need other assemblies, you can use the reference tag as follows:
/// <remarks>
/// <reference assembly="MyAssembly.dll"/>
/// </remarks>
...
The full code of this class is contained in the NLiterate project. Recompile the project with documentation enabled.
First, you need to prepare NDoc to run NLiterate. This is done by the following steps:
When you open NDoc, click on the Documenter list box and choose Literate. You can then simply launch project compilation in the NDoc GUI to launch the NLiterate compilation process.
The NLiterate implementation is quite straightforward, thanks to the .NET Framework tools. In fact, a lot of the ingredients needed where already present in the framework:
Having all those tools under the hood, the NLiterate execution logic is as follows:
System.CodeDom.Compiler) namespace. Code sections that have the attribute compilable="true" are located by NLiterate. They are the root of the tree composed by the code and the reference it contains. For example, the code section with @id="main" is the root of a tree that has one child snippetid:
/// <code id="main" compilable="true">
/// {[snippetid Some comment]}
/// </code>
...
/// <code id="snippetid" >
/// ...
/// </code>
Such code sections are easily located using the following XPath expression: "descendant::code[@compilable='true']".
The syntax to create a reference is as follows:
{[snippetid Comments describing the snippets]}
where snippetid is the id of the reference snippet, while the result of the text is used to describe the snippet. All is enclosed in {[...]}. The references are matched using regular expressions and the Regex class. To build the regular expression, I recommend Expresso (see [4]):
private static Regex snippetRegex = new Regex(
@"{\[(?<SNIPPETID>\w+\b).*?\]}",
RegexOptions.IgnoreCase
| RegexOptions.Multiline
| RegexOptions.ExplicitCapture
| RegexOptions.Compiled
);
With this tool, location of the reference is a piece of cake:
string code; // the code snippet
foreach(Match m in snippetRegex.Matches(code))
{
string refid = m.Groups["SnippetID"].Value;
...
}
We then apply again XPath queries to find the reference code section in the XML document. Note that if you are not happy with this notation, it can easily be modified by changing the regular expression.
At this point, we have built a tree where each node contains the "raw" code and a list of reference snippets. Again, with regular expressions, merging the parts is straight-forward:
public String GetMergedCode()
{
return snippetRegex.Replace(this.code,new MatchEvaluator(this.CodeMerger));
}
private string CodeMerger(Match m)
{
// get id
string cid = m.Groups["SnippetID"].Value;
// find code snippet with the right id
CodeSnippetNode child = findNodeByID(cid);
// return child merged code
return child.GetMergedCode()
}
Compilation is done using the System.CodeDom.Compiler classes. Rather than detailing this part, I recommend users to read the DotNetScript article (see [5]) on CodeProject.
General
News
Question
Answer
Joke
Rant
Admin
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 26 Apr 2004 Editor: Smitha Vijayan |
Copyright 2004 by Jonathan de Halleux Everything else Copyright © CodeProject, 1999-2009 Web16 | Advertise on the Code Project |