Nocco is a quick-and-dirty, literate-programming-style documentation
generator. It is a C# port of Docco,
which was written by Jeremy Ashkenas in
Coffescript and runs on node.js.
Nocco produces HTML that displays your comments alongside your code.
Comments are passed through
Markdown, and code is
highlighted using google-code-prettify
syntax highlighting. This page is the result of running Nocco against its
own source files.
Currently, to build Nocco, you'll have to have Visual Studio 2010. The project
depends on MarkdownSharp and you'll
have to install .NET MVC 3 to get the
System.Web.Razor assembly.
To use Nocco, run it from the command-line:
nocco *.cs
...will generate linked HTML documentation for the named source files, saving
it into a docs folder.
The source for Nocco is available on GitHub,
and released under the MIT license.
If .NET doesn't run on your platform, or you'd prefer a more convenient
package, get Rocco, the Ruby port that's
available as a gem. If you're writing shell scripts, try
Shocco, a port for the POSIX shell.
Both are by Ryan Tomayko. If Python's more
your speed, take a look at Nick Fitzgerald's
Pycco.
|
|
Import namespaces to allow us to type shorter type names.
|
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web.Razor;
namespace Nocco {
class Nocco {
private static string ExecutingDirectory;
private static List<string> Files;
private static Type TemplateType;
|
Main Documentation Generation Functions
|
|
Generate the documentation for a source file by reading it in, splitting it
up into comment/code sections, highlighting them for the appropriate language,
and merging them into an HTML template.
|
private static void GenerateDocumentation(string source) {
var lines = File.ReadAllLines(source);
var sections = Parse(source, lines);
Hightlight(source, sections);
GenerateHtml(source, sections);
}
|
Given a string of source code, parse out each comment and the code that
follows it, and create an individual Section for it.
|
private static List<Section> Parse(string source, string[] lines) {
List<Section> sections = new List<Section>();
var language = GetLanguage(source);
var hasCode = false;
var docsText = new StringBuilder();
var codeText = new StringBuilder();
Action<string, string> save = (string docs, string code) => sections.Add(new Section() { DocsHtml = docs, CodeHtml = code });
foreach (var line in lines) {
if (language.CommentMatcher.IsMatch(line) && !language.CommentFilter.IsMatch(line)) {
if (hasCode) {
save(docsText.ToString(), codeText.ToString());
hasCode = false;
docsText = new StringBuilder();
codeText = new StringBuilder();
}
docsText.AppendLine(language.CommentMatcher.Replace(line, ""));
}
else {
hasCode = true;
codeText.AppendLine(line);
}
}
save(docsText.ToString(), codeText.ToString());
return sections;
}
|
Prepares a single chunk of code for HTML output and runs the text of its
corresponding comment through Markdown, using a C# implementation
called MarkdownSharp.
|
private static void Hightlight(string source, List<Section> sections) {
var markdown = new MarkdownSharp.Markdown();
for (var i=0; i<sections.Count; i++) {
var section = sections[i];
section.DocsHtml = markdown.Transform(section.DocsHtml);
section.CodeHtml = System.Web.HttpUtility.HtmlEncode(section.CodeHtml);
}
}
|
Once all of the code is finished highlighting, we can generate the HTML file
and write out the documentation. Pass the completed sections into the template
found in Resources/Nocco.cshtml
|
private static void GenerateHtml(string source, List<Section> sections) {
int depth;
var destination = GetDestination(source, out depth);
string pathToRoot = "";
for (var i = 0; i < depth; i++)
pathToRoot = Path.Combine("..", pathToRoot);
var htmlTemplate = Activator.CreateInstance(TemplateType) as TemplateBase;
htmlTemplate.Title = Path.GetFileName(source);
htmlTemplate.PathToCss = Path.Combine(pathToRoot, "nocco.css").Replace('\\', '/');
htmlTemplate.GetSourcePath = (string s) => Path.Combine(pathToRoot, Path.ChangeExtension(s.ToLower(), ".html").Substring(2)).Replace('\\', '/');
htmlTemplate.Sections = sections;
htmlTemplate.Sources = Files;
htmlTemplate.Execute();
File.WriteAllText(destination, htmlTemplate.Buffer.ToString());
}
|
Helpers & Setup
|
|
Setup the Razor templating engine so that we can quickly pass the data in
and generate HTML.
The file Resources\Nocco.cshtml is read and compiled into a new dll
with a type that extends the TemplateBase class. This new assembly is
loaded so that we can create an instance and pass data into it
and generate the HTML.
|
private static Type SetupRazorTemplate() {
RazorEngineHost host = new RazorEngineHost(new CSharpRazorCodeLanguage());
host.DefaultBaseClass = typeof(TemplateBase).FullName;
host.DefaultNamespace = "RazorOutput";
host.DefaultClassName = "Template";
host.NamespaceImports.Add("System");
GeneratorResults razorResult = null;
using (var reader = new StreamReader(Path.Combine(ExecutingDirectory, "Resources", "Nocco.cshtml"))) {
razorResult = new RazorTemplateEngine(host).GenerateCode(reader);
}
var compilerParams = new CompilerParameters {
GenerateInMemory = true,
GenerateExecutable = false,
IncludeDebugInformation = false,
CompilerOptions = "/target:library /optimize"
};
compilerParams.ReferencedAssemblies.Add(typeof(Nocco).Assembly.CodeBase.Replace("file:///", "").Replace("/", "\\"));
var codeProvider = new Microsoft.CSharp.CSharpCodeProvider();
CompilerResults results = codeProvider.CompileAssemblyFromDom(compilerParams, razorResult.GeneratedCode);
|
Check for errors that may have occurred during template generation
|
if (results.Errors.HasErrors) {
foreach (var err in results.Errors.OfType<CompilerError>().Where(ce => !ce.IsWarning))
Console.WriteLine("Error Compiling Template: ({0}, {1}) {2}", err.Line, err.Column, err.ErrorText);
}
return results.CompiledAssembly.GetType("RazorOutput.Template");
}
|
A list of the languages that Nocco supports, mapping the file extension to
the symbol that indicates a comment. To add another language to Nocco's
repertoire, add it here.
|
private static Dictionary<string, Language> Languages = new Dictionary<string, Language> {
{ ".js", new Language {
Name = "javascript",
Symbol = "//"
}},
{ ".cs", new Language {
Name = "csharp",
Symbol = "//"
}},
{ ".vb", new Language {
Name = "vb.net",
Symbol = "'"
}}
};
|
Get the current language we're documenting, based on the extension.
|
private static Language GetLanguage(string source) {
var extension = Path.GetExtension(source);
return Languages.ContainsKey(extension) ? Languages[extension] : null;
}
|
Compute the destination HTML path for an input source file path. If the source
is Example.cs , the HTML will be at docs/example.html
|
private static string GetDestination(string filepath, out int depth) {
var dirs = Path.GetDirectoryName(filepath).Substring(1).Split(new char[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
depth = dirs.Length;
var dest = "docs";
foreach(var dir in dirs)
dest = Path.Combine(dest, dir);
EnsureDirectory(dest);
return Path.Combine(dest, Path.GetFileNameWithoutExtension(filepath).ToLower() + ".html");
}
|
Ensure that the destination directory exists.
|
private static void EnsureDirectory(string dir) {
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
}
|
Find all the files that match the pattern(s) passed in as arguments and
generate documentation for each one.
|
public static void Generate(string[] targets) {
if (targets.Length > 0) {
EnsureDirectory("docs");
ExecutingDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
File.Copy(Path.Combine(ExecutingDirectory, "Resources", "Nocco.css"), Path.Combine("docs", "nocco.css"), true);
File.Copy(Path.Combine(ExecutingDirectory, "Resources", "prettify.js"), Path.Combine("docs", "prettify.js"), true);
TemplateType = SetupRazorTemplate();
Files = new List<string>();
foreach (var target in targets)
Files.AddRange(Directory.GetFiles(".", target, SearchOption.AllDirectories).Where(filename => GetLanguage(Path.GetFileName(filename)) != null));
foreach (var file in Files)
GenerateDocumentation(file);
}
}
}
}
|