diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs index cb76de25ee..a2592d9bf1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertFromMarkdownCommand.cs @@ -19,28 +19,28 @@ namespace Microsoft.PowerShell.Commands /// [Cmdlet( VerbsData.ConvertFrom, "Markdown", - DefaultParameterSetName = PathParamSet, + DefaultParameterSetName = PathParameterSet, HelpUri = "TBD" )] [OutputType(typeof(Microsoft.PowerShell.MarkdownRender.MarkdownInfo))] public class ConvertFromMarkdownCommand : PSCmdlet { /// - /// Path to the file to convert from Markdown to MarkdownInfo + /// Path to the file to convert from Markdown to MarkdownInfo. /// [ValidateNotNullOrEmpty] - [Parameter(ParameterSetName = PathParamSet, Mandatory = true)] + [Parameter(ParameterSetName = PathParameterSet, Mandatory = true)] public string[] Path { get; set; } /// - /// Path to the file to convert from Markdown to MarkdownInfo + /// Path to the file to convert from Markdown to MarkdownInfo. /// [ValidateNotNullOrEmpty] - [Parameter(ParameterSetName = LitPathParamSet, Mandatory = true)] + [Parameter(ParameterSetName = LiteralPathParameterSet, Mandatory = true)] public string[] LiteralPath { get; set; } /// - /// InputObject of type System.IO.FileInfo or string with content to convert from Markdown to MarkdownInfo + /// InputObject of type System.IO.FileInfo or string with content to convert from Markdown to MarkdownInfo. /// [ValidateNotNullOrEmpty] [Parameter(ParameterSetName = InputObjParamSet, Mandatory = true, ValueFromPipeline = true)] @@ -52,19 +52,18 @@ namespace Microsoft.PowerShell.Commands [Parameter()] public SwitchParameter AsVT100EncodedString { get; set; } - private const string PathParamSet = "PathParamSet"; - private const string LitPathParamSet = "LiteralParamSet"; - + private const string PathParameterSet = "PathParamSet"; + private const string LiteralPathParameterSet = "LiteralParamSet"; private const string InputObjParamSet = "InputObjParamSet"; + private MarkdownConversionType conversionType = MarkdownConversionType.HTML; + private MarkdownOptionInfo mdOption = null; /// - /// Override ProcessRecord + /// Override BeginProcess. /// - protected override void ProcessRecord() + protected override void BeginProcessing() { - var conversionType = MarkdownConversionType.HTML; - - var mdOption = (SessionState.PSVariable.GetValue("MarkdownOptionInfo", new MarkdownOptionInfo())) as MarkdownOptionInfo; + mdOption = (SessionState.PSVariable.GetValue("MarkdownOptionInfo", new MarkdownOptionInfo())) as MarkdownOptionInfo; if(mdOption == null) { @@ -75,14 +74,20 @@ namespace Microsoft.PowerShell.Commands { conversionType = MarkdownConversionType.VT100; } + } + /// + /// Override ProcessRecord. + /// + protected override void ProcessRecord() + { switch (ParameterSetName) { case InputObjParamSet: Object baseObj = InputObject.BaseObject; - var fileInfo = baseObj as FileInfo; - if (fileInfo != null) + //var fileInfo = baseObj as FileInfo; + if (baseObj is FileInfo fileInfo) { WriteObject( MarkdownConverter.Convert( @@ -92,33 +97,28 @@ namespace Microsoft.PowerShell.Commands ) ); } + else if (baseObj is string inpObj) + { + WriteObject(MarkdownConverter.Convert(inpObj, conversionType, mdOption)); + } else { - var inpObj = baseObj as string; - if (inpObj != null) - { - WriteObject(MarkdownConverter.Convert(inpObj, conversionType, mdOption)); - } - else - { - string errorMessage = StringUtil.Format(ConvertMarkdownStrings.InvalidInputObjectType, baseObj.GetType()); - ErrorRecord errorRecord = new ErrorRecord( - new InvalidDataException(errorMessage), - "InvalidInputObject", - ErrorCategory.InvalidData, - InputObject); + string errorMessage = StringUtil.Format(ConvertMarkdownStrings.InvalidInputObjectType, baseObj.GetType()); + ErrorRecord errorRecord = new ErrorRecord( + new InvalidDataException(errorMessage), + "InvalidInputObject", + ErrorCategory.InvalidData, + InputObject); - WriteError(errorRecord); - } + WriteError(errorRecord); } - break; - case PathParamSet: + case PathParameterSet: ConvertEachFile(Path, conversionType, isLiteral: false, optionInfo: mdOption); break; - case LitPathParamSet: + case LiteralPathParameterSet: ConvertEachFile(LiteralPath, conversionType, isLiteral: true, optionInfo: mdOption); break; } @@ -128,7 +128,6 @@ namespace Microsoft.PowerShell.Commands { foreach (var path in paths) { - // ResolvePath checks for file existence. var resolvedPaths = ResolvePath(path, isLiteral); foreach (var resolvedPath in resolvedPaths) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs index eba767db17..e2ce0bf931 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MarkdownOptionCommands.cs @@ -22,101 +22,112 @@ namespace Microsoft.PowerShell.Commands public class SetMarkdownOptionCommand : PSCmdlet { /// + /// Gets or sets the VT100 escape sequence for Header Level 1. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] public string Header1Color { get; set;} /// + /// Gets or sets the VT100 escape sequence for Header Level 2. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] public string Header2Color { get; set;} /// + /// Gets or sets the VT100 escape sequence for Header Level 3. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] public string Header3Color { get; set;} /// + /// Gets or sets the VT100 escape sequence for Header Level 4. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] public string Header4Color { get; set;} /// + /// Gets or sets the VT100 escape sequence for Header Level 5. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] public string Header5Color { get; set;} /// + /// Gets or sets the VT100 escape sequence for Header Level 6. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] public string Header6Color { get; set;} /// + /// Gets or sets the VT100 escape sequence for code block background. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] - public string CodeBlockForegroundColor { get; set;} - - /// - /// - [ValidatePattern(@"^\[*[0-9;]*?m{1}")] - [Parameter(ParameterSetName = IndividualSetting)] - public string CodeBlockBackgroundColor { get; set;} + public string Code { get; set;} /// + /// Gets or sets the VT100 escape sequence for image alt text foreground. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] public string ImageAltTextForegroundColor { get; set;} /// + /// Gets or sets the VT100 escape sequence for link foreground. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] public string LinkForegroundColor { get; set;} /// + /// Gets or sets the VT100 escape sequence for italics text foreground. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] public string ItalicsForegroundColor { get; set;} /// + /// Gets or sets the VT100 escape sequence for bold text foreground. /// [ValidatePattern(@"^\[*[0-9;]*?m{1}")] [Parameter(ParameterSetName = IndividualSetting)] public string BoldForegroundColor { get; set;} /// + /// Gets or sets the switch to PassThru the values set. /// [Parameter()] public SwitchParameter PassThru { get; set;} /// + /// Gets or sets the Theme. /// [ValidateNotNullOrEmpty] [Parameter(ParameterSetName = ThemeParamSet, Mandatory = true)] + [ValidateSet(DarkThemeName, LightThemeName)] public string Theme { get; set;} /// + /// Gets or sets InputObject. /// [ValidateNotNullOrEmpty] [Parameter(ParameterSetName = InputObjectParamSet, Mandatory = true, ValueFromPipeline = true)] public PSObject InputObject { get; set;} private const string IndividualSetting = "IndividualSetting"; - private const string InputObjectParamSet = "InputObject"; - private const string ThemeParamSet = "Theme"; + private const string MarkdownOptionInfoVariableName = "MarkdownOptionInfo"; + private const string LightThemeName = "Light"; + private const string DarkThemeName = "Dark"; /// + /// Override EndProcessing. /// protected override void EndProcessing() { @@ -126,11 +137,11 @@ namespace Microsoft.PowerShell.Commands { case ThemeParamSet: mdOptionInfo = new MarkdownOptionInfo(); - if(string.Equals(Theme, "Light", StringComparison.OrdinalIgnoreCase)) + if(string.Equals(Theme, LightThemeName, StringComparison.OrdinalIgnoreCase)) { mdOptionInfo.SetLightTheme(); } - else if(string.Equals(Theme, "Dark", StringComparison.OrdinalIgnoreCase)) + else if(string.Equals(Theme, DarkThemeName, StringComparison.OrdinalIgnoreCase)) { mdOptionInfo.SetDarkTheme(); } @@ -144,7 +155,6 @@ namespace Microsoft.PowerShell.Commands { throw new ArgumentException(); } - break; case IndividualSetting: @@ -154,7 +164,7 @@ namespace Microsoft.PowerShell.Commands } var sessionVar = SessionState.PSVariable; - sessionVar.Set("MarkdownOptionInfo", mdOptionInfo); + sessionVar.Set(MarkdownOptionInfoVariableName, mdOptionInfo); if(PassThru.IsPresent) { @@ -194,14 +204,9 @@ namespace Microsoft.PowerShell.Commands mdOptionInfo.Header6 = Header6Color; } - if (!String.IsNullOrEmpty(CodeBlockBackgroundColor)) + if (!String.IsNullOrEmpty(Code)) { - mdOptionInfo.Code = CodeBlockBackgroundColor; - } - - if (!String.IsNullOrEmpty(CodeBlockForegroundColor)) - { - mdOptionInfo.Code = CodeBlockForegroundColor; + mdOptionInfo.Code = Code; } if (!String.IsNullOrEmpty(ImageAltTextForegroundColor)) @@ -227,6 +232,7 @@ namespace Microsoft.PowerShell.Commands } /// + /// Implements the cmdlet for getting the markdown options that are set. /// [Cmdlet( VerbsCommon.Get, "MarkdownOption", @@ -235,11 +241,14 @@ namespace Microsoft.PowerShell.Commands [OutputType(typeof(Microsoft.PowerShell.MarkdownRender.MarkdownOptionInfo))] public class GetMarkdownOptionCommand : PSCmdlet { + private const string MarkdownOptionInfoVariableName = "MarkdownOptionInfo"; + /// + /// Override endproessing. /// protected override void EndProcessing() { - WriteObject(SessionState.PSVariable.GetValue("MarkdownOptionInfo", new MarkdownOptionInfo())); + WriteObject(SessionState.PSVariable.GetValue(MarkdownOptionInfoVariableName, new MarkdownOptionInfo())); } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs index 8e4f639844..bd37f41fb1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowMarkdownCommand.cs @@ -58,7 +58,8 @@ namespace Microsoft.PowerShell.Commands { Object inpObj = InputObject.BaseObject; var markdownInfo = inpObj as MarkdownInfo; - if (markdownInfo == null) + + if (markdownInfo != null) { var errorRecord = new ErrorRecord( new ArgumentException(), diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertMarkdownStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertMarkdownStrings.resx index 5eff4c73f5..f9aacc5d6f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertMarkdownStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/ConvertMarkdownStrings.resx @@ -124,6 +124,6 @@ The given file path '{0}' is not found. - The given path '{0}' is not supported. This command only supports the FileSystem Provider paths. + Only FileSystem Provider paths are supported. The given path '{0}' is not supported. diff --git a/src/Microsoft.PowerShell.MarkdownRender/ListBlockRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/ListBlockRenderer.cs index 3747bebe1a..5ee5dd5da8 100644 --- a/src/Microsoft.PowerShell.MarkdownRender/ListBlockRenderer.cs +++ b/src/Microsoft.PowerShell.MarkdownRender/ListBlockRenderer.cs @@ -21,9 +21,7 @@ namespace Microsoft.PowerShell.MarkdownRender foreach (var item in obj) { - var listItem = item as ListItemBlock; - - if (listItem != null) + if (item is ListItemBlock listItem) { if (obj.IsOrdered) { @@ -44,9 +42,7 @@ namespace Microsoft.PowerShell.MarkdownRender // For a numbered list, we need to make sure the index is incremented. foreach (var line in block) { - var paragraphBlock = line as ParagraphBlock; - - if(paragraphBlock != null) + if(line is ParagraphBlock paragraphBlock) { renderer.Write(index.ToString()).Write(". ").Write(paragraphBlock.Inline); } diff --git a/src/Microsoft.PowerShell.MarkdownRender/ListItemBlockRenderer.cs b/src/Microsoft.PowerShell.MarkdownRender/ListItemBlockRenderer.cs index 276d773757..d96b744d65 100644 --- a/src/Microsoft.PowerShell.MarkdownRender/ListItemBlockRenderer.cs +++ b/src/Microsoft.PowerShell.MarkdownRender/ListItemBlockRenderer.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Threading; using Markdig; using Markdig.Syntax; using Markdig.Renderers; @@ -33,7 +34,7 @@ namespace Microsoft.PowerShell.MarkdownRender private void RenderWithIndent(VT100Renderer renderer, MarkdownObject block, char listBullet, int indentLevel) { // Indent left by 2 for each level on list. - string indent = "".PadLeft(indentLevel * 2); + string indent = Padding(indentLevel * 2); var paragraphBlock = block as ParagraphBlock; @@ -62,5 +63,24 @@ namespace Microsoft.PowerShell.MarkdownRender } } } + + // Typical padding is at most a screen's width, any more than that and we won't bother caching. + private const int IndentCacheMax = 120; + private static readonly string[] IndentCache = new string[IndentCacheMax]; + internal static string Padding(int countOfSpaces) + { + if (countOfSpaces >= IndentCacheMax) + return new string(' ', countOfSpaces); + + var result = IndentCache[countOfSpaces]; + + if (result == null) + { + Interlocked.CompareExchange(ref IndentCache[countOfSpaces], new string(' ', countOfSpaces), comparand:null); + result = IndentCache[countOfSpaces]; + } + + return result; + } } } diff --git a/src/Microsoft.PowerShell.MarkdownRender/Microsoft.PowerShell.MarkdownRender.csproj b/src/Microsoft.PowerShell.MarkdownRender/Microsoft.PowerShell.MarkdownRender.csproj index bac0826757..bae34827c7 100644 --- a/src/Microsoft.PowerShell.MarkdownRender/Microsoft.PowerShell.MarkdownRender.csproj +++ b/src/Microsoft.PowerShell.MarkdownRender/Microsoft.PowerShell.MarkdownRender.csproj @@ -6,7 +6,7 @@ Microsoft.PowerShell.MarkdownRender - + diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 index 3424b1949d..6877136b09 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/MarkdownCmdlets.Tests.ps1 @@ -150,7 +150,7 @@ bool function()`n{`n} $expectedString = GetExpectedString -ElementType $element -CodeText $codeText } - $output.VT100EncodedString | Should BeExactly $expectedString + $output.VT100EncodedString | Should -BeExactly $expectedString } It 'Can convert element : to HTML using pipeline input' -TestCases $TestCases { @@ -181,22 +181,22 @@ bool function()`n{`n} $expectedString = GetExpectedHTML -ElementType $element -Text $text -Url $url } - $output.Html | Should BeExactly $expectedString + $output.Html | Should -BeExactly $expectedString } It 'Can convert input from a file path to vt100 encoded string' { $output = ConvertFrom-Markdown -Path $mdFile.FullName -AsVT100EncodedString - $output.VT100EncodedString | Should BeExactly $expectedStringFromFile + $output.VT100EncodedString | Should -BeExactly $expectedStringFromFile } It 'Can convert input from a fileinfo object to vt100 encoded string' { $ouputFromFileInfo = $mdFile | ConvertFrom-Markdown -AsVT100EncodedString - $ouputFromFileInfo.VT100EncodedString | Should BeExactly $expectedStringFromFile + $ouputFromFileInfo.VT100EncodedString | Should -BeExactly $expectedStringFromFile } It 'Can convert input from a literal path to vt100 encoded string' { $output = ConvertFrom-Markdown -Path $mdLiteralPath -AsVT100EncodedString - $output.VT100EncodedString | Should BeExactly $expectedStringFromFile + $output.VT100EncodedString | Should -BeExactly $expectedStringFromFile } } @@ -213,4 +213,80 @@ bool function()`n{`n} { ConvertFrom-Markdown -InputObject 1 -ErrorAction Stop } | Should -Throw -ErrorId 'InvalidInputObject,Microsoft.PowerShell.Commands.ConvertFromMarkdownCommand' } } + + Context "Get/Set-MarkdownOption tests" { + + BeforeAll { + $esc = [char]0x1b + } + + BeforeEach { + $originalOptions = Get-MarkdownOption + } + + AfterEach { + Set-MarkdownOption -InputObject $originalOptions + } + + It "Verify default values for MarkdownOptions" { + $options = Get-MarkdownOption + + $options.Header1 | Should -BeExactly "$esc[7m[7m$esc[0m" + $options.Header2 | Should -BeExactly "$esc[4;93m[4;93m$esc[0m" + $options.Header3 | Should -BeExactly "$esc[4;94m[4;94m$esc[0m" + $options.Header4 | Should -BeExactly "$esc[4;95m[4;95m$esc[0m" + $options.Header5 | Should -BeExactly "$esc[4;96m[4;96m$esc[0m" + $options.Header6 | Should -BeExactly "$esc[4;97m[4;97m$esc[0m" + $options.Code | Should -BeExactly "$esc[48;2;155;155;155;38;2;30;30;30m[48;2;155;155;155;38;2;30;30;30m$esc[0m" + $options.Link | Should -BeExactly "$esc[4;38;5;117m[4;38;5;117m$esc[0m" + $options.Image | Should -BeExactly "$esc[33m[33m$esc[0m" + $options.EmphasisBold | Should -BeExactly "$esc[1m[1m$esc[0m" + $options.EmphasisItalics | Should -BeExactly "$esc[36m[36m$esc[0m" + } + + It "Verify Set-MarkdownOption can get options" { + Set-MarkdownOption ` + -Header1Color "[4;1m" ` + -Header2Color "[93m" ` + -Header3Color "[94m" ` + -Header4Color "[95m" ` + -Header5Color "[96m" ` + -Header6Color "[97m" ` + -ImageAltTextForegroundColor "[34m" ` + -LinkForegroundColor "[4;38;5;88m" ` + -ItalicsForegroundColor "[35m" ` + -BoldForegroundColor "[32m" + + $newOptions = Get-MarkdownOption + + $options.Header1 | Should -BeExactly "$esc[4;1m[4;1m$esc[0m" + $options.Header2 | Should -BeExactly "$esc[93m[93m$esc[0m" + $options.Header3 | Should -BeExactly "$esc[94m[94m$esc[0m" + $options.Header4 | Should -BeExactly "$esc[95m[95m$esc[0m" + $options.Header5 | Should -BeExactly "$esc[96m[96m$esc[0m" + $options.Header6 | Should -BeExactly "$esc[97m[97m$esc[0m" + #$options.Code | Should -BeExactly "$esc[48;2;155;155;155;38;2;30;30;30m[48;2;155;155;155;38;2;30;30;30m$esc[0m" + $options.Link | Should -BeExactly "$esc[4;38;5;88m[4;38;5;88m$esc[0m" + $options.Image | Should -BeExactly "$esc[34m[34m$esc[0m" + $options.EmphasisBold | Should -BeExactly "$esc[32m[32m$esc[0m" + $options.EmphasisItalics | Should -BeExactly "$esc[35m[35m$esc[0m" + } + + It "Verify defaults for light theme" { + $options = Get-MarkdownOption + + $options.Header1 | Should -BeExactly "$esc[7m[7m$esc[0m" + $options.Header2 | Should -BeExactly "$esc[4;33m[4;33m$esc[0m" + $options.Header3 | Should -BeExactly "$esc[4;34m[4;34m$esc[0m" + $options.Header4 | Should -BeExactly "$esc[4;35m[4;35m$esc[0m" + $options.Header5 | Should -BeExactly "$esc[4;36m[4;36m$esc[0m" + $options.Header6 | Should -BeExactly "$esc[4;30m[4;30m$esc[0m" + $options.Code | Should -BeExactly "$esc[48;2;155;155;155;38;2;30;30;30m[48;2;155;155;155;38;2;30;30;30m$esc[0m" + $options.Link | Should -BeExactly "$esc[4;38;5;117m[4;38;5;117m$esc[0m" + $options.Image | Should -BeExactly "$esc[33m[33m$esc[0m" + $options.EmphasisBold | Should -BeExactly "$esc[1m[1m$esc[0m" + $options.EmphasisItalics | Should -BeExactly "$esc[36m[36m$esc[0m" + } + } + }