Skip to content
Merged
23 changes: 14 additions & 9 deletions src/code/PSScriptContents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed class PSScriptContents
/// <summary>
/// End of file contents for the .ps1 file.
/// </summary>
public string[] EndOfFileContents { get; private set; } = Utils.EmptyStrArray;
public string[] ScriptContents { get; private set; } = Utils.EmptyStrArray;

/// <summary>
/// End of file contents for the .ps1 file.
Expand All @@ -38,7 +38,7 @@ public sealed class PSScriptContents
/// </summary>
public PSScriptContents(string[] endOfFileContents)
{
EndOfFileContents = endOfFileContents;
ScriptContents = endOfFileContents;
ContainsSignature = CheckForSignature();
}

Expand All @@ -60,7 +60,7 @@ internal void ParseContent(string[] commentLines)
{
if (commentLines.Length != 0)
{
EndOfFileContents = commentLines;
ScriptContents = commentLines;
ContainsSignature = CheckForSignature();
}
}
Expand All @@ -74,7 +74,7 @@ internal void ParseContent(string[] commentLines)
internal string[] EmitContent()
{
RemoveSignatureString();
return EndOfFileContents;
return ScriptContents;
}

#endregion
Expand All @@ -86,11 +86,12 @@ internal string[] EmitContent()
/// </summary>
private bool CheckForSignature()
{
for (int i = 0; i < EndOfFileContents.Length; i++)
for (int i = 0; i < ScriptContents.Length; i++)
{
if (String.Equals(EndOfFileContents[i], signatureStartString, StringComparison.InvariantCultureIgnoreCase))
if (String.Equals(ScriptContents[i], signatureStartString, StringComparison.InvariantCultureIgnoreCase))
{
_signatureStartIndex = i;
break;
}
}

Expand All @@ -105,9 +106,13 @@ private void RemoveSignatureString()
{
if (ContainsSignature)
{
string[] newEndOfFileContents = new string[EndOfFileContents.Length - _signatureStartIndex];
Array.Copy(EndOfFileContents, newEndOfFileContents, _signatureStartIndex);
EndOfFileContents = newEndOfFileContents;
// The script signature comment block always appears at the end of the script file,
// so its start location becomes the end of the content section after the signature
// comment block is removed, and is also the length of the content section minus the
// signature block.
string[] contentsWithoutSignature = new string[_signatureStartIndex];
Array.Copy(ScriptContents, contentsWithoutSignature, _signatureStartIndex);
ScriptContents = contentsWithoutSignature;

ContainsSignature = false;
}
Expand Down
6 changes: 4 additions & 2 deletions src/code/PSScriptFileInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,15 @@ internal static bool TryParseScriptFileContents(
while (j < fileContents.Length)
{
string blockLine = fileContents[j];
psScriptInfoCommentContent.Add(blockLine);
if (blockLine.StartsWith("#>"))
{

reachedPSScriptInfoCommentEnd = true;
i = j + 1;
break;
}

psScriptInfoCommentContent.Add(blockLine);
j++;
}

Expand All @@ -159,14 +160,15 @@ internal static bool TryParseScriptFileContents(
while (j < fileContents.Length)
{
string blockLine = fileContents[j];

if (blockLine.StartsWith("#>"))
{
reachedHelpInfoCommentEnd = true;
i = j + 1;
endOfFileContentsStartIndex = i;
break;
}

helpInfoCommentContent.Add(blockLine);
j++;
}
Expand Down
240 changes: 96 additions & 144 deletions src/code/PSScriptHelp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,52 +18,12 @@ public sealed class PSScriptHelp
/// <summary>
/// The description of the script.
/// </summary>
public string Description { get; private set; }
public string Description { get; private set; } = String.Empty;

/// <summary>
/// The synopsis of the script.
/// This contains all help content aside from Description
/// </summary>
public string Synopsis { get; private set; }

/// <summary>
/// The example(s) relating to the script's usage.
/// </summary>
public string[] Example { get; private set; } = Utils.EmptyStrArray;

/// <summary>
/// The inputs to the script.
/// </summary>
public string[] Inputs { get; private set; } = Utils.EmptyStrArray;

/// <summary>
/// The outputs to the script.
/// </summary>
public string[] Outputs { get; private set; } = Utils.EmptyStrArray;

/// <summary>
/// The notes for the script.
/// </summary>
public string[] Notes { get; private set; } = Utils.EmptyStrArray;

/// <summary>
/// The links for the script.
/// </summary>
public string[] Links { get; private set; } = Utils.EmptyStrArray;

/// <summary>
/// The components for the script.
/// </summary>
public string[] Component { get; private set; } = Utils.EmptyStrArray;

/// <summary>
/// The roles for the script.
/// </summary>
public string[] Role { get; private set; } = Utils.EmptyStrArray;

/// <summary>
/// The functionality components for the script.
/// </summary>
public string[] Functionality { get; private set; } = Utils.EmptyStrArray;
public List<string> HelpContent { get; private set; } = new List<string>();

#endregion

Expand All @@ -74,35 +34,7 @@ public sealed class PSScriptHelp
/// </summary>
public PSScriptHelp (string description)
{
this.Description = description;
}

/// <summary>
/// This constructor takes values for description as well as other properties and creates a new PSScriptHelp instance.
/// Currently, the New-PSScriptFileInfo and Update-PSScriptFileInfo cmdlets don't support the user providing these values.
/// </summary>
public PSScriptHelp (
string description,
string synopsis,
string[] example,
string[] inputs,
string[] outputs,
string[] notes,
string[] links,
string[] component,
string[] role,
string[] functionality)
{
this.Description = description;
this.Synopsis = synopsis;
this.Example = example;
this.Inputs = inputs;
this.Outputs = outputs;
this.Notes = notes;
this.Links = links;
this.Component = component;
this.Role = role;
this.Functionality = functionality;
Description = description;
}

/// <summary>
Expand All @@ -121,41 +53,99 @@ internal PSScriptHelp() {}
/// </summary>
internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord error)
{
bool successfullyParsed = true;
string[] spaceDelimeter = new string[]{" "};
string[] newlineDelimeter = new string[]{Environment.NewLine};
error = null;

// parse content into a hashtable
Hashtable parsedHelpMetadata = Utils.ParseCommentBlockContent(commentLines);
// Parse content into a hashtable.
Hashtable parsedHelpMetadata = ParseHelpContentHelper(commentLines);

if (!ValidateParsedContent(parsedHelpMetadata, out error))
if (!ValidateParsedContent(parsedHelpMetadata, out ErrorRecord validationError))
{
error = validationError;
return false;
}

// populate object
Description = (string) parsedHelpMetadata["DESCRIPTION"];
Synopsis = (string) parsedHelpMetadata["SYNOPSIS"] ?? String.Empty;
Example = Utils.GetStringArrayFromString(newlineDelimeter, (string) parsedHelpMetadata["EXAMPLE"]);
Inputs = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["INPUT"]);
Outputs = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["OUTPUTS"]);
Notes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["NOTES"]);
Links = Utils.GetStringArrayFromString(newlineDelimeter, (string) parsedHelpMetadata["LINKS"]);
Component = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["COMPONENT"]);
Role = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["ROLE"]);
Functionality = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["FUNCTIONALITY"]);

