Skip to content

Commit 7427a41

Browse files
committed
Add serialisation methods for SettingsData in various parsers
1 parent 1d55ace commit 7427a41

File tree

4 files changed

+166
-1
lines changed

4 files changed

+166
-1
lines changed

Engine/Settings/ISettingsParser.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,13 @@ public interface ISettingsParser
3131
/// <param name="sourcePath">The source path of the settings file.</param>
3232
/// <returns>The parsed SettingsData.</returns>
3333
SettingsData Parse(Stream content, string sourcePath);
34+
35+
/// <summary>
36+
/// Serialises the SettingsData into a string representation.
37+
/// </summary>
38+
/// <param name="settingsData">The SettingsData to serialise.</param>
39+
/// <returns>The string representation of the settings.</returns>
40+
string Serialise(SettingsData settingsData);
41+
3442
}
3543
}

Engine/Settings/JsonSettingsParser.cs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.IO;
77
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Linq;
89

910
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer
1011
{
@@ -102,6 +103,66 @@ public SettingsData Parse(Stream content, string sourcePath)
102103
RuleArguments = ruleArgs
103104
};
104105
}
106+
107+
/// <summary>
108+
/// Serializes a <see cref="SettingsData"/> instance into a PSScriptAnalyzerSettings.json
109+
/// formatted string. Omits empty collections and false boolean flags for brevity.
110+
/// </summary>
111+
/// <param name="data">Settings snapshot to serialize.</param>
112+
/// <param name="pretty">True for indented JSON, false for minified.</param>
113+
/// <returns>JSON string suitable for saving as PSScriptAnalyzerSettings.json.</returns>
114+
/// <exception cref="ArgumentNullException">If <paramref name="data"/> is null.</exception>
115+
public string Serialise(SettingsData data)
116+
{
117+
if (data == null) throw new ArgumentNullException(nameof(data));
118+
119+
var root = new JObject();
120+
var serializer = JsonSerializer.CreateDefault();
121+
122+
void AddArray(string name, IList<string> list)
123+
{
124+
if (list != null && list.Count > 0)
125+
{
126+
root[name] = new JArray(list);
127+
}
128+
}
129+
130+
AddArray("IncludeRules", data.IncludeRules);
131+
AddArray("ExcludeRules", data.ExcludeRules);
132+
AddArray("Severity", data.Severities);
133+
AddArray("CustomRulePath", data.CustomRulePath);
134+
135+
if (data.IncludeDefaultRules)
136+
{
137+
root["IncludeDefaultRules"] = true;
138+
}
139+
if (data.RecurseCustomRulePath)
140+
{
141+
root["RecurseCustomRulePath"] = true;
142+
}
143+
144+
if (data.RuleArguments != null && data.RuleArguments.Count > 0)
145+
{
146+
var rulesObj = new JObject();
147+
foreach (var rule in data.RuleArguments)
148+
{
149+
var argsObj = new JObject();
150+
if (rule.Value != null)
151+
{
152+
foreach (var arg in rule.Value)
153+
{
154+
// Serialize scalar or complex value
155+
argsObj[arg.Key] = arg.Value != null
156+
? JToken.FromObject(arg.Value, serializer)
157+
: JValue.CreateNull();
158+
}
159+
}
160+
rulesObj[rule.Key] = argsObj;
161+
}
162+
root["Rules"] = rulesObj;
163+
}
164+
return root.ToString(Formatting.Indented);
165+
}
105166
}
106167

107168
}

Engine/Settings/Psd1SettingsParser.cs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using System.Collections;
6+
using System.Collections.Generic;
67
using System.IO;
78
using System.Linq;
89
using System.Management.Automation.Language;
@@ -78,6 +79,92 @@ public SettingsData Parse(Stream content, string sourcePath)
7879

7980
return HashtableSettingsConverter.Convert(raw);
8081
}
81-
}
8282

