Skip to content

Commit 8b0c5c2

Browse files
committed
refactor: small refactor and in-line docs
1 parent a9ad8c7 commit 8b0c5c2

File tree

10 files changed

+1270
-716
lines changed

10 files changed

+1270
-716
lines changed

dev/pyRevitLoader/pyRevitExtensionParser/BundleParser.cs

Lines changed: 554 additions & 360 deletions
Large diffs are not rendered by default.

dev/pyRevitLoader/pyRevitExtensionParser/ExtensionParser.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.IO;
44
using System.Linq;
55
using System.Text;
6-
using static pyRevitExtensionParser.BundleParser;
76

87
namespace pyRevitExtensionParser
98
{
@@ -150,7 +149,7 @@ private static ParsedExtension ParseExtension(string extDir)
150149

151150
var bundlePath = Path.Combine(extDir, "bundle.yaml");
152151
ParsedBundle parsedBundle = FileExists(bundlePath)
153-
? BundleYamlParser.Parse(bundlePath)
152+
? BundleParser.BundleYamlParser.Parse(bundlePath)
154153
: null;
155154

156155
// Read extension config from pyRevit config file (cached)
@@ -399,7 +398,7 @@ private static List<ParsedComponent> ParseComponents(
399398
}
400399

401400
// Then parse bundle and override with bundle values if they exist
402-
var bundleInComponent = FileExists(bundleFile) ? BundleYamlParser.Parse(bundleFile) : null;
401+
var bundleInComponent = FileExists(bundleFile) ? BundleParser.BundleYamlParser.Parse(bundleFile) : null;
403402

404403
// Override script values with bundle values (bundle takes precedence)
405404
if (bundleInComponent != null)

dev/pyRevitLoader/pyRevitExtensionParser/IniFile.cs

Lines changed: 172 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,92 @@
66

77
namespace pyRevitExtensionParser
88
{
9+
/// <summary>
10+
/// Provides functionality for reading and writing INI configuration files using Windows API.
11+
/// Supports both standard key-value pairs and Python-style list values.
12+
/// </summary>
913
internal class IniFile
1014
{
15+
/// <summary>
16+
/// The full path to the INI file.
17+
/// </summary>
1118
private readonly string _path;
1219

20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="IniFile"/> class.
22+
/// </summary>
23+
/// <param name="iniPath">The full path to the INI file.</param>
1324
public IniFile(string iniPath)
1425
{
1526
_path = iniPath;
1627
}
1728

29+
/// <summary>
30+
/// Writes a value to an INI file using the Windows API.
31+
/// </summary>
32+
/// <param name="section">The section name in the INI file.</param>
33+
/// <param name="key">The key name within the section.</param>
34+
/// <param name="val">The value to write.</param>
35+
/// <param name="filePath">The path to the INI file.</param>
36+
/// <returns>Non-zero if successful; otherwise, zero.</returns>
1837
[DllImport("kernel32")]
1938
private static extern long WritePrivateProfileString(string section, string key, string val, string filePath);
2039

40+
/// <summary>
41+
/// Retrieves a value from an INI file using the Windows API.
42+
/// </summary>
43+
/// <param name="section">The section name in the INI file. Pass null to retrieve all section names.</param>
44+
/// <param name="key">The key name within the section. Pass null to retrieve all keys in a section.</param>
45+
/// <param name="def">The default value to return if the key is not found.</param>
46+
/// <param name="retVal">A StringBuilder to receive the retrieved value.</param>
47+
/// <param name="size">The size of the buffer (StringBuilder capacity).</param>
48+
/// <param name="filePath">The path to the INI file.</param>
49+
/// <returns>The number of characters copied to the buffer, excluding the null terminator.</returns>
2150
[DllImport("kernel32")]
2251
private static extern int GetPrivateProfileString(string section, string key, string def, StringBuilder retVal, int size, string filePath);
2352

53+
/// <summary>
54+
/// Reads a value from the INI file for the specified section and key.
55+
/// </summary>
56+
/// <param name="section">The section name to read from.</param>
57+
/// <param name="key">The key name within the section.</param>
58+
/// <returns>The value associated with the key, or an empty string if not found.</returns>
2459
public string IniReadValue(string section, string key)
2560
{
26-
var sb = new StringBuilder(512);
61+
// Increased capacity for larger ini values (like Python lists)
62+
var sb = new StringBuilder(2048);
2763
GetPrivateProfileString(section, key, "", sb, sb.Capacity, _path);
2864
return sb.ToString();
2965
}
3066

67+
/// <summary>
68+
/// Writes a value to the INI file for the specified section and key.
69+
/// </summary>
70+
/// <param name="section">The section name to write to.</param>
71+
/// <param name="key">The key name within the section.</param>
72+
/// <param name="value">The value to write.</param>
3173
public void IniWriteValue(string section, string key, string value)
3274
{
3375
WritePrivateProfileString(section, key, value, _path);
3476
}
3577

78+
/// <summary>
79+
/// Retrieves all section names from the INI file.
80+
/// </summary>
81+
/// <returns>An enumerable collection of section names.</returns>
3682
public IEnumerable<string> GetSections()
3783
{
3884
var sb = new StringBuilder(2048);
3985
GetPrivateProfileString(null, null, null, sb, sb.Capacity, _path);
4086
return sb.ToString().Split(new[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
4187
}
42-
// Adds a value to the Python-like list in the .ini file
88+
/// <summary>
89+
/// Adds a value to a Python-style list stored in the INI file.
90+
/// The list is stored as a string in the format: ["value1", "value2", "value3"]
91+
/// </summary>
92+
/// <param name="section">The section name containing the list.</param>
93+
/// <param name="key">The key name for the list.</param>
94+
/// <param name="value">The value to add to the list. Duplicate values are not added.</param>
4395
public void AddValueToPythonList(string section, string key, string value)
4496
{
4597
var list = GetPythonList(section, key);
@@ -50,7 +102,12 @@ public void AddValueToPythonList(string section, string key, string value)
50102
}
51103
}
52104

53-
// Removes a value from the Python-like list in the .ini file
105+
/// <summary>
106+
/// Removes a value from a Python-style list stored in the INI file.
107+
/// </summary>
108+
/// <param name="section">The section name containing the list.</param>
109+
/// <param name="key">The key name for the list.</param>
110+
/// <param name="value">The value to remove from the list.</param>
54111
public void RemoveValueFromPythonList(string section, string key, string value)
55112
{
56113
var list = GetPythonList(section, key);
@@ -61,38 +118,143 @@ public void RemoveValueFromPythonList(string section, string key, string value)
61118
}
62119
}
63120

64-
// Reads the Python-like list from the .ini file
121+
/// <summary>
122+
/// Reads a Python-style list from the INI file and parses it into a C# List.
123+
/// Expected format: ["value1", "value2", "value3"]
124+
/// </summary>
125+
/// <param name="section">The section name containing the list.</param>
126+
/// <param name="key">The key name for the list.</param>
127+
/// <returns>A List&lt;string&gt; containing the parsed values.</returns>
65128
public List<string> GetPythonList(string section, string key)
66129
{
67130
string pythonListString = IniReadValue(section, key);
68131
return PythonListParser.Parse(pythonListString);
69132
}
70133

71-
// Saves the Python-like list to the .ini file
134+
/// <summary>
135+
/// Converts a C# List to a Python-style list string and saves it to the INI file.
136+
/// The list is stored in the format: ["value1", "value2", "value3"]
137+
/// </summary>
138+
/// <param name="section">The section name to save the list to.</param>
139+
/// <param name="key">The key name for the list.</param>
140+
/// <param name="list">The list to save.</param>
72141
public void SavePythonList(string section, string key, List<string> list)
73142
{
74143
string pythonListString = PythonListParser.ToPythonListString(list);
75144
IniWriteValue(section, key, pythonListString);
76145
}
77146
}
147+
/// <summary>
148+
/// Provides utilities for parsing and formatting Python-style list strings.
149+
/// Handles conversion between Python list format (["item1", "item2"]) and C# List&lt;string&gt;.
150+
/// Optimized for performance with compiled regex and minimal string allocations.
151+
/// </summary>
78152
public static class PythonListParser
79153
{
80-
// Parses a Python-like list string into a C# List<string>
154+
/// <summary>
155+
/// Pre-compiled regex pattern for extracting quoted values from Python-style lists.
156+
/// Pattern matches content between double quotes: "([^"]*)"
157+
/// Compiled for better performance when used repeatedly.
158+
/// </summary>
159+
private static readonly Regex QuotedValueRegex = new Regex(@"""([^""]*)""", RegexOptions.Compiled);
160+
161+
/// <summary>
162+
/// Parses a Python-style list string into a C# List&lt;string&gt;.
163+
/// Handles escaped backslashes in paths (e.g., "C:\\path\\to\\file" becomes "C:\path\to\file").
164+
/// </summary>
165+
/// <param name="pythonListString">
166+
/// The Python-style list string to parse. Expected format: ["value1", "value2", "value3"]
167+
/// Can be null, empty, or contain escaped backslashes.
168+
/// </param>
169+
/// <returns>
170+
/// A List&lt;string&gt; containing the parsed values. Returns an empty list if input is null or empty.
171+
/// Backslashes are unescaped (double backslash \\ becomes single backslash \).
172+
/// </returns>
173+
/// <example>
174+
/// Input: ["C:\\Users\\Documents", "C:\\Program Files"]
175+
/// Output: List with "C:\Users\Documents" and "C:\Program Files"
176+
/// </example>
81177
public static List<string> Parse(string pythonListString)
82178
{
179+
if (string.IsNullOrEmpty(pythonListString))
180+
return new List<string>();
181+
83182
var list = new List<string>();
84-
var matches = Regex.Matches(pythonListString, @"""([^""]*)""");
183+
var matches = QuotedValueRegex.Matches(pythonListString);
184+
185+
// Pre-allocate capacity if we know the count to avoid resizing
186+
if (matches.Count > 0)
187+
{
188+
list.Capacity = matches.Count;
189+
}
190+
85191
foreach (Match match in matches)
86192
{
87-
list.Add(match.Groups[1].Value.Replace(@"\\", @"\"));
193+
// Only replace if backslashes are present to avoid unnecessary allocations
194+
string value = match.Groups[1].Value;
195+
if (value.Contains(@"\\"))
196+
{
197+
// Unescape: convert \\ to \
198+
list.Add(value.Replace(@"\\", @"\"));
199+
}
200+
else
201+
{
202+
list.Add(value);
203+
}
88204
}
89205
return list;
90206
}
91207

92-
// Converts a C# List<string> back to a Python-like list string
208+
/// <summary>
209+
/// Converts a C# List&lt;string&gt; to a Python-style list string.
210+
/// Escapes backslashes in paths (e.g., "C:\path\to\file" becomes "C:\\path\\to\\file").
211+
/// </summary>
212+
/// <param name="list">
213+
/// The list to convert. Can be null or empty.
214+
/// </param>
215+
/// <returns>
216+
/// A Python-style list string in the format: ["value1", "value2", "value3"]
217+
/// Returns "[]" for null or empty lists.
218+
/// Backslashes are escaped (single backslash \ becomes double backslash \\).
219+
/// </returns>
220+
/// <example>
221+
/// Input: List with "C:\Users\Documents" and "C:\Program Files"
222+
/// Output: ["C:\\Users\\Documents", "C:\\Program Files"]
223+
/// </example>
93224
public static string ToPythonListString(List<string> list)
94225
{
95-
return $"[{string.Join(", ", list.ConvertAll(item => $"\"{item.Replace(@"\", @"\\")}\""))}]";
226+
if (list == null || list.Count == 0)
227+
return "[]";
228+
229+
// Estimate capacity: brackets + items + quotes + commas + spaces
230+
// Average of 20 characters per item is a reasonable estimate for file paths
231+
int estimatedCapacity = 2 + (list.Count * 20);
232+
var sb = new StringBuilder(estimatedCapacity);
233+
sb.Append('[');
234+
235+
for (int i = 0; i < list.Count; i++)
236+
{
237+
// Add comma separator after first item
238+
if (i > 0)
239+
sb.Append(", ");
240+
241+
sb.Append('"');
242+
string item = list[i];
243+
// Only replace if backslashes are present to avoid unnecessary allocations
244+
if (item.Contains(@"\"))
245+
{
246+
// Escape: convert \ to \\
247+
sb.Append(item.Replace(@"\", @"\\"));
248+
}
249+
else
250+
{
251+
sb.Append(item);
252+
}
253+
sb.Append('"');
254+
}
255+
256+
sb.Append(']');
257+
return sb.ToString();
96258
}
97259
}
98260
}

0 commit comments

Comments
 (0)