|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionNLiterate 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. About Literate ProgrammingThe 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]). Examples of code in the documentationC# documentation lets you integrate snippets of code in the documentation using the /// <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 to the rescueNLiterate 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. QuickStart ExampleIn 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 Writing the documentationLet'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:
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 /// <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 {[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. Referencing assembliesBy 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 /// <remarks>
/// <reference assembly="MyAssembly.dll"/>
/// </remarks>
...
The full code of this class is contained in the NLiterate project. Recompile the project with documentation enabled. Run NLiterate in NDocFirst, 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. ImplementationThe 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:
Load XMLLocate Snippets in the XMLCode sections that have the attribute /// <code id="main" compilable="true">
/// {[snippetid Some comment]}
/// </code>
...
/// <code id="snippetid" >
/// ...
/// </code>
Such code sections are easily located using the following XPath expression: Build the treeThe syntax to create a reference is as follows: {[snippetid Comments describing the snippets]}
where 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. Merge snippetsAt 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()
}
CompileCompilation is done using the History
References
|
||||||||||||||||||||||