// NAnt - A .NET build tool
// Copyright (C) 2001-2002 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Gerry Shaw (gerry_shaw@yahoo.com)
// Ian MacLean (ian_maclean@another.com)
// Giuseppe Greco (giuseppe.greco@agamura.com)
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using NDoc.Core;
using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;
using NAnt.Core.Util;
using NAnt.DotNet.Types;
namespace NAnt.DotNet.Tasks {
///
/// Runs NDoc V1.3.1 to create documentation.
///
///
///
/// See the NDoc home page for more
/// information.
///
///
/// By default, only the NDoc MSDN documenter ships as part of the NAnt
/// distribution. To make another NDoc documenter from the NDoc V1.3.1
/// distribution available to the , copy the
/// documenter assembly (and possible dependencies) to the "lib"
/// directory corresponding with the CLR you're running NAnt on
/// (eg. <nant root>/bin/lib/net/1.1).
///
///
///
///
/// Document two assemblies using the MSDN documenter. The namespaces are
/// documented in NamespaceSummary.xml.
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
///
/// ]]>
///
/// Content of NamespaceSummary.xml :
///
///
///
/// The Foo.Bar namespace reinvents the wheel.
///
///
/// The Foo.Bar.Tests namespace ensures that the Foo.Bar namespace reinvents the wheel correctly.
///
///
/// ]]>
///
///
[TaskName("ndoc")]
public class NDocTask : Task {
#region Private Instance Fields
private XmlNodeList _docNodes;
private AssemblyFileSet _assemblies = new AssemblyFileSet();
private FileSet _summaries = new FileSet();
private RawXml _documenters;
private DirSet _referencePaths = new DirSet();
#endregion Private Instance Fields
#region Public Instance Properties
///
/// The set of assemblies to document.
///
[BuildElement("assemblies", Required=true)]
public AssemblyFileSet Assemblies {
get { return _assemblies; }
set { _assemblies = value; }
}
///
/// The set of namespace summary files.
///
[BuildElement("summaries")]
public FileSet Summaries {
get { return _summaries; }
set { _summaries = value; }
}
///
/// Specifies the formats in which the documentation should be generated.
///
[BuildElement("documenters", Required=true)]
public RawXml Documenters {
get { return _documenters; }
set { _documenters = value; }
}
///
/// Collection of additional directories to search for referenced
/// assemblies.
///
[BuildElement("referencepaths")]
public DirSet ReferencePaths {
get { return _referencePaths; }
set { _referencePaths = value; }
}
#endregion Public Instance Properties
#region Override implementation of Task
///
/// Initializes the taks and verifies the parameters.
///
/// containing the XML fragment used to define this task instance.
protected override void InitializeTask(XmlNode taskNode) {
// expand and store clone of the xml node
_docNodes = Documenters.Xml.Clone().SelectNodes("nant:documenter",
NamespaceManager);
ExpandPropertiesInNodes(_docNodes);
}
///
/// Generates an NDoc project and builds the documentation.
///
protected override void ExecuteTask() {
// ensure base directory is set, even if fileset was not initialized
// from XML
if (Assemblies.BaseDirectory == null) {
Assemblies.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
if (Summaries.BaseDirectory == null) {
Summaries.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
if (ReferencePaths.BaseDirectory == null) {
ReferencePaths.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
}
// Make sure there is at least one included assembly. This can't
// be done in the InitializeTask() method because the files might
// not have been built at startup time.
if (Assemblies.FileNames.Count == 0) {
throw new BuildException(ResourceUtils.GetString("NA2020"), Location);
}
// create NDoc Project
NDoc.Core.Project project = null;
try {
project = new NDoc.Core.Project();
} catch (Exception ex) {
throw new BuildException(ResourceUtils.GetString("NA2021"), Location, ex);
}
// set-up probe path, meaning list of directories where NDoc searches
// for documenters
// by default, NDoc scans the startup path of the app, so we do not
// need to add this explicitly
string privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
if (privateBinPath != null) {
// have NDoc also probe for documenters in the privatebinpath
foreach (string relativePath in privateBinPath.Split(Path.PathSeparator)) {
project.AppendProbePath(Path.Combine(
AppDomain.CurrentDomain.BaseDirectory, relativePath));
}
}
// check for valid documenters (any other validation can be done by NDoc itself at project load time)
foreach (XmlNode node in _docNodes) {
//skip non-nant namespace elements and special elements like comments, pis, text, etc.
if (!(node.NodeType == XmlNodeType.Element) || !node.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant"))) {
continue;
}
string documenterName = node.Attributes["name"].Value;
CheckAndGetDocumenter(project, documenterName);
}
// write documenter project settings to temp file
string projectFileName = Path.GetTempFileName();
Log(Level.Verbose, ResourceUtils.GetString("String_WritingProjectSettings"), projectFileName);
XmlTextWriter writer = new XmlTextWriter(projectFileName, Encoding.UTF8);
writer.Formatting = Formatting.Indented;
writer.WriteStartDocument();
writer.WriteStartElement("project");
// write assemblies section
writer.WriteStartElement("assemblies");
foreach (string assemblyPath in Assemblies.FileNames) {
string docPath = Path.ChangeExtension(assemblyPath, ".xml");
writer.WriteStartElement("assembly");
writer.WriteAttributeString("location", assemblyPath);
if (File.Exists(docPath)) {
writer.WriteAttributeString("documentation", docPath);
}
writer.WriteEndElement();
}
writer.WriteEndElement();
// write summaries section
StringBuilder sb = new StringBuilder();
foreach (string summaryPath in Summaries.FileNames) {
// write out the namespace summary nodes
try {
XmlTextReader tr = new XmlTextReader(summaryPath);
tr.MoveToContent(); // skip XmlDeclaration and Processing Instructions
sb.Append(tr.ReadOuterXml());
tr.Close();
} catch (IOException ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA2022"), summaryPath), Location, ex);
}
}
writer.WriteRaw(sb.ToString());
// write out the documenters section
writer.WriteStartElement("documenters");
foreach (XmlNode node in _docNodes) {
//skip non-nant namespace elements and special elements like comments, pis, text, etc.
if (!(node.NodeType == XmlNodeType.Element) || !node.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant"))) {
continue;
}
writer.WriteRaw(node.OuterXml);
}
writer.WriteEndElement();
// end project element
writer.WriteEndElement();
writer.Close();
try {
// read NDoc project file
Log(Level.Verbose, ResourceUtils.GetString("String_NDocProjectFile"),
Path.GetFullPath(projectFileName));
project.Read(projectFileName);
// add additional directories to search for referenced assemblies
if (ReferencePaths.DirectoryNames.Count > 0) {
foreach (string directory in ReferencePaths.DirectoryNames) {
project.ReferencePaths.Add(new ReferencePath(directory));
}
}
foreach (XmlNode node in _docNodes) {
//skip non-nant namespace elements and special elements like comments, pis, text, etc.
if (!(node.NodeType == XmlNodeType.Element) || !node.NamespaceURI.Equals(NamespaceManager.LookupNamespace("nant"))) {
continue;
}
string documenterName = node.Attributes["name"].Value;
IDocumenter documenter = CheckAndGetDocumenter(project, documenterName);
// hook up events for feedback during the build
documenter.DocBuildingStep += new DocBuildingEventHandler(OnDocBuildingStep);
documenter.DocBuildingProgress += new DocBuildingEventHandler(OnDocBuildingProgress);
// build documentation
documenter.Build(project);
}
} catch (Exception ex) {
throw new BuildException(ResourceUtils.GetString("NA2023"), Location, ex);
}
}
#endregion Override implementation of Task
#region Private Instance Methods
///
/// Represents the method that will be called to update the overall
/// percent complete value and the current step name.
///
/// The source of the event.
/// A that contains the event data.
private void OnDocBuildingStep(object sender, ProgressArgs e) {
Log(Level.Info, e.Status);
}
///
/// Represents the method that will be called to update the current
/// step's precent complete value.
///
/// The source of the event.
/// A that contains the event data.
private void OnDocBuildingProgress(object sender, ProgressArgs e) {
Log(Level.Verbose, e.Progress + ResourceUtils.GetString("String_PercentageComplete"));
}
///
/// Returns the documenter for the given project.
///
///
/// Documenter is not found.
///
///
/// is .
///
private IDocumenter CheckAndGetDocumenter(NDoc.Core.Project project, string documenterName){
IDocumenter documenter = null;
if (project == null) {
throw new ArgumentNullException("project");
}
StringCollection documenters = new StringCollection();
foreach (IDocumenter d in project.Documenters) {
documenters.Add(d.Name);
// ignore case when comparing documenter names
if (string.Compare(d.Name, documenterName, true, CultureInfo.InvariantCulture) == 0) {
documenter = (IDocumenter) d;
break;
}
}
// throw an exception if the documenter could not be found.
if (documenter == null) {
if (documenters.Count == 0) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA2024"), documenterName), Location);
} else {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
ResourceUtils.GetString("NA2025"), documenterName,
StringUtils.Join(", ", documenters)), Location);
}
}
return documenter;
}
///
/// Performs macro expansion for the given nodes.
///
/// for which expansion should be performed.
private void ExpandPropertiesInNodes(XmlNodeList nodes) {
foreach (XmlNode node in nodes) {
// do not process comment nodes, or entities and other internal element types.
if (node.NodeType == XmlNodeType.Element) {
ExpandPropertiesInNodes(node.ChildNodes);
foreach (XmlAttribute attr in node.Attributes) {
// use "this" keyword as workaround for Mono bug #71992
attr.Value = this.Project.ExpandProperties(attr.Value, Location);
}
// convert output directory to full path relative to project base directory
XmlNode outputDirProperty = (XmlNode) node.SelectSingleNode("property[@name='OutputDirectory']");
if (outputDirProperty != null) {
XmlAttribute valueAttribute = (XmlAttribute) outputDirProperty.Attributes.GetNamedItem("value");
if (valueAttribute != null) {
// use "this" keyword as workaround for Mono bug #71992
valueAttribute.Value = this.Project.GetFullPath(valueAttribute.Value);
}
}
}
}
}
#endregion Private Instance Methods
}
}