diff --git a/.gitignore b/.gitignore
index 6ffdd84..8cbc059 100644
--- a/.gitignore
+++ b/.gitignore
@@ -252,4 +252,7 @@ paket-files/
# SSDT artifacts
Model.xml
-*.FileListAbsolute.txt
\ No newline at end of file
+*.FileListAbsolute.txt
+PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/Readme.md
+PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/Docs/PxtlCa.XmlCommentMarkDownGenerator.MSBuild_altered.md
+PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/Docs/PxtlCa.XmlCommentMarkDownGenerator.MSBuild.md
diff --git a/PretzelPlugin/Plugin.csx b/PretzelPlugin/Plugin.csx
new file mode 100644
index 0000000..5efa635
--- /dev/null
+++ b/PretzelPlugin/Plugin.csx
@@ -0,0 +1,60 @@
+using Pretzel.Logic.Templating;
+using Pretzel.Logic.Templating.Context;
+using System.ComponentModel.Composition;
+using System.Collections.Generic;
+using System.Linq;
+
+
+[SiteEngineInfo(Engine="customxmlmd")]
+public class Processor : ISiteEngine
+{
+
+ public string AllowableCustomTags {get; set;}
+ public void Initialize()
+ {
+
+ }
+
+ public bool CanProcess(SiteContext context)
+ {
+ var readmePage = context.Posts.Where(p => p.File.ToLower().EndsWith("readme.md")).FirstOrDefault();
+ if(null == readmePage)
+ {
+ //if no readme.md then return false
+ return false;
+ }
+ if(readmePage.Bag.ContainsKey("mergexmlcomments"))
+ {
+ Console.WriteLine("about to check 'mergexmlcomments' a bool");
+ if(!(bool)(readmePage.Bag["mergexmlcomments"]))
+ {
+ Console.WriteLine("as boolean 'mergexmlcomments' is false");
+ //if there is a mergexmlcomments value in the front matter
+ //but it is false
+ return false;
+ }
+ Console.WriteLine("as boolean 'mergexmlcomments' is true");
+ AllowableCustomTags = (string)(readmePage.Bag["allowedcustomtags"]);
+ return true;
+ }
+ else
+ {
+ Console.WriteLine("Bag doesn't contain 'mergexmlcomments'");
+ //no mergexmlcomments value
+ return false;
+ }
+ }
+
+ public void Process(SiteContext context, bool skipFileOnError = false)
+ {
+ Console.WriteLine($@"About to check CanProcess(context) again");
+ if(CanProcess(context))
+ {
+ Console.WriteLine($@"Custom Tag to allow: {AllowableCustomTags}");
+ }
+ else
+ {
+ Console.WriteLine($@"CanProcess(context) is false this time");
+ }
+ }
+}
diff --git a/PretzelPlugin/readme.md b/PretzelPlugin/readme.md
new file mode 100644
index 0000000..afde74d
--- /dev/null
+++ b/PretzelPlugin/readme.md
@@ -0,0 +1,20 @@
+---
+layout: post
+title: "README"
+author: "The end user"
+comments: false
+mergexmlcomments: true
+allowedcustomtags: "all"
+---
+
+## Hello world...
+
+```cs
+static void Main()
+{
+ Console.WriteLine("Hello World!");
+}
+```
+
+
+This is my first post on the site!
diff --git a/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/GenerateMarkdown.cs b/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/GenerateMarkdown.cs
new file mode 100644
index 0000000..90949fa
--- /dev/null
+++ b/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/GenerateMarkdown.cs
@@ -0,0 +1,273 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+//using System.Threading.Tasks;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System.IO;
+using System.Xml.Linq;
+using System.Xml;
+using YamlDotNet.Serialization;
+using YamlDotNet.Serialization.NamingConventions;
+using PxtlCa.XmlCommentMarkDownGenerator.MSBuild.Options;
+
+namespace PxtlCa.XmlCommentMarkDownGenerator.MSBuild
+{
+ ///
+ /// A task that generates and optionally merges markdown
+ ///
+ public class GenerateMarkdown : Task
+ {
+ ///
+ /// The file(s) from which to generate markdown. This should be in XmlDocumentation format.
+ ///
+ [Required]
+ public ITaskItem[] InputXml { get; set; }
+
+ ///
+ /// DocumentationPath is the top level directory in which to search for files.
+ /// It is also the path where generated markdown files are created.
+ ///
+ [Required]
+ public ITaskItem DocumentationPath { get; set; }
+
+ ///
+ /// Whether the generated markdown files should merge. Only valid if multiple markdown files exist.
+ /// DocumentationPath is the top level directory in which to search for files.
+ /// Both existing markdown files and the generated files are merged.
+ ///
+ public bool MergeFiles { get; set; }
+
+ ///
+ /// The file to be created by the merge. Unused if MergeFiles evaluates to false.
+ ///
+ public ITaskItem OutputFile { get; set; }
+
+ ///
+ /// Defaults to false. When true unexpected tags in the documentation
+ /// will generate warnings rather than errors.
+ ///
+ public bool WarnOnUnexpectedTag { get; set; } = false;
+
+
+
+ ///
+ /// Runs the task as configured
+ ///
+ /// true if task has succeeded
+ public override bool Execute()
+ {
+ if (InputXml.Length == 0)
+ {
+ Log.LogError("InputXml cannot be empty");
+ }
+ else
+ {
+ UpdateParametersFromInput();
+ if (MergeFiles && (null == OutputFile))
+ {
+ Log.LogError("OutputFile must be specified if input files are merged");
+ }
+ else if (DocumentationPathIsFile && InputXml.Length != 1)
+ {
+ Log.LogError("DocumentationPath must specify a directory if more than one input XML value is supplied");
+ }
+ else
+ {
+ try
+ {
+ CreateDirectoryIfNeeded();
+ GenerateFiles();
+ if (MergeFiles)
+ {
+ Merge();
+ }
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LoggedException = ex;
+ Log.LogErrorFromException(ex);
+ }
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// This updates the tag and merge parameters based on the input front matter
+ ///
+ public void UpdateParametersFromInput()
+ {
+ if(DocumentationPathIsFile)
+ {
+ if (TryGetFrontMatter(DocumentationPath.ItemSpec, out string frontMatter, out bool isEmpty) &&
+ (!isEmpty))
+ {
+ ReadOptionsFromString(frontMatter);
+ return;
+ }
+ }
+ else
+ {
+ var mdFiles = Directory.EnumerateFiles(DocumentationPath.ItemSpec, "*.md", SearchOption.AllDirectories).ToList();
+ foreach (var mdFile in mdFiles)
+ {
+ if (TryGetFrontMatter(mdFile, out string frontMatter, out bool isEmpty) &&
+ (!isEmpty))
+ {
+ ReadOptionsFromString(frontMatter);
+ return;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Use this to handle front matter in markdown files
+ ///
+ /// the path to the file
+ /// the front matter found
+ /// whether the front matter found is trivial
+ /// true if front matter indicator(s) are found
+ public static bool TryGetFrontMatter(string filePath, out string frontMatter, out bool isEmpty)
+ {
+ var lines = File.ReadLines(filePath);
+ var firstDashedLine = lines.FirstOrDefault() ?? string.Empty;
+ if (firstDashedLine.StartsWith("---"))
+ {
+ var followingLines = lines.Skip(1).TakeWhile(line => !line.StartsWith("---")).ToList();
+ if(followingLines.Count == 0)
+ {
+ frontMatter = firstDashedLine;
+ isEmpty = true;
+ return true;
+ }
+ else
+ {
+ followingLines.Insert(0, firstDashedLine);
+ frontMatter = String.Join(Environment.NewLine, followingLines);
+ isEmpty = false;
+ return true;
+ }
+ }
+ frontMatter = string.Empty;
+ isEmpty = true;
+ return false;
+ }
+
+ private void ReadOptionsFromString(string frontMatter)
+ {
+ var input = new StringReader(frontMatter);
+ var deserializer = new DeserializerBuilder()
+ .WithNamingConvention(new CamelCaseNamingConvention())
+ .Build();
+ var options = deserializer.Deserialize(input);
+ MergeFiles = options.MergeXmlComments;
+ if(Enum.TryParse(options.AllowedCustomTags, true,
+ out AllowedTagOptions result))
+ {
+ //warn (rather than treat as error condition)
+ //if user indicates one of the two currently used options
+ WarnOnUnexpectedTag = result == AllowedTagOptions.All;
+ }
+ }
+
+ ///
+ /// for testing.
+ /// sets the exception for throw outside the catch
+ ///
+ public Exception LoggedException { get; set; }
+
+ private void Merge()
+ {
+ //get all md files in Documentation Path
+ //except those generated in this task
+ var otherMDFiles = Directory.EnumerateFiles(DocumentationPath.ItemSpec, "*.md", SearchOption.AllDirectories).ToList();
+ otherMDFiles = otherMDFiles.Except(GeneratedMDFiles).ToList();
+ var mergeInto = otherMDFiles.FirstOrDefault();
+ if (null == mergeInto)
+ {
+ mergeInto = GeneratedMDFiles.First();
+ File.Copy(mergeInto, OutputFile.ItemSpec, true);
+ foreach (var mdFile in GeneratedMDFiles.Skip(1))
+ {
+ File.AppendAllText(OutputFile.ItemSpec, Environment.NewLine);
+ File.AppendAllText(OutputFile.ItemSpec, File.ReadAllText(mdFile));
+ }
+ }
+ else
+ {
+ File.Copy(mergeInto, OutputFile.ItemSpec, true);
+ foreach (var mdFile in otherMDFiles.Skip(1))
+ {
+ File.AppendAllText(OutputFile.ItemSpec, Environment.NewLine);
+ File.AppendAllText(OutputFile.ItemSpec, File.ReadAllText(mdFile));
+ }
+ foreach (var mdFile in GeneratedMDFiles)
+ {
+ File.AppendAllText(OutputFile.ItemSpec, Environment.NewLine);
+ File.AppendAllText(OutputFile.ItemSpec, File.ReadAllText(mdFile));
+ }
+ }
+ }
+
+ private void GenerateFiles()
+ {
+ foreach (var inputFile in InputXml)
+ {
+ try
+ {
+ var mdOutput = OutputPath(inputFile.ItemSpec);
+ GeneratedMDFiles.Add(mdOutput);
+ var sr = new StreamReader(inputFile.ItemSpec);
+ using (var sw = new StreamWriter(mdOutput))
+ {
+ var xml = sr.ReadToEnd();
+ var doc = XDocument.Parse(xml);
+ var md = doc.Root.ToMarkDown();
+ sw.Write(md);
+ sw.Close();
+ }
+ }catch(XmlException xmlException)
+ {
+ if(WarnOnUnexpectedTag && null != xmlException.InnerException &&
+ xmlException.InnerException.GetType() == typeof(KeyNotFoundException))
+ {
+ Log.LogWarningFromException(xmlException);
+ continue;
+ }
+ throw;
+ }
+ }
+ }
+
+ private string OutputPath(string inputXml)
+ {
+ if (DocumentationPathIsFile)
+ {
+ return DocumentationPath.ItemSpec;
+ }
+ return $@"{DocumentationPath.ItemSpec}\{Path.GetFileNameWithoutExtension(inputXml)}.md";
+ }
+
+ private bool DocumentationPathIsFile
+ {
+ get { return File.Exists(DocumentationPath.ItemSpec); }
+ }
+
+ ///
+ /// The files generated during execution of the task
+ ///
+ public List GeneratedMDFiles { get; private set; } = new List();
+
+ private void CreateDirectoryIfNeeded()
+ {
+ if ((!DocumentationPathIsFile) && (!Directory.Exists(DocumentationPath.ItemSpec)))
+ {
+ Directory.CreateDirectory(DocumentationPath.ItemSpec);
+ }
+ }
+ }
+}
diff --git a/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/Options/YamlOptions.cs b/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/Options/YamlOptions.cs
new file mode 100644
index 0000000..1e1bff3
--- /dev/null
+++ b/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/Options/YamlOptions.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using YamlDotNet.Serialization;
+
+namespace PxtlCa.XmlCommentMarkDownGenerator.MSBuild.Options
+{
+ ///
+ /// Specifies the manner in which custom tags will be handled
+ ///
+ public enum AllowedTagOptions
+ {
+ ///
+ /// All custom tags are allowed
+ ///
+ All,
+ ///
+ /// No custom tags are allowed
+ ///
+ None
+ }
+ ///
+ /// The options to be deserialized from the front matter found.
+ ///
+ public class YamlOptions
+ {
+ [YamlMember(Alias = "MergeXmlComments", ApplyNamingConventions = false)]
+ public bool MergeXmlComments { get; set; }
+ [YamlMember(Alias = "AllowedCustomTags", ApplyNamingConventions = false)]
+ public string AllowedCustomTags { get; set; }
+ }
+}
diff --git a/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/Properties/AssemblyInfo.cs b/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..67664f2
--- /dev/null
+++ b/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("PxtlCa.XmlCommentMarkDownGenerator.MSBuild")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PxtlCa.XmlCommentMarkDownGenerator.MSBuild")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("9fcee209-508f-474e-80c2-d0966e4aa126")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("0.3.*")]
diff --git a/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/PxtlCa.XmlCommentMarkDownGenerator.MSBuild.csproj b/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/PxtlCa.XmlCommentMarkDownGenerator.MSBuild.csproj
new file mode 100644
index 0000000..a9b3b70
--- /dev/null
+++ b/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/PxtlCa.XmlCommentMarkDownGenerator.MSBuild.csproj
@@ -0,0 +1,64 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {9FCEE209-508F-474E-80C2-D0966E4AA126}
+ Library
+ Properties
+ PxtlCa.XmlCommentMarkDownGenerator.MSBuild
+ PxtlCa.XmlCommentMarkDownGenerator.MSBuild
+ v4.5.2
+ 512
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ bin\Debug\PxtlCa.XmlCommentMarkDownGenerator.MSBuild.xml
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\packages\YamlDotNet.4.2.2\lib\net35\YamlDotNet.dll
+
+
+
+
+
+
+
+
+
+ {1375dc94-03f8-496c-981c-d5d6e1b0fd26}
+ PxtlCa.XmlCommentMarkDownGenerator
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/packages.config b/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/packages.config
new file mode 100644
index 0000000..0cfcc50
--- /dev/null
+++ b/PxtlCa.XmlCommentMarkDownGenerator.MSBuild/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/PxtlCa.XmlCommentMarkDownGenerator.nuspec b/PxtlCa.XmlCommentMarkDownGenerator.nuspec
index fff99a6..fa0cd86 100644
--- a/PxtlCa.XmlCommentMarkDownGenerator.nuspec
+++ b/PxtlCa.XmlCommentMarkDownGenerator.nuspec
@@ -2,7 +2,7 @@
PxtlCa.XmlCommentMarkDownGenerator
- 0.2.6130.564
+ 0.3.0.0
PxtlCa.XmlCommentMarkDownGenerator
Martin Zarate
https://opensource.org/licenses/MIT
@@ -14,6 +14,7 @@
+
diff --git a/PxtlCa.XmlCommentMarkDownGenerator.targets b/PxtlCa.XmlCommentMarkDownGenerator.targets
index 1d94f3c..831f26a 100644
--- a/PxtlCa.XmlCommentMarkDownGenerator.targets
+++ b/PxtlCa.XmlCommentMarkDownGenerator.targets
@@ -1,20 +1,17 @@
+
-
-
-
\ No newline at end of file
diff --git a/PxtlCa.XmlCommentMarkDownGenerator/Properties/AssemblyInfo.cs b/PxtlCa.XmlCommentMarkDownGenerator/Properties/AssemblyInfo.cs
index a3bd64b..9046924 100644
--- a/PxtlCa.XmlCommentMarkDownGenerator/Properties/AssemblyInfo.cs
+++ b/PxtlCa.XmlCommentMarkDownGenerator/Properties/AssemblyInfo.cs
@@ -39,4 +39,4 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.2.*")]
+[assembly: AssemblyVersion("0.3.*")]
diff --git a/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/BuildEngine.cs b/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/BuildEngine.cs
new file mode 100644
index 0000000..822f476
--- /dev/null
+++ b/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/BuildEngine.cs
@@ -0,0 +1,231 @@
+using System;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Tasks;
+using Microsoft.Build.Utilities;
+using PxtlCa.XmlCommentMarkdownGenerator;
+using PxtlCa.XmlCommentMarkDownGenerator.MSBuild;
+using Rhino.Mocks;
+using System.IO;
+using System.Xml.Linq;
+using System.Xml.XPath;
+using System.Xml;
+using System.Linq;
+
+namespace PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test
+{
+ [TestClass]
+ public class BuildEngine
+ {
+ [TestMethod]
+ [DeploymentItem("Docs")]
+ public void ExecuteMerge()
+ {
+ PrepareDocsDirectory();
+ var mockRepo = new MockRepository();
+ var buildEngine = mockRepo.Stub();
+
+ var inputPath = @"..\..\..\PxtlCa.XmlCommentMarkdownGenerator.MSBuild\bin\Debug\PxtlCa.XmlCommentMarkDownGenerator.MSBuild.xml";
+ var docPath = @"Docs";
+ var outputFile = new TaskItem(@"..\..\Readme.md");
+
+
+ var inputXml = new ITaskItem[] { new TaskItem(inputPath) };
+ var documentPath = new TaskItem(docPath);
+ var merge = true;
+
+ var task = new GenerateMarkdown
+ {
+ BuildEngine = buildEngine,
+ DocumentationPath = documentPath,
+ InputXml = inputXml,
+ MergeFiles = merge,
+ OutputFile = outputFile
+ };
+
+ task.Execute();
+
+ var expectFileExists = true;
+ var fileActuallyExists = System.IO.File.Exists(outputFile.ItemSpec);
+
+ Assert.AreEqual(expectFileExists, fileActuallyExists);
+ }
+
+ [TestMethod]
+ [DeploymentItem("Docs")]
+ public void TestAvertMerge()
+ {
+ PrepareDocsDirectory(overrideMerge:true);
+ var mockRepo = new MockRepository();
+ var buildEngine = mockRepo.Stub();
+
+ var inputPath = @"..\..\..\PxtlCa.XmlCommentMarkdownGenerator.MSBuild\bin\Debug\PxtlCa.XmlCommentMarkDownGenerator.MSBuild.xml";
+ var docPath = @"Docs";
+ var outputFile = new TaskItem(@"..\..\Readme.md");
+
+
+ var inputXml = new ITaskItem[] { new TaskItem(inputPath) };
+ var documentPath = new TaskItem(docPath);
+ var merge = true;
+
+ var task = new GenerateMarkdown
+ {
+ BuildEngine = buildEngine,
+ DocumentationPath = documentPath,
+ InputXml = inputXml,
+ MergeFiles = merge,
+ OutputFile = outputFile
+ };
+
+ task.Execute();
+
+ var expectFileExists = true;
+ var fileActuallyExists = System.IO.File.Exists(outputFile.ItemSpec);
+
+ Assert.AreEqual(expectFileExists, fileActuallyExists);
+
+ var docCount = Directory.EnumerateFiles(@"Docs", "*.md", SearchOption.TopDirectoryOnly).ToList().Count;
+
+ //different than the case where the files are merged (into _one_ file)
+ var expectedDocCount = 2;
+
+ Assert.AreEqual(expectedDocCount, docCount);
+ }
+
+ [TestMethod]
+ [DeploymentItem("Docs")]
+ public void HandleUnexpectedTag()
+ {
+ PrepareDocsDirectory();
+ var mockRepo = new MockRepository();
+ var buildEngine = mockRepo.Stub();
+
+ var inputPath = @"..\..\..\PxtlCa.XmlCommentMarkdownGenerator.MSBuild\bin\Debug\PxtlCa.XmlCommentMarkDownGenerator.MSBuild.xml";
+ var toAlter = File.ReadAllText(inputPath);
+ toAlter = toAlter.Replace(@"", @"X");
+
+ var alteredPath = inputPath.Replace("MSBuild.xml", "MSBuild_altered.xml");
+ File.WriteAllText(alteredPath, toAlter);
+
+ var docPath = @"Docs";
+ var outputFile = new TaskItem(@"..\..\Readme.md");
+
+
+ var inputXml = new ITaskItem[] { new TaskItem(alteredPath) };
+
+ var documentPath = new TaskItem(docPath);
+ var merge = true;
+
+ var task = new GenerateMarkdown
+ {
+ BuildEngine = buildEngine,
+ DocumentationPath = documentPath,
+ InputXml = inputXml,
+ MergeFiles = merge,
+ WarnOnUnexpectedTag = true,
+ OutputFile = outputFile
+ };
+
+ task.Execute();
+
+ if (null != task.LoggedException)
+ {
+ throw task.LoggedException;
+ }
+
+ var expectFileExists = true;
+ var fileActuallyExists = System.IO.File.Exists(outputFile.ItemSpec);
+
+ Assert.AreEqual(expectFileExists, fileActuallyExists);
+ }
+
+
+ [TestMethod]
+ [DeploymentItem("Docs")]
+ [ExpectedException(typeof(XmlException))]
+ public void ExpectedErrorUnexpectedTag()
+ {
+ PrepareDocsDirectory();
+ var mockRepo = new MockRepository();
+ var buildEngine = mockRepo.Stub();
+
+ var inputPath = @"..\..\..\PxtlCa.XmlCommentMarkdownGenerator.MSBuild\bin\Debug\PxtlCa.XmlCommentMarkDownGenerator.MSBuild.xml";
+ var toAlter = File.ReadAllText(inputPath);
+ toAlter = toAlter.Replace(@"", @"X");
+
+ var alteredPath = inputPath.Replace("MSBuild.xml", "MSBuild_altered.xml");
+ File.WriteAllText(alteredPath, toAlter);
+
+ var docPath = @"Docs";
+ var outputFile = new TaskItem(@"..\..\Readme.md");
+
+
+ var inputXml = new ITaskItem[] { new TaskItem(alteredPath) };
+
+ var documentPath = new TaskItem(docPath);
+ var merge = true;
+
+ var task = new GenerateMarkdown
+ {
+ BuildEngine = buildEngine,
+ DocumentationPath = documentPath,
+ InputXml = inputXml,
+ MergeFiles = merge,
+ OutputFile = outputFile,
+ WarnOnUnexpectedTag = false
+ };
+
+ task.Execute();
+
+ if(null != task.LoggedException)
+ {
+ throw task.LoggedException;
+ }
+ var expectFileExists = true;
+ var fileActuallyExists = System.IO.File.Exists(outputFile.ItemSpec);
+
+ Assert.AreEqual(expectFileExists, fileActuallyExists);
+ }
+
+ public static void PrepareDocsDirectory(bool overrideMerge = false)
+ {
+ var mergeDirectiveInserted = false;
+ if(Directory.Exists("Docs"))
+ {
+ Directory.Delete("Docs", true);
+ }
+ Directory.CreateDirectory("Docs");
+ var filesToMove = Directory.EnumerateFiles(Environment.CurrentDirectory, "*.md", SearchOption.TopDirectoryOnly).ToList();
+ foreach (var mdFile in filesToMove)
+ {
+ if(overrideMerge && (!mergeDirectiveInserted))
+ {
+ mergeDirectiveInserted = TryUpdateFile(mergeDirectiveInserted, mdFile);
+ }
+ File.Copy(mdFile, $@".\Docs\{Path.GetFileName(mdFile)}");
+ }
+ }
+
+ private static bool TryUpdateFile(bool mergeDirectiveInserted, string mdFile)
+ {
+ if (GenerateMarkdown.TryGetFrontMatter(mdFile, out string frontMatter, out bool isEmpty))
+ {
+ if (isEmpty)
+ {
+ //get all the lines starting with the second "---" line
+ var lines = File.ReadLines(mdFile).Skip(1).ToList();
+ //now insert the front matter in reverse order
+ lines.Insert(0, "AllowedCustomTags: all");
+ lines.Insert(0, "MergeXmlComments: false");
+ lines.Insert(0, "---");
+ //overwrite the file with the update
+ File.WriteAllText(mdFile, string.Join(Environment.NewLine, lines));
+ //stop any additional preprocesssing
+ mergeDirectiveInserted = true;
+ }
+ }
+
+ return mergeDirectiveInserted;
+ }
+ }
+}
diff --git a/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/Docs/Notes.md b/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/Docs/Notes.md
new file mode 100644
index 0000000..5ceef91
--- /dev/null
+++ b/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/Docs/Notes.md
@@ -0,0 +1,3 @@
+---
+---
+## No Documentation Yet Authored
diff --git a/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/Properties/AssemblyInfo.cs b/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..734583c
--- /dev/null
+++ b/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,20 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test")]
+[assembly: AssemblyCopyright("Copyright © 2017")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+[assembly: ComVisible(false)]
+
+[assembly: Guid("2358183d-b57c-467d-b89c-16c6be040753")]
+
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test.csproj b/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test.csproj
new file mode 100644
index 0000000..0b9c57c
--- /dev/null
+++ b/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test.csproj
@@ -0,0 +1,88 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {2358183D-B57C-467D-B89C-16C6BE040753}
+ Library
+ Properties
+ PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test
+ PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test
+ v4.5.2
+ 512
+ {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ 15.0
+ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
+ $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
+ False
+ UnitTest
+
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+ ..\packages\RhinoMocks.3.6.1\lib\net\Rhino.Mocks.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+ {9fcee209-508f-474e-80c2-d0966e4aa126}
+ PxtlCa.XmlCommentMarkDownGenerator.MSBuild
+
+
+ {1375dc94-03f8-496c-981c-d5d6e1b0fd26}
+ PxtlCa.XmlCommentMarkDownGenerator
+
+
+
+
+
\ No newline at end of file
diff --git a/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/packages.config b/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/packages.config
new file mode 100644
index 0000000..5930689
--- /dev/null
+++ b/PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test/packages.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 4df05cc..e8de49e 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Can be used as a stand-alone Markdown command-line tool, but is also available a
https://www.nuget.org/packages/PxtlCa.XmlCommentMarkDownGenerator
-When used as a nuget package, it will add a target to your project to automatically convert generated xml into markdown file stored in Docs at the solution level (peer to the project folder).
+When used as a nuget package, it will add an MSBuild task to your project to automatically convert generated xml into markdown file stored in Docs at the project level. It will also merge any existing markdown files in Docs with the converted markdown. Takes multiple input xml files.
(note: the above nuget target was broken after 0.1.5977.1837 because I forgot to commit the nuspec line that does it. Oops. Fixed in 0.2.6130.564)
diff --git a/XmlCommentMarkDownGenerator.sln b/XmlCommentMarkDownGenerator.sln
index a3870bb..cd6610d 100644
--- a/XmlCommentMarkDownGenerator.sln
+++ b/XmlCommentMarkDownGenerator.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
+# Visual Studio 15
+VisualStudioVersion = 15.0.26430.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9CB54D49-0D43-462C-B250-38BEB7F38312}"
ProjectSection(SolutionItems) = preProject
@@ -17,6 +17,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PxtlCa.XmlCommentMarkDownGe
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PxtlCa.XmlCommentMarkDownGenerator.Test", "PxtlCa.XmlCommentMarkDownGenerator.Test\PxtlCa.XmlCommentMarkDownGenerator.Test.csproj", "{7993D07F-7F81-477E-ABA1-E3E6DC5CAB6A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PxtlCa.XmlCommentMarkDownGenerator.MSBuild", "PxtlCa.XmlCommentMarkDownGenerator.MSBuild\PxtlCa.XmlCommentMarkDownGenerator.MSBuild.csproj", "{9FCEE209-508F-474E-80C2-D0966E4AA126}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test", "PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test\PxtlCa.XmlCommentMarkdownGenerator.MSBuild.Test.csproj", "{2358183D-B57C-467D-B89C-16C6BE040753}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -31,6 +35,14 @@ Global
{7993D07F-7F81-477E-ABA1-E3E6DC5CAB6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7993D07F-7F81-477E-ABA1-E3E6DC5CAB6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7993D07F-7F81-477E-ABA1-E3E6DC5CAB6A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9FCEE209-508F-474E-80C2-D0966E4AA126}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9FCEE209-508F-474E-80C2-D0966E4AA126}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9FCEE209-508F-474E-80C2-D0966E4AA126}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9FCEE209-508F-474E-80C2-D0966E4AA126}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2358183D-B57C-467D-B89C-16C6BE040753}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2358183D-B57C-467D-B89C-16C6BE040753}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2358183D-B57C-467D-B89C-16C6BE040753}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2358183D-B57C-467D-B89C-16C6BE040753}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE