-
-
Notifications
You must be signed in to change notification settings - Fork 986
/
Copy pathPowerRequestsParser.cs
187 lines (174 loc) · 5.92 KB
/
PowerRequestsParser.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
using System;
using System.Collections.Generic;
using System.IO;
namespace BenchmarkDotNet.IntegrationTests;
/// <summary>
/// Parses the output of 'powercfg /requests' command into a list of <see cref="PowerRequest"/>s.
/// </summary>
/// <remarks>
/// <para>
/// Not using <see cref="https://github.com/sprache/Sprache">Sprache</see>. It is superseded by Superpower.
/// Not using <see cref="https://github.com/datalust/superpower">Superpower</see>. I gained more knowledge
/// implementing this class from scratch.
/// </para>
/// <para>Example input:</para>
/// <code>
/// DISPLAY:
/// [PROCESS] \Device\HarddiskVolume3\Program Files (x86)\Google\Chrome\Application\chrome.exe
/// Video Wake Lock
///
/// SYSTEM:
/// [DRIVER] Realtek High Definition Audio(SST) ...
/// Er wordt momenteel een audiostream gebruikt.
/// [PROCESS] \Device\HarddiskVolume3\...\NoSleep.exe
/// [PROCESS] \Device\HarddiskVolume3\Program Files (x86)\Google\Chrome\Application\chrome.exe
/// Video Wake Lock
///
/// AWAYMODE:
/// None.
///
/// EXECUTION:
/// [PROCESS] \Device\HarddiskVolume3\Program Files (x86)\Google\Chrome\Application\chrome.exe
/// Playing audio
///
/// PERFBOOST:
/// None.
///
/// ACTIVELOCKSCREEN:
/// None.
///
/// </code>
/// </remarks>
internal class PowerRequestsParser
{
/// <summary>
/// Parses output of 'powercfg /requests' into a list of <see cref="PowerRequest"/>s.
/// </summary>
/// <remarks>
/// <para>
/// This method takes a list of <see cref="Token"/>s. Examines next token and decides how to
/// parse.
/// </para>
/// </remarks>
/// <param name="input">Output of 'powercfg /requests'.</param>
public static IEnumerable<PowerRequest> Parse(string input)
{
using TokenStream tokens = new TokenStream(Tokens(input));
while (tokens.TryPeek().HasValue)
{
foreach (PowerRequest item in ParseRequestType(tokens))
{
yield return item;
}
}
}
private static IEnumerable<PowerRequest> ParseRequestType(TokenStream tokens)
{
Token requestType = tokens.Take(TokenType.RequestType);
if (tokens.Peek().TokenType == TokenType.RequesterType)
{
while (tokens.Peek().TokenType == TokenType.RequesterType)
{
yield return ParseRequesterType(requestType, tokens);
}
}
else
{
_ = tokens.Take(TokenType.None);
}
_ = tokens.Take(TokenType.EmptyLine);
}
private static PowerRequest ParseRequesterType(Token requestType, TokenStream tokens)
{
Token requesterType = tokens.Take(TokenType.RequesterType);
Token requesterName = tokens.Take(TokenType.RequesterName);
Token? reason = null;
if (tokens.Peek().TokenType == TokenType.Reason)
{
reason = tokens.Take(TokenType.Reason);
}
return new PowerRequest(requestType.Value, requesterType.Value, requesterName.Value, reason?.Value);
}
/// <summary>
/// Converts the input into a list of <see cref="Toden"/>s.
/// </summary>
/// <remarks>
/// <para>
/// Looking at above sample, tokenizing is made simple when done line by line. Each line
/// contains one or two <see cref="Token"/>s.
/// </para>
/// </remarks>
/// <param name="input">Output of 'powercfg /requests'.</param>
private static IEnumerable<Token> Tokens(string input)
{
// Contrary to calling input.Split('\r', '\n'), StringReader's ReadLine method does not
// return an empty string when CR is followed by LF.
StringReader reader = new StringReader(input);
string? line;
while ((line = reader.ReadLine()) != null)
{
if (line.Length == 0)
{
yield return new Token(TokenType.EmptyLine, "");
}
else if (line[line.Length - 1] == ':')
{
yield return new Token(TokenType.RequestType, line.Substring(0, line.Length - 1).ToString());
}
else if (string.Equals(line, "None.", StringComparison.InvariantCulture))
{
yield return new Token(TokenType.None, line);
}
else if (line[0] == '[')
{
int pos = line.IndexOf(']');
yield return new Token(TokenType.RequesterType, line.Substring(1, pos - 1));
yield return new Token(TokenType.RequesterName, line.Substring(pos + 2));
}
else
{
yield return new Token(TokenType.Reason, line);
}
}
}
/// <summary>
/// Adds <see cref="Peek"/> and <see cref="TryPeek"/> to an <see cref="IEnumerable{T}"/> of
/// <see cref="Token"/>s.
/// </summary>
/// <param name="tokens"></param>
private class TokenStream(IEnumerable<Token> tokens) : IDisposable
{
private readonly IEnumerator<Token> tokens = tokens.GetEnumerator();
private Token? cached;
public Token? TryPeek() => cached ??= tokens.MoveNext() ? tokens.Current : null;
public Token Peek() => TryPeek() ?? throw new EndOfStreamException();
public Token Take(TokenType requestType)
{
Token peek = Peek();
if (peek.TokenType == requestType)
{
cached = null;
return peek;
}
else
{
throw new InvalidCastException($"Unexpected Token of type '{peek.TokenType}'. Expected type '{requestType}'.");
}
}
public void Dispose() => tokens.Dispose();
}
private enum TokenType
{
EmptyLine,
None,
Reason,
RequesterName,
RequesterType,
RequestType
}
private readonly struct Token(TokenType tokenType, string value)
{
public TokenType TokenType { get; } = tokenType;
public string Value { get; } = value;
}
}