Skip to content

Commit 5514095

Browse files
committed
Added Channel to generator so loading and generating run in parallel
1 parent 950a851 commit 5514095

File tree

3 files changed

+89
-96
lines changed

3 files changed

+89
-96
lines changed

Generator/CodeGenerator.cs

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,20 @@
11
using System.Text;
2+
using System.Threading.Channels;
23
namespace System.Management.Generator;
34

4-
public class CodeGenerator
5+
public class CodeGenerator(ChannelReader<ClassDefinition> channel)
56
{
67
private static readonly HashSet<string> _excludedFolders = new(StringComparer.OrdinalIgnoreCase) { "bin", "obj" };
78

8-
private static DirectoryInfo FindTypesDirectory()
9-
{
10-
var dir = new DirectoryInfo(Environment.CurrentDirectory);
11-
while (dir != null)
12-
{
13-
var typesDir = new DirectoryInfo(Path.Combine(dir.FullName, "Types"));
14-
if (typesDir.Exists)
15-
return typesDir;
16-
dir = dir.Parent;
17-
}
18-
throw new DirectoryNotFoundException("Could not find 'Types' directory in any parent directory.");
19-
}
9+
private readonly DirectoryInfo _targetDirectory = FindTypesDirectory();;
10+
private readonly Dictionary<string, ClassDefinition> _classDefinitions = new(StringComparer.OrdinalIgnoreCase);
2011

21-
private readonly Dictionary<string, ClassDefinition> _classDefinitions;
22-
private readonly DirectoryInfo _targetDirectory;
23-
24-
public CodeGenerator(IEnumerable<ClassDefinition> classDefinitions)
25-
{
26-
_classDefinitions = classDefinitions.ToDictionary(t => t.ClassName, t => t, StringComparer.InvariantCultureIgnoreCase);
27-
_targetDirectory = FindTypesDirectory();
28-
}
29-
30-
public void GenerateCode()
12+
public async Task GenerateCode()
3113
{
3214
var existingFiles = _targetDirectory.GetDirectories().Where(d => !_excludedFolders.Contains(d.Name)).SelectMany(d => d.GetFiles("*.g.cs", SearchOption.AllDirectories)).Select(fi => fi.FullName).ToHashSet(StringComparer.OrdinalIgnoreCase);
33-
foreach (var typeDefinition in _classDefinitions.Values)
15+
await foreach (var typeDefinition in channel.ReadAllAsync())
3416
{
17+
_classDefinitions[typeDefinition.ClassName] = typeDefinition;
3518
Console.WriteLine($"Generating class for {typeDefinition.ClassName}.");
3619
(var namespaceName, var className) = ParseClassName(typeDefinition.ClassName);
3720

@@ -92,6 +75,19 @@ public void GenerateCode()
9275
}
9376
}
9477

78+
private static DirectoryInfo FindTypesDirectory()
79+
{
80+
var dir = new DirectoryInfo(Environment.CurrentDirectory);
81+
while (dir != null)
82+
{
83+
var typesDir = new DirectoryInfo(Path.Combine(dir.FullName, "Types"));
84+
if (typesDir.Exists)
85+
return typesDir;
86+
dir = dir.Parent;
87+
}
88+
throw new DirectoryNotFoundException("Could not find 'Types' directory in any parent directory.");
89+
}
90+
9591
private IEnumerable<PropertyDefinition> GetInheritedPropertiesFrom(string? superClass)
9692
=> superClass != null && _classDefinitions.TryGetValue(superClass, out var td) ? td.Properties.Concat(GetInheritedPropertiesFrom(td.SuperClass)) : [];
9793

Generator/DefinitionLoader.cs

Lines changed: 63 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using System.Diagnostics.CodeAnalysis;
22
using System.Text.RegularExpressions;
3+
using System.Threading.Channels;
34
namespace System.Management.Generator;
45

5-
internal partial class DefinitionLoader(IEnumerable<string> classNames)
6+
internal partial class DefinitionLoader(ChannelWriter<ClassDefinition> channel)
67
{
78

89
[GeneratedRegex(@"<code class=""lang-syntax"">([^<]*)</code>")]
@@ -33,68 +34,56 @@ internal partial class DefinitionLoader(IEnumerable<string> classNames)
3334
private static partial Regex GetLinkRegex();
3435
private static readonly Regex LinkRegex = GetLinkRegex();
3536

36-
private readonly Dictionary<string, ClassDefinition> _classDefinitions = classNames.ToDictionary(n => n, n => default(ClassDefinition), StringComparer.OrdinalIgnoreCase);
37-
public IEnumerable<ClassDefinition> LoadedClassDefinitions => _classDefinitions.Values;
37+
private readonly Dictionary<string, string> _typeMismatches = new() { ["Win32_LogicalElement"] = "CIM_LogicalElement" };
38+
private readonly HashSet<string> _loadedClassDefinitions = new(StringComparer.OrdinalIgnoreCase);
3839

39-
private string? CheckClass(string? className)
40+
public async Task Load(IEnumerable<string> classesToLoad)
4041
{
41-
if (className == null)
42+
foreach (var className in classesToLoad)
4243
{
43-
return null;
44+
await TryLoadClass(className);
4445
}
4546

46-
if ("Win32_LogicalElement".Equals(className))
47+
channel.Complete();
48+
}
49+
50+
private async Task<string?> TryLoadClass(string? className)
51+
{
52+
if (className == null)
4753
{
48-
return CheckClass("CIM_LogicalElement");
54+
return null;
4955
}
5056

51-
if (!_classDefinitions.ContainsKey(className))
57+
if (_typeMismatches.TryGetValue(className, out var correctClassName))
5258
{
53-
if (className.IndexOf('_') == -1)
54-
{
55-
return CheckClass($"__{className}");
56-
}
57-
58-
_classDefinitions.Add(className, default);
59-
Console.WriteLine($"Met new class {className}.");
59+
return await TryLoadClass(correctClassName);
6060
}
61-
return className;
62-
}
6361

64-
public async Task Load()
65-
{
66-
foreach (var className in GetUnloadedClassNames())
62+
if (_loadedClassDefinitions.Add(className))
6763
{
6864
if (await LoadClassDefinition(className) is ClassDefinition classDefinition)
6965
{
70-
_classDefinitions[className] = classDefinition;
66+
Console.WriteLine($"Loaded info for {className}:");
67+
await channel.WriteAsync(classDefinition);
68+
return className;
7169
}
72-
else
70+
else if (className.IndexOf('_') == -1)
7371
{
74-
_classDefinitions.Remove(className);
72+
var prefixedClassName = $"__{className}";
73+
_typeMismatches[className] = prefixedClassName;
74+
return await TryLoadClass(prefixedClassName);
7575
}
7676
}
77-
}
7877

79-
private IEnumerable<string> GetUnloadedClassNames()
80-
{
81-
var classNames = _classDefinitions.Where(kvp => kvp.Value.ClassName == null).Select(kvp => kvp.Key).ToArray();
82-
while (classNames.Length > 0)
83-
{
84-
foreach (var className in classNames)
85-
{
86-
yield return className;
87-
}
88-
classNames = _classDefinitions.Where(kvp => kvp.Value.ClassName == null).Select(kvp => kvp.Key).ToArray();
89-
}
78+
return null;
9079
}
9180

9281
private async Task<ClassDefinition?> LoadClassDefinition(string className)
9382
{
94-
Console.WriteLine($"Getting info for {className}:");
83+
Console.WriteLine($"Loading info for {className}:");
9584
Uri? classUri = null;
9685
Uri[] uris = className[0] == '_'
97-
? [new Uri($"https://learn.microsoft.com/en-gb/windows/win32/wmisdk/{className.Replace('_', '-')}")]
86+
? [new Uri($"https://learn.microsoft.com/en-us/windows/win32/wmisdk/{className.Replace('_', '-')}")]
9887
: [new Uri($"https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/{className.Replace('_', '-')}"),
9988
new Uri($"https://learn.microsoft.com/en-us/previous-versions/windows/desktop/secrcw32prov//{className.Replace('_', '-')}")];
10089

@@ -111,15 +100,14 @@ private IEnumerable<string> GetUnloadedClassNames()
111100
{
112101
if (i + 1 == uris.Length)
113102
{
114-
ErrorReporter.Report($"Unable to find Microsoft Learn page to parse for {className}: {ex.Message}", ex);
103+
ErrorReporter.Report($"Unable to find Microsoft Learn page to parse for {className}: {ex.Message}", ex, throwOrBreak: false);
115104
break;
116105
}
117106
}
118107
}
119108

120109
if (classUri == null || pageContents == null)
121110
{
122-
ErrorReporter.Report($"Failed to load {className}.");
123111
return null;
124112
}
125113

@@ -151,14 +139,14 @@ private IEnumerable<string> GetUnloadedClassNames()
151139
ErrorReporter.Report($"Encounterd different type {classDef[1]} when parsing data for {className}.");
152140
}
153141

154-
superClass = CheckClass(classDef.Length > 2 ? classDef[^1] : null);
142+
superClass = await TryLoadClass(classDef.Length > 2 ? classDef[^1] : null);
155143

156144
break;
157145
}
158146