83+
/// <summary>
84+
/// Serializes a <see cref="SettingsData"/> instance into a formatted .psd1 settings file
85+
/// (PowerShell hashtable) similar to shipped presets.
86+
/// Omits empty collections and flags (if false) to keep output concise.
87+
/// </summary>
88+
/// <param name="settingsData">Settings to serialize.</param>
89+
/// <returns>Formatted .psd1 content as a string.</returns>
90+
public string Serialise(SettingsData settingsData)
91+
{
92+
if (settingsData == null) throw new ArgumentNullException(nameof(settingsData));
93+
94+
var sb = new System.Text.StringBuilder();
95+
var indent = " ";
96+
97+
string Quote(string s) => "'" + s.Replace("'", "''") + "'";
98+
99+
void AppendStringList(string key, List<string> list)
100+
{
101+
if (list == null || list.Count == 0) return;
102+
sb.Append(indent).Append(key).Append(" = @(").AppendLine();
103+
for (int i = 0; i < list.Count; i++)
104+
{
105+
sb.Append(indent).Append(indent).Append(Quote(list[i]));
106+
sb.AppendLine(i == list.Count - 1 ? string.Empty : ",");
107+
}
108+
sb.AppendLine(indent + ")").AppendLine();
109+
}
110+
111+
string FormatScalar(object value)
112+
{
113+
if (value == null) return "$null";
114+
return value switch
115+
{
116+
string s => Quote(s),
117+
bool b => b ? "$true" : "$false",
118+
Enum e => Quote(e.ToString()),
119+
int or long or short or byte or sbyte or uint or ulong or ushort => Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture),
120+
float f => f.ToString(System.Globalization.CultureInfo.InvariantCulture),
121+
double d => d.ToString(System.Globalization.CultureInfo.InvariantCulture),
122+
decimal m => m.ToString(System.Globalization.CultureInfo.InvariantCulture),
123+
_ => Quote(value.ToString())
124+
};
125+
}
126+
127+
sb.AppendLine("@{");
128+
129+
// Ordered sections
130+
AppendStringList("IncludeRules", settingsData.IncludeRules);
131+
AppendStringList("ExcludeRules", settingsData.ExcludeRules);
132+
AppendStringList("Severity", settingsData.Severities);
133+
AppendStringList("CustomRulePath", settingsData.CustomRulePath);
134+
135+
if (settingsData.IncludeDefaultRules)
136+
{
137+
sb.Append(indent).Append("IncludeDefaultRules = ").AppendLine("$true").AppendLine();
138+
}
139+
if (settingsData.RecurseCustomRulePath)
140+
{
141+
sb.Append(indent).Append("RecurseCustomRulePath = ").AppendLine("$true").AppendLine();
142+
}
143+
144+
// Rules block
145+
if (settingsData.RuleArguments != null && settingsData.RuleArguments.Count > 0)
146+
{
147+
sb.Append(indent).AppendLine("Rules = @{");
148+
foreach (var ruleKvp in settingsData.RuleArguments)
149+
{
150+
sb.Append(indent).Append(indent).Append(ruleKvp.Key).Append(" = @{").AppendLine();
151+
if (ruleKvp.Value != null && ruleKvp.Value.Count > 0)
152+
{
153+
foreach (var argKvp in ruleKvp.Value)
154+
{
155+
sb.Append(indent).Append(indent).Append(indent)
156+
.Append(argKvp.Key).Append(" = ")
157+
.AppendLine(FormatScalar(argKvp.Value));
158+
}
159+
}
160+
sb.Append(indent).Append(indent).AppendLine("}").AppendLine();
161+
}
162+
sb.Append(indent).AppendLine("}");
163+
}
164+
165+
sb.AppendLine("}");
166+
167+
return sb.ToString();
168+
}
169+
}
83170
}

Engine/Settings/Settings.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,15 @@ private static SettingsData ParseFile(string path)
288288
return data;
289289
}
290290

291+
public static string Serialise(SettingsData data, string format)
292+
{
293+
// Check each parser to see if the format matches
294+
// and use it to serialize the data
295+
var parser = Array.Find(s_parsers, p => string.Equals(p.FormatName, format, StringComparison.OrdinalIgnoreCase)) ??
296+
throw new NotSupportedException($"No parser registered for format '{format}'.");
297+
return parser.Serialise(data);
298+
}
299+
291300
/// <summary>
292301
/// Retrieves the Settings directory from the Module directory structure
293302
/// </summary>

0 commit comments

Comments
 (0)