return successfullyParsed;
// Populate object.
List<string> descriptionValue = (List<string>) parsedHelpMetadata["DESCRIPTION"];
Description = String.Join(Environment.NewLine, descriptionValue);
if (parsedHelpMetadata.ContainsKey("HELPCONTENT"))
{
HelpContent = (List<string>) parsedHelpMetadata["HELPCONTENT"];
}

return true;
}

/// <summary>
/// Parses metadata out of PSScriptCommentInfo comment block's lines (which are passed in) into a hashtable.
/// </summary>
public static Hashtable ParseHelpContentHelper(string[] commentLines)
{
/**
Comment lines can look like this:

.KEY1 value

.KEY2 value

.KEY2 value2

.KEY3
value

.KEY4 value
value continued

*/

// Parse out Description and everything else into a bucket list.

List<string> helpContent = new List<string>();
List<string> descriptionValue = new List<string>();
bool parsingDescription = false;

for(int i = 0; i < commentLines.Length; i++)
{
string line = commentLines[i];
if (line.Trim().StartsWith(".DESCRIPTION"))
{
parsingDescription = true;
}
else if (line.Trim().StartsWith("."))
{
parsingDescription = false;
helpContent.Add(line);
}
else if (!String.IsNullOrEmpty(line))
{
if (parsingDescription)
{
descriptionValue.Add(line);
}
else
{
helpContent.Add(line);
}
}
}

Hashtable parsedHelpMetadata = new Hashtable();
parsedHelpMetadata.Add("DESCRIPTION", descriptionValue);
if (helpContent.Count != 0)
{
parsedHelpMetadata.Add("HELPCONTENT", helpContent);
}

return parsedHelpMetadata;
}

