-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
MSBuildPropertyParser.cs
132 lines (118 loc) · 3.78 KB
/
MSBuildPropertyParser.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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable enable
namespace Microsoft.DotNet.Cli.Utils;
/// <summary>
/// Parses property key value pairs that have already been forwarded through the PropertiesOption class.
/// Does not parse -p and etc. formats, (this is done by PropertiesOption) but does parse property values separated by =, ;, and using quotes.
/// </summary>
public static class MSBuildPropertyParser
{
public static IEnumerable<(string key, string value)> ParseProperties(string input)
{
var currentPos = 0;
StringBuilder currentKey = new();
StringBuilder currentValue = new();
(string key, string value) EmitAndReset()
{
var key = currentKey.ToString();
var value = currentValue.ToString();
currentKey.Clear();
currentValue.Clear();
return (key, value);
}
char? Peek() => currentPos < input.Length ? input[currentPos] : null;
bool TryConsume(out char? consumed)
{
if (input.Length > currentPos)
{
consumed = input[currentPos];
currentPos++;
return true;
}
else
{
consumed = null;
return false;
}
}
void ParseKey()
{
while (TryConsume(out var c) && c != '=')
{
currentKey.Append(c);
}
}
void ParseQuotedValue()
{
TryConsume(out var leadingQuote); // consume the leading quote, which we know is there
currentValue.Append(leadingQuote);
while (TryConsume(out char? c))
{
currentValue.Append(c);
if (c == '"')
{
// we're done
return;
}
if (c == '\\' && Peek() == '"')
{
// consume the escaped quote
TryConsume(out var c2);
currentValue.Append(c2);
}
}
}
void ParseUnquotedValue()
{
while (TryConsume(out char? c) && c != ';')
{
currentValue.Append(c);
}
// we're either at the end or
if (AtEnd()) return;
// we're just past a semicolon
// if semicolon, we need to check if there are any other = in the string (signifying property pairs)
if (input.IndexOf('=', currentPos) != -1)
{
// there are more = in the string, so eject and let a new key/value pair be parsed
return;
}
else
{
currentValue.Append(';');
// there are no more = in the string, so consume the remainder of the string
while (TryConsume(out char? c))
{
currentValue.Append(c);
}
}
}
void ParseValue()
{
if (Peek() == '"')
{
ParseQuotedValue();
}
else
{
ParseUnquotedValue();
}
}
(string key, string value) ParseKeyValue()
{
ParseKey();
ParseValue();
return EmitAndReset();
}
bool AtEnd() => currentPos == input.Length;
while (!(AtEnd()))
{
yield return ParseKeyValue();
if (Peek() is char c && (c == ';' || c == ','))
{
TryConsume(out _); // swallow the next semicolon or comma delimiter
}
}
}
}