Skip to content

Commit fba5d3a

Browse files
anamnaviPaulHigin
andauthored
Script file duplicate keys and parsing help comment block (#726)
Co-authored-by: Paul Higinbotham <paulhi@microsoft.com>
1 parent bffff47 commit fba5d3a

File tree

5 files changed

+206
-224
lines changed

5 files changed

+206
-224
lines changed

src/code/PSScriptContents.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public sealed class PSScriptContents
1515
/// <summary>
1616
/// End of file contents for the .ps1 file.
1717
/// </summary>
18-
public string[] EndOfFileContents { get; private set; } = Utils.EmptyStrArray;
18+
public string[] ScriptContents { get; private set; } = Utils.EmptyStrArray;
1919

2020
/// <summary>
2121
/// End of file contents for the .ps1 file.
@@ -38,7 +38,7 @@ public sealed class PSScriptContents
3838
/// </summary>
3939
public PSScriptContents(string[] endOfFileContents)
4040
{
41-
EndOfFileContents = endOfFileContents;
41+
ScriptContents = endOfFileContents;
4242
ContainsSignature = CheckForSignature();
4343
}
4444

@@ -60,7 +60,7 @@ internal void ParseContent(string[] commentLines)
6060
{
6161
if (commentLines.Length != 0)
6262
{
63-
EndOfFileContents = commentLines;
63+
ScriptContents = commentLines;
6464
ContainsSignature = CheckForSignature();
6565
}
6666
}
@@ -74,7 +74,7 @@ internal void ParseContent(string[] commentLines)
7474
internal string[] EmitContent()
7575
{
7676
RemoveSignatureString();
77-
return EndOfFileContents;
77+
return ScriptContents;
7878
}
7979

8080
#endregion
@@ -86,11 +86,12 @@ internal string[] EmitContent()
8686
/// </summary>
8787
private bool CheckForSignature()
8888
{
89-
for (int i = 0; i < EndOfFileContents.Length; i++)
89+
for (int i = 0; i < ScriptContents.Length; i++)
9090
{
91-
if (String.Equals(EndOfFileContents[i], signatureStartString, StringComparison.InvariantCultureIgnoreCase))
91+
if (String.Equals(ScriptContents[i], signatureStartString, StringComparison.InvariantCultureIgnoreCase))
9292
{
9393
_signatureStartIndex = i;
94+
break;
9495
}
9596
}
9697

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

112117
ContainsSignature = false;
113118
}

src/code/PSScriptFileInfo.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,15 @@ internal static bool TryParseScriptFileContents(
132132
while (j < fileContents.Length)
133133
{
134134
string blockLine = fileContents[j];
135+
psScriptInfoCommentContent.Add(blockLine);
135136
if (blockLine.StartsWith("#>"))
136137
{
138+
137139
reachedPSScriptInfoCommentEnd = true;
138140
i = j + 1;
139141
break;
140142
}
141143

142-
psScriptInfoCommentContent.Add(blockLine);
143144
j++;
144145
}
145146

@@ -159,14 +160,15 @@ internal static bool TryParseScriptFileContents(
159160
while (j < fileContents.Length)
160161
{
161162
string blockLine = fileContents[j];
163+
162164
if (blockLine.StartsWith("#>"))
163165
{
164166
reachedHelpInfoCommentEnd = true;
165167
i = j + 1;
166168
endOfFileContentsStartIndex = i;
167169
break;
168170
}
169-
171+
170172
helpInfoCommentContent.Add(blockLine);
171173
j++;
172174
}

src/code/PSScriptHelp.cs

Lines changed: 96 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -18,52 +18,12 @@ public sealed class PSScriptHelp
1818
/// <summary>
1919
/// The description of the script.
2020
/// </summary>
21-
public string Description { get; private set; }
21+
public string Description { get; private set; } = String.Empty;
2222

2323
/// <summary>
24-
/// The synopsis of the script.
24+
/// This contains all help content aside from Description
2525
/// </summary>
26-
public string Synopsis { get; private set; }
27-
28-
/// <summary>
29-
/// The example(s) relating to the script's usage.
30-
/// </summary>
31-
public string[] Example { get; private set; } = Utils.EmptyStrArray;
32-
33-
/// <summary>
34-
/// The inputs to the script.
35-
/// </summary>
36-
public string[] Inputs { get; private set; } = Utils.EmptyStrArray;
37-
38-
/// <summary>
39-
/// The outputs to the script.
40-
/// </summary>
41-
public string[] Outputs { get; private set; } = Utils.EmptyStrArray;
42-
43-
/// <summary>
44-
/// The notes for the script.
45-
/// </summary>
46-
public string[] Notes { get; private set; } = Utils.EmptyStrArray;
47-
48-
/// <summary>
49-
/// The links for the script.
50-
/// </summary>
51-
public string[] Links { get; private set; } = Utils.EmptyStrArray;
52-
53-
/// <summary>
54-
/// The components for the script.
55-
/// </summary>
56-
public string[] Component { get; private set; } = Utils.EmptyStrArray;
57-
58-
/// <summary>
59-
/// The roles for the script.
60-
/// </summary>
61-
public string[] Role { get; private set; } = Utils.EmptyStrArray;
62-
63-
/// <summary>
64-
/// The functionality components for the script.
65-
/// </summary>
66-
public string[] Functionality { get; private set; } = Utils.EmptyStrArray;
26+
public List<string> HelpContent { get; private set; } = new List<string>();
6727

6828
#endregion
6929

@@ -74,35 +34,7 @@ public sealed class PSScriptHelp
7434
/// </summary>
7535
public PSScriptHelp (string description)
7636
{
77-
this.Description = description;
78-
}
79-
80-
/// <summary>
81-
/// This constructor takes values for description as well as other properties and creates a new PSScriptHelp instance.
82-
/// Currently, the New-PSScriptFileInfo and Update-PSScriptFileInfo cmdlets don't support the user providing these values.
83-
/// </summary>
84-
public PSScriptHelp (
85-
string description,
86-
string synopsis,
87-
string[] example,
88-
string[] inputs,
89-
string[] outputs,
90-
string[] notes,
91-
string[] links,
92-
string[] component,
93-
string[] role,
94-
string[] functionality)
95-
{
96-
this.Description = description;
97-
this.Synopsis = synopsis;
98-
this.Example = example;
99-
this.Inputs = inputs;
100-
this.Outputs = outputs;
101-
this.Notes = notes;
102-
this.Links = links;
103-
this.Component = component;
104-
this.Role = role;
105-
this.Functionality = functionality;
37+
Description = description;
10638
}
10739

10840
/// <summary>
@@ -121,41 +53,99 @@ internal PSScriptHelp() {}
12153
/// </summary>
12254
internal bool ParseContentIntoObj(string[] commentLines, out ErrorRecord error)
12355
{
124-
bool successfullyParsed = true;
125-
string[] spaceDelimeter = new string[]{" "};
126-
string[] newlineDelimeter = new string[]{Environment.NewLine};
56+
error = null;
12757

128-
// parse content into a hashtable
129-
Hashtable parsedHelpMetadata = Utils.ParseCommentBlockContent(commentLines);
58+
// Parse content into a hashtable.
59+
Hashtable parsedHelpMetadata = ParseHelpContentHelper(commentLines);
13060

131-
if (!ValidateParsedContent(parsedHelpMetadata, out error))
61+
if (!ValidateParsedContent(parsedHelpMetadata, out ErrorRecord validationError))
13262
{
63+
error = validationError;
13364
return false;
13465
}
13566

136-
// populate object
137-
Description = (string) parsedHelpMetadata["DESCRIPTION"];
138-
Synopsis = (string) parsedHelpMetadata["SYNOPSIS"] ?? String.Empty;
139-
Example = Utils.GetStringArrayFromString(newlineDelimeter, (string) parsedHelpMetadata["EXAMPLE"]);
140-
Inputs = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["INPUT"]);
141-
Outputs = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["OUTPUTS"]);
142-
Notes = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["NOTES"]);
143-
Links = Utils.GetStringArrayFromString(newlineDelimeter, (string) parsedHelpMetadata["LINKS"]);
144-
Component = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["COMPONENT"]);
145-
Role = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["ROLE"]);
146-
Functionality = Utils.GetStringArrayFromString(spaceDelimeter, (string) parsedHelpMetadata["FUNCTIONALITY"]);
147-
148-
return successfullyParsed;
67+
// Populate object.
68+
List<string> descriptionValue = (List<string>) parsedHelpMetadata["DESCRIPTION"];
69+
Description = String.Join(Environment.NewLine, descriptionValue);
70+
if (parsedHelpMetadata.ContainsKey("HELPCONTENT"))
71+
{
72+
HelpContent = (List<string>) parsedHelpMetadata["HELPCONTENT"];
73+
}
74+
75+
return true;
14976
}
15077

