Skip to content

Commit 7d994ad

Browse files
committed
Tag, TagList, TagListParser
A lot of things in MM are structured like :tag[value]trailer and this formalizes that structure
1 parent 9998491 commit 7d994ad

8 files changed

Lines changed: 672 additions & 0 deletions

File tree

ModuleManager/ModuleManager.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@
7575
<Compile Include="Properties\Resources.Designer.cs" />
7676
<Compile Include="Progress\IPatchProgress.cs" />
7777
<Compile Include="Progress\PatchProgress.cs" />
78+
<Compile Include="Tags\Tag.cs" />
79+
<Compile Include="Tags\TagList.cs" />
80+
<Compile Include="Tags\TagListParser.cs" />
7881
<Compile Include="Threading\ITaskStatus.cs" />
7982
<Compile Include="Threading\TaskStatus.cs" />
8083
<Compile Include="Threading\TaskStatusWrapper.cs" />

ModuleManager/Tags/Tag.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
3+
namespace ModuleManager.Tags
4+
{
5+
public struct Tag
6+
{
7+
public readonly string key;
8+
public readonly string value;
9+
public readonly string trailer;
10+
11+
public Tag(string key, string value, string trailer)
12+
{
13+
this.key = key ?? throw new ArgumentNullException(nameof(key));
14+
if (key == string.Empty) throw new ArgumentException("can't be empty", nameof(key));
15+
16+
if (value == null && trailer != null)
17+
throw new ArgumentException("trailer must be null if value is null");
18+
19+
if (trailer == string.Empty) throw new ArgumentException("can't be empty (null allowed)", nameof(trailer));
20+
21+
this.value = value;
22+
this.trailer = trailer;
23+
}
24+
25+
public override string ToString()
26+
{
27+
string s = "< '" + key + "' ";
28+
if (value != null) s += "[ '" + value + "' ] ";
29+
if (trailer != null) s += "'" + trailer + "' ";
30+
s += ">";
31+
return s;
32+
}
33+
}
34+
}