/// <summary>
/// Valides parsed help info content from the hashtable to ensure required help metadata (Description) is present
/// and does not contain empty values.
/// </summary>
internal bool ValidateParsedContent(Hashtable parsedHelpMetadata, out ErrorRecord error)
{
error = null;
if (!parsedHelpMetadata.ContainsKey("DESCRIPTION") || String.IsNullOrEmpty((string) parsedHelpMetadata["DESCRIPTION"]) || String.Equals(((string) parsedHelpMetadata["DESCRIPTION"]).Trim(), String.Empty))
if (!parsedHelpMetadata.ContainsKey("DESCRIPTION"))
{
var exMessage = "PSScript file must contain value for Description. Ensure value for Description is passed in and try again.";
var ex = new ArgumentException(exMessage);
Expand All @@ -164,7 +154,18 @@ internal bool ValidateParsedContent(Hashtable parsedHelpMetadata, out ErrorRecor
return false;
}

if (StringContainsComment((string) parsedHelpMetadata["DESCRIPTION"]))
List<string> descriptionValue = (List<string>) parsedHelpMetadata["DESCRIPTION"];
string descriptionString = String.Join("", descriptionValue);
if (descriptionValue.Count == 0 || (String.IsNullOrEmpty(descriptionString)) || String.IsNullOrWhiteSpace(descriptionString))
{
var exMessage = "PSScript file value for Description cannot be null, empty or whitespace. Ensure value for Description meets these conditions and try again.";
var ex = new ArgumentException(exMessage);
var PSScriptInfoMissingDescriptionError = new ErrorRecord(ex, "PSScriptInfoMissingDescription", ErrorCategory.InvalidArgument, null);
error = PSScriptInfoMissingDescriptionError;
return false;
}

if (StringContainsComment(descriptionString))
{
var exMessage = "PSScript file's value for Description cannot contain '<#' or '#>'. Pass in a valid value for Description and try again.";
var ex = new ArgumentException(exMessage);
Expand Down Expand Up @@ -216,60 +217,11 @@ internal string[] EmitContent()
psHelpInfoLines.Add($".DESCRIPTION");
psHelpInfoLines.Add($"{Description}{Environment.NewLine}");

if (!String.IsNullOrEmpty(Synopsis))
{
psHelpInfoLines.Add($".SYNOPSIS");
psHelpInfoLines.Add($"{Synopsis}{Environment.NewLine}");
}

foreach (string currentExample in Example)
{
psHelpInfoLines.Add($".EXAMPLE");
psHelpInfoLines.Add($"{currentExample}{Environment.NewLine}");
}

foreach (string input in Inputs)
{
psHelpInfoLines.Add($".INPUTS");
psHelpInfoLines.Add($"{input}{Environment.NewLine}");
}

foreach (string output in Outputs)
{
psHelpInfoLines.Add($".OUTPUTS");
psHelpInfoLines.Add($"{output}{Environment.NewLine}");
}

if (Notes.Length > 0)
if (HelpContent.Count != 0)
{
psHelpInfoLines.Add($".NOTES");
psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Notes)}{Environment.NewLine}");
}

foreach (string link in Links)
{
psHelpInfoLines.Add($".LINK");
psHelpInfoLines.Add($"{link}{Environment.NewLine}");
}

if (Component.Length > 0)
{
psHelpInfoLines.Add($".COMPONENT");
psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Component)}{Environment.NewLine}");
psHelpInfoLines.AddRange(HelpContent);
}

if (Role.Length > 0)
{
psHelpInfoLines.Add($".ROLE");
psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Role)}{Environment.NewLine}");
}

if (Functionality.Length > 0)
{
psHelpInfoLines.Add($".FUNCTIONALITY");
psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Functionality)}{Environment.NewLine}");
}

psHelpInfoLines.Add("#>");

return psHelpInfoLines.ToArray();
Expand Down
Loading