Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
HelpCommentsParser.cs1227 linesDownload Raw Back to help
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Globalization;8using System.IO;9using System.Linq;10using System.Management.Automation.Help;11using System.Management.Automation.Language;12using System.Management.Automation.Runspaces;13using System.Reflection;14using System.Text;15using System.Text.RegularExpressions;16using System.Xml;17 18namespace System.Management.Automation19{20    /// <summary>21    /// Parses help comments and turns them into HelpInfo objects.22    /// </summary>23    internal sealed class HelpCommentsParser24    {25        private HelpCommentsParser()26        {27        }28 29        private HelpCommentsParser(List<string> parameterDescriptions)30        {31            _parameterDescriptions = parameterDescriptions;32        }33 34        private HelpCommentsParser(CommandInfo commandInfo, List<string> parameterDescriptions)35        {36            FunctionInfo fi = commandInfo as FunctionInfo;37            if (fi != null)38            {39                _scriptBlock = fi.ScriptBlock;40                _commandName = fi.Name;41            }42            else43            {44                ExternalScriptInfo si = commandInfo as ExternalScriptInfo;45                if (si != null)46                {47                    _scriptBlock = si.ScriptBlock;48                    _commandName = si.Path;49                }50            }51 52            _commandMetadata = commandInfo.CommandMetadata;53            _parameterDescriptions = parameterDescriptions;54        }55 56        private readonly Language.CommentHelpInfo _sections = new Language.CommentHelpInfo();57        private readonly Dictionary<string, string> _parameters = new Dictionary<string, string>();58        private readonly List<string> _examples = new List<string>();59        private readonly List<string> _inputs = new List<string>();60        private readonly List<string> _outputs = new List<string>();61        private readonly List<string> _links = new List<string>();62        internal bool isExternalHelpSet = false;63 64        private readonly ScriptBlock _scriptBlock;65        private readonly CommandMetadata _commandMetadata;66        private readonly string _commandName;67        private readonly List<string> _parameterDescriptions;68        private XmlDocument _doc;69        internal static readonly string mshURI = "http://msh";70        internal static readonly string mamlURI = "http://schemas.microsoft.com/maml/2004/10";71        internal static readonly string commandURI = "http://schemas.microsoft.com/maml/dev/command/2004/10";72        internal static readonly string devURI = "http://schemas.microsoft.com/maml/dev/2004/10";73 74        private const string directive = @"^\s*\.(\w+)(\s+(\S.*))?\s*$";75        private const string blankline = @"^\s*$";76        // Although "http://msh" is the default namespace, it still must be explicitly qualified with non-empty prefix,77        // because XPath 1.0 will associate empty prefix with "null" namespace (not with "default") and query will fail.78        // See: http://www.w3.org/TR/1999/REC-xpath-19991116/#node-tests79        internal static readonly string ProviderHelpCommandXPath =80            "/msh:helpItems/msh:providerHelp/msh:CmdletHelpPaths/msh:CmdletHelpPath{0}/command:command[command:details/command:verb='{1}' and command:details/command:noun='{2}']";81 82        private void DetermineParameterDescriptions()83        {84            int i = 0;85            foreach (string parameterName in _commandMetadata.StaticCommandParameterMetadata.BindableParameters.Keys)86            {87                string description;88                if (!_parameters.TryGetValue(parameterName.ToUpperInvariant(), out description))89                {90                    if (i < _parameterDescriptions.Count)91                    {92                        _parameters.Add(parameterName.ToUpperInvariant(), _parameterDescriptions[i]);93                    }94                }95 96                ++i;97            }98        }99 100        private string GetParameterDescription(string parameterName)101        {102            Diagnostics.Assert(!string.IsNullOrEmpty(parameterName), "Parameter name must not be empty");103 104            string description;105            _parameters.TryGetValue(parameterName.ToUpperInvariant(), out description);106            return description;107        }108 109        private XmlElement BuildXmlForParameter(110            string parameterName,111            bool isMandatory,112            bool valueFromPipeline,113            bool valueFromPipelineByPropertyName,114            string position,115            Type type,116            string description,117            bool supportsWildcards,118            string defaultValue,119            bool forSyntax)120        {121            XmlElement command_parameter = _doc.CreateElement("command:parameter", commandURI);122            command_parameter.SetAttribute("required", isMandatory ? "true" : "false");123            // command_parameter.SetAttribute("variableLength", "unknown");124            command_parameter.SetAttribute("globbing", supportsWildcards ? "true" : "false");125            string fromPipeline;126            if (valueFromPipeline && valueFromPipelineByPropertyName)127            {128                fromPipeline = "true (ByValue, ByPropertyName)";129            }130            else if (valueFromPipeline)131            {132                fromPipeline = "true (ByValue)";133            }134            else if (valueFromPipelineByPropertyName)135            {136                fromPipeline = "true (ByPropertyName)";137            }138            else139            {140                fromPipeline = "false";141            }142 143            command_parameter.SetAttribute("pipelineInput", fromPipeline);144            command_parameter.SetAttribute("position", position);145 146            XmlElement name = _doc.CreateElement("maml:name", mamlURI);147            XmlText name_text = _doc.CreateTextNode(parameterName);148            command_parameter.AppendChild(name).AppendChild(name_text);149            if (!string.IsNullOrEmpty(description))150            {151                XmlElement maml_description = _doc.CreateElement("maml:description", mamlURI);152                XmlElement maml_para = _doc.CreateElement("maml:para", mamlURI);153                XmlText maml_para_text = _doc.CreateTextNode(description);154                command_parameter.AppendChild(maml_description).AppendChild(maml_para).AppendChild(maml_para_text);155            }156 157            if (type == null)158                type = typeof(object);159 160            var elementType = type.IsArray ? type.GetElementType() : type;161 162            if (elementType.IsEnum)163            {164                XmlElement parameterValueGroup = _doc.CreateElement("command:parameterValueGroup", commandURI);165                foreach (string valueName in Enum.GetNames(elementType))166                {167                    XmlElement parameterValue = _doc.CreateElement("command:parameterValue", commandURI);168                    parameterValue.SetAttribute("required", "false");169                    XmlText parameterValue_text = _doc.CreateTextNode(valueName);170                    parameterValueGroup.AppendChild(parameterValue).AppendChild(parameterValue_text);171                }172 173                command_parameter.AppendChild(parameterValueGroup);174            }175            else176            {177                bool isSwitchParameter = elementType == typeof(SwitchParameter);178                if (!forSyntax || !isSwitchParameter)179                {180                    XmlElement parameterValue = _doc.CreateElement("command:parameterValue", commandURI);181                    parameterValue.SetAttribute("required", isSwitchParameter ? "false" : "true");182                    // parameterValue.SetAttribute("variableLength", "unknown");183                    XmlText parameterValue_text = _doc.CreateTextNode(type.Name);184                    command_parameter.AppendChild(parameterValue).AppendChild(parameterValue_text);185                }186            }187 188            if (!forSyntax)189            {190                XmlElement devType = _doc.CreateElement("dev:type", devURI);191                XmlElement typeName = _doc.CreateElement("maml:name", mamlURI);192                XmlText typeName_text = _doc.CreateTextNode(type.Name);193                command_parameter.AppendChild(devType).AppendChild(typeName).AppendChild(typeName_text);194 195                XmlElement defaultValueElement = _doc.CreateElement("dev:defaultValue", devURI);196                XmlText defaultValue_text = _doc.CreateTextNode(defaultValue);197                command_parameter.AppendChild(defaultValueElement).AppendChild(defaultValue_text);198            }199 200            return command_parameter;201        }202 203        /// <summary>204        /// Create the maml xml after a successful analysis of the comments.205        /// </summary>206        /// <returns>The xml node for the command constructed.</returns>207        internal XmlDocument BuildXmlFromComments()208        {209            Diagnostics.Assert(!string.IsNullOrEmpty(_commandName), "Name can never be null");210 211            _doc = new XmlDocument();212            XmlElement command = _doc.CreateElement("command:command", commandURI);213            command.SetAttribute("xmlns:maml", mamlURI);214            command.SetAttribute("xmlns:command", commandURI);215            command.SetAttribute("xmlns:dev", devURI);216            _doc.AppendChild(command);217 218            XmlElement details = _doc.CreateElement("command:details", commandURI);219            command.AppendChild(details);220 221            XmlElement name = _doc.CreateElement("command:name", commandURI);222            XmlText name_text = _doc.CreateTextNode(_commandName);223            details.AppendChild(name).AppendChild(name_text);224 225            if (!string.IsNullOrEmpty(_sections.Synopsis))226            {227                XmlElement synopsis = _doc.CreateElement("maml:description", mamlURI);228                XmlElement synopsis_para = _doc.CreateElement("maml:para", mamlURI);229                XmlText synopsis_text = _doc.CreateTextNode(_sections.Synopsis);230                details.AppendChild(synopsis).AppendChild(synopsis_para).AppendChild(synopsis_text);231            }232 233            #region Syntax234 235            // The syntax is automatically generated from parameter metadata236            DetermineParameterDescriptions();237 238            XmlElement syntax = _doc.CreateElement("command:syntax", commandURI);239            MergedCommandParameterMetadata parameterMetadata = _commandMetadata.StaticCommandParameterMetadata;240            if (parameterMetadata.ParameterSetCount > 0)241            {242                for (int i = 0; i < parameterMetadata.ParameterSetCount; ++i)243                {244                    BuildSyntaxForParameterSet(command, syntax, parameterMetadata, i);245                }246            }247            else248            {249                BuildSyntaxForParameterSet(command, syntax, parameterMetadata, int.MaxValue);250            }251 252            #endregion Syntax253 254            #region Parameters255 256            XmlElement commandParameters = _doc.CreateElement("command:parameters", commandURI);257            foreach (KeyValuePair<string, MergedCompiledCommandParameter> pair in parameterMetadata.BindableParameters)258            {259                MergedCompiledCommandParameter mergedParameter = pair.Value;260                if (mergedParameter.BinderAssociation == ParameterBinderAssociation.CommonParameters)261                {262                    continue;263                }264 265                string parameterName = pair.Key;266                string description = GetParameterDescription(parameterName);267 268                ParameterSetSpecificMetadata parameterSetData;269                bool isMandatory = false;270                bool valueFromPipeline = false;271                bool valueFromPipelineByPropertyName = false;272                string position = "named";273                int i = 0;274 275                CompiledCommandParameter parameter = mergedParameter.Parameter;276                parameter.ParameterSetData.TryGetValue(ParameterAttribute.AllParameterSets, out parameterSetData);277                while (parameterSetData == null && i < 32)278                {279                    parameterSetData = parameter.GetParameterSetData(1u << i++);280                }281 282                if (parameterSetData != null)283                {284                    isMandatory = parameterSetData.IsMandatory;285                    valueFromPipeline = parameterSetData.ValueFromPipeline;286                    valueFromPipelineByPropertyName = parameterSetData.ValueFromPipelineByPropertyName;287                    position = parameterSetData.IsPositional ? (1 + parameterSetData.Position).ToString(CultureInfo.InvariantCulture) : "named";288                }289 290                var compiledAttributes = parameter.CompiledAttributes;291                bool supportsWildcards = compiledAttributes.OfType<SupportsWildcardsAttribute>().Any();292 293                string defaultValueStr = string.Empty;294                object defaultValue = null;295                var defaultValueAttribute = compiledAttributes.OfType<PSDefaultValueAttribute>().FirstOrDefault();296                if (defaultValueAttribute != null)297                {298                    defaultValueStr = defaultValueAttribute.Help;299                    if (string.IsNullOrEmpty(defaultValueStr))300                    {301                        defaultValue = defaultValueAttribute.Value;302                    }303                }304 305                if (string.IsNullOrEmpty(defaultValueStr))306                {307                    if (defaultValue == null)308                    {309                        RuntimeDefinedParameter rdp;310                        if (_scriptBlock.RuntimeDefinedParameters.TryGetValue(parameterName, out rdp))311                        {312                            defaultValue = rdp.Value;313                        }314                    }315 316                    var wrapper = defaultValue as Compiler.DefaultValueExpressionWrapper;317                    if (wrapper != null)318                    {319                        defaultValueStr = wrapper.Expression.Extent.Text;320                    }321                    else if (defaultValue != null)322                    {323                        defaultValueStr = PSObject.ToStringParser(null, defaultValue);324                    }325                }326 327                XmlElement parameterElement = BuildXmlForParameter(parameterName, isMandatory,328                    valueFromPipeline, valueFromPipelineByPropertyName, position,329                    parameter.Type, description, supportsWildcards, defaultValueStr, forSyntax: false);330                commandParameters.AppendChild(parameterElement);331            }332 333            command.AppendChild(commandParameters);334 335            #endregion Parameters336 337            if (!string.IsNullOrEmpty(_sections.Description))338            {339                XmlElement description = _doc.CreateElement("maml:description", mamlURI);340                XmlElement description_para = _doc.CreateElement("maml:para", mamlURI);341                XmlText description_text = _doc.CreateTextNode(_sections.Description);342                command.AppendChild(description).AppendChild(description_para).AppendChild(description_text);343            }344 345            if (!string.IsNullOrEmpty(_sections.Notes))346            {347                XmlElement alertSet = _doc.CreateElement("maml:alertSet", mamlURI);348                XmlElement alert = _doc.CreateElement("maml:alert", mamlURI);349                XmlElement alert_para = _doc.CreateElement("maml:para", mamlURI);350                XmlText alert_para_text = _doc.CreateTextNode(_sections.Notes);351                command.AppendChild(alertSet).AppendChild(alert).AppendChild(alert_para).AppendChild(alert_para_text);352            }353 354            if (_examples.Count > 0)355            {356                XmlElement examples = _doc.CreateElement("command:examples", commandURI);357                int count = 1;358                foreach (string example in _examples)359                {360                    XmlElement example_node = _doc.CreateElement("command:example", commandURI);361 362                    // The title is automatically generated363                    XmlElement title = _doc.CreateElement("maml:title", mamlURI);364                    string titleStr = string.Format(CultureInfo.InvariantCulture,365                        "\t\t\t\t-------------------------- {0} {1} --------------------------",366                        HelpDisplayStrings.ExampleUpperCase, count++);367                    XmlText title_text = _doc.CreateTextNode(titleStr);368                    example_node.AppendChild(title).AppendChild(title_text);369 370                    string prompt_str;371                    string code_str;372                    string remarks_str;373                    GetExampleSections(example, out prompt_str, out code_str, out remarks_str);374 375                    // Introduction (usually the prompt)376                    XmlElement introduction = _doc.CreateElement("maml:introduction", mamlURI);377                    XmlElement introduction_para = _doc.CreateElement("maml:para", mamlURI);378                    XmlText introduction_para_text = _doc.CreateTextNode(prompt_str);379                    example_node.AppendChild(introduction).AppendChild(introduction_para).AppendChild(introduction_para_text);380 381                    // Example code382                    XmlElement code = _doc.CreateElement("dev:code", devURI);383                    XmlText code_text = _doc.CreateTextNode(code_str);384                    example_node.AppendChild(code).AppendChild(code_text);385 386                    // Remarks are comments on the example387                    XmlElement remarks = _doc.CreateElement("dev:remarks", devURI);388                    XmlElement remarks_para = _doc.CreateElement("maml:para", mamlURI);389                    XmlText remarks_para_text = _doc.CreateTextNode(remarks_str);390                    example_node.AppendChild(remarks).AppendChild(remarks_para).AppendChild(remarks_para_text);391                    // The convention is to have 4 blank paras after the example for spacing392                    for (int i = 0; i < 4; i++)393                    {394                        remarks.AppendChild(_doc.CreateElement("maml:para", mamlURI));395                    }396 397                    examples.AppendChild(example_node);398                }399 400                command.AppendChild(examples);401            }402 403            if (_inputs.Count > 0)404            {405                XmlElement inputTypes = _doc.CreateElement("command:inputTypes", commandURI);406                foreach (string inputStr in _inputs)407                {408                    XmlElement inputType = _doc.CreateElement("command:inputType", commandURI);409                    XmlElement type = _doc.CreateElement("dev:type", devURI);410                    XmlElement maml_name = _doc.CreateElement("maml:name", mamlURI);411                    XmlText maml_name_text = _doc.CreateTextNode(inputStr);412                    inputTypes.AppendChild(inputType).AppendChild(type).AppendChild(maml_name).AppendChild(maml_name_text);413                }414 415                command.AppendChild(inputTypes);416            }417            // For outputs, we prefer what was specified in the comments, but if there are no comments418            // and the OutputType attribute was specified, we'll use those instead.419            IEnumerable outputs = null;420            if (_outputs.Count > 0)421            {422                outputs = _outputs;423            }424            else if (_scriptBlock.OutputType.Count > 0)425            {426                outputs = _scriptBlock.OutputType;427            }428 429            if (outputs != null)430            {431                XmlElement returnValues = _doc.CreateElement("command:returnValues", commandURI);432                foreach (object output in outputs)433                {434                    XmlElement returnValue = _doc.CreateElement("command:returnValue", commandURI);435                    XmlElement type = _doc.CreateElement("dev:type", devURI);436                    XmlElement maml_name = _doc.CreateElement("maml:name", mamlURI);437                    string returnValueStr = output as string ?? ((PSTypeName)output).Name;438                    XmlText maml_name_text = _doc.CreateTextNode(returnValueStr);439                    returnValues.AppendChild(returnValue).AppendChild(type).AppendChild(maml_name).AppendChild(maml_name_text);440                }441 442                command.AppendChild(returnValues);443            }444 445            if (_links.Count > 0)446            {447                XmlElement links = _doc.CreateElement("maml:relatedLinks", mamlURI);448                foreach (string link in _links)449                {450                    XmlElement navigationLink = _doc.CreateElement("maml:navigationLink", mamlURI);451                    bool isOnlineHelp = Uri.IsWellFormedUriString(link, UriKind.Absolute);452                    string nodeName = isOnlineHelp ? "maml:uri" : "maml:linkText";453                    XmlElement linkText = _doc.CreateElement(nodeName, mamlURI);454                    XmlText linkText_text = _doc.CreateTextNode(link);455                    links.AppendChild(navigationLink).AppendChild(linkText).AppendChild(linkText_text);456                }457 458                command.AppendChild(links);459            }460 461            return _doc;462        }463 464        private void BuildSyntaxForParameterSet(XmlElement command, XmlElement syntax, MergedCommandParameterMetadata parameterMetadata, int i)465        {466            XmlElement syntaxItem = _doc.CreateElement("command:syntaxItem", commandURI);467            XmlElement syntaxItemName = _doc.CreateElement("maml:name", mamlURI);468            XmlText syntaxItemName_text = _doc.CreateTextNode(_commandName);469 470            syntaxItem.AppendChild(syntaxItemName).AppendChild(syntaxItemName_text);471 472            Collection<MergedCompiledCommandParameter> compiledParameters =473                parameterMetadata.GetParametersInParameterSet(1u << i);474 475            foreach (MergedCompiledCommandParameter mergedParameter in compiledParameters)476            {477                if (mergedParameter.BinderAssociation == ParameterBinderAssociation.CommonParameters)478                {479                    continue;480                }481 482                CompiledCommandParameter parameter = mergedParameter.Parameter;483                ParameterSetSpecificMetadata parameterSetData = parameter.GetParameterSetData(1u << i);484                string description = GetParameterDescription(parameter.Name);485                bool supportsWildcards = parameter.CompiledAttributes.Any(static attribute => attribute is SupportsWildcardsAttribute);486                XmlElement parameterElement = BuildXmlForParameter(parameter.Name,487                    parameterSetData.IsMandatory, parameterSetData.ValueFromPipeline,488                    parameterSetData.ValueFromPipelineByPropertyName,489                    parameterSetData.IsPositional ? (1 + parameterSetData.Position).ToString(CultureInfo.InvariantCulture) : "named",490                    parameter.Type, description, supportsWildcards, defaultValue: string.Empty, forSyntax: true);491                syntaxItem.AppendChild(parameterElement);492            }493 494            command.AppendChild(syntax).AppendChild(syntaxItem);495        }496 497        private static void GetExampleSections(string content, out string prompt_str, out string code_str, out string remarks_str)498        {499            const string default_prompt_str = "PS > ";500 501            var promptMatch = Regex.Match(content, "^.*?>");502            prompt_str = promptMatch.Success ? promptMatch.Value : default_prompt_str;503            if (promptMatch.Success)504            {505                content = content.Substring(prompt_str.Length);506            }507 508            var codeAndRemarksMatch = Regex.Match(content, "^(?<code>.*?)\r?\n\r?\n(?<remarks>.*)$", RegexOptions.Singleline);509            if (codeAndRemarksMatch.Success)510            {511                code_str = codeAndRemarksMatch.Groups["code"].Value.Trim();512                remarks_str = codeAndRemarksMatch.Groups["remarks"].Value;513            }514            else515            {516                code_str = content.Trim();517                remarks_str = string.Empty;518            }519        }520 521        /// <summary>522        /// Split the text in the comment token into multiple lines, appending commentLines.523        /// </summary>524        /// <param name="comment">A single line or multiline comment token.</param>525        /// <param name="commentLines"></param>526        private static void CollectCommentText(Token comment, List<string> commentLines)527        {528            string text = comment.Text;529            CollectCommentText(text, commentLines);530        }531 532        private static void CollectCommentText(string text, List<string> commentLines)533        {534            int i = 0;535            if (text[0] == '<')536            {537                int start = 2;538                // The full text includes '<#', so start at index 2 to skip those characters,539                // and the full text also includes '#>' at the end, so skip those as well.540                for (i = 2; i < text.Length - 2; i++)541                {542                    if (text[i] == '\n')543                    {544                        commentLines.Add(text.Substring(start, i - start));545                        start = i + 1;546                    }547                    else if (text[i] == '\r')548                    {549                        commentLines.Add(text.Substring(start, i - start));550 551                        // No need to check length here, comment text has at least '#>' at the end.552                        if (text[i + 1] == '\n')553                        {554                            i++;555                        }556 557                        start = i + 1;558                    }559                }560 561                commentLines.Add(text.Substring(start, i - start));562            }563            else564            {565                for (; i < text.Length; i++)566                {567                    // Skip all leading '#' characters as it is a common convention568                    // to use more than one '#' character.569                    if (text[i] != '#')570                    {571                        break;572                    }573                }574 575                commentLines.Add(text.Substring(i));576            }577        }578 579        /// <summary>580        /// Collect the text of a section.  Stop collecting the section581        /// when a new directive is found (even if it is an unknown directive).582        /// </summary>583        /// <param name="commentLines">The comment block, as a list of lines.</param>584        /// <param name="i"></param>585        /// <returns>The text of the help section, with 'i' left on the last line collected.</returns>586        private static string GetSection(List<string> commentLines, ref int i)587        {588            bool capturing = false;589            int countLeadingWS = 0;590            StringBuilder sb = new StringBuilder();591            const char nbsp = (char)0xA0;592 593            for (i++; i < commentLines.Count; i++)594            {595                string line = commentLines[i];596                if (!capturing && Regex.IsMatch(line, blankline))597                {598                    // Skip blank lines before capturing anything in the section.599                    continue;600                }601 602                if (Regex.IsMatch(line, directive))603                {604                    // Break on any directive even if we haven't started capturing.605                    i--;606                    break;607                }608 609                // The first line of a section sets how much whitespace we'll ignore (and hence strip).610                if (!capturing)611                {612                    int j = 0;613                    while (j < line.Length && (line[j] == ' ' || line[j] == '\t' || line[j] == nbsp))614                    {615                        countLeadingWS++;616                        j++;617                    }618                }619 620                capturing = true;621 622                // Skip leading whitespace based on the first line in the section, skipping623                // only as much whitespace as the first line had, no more (and possibly less.)624                int start = 0;625                while (start < line.Length && start < countLeadingWS &&626                       (line[start] == ' ' || line[start] == '\t' || line[start] == nbsp))627                {628                    start++;629                }630 631                sb.Append(line.AsSpan(start));632                sb.Append('\n');633            }634 635            return sb.ToString();636        }637 638        internal string GetHelpFile(CommandInfo commandInfo)639        {640            if (_sections.MamlHelpFile == null)641            {642                return null;643            }644 645            string helpFileToLoad = _sections.MamlHelpFile;646            Collection<string> searchPaths = new Collection<string>();647            string scriptFile = ((IScriptCommandInfo)commandInfo).ScriptBlock.File;648            if (!string.IsNullOrEmpty(scriptFile))649            {650                helpFileToLoad = Path.Combine(Path.GetDirectoryName(scriptFile), _sections.MamlHelpFile);651            }652            else if (commandInfo.Module != null)653            {654                helpFileToLoad = Path.Combine(Path.GetDirectoryName(commandInfo.Module.Path), _sections.MamlHelpFile);655            }656 657            string location = MUIFileSearcher.LocateFile(helpFileToLoad, searchPaths);658 659            return location;660        }661 662        internal RemoteHelpInfo GetRemoteHelpInfo(ExecutionContext context, CommandInfo commandInfo)663        {664            if (string.IsNullOrEmpty(_sections.ForwardHelpTargetName) || string.IsNullOrEmpty(_sections.RemoteHelpRunspace))665            {666                return null;667            }668 669            // get the PSSession object from the variable specified in the comments670            IScriptCommandInfo scriptCommandInfo = (IScriptCommandInfo)commandInfo;671            SessionState sessionState = scriptCommandInfo.ScriptBlock.SessionState;672            object runspaceInfoAsObject = sessionState.PSVariable.GetValue(_sections.RemoteHelpRunspace);673            PSSession runspaceInfo;674            if (runspaceInfoAsObject == null ||675                !LanguagePrimitives.TryConvertTo(runspaceInfoAsObject, out runspaceInfo))676            {677                string errorMessage = HelpErrors.RemoteRunspaceNotAvailable;678                throw new InvalidOperationException(errorMessage);679            }680 681            return new RemoteHelpInfo(682                context,683                (RemoteRunspace)runspaceInfo.Runspace,684                commandInfo.Name,685                _sections.ForwardHelpTargetName,686                _sections.ForwardHelpCategory,687                commandInfo.HelpCategory);688        }689 690        /// <summary>691        /// Look for special comments indicating the comment block is meant692        /// to be used for help.693        /// </summary>694        /// <param name="comments">The list of comments to process.</param>695        /// <returns>True if any special comments are found, false otherwise.</returns>696        internal bool AnalyzeCommentBlock(List<Token> comments)697        {698            if (comments == null || comments.Count == 0)699            {700                return false;701            }702 703            List<string> commentLines = new List<string>();704            foreach (Token comment in comments)705            {706                CollectCommentText(comment, commentLines);707            }708 709            return AnalyzeCommentBlock(commentLines);710        }711 712        private bool AnalyzeCommentBlock(List<string> commentLines)713        {714            bool directiveFound = false;715            for (int i = 0; i < commentLines.Count; i++)716            {717                Match match = Regex.Match(commentLines[i], directive);718                if (match.Success)719                {720                    directiveFound = true;721 722                    if (match.Groups[3].Success)723                    {724                        switch (match.Groups[1].Value.ToUpperInvariant())725                        {726                            case "PARAMETER":727                                {728                                    string param = match.Groups[3].Value.ToUpperInvariant().Trim();729                                    string section = GetSection(commentLines, ref i);730                                    if (!_parameters.ContainsKey(param))731                                    {732                                        _parameters.Add(param, section);733                                    }734 735                                    break;736                                }737                            case "FORWARDHELPTARGETNAME":738                                _sections.ForwardHelpTargetName = match.Groups[3].Value.Trim();739                                break;740                            case "FORWARDHELPCATEGORY":741                                _sections.ForwardHelpCategory = match.Groups[3].Value.Trim();742                                break;743                            case "REMOTEHELPRUNSPACE":744                                _sections.RemoteHelpRunspace = match.Groups[3].Value.Trim();745                                break;746                            case "EXTERNALHELP":747                                _sections.MamlHelpFile = match.Groups[3].Value.Trim();748                                isExternalHelpSet = true;749                                break;750                            default:751                                return false;752                        }753                    }754                    else755                    {756                        switch (match.Groups[1].Value.ToUpperInvariant())757                        {758                            case "SYNOPSIS":759                                _sections.Synopsis = GetSection(commentLines, ref i);760                                break;761                            case "DESCRIPTION":762                                _sections.Description = GetSection(commentLines, ref i);763                                break;764                            case "NOTES":765                                _sections.Notes = GetSection(commentLines, ref i);766                                break;767                            case "LINK":768                                _links.Add(GetSection(commentLines, ref i).Trim());769                                break;770                            case "EXAMPLE":771                                _examples.Add(GetSection(commentLines, ref i));772                                break;773                            case "INPUTS":774                                _inputs.Add(GetSection(commentLines, ref i));775                                break;776                            case "OUTPUTS":777                                _outputs.Add(GetSection(commentLines, ref i));778                                break;779                            case "COMPONENT":780                                _sections.Component = GetSection(commentLines, ref i).Trim();781                                break;782                            case "ROLE":783                                _sections.Role = GetSection(commentLines, ref i).Trim();784                                break;785                            case "FUNCTIONALITY":786                                _sections.Functionality = GetSection(commentLines, ref i).Trim();787                                break;788                            default:789                                return false;790                        }791                    }792                }793                else if (!Regex.IsMatch(commentLines[i], blankline))794                {795                    return false;796                }797            }798 799            _sections.Examples = new ReadOnlyCollection<string>(_examples);800            _sections.Inputs = new ReadOnlyCollection<string>(_inputs);801            _sections.Outputs = new ReadOnlyCollection<string>(_outputs);802            _sections.Links = new ReadOnlyCollection<string>(_links);803            // TODO, Changing this to an IDictionary because ReadOnlyDictionary is available only in .NET 4.5804            // This is a temporary workaround and will be fixed later. Tracked by Win8: 354135805            _sections.Parameters = new Dictionary<string, string>(_parameters);806 807            return directiveFound;808        }809 810        /// <summary>811        /// The analysis of the comments finds the component, functionality, and role fields, but812        /// those fields aren't added to the xml because they aren't children of the command xml813        /// node, they are under a sibling of the command xml node and apply to all command nodes814        /// in a maml file.815        /// </summary>816        /// <param name="helpInfo">The helpInfo object to set the fields on.</param>817        internal void SetAdditionalData(MamlCommandHelpInfo helpInfo)818        {819            helpInfo.SetAdditionalDataFromHelpComment(820                _sections.Component,821                _sections.Functionality,822                _sections.Role);823        }824 825        internal static CommentHelpInfo GetHelpContents(List<Language.Token> comments, List<string> parameterDescriptions)826        {827            HelpCommentsParser helpCommentsParser = new HelpCommentsParser(parameterDescriptions);828            helpCommentsParser.AnalyzeCommentBlock(comments);829            return helpCommentsParser._sections;830        }831 832        internal static HelpInfo CreateFromComments(ExecutionContext context,833                                                    CommandInfo commandInfo,834                                                    List<Language.Token> comments,835                                                    List<string> parameterDescriptions,836                                                    bool dontSearchOnRemoteComputer,837                                                    out string helpFile, out string helpUriFromDotLink)838        {839            HelpCommentsParser helpCommentsParser = new HelpCommentsParser(commandInfo, parameterDescriptions);840            helpCommentsParser.AnalyzeCommentBlock(comments);841 842            if (helpCommentsParser._sections.Links != null && helpCommentsParser._sections.Links.Count != 0)843            {844                helpUriFromDotLink = helpCommentsParser._sections.Links[0];845            }846            else847            {848                helpUriFromDotLink = null;849            }850 851            helpFile = helpCommentsParser.GetHelpFile(commandInfo);852 853            // If only .ExternalHelp is defined and the help file is not found, then we854            // use the metadata driven help855            if (comments.Count == 1 && helpCommentsParser.isExternalHelpSet && helpFile == null)856            {857                return null;858            }859 860            return CreateFromComments(context, commandInfo, helpCommentsParser, dontSearchOnRemoteComputer);861        }862 863        internal static HelpInfo CreateFromComments(ExecutionContext context, CommandInfo commandInfo, HelpCommentsParser helpCommentsParser,864            bool dontSearchOnRemoteComputer)865        {866            if (!dontSearchOnRemoteComputer)867            {868                RemoteHelpInfo remoteHelpInfo = helpCommentsParser.GetRemoteHelpInfo(context, commandInfo);869                if (remoteHelpInfo != null)870                {871                    // Add HelpUri if necessary872                    if (remoteHelpInfo.GetUriForOnlineHelp() == null)873                    {874                        DefaultCommandHelpObjectBuilder.AddRelatedLinksProperties(remoteHelpInfo.FullHelp,875                                                                                  commandInfo.CommandMetadata.HelpUri);876                    }877 878                    return remoteHelpInfo;879                }880            }881 882            XmlDocument doc = helpCommentsParser.BuildXmlFromComments();883            HelpCategory helpCategory = commandInfo.HelpCategory;884            MamlCommandHelpInfo localHelpInfo = MamlCommandHelpInfo.Load(doc.DocumentElement, helpCategory);885            if (localHelpInfo != null)886            {887                helpCommentsParser.SetAdditionalData(localHelpInfo);888 889                if (!string.IsNullOrEmpty(helpCommentsParser._sections.ForwardHelpTargetName)890                    || !string.IsNullOrEmpty(helpCommentsParser._sections.ForwardHelpCategory))891                {892                    if (string.IsNullOrEmpty(helpCommentsParser._sections.ForwardHelpTargetName))893                    {894                        localHelpInfo.ForwardTarget = localHelpInfo.Name;895                    }896                    else897                    {898                        localHelpInfo.ForwardTarget = helpCommentsParser._sections.ForwardHelpTargetName;899                    }900 901                    if (!string.IsNullOrEmpty(helpCommentsParser._sections.ForwardHelpCategory))902                    {903                        try904                        {905                            localHelpInfo.ForwardHelpCategory = (HelpCategory)Enum.Parse(typeof(HelpCategory), helpCommentsParser._sections.ForwardHelpCategory, true);906                        }907                        catch (System.ArgumentException)908                        {909                            // Ignore conversion errors.910                        }911                    }912                    else913                    {914                        localHelpInfo.ForwardHelpCategory = (HelpCategory.Alias |915                                                             HelpCategory.Cmdlet |916                                                             HelpCategory.ExternalScript |917                                                             HelpCategory.Filter |918                                                             HelpCategory.Function |919                                                             HelpCategory.ScriptCommand);920                    }921                }922 923                // Add HelpUri if necessary924                if (localHelpInfo.GetUriForOnlineHelp() == null)925                {926                    DefaultCommandHelpObjectBuilder.AddRelatedLinksProperties(localHelpInfo.FullHelp, commandInfo.CommandMetadata.HelpUri);927                }928            }929 930            return localHelpInfo;931        }932 933        /// <summary>934        /// Analyze a block of comments to determine if it is a special help block.935        /// </summary>936        /// <param name="commentBlock">The block of comments to analyze.</param>937        /// <returns>True if the block is our special comment block for help, false otherwise.</returns>938        internal static bool IsCommentHelpText(List<Token> commentBlock)939        {940            if ((commentBlock == null) || (commentBlock.Count == 0))941                return false;942 943            HelpCommentsParser generator = new HelpCommentsParser();944            return generator.AnalyzeCommentBlock(commentBlock);945        }946 947        #region Collect comments from AST948 949        private static List<Language.Token> GetCommentBlock(Language.Token[] tokens, ref int startIndex)950        {951            var result = new List<Language.Token>();952 953            // Any whitespace between the token and the first comment is allowed.954            int nextMaxStartLine = Int32.MaxValue;955 956            for (int i = startIndex; i < tokens.Length; i++)957            {958                Language.Token current = tokens[i];959 960                // If the current token starts on a line beyond the current "chunk",961                // then we're done scanning.962                if (current.Extent.StartLineNumber > nextMaxStartLine)963                {964                    startIndex = i;965                    break;966                }967 968                if (current.Kind == TokenKind.Comment)969                {970                    result.Add(current);971 972                    // The next comment must be on either the same line as this comment ends, or973                    // the next line, but nowhere else, otherwise it's not in the same "chunk".974                    nextMaxStartLine = current.Extent.EndLineNumber + 1;975                }976                else if (current.Kind != TokenKind.NewLine)977                {978                    // A non-comment, non-position token means we are no longer collecting comments979                    startIndex = i;980                    break;981                }982            }983 984            return result;985        }986 987        private static List<Language.Token> GetPrecedingCommentBlock(Language.Token[] tokens, int tokenIndex, int proximity)988        {989            var result = new List<Language.Token>();990            int minEndLine = tokens[tokenIndex].Extent.StartLineNumber - proximity;991 992            for (int i = tokenIndex - 1; i >= 0; i--)993            {994                Language.Token current = tokens[i];995 996                if (current.Extent.EndLineNumber < minEndLine)997                    break;998 999                if (current.Kind == TokenKind.Comment)1000                {1001                    result.Add(current);1002                    minEndLine = current.Extent.StartLineNumber - 1;1003                }1004                else if (current.Kind != TokenKind.NewLine)1005                {1006                    break;1007                }1008            }1009 1010            result.Reverse();1011            return result;1012        }1013 1014        private static int FirstTokenInExtent(Language.Token[] tokens, IScriptExtent extent, int startIndex = 0)1015        {1016            int index;1017            for (index = startIndex; index < tokens.Length; ++index)1018            {1019                if (!tokens[index].Extent.IsBefore(extent))1020                {1021                    break;1022                }1023            }1024 1025            return index;1026        }1027 1028        private static int LastTokenInExtent(Language.Token[] tokens, IScriptExtent extent, int startIndex)1029        {1030            int index;1031            for (index = startIndex; index < tokens.Length; ++index)1032            {1033                if (tokens[index].Extent.IsAfter(extent))1034                {1035                    break;1036                }1037            }1038 1039            return index - 1;1040        }1041 1042        internal const int CommentBlockProximity = 2;1043 1044        private static List<string> GetParameterComments(Language.Token[] tokens, IParameterMetadataProvider ipmp, int startIndex)1045        {1046            var result = new List<string>();1047            var parameters = ipmp.Parameters;1048            if (parameters == null || parameters.Count == 0)1049            {1050                return result;1051            }1052 1053            foreach (var parameter in parameters)1054            {1055                var commentLines = new List<string>();1056 1057                var firstToken = FirstTokenInExtent(tokens, parameter.Extent, startIndex);1058                var comments = GetPrecedingCommentBlock(tokens, firstToken, CommentBlockProximity);1059                if (comments != null)1060                {1061                    foreach (var comment in comments)1062                    {1063                        CollectCommentText(comment, commentLines);1064                    }1065                }1066 1067                var lastToken = LastTokenInExtent(tokens, parameter.Extent, firstToken);1068                for (int i = firstToken; i < lastToken; ++i)1069                {1070                    if (tokens[i].Kind == TokenKind.Comment)1071                    {1072                        CollectCommentText(tokens[i], commentLines);1073                    }1074                }1075 1076                lastToken += 1;1077                comments = GetCommentBlock(tokens, ref lastToken);1078                if (comments != null)1079                {1080                    foreach (var comment in comments)1081                    {1082                        CollectCommentText(comment, commentLines);1083                    }1084                }1085 1086                int n = -1;1087                result.Add(GetSection(commentLines, ref n));1088            }1089 1090            return result;1091        }1092 1093        internal static Tuple<List<Language.Token>, List<string>> GetHelpCommentTokens(IParameterMetadataProvider ipmp,1094            Dictionary<Ast, Token[]> scriptBlockTokenCache)1095        {1096            Diagnostics.Assert(scriptBlockTokenCache != null, "scriptBlockTokenCache cannot be null");1097            var ast = (Ast)ipmp;1098 1099            var rootAst = ast;1100            Ast configAst = null;1101            while (rootAst.Parent != null)1102            {1103                rootAst = rootAst.Parent;1104                if (rootAst is ConfigurationDefinitionAst)1105                {1106                    configAst = rootAst;1107                }1108            }1109 1110            //  tokens saved from reparsing the script.1111            Language.Token[] tokens = null;1112            scriptBlockTokenCache.TryGetValue(rootAst, out tokens);1113 1114            if (tokens == null)1115            {1116                ParseError[] errors;1117                // storing all comment tokens1118                Language.Parser.ParseInput(rootAst.Extent.Text, out tokens, out errors);1119                scriptBlockTokenCache[rootAst] = tokens;1120            }1121 1122            int savedStartIndex;1123            int startTokenIndex;1124            int lastTokenIndex;1125 1126            var funcDefnAst = ast as FunctionDefinitionAst;1127            List<Language.Token> commentBlock;1128            if (funcDefnAst != null || configAst != null)1129            {1130                // The first comment block preceding the function or configuration keyword is a candidate help comment block.1131                var funcOrConfigTokenIndex =1132                    savedStartIndex = FirstTokenInExtent(tokens, configAst == null ? ast.Extent : configAst.Extent);1133 1134                commentBlock = GetPrecedingCommentBlock(tokens, funcOrConfigTokenIndex, CommentBlockProximity);1135 1136                if (HelpCommentsParser.IsCommentHelpText(commentBlock))1137                {1138                    return Tuple.Create(commentBlock, GetParameterComments(tokens, ipmp, savedStartIndex));1139                }1140 1141                // comment block is behind function definition1142                // we don't support it for configuration declaration as this style is rarely used1143                if (funcDefnAst != null)1144                {1145                    startTokenIndex =1146                        FirstTokenInExtent(tokens, funcDefnAst.Body.Extent) + 1;1147                    lastTokenIndex = LastTokenInExtent(tokens, ast.Extent, funcOrConfigTokenIndex);1148 1149                    Diagnostics.Assert(tokens[startTokenIndex - 1].Kind == TokenKind.LCurly,1150                        "Unexpected first token in function");1151                    Diagnostics.Assert(tokens[lastTokenIndex].Kind == TokenKind.RCurly,1152                        "Unexpected last token in function");1153                }1154                else1155                {1156                    return null;1157                }1158            }1159            else if (ast == rootAst)1160            {1161                startTokenIndex = savedStartIndex = 0;1162                lastTokenIndex = tokens.Length - 1;1163            }1164            else1165            {1166                // This case should be rare (but common with implicit remoting).1167                // We have a script block that was used to generate a function like:1168                //     $sb = { }1169                //     set-item function:foo $sb1170                //     help foo1171                startTokenIndex = savedStartIndex = FirstTokenInExtent(tokens, ast.Extent) + 1;1172                lastTokenIndex = LastTokenInExtent(tokens, ast.Extent, startTokenIndex);1173 1174                Diagnostics.Assert(tokens[startTokenIndex - 1].Kind == TokenKind.LCurly,1175                    "Unexpected first token in script block");1176                Diagnostics.Assert(tokens[lastTokenIndex].Kind == TokenKind.RCurly,1177                    "Unexpected last token in script block");1178            }1179 1180            while (true)1181            {1182                commentBlock = GetCommentBlock(tokens, ref startTokenIndex);1183                if (commentBlock.Count == 0)1184                    break;1185 1186                if (!HelpCommentsParser.IsCommentHelpText(commentBlock))1187                    continue;1188 1189                if (ast == rootAst)1190                {1191                    // One more check - make sure the comment doesn't belong to the first function in the script.1192                    var endBlock = ((ScriptBlockAst)ast).EndBlock;1193                    if (endBlock == null || !endBlock.Unnamed)1194                    {1195                        return Tuple.Create(commentBlock, GetParameterComments(tokens, ipmp, savedStartIndex));1196                    }1197 1198                    var firstStatement = endBlock.Statements.FirstOrDefault();1199                    if (firstStatement is FunctionDefinitionAst)1200                    {

Showing the first 1,200 of 1227 lines. Download the file for the rest.