78+
/// <summary>
79+
/// Parses metadata out of PSScriptCommentInfo comment block's lines (which are passed in) into a hashtable.
80+
/// </summary>
81+
public static Hashtable ParseHelpContentHelper(string[] commentLines)
82+
{
83+
/**
84+
Comment lines can look like this:
85+
86+
.KEY1 value
87+
88+
.KEY2 value
89+
90+
.KEY2 value2
91+
92+
.KEY3
93+
value
94+
95+
.KEY4 value
96+
value continued
97+
98+
*/
99+
100+
// Parse out Description and everything else into a bucket list.
101+
102+
List<string> helpContent = new List<string>();
103+
List<string> descriptionValue = new List<string>();
104+
bool parsingDescription = false;
105+
106+
for(int i = 0; i < commentLines.Length; i++)
107+
{
108+
string line = commentLines[i];
109+
if (line.Trim().StartsWith(".DESCRIPTION"))
110+
{
111+
parsingDescription = true;
112+
}
113+
else if (line.Trim().StartsWith("."))
114+
{
115+
parsingDescription = false;
116+
helpContent.Add(line);
117+
}
118+
else if (!String.IsNullOrEmpty(line))
119+
{
120+
if (parsingDescription)
121+
{
122+
descriptionValue.Add(line);
123+
}
124+
else
125+
{
126+
helpContent.Add(line);
127+
}
128+
}
129+
}
130+
131+
Hashtable parsedHelpMetadata = new Hashtable();
132+
parsedHelpMetadata.Add("DESCRIPTION", descriptionValue);
133+
if (helpContent.Count != 0)
134+
{
135+
parsedHelpMetadata.Add("HELPCONTENT", helpContent);
136+
}
137+
138+
return parsedHelpMetadata;
139+
}
140+
151141
/// <summary>
152142
/// Valides parsed help info content from the hashtable to ensure required help metadata (Description) is present
153143
/// and does not contain empty values.
154144
/// </summary>
155145
internal bool ValidateParsedContent(Hashtable parsedHelpMetadata, out ErrorRecord error)
156146
{
157147
error = null;
158-
if (!parsedHelpMetadata.ContainsKey("DESCRIPTION") || String.IsNullOrEmpty((string) parsedHelpMetadata["DESCRIPTION"]) || String.Equals(((string) parsedHelpMetadata["DESCRIPTION"]).Trim(), String.Empty))
148+
if (!parsedHelpMetadata.ContainsKey("DESCRIPTION"))
159149
{
160150
var exMessage = "PSScript file must contain value for Description. Ensure value for Description is passed in and try again.";
161151
var ex = new ArgumentException(exMessage);
@@ -164,7 +154,18 @@ internal bool ValidateParsedContent(Hashtable parsedHelpMetadata, out ErrorRecor
164154
return false;
165155
}
166156

167-
if (StringContainsComment((string) parsedHelpMetadata["DESCRIPTION"]))
157+
List<string> descriptionValue = (List<string>) parsedHelpMetadata["DESCRIPTION"];
158+
string descriptionString = String.Join("", descriptionValue);
159+
if (descriptionValue.Count == 0 || (String.IsNullOrEmpty(descriptionString)) || String.IsNullOrWhiteSpace(descriptionString))
160+
{
161+
var exMessage = "PSScript file value for Description cannot be null, empty or whitespace. Ensure value for Description meets these conditions and try again.";
162+
var ex = new ArgumentException(exMessage);
163+
var PSScriptInfoMissingDescriptionError = new ErrorRecord(ex, "PSScriptInfoMissingDescription", ErrorCategory.InvalidArgument, null);
164+
error = PSScriptInfoMissingDescriptionError;
165+
return false;
166+
}
167+
168+
if (StringContainsComment(descriptionString))
168169
{
169170
var exMessage = "PSScript file's value for Description cannot contain '<#' or '#>'. Pass in a valid value for Description and try again.";
170171
var ex = new ArgumentException(exMessage);
@@ -216,60 +217,11 @@ internal string[] EmitContent()
216217
psHelpInfoLines.Add($".DESCRIPTION");
217218
psHelpInfoLines.Add($"{Description}{Environment.NewLine}");
218219

219-
if (!String.IsNullOrEmpty(Synopsis))
220-
{
221-
psHelpInfoLines.Add($".SYNOPSIS");
222-
psHelpInfoLines.Add($"{Synopsis}{Environment.NewLine}");
223-
}
224-
225-
foreach (string currentExample in Example)
226-
{
227-
psHelpInfoLines.Add($".EXAMPLE");
228-
psHelpInfoLines.Add($"{currentExample}{Environment.NewLine}");
229-
}
230-
231-
foreach (string input in Inputs)
232-
{
233-
psHelpInfoLines.Add($".INPUTS");
234-
psHelpInfoLines.Add($"{input}{Environment.NewLine}");
235-
}
236-
237-
foreach (string output in Outputs)
238-
{
239-
psHelpInfoLines.Add($".OUTPUTS");
240-
psHelpInfoLines.Add($"{output}{Environment.NewLine}");
241-
}
242-
243-
if (Notes.Length > 0)
220+
if (HelpContent.Count != 0)
244221
{
245-
psHelpInfoLines.Add($".NOTES");
246-
psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Notes)}{Environment.NewLine}");
247-
}
248-
249-
foreach (string link in Links)
250-
{
251-
psHelpInfoLines.Add($".LINK");
252-
psHelpInfoLines.Add($"{link}{Environment.NewLine}");
253-
}
254-
255-
if (Component.Length > 0)
256-
{
257-
psHelpInfoLines.Add($".COMPONENT");
258-
psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Component)}{Environment.NewLine}");
222+
psHelpInfoLines.AddRange(HelpContent);
259223
}
260224

261-
if (Role.Length > 0)
262-
{
263-
psHelpInfoLines.Add($".ROLE");
264-
psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Role)}{Environment.NewLine}");
265-
}
266-
267-
if (Functionality.Length > 0)
268-
{
269-
psHelpInfoLines.Add($".FUNCTIONALITY");
270-
psHelpInfoLines.Add($"{String.Join(Environment.NewLine, Functionality)}{Environment.NewLine}");
271-
}
272-
273225
psHelpInfoLines.Add("#>");
274226

275227
return psHelpInfoLines.ToArray();

0 commit comments

Comments
 (0)