159147
var propertyBlock = pageContents.IndexOf("<h3 id=\"properties\"");
160148
var endBlock = pageContents.IndexOf("<h3", propertyBlock + 1);
161-
var properties = propertyBlock == -1 ? [] : ParseProperties(codeLines[(lineIndex + 2)..^1], pageContents[propertyBlock..endBlock]).ToList();
149+
var properties = propertyBlock == -1 ? [] : await ParseProperties(codeLines[(lineIndex + 2)..^1], pageContents[propertyBlock..endBlock]);
162150

163151
var methodBlock = pageContents.IndexOf("<h3 id=\"methods\"");
164152
endBlock = pageContents.IndexOf("<h3", methodBlock + 1);
@@ -177,31 +165,37 @@ private static async Task<string> GetPageContentsAsync(Uri url)
177165
return await response.Content.ReadAsStringAsync();
178166
}
179167

180-
private IEnumerable<PropertyDefinition> ParseProperties(string[] propertyLines, string propertiesBlock)
168+
private async Task<List<PropertyDefinition>> ParseProperties(string[] propertyLines, string propertiesBlock)
181169
{
182-
foreach (var property in propertyLines.Select(ParsePropertyLine).OfType<PropertyDefinition>())
170+
var result = new List<PropertyDefinition>(propertyLines.Length);
171+
foreach (var propertyLine in propertyLines)
183172
{
184-
if (TryGetPropertyV1(property.Name, propertiesBlock, out var propertyBlock))
173+
if (await ParsePropertyLine(propertyLine) is PropertyDefinition property)
185174
{
186-
if (PropertyIsInherited(propertyBlock))
175+
if (TryGetPropertyV1(property.Name, propertiesBlock, out var propertyBlock))
187176
{
188-
continue;
177+
if (PropertyIsInherited(propertyBlock))
178+
{
179+
continue;
180+
}
181+
result.Add(await UpdatePropertyV1(property, propertyBlock));
189182
}
190-
yield return UpdatePropertyV1(property, propertyBlock);
191-
}
192-
else if (TryGetPropertyV2(property.Name, propertiesBlock, out propertyBlock))
193-
{
194-
if (PropertyIsInherited(propertyBlock))
183+
else if (TryGetPropertyV2(property.Name, propertiesBlock, out propertyBlock))
195184
{
196-
continue;
185+
if (PropertyIsInherited(propertyBlock))
186+
{
187+
continue;
188+
}
189+
result.Add(await UpdatePropertyV2(property, propertyBlock));
190+
}
191+
else
192+
{
193+
ErrorReporter.Report($"No description found for property {property.Name}.", throwOrBreak: false);
197194
}
198-
yield return UpdatePropertyV2(property, propertyBlock);
199-
}
200-
else
201-
{
202-
ErrorReporter.Report($"No description found for property {property.Name}.", throwOrBreak: false);
203195
}
204196
}
197+
198+
return result;
205199
}
206200

