-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Added Markdown rendering cmdlets #6926
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added Markdown rendering cmdlets #6926
Conversation
|
@SteveL-MSFT We need an (formal) approvement from PowerShell Committee to use third party application - markdig. |
iSazonov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leave a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LitPathParamSet looks bad. I'd prefer LiteralPathParameterSet and PathParameterSet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems we should use Markdown in all comments (starting with capital letter).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please address this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please set final dot in all public comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this in BeginProcessing method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For long paths it is better to place '{0}' at the very end.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use C# syntax. Below too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use a cache for the space string.
https://github.com/PowerShell/PowerShell/blob/master/src/System.Management.Automation/utils/StringUtil.cs#L69
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The property (and below) is already defined in PowerShell.Common.props.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Markdown document can be large and such methods will allocate many temporary strings. Can we implement the rendering as methods which write to target buffer/string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Markdown document is loaded by markdig in an efficient way see this post
The formatting done here is typically on small strings like headers, links etc. We can revisit this if we see a performance bottleneck here.
|
@iSazonov We are ok to use markdig |
|
@SteveL-MSFT Please set the Committee label then. |
|
@PowerShell/powershell-committee reviewed this and approves of using MarkDig |
|
This PR has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed if no further activity occurs within 10 days. |
|
I will be resuming work on this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we use PSObject.Base()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need useful public comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same - PSObject.Base()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems we already fixed this and can remove the workaround.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will test this and update if workaround is not needed anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can obj be null?
Below I see some methods with such pattern.
Suggestion:
if (obj == null)
{
return;
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No obj cannot be null. Renderer will not be called in that case.
7492db9 to
aa80db2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BeginProcess -> BeginProcessing()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the unneeded comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
endproessing -> EndProcessing()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why we duplicate the code from StringUtil.cs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StringUtil class is internal.
|
@iSazonov Can you have another look? |
JamesWTruher
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's only 1 where i think there's a real issue (your UseBrowser handling) - the rest are just comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you describe what you're doing here, rather than the generic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you might include a check fileInfo.Exists - I'm not sure what might happen if you call the rest of the code if the file doesn't exist. If there's a simple error and nothing dire happens, then ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In ReadContentFromFile() we have Assert - I believe we should replace it with writing non-terminating error if file access failed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a non-terminating error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little surprised that this regex catches an actual color escape sequence <ESC>[32;1m (it didn't for me since it doesn't include ESCAPE. Since these are all colors, is there a reason why you don't actually just allow the user to provide 'System.ConsoleColor'?
Mind you, this approach does allow for supporting those terminals which have support for RGB colors, which is definitely cool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason we did not use System.ConsoleColor is to be in parity with PSReadline. I do not expect the user the provide ESCAPE.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this. I think it would miss Show-Markdown -UseBrowser:$false
what about (bool)UseBrowser
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems we could use UseBrowser.IsPresent .
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does something reasonable happen on non-Windows systems?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems we have startInfo.UseShellExecute = true working on Unix systems now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested it, UseShellExecute works on non-Windows now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since you're reusing a bunch of these, I'd be happier if they were constants
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice, i wonder if you should check for empty string and include this as "image"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the scenario I mentioned above was this scenario:
[io.fileinfo]::new("lol-I-dont-exist") | ConvertFrom-Markdown
it's a pretty corner-case scenario, I'll admit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah - now I understand - you don't actually expect the user to give you the actual escape sequence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could use MarkdownOptionInfoVariableName const.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are in different classes. Too much overhead to define a single string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see we define the const twice. Could we use one definition for all uses?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are in different classes. Too much overhead to define a single string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In ReadContentFromFile() we have Assert - I believe we should replace it with writing non-terminating error if file access failed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above we should replace the assert with writing non-terminating error if file access failed in StreamReader() or FileStream() (FileNotFound, Access Denied ...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should baseObj.GetType() be explicitly baseObj.GetType().ToString()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have this is most csproj files. So kept it consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we exclude the reflection?
Maybe use HashSet? Or switch()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, added switch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see such definition above. Can we define this in one place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are in different classes. Too much overhead to have separate class for defining a character.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we define the color with other colors in one place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
made a constant in this class. It is not a color but width and is not settable by user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove $FullCLR. Below too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
b3dac3c to
b5601cf
Compare
|
Filed issue #7283 to track @daxian-dbw non-blocking feedback. |
|
@daxian-dbw All tests passed. |
| using (StreamReader reader = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))) | ||
| try | ||
| { | ||
| using (StreamReader reader = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe we could use one try block instead of try-using.
| return mdContent; | ||
| } | ||
| } | ||
| catch(FileNotFoundException fnfe) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe use one catch for all exceptions with custom (more useful) message and place original exception in InnerException?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are catching individual exceptions, I think the default messages are correct, without the need of custom messages. Examples below.
ConvertFrom-Markdown : Access to the path 'C:\Windows\System32\Configuration' is denied.
At line:1 char:1
+ ConvertFrom-Markdown -Path C:\Windows\System32\Configuration
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : SecurityError: (C:\Windows\System32\Configuration:String) [ConvertFrom-Markdown], UnauthorizedAccessException
+ FullyQualifiedErrorId : FileUnauthorizedAccess,Microsoft.PowerShell.Commands.ConvertFromMarkdownCommand
ConvertFrom-Markdown : Cannot find path 'D:\temp\sdfsdfs.md' because it does not exist.
At line:1 char:1
+ ConvertFrom-Markdown -Path D:\temp\\sdfsdfs.md
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (D:\temp\\sdfsdfs.md:String) [ConvertFrom-Markdown], ItemNotFoundException
+ FullyQualifiedErrorId : FileNotFound,Microsoft.PowerShell.Commands.ConvertFromMarkdownCommand
| try | ||
| { | ||
| writer.Write(html); | ||
| using (var writer = new StreamWriter(new FileStream(tmpFilePath, FileMode.Create, FileAccess.Write, FileShare.Write))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same - we could use one try block instead of try-using.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer using since there are multiple catch blocks and disposing in finally will be far too disjoint from where StreamWriter / FileStream are used.
| e, | ||
| "ErrorLaunchingDefaultApplication", | ||
| ErrorCategory.InvalidOperation, | ||
| targetObject : null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sense send startInfo argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
startInfo is internally generated. I think seeing it in the error will be more confusing than useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still wonder that we don't use latest version for the initial PR.
| public string AsEscapeSequence(string propertyName) | ||
| { | ||
| var propertyValue = this.GetType().GetProperty(propertyName)?.GetValue(this) as string; | ||
| var propName = propertyName?.ToLower(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this. Given that this method is public can we use enum type parameter like
string propertyName -> EscapeSequenceEnum propertyName ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed it to an enum
|
It looks @iSazonov's comments are new, although they are shown as |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please address this?
| { | ||
| var subListItemBlock = subListItem as ListItemBlock; | ||
|
|
||
| if (subListItemBlock != null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use C# 7.0 syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed.
|
Only Question is that if you are adding a ConvertFrom-Markdown why aren't you adding a ConvertTo-Markdown cmdlet as well for consistency ? |
|
@kilasuit The goal here is to open the way for native supporting help files in Mardown format. So currently we only need "convert from Markdown" and rendering. Later we could add other cmdlets. |
…ig version to latest
557b664 to
a1627a3
Compare
|
@iSazonov I believe all your comments have been addressed. I will merge this PR soon. |
| # 8359D422-E0C4-4A0D-94EB-3C9DD16B7932 - PowerShell-Win | ||
| # Linux is invalid and mapped to Release | ||
| # | ||
| # 73EA0BE6-C0C5-4B56-A5AA-DADA4C01D690 - powershell-unix |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TravisEz13 This PR has removed all your comments about the configurations in the sln and changed those configurations as well (this always happens locally by default when one saves the solution).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bergmeister Thanks for catching this. I thought this was part of the markdown changes, and didn't realize it's unintentional. @adityapatwardhan will submit a PR to revert this shortly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And, we apologize for the inconvenience.
|
@daxian-dbw This PR seems to have not been squash-merged. This makes it very hard to track changes in master now as this or some other PR seems to have invalided some of the PowerShell.sln VS solution configuration causing my PR below to not work any more... |
|
@bergmeister Yes, this PR was merged with a commit node on purpose. For big features, we prefer to merge using a merge commit as long as the history is reasonably clean. However, @adityapatwardhan merged #7284 with a merge commit by accident (that one should be squashed), and now the history on master looks a little confusing: |

PR Summary
ConvertFrom-Markdown is used for converting a markdown document or string to a MarkdownInfo object. It can optionally return a HTML or VT100 encoded string in addition to a AST of the markdown document.
Show-Markdown is used to either display the VT100 encoded string on console or redirect the HTML string to the browser.
Set/Get-MarkdownOption cmdlets get be used to view or set markdown rendering options.## PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:to the beginning of the title and remove the prefix when the PR is ready.[feature]if the change is significant or affects feature tests