ModuleManager/Tags/TagList.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using ModuleManager.Collections;
6+
7+
namespace ModuleManager.Tags
8+
{
9+
public interface ITagList : IEnumerable<Tag>
10+
{
11+
Tag PrimaryTag { get; }
12+
}
13+
14+
public class TagList : ITagList
15+
{
16+
private readonly Tag[] tags;
17+
18+
public TagList(Tag primaryTag, IEnumerable<Tag> tags)
19+
{
20+
PrimaryTag = primaryTag;
21+
this.tags = tags?.ToArray() ?? throw new ArgumentNullException(nameof(tags));
22+
}
23+
24+
public Tag PrimaryTag { get; private set; }
25+
26+
public ArrayEnumerator<Tag> GetEnumerator() => new ArrayEnumerator<Tag>(tags);
27+
IEnumerator<Tag> IEnumerable<Tag>.GetEnumerator() => GetEnumerator();
28+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
29+
}
30+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace ModuleManager.Tags
5+
{
6+
public interface ITagListParser
7+
{
8+
ITagList Parse(string ToParse);
9+
}
10+
11+
public class TagListParser : ITagListParser
12+
{
13+
public ITagList Parse(string toParse)
14+
{
15+
if (toParse == null) throw new ArgumentNullException(nameof(toParse));
16+
if (toParse.Length == 0) throw new FormatException("can't create tag list from empty string");
17+
if (toParse[0] == '[') throw new FormatException("can't create tag list beginning with [");
18+
if (toParse[0] == ':') throw new FormatException("can't create tag list beginning with :");
19+
if (toParse[toParse.Length - 1] == ':') throw new FormatException("tag list can't end with :");
20+
21+
List<Tag> tags = new List<Tag>();
22+
Tag primaryTag = ParsePrimaryTag(toParse, ref tags);
23+
return new TagList(primaryTag, tags);
24+
}
25+
26+
private static Tag ParsePrimaryTag(string toParse, ref List<Tag> tags)
27+
{
28+
for (int i = 1; i < toParse.Length; i++)
29+
{
30+
char c = toParse[i];
31+
32+
if (c == '[')
33+
{
34+
int j = ClosingBracketIndex(toParse, i + 1);
35+
return ParsePrimaryTrailer(toParse, j + 1, ref tags, toParse.Substring(0, i), toParse.Substring(i + 1, j - i - 1));
36+
}
37+
else if (c == ':')
38+
{
39+
ParseTag(toParse, i + 1, ref tags);
40+
return new Tag(toParse.Substring(0, i), null, null);
41+
}
42+
else if (c == ']')
43+
{
44+
throw new FormatException("encountered closing bracket in primary key");
45+
}
46+
}
47+
48+
return new Tag(toParse, null, null);
49+
}
50+
51+
private static Tag ParsePrimaryTrailer(string toParse, int start, ref List<Tag> tags, string primaryKey, string primaryValue)
52+
{
53+
for (int i = start; i < toParse.Length; i++)
54+
{
55+
char c = toParse[i];
56+
57+
if (c == ':')
58+
{
59+
string trailer = i == start ? null : toParse.Substring(start, i - start);
60+
ParseTag(toParse, i + 1, ref tags);
61+
return new Tag(primaryKey, primaryValue, trailer);
62+
}
63+
else if (c == '[')
64+
{
65+
throw new FormatException("encountered opening bracket in primary trailer");
66+
}
67+
else if (c == ']')
68+
{
69+
throw new FormatException("encountered closing bracket in primary trailer");
70+
}
71+
}
72+
73+
string primaryTrailer = toParse.Length - start == 0 ? null : toParse.Substring(start);
74+
return new Tag(primaryKey, primaryValue, primaryTrailer);
75+
}
76+
77+
private static void ParseTag(string toParse, int start, ref List<Tag> tags)
78+
{
79+
for (int i = start; i < toParse.Length; i++)
80+
{
81+
char c = toParse[i];
82+
83+
if (c == '[')
84+
{
85+
if (i == start)
86+
throw new FormatException("tag can't start with [");
87+
88+
int j = ClosingBracketIndex(toParse, i + 1);
89+
ParseTrailer(toParse, j + 1, ref tags, toParse.Substring(start, i - start), toParse.Substring(i + 1, j - i - 1));
90+
return;
91+
}
92+
else if (c == ':')
93+
{
94+
if (i == start)
95+
throw new FormatException("tag can't start with :");
96+
97+
tags.Add(new Tag(toParse.Substring(start, i - start), null, null));
98+
ParseTag(toParse, i + 1, ref tags);
99+
return;
100+
}
101+
else if (c == ']')
102+
{
103+
throw new FormatException("encountered closing bracket in key");
104+
}
105+
}
106+
107+
tags.Add(new Tag(toParse.Substring(start), null, null));
108+
}
109+
110+
private static void ParseTrailer(string toParse, int start, ref List<Tag> tags, string key, string value)
111+
{
112+
for (int i = start; i < toParse.Length; i++)
113+
{
114+
char c = toParse[i];
115+
116+
if (c == ':')
117+
{
118+
string trailer = i == start ? null : toParse.Substring(start, i - start);
119+
tags.Add(new Tag(key, value, trailer));
120+
ParseTag(toParse, i + 1, ref tags);
121+
return;
122+
}
123+
else if (c == '[')
124+
{
125+
throw new FormatException("encountered opening bracket in trailer");
126+
}
127+
else if (c == ']')
128+
{
129+
throw new FormatException("encountered closing bracket in trailer");
130+
}
131+
}
132+
133+
string finalTrailer = toParse.Length - start == 0 ? null : toParse.Substring(start);
134+
tags.Add(new Tag(key, value, finalTrailer));
135+
}
136+
137+
private static int ClosingBracketIndex(string toParse, int start)
138+
{
139+
int bracketLevel = 0;
140+
141+
for (int i = start; i < toParse.Length; i++)
142+
{
143+
char c = toParse[i];
144+
145+
if (c == '[')
146+
{
147+
bracketLevel++;
148+
}
149+
else if (c == ']')
150+
{
151+
bracketLevel--;
152+
}
153+
154+
if (bracketLevel == -1) return i;
155+
}
156+
157+
throw new FormatException("reached end of the tag list without encountering a close bracket");
158+
}
159+
}
160+
}

ModuleManagerTests/ModuleManagerTests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@
7979
<Compile Include="PatchExtractorTest.cs" />
8080
<Compile Include="Properties\AssemblyInfo.cs" />
8181
<Compile Include="Progress\PatchProgressTest.cs" />
82+
<Compile Include="Tags\TagListParserTest.cs" />
83+
<Compile Include="Tags\TagListTest.cs" />
84+
<Compile Include="Tags\TagTest.cs" />
8285
<Compile Include="Threading\TaskStatusTest.cs" />
8386
<Compile Include="Threading\BackgroundTaskTest.cs" />
8487
<Compile Include="Utils\CounterTest.cs" />

0 commit comments

Comments
 (0)