207201
private static bool TryGetPropertyV1(string propertyName, string propertiesBlock, [MaybeNullWhen(false)]out string propertyBlock)
@@ -235,11 +229,12 @@ private static bool TryGetPropertyV2(string propertyName, string propertiesBlock
235229
private static bool PropertyIsInherited(string propertyBlock)
236230
=> propertyBlock.Contains("This property is inherited from");
237231

238-
private PropertyDefinition UpdatePropertyV1(PropertyDefinition property, string propertyBlock)
232+
private async Task<PropertyDefinition> UpdatePropertyV1(PropertyDefinition property, string propertyBlock)
239233
{
240234
foreach (var paragraph in ParagraphRegex.Matches(propertyBlock).Select(TrimHTML))
241235
{
242-
if (!ParseSubProperty(ref property, paragraph))
236+
(var parsed, property) = await ParseSubProperty(property, paragraph);
237+
if (!parsed)
243238
{
244239
property = property with { Description = paragraph };
245240
break;
@@ -249,11 +244,11 @@ private PropertyDefinition UpdatePropertyV1(PropertyDefinition property, string
249244
return property;
250245
}
251246

252-
private PropertyDefinition UpdatePropertyV2(PropertyDefinition property, string propertyBlock)
247+
private async Task<PropertyDefinition> UpdatePropertyV2(PropertyDefinition property, string propertyBlock)
253248
{
254249
foreach (var propertyDescription in PropertyRegex.Matches(propertyBlock).Select(TrimHTML))
255250
{
256-
ParseSubProperty(ref property, propertyDescription);
251+
(_, property) = await ParseSubProperty(property, propertyDescription);
257252
}
258253

259254
if (TrimHTML(DescriptionRegex.Match(propertyBlock)) is string description && description.Length > 0)
@@ -264,12 +259,12 @@ private PropertyDefinition UpdatePropertyV2(PropertyDefinition property, string
264259
return property;
265260
}
266261

267-
private bool ParseSubProperty(ref PropertyDefinition property, string paragraph)
262+
private async Task<(bool, PropertyDefinition)> ParseSubProperty(PropertyDefinition property, string paragraph)
268263
{
269264
var colonIndex = paragraph.IndexOf(':');
270265
if (colonIndex == -1)
271266
{
272-
return false;
267+
return (false, property);
273268
}
274269

275270
switch (paragraph[..colonIndex])
@@ -289,7 +284,7 @@ private bool ParseSubProperty(ref PropertyDefinition property, string paragraph)
289284
}
290285
else if (typeName.Contains('_'))
291286
{
292-
property = property with { Type = CimType.Reference, ReferencedClass = CheckClass(typeName) };
287+
property = property with { Type = CimType.Reference, ReferencedClass = await TryLoadClass(typeName) };
293288
}
294289
else
295290
{
@@ -314,7 +309,7 @@ private bool ParseSubProperty(ref PropertyDefinition property, string paragraph)
314309
property = property with { Qualifiers = ParseQualifiers(paragraph[(colonIndex + 2)..]) };
315310
break;
316311
}
317-
return true;
312+
return (true, property);
318313
}
319314

320315
private static List<QualifierDefinition> ParseQualifiers(string qualifiers)
@@ -334,7 +329,7 @@ private static string TrimHTML(string source)
334329
.Replace("&nbsp;", " ")
335330
.Trim();
336331

337-
private PropertyDefinition? ParsePropertyLine(string propertyLine)
332+
private async Task<PropertyDefinition?> ParsePropertyLine(string propertyLine)
338333
{
339334
var parts = TrimHTML(propertyLine).Split(' ', options: StringSplitOptions.RemoveEmptyEntries);
340335

@@ -347,7 +342,7 @@ private static string TrimHTML(string source)
347342
if (!Enum.TryParse(parts[0], ignoreCase: true, out CimType type) && parts[0].Contains('_'))
348343
{
349344
type = CimType.Reference;
350-
referenceType = CheckClass(parts[0]);
345+
referenceType = await TryLoadClass(parts[0]);
351346
}
352347

353348
var name = parts[^2][0] == '=' ? parts[^3] : parts[^1];

Generator/Program.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// See https://aka.ms/new-console-template for more information
22
using System.Management.Generator;
3+
using System.Threading.Channels;
34

45
List<string> classes =
56
[
@@ -80,7 +81,8 @@
8081
"Win32_MethodParameterClass", "Win32_WMIElementSetting", "Win32_WMISetting",
8182
];
8283

83-
var loader = new DefinitionLoader(classes);
84-
await loader.Load();
85-
var generator = new CodeGenerator(loader.LoadedClassDefinitions);
86-
generator.GenerateCode();
84+
var channel = Channel.CreateUnbounded<ClassDefinition>();
85+
86+
var loader = new DefinitionLoader(channel.Writer);
87+
var generator = new CodeGenerator(channel.Reader);
88+
await Task.WhenAll(loader.Load(classes), generator.GenerateCode());

0 commit comments

Comments
 (0)