diff --git a/CHANGELOG.md b/CHANGELOG.md index 0467a2da..0b4f7aa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG ## Not released * Clean up trailing whitespace during markdown generation [#225](https://github.com/PowerShell/platyPS/issues/225) +* Preserve line breaks after headers when Update-MarkdownHelp is used [#319](https://github.com/PowerShell/platyPS/issues/319) ## 0.8.3 diff --git a/src/Markdown.MAML/Markdown.MAML.csproj b/src/Markdown.MAML/Markdown.MAML.csproj index 2abdec2e..3e7f9e72 100644 --- a/src/Markdown.MAML/Markdown.MAML.csproj +++ b/src/Markdown.MAML/Markdown.MAML.csproj @@ -61,6 +61,8 @@ + + diff --git a/src/Markdown.MAML/Model/MAML/MamlCommand.cs b/src/Markdown.MAML/Model/MAML/MamlCommand.cs index fec99500..3cec6a68 100644 --- a/src/Markdown.MAML/Model/MAML/MamlCommand.cs +++ b/src/Markdown.MAML/Model/MAML/MamlCommand.cs @@ -6,9 +6,12 @@ namespace Markdown.MAML.Model.MAML public class MamlCommand { public SourceExtent Extent { get; set; } + public string Name { get; set; } - public string Synopsis { get; set; } - public string Description { get; set; } + + public SectionBody Synopsis { get; set; } + + public SectionBody Description { get; set; } public List Inputs { @@ -25,7 +28,7 @@ public List Parameters get { return _parameters; } } - public string Notes { get; set; } + public SectionBody Notes { get; set; } public bool IsWorkflow { get; set; } diff --git a/src/Markdown.MAML/Model/MAML/MamlExample.cs b/src/Markdown.MAML/Model/MAML/MamlExample.cs index 2e3f1af4..22df8c73 100644 --- a/src/Markdown.MAML/Model/MAML/MamlExample.cs +++ b/src/Markdown.MAML/Model/MAML/MamlExample.cs @@ -1,4 +1,5 @@ -using System; +using Markdown.MAML.Model.Markdown; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -12,5 +13,10 @@ public class MamlExample public string Code { get; set; } public string Remarks { get; set; } public string Introduction { get; set; } + + /// + /// Additional options that determine how the section will be formated when rendering markdown. + /// + public SectionFormatOption FormatOption { get; set; } } } diff --git a/src/Markdown.MAML/Model/MAML/MamlInputOutput.cs b/src/Markdown.MAML/Model/MAML/MamlInputOutput.cs index 96a0906c..53fb7aca 100644 --- a/src/Markdown.MAML/Model/MAML/MamlInputOutput.cs +++ b/src/Markdown.MAML/Model/MAML/MamlInputOutput.cs @@ -1,4 +1,5 @@ -using System; +using Markdown.MAML.Model.Markdown; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -14,6 +15,11 @@ public class MamlInputOutput : IEquatable public string TypeName { get; set; } public string Description { get; set; } + /// + /// Additional options that determine how the section will be formated when rendering markdown. + /// + public SectionFormatOption FormatOption { get; set; } + bool IEquatable.Equals(MamlInputOutput other) { if (!StringComparer.OrdinalIgnoreCase.Equals(other.TypeName, this.TypeName)) diff --git a/src/Markdown.MAML/Model/MAML/MamlParameter.cs b/src/Markdown.MAML/Model/MAML/MamlParameter.cs index 6de90f6b..f1e594c4 100644 --- a/src/Markdown.MAML/Model/MAML/MamlParameter.cs +++ b/src/Markdown.MAML/Model/MAML/MamlParameter.cs @@ -9,6 +9,11 @@ public class MamlParameter : ICloneable { public SourceExtent Extent { get; set; } + /// + /// Additional options that determine how the section will be formated when rendering markdown. + /// + public SectionFormatOption FormatOption { get; set; } + public string Type { get; set; } public string FullType { get; set; } diff --git a/src/Markdown.MAML/Model/Markdown/HeadingNode.cs b/src/Markdown.MAML/Model/Markdown/HeadingNode.cs index 5a98c935..dc62af74 100644 --- a/src/Markdown.MAML/Model/Markdown/HeadingNode.cs +++ b/src/Markdown.MAML/Model/Markdown/HeadingNode.cs @@ -9,10 +9,16 @@ public override MarkdownNodeType NodeType public int HeadingLevel { get; private set; } - public HeadingNode(string headingText, int headingLevel, SourceExtent sourceExtent) + /// + /// Additional options that determine how the section will be formated when rendering markdown. This options will be passed on to MAML models when they are generated. + /// + public SectionFormatOption FormatOption { get; private set; } + + public HeadingNode(string headingText, int headingLevel, SourceExtent sourceExtent, SectionFormatOption formatOption) : base(headingText, sourceExtent) { this.HeadingLevel = headingLevel; + this.FormatOption = formatOption; } } } diff --git a/src/Markdown.MAML/Model/Markdown/SectionBody.cs b/src/Markdown.MAML/Model/Markdown/SectionBody.cs new file mode 100644 index 00000000..aaee004b --- /dev/null +++ b/src/Markdown.MAML/Model/Markdown/SectionBody.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.MAML.Model.Markdown +{ + /// + /// A section of text with formatting options. + /// + public sealed class SectionBody + { + public SectionBody(string text, SectionFormatOption formatOption) + { + Text = text; + FormatOption = formatOption; + } + + public SectionBody(string text) + : this(text, SectionFormatOption.None) + { } + + /// + /// The text of the section body. + /// + public string Text { get; private set; } + + /// + /// Additional options that determine how the section will be formated when rendering markdown. + /// + public SectionFormatOption FormatOption { get; private set; } + + public override string ToString() + { + return Text; + } + } +} diff --git a/src/Markdown.MAML/Model/Markdown/SectionFormatOption.cs b/src/Markdown.MAML/Model/Markdown/SectionFormatOption.cs new file mode 100644 index 00000000..643a9696 --- /dev/null +++ b/src/Markdown.MAML/Model/Markdown/SectionFormatOption.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Markdown.MAML.Model.Markdown +{ + /// + /// Define options that determine how sections will be formated when rendering markdown. + /// + [Flags()] + public enum SectionFormatOption : byte + { + None = 0, + + /// + /// A line break should be added after the section header. + /// + LineBreakAfterHeader = 1 + } +} diff --git a/src/Markdown.MAML/Parser/MarkdownParser.cs b/src/Markdown.MAML/Parser/MarkdownParser.cs index 6c20c603..8bd1f53b 100644 --- a/src/Markdown.MAML/Parser/MarkdownParser.cs +++ b/src/Markdown.MAML/Parser/MarkdownParser.cs @@ -307,7 +307,10 @@ private void CreateHashHeader(Match regexMatch, SourceExtent sourceExtent) new HeadingNode( regexMatch.Groups[2].Value, regexMatch.Groups[1].Value.Length, - sourceExtent)); + sourceExtent, + + // Detect if a line break after the header exists. Mutiple line breaks will be reduced to one. + (regexMatch.Groups[3].Captures.Count > 1) ? SectionFormatOption.LineBreakAfterHeader : SectionFormatOption.None)); } private void CreateHashHeader2(Match regexMatch, SourceExtent sourceExtent) @@ -318,7 +321,10 @@ private void CreateHashHeader2(Match regexMatch, SourceExtent sourceExtent) new HeadingNode( regexMatch.Groups[3].Value, regexMatch.Groups[2].Value.Length, - sourceExtent)); + sourceExtent, + + // Detect if a line break after the header exists. Mutiple line breaks will be reduced to one. + (regexMatch.Groups[4].Captures.Count > 1) ? SectionFormatOption.LineBreakAfterHeader : SectionFormatOption.None)); } private void CreateUnderlineHeader(Match regexMatch, SourceExtent sourceExtent) @@ -333,7 +339,10 @@ private void CreateUnderlineHeader(Match regexMatch, SourceExtent sourceExtent) new HeadingNode( regexMatch.Groups[1].Value, headerLevel, - sourceExtent)); + sourceExtent, + + // Detect if a line break after the header exists. Mutiple line breaks will be reduced to one. + (regexMatch.Groups[3].Captures.Count > 1) ? SectionFormatOption.LineBreakAfterHeader : SectionFormatOption.None)); } private void CreateTickCodeBlock(Match regexMatch, SourceExtent sourceExtent) diff --git a/src/Markdown.MAML/Renderer/MamlRenderer.cs b/src/Markdown.MAML/Renderer/MamlRenderer.cs index 6c070843..e2cc98ab 100644 --- a/src/Markdown.MAML/Renderer/MamlRenderer.cs +++ b/src/Markdown.MAML/Renderer/MamlRenderer.cs @@ -64,14 +64,14 @@ private static XElement CreateCommandElement(MamlCommand command) new XElement(commandNS + "name", command.Name), new XElement(commandNS + "verb", verb), new XElement(commandNS + "noun", noun), - new XElement(mamlNS + "description", GenerateParagraphs(command.Synopsis))), - new XElement(mamlNS + "description", GenerateParagraphs(command.Description)), + new XElement(mamlNS + "description", GenerateParagraphs(command.Synopsis?.Text))), + new XElement(mamlNS + "description", GenerateParagraphs(command.Description?.Text)), new XElement(commandNS + "syntax", command.Syntax.Select(syn => CreateSyntaxItem(syn, command))), new XElement(commandNS + "parameters", command.Parameters.Select(param => CreateParameter(param))), new XElement(commandNS + "inputTypes", command.Inputs.Select(input => CreateInput(input))), new XElement(commandNS + "returnValues", command.Outputs.Select(output => CreateOutput(output))), new XElement(mamlNS + "alertSet", - new XElement(mamlNS + "alert", GenerateParagraphs(command.Notes))), + new XElement(mamlNS + "alert", GenerateParagraphs(command.Notes?.Text))), new XElement(commandNS + "examples", command.Examples.Select(example => CreateExample(example))), new XElement(commandNS + "relatedLinks", command.Links.Select(link => CreateLink(link)))); } diff --git a/src/Markdown.MAML/Renderer/Markdownv2Renderer.cs b/src/Markdown.MAML/Renderer/Markdownv2Renderer.cs index a489ca82..a3dafd3f 100644 --- a/src/Markdown.MAML/Renderer/Markdownv2Renderer.cs +++ b/src/Markdown.MAML/Renderer/Markdownv2Renderer.cs @@ -114,7 +114,9 @@ private void AddCommand(MamlCommand command) private void AddLinks(MamlCommand command) { - AddHeader(ModelTransformerBase.COMMAND_ENTRIES_HEADING_LEVEL, MarkdownStrings.RELATED_LINKS); + var extraNewLine = command.Links != null && command.Links.Count > 0; + + AddHeader(ModelTransformerBase.COMMAND_ENTRIES_HEADING_LEVEL, MarkdownStrings.RELATED_LINKS, extraNewLine); foreach (var link in command.Links) { if (link.IsSimplifiedTextLink) @@ -143,7 +145,7 @@ private void AddInputOutput(MamlInputOutput io) return; } - var extraNewLine = string.IsNullOrEmpty(io.Description); + var extraNewLine = string.IsNullOrEmpty(io.Description) || ShouldBreak(io.FormatOption); AddHeader(ModelTransformerBase.INPUT_OUTPUT_TYPENAME_HEADING_LEVEL, io.TypeName, extraNewLine); AddParagraphs(io.Description); } @@ -265,9 +267,18 @@ private string JoinWithComma(IEnumerable args) return string.Join(", ", args); } + private bool ShouldBreak(SectionFormatOption formatOption) + { + // If the line break flag is set return true. + return formatOption.HasFlag(SectionFormatOption.LineBreakAfterHeader); + } + private void AddParameter(MamlParameter parameter, MamlCommand command) { - AddHeader(ModelTransformerBase.PARAMETERSET_NAME_HEADING_LEVEL, '-' + parameter.Name, extraNewLine: false); + var extraNewLine = ShouldBreak(parameter.FormatOption); + + AddHeader(ModelTransformerBase.PARAMETERSET_NAME_HEADING_LEVEL, '-' + parameter.Name, extraNewLine: extraNewLine); + // for some reason, in the update mode parameters produces extra newline. AddParagraphs(parameter.Description, this._mode == ParserMode.FormattingPreserve); @@ -332,7 +343,10 @@ private void AddExamples(MamlCommand command) AddHeader(ModelTransformerBase.COMMAND_ENTRIES_HEADING_LEVEL, MarkdownStrings.EXAMPLES); foreach (var example in command.Examples) { - AddHeader(ModelTransformerBase.EXAMPLE_HEADING_LEVEL, example.Title, extraNewLine: false); + var extraNewLine = ShouldBreak(example.FormatOption); + + AddHeader(ModelTransformerBase.EXAMPLE_HEADING_LEVEL, example.Title, extraNewLine: extraNewLine); + if (!string.IsNullOrEmpty(example.Introduction)) { AddParagraphs(example.Introduction); @@ -432,17 +446,17 @@ private void AddSyntax(MamlCommand command) } } - private void AddEntryHeaderWithText(string header, string text) + private void AddEntryHeaderWithText(string header, SectionBody body) { - AddHeader(ModelTransformerBase.COMMAND_ENTRIES_HEADING_LEVEL, header, extraNewLine: false); + var extraNewLine = body == null || string.IsNullOrEmpty(body.Text) || ShouldBreak(body.FormatOption); + + // Add header + AddHeader(ModelTransformerBase.COMMAND_ENTRIES_HEADING_LEVEL, header, extraNewLine: extraNewLine); + // to correctly handle empty text case, we are adding new-line here - if (string.IsNullOrEmpty(text)) - { - _stringBuilder.Append(Environment.NewLine); - } - else + if (body != null && !string.IsNullOrEmpty(body.Text)) { - AddParagraphs(text); + AddParagraphs(body.Text); } } @@ -525,6 +539,12 @@ private void AddParagraphs(string body, bool noNewLines = false) body = GetAutoWrappingForMarkdown(paragraphs.Select(para => GetEscapedMarkdownText(para.Trim())).ToArray()); } + // The the body already ended in a line break don't add extra lines on to the end + if (body.EndsWith("\r\n\r\n")) + { + noNewLines = true; + } + _stringBuilder.AppendFormat("{0}{1}{1}", body, noNewLines ? null : Environment.NewLine); } diff --git a/src/Markdown.MAML/Renderer/YamlRenderer.cs b/src/Markdown.MAML/Renderer/YamlRenderer.cs index 2fb2d355..95d2178a 100644 --- a/src/Markdown.MAML/Renderer/YamlRenderer.cs +++ b/src/Markdown.MAML/Renderer/YamlRenderer.cs @@ -22,9 +22,9 @@ public static string MamlModelToString(MamlCommand mamlCommand) var model = new YamlCommand { Name = mamlCommand.Name, - Notes = mamlCommand.Notes, - Remarks = mamlCommand.Description, - Summary = mamlCommand.Synopsis, + Notes = mamlCommand.Notes.Text, + Remarks = mamlCommand.Description.Text, + Summary = mamlCommand.Synopsis.Text, Examples = mamlCommand.Examples.Select(CreateExample).ToList(), Inputs = mamlCommand.Inputs.Select(CreateInputOutput).ToList(), Links = mamlCommand.Links.Select(CreateLink).ToList(), diff --git a/src/Markdown.MAML/Transformer/MamlModelMerger.cs b/src/Markdown.MAML/Transformer/MamlModelMerger.cs index 8b413c32..f08aaf48 100644 --- a/src/Markdown.MAML/Transformer/MamlModelMerger.cs +++ b/src/Markdown.MAML/Transformer/MamlModelMerger.cs @@ -1,4 +1,5 @@ using Markdown.MAML.Model.MAML; +using Markdown.MAML.Model.Markdown; using System; using System.Collections.Generic; using System.Linq; @@ -36,18 +37,9 @@ public MamlCommand Merge(MamlCommand metadataModel, MamlCommand stringModel) result = new MamlCommand() { Name = metadataModel.Name, - Synopsis = metadataStringCompare(metadataModel.Synopsis, - stringModel.Synopsis, - metadataModel.Name, - "synopsis"), - Description = metadataStringCompare(metadataModel.Description, - stringModel.Description, - metadataModel.Name, - "description"), - Notes = metadataStringCompare(metadataModel.Notes, - stringModel.Notes, - metadataModel.Name, - "notes"), + Synopsis = stringModel.Synopsis, + Description = stringModel.Description, + Notes = stringModel.Notes, Extent = stringModel.Extent }; } @@ -95,8 +87,6 @@ public MamlCommand Merge(MamlCommand metadataModel, MamlCommand stringModel) //Result takes in the merged parameter results. MergeParameters(result, metadataModel, stringModel); - - if (!_cmdletUpdated) { Report("\tNo updates done\r\n"); @@ -192,11 +182,7 @@ private void MergeParameters(MamlCommand result, MamlCommand metadataModel, Maml { try { - param.Description = metadataStringCompare(param.Description, - strParam.Description, - metadataModel.Name, - param.Name, - "parameter description"); + param.Description = strParam.Description; } catch (Exception ex) { @@ -228,6 +214,8 @@ private void MergeParameters(MamlCommand result, MamlCommand metadataModel, Maml _cmdletUpdated = true; } + // Update the parameter with the merged in FormatOption + param.FormatOption = strParam.FormatOption; } result.Parameters.Add(param); @@ -286,42 +274,6 @@ private void MergeParameters(MamlCommand result, MamlCommand metadataModel, Maml Report($" Exception parameter merge: \r\n{ex.Message}\r\n"); _cmdletUpdated = true; } - - - } - - /// - /// Compares parameters - /// - private string metadataStringCompare(string metadataContent, string stringContent, string moduleName, string paramName, string contentItemName) - { - var pretifiedStringContent = stringContent == null ? "" : Pretify(stringContent).TrimEnd(' '); - var pretifiedMetadataContent = metadataContent == null ? "" : Pretify(metadataContent).TrimEnd(' '); - - return stringContent; - } - - /// - /// Cleans the extra \r\n and inserts a tab at the beginning of new lines, mid paragraphs - /// - private static string Pretify(string multiLineText) - { - if(string.IsNullOrEmpty(multiLineText)) - { - multiLineText = ""; - } - return Regex.Replace(multiLineText, "(\r\n)+", "\r\n "); - } - - /// - /// Compares Cmdlet values: Synopsis, Description, and Notes. Preserves the content from the string model (old) or the metadata model (new). - /// - private string metadataStringCompare(string metadataContent, string stringContent, string moduleName, string contentItemName) - { - var metadataContentPretified = (metadataContent == null ? "" : Pretify(metadataContent).TrimEnd(' ')); - var stringContentPretified = (stringContent == null ? "" : Pretify(stringContent).TrimEnd(' ')); - - return stringContent; } private void Report(string format, params object[] objects) diff --git a/src/Markdown.MAML/Transformer/MamlMultiModelMerger.cs b/src/Markdown.MAML/Transformer/MamlMultiModelMerger.cs index dd660b35..945d29f9 100644 --- a/src/Markdown.MAML/Transformer/MamlMultiModelMerger.cs +++ b/src/Markdown.MAML/Transformer/MamlMultiModelMerger.cs @@ -1,4 +1,5 @@ using Markdown.MAML.Model.MAML; +using Markdown.MAML.Model.Markdown; using System; using System.Collections; using System.Collections.Generic; @@ -48,9 +49,9 @@ public MamlCommand Merge(Dictionary applicableTag2Model) result = new MamlCommand() { Name = referenceModel.Name, - Synopsis = MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Synopsis)), - Description = MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Description)), - Notes = MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Notes)), + Synopsis = new SectionBody(MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Synopsis.Text))), + Description = new SectionBody(MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Description.Text))), + Notes = new SectionBody(MergeText(tagsModel.ToDictionary(pair => pair.Key, pair => pair.Value.Notes.Text))), Extent = referenceModel.Extent }; diff --git a/src/Markdown.MAML/Transformer/ModelTransformerBase.cs b/src/Markdown.MAML/Transformer/ModelTransformerBase.cs index f861b3d1..b6b1a60a 100644 --- a/src/Markdown.MAML/Transformer/ModelTransformerBase.cs +++ b/src/Markdown.MAML/Transformer/ModelTransformerBase.cs @@ -176,6 +176,7 @@ protected MamlExample ExampleRule() Title = headingNode.Text }; example.Introduction = GetTextFromParagraphNode(ParagraphNodeRule()); + example.FormatOption = headingNode.FormatOption; CodeBlockNode codeBlock; while ((codeBlock = CodeBlockRule()) != null) { @@ -252,11 +253,11 @@ protected MamlInputOutput InputOutputRule() MamlInputOutput typeEntity = new MamlInputOutput() { - TypeName = headingNode.Text + TypeName = headingNode.Text, + Description = SimpleTextSectionRule(), + FormatOption = headingNode.FormatOption }; - typeEntity.Description = SimpleTextSectionRule(); - return typeEntity; } diff --git a/src/Markdown.MAML/Transformer/ModelTransformerVersion2.cs b/src/Markdown.MAML/Transformer/ModelTransformerVersion2.cs index b2b4d636..aeb5c76b 100644 --- a/src/Markdown.MAML/Transformer/ModelTransformerVersion2.cs +++ b/src/Markdown.MAML/Transformer/ModelTransformerVersion2.cs @@ -47,12 +47,12 @@ override protected bool SectionDispatch(MamlCommand command) { case "DESCRIPTION": { - command.Description = SimpleTextSectionRule(); + command.Description = new SectionBody(SimpleTextSectionRule(), headingNode.FormatOption); break; } case "SYNOPSIS": { - command.Synopsis = SimpleTextSectionRule(); + command.Synopsis = new SectionBody(SimpleTextSectionRule(), headingNode.FormatOption); break; } case "SYNTAX": @@ -82,7 +82,7 @@ override protected bool SectionDispatch(MamlCommand command) } case "NOTES": { - command.Notes = SimpleTextSectionRule(); + command.Notes = new SectionBody(SimpleTextSectionRule(), headingNode.FormatOption); break; } case "RELATED LINKS": @@ -378,6 +378,7 @@ private bool ParameterRule(MamlCommand commmand) }; parameter.Description = ParagraphOrCodeBlockNodeRule("yaml"); + parameter.FormatOption = headingNode.FormatOption; if (StringComparer.OrdinalIgnoreCase.Equals(parameter.Name, MarkdownStrings.CommonParametersToken)) { diff --git a/src/platyPS/platyPS.psm1 b/src/platyPS/platyPS.psm1 index b7ae116a..2a2e6687 100644 --- a/src/platyPS/platyPS.psm1 +++ b/src/platyPS/platyPS.psm1 @@ -427,7 +427,7 @@ function Update-MarkdownHelp } $md = ConvertMamlModelToMarkdown -mamlCommand $newModel -metadata $metadata -PreserveFormatting - MySetContent -path $file.FullName -value $md -Encoding $Encoding -Force # yeild + MySetContent -path $file.FullName -value $md -Encoding $Encoding -Force # yield } } } @@ -2276,7 +2276,7 @@ function ConvertPsObjectsToMamlModel #Get Description #Not provided by the command object. - $MamlCommandObject.Description = "{{Fill in the Description}}" + $MamlCommandObject.Description = New-Object -TypeName Markdown.MAML.Model.Markdown.SectionBody ("{{Fill in the Description}}") #endregion @@ -2457,24 +2457,24 @@ function ConvertPsObjectsToMamlModel # Help object ALWAYS contains SYNOPSIS. # If it's not available, it's auto-generated. # We don't want to include auto-generated SYNOPSIS (see https://github.com/PowerShell/platyPS/issues/110) - $MamlCommandObject.Synopsis = "{{Fill in the Synopsis}}" + $MamlCommandObject.Synopsis = New-Object -TypeName Markdown.MAML.Model.Markdown.SectionBody ("{{Fill in the Synopsis}}") } else { - $MamlCommandObject.Synopsis = $Help.Synopsis.Trim() + $MamlCommandObject.Synopsis = New-Object -TypeName Markdown.MAML.Model.Markdown.SectionBody ($Help.Synopsis.Trim()) } #Get Description if($Help.description -ne $null) { - $MamlCommandObject.Description = $Help.description.Text | AddLineBreaksForParagraphs + $MamlCommandObject.Description = New-Object -TypeName Markdown.MAML.Model.Markdown.SectionBody ($Help.description.Text | AddLineBreaksForParagraphs) } #Add to Notes #From the Help AlertSet data if($help.alertSet) { - $MamlCommandObject.Notes = $help.alertSet.alert.Text | AddLineBreaksForParagraphs + $MamlCommandObject.Notes = New-Object -TypeName Markdown.MAML.Model.Markdown.SectionBody ($help.alertSet.alert.Text | AddLineBreaksForParagraphs) } # Not provided by the command object. Using the Command Type to create a note declaring it's type. diff --git a/test/Markdown.MAML.Test/EndToEnd/EndToEndTests.cs b/test/Markdown.MAML.Test/EndToEnd/EndToEndTests.cs index 55cd7fb4..eaf93488 100644 --- a/test/Markdown.MAML.Test/EndToEnd/EndToEndTests.cs +++ b/test/Markdown.MAML.Test/EndToEnd/EndToEndTests.cs @@ -5,6 +5,7 @@ using Xunit; using Markdown.MAML.Parser; using Markdown.MAML.Transformer; +using System.Linq; namespace Markdown.MAML.Test.EndToEnd { @@ -47,6 +48,118 @@ And this is my last line. Assert.Equal(3, description.Length); } + [Fact] + public void PreserveMarkdownWhenUpdatingMarkdownHelp() + { + var expected = @"# Update-MarkdownHelp + +## SYNOPSIS + +Example markdown to test that markdown is preserved. + +## SYNTAX + +``` +Update-MarkdownHelp [-Name] [-Path ] +``` + +## DESCRIPTION +When calling Update-MarkdownHelp line breaks should be preserved. + +## EXAMPLES + +### Example 1: With no line break or description +``` +PS C:\> Update-MarkdownHelp +``` + +This is example 1 remark. + +### Example 2: With no line break +This is an example description. + +``` +PS C:\> Update-MarkdownHelp +``` + +This is example 2 remark. + +### Example 3: With line break and no description + +``` +PS C:\> Update-MarkdownHelp +``` + +This is example 3 remark. + +### Example 4: With line break and description + +This is an example description. + +``` +PS C:\> Update-MarkdownHelp +``` + +This is example 4 remark. + +## PARAMETERS + +### -Name + +Parameter name description with line break. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 1 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Path +Parameter path description with no line break. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +## INPUTS + +### String[] + +This is an input description. + +## OUTPUTS + +### System.Object + +This is an output description. + +## NOTES + +## RELATED LINKS +"; + + // Parse markdown and convert back to markdown to make sure there are no changes + var actualFull = MarkdownStringToMarkdownString(expected, ParserMode.Full); + var actualFormattingPreserve = MarkdownStringToMarkdownString(expected, ParserMode.FormattingPreserve); + + Assert.Equal(expected, actualFull); + Assert.Equal(expected, actualFormattingPreserve); + } + [Fact] public void CanUseSharpInsideParagraphs() { @@ -386,5 +499,20 @@ public static string MarkdownStringToMamlString(string markdown) return maml; } + + private string MarkdownStringToMarkdownString(string markdown, ParserMode parserMode) + { + // Parse + var parser = new MarkdownParser(); + var markdownModel = parser.ParseString(new string[] { markdown }, ParserMode.FormattingPreserve, null); + + // Convert model to Maml + var transformer = new ModelTransformerVersion2(); + var mamlModel = transformer.NodeModelToMamlModel(markdownModel).FirstOrDefault(); + + // Render as markdown + var renderer = new MarkdownV2Renderer(parserMode); + return renderer.MamlModelToString(mamlModel, true); + } } } diff --git a/test/Markdown.MAML.Test/Parser/ParserTests.cs b/test/Markdown.MAML.Test/Parser/ParserTests.cs index 26d77fd1..fb14b9de 100644 --- a/test/Markdown.MAML.Test/Parser/ParserTests.cs +++ b/test/Markdown.MAML.Test/Parser/ParserTests.cs @@ -4,6 +4,7 @@ using Markdown.MAML.Model.Markdown; using Markdown.MAML.Parser; using Xunit; +using System.Collections.Generic; namespace Markdown.MAML.Test.Parser { @@ -589,6 +590,83 @@ Deletes entries from the command history. Assert.Equal(descriptionText, paragraphNode.Spans.First().Text); } + [Fact] + public void PreservesLineBreakAfterHeaderWithHashPrefix() + { + var expectedSynopsis = "This is the synopsis text."; + var expectedDescription = "This is the description text."; + + // Parse markdown + var documentNode = MarkdownStringToDocumentNode($"## SYNOPSIS\r\n{expectedSynopsis}\r\n\r\n## DESCRIPTION\r\n\r\n{expectedDescription}"); + + // Get results + var actualSynopsis = GetParagraph(documentNode, "SYNOPSIS").Spans.FirstOrDefault().Text; + var actualDescription = GetParagraph(documentNode, "DESCRIPTION").Spans.FirstOrDefault().Text; + var synopsisHasLineBreak = GetHeading(documentNode, "SYNOPSIS").FormatOption == SectionFormatOption.LineBreakAfterHeader; + var descriptionHasLineBreak = GetHeading(documentNode, "DESCRIPTION").FormatOption == SectionFormatOption.LineBreakAfterHeader; + + // Check that text matches and line breaks haven't been captured as text + Assert.Equal(expectedSynopsis, actualSynopsis); + Assert.Equal(expectedDescription, actualDescription); + + // Does not use line break and should not be added + Assert.Equal(false, synopsisHasLineBreak); + + // Uses line break and should be preserved + Assert.Equal(true, descriptionHasLineBreak); + } + + [Fact] + public void PreservesLineBreakAfterParameter() + { + var expectedP1 = "Name parameter description."; + var expectedP2 = "Path parameter description."; + + // Parse markdown + var documentNode = MarkdownStringToDocumentNode($"## PARAMETERS\r\n\r\n### -Name\r\n{expectedP1}\r\n\r\n```yaml\r\n```\r\n\r\n### -Path\r\n\r\n{expectedP2}"); + + var actualP1 = GetParagraph(documentNode, "-Name").Spans.FirstOrDefault().Text; + var actualP2 = GetParagraph(documentNode, "-Path").Spans.FirstOrDefault().Text; + var hasLineBreakP1 = GetHeading(documentNode, "-Name").FormatOption == SectionFormatOption.LineBreakAfterHeader; + var hasLineBreakP2 = GetHeading(documentNode, "-Path").FormatOption == SectionFormatOption.LineBreakAfterHeader; + + // Check that text matches and line breaks haven't been captured as text + Assert.Equal(expectedP1, actualP1); + Assert.Equal(expectedP2, actualP2); + + // Does not use line break and should not be added + Assert.Equal(false, hasLineBreakP1); + + // Uses line break and should be preserved + Assert.Equal(true, hasLineBreakP2); + } + + [Fact] + public void PreservesLineBreakAfterHeaderWithUnderlines() + { + var expectedSynopsis = "This is the synopsis text."; + var expectedDescription = "This is the description text."; + + // Parse markdown + var documentNode = MarkdownStringToDocumentNode($"SYNOPSIS\r\n---\r\n{expectedSynopsis}\r\n\r\nDESCRIPTION\r\n---\r\n\r\n{expectedDescription}"); + + // Get results + var actualSynopsis = GetParagraph(documentNode, "SYNOPSIS").Spans.FirstOrDefault().Text; + var actualDescription = GetParagraph(documentNode, "DESCRIPTION").Spans.FirstOrDefault().Text; + var synopsisHasLineBreak = GetHeading(documentNode, "SYNOPSIS").FormatOption == SectionFormatOption.LineBreakAfterHeader; + var descriptionHasLineBreak = GetHeading(documentNode, "DESCRIPTION").FormatOption == SectionFormatOption.LineBreakAfterHeader; + + // Check that text matches and line breaks haven't been captured as text + Assert.Equal(expectedSynopsis, actualSynopsis); + Assert.Equal(expectedDescription, actualDescription); + + // Does not use line break and should not be added + Assert.Equal(false, synopsisHasLineBreak); + + // Uses line break and should be preserved + Assert.Equal(true, descriptionHasLineBreak); + } + private TNode ParseAndGetExpectedChild( string markdownString, MarkdownNodeType expectedNodeType) @@ -609,6 +687,30 @@ private TNode AssertNodeType( return Assert.IsType(markdownNode); } + private ParagraphNode GetParagraph(DocumentNode documentNode, string heading) + { + return documentNode + .Children + + // Skip until we reach the heading + .SkipWhile(node => node.NodeType != MarkdownNodeType.Heading || (node as HeadingNode).Text != heading) + + // Get the next paragraph after the heading + .Skip(1) + .OfType() + .FirstOrDefault(); + } + + private HeadingNode GetHeading(DocumentNode documentNode, string text = null) + { + return documentNode + .Children + .OfType() + + // If heading was specified, get the specific heading + .FirstOrDefault(node => string.IsNullOrEmpty(text) || node.Text == text); + } + private DocumentNode MarkdownStringToDocumentNode(string markdown) { var parser = new MarkdownParser(); diff --git a/test/Markdown.MAML.Test/Renderer/MamlRendererTests.cs b/test/Markdown.MAML.Test/Renderer/MamlRendererTests.cs index 8d52e9c3..87eda389 100644 --- a/test/Markdown.MAML.Test/Renderer/MamlRendererTests.cs +++ b/test/Markdown.MAML.Test/Renderer/MamlRendererTests.cs @@ -7,6 +7,7 @@ using Xunit; using Markdown.MAML.Test.EndToEnd; using Markdown.MAML.Model.MAML; +using Markdown.MAML.Model.Markdown; namespace Markdown.MAML.Test.Renderer { @@ -19,8 +20,8 @@ public void RendererProduceNameAndSynopsis() MamlCommand command = new MamlCommand() { Name = "Get-Foo", - Synopsis = "This is the synopsis", - Description = "This is a long description." + Synopsis = new SectionBody("This is the synopsis"), + Description = new SectionBody("This is a long description.") }; command.Parameters.Add(new MamlParameter() @@ -28,7 +29,7 @@ public void RendererProduceNameAndSynopsis() Type = "String", Name = "Name", Required = true, - Description = "Parameter Description.", + Description = "This is the name parameter description.", VariableLength = true, Globbing = true, PipelineInput = "True (ByValue)", @@ -36,6 +37,19 @@ public void RendererProduceNameAndSynopsis() Aliases = new string []{"GF","Foos","Do"}, } ); + command.Parameters.Add(new MamlParameter() + { + Type = "String", + Name = "Path", + Required = true, + Description = "This is the path parameter description.", + VariableLength = true, + Globbing = true, + PipelineInput = "True (ByValue)", + Position = "2", + Aliases = new string[] { }, + } + ); command.Inputs.Add(new MamlInputOutput() { TypeName = "String", @@ -73,6 +87,22 @@ public void RendererProduceNameAndSynopsis() string[] synopsis = EndToEndTests.GetXmlContent(maml, "/msh:helpItems/command:command/command:details/maml:description/maml:para"); Assert.Equal(1, synopsis.Length); Assert.Equal("This is the synopsis", synopsis[0]); + + string[] description = EndToEndTests.GetXmlContent(maml, "/msh:helpItems/command:command/maml:description/maml:para"); + Assert.Equal(1, description.Length); + Assert.Equal("This is a long description.", description[0]); + + string[] parameter1 = EndToEndTests.GetXmlContent(maml, "/msh:helpItems/command:command/command:parameters/command:parameter[maml:name='Name']/maml:Description/maml:para"); + Assert.Equal(1, parameter1.Length); + Assert.Equal("This is the name parameter description.", parameter1[0]); + + string[] parameter2 = EndToEndTests.GetXmlContent(maml, "/msh:helpItems/command:command/command:parameters/command:parameter[maml:name='Path']/maml:Description/maml:para"); + Assert.Equal(1, parameter2.Length); + Assert.Equal("This is the path parameter description.", parameter2[0]); + + string[] example1 = EndToEndTests.GetXmlContent(maml, "/msh:helpItems/command:command/command:examples/command:example[maml:title='Example 1']/dev:code"); + Assert.Equal(1, example1.Length); + Assert.Equal("PS:> Get-Help -YouNeedIt", example1[0]); } [Fact] @@ -130,14 +160,14 @@ public void RendererProduceEscapeXmlSpecialChars() MamlCommand command = new MamlCommand() { Name = "Get-Foo", - Synopsis = "" // < and > should be properly escaped + Synopsis = new SectionBody("") // < and > should be properly escaped }; string maml = renderer.MamlModelToString(new[] { command }); string[] synopsis = EndToEndTests.GetXmlContent(maml, "/msh:helpItems/command:command/command:details/maml:description/maml:para"); Assert.Equal(1, synopsis.Length); - Assert.Equal(synopsis[0], command.Synopsis); + Assert.Equal(synopsis[0], command.Synopsis.Text); } } diff --git a/test/Markdown.MAML.Test/Renderer/MarkdownV2RendererTests.cs b/test/Markdown.MAML.Test/Renderer/MarkdownV2RendererTests.cs index f977f96d..6127df2f 100644 --- a/test/Markdown.MAML.Test/Renderer/MarkdownV2RendererTests.cs +++ b/test/Markdown.MAML.Test/Renderer/MarkdownV2RendererTests.cs @@ -8,6 +8,7 @@ using Xunit; using System.Collections; using Markdown.MAML.Parser; +using Markdown.MAML.Model.Markdown; namespace Markdown.MAML.Test.Renderer { @@ -56,6 +57,130 @@ public void ReturnsSyntaxString() Assert.Equal("Get-Foo [-Bar ] []", syntaxString); } + [Fact] + public void RendererIgnoresLineBreakWhenBodyIsEmpty() + { + var renderer = new MarkdownV2Renderer(ParserMode.Full); + MamlCommand command = new MamlCommand() + { + Name = "Test-LineBreak", + Notes = new SectionBody("", SectionFormatOption.LineBreakAfterHeader) + }; + + string markdown = renderer.MamlModelToString(command, null); + + Assert.DoesNotContain("\r\n\r\n\r\n", markdown); + } + + [Fact] + public void RendererLineBreakAfterParameter() + { + var renderer = new MarkdownV2Renderer(ParserMode.Full); + + MamlCommand command = new MamlCommand() + { + Name = "Test-LineBreak", + Synopsis = new SectionBody("This is the synopsis"), + Description = new SectionBody("This is a long description"), + Notes = new SectionBody("This is a note") + }; + + var parameter1 = new MamlParameter() + { + Type = "String", + Name = "Name", + Required = true, + Description = "Name description.", + Globbing = true + }; + + var parameter2 = new MamlParameter() + { + Type = "String", + Name = "Path", + FormatOption = SectionFormatOption.LineBreakAfterHeader, + Required = true, + Description = "Path description.", + Globbing = true + }; + + command.Parameters.Add(parameter1); + command.Parameters.Add(parameter2); + + var syntax1 = new MamlSyntax() + { + ParameterSetName = "ByName" + }; + + syntax1.Parameters.Add(parameter1); + syntax1.Parameters.Add(parameter2); + command.Syntax.Add(syntax1); + + string markdown = renderer.MamlModelToString(command, null); + + // Does not use line break and should not be added + Assert.Contains("### -Name\r\nName description.", markdown); + + // Uses line break and should be preserved + Assert.Contains("### -Path\r\n\r\nPath description.", markdown); + } + + [Fact] + public void RendererLineBreakAfterExample() + { + var renderer = new MarkdownV2Renderer(ParserMode.Full); + + MamlCommand command = new MamlCommand() + { + Name = "Test-LineBreak", + }; + + var example1 = new MamlExample() + { + Title = "Example 1", + Code = "PS C:\\> Get-Help", + Remarks = "This is an example to get help." + }; + + var example2 = new MamlExample() + { + Title = "Example 2", + Code = "PS C:\\> Get-Help -Full", + Introduction = "Intro" + }; + + var example3 = new MamlExample() + { + Title = "Example 3", + FormatOption = SectionFormatOption.LineBreakAfterHeader, + Code = "PS C:\\> Get-Help", + Remarks = "This is an example to get help." + }; + + var example4 = new MamlExample() + { + Title = "Example 4", + FormatOption = SectionFormatOption.LineBreakAfterHeader, + Code = "PS C:\\> Get-Help -Full", + Introduction = "Intro" + }; + + command.Examples.Add(example1); + command.Examples.Add(example2); + command.Examples.Add(example3); + command.Examples.Add(example4); + + string markdown = renderer.MamlModelToString(command, null); + + // Does not use line break and should not be added + Assert.Contains("### Example 1\r\n```", markdown); + Assert.Contains("### Example 2\r\nIntro\r\n\r\n```", markdown); + + // Uses line break and should be preserved + Assert.Contains("### Example 3\r\n\r\n```", markdown); + Assert.Contains("### Example 4\r\n\r\nIntro\r\n\r\n```", markdown); + } + [Fact] public void RendererCreatesWorkflowParametersEntry() { @@ -104,7 +229,6 @@ Workflow [] [] ## NOTES ## RELATED LINKS - ", markdown); } @@ -115,7 +239,7 @@ public void RendererNormalizeQuotesAndDashes() MamlCommand command = new MamlCommand() { Name = "Test-Quotes", - Description = @"”“‘’––-" + Description = new SectionBody(@"”“‘’––-") }; string markdown = renderer.MamlModelToString(command, null); @@ -147,7 +271,6 @@ public void RendererNormalizeQuotesAndDashes() ## NOTES ## RELATED LINKS - ", markdown); } @@ -158,9 +281,9 @@ public void RendererProduceMarkdownV2Output() MamlCommand command = new MamlCommand() { Name = "Get-Foo", - Synopsis = "This is the synopsis", - Description = "This is a long description.\r\nWith two paragraphs. And the second one contains of few line! They should be auto-wrapped. Because, why not? We can do that kind of the things, no problem.\r\n\r\n-- Foo. Bar.\r\n-- Don't break. The list.\r\n-- Into. Pieces", - Notes = "This is a multiline note.\r\nSecond line." + Synopsis = new SectionBody("This is the synopsis"), + Description = new SectionBody("This is a long description.\r\nWith two paragraphs. And the second one contains of few line! They should be auto-wrapped. Because, why not? We can do that kind of the things, no problem.\r\n\r\n-- Foo. Bar.\r\n-- Don't break. The list.\r\n-- Into. Pieces"), + Notes = new SectionBody("This is a multiline note.\r\nSecond line.") }; var parameter = new MamlParameter() @@ -460,7 +583,6 @@ public void RenderesAllParameterSetMoniker() ## NOTES ## RELATED LINKS - ", markdown); } @@ -472,7 +594,7 @@ public void RenderesWithPreservedFormatting() { Name = "Get-Foo", SupportCommonParameters = false, - Description = @"Hello + Description = new SectionBody(@"Hello This \ should be preserved by renderer With all [hyper](https://links.com) and yada -- yada @@ -483,7 +605,7 @@ With all [hyper](https://links.com) and yada * [ ] But * [ ] It should be left" - }; + )}; command.Links.Add( new MamlLink(isSimplifiedTextLink: true) diff --git a/test/Markdown.MAML.Test/Renderer/YamlRendererTests.cs b/test/Markdown.MAML.Test/Renderer/YamlRendererTests.cs index 50c6e82c..48bb359d 100644 --- a/test/Markdown.MAML.Test/Renderer/YamlRendererTests.cs +++ b/test/Markdown.MAML.Test/Renderer/YamlRendererTests.cs @@ -9,6 +9,7 @@ using Xunit; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; +using Markdown.MAML.Model.Markdown; namespace Markdown.MAML.Test.Renderer { @@ -27,9 +28,9 @@ public void RendererProducesNameAndTextFields() Assert.NotNull(writtenModel); Assert.Equal(model.Name, writtenModel.Name); - Assert.Equal(model.Description, writtenModel.Remarks); - Assert.Equal(model.Synopsis, writtenModel.Summary); - Assert.Equal(model.Notes, writtenModel.Notes); + Assert.Equal(model.Description.Text, writtenModel.Remarks); + Assert.Equal(model.Synopsis.Text, writtenModel.Summary); + Assert.Equal(model.Notes.Text, writtenModel.Notes); } [Fact] @@ -206,9 +207,9 @@ private static MamlCommand CreateModel() var command = new MamlCommand { Name = "Test-Unit", - Description = "A test cmdlet", - Synopsis = "A cmdlet to test", - Notes = "This is just a test", + Description = new SectionBody("A test cmdlet"), + Synopsis = new SectionBody("A cmdlet to test"), + Notes = new SectionBody("This is just a test"), ModuleName = "TestModule" }; diff --git a/test/Markdown.MAML.Test/Transformer/MamlModelMergerTests.cs b/test/Markdown.MAML.Test/Transformer/MamlModelMergerTests.cs index 10d479ed..b2c1a69c 100644 --- a/test/Markdown.MAML.Test/Transformer/MamlModelMergerTests.cs +++ b/test/Markdown.MAML.Test/Transformer/MamlModelMergerTests.cs @@ -1,4 +1,5 @@ using Markdown.MAML.Model.MAML; +using Markdown.MAML.Model.Markdown; using Markdown.MAML.Transformer; using System; using System.Collections.Generic; @@ -22,8 +23,8 @@ public void RendererProduceMarkdownV2Output() var result = merger.Merge(metadataCommand, originalCommand); - Assert.Equal(2, result.Parameters.Count); - Assert.Equal(2, originalCommand.Parameters.Count); + Assert.Equal(3, result.Parameters.Count); + Assert.Equal(3, originalCommand.Parameters.Count); Assert.Equal("Name", result.Parameters[0].Name); Assert.Equal("NewParam", result.Parameters[1].Name); Assert.Contains("Parameter Updated: Name", _reportStream); @@ -35,15 +36,18 @@ public void RendererProduceMarkdownV2Output() Assert.Contains("Parameter Deleted: Remove", _reportStream); Assert.Contains("---- UPDATING Cmdlet : Get-Foo ----", _reportStream); Assert.Contains("---- COMPLETED UPDATING Cmdlet : Get-Foo ----\r\n\r\n", _reportStream); - + Assert.Equal(originalCommand.Synopsis.Text, result.Synopsis.Text); + Assert.Equal(originalCommand.Description.Text, result.Description.Text); + Assert.Equal(originalCommand.Notes.Text, result.Notes.Text); Assert.Equal(originalCommand.Parameters[0].Description, result.Parameters[0].Description); + Assert.Equal(originalCommand.Parameters[0].FormatOption, result.Parameters[0].FormatOption); + Assert.Equal(originalCommand.Parameters[2].Description, result.Parameters[2].Description); + Assert.Equal(originalCommand.Parameters[2].FormatOption, result.Parameters[2].FormatOption); Assert.Equal(originalCommand.Links.Count, result.Links.Count); Assert.Equal(originalCommand.Links[0].LinkName, result.Links[0].LinkName); Assert.Equal(originalCommand.Links[0].LinkUri, result.Links[0].LinkUri); - - } private void WriteMessage(string message) @@ -56,9 +60,9 @@ private MamlCommand GetOriginal() MamlCommand originalCommand = new MamlCommand() { Name = "Get-Foo", - Synopsis = "This is the synopsis", - Description = "This is a long description.\r\nWith two paragraphs.", - Notes = "This is a multiline note.\r\nSecond line." + Synopsis = new SectionBody("This is the synopsis"), + Description = new SectionBody("This is a long description.\r\nWith two paragraphs."), + Notes = new SectionBody("This is a multiline note.\r\nSecond line.") }; var parameterName = new MamlParameter() @@ -87,9 +91,19 @@ private MamlCommand GetOriginal() DefaultValue = "dodododo", Aliases = new string[] { "Pa1", "RemovedParam", "Gone" }, }; + var parameterPath = new MamlParameter() + { + Type = "String", + Name = "Path", + Required = true, + Description = "Parameter path description.", + FormatOption = SectionFormatOption.LineBreakAfterHeader, + Globbing = true + }; originalCommand.Parameters.Add(parameterName); originalCommand.Parameters.Add(removedParameterName); + originalCommand.Parameters.Add(parameterPath); var syntax1 = new MamlSyntax() { @@ -97,6 +111,7 @@ private MamlCommand GetOriginal() }; syntax1.Parameters.Add(parameterName); syntax1.Parameters.Add(removedParameterName); + syntax1.Parameters.Add(parameterPath); originalCommand.Syntax.Add(syntax1); var syntax2 = new MamlSyntax() @@ -105,10 +120,9 @@ private MamlCommand GetOriginal() IsDefault = true }; syntax2.Parameters.Add(parameterName); + syntax2.Parameters.Add(parameterPath); originalCommand.Syntax.Add(syntax2); - - originalCommand.Inputs.Add(new MamlInputOutput() { TypeName = "String", @@ -150,9 +164,9 @@ private MamlCommand GetRegenerated() MamlCommand metadataCommand = new MamlCommand() { Name = "Get-Foo", - Description = "This is a long description.\r\nWith two paragraphs.", - Synopsis = "This is a old synopsis.", - Notes = "These are old notes" + Description = new SectionBody("This is a long description.\r\nWith two paragraphs."), + Synopsis = new SectionBody("This is a old synopsis."), // DIFF!! + Notes = new SectionBody("These are old notes") // DIFF!! }; var parameterName1 = new MamlParameter() @@ -175,8 +189,18 @@ private MamlCommand GetRegenerated() Description = "Old Param Description" }; + var parameterPath = new MamlParameter() + { + Type = "String", + Name = "Path", + Required = true, + Description = "Parameter path description.", + Globbing = true + }; + metadataCommand.Parameters.Add(parameterName1); metadataCommand.Parameters.Add(parameterNew); + metadataCommand.Parameters.Add(parameterPath); var syntax3 = new MamlSyntax() { @@ -185,6 +209,7 @@ private MamlCommand GetRegenerated() syntax3.Parameters.Add(parameterName1); syntax3.Parameters.Add(parameterNew); + syntax3.Parameters.Add(parameterPath); metadataCommand.Syntax.Add(syntax3); var syntax4 = new MamlSyntax() @@ -194,6 +219,7 @@ private MamlCommand GetRegenerated() }; syntax4.Parameters.Add(parameterName1); + syntax4.Parameters.Add(parameterPath); metadataCommand.Syntax.Add(syntax4); return metadataCommand; diff --git a/test/Markdown.MAML.Test/Transformer/MamlMultiModelMergerTests.cs b/test/Markdown.MAML.Test/Transformer/MamlMultiModelMergerTests.cs index 7b8db5b0..47e26d54 100644 --- a/test/Markdown.MAML.Test/Transformer/MamlMultiModelMergerTests.cs +++ b/test/Markdown.MAML.Test/Transformer/MamlMultiModelMergerTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using Xunit; +using Markdown.MAML.Model.Markdown; namespace Markdown.MAML.Test.Transformer { @@ -20,7 +21,7 @@ public void Merge3SimpleModels() var result = merger.Merge(input); - Assert.Equal(result.Synopsis, @"! First, Second + Assert.Equal(result.Synopsis.Text, @"! First, Second This is the synopsis @@ -30,9 +31,9 @@ This is the synopsis 3 "); - Assert.Equal(result.Description, "This is a long description.\r\nWith two paragraphs."); + Assert.Equal(result.Description.Text, "This is a long description.\r\nWith two paragraphs."); - Assert.Equal(result.Notes, @"! First + Assert.Equal(result.Notes.Text, @"! First This is a multiline note. Second line. @@ -98,9 +99,9 @@ private MamlCommand GetModel1() MamlCommand command = new MamlCommand() { Name = "Get-Foo", - Synopsis = "This is the synopsis", - Description = "This is a long description.\r\nWith two paragraphs.", - Notes = "This is a multiline note.\r\nSecond line.\r\nFirst Command" + Synopsis = new SectionBody("This is the synopsis"), + Description = new SectionBody("This is a long description.\r\nWith two paragraphs."), + Notes = new SectionBody("This is a multiline note.\r\nSecond line.\r\nFirst Command") }; command.Links.Add(new MamlLink(true) @@ -154,9 +155,9 @@ private MamlCommand GetModel2() MamlCommand command = new MamlCommand() { Name = "Get-Foo", - Synopsis = "This is the synopsis", - Description = "This is a long description.\r\nWith two paragraphs.", - Notes = "This is a multiline note.\r\nSecond line.\r\nSecond Command" + Synopsis = new SectionBody("This is the synopsis"), + Description = new SectionBody("This is a long description.\r\nWith two paragraphs."), + Notes = new SectionBody("This is a multiline note.\r\nSecond line.\r\nSecond Command") }; command.Links.Add(new MamlLink(true) @@ -211,9 +212,9 @@ private MamlCommand GetModel3() MamlCommand command = new MamlCommand() { Name = "Get-Foo", - Synopsis = "This is the synopsis 3", - Description = "This is a long description.\r\nWith two paragraphs.", - Notes = "This is a multiline note.\r\nSecond line.\r\nThird Command" + Synopsis = new SectionBody("This is the synopsis 3"), + Description = new SectionBody("This is a long description.\r\nWith two paragraphs."), + Notes = new SectionBody("This is a multiline note.\r\nSecond line.\r\nThird Command") }; command.Links.Add(new MamlLink(true) @@ -338,9 +339,9 @@ private MamlCommand GetModel1() MamlCommand command = new MamlCommand() { Name = "Get-Foo", - Synopsis = "This is the synopsis", - Description = "This is a long description.\r\nWith two paragraphs.", - Notes = "This is a multiline note.\r\nSecond line.\r\nFirst Command" + Synopsis = new SectionBody("This is the synopsis"), + Description = new SectionBody("This is a long description.\r\nWith two paragraphs."), + Notes = new SectionBody("This is a multiline note.\r\nSecond line.\r\nFirst Command") }; var parameterName = new MamlParameter() @@ -371,9 +372,9 @@ private MamlCommand GetModel2() MamlCommand command = new MamlCommand() { Name = "Get-Foo", - Synopsis = "This is the synopsis", - Description = "This is a long description.\r\nWith two paragraphs.", - Notes = "This is a multiline note.\r\nSecond line.\r\nSecond Command" + Synopsis = new SectionBody("This is the synopsis"), + Description = new SectionBody("This is a long description.\r\nWith two paragraphs."), + Notes = new SectionBody("This is a multiline note.\r\nSecond line.\r\nSecond Command") }; var parameterName = new MamlParameter() diff --git a/test/Markdown.MAML.Test/Transformer/ParserAndTransformerTestsV2.cs b/test/Markdown.MAML.Test/Transformer/ParserAndTransformerTestsV2.cs index 7acd1030..8dd714b1 100644 --- a/test/Markdown.MAML.Test/Transformer/ParserAndTransformerTestsV2.cs +++ b/test/Markdown.MAML.Test/Transformer/ParserAndTransformerTestsV2.cs @@ -20,8 +20,8 @@ public void TransformSimpleCommand() This is Synopsis "); MamlCommand mamlCommand = NodeModelToMamlModelV2(doc).First(); - Assert.Equal(mamlCommand.Name, "Get-Foo"); - Assert.Equal(mamlCommand.Synopsis, "This is Synopsis"); + Assert.Equal("Get-Foo", mamlCommand.Name); + Assert.Equal("This is Synopsis", mamlCommand.Synopsis.Text); } [Fact] @@ -34,8 +34,8 @@ public void TransformSynopsisWithHyperlink() Here is a [hyperlink](http://non-existing-uri). "); MamlCommand mamlCommand = NodeModelToMamlModelV2(doc).First(); - Assert.Equal(mamlCommand.Name, "Get-Foo"); - Assert.Equal(mamlCommand.Synopsis, "Here is a hyperlink (http://non-existing-uri)."); + Assert.Equal("Get-Foo", mamlCommand.Name); + Assert.Equal("Here is a hyperlink (http://non-existing-uri).", mamlCommand.Synopsis.Text); } [Fact] @@ -54,14 +54,13 @@ public void SkipYamlMetadataBlock() This is Synopsis "); MamlCommand mamlCommand = NodeModelToMamlModelV2(doc).First(); - Assert.Equal(mamlCommand.Name, "Get-Foo"); - Assert.Equal(mamlCommand.Synopsis, "This is Synopsis"); + Assert.Equal("Get-Foo", mamlCommand.Name); + Assert.Equal("This is Synopsis", mamlCommand.Synopsis.Text); } [Fact] public void TransformCommandWithExtraLine() { - var doc = ParseString(@" #Add-Member @@ -70,8 +69,73 @@ Adds custom properties and methods to an instance of a Windows PowerShell object "); MamlCommand mamlCommand = NodeModelToMamlModelV2(doc).First(); - Assert.Equal(mamlCommand.Name, "Add-Member"); - Assert.Equal(mamlCommand.Synopsis, "Adds custom properties and methods to an instance of a Windows PowerShell object."); + Assert.Equal("Add-Member", mamlCommand.Name); + Assert.Equal("Adds custom properties and methods to an instance of a Windows PowerShell object.", mamlCommand.Synopsis.Text); + } + + [Fact] + public void TransformCommandWithHeaderLineBreak() + { + var doc = ParseString(@" +#Add-Member + +##SYNOPSIS + +Adds custom properties and methods to an instance of a Windows PowerShell object."); + MamlCommand mamlCommand = NodeModelToMamlModelV2(doc).First(); + Assert.Equal("Add-Member", mamlCommand.Name); + Assert.Equal("Adds custom properties and methods to an instance of a Windows PowerShell object.", mamlCommand.Synopsis.Text); + Assert.Equal(SectionFormatOption.LineBreakAfterHeader, mamlCommand.Synopsis.FormatOption); + } + + [Fact] + public void TransformCommandWithParameterHeaderLineBreak() + { + var doc = ParseString(@" +# Add-Member + +## PARAMETERS + +### -Name + +This is the name parameter. + +```yaml +Type: String +Parameter Sets: (All) +Aliases: + +Required: True +Position: 0 +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` +"); + MamlCommand mamlCommand = NodeModelToMamlModelV2(doc).First(); + Assert.Equal("This is the name parameter.", mamlCommand.Parameters[0].Description); + Assert.Equal(SectionFormatOption.LineBreakAfterHeader, mamlCommand.Parameters[0].FormatOption); + } + + [Fact] + public void TransformCommandWithExampleHeaderLineBreak() + { + var doc = ParseString(@" +# Add-Member + +## EXAMPLES + +### Example 1 + +This is an example. + +```powershell +PS C:\> Get-PSDocumentHeader -Path '.\build\Default\Server1.md'; +``` +"); + MamlCommand mamlCommand = NodeModelToMamlModelV2(doc).First(); + Assert.Equal("This is an example.", mamlCommand.Examples[0].Introduction); + Assert.Equal(SectionFormatOption.LineBreakAfterHeader, mamlCommand.Examples[0].FormatOption); } [Fact] @@ -91,7 +155,7 @@ public void TransformMultilineDescription() And this is my last line. "); MamlCommand mamlCommand = NodeModelToMamlModelV2(doc).First(); - string[] description = mamlCommand.Description.Split('\n').Select(x => x.Trim()).ToArray(); + string[] description = mamlCommand.Description.Text.Split('\n').Select(x => x.Trim()).ToArray(); Assert.Equal(3, description.Length); Assert.Equal("Hello,", description[0]); Assert.Equal("I'm a multiline description.", description[1]); @@ -358,49 +422,43 @@ public void ProduceRelatedLinks() [Fact] public void HandlesHyperLinksInsideText() { - var doc = ParseString(@" # Get-Foo ## SYNOPSIS - Runs the [Set-WSManQuickConfig]() cmdlet "); var mamlCommand = NodeModelToMamlModelV2(doc).ToArray(); Assert.Equal(mamlCommand.Count(), 1); - Assert.Equal(mamlCommand[0].Synopsis, "Runs the Set-WSManQuickConfig cmdlet"); + Assert.Equal("Runs the Set-WSManQuickConfig cmdlet", mamlCommand[0].Synopsis.Text); } [Fact] public void HandlesItalicInsideText() { - var doc = ParseString(@" # Get-Foo ## SYNOPSIS - Runs the *Set-WSManQuickConfig* cmdlet "); var mamlCommand = NodeModelToMamlModelV2(doc).ToArray(); Assert.Equal(mamlCommand.Count(), 1); - Assert.Equal(mamlCommand[0].Synopsis, "Runs the Set-WSManQuickConfig cmdlet"); + Assert.Equal("Runs the Set-WSManQuickConfig cmdlet", mamlCommand[0].Synopsis.Text); } [Fact] public void HandlesBoldInsideText() { - var doc = ParseString(@" # Get-Foo ## SYNOPSIS - Runs the **Set-WSManQuickConfig** cmdlet "); var mamlCommand = NodeModelToMamlModelV2(doc).ToArray(); Assert.Equal(mamlCommand.Count(), 1); - Assert.Equal(mamlCommand[0].Synopsis, "Runs the Set-WSManQuickConfig cmdlet"); + Assert.Equal("Runs the Set-WSManQuickConfig cmdlet", mamlCommand[0].Synopsis.Text); } [Fact] @@ -504,7 +562,6 @@ public void ProducesParameterEntriesForCornerCases() ## PARAMETERS ### NonExistingTypeParam - This is NonExistingTypeParam description. ```yaml @@ -1014,7 +1071,7 @@ This is intentional. MamlCommand mamlCommand = NodeModelToMamlModelV2(doc).First(); Assert.Equal("Get-Foo", mamlCommand.Name); - Assert.Equal(description, mamlCommand.Description); + Assert.Equal(description, mamlCommand.Description.Text); } private DocumentNode ParseString(string markdown) diff --git a/test/Pester/PlatyPs.Tests.ps1 b/test/Pester/PlatyPs.Tests.ps1 index 1e6d2ca7..d005a7c3 100644 --- a/test/Pester/PlatyPs.Tests.ps1 +++ b/test/Pester/PlatyPs.Tests.ps1 @@ -606,9 +606,9 @@ Describe 'Get-Help & Get-Command on Add-Computer to build MAML Model Object' { It 'Validates attributes by checking several sections of the single attributes for Add-Computer' { $mamlModelObject.Name | Should be "Add-Computer" - $mamlModelObject.Synopsis | Should be "Add the local computer to a domain or workgroup." - $mamlModelObject.Description.Substring(0,135) | Should be "The Add-Computer cmdlet adds the local computer or remote computers to a domain or workgroup, or moves them from one domain to another." - $mamlModelObject.Notes.Substring(0,31) | Should be "In Windows PowerShell 2.0, the " + $mamlModelObject.Synopsis.Text | Should be "Add the local computer to a domain or workgroup." + $mamlModelObject.Description.Text.Substring(0,135) | Should be "The Add-Computer cmdlet adds the local computer or remote computers to a domain or workgroup, or moves them from one domain to another." + $mamlModelObject.Notes.Text.Substring(0,31) | Should be "In Windows PowerShell 2.0, the " } It 'Validates the examples by checking Add-Computer Example 1' {