collapse Table of Contents
  1. Writing Documentation for mdoc - Jonathan Pryor's web log
    1. Writing Documentation for mdoc

Writing Documentation for mdoc - Jonathan Pryor's web log

« Using mdoc | Main | mdoc XML Schema »

Writing Documentation for mdoc

Last time, we create an assembly and used mdoc to generate a documentation repository containing stubs. Stubs have some utility -- you can view the types, members, and parameter types that are currently present -- but they're far from ideal. We want actual documentation.

Unfortunately, mdoc isn't an AI, and can't write documentation for you. It manages documentation; it doesn't create it.

How do we get actual documentation into the respository? There are three ways:

  1. Manually edit the XML files within the repository directory (if following from last time, this would be all .xml files within the Documentation/en directory.
  2. Use monodoc --edit Documentation/en.
  3. We can continue writing XML documentation within our source code.

Manually editing the files should be self-explanatory; it's not exactly ideal, but it works, and is how I write most of my documentation.

When using monodoc --edit Documentation/en, the contents of Documentation/en will be shown sorted in the tree view by it's assembly name, e.g. in the Mono Documentation → Demo node. When viewing documentation, there are [Edit] links that, when clicked, will allow editing the node (which directly edits the files within Documentation/en.

However, I can't recommend monodoc as an actual editor. It's usability is terrible, and has one major usability flaw: when editing method overloads, most of the documentation will be the same (or similar enough that you'll want to copy everything anyway), e.g. <summary/>, <param/>, etc. The monodoc editor doesn't allow copying all of this at once, but only each element individually. It makes for a very slow experience.

Which brings us to inline XML documentation. mdoc update supports importing XML documentation as produced by csc /doc. So let's edit our source code to add inline documentation:

using System;

namespace Cadenza {
    /// <summary>
    ///  Extension methods on <see cref="T:System.Object" />.
    /// </summary>
    public static class ObjectCoda {
        /// <typeparam name="TSource">The type to operate on.</typeparam>
        /// <typeparam name="TResult">The type to return.</typeparam>
        /// <param name="self">
        ///   A <typeparamref name="TSource" /> containing the value to manipulate.
        ///   This value may be <see langword="null" /> (unlike most other
        ///   extension methods).
        /// </param>
        /// <param name="selector">
        ///   A <see cref="T:System.Func{TSource,TResult}" /> which will be
        ///   invoked with <paramref name="self" /> as a parameter.
        /// </param>
        /// <summary>
        ///   Supports chaining otherwise temporary values.
        /// </summary>
        /// <returns>
        ///   The value of type <typeparamref name="TResult" /> returned by
        ///   <paramref name="selector" />.
        /// </returns>
        /// <remarks>
        ///   <para>
        ///     <c>With</c> is useful for easily using an intermediate value within
        ///     an expression "chain" without requiring an explicit variable
        ///     declaration (which is useful for reducing in-scope variables, as no
        ///     variable is explicitly declared).
        ///   </para>
        ///   <code lang="C#" src="../../example.cs#With" />
        /// </remarks>
        /// <exception cref="T:System.ArgumentNullException">
        ///   <paramref name="selector" /> is <see langword="null" />.
        /// </exception>
        public static TResult With<TSource, TResult>(
                this TSource self, 
                Func<TSource, TResult> selector)
        {
            if (selector == null)
                throw new ArgumentNullException ("selector");
            return selector (self);
        }
    }
}

(As an aside, notice that our file ballooned from 14 lines to 45 lines because of all the documentation. This is why I prefer to keep my documentation external to the source code, as it really bloats the source. Certainly, the IDE can hide comments, but I find that this defeats the purpose of having comments in the first place.)

Compile it into an assembly (use csc if running on Windows), specifying the /doc parameter to extract XML documentation comments:

$ gmcs /t:library /out:Demo.dll /doc:Demo.xml demo.cs

Update our documentation repository, but import Demo.xml:

$ mdoc update -o Documentation/en -i Demo.xml Demo.dll --exceptions=added
Updating: Cadenza.ObjectCoda
Members Added: 0, Members Deleted: 0

(No members were added or deleted as we're only changing the documentation, and didn't add any types or members to the assembly.)

Now when we view ObjectCoda.xml, we can see the documentation that was present in the source code.

However, notice one other change. In the documentation we wrote, we had:

        ///   <code lang="C#" src="../../example.cs#With" />

Yet, within ObjectCoda.xml, we have:

          <code lang="C#" src="../../example.cs#With">Console.WriteLine(
    args.OrderBy(v => v)
    .With(c => c.ElementAt (c.Count()/2)));
</code>

What's going on here? What's going on is that mdoc will search for all <code/> elements. If they contain a //code/@src attribute, the specified file is read in and inserted as the //code element's value. The filename specified in the //code/@src attribute is relative to the documentation repository root. A further extension is that, for C# code, if the filename has an "anchor", a #region block of the same name is searched for within the source code.

The ../../example.cs file referenced in the //code/@src value has the contents:

using System;
using System.Linq;
using Cadenza;

class Demo {
    public static void Main (string[] args)
    {
        #region With
        Console.WriteLine(
            args.OrderBy(v => v)
            .With(c => c.ElementAt (c.Count()/2)));
        #endregion
    }
}

This makes keeping documentation examples actually compiling trivial to support. For example, I'll have documentation refer to my unit tests, e.g.

<code lang="C#" src="../../Test/Cadenza/ObjectTest.cs#With" />

One final point worth mentioning: you can import documentation as often as you want. The imported documentation will always overwrite whatever is already present within the documentation repository. Consequently, if you want to use mdoc for display purposes but want to continue using inline XML documentation, always import the compiler-generated .xml file.

Now, we can update our HTML documentation:

$ mdoc export-html -o html Documentation/en
Cadenza.ObjectCoda

The current Demo.dll documentation.

Next time, we'll cover customizing the static HTML output.

Posted on 10 Jan 2010 | Path: /development/mono/mdoc/ | Permalink
blog comments powered by Disqus