tangled
alpha
login
or
join now
benharri.org
/
ircsharp
1
fork
atom
IRC parsing, tokenization, and state handling in C#
1
fork
atom
overview
issues
pulls
pipelines
stateful in progress
benharri.org
5 years ago
6a3bfd31
616abc70
+266
-11
6 changed files
expand all
collapse all
unified
split
IrcTokens
Hostmask.cs
Line.cs
StatefulDecoder.cs
StatefulEncoder.cs
Tests
StatefulDecoderTests.cs
StatefulEncoderTests.cs
+13
-3
IrcTokens/Hostmask.cs
···
11
11
12
12
public override string ToString() => _source;
13
13
14
14
+
public override int GetHashCode() => _source.GetHashCode();
15
15
+
16
16
+
public override bool Equals(object obj)
17
17
+
{
18
18
+
if (obj == null || GetType() != obj.GetType())
19
19
+
return false;
20
20
+
21
21
+
return _source == ((Hostmask) obj)._source;
22
22
+
}
23
23
+
14
24
private readonly string _source;
15
15
-
25
25
+
16
26
public Hostmask(string source)
17
27
{
18
28
if (source == null) return;
···
30
40
{
31
41
NickName = source;
32
42
}
33
33
-
43
43
+
34
44
if (NickName.Contains('!'))
35
45
{
36
46
var userSplit = NickName.Split('!');
···
39
49
}
40
50
}
41
51
}
42
42
-
}
52
52
+
}
+16
-8
IrcTokens/Line.cs
···
15
15
public List<string> Params { get; set; }
16
16
17
17
private Hostmask _hostmask;
18
18
-
private string _rawLine;
18
18
+
private readonly string _rawLine;
19
19
20
20
-
public override string ToString() =>
21
21
-
$"Line(tags={string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}, params={string.Join(",", Params)})";
20
20
+
public override string ToString() =>
21
21
+
$"Line(source={Source}, command={Command}, tags={string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}, params={string.Join(",", Params)})";
22
22
+
23
23
+
public override int GetHashCode() => Format().GetHashCode();
24
24
+
25
25
+
public override bool Equals(object obj)
26
26
+
{
27
27
+
if (obj == null || GetType() != obj.GetType())
28
28
+
return false;
29
29
+
30
30
+
return Format() == ((Line) obj).Format();
31
31
+
}
22
32
23
33
public Hostmask Hostmask =>
24
34
_hostmask ??= new Hostmask(Source);
···
108
118
}
109
119
110
120
if (Source != null)
111
111
-
{
112
121
outs.Add($":{Source}");
113
113
-
}
114
122
115
123
outs.Add(Command);
116
124
···
122
130
foreach (var p in Params)
123
131
{
124
132
if (p.Contains(' '))
125
125
-
throw new ArgumentException("non-last parameters cannot have spaces");
133
133
+
throw new ArgumentException("non-last parameters cannot have spaces", p);
126
134
if (p.StartsWith(':'))
127
127
-
throw new ArgumentException("non-last parameters cannot start with colon");
135
135
+
throw new ArgumentException("non-last parameters cannot start with colon", p);
128
136
}
129
137
outs.AddRange(Params);
130
138
131
131
-
if (last == null || string.IsNullOrWhiteSpace(last) || last.Contains(' ') || last.StartsWith(':'))
139
139
+
if (string.IsNullOrWhiteSpace(last) || last.Contains(' ') || last.StartsWith(':'))
132
140
last = $":{last}";
133
141
outs.Add(last);
134
142
}
+33
IrcTokens/StatefulDecoder.cs
···
1
1
+
using System.Collections.Generic;
2
2
+
using System.Linq;
3
3
+
using System.Text;
4
4
+
5
5
+
namespace IrcTokens
6
6
+
{
7
7
+
public class StatefulDecoder
8
8
+
{
9
9
+
private string _buffer;
10
10
+
public EncodingInfo Encoding { get; set; }
11
11
+
public EncodingInfo Fallback { get; set; }
12
12
+
13
13
+
public string Pending => _buffer;
14
14
+
15
15
+
public void Clear()
16
16
+
{
17
17
+
_buffer = "";
18
18
+
}
19
19
+
20
20
+
public List<Line> Push(string data)
21
21
+
{
22
22
+
if (string.IsNullOrEmpty(data))
23
23
+
return null;
24
24
+
25
25
+
_buffer += data;
26
26
+
return _buffer
27
27
+
.Split('\n')
28
28
+
.Select(l => l.TrimEnd('\r'))
29
29
+
.Select(l => new Line(l))
30
30
+
.ToList();
31
31
+
}
32
32
+
}
33
33
+
}
+35
IrcTokens/StatefulEncoder.cs
···
1
1
+
using System.Collections.Generic;
2
2
+
using System.Linq;
3
3
+
using System.Text;
4
4
+
5
5
+
namespace IrcTokens
6
6
+
{
7
7
+
public class StatefulEncoder
8
8
+
{
9
9
+
private string _buffer;
10
10
+
public EncodingInfo Encoding { get; set; }
11
11
+
private List<Line> _bufferedLines;
12
12
+
13
13
+
public string Pending => _buffer;
14
14
+
15
15
+
public void Clear()
16
16
+
{
17
17
+
_buffer = "";
18
18
+
_bufferedLines.Clear();
19
19
+
}
20
20
+
21
21
+
public void Push(Line line)
22
22
+
{
23
23
+
_buffer += $"{line.Format()}\r\n";
24
24
+
_bufferedLines.Add(line);
25
25
+
}
26
26
+
27
27
+
public List<Line> Pop(int byteCount)
28
28
+
{
29
29
+
var sent = _buffer.Substring(byteCount).Count(c => c == '\n');
30
30
+
_buffer = _buffer.Substring(byteCount);
31
31
+
_bufferedLines = _bufferedLines.Skip(sent).ToList();
32
32
+
return _bufferedLines.Take(sent).ToList();
33
33
+
}
34
34
+
}
35
35
+
}
+97
IrcTokens/Tests/StatefulDecoderTests.cs
···
1
1
+
using System.Collections.Generic;
2
2
+
using System.Linq;
3
3
+
using System.Text;
4
4
+
using Microsoft.VisualStudio.TestTools.UnitTesting;
5
5
+
6
6
+
namespace IrcTokens.Tests
7
7
+
{
8
8
+
[TestClass]
9
9
+
public class StatefulDecoderTests
10
10
+
{
11
11
+
private StatefulDecoder _decoder;
12
12
+
13
13
+
[TestInitialize]
14
14
+
public void TestInitialize()
15
15
+
{
16
16
+
_decoder = new StatefulDecoder();
17
17
+
}
18
18
+
19
19
+
[TestMethod]
20
20
+
public void TestPartial()
21
21
+
{
22
22
+
var lines = _decoder.Push("PRIVMSG ");
23
23
+
Assert.AreEqual(new List<string>(), lines);
24
24
+
25
25
+
lines = _decoder.Push("#channel hello\r\n");
26
26
+
Assert.AreEqual(1, lines.Count);
27
27
+
28
28
+
var line = new Line("PRIVMSG #channel hello");
29
29
+
CollectionAssert.AreEqual(new List<Line> {line}, lines);
30
30
+
}
31
31
+
32
32
+
[TestMethod]
33
33
+
public void TestMultiple()
34
34
+
{
35
35
+
_decoder.Push("PRIVMSG #channel1 hello\r\n");
36
36
+
var lines = _decoder.Push("PRIVMSG #channel2 hello\r\n");
37
37
+
Assert.AreEqual(2, lines.Count);
38
38
+
39
39
+
var line1 = new Line("PRIVMSG #channel1 hello");
40
40
+
var line2 = new Line("PRIVMSG #channel2 hello");
41
41
+
Assert.AreEqual(line1, lines[0]);
42
42
+
Assert.AreEqual(line2, lines[1]);
43
43
+
}
44
44
+
45
45
+
[TestMethod]
46
46
+
public void TestEncoding()
47
47
+
{
48
48
+
var iso8859 = Encoding.GetEncodings().Single(ei => ei.Name == "iso-8859-1");
49
49
+
_decoder = new StatefulDecoder {Encoding = iso8859};
50
50
+
var lines = _decoder.Push("PRIVMSG #channel :hello Č\r\n");
51
51
+
var line = new Line("PRIVMSG #channel :hello Č");
52
52
+
Assert.AreEqual(line, lines[0]);
53
53
+
}
54
54
+
55
55
+
[TestMethod]
56
56
+
public void TestEncodingFallback()
57
57
+
{
58
58
+
var latin1 = Encoding.GetEncodings().Single(ei => ei.Name == "latin-1");
59
59
+
_decoder = new StatefulDecoder {Fallback = latin1};
60
60
+
var lines = _decoder.Push("PRIVMSG #channel hélló\r\n");
61
61
+
Assert.AreEqual(1, lines.Count);
62
62
+
Assert.AreEqual(new Line("PRIVMSG #channel hélló"), lines[0]);
63
63
+
}
64
64
+
65
65
+
[TestMethod]
66
66
+
public void TestEmpty()
67
67
+
{
68
68
+
var lines = _decoder.Push(string.Empty);
69
69
+
Assert.IsNull(lines);
70
70
+
}
71
71
+
72
72
+
[TestMethod]
73
73
+
public void TestBufferUnfinished()
74
74
+
{
75
75
+
_decoder.Push("PRIVMSG #channel hello");
76
76
+
var lines = _decoder.Push(string.Empty);
77
77
+
Assert.IsNull(lines);
78
78
+
}
79
79
+
80
80
+
[TestMethod]
81
81
+
public void TestClear()
82
82
+
{
83
83
+
_decoder.Push("PRIVMSG ");
84
84
+
_decoder.Clear();
85
85
+
Assert.AreEqual(string.Empty, _decoder.Pending);
86
86
+
}
87
87
+
88
88
+
[TestMethod]
89
89
+
public void TestTagEncodingMismatch()
90
90
+
{
91
91
+
_decoder.Push("@asd=á ");
92
92
+
var lines = _decoder.Push("PRIVMSG #chan :á\r\n");
93
93
+
Assert.AreEqual("á", lines[0].Params[0]);
94
94
+
Assert.AreEqual("á", lines[0].Tags["asd"]);
95
95
+
}
96
96
+
}
97
97
+
}
+72
IrcTokens/Tests/StatefulEncoderTests.cs
···
1
1
+
using System.Linq;
2
2
+
using System.Text;
3
3
+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4
4
+
5
5
+
namespace IrcTokens.Tests
6
6
+
{
7
7
+
[TestClass]
8
8
+
public class StatefulEncoderTests
9
9
+
{
10
10
+
private StatefulEncoder _encoder;
11
11
+
12
12
+
[TestInitialize]
13
13
+
public void TestInitialize()
14
14
+
{
15
15
+
_encoder = new StatefulEncoder();
16
16
+
}
17
17
+
18
18
+
[TestMethod]
19
19
+
public void TestPush()
20
20
+
{
21
21
+
var line = new Line("PRIVMSG #channel hello");
22
22
+
_encoder.Push(line);
23
23
+
Assert.AreEqual("PRIVMSG #channel hello\r\n", _encoder.Pending);
24
24
+
}
25
25
+
26
26
+
[TestMethod]
27
27
+
public void TestPopPartial()
28
28
+
{
29
29
+
var line = new Line("PRIVMSG #channel hello");
30
30
+
_encoder.Push(line);
31
31
+
_encoder.Pop("PRIVMSG #channel hello".Length);
32
32
+
Assert.AreEqual("\r\n", _encoder.Pending);
33
33
+
}
34
34
+
35
35
+
[TestMethod]
36
36
+
public void TestPopReturned()
37
37
+
{
38
38
+
var line = new Line("PRIVMSG #channel hello");
39
39
+
_encoder.Push(line);
40
40
+
_encoder.Push(line);
41
41
+
var lines = _encoder.Pop("PRIVMSG #channel hello\r\n".Length);
42
42
+
Assert.AreEqual(1, lines.Count);
43
43
+
Assert.AreEqual(line, lines[0]);
44
44
+
}
45
45
+
46
46
+
[TestMethod]
47
47
+
public void TestPopNoneReturned()
48
48
+
{
49
49
+
var line = new Line("PRIVMSG #channel hello");
50
50
+
_encoder.Push(line);
51
51
+
var lines = _encoder.Pop(1);
52
52
+
Assert.AreEqual(0, lines.Count);
53
53
+
}
54
54
+
55
55
+
[TestMethod]
56
56
+
public void TestClear()
57
57
+
{
58
58
+
_encoder.Push(new Line("PRIVMSG #channel hello"));
59
59
+
_encoder.Clear();
60
60
+
Assert.AreEqual(string.Empty, _encoder.Pending);
61
61
+
}
62
62
+
63
63
+
[TestMethod]
64
64
+
public void TestEncoding()
65
65
+
{
66
66
+
var iso88592 = Encoding.GetEncodings().Single(ei => ei.Name == "iso-8859-2");
67
67
+
_encoder = new StatefulEncoder {Encoding = iso88592};
68
68
+
_encoder.Push(new Line("PRIVMSG #channel :hello Č"));
69
69
+
Assert.AreEqual("PRIVMSG #channel :hello Č\r\n", _encoder.Pending);
70
70
+
}
71
71
+
}
72
72
+
}