tidy up, work on stateful

This commit is contained in:
Ben Harris 2020-04-28 00:35:52 -04:00
parent 933a4f8560
commit 80afa2c0ae
17 changed files with 314 additions and 305 deletions

View File

@ -17,10 +17,7 @@ namespace IrcStates
private static string Replace(string s, string upper, string lower) private static string Replace(string s, string upper, string lower)
{ {
for (var i = 0; i < upper.Length; ++i) for (var i = 0; i < upper.Length; ++i) s = s.Replace(upper[i], lower[i]);
{
s = s.Replace(upper[i], lower[i]);
}
return s; return s;
} }
@ -28,14 +25,12 @@ namespace IrcStates
public static string CaseFold(CaseMapping mapping, string s) public static string CaseFold(CaseMapping mapping, string s)
{ {
if (s != null) if (s != null)
{
return mapping switch return mapping switch
{ {
CaseMapping.Rfc1459 => Replace(s, Rfc1459UpperChars, Rfc1459LowerChars), CaseMapping.Rfc1459 => Replace(s, Rfc1459UpperChars, Rfc1459LowerChars),
CaseMapping.Ascii => Replace(s, AsciiUpperChars, AsciiLowerChars), CaseMapping.Ascii => Replace(s, AsciiUpperChars, AsciiLowerChars),
_ => throw new ArgumentOutOfRangeException(nameof(mapping), mapping, null) _ => throw new ArgumentOutOfRangeException(nameof(mapping), mapping, null)
}; };
}
return string.Empty; return string.Empty;
} }

View File

@ -1,8 +1,10 @@
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace IrcStates namespace IrcStates
{ {
public static class Numeric public static class Numeric
{ {
#pragma warning disable CA1707 // Identifiers should not contain underscores
public const string RPL_WELCOME = "001"; public const string RPL_WELCOME = "001";
public const string RPL_ISUPPORT = "005"; public const string RPL_ISUPPORT = "005";
public const string RPL_MOTD = "372"; public const string RPL_MOTD = "372";
@ -45,5 +47,6 @@ namespace IrcStates
public const string RPL_ENDOFWHOIS = "318"; public const string RPL_ENDOFWHOIS = "318";
public const string ERR_NOSUCHCHANNEL = "403"; public const string ERR_NOSUCHCHANNEL = "403";
#pragma warning restore CA1707 // Identifiers should not contain underscores
} }
} }

View File

@ -1,6 +1,79 @@
namespace IrcStates using System;
using System.Collections.Generic;
using System.Linq;
using IrcTokens;
namespace IrcStates
{ {
public class Server public class Server
{ {
private readonly StatefulDecoder _decoder;
public Dictionary<string, List<Func<string, Line, Emit>>> LineHandlers;
private Dictionary<string, string> TempCaps;
public Server(string name)
{
Name = name;
Registered = false;
Modes = new List<string>();
Motd = new List<string>();
_decoder = new StatefulDecoder();
LineHandlers = new Dictionary<string, List<Func<string, Line, Emit>>>();
Users = new Dictionary<string, User>();
Channels = new Dictionary<string, Channel>();
ISupport = new ISupport();
HasCap = false;
TempCaps = new Dictionary<string, string>();
AvailableCaps = new Dictionary<string, string>();
AgreedCaps = new List<string>();
}
public string Name { get; set; }
public string NickName { get; set; }
public string NickNameLower { get; set; }
public string UserName { get; set; }
public string HostName { get; set; }
public string RealName { get; set; }
public string Account { get; set; }
public string Away { get; set; }
public bool Registered { get; set; }
public List<string> Modes { get; set; }
public List<string> Motd { get; set; }
public Dictionary<string, User> Users { get; set; }
public Dictionary<string, Channel> Channels { get; set; }
public Dictionary<string, string> AvailableCaps { get; set; }
public List<string> AgreedCaps { get; set; }
public ISupport ISupport { get; set; }
public bool HasCap { get; set; }
public override string ToString()
{
return $"Server(name={Name})";
}
public List<(Line, Emit)> Recv(byte[] data)
{
var lines = _decoder.Push(data);
if (lines == null) throw new ServerDisconnectedException();
var emits = lines.Select(ParseTokens).ToList();
return null;
}
private Emit ParseTokens(Line line)
{
Emit ret;
if (LineHandlers.ContainsKey(line.Command))
foreach (var callback in LineHandlers[line.Command])
{
var emit = callback(line.Command, line);
}
throw new NotImplementedException();
}
} }
} }

View File

@ -0,0 +1,9 @@
using System;
namespace IrcStates
{
public class ServerDisconnectedException : Exception
{
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace IrcStates
{
public class ServerException : Exception
{
}
}

View File

@ -21,7 +21,7 @@ namespace IrcStates
public void SetNickName(string nick, string nickLower) public void SetNickName(string nick, string nickLower)
{ {
NickName = nick; NickName = nick;
NickNameLower = nickLower; NickNameLower = nickLower;
} }
} }

View File

@ -3,47 +3,15 @@
namespace IrcTokens namespace IrcTokens
{ {
/// <summary> /// <summary>
/// Represents the three parts of a hostmask. Parse with the constructor. /// Represents the three parts of a hostmask. Parse with the constructor.
/// </summary> /// </summary>
public class Hostmask : IEquatable<Hostmask> public class Hostmask : IEquatable<Hostmask>
{ {
public string NickName { get; set; }
public string UserName { get; set; }
public string HostName { get; set; }
public override string ToString()
{
return _source;
}
public override int GetHashCode()
{
return _source.GetHashCode(StringComparison.Ordinal);
}
public bool Equals(Hostmask other)
{
if (other == null)
{
return false;
}
return _source == other._source;
}
public override bool Equals(object obj)
{
return Equals(obj as Hostmask);
}
private readonly string _source; private readonly string _source;
public Hostmask(string source) public Hostmask(string source)
{ {
if (source == null) if (source == null) return;
{
return;
}
_source = source; _source = source;
@ -66,5 +34,31 @@ namespace IrcTokens
UserName = userSplit[1]; UserName = userSplit[1];
} }
} }
public string NickName { get; set; }
public string UserName { get; set; }
public string HostName { get; set; }
public bool Equals(Hostmask other)
{
if (other == null) return false;
return _source == other._source;
}
public override string ToString()
{
return _source;
}
public override int GetHashCode()
{
return _source.GetHashCode(StringComparison.Ordinal);
}
public override bool Equals(object obj)
{
return Equals(obj as Hostmask);
}
} }
} }

View File

@ -2,83 +2,34 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text;
namespace IrcTokens namespace IrcTokens
{ {
/// <summary> /// <summary>
/// Tools to represent, parse, and format IRC lines /// Tools to represent, parse, and format IRC lines
/// </summary> /// </summary>
public class Line : IEquatable<Line> public class Line : IEquatable<Line>
{ {
public Dictionary<string, string> Tags { get; set; } private static readonly string[] TagUnescaped = {"\\", " ", ";", "\r", "\n"};
public string Source { get; set; }
public string Command { get; set; } private static readonly string[] TagEscaped = {"\\\\", "\\s", "\\:", "\\r", "\\n"};
public List<string> Params { get; set; }
private Hostmask _hostmask; private Hostmask _hostmask;
public override string ToString() public Line()
{ {
var vars = new List<string>();
if (Command != null)
{
vars.Add($"command={Command}");
}
if (Source != null)
{
vars.Add($"source={Source}");
}
if (Params != null && Params.Any())
{
vars.Add($"params=[{string.Join(",", Params)}]");
}
if (Tags != null && Tags.Any())
{
vars.Add($"tags=[{string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}]");
}
return $"Line({string.Join(", ", vars)})";
} }
public override int GetHashCode()
{
return Format().GetHashCode(StringComparison.Ordinal);
}
public bool Equals(Line other)
{
if (other == null)
{
return false;
}
return Format() == other.Format();
}
public override bool Equals(object obj)
{
return Equals(obj as Line);
}
public Hostmask Hostmask =>
_hostmask ??= new Hostmask(Source);
public Line() { }
/// <summary> /// <summary>
/// Build new <see cref="Line"/> object parsed from <param name="line">a string</param>. Analogous to irctokens.tokenise() /// Build new <see cref="Line" /> object parsed from
/// <param name="line">a string</param>
/// . Analogous to irctokens.tokenise()
/// </summary> /// </summary>
/// <param name="line">irc line to parse</param> /// <param name="line">irc line to parse</param>
public Line(string line) public Line(string line)
{ {
if (string.IsNullOrWhiteSpace(line)) if (string.IsNullOrWhiteSpace(line)) throw new ArgumentNullException(nameof(line));
{
throw new ArgumentNullException(nameof(line));
}
string[] split; string[] split;
@ -91,24 +42,22 @@ namespace IrcTokens
line = string.Join(" ", split.Skip(1)); line = string.Join(" ", split.Skip(1));
foreach (var part in messageTags.Substring(1).Split(';')) foreach (var part in messageTags.Substring(1).Split(';'))
{
if (part.Contains('=', StringComparison.Ordinal)) if (part.Contains('=', StringComparison.Ordinal))
{ {
split = part.Split('=', 2); split = part.Split('=', 2);
Tags[split[0]] = Protocol.UnescapeTag(split[1]); Tags[split[0]] = UnescapeTag(split[1]);
} }
else else
{ {
Tags[part] = string.Empty; Tags[part] = string.Empty;
} }
}
} }
string trailing; string trailing;
if (line.Contains(" :", StringComparison.Ordinal)) if (line.Contains(" :", StringComparison.Ordinal))
{ {
split = line.Split(" :", 2); split = line.Split(" :", 2);
line = split[0]; line = split[0];
trailing = split[1]; trailing = split[1];
} }
else else
@ -118,7 +67,7 @@ namespace IrcTokens
Params = line.Contains(' ', StringComparison.Ordinal) Params = line.Contains(' ', StringComparison.Ordinal)
? line.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList() ? line.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList()
: new List<string> { line }; : new List<string> {line};
if (Params[0].StartsWith(':')) if (Params[0].StartsWith(':'))
{ {
@ -132,14 +81,102 @@ namespace IrcTokens
Params.RemoveAt(0); Params.RemoveAt(0);
} }
if (trailing != null) if (trailing != null) Params.Add(trailing);
{ }
Params.Add(trailing);
} public Dictionary<string, string> Tags { get; set; }
public string Source { get; set; }
public string Command { get; set; }
public List<string> Params { get; set; }
public Hostmask Hostmask =>
_hostmask ??= new Hostmask(Source);
public bool Equals(Line other)
{
if (other == null) return false;
return Format() == other.Format();
} }
/// <summary> /// <summary>
/// Format a <see cref="Line"/> as a standards-compliant IRC line /// Unescape ircv3 tag
/// </summary>
/// <param name="val">escaped string</param>
/// <returns>unescaped string</returns>
public static string UnescapeTag(string val)
{
var unescaped = new StringBuilder();
var graphemeIterator = StringInfo.GetTextElementEnumerator(val);
graphemeIterator.Reset();
while (graphemeIterator.MoveNext())
{
var current = graphemeIterator.GetTextElement();
if (current == @"\")
try
{
graphemeIterator.MoveNext();
var next = graphemeIterator.GetTextElement();
var pair = current + next;
unescaped.Append(TagEscaped.Contains(pair)
? TagUnescaped[Array.IndexOf(TagEscaped, pair)]
: next);
}
catch (InvalidOperationException)
{
// ignored
}
else
unescaped.Append(current);
}
return unescaped.ToString();
}
/// <summary>
/// Escape strings for use in ircv3 tags
/// </summary>
/// <param name="val">string to escape</param>
/// <returns>escaped string</returns>
public static string EscapeTag(string val)
{
for (var i = 0; i < TagUnescaped.Length; ++i)
val = val?.Replace(TagUnescaped[i], TagEscaped[i], StringComparison.Ordinal);
return val;
}
public override string ToString()
{
var vars = new List<string>();
if (Command != null) vars.Add($"command={Command}");
if (Source != null) vars.Add($"source={Source}");
if (Params != null && Params.Any()) vars.Add($"params=[{string.Join(",", Params)}]");
if (Tags != null && Tags.Any())
vars.Add($"tags=[{string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}]");
return $"Line({string.Join(", ", vars)})";
}
public override int GetHashCode()
{
return Format().GetHashCode(StringComparison.Ordinal);
}
public override bool Equals(object obj)
{
return Equals(obj as Line);
}
/// <summary>
/// Format a <see cref="Line" /> as a standards-compliant IRC line
/// </summary> /// </summary>
/// <returns>formatted irc line</returns> /// <returns>formatted irc line</returns>
public string Format() public string Format()
@ -149,16 +186,15 @@ namespace IrcTokens
if (Tags != null && Tags.Any()) if (Tags != null && Tags.Any())
{ {
var tags = Tags.Keys var tags = Tags.Keys
.Select(key => string.IsNullOrWhiteSpace(Tags[key]) ? key : $"{key}={Protocol.EscapeTag(Tags[key])}") .OrderBy(k => k)
.Select(key =>
string.IsNullOrWhiteSpace(Tags[key]) ? key : $"{key}={EscapeTag(Tags[key])}")
.ToList(); .ToList();
outs.Add($"@{string.Join(";", tags)}"); outs.Add($"@{string.Join(";", tags)}");
} }
if (Source != null) if (Source != null) outs.Add($":{Source}");
{
outs.Add($":{Source}");
}
outs.Add(Command); outs.Add(Command);
@ -170,21 +206,17 @@ namespace IrcTokens
foreach (var p in Params) foreach (var p in Params)
{ {
if (p.Contains(' ', StringComparison.Ordinal)) if (p.Contains(' ', StringComparison.Ordinal))
{
throw new ArgumentException(@"non-last parameters cannot have spaces", p); throw new ArgumentException(@"non-last parameters cannot have spaces", p);
}
if (p.StartsWith(':')) if (p.StartsWith(':'))
{
throw new ArgumentException(@"non-last parameters cannot start with colon", p); throw new ArgumentException(@"non-last parameters cannot start with colon", p);
}
} }
outs.AddRange(Params); outs.AddRange(Params);
if (string.IsNullOrWhiteSpace(last) || last.Contains(' ', StringComparison.Ordinal) || last.StartsWith(':')) if (string.IsNullOrWhiteSpace(last) || last.Contains(' ', StringComparison.Ordinal) ||
{ last.StartsWith(':'))
last = $":{last}"; last = $":{last}";
}
outs.Add(last); outs.Add(last);
} }

View File

@ -1,76 +0,0 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text;
namespace IrcTokens
{
internal class Protocol
{
private static readonly string[] TagUnescaped =
{
"\\", " ", ";", "\r", "\n"
};
private static readonly string[] TagEscaped =
{
"\\\\", "\\s", "\\:", "\\r", "\\n"
};
/// <summary>
/// Unescape ircv3 tag
/// </summary>
/// <param name="val">escaped string</param>
/// <returns>unescaped string</returns>
public static string UnescapeTag(string val)
{
var unescaped = new StringBuilder();
var graphemeIterator = StringInfo.GetTextElementEnumerator(val);
graphemeIterator.Reset();
while (graphemeIterator.MoveNext())
{
var current = graphemeIterator.GetTextElement();
if (current == @"\")
{
try
{
graphemeIterator.MoveNext();
var next = graphemeIterator.GetTextElement();
var pair = current + next;
unescaped.Append(TagEscaped.Contains(pair)
? TagUnescaped[Array.IndexOf(TagEscaped, pair)]
: next);
}
catch (InvalidOperationException)
{
// ignored
}
}
else
{
unescaped.Append(current);
}
}
return unescaped.ToString();
}
/// <summary>
/// Escape strings for use in ircv3 tags
/// </summary>
/// <param name="val">string to escape</param>
/// <returns>escaped string</returns>
public static string EscapeTag(string val)
{
for (var i = 0; i < TagUnescaped.Length; ++i)
{
val = val.Replace(TagUnescaped[i], TagEscaped[i], StringComparison.Ordinal);
}
return val;
}
}
}

View File

@ -11,17 +11,20 @@ namespace IrcTokens
private Encoding _encoding; private Encoding _encoding;
private Encoding _fallback; private Encoding _fallback;
public StatefulDecoder()
{
Clear();
}
public Encoding Encoding public Encoding Encoding
{ {
get => _encoding ?? Encoding.GetEncoding(Encoding.UTF8.CodePage, EncoderFallback.ExceptionFallback, get => _encoding ?? Encoding.GetEncoding(Encoding.UTF8.CodePage, EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback); DecoderFallback.ExceptionFallback);
set set
{ {
if (value != null) if (value != null)
{
_encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback, _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback,
DecoderFallback.ReplacementFallback); DecoderFallback.ReplacementFallback);
}
} }
} }
@ -32,20 +35,13 @@ namespace IrcTokens
set set
{ {
if (value != null) if (value != null)
{
_fallback = Encoding.GetEncoding(value.CodePage, EncoderFallback.ReplacementFallback, _fallback = Encoding.GetEncoding(value.CodePage, EncoderFallback.ReplacementFallback,
DecoderFallback.ReplacementFallback); DecoderFallback.ReplacementFallback);
}
} }
} }
public string Pending => Encoding.GetString(_buffer); public string Pending => Encoding.GetString(_buffer);
public StatefulDecoder()
{
Clear();
}
public void Clear() public void Clear()
{ {
_buffer = Array.Empty<byte>(); _buffer = Array.Empty<byte>();
@ -58,16 +54,13 @@ namespace IrcTokens
public List<Line> Push(byte[] data) public List<Line> Push(byte[] data)
{ {
if (data == null || data.Length == 0) if (data == null || data.Length == 0) return null;
{
return null;
}
_buffer = _buffer.Concat(data).ToArray(); _buffer = _buffer.Concat(data).ToArray();
// simulate string.Split('\n') before decoding // simulate string.Split('\n') before decoding
var newLineIndices = _buffer.Select((b, i) => b == '\n' ? i : -1).Where(i => i != -1).ToArray(); var newLineIndices = _buffer.Select((b, i) => b == '\n' ? i : -1).Where(i => i != -1).ToArray();
var lines = new List<byte[]>(); var lines = new List<byte[]>();
for (int i = 0, currentIndex = 0; i < newLineIndices.Length; ++i) for (int i = 0, currentIndex = 0; i < newLineIndices.Length; ++i)
{ {
@ -99,7 +92,6 @@ namespace IrcTokens
var decodeLines = new List<string>(); var decodeLines = new List<string>();
foreach (var line in listLines.Select(l => l.ToArray())) foreach (var line in listLines.Select(l => l.ToArray()))
{
try try
{ {
decodeLines.Add(Encoding.GetString(line)); decodeLines.Add(Encoding.GetString(line));
@ -108,7 +100,6 @@ namespace IrcTokens
{ {
decodeLines.Add(Fallback.GetString(line)); decodeLines.Add(Fallback.GetString(line));
} }
}
return decodeLines return decodeLines
.Select(l => new Line(l)) .Select(l => new Line(l))

View File

@ -7,8 +7,14 @@ namespace IrcTokens
{ {
public class StatefulEncoder public class StatefulEncoder
{ {
private Queue<Line> _bufferedLines;
private Encoding _encoding; private Encoding _encoding;
public StatefulEncoder()
{
Clear();
}
public Encoding Encoding public Encoding Encoding
{ {
get => _encoding ?? Encoding.GetEncoding(Encoding.UTF8.CodePage, EncoderFallback.ExceptionFallback, get => _encoding ?? Encoding.GetEncoding(Encoding.UTF8.CodePage, EncoderFallback.ExceptionFallback,
@ -16,15 +22,11 @@ namespace IrcTokens
set set
{ {
if (value != null) if (value != null)
{
_encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback, _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback); DecoderFallback.ExceptionFallback);
}
} }
} }
private Queue<Line> _bufferedLines;
public byte[] PendingBytes { get; private set; } public byte[] PendingBytes { get; private set; }
public string Pending() public string Pending()
@ -40,23 +42,15 @@ namespace IrcTokens
} }
} }
public StatefulEncoder()
{
Clear();
}
public void Clear() public void Clear()
{ {
PendingBytes = Array.Empty<byte>(); PendingBytes = Array.Empty<byte>();
_bufferedLines = new Queue<Line>(); _bufferedLines = new Queue<Line>();
} }
public void Push(Line line) public void Push(Line line)
{ {
if (line == null) if (line == null) throw new ArgumentNullException(nameof(line));
{
throw new ArgumentNullException(nameof(line));
}
PendingBytes = PendingBytes.Concat(Encoding.GetBytes($"{line.Format()}\r\n")).ToArray(); PendingBytes = PendingBytes.Concat(Encoding.GetBytes($"{line.Format()}\r\n")).ToArray();
_bufferedLines.Enqueue(line); _bufferedLines.Enqueue(line);
@ -66,8 +60,8 @@ namespace IrcTokens
{ {
var sent = PendingBytes.Take(byteCount).Count(c => c == '\n'); var sent = PendingBytes.Take(byteCount).Count(c => c == '\n');
PendingBytes = PendingBytes.Skip(byteCount).ToArray(); PendingBytes = PendingBytes.Skip(byteCount).ToArray();
_bufferedLines = new Queue<Line>(_bufferedLines.Skip(sent)); _bufferedLines = new Queue<Line>(_bufferedLines.Take(sent));
return Enumerable.Range(0, sent) return Enumerable.Range(0, sent)
.Select(_ => _bufferedLines.Dequeue()) .Select(_ => _bufferedLines.Dequeue())

View File

@ -1,6 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace IrcTokens.Tests namespace IrcTokens.Tests
{ {
@ -13,8 +13,8 @@ namespace IrcTokens.Tests
var line = new Line var line = new Line
{ {
Command = "PRIVMSG", Command = "PRIVMSG",
Params = new List<string> { "#channel", "hello" }, Params = new List<string> {"#channel", "hello"},
Tags = new Dictionary<string, string> { { "id", "\\" + " " + ";" + "\r\n" } } Tags = new Dictionary<string, string> {{"id", "\\" + " " + ";" + "\r\n"}}
}.Format(); }.Format();
Assert.AreEqual("@id=\\\\\\s\\:\\r\\n PRIVMSG #channel hello", line); Assert.AreEqual("@id=\\\\\\s\\:\\r\\n PRIVMSG #channel hello", line);
@ -23,11 +23,7 @@ namespace IrcTokens.Tests
[TestMethod] [TestMethod]
public void TestMissingTag() public void TestMissingTag()
{ {
var line = new Line var line = new Line {Command = "PRIVMSG", Params = new List<string> {"#channel", "hello"}}.Format();
{
Command = "PRIVMSG",
Params = new List<string> { "#channel", "hello" }
}.Format();
Assert.AreEqual("PRIVMSG #channel hello", line); Assert.AreEqual("PRIVMSG #channel hello", line);
} }
@ -38,8 +34,8 @@ namespace IrcTokens.Tests
var line = new Line var line = new Line
{ {
Command = "PRIVMSG", Command = "PRIVMSG",
Params = new List<string> { "#channel", "hello" }, Params = new List<string> {"#channel", "hello"},
Tags = new Dictionary<string, string> { { "a", null } } Tags = new Dictionary<string, string> {{"a", null}}
}.Format(); }.Format();
Assert.AreEqual("@a PRIVMSG #channel hello", line); Assert.AreEqual("@a PRIVMSG #channel hello", line);
@ -51,8 +47,8 @@ namespace IrcTokens.Tests
var line = new Line var line = new Line
{ {
Command = "PRIVMSG", Command = "PRIVMSG",
Params = new List<string> { "#channel", "hello" }, Params = new List<string> {"#channel", "hello"},
Tags = new Dictionary<string, string> { { "a", "" } } Tags = new Dictionary<string, string> {{"a", ""}}
}.Format(); }.Format();
Assert.AreEqual("@a PRIVMSG #channel hello", line); Assert.AreEqual("@a PRIVMSG #channel hello", line);
@ -63,9 +59,7 @@ namespace IrcTokens.Tests
{ {
var line = new Line var line = new Line
{ {
Command = "PRIVMSG", Command = "PRIVMSG", Params = new List<string> {"#channel", "hello"}, Source = "nick!user@host"
Params = new List<string> { "#channel", "hello" },
Source = "nick!user@host"
}.Format(); }.Format();
Assert.AreEqual(":nick!user@host PRIVMSG #channel hello", line); Assert.AreEqual(":nick!user@host PRIVMSG #channel hello", line);
@ -74,25 +68,21 @@ namespace IrcTokens.Tests
[TestMethod] [TestMethod]
public void TestCommandLowercase() public void TestCommandLowercase()
{ {
var line = new Line { Command = "privmsg" }.Format(); var line = new Line {Command = "privmsg"}.Format();
Assert.AreEqual("privmsg", line); Assert.AreEqual("privmsg", line);
} }
[TestMethod] [TestMethod]
public void TestCommandUppercase() public void TestCommandUppercase()
{ {
var line = new Line { Command = "PRIVMSG" }.Format(); var line = new Line {Command = "PRIVMSG"}.Format();
Assert.AreEqual("PRIVMSG", line); Assert.AreEqual("PRIVMSG", line);
} }
[TestMethod] [TestMethod]
public void TestTrailingSpace() public void TestTrailingSpace()
{ {
var line = new Line var line = new Line {Command = "PRIVMSG", Params = new List<string> {"#channel", "hello world"}}.Format();
{
Command = "PRIVMSG",
Params = new List<string> { "#channel", "hello world" }
}.Format();
Assert.AreEqual("PRIVMSG #channel :hello world", line); Assert.AreEqual("PRIVMSG #channel :hello world", line);
} }
@ -100,11 +90,7 @@ namespace IrcTokens.Tests
[TestMethod] [TestMethod]
public void TestTrailingNoSpace() public void TestTrailingNoSpace()
{ {
var line = new Line var line = new Line {Command = "PRIVMSG", Params = new List<string> {"#channel", "helloworld"}}.Format();
{
Command = "PRIVMSG",
Params = new List<string> { "#channel", "helloworld" }
}.Format();
Assert.AreEqual("PRIVMSG #channel helloworld", line); Assert.AreEqual("PRIVMSG #channel helloworld", line);
} }
@ -112,11 +98,7 @@ namespace IrcTokens.Tests
[TestMethod] [TestMethod]
public void TestTrailingDoubleColon() public void TestTrailingDoubleColon()
{ {
var line = new Line var line = new Line {Command = "PRIVMSG", Params = new List<string> {"#channel", ":helloworld"}}.Format();
{
Command = "PRIVMSG",
Params = new List<string> { "#channel", ":helloworld" }
}.Format();
Assert.AreEqual("PRIVMSG #channel ::helloworld", line); Assert.AreEqual("PRIVMSG #channel ::helloworld", line);
} }
@ -126,11 +108,7 @@ namespace IrcTokens.Tests
{ {
Assert.ThrowsException<ArgumentException>(() => Assert.ThrowsException<ArgumentException>(() =>
{ {
new Line new Line {Command = "USER", Params = new List<string> {"user", "0 *", "real name"}}.Format();
{
Command = "USER",
Params = new List<string> { "user", "0 *", "real name" }
}.Format();
}); });
} }
@ -139,11 +117,7 @@ namespace IrcTokens.Tests
{ {
Assert.ThrowsException<ArgumentException>(() => Assert.ThrowsException<ArgumentException>(() =>
{ {
new Line new Line {Command = "PRIVMSG", Params = new List<string> {":#channel", "hello"}}.Format();
{
Command = "PRIVMSG",
Params = new List<string> { ":#channel", "hello" }
}.Format();
}); });
} }
} }

View File

@ -44,7 +44,7 @@ namespace IrcTokens.Tests
[TestMethod] [TestMethod]
public void TestHostmaskFromLine() public void TestHostmaskFromLine()
{ {
var line = new Line(":nick!user@host PRIVMSG #channel hello"); var line = new Line(":nick!user@host PRIVMSG #channel hello");
var hostmask = new IrcTokens.Hostmask("nick!user@host"); var hostmask = new IrcTokens.Hostmask("nick!user@host");
Assert.AreEqual(hostmask.ToString(), line.Hostmask.ToString()); Assert.AreEqual(hostmask.ToString(), line.Hostmask.ToString());
Assert.AreEqual("nick", line.Hostmask.NickName); Assert.AreEqual("nick", line.Hostmask.NickName);

View File

@ -1,8 +1,8 @@
using IrcTokens.Tests.Data; using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using IrcTokens.Tests.Data;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using YamlDotNet.Serialization; using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NamingConventions;
@ -26,13 +26,14 @@ namespace IrcTokens.Tests
foreach (var test in LoadYaml<SplitModel>("Tests/Data/msg-split.yaml").Tests) foreach (var test in LoadYaml<SplitModel>("Tests/Data/msg-split.yaml").Tests)
{ {
var tokens = new Line(test.Input); var tokens = new Line(test.Input);
var atoms = test.Atoms; var atoms = test.Atoms;
Assert.AreEqual(atoms.Verb.ToUpper(CultureInfo.InvariantCulture), tokens.Command, Assert.AreEqual(atoms.Verb.ToUpper(CultureInfo.InvariantCulture), tokens.Command,
$"command failed on: '{test.Input}'"); $"command failed on: '{test.Input}'");
Assert.AreEqual(atoms.Source, tokens.Source, $"source failed on: '{test.Input}'"); Assert.AreEqual(atoms.Source, tokens.Source, $"source failed on: '{test.Input}'");
CollectionAssert.AreEqual(atoms.Tags, tokens.Tags, $"tags failed on: '{test.Input}'"); CollectionAssert.AreEqual(atoms.Tags, tokens.Tags, $"tags failed on: '{test.Input}'");
CollectionAssert.AreEqual(atoms.Params ?? new List<string>(), tokens.Params, $"params failed on: '{test.Input}'"); CollectionAssert.AreEqual(atoms.Params ?? new List<string>(), tokens.Params,
$"params failed on: '{test.Input}'");
} }
} }
@ -44,10 +45,7 @@ namespace IrcTokens.Tests
var atoms = test.Atoms; var atoms = test.Atoms;
var line = new Line var line = new Line
{ {
Command = atoms.Verb, Command = atoms.Verb, Params = atoms.Params, Source = atoms.Source, Tags = atoms.Tags
Params = atoms.Params,
Source = atoms.Source,
Tags = atoms.Tags
}.Format(); }.Format();
Assert.IsTrue(test.Matches.Contains(line), test.Description); Assert.IsTrue(test.Matches.Contains(line), test.Description);

View File

@ -1,6 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic;
using System.Collections.Generic;
using System.Text; using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace IrcTokens.Tests namespace IrcTokens.Tests
{ {
@ -25,7 +25,7 @@ namespace IrcTokens.Tests
Assert.AreEqual(1, lines.Count); Assert.AreEqual(1, lines.Count);
var line = new Line("PRIVMSG #channel hello"); var line = new Line("PRIVMSG #channel hello");
CollectionAssert.AreEqual(new List<Line> { line }, lines); CollectionAssert.AreEqual(new List<Line> {line}, lines);
} }
[TestMethod] [TestMethod]
@ -44,9 +44,9 @@ namespace IrcTokens.Tests
public void TestEncoding() public void TestEncoding()
{ {
var iso8859 = Encoding.GetEncoding("iso-8859-1"); var iso8859 = Encoding.GetEncoding("iso-8859-1");
_decoder = new IrcTokens.StatefulDecoder { Encoding = iso8859 }; _decoder = new IrcTokens.StatefulDecoder {Encoding = iso8859};
var lines = _decoder.Push(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n")); var lines = _decoder.Push(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"));
var line = new Line("PRIVMSG #channel :hello Ç"); var line = new Line("PRIVMSG #channel :hello Ç");
Assert.IsTrue(line.Equals(lines[0])); Assert.IsTrue(line.Equals(lines[0]));
} }
@ -54,7 +54,7 @@ namespace IrcTokens.Tests
public void TestEncodingFallback() public void TestEncodingFallback()
{ {
var latin1 = Encoding.GetEncoding("iso-8859-1"); var latin1 = Encoding.GetEncoding("iso-8859-1");
_decoder = new IrcTokens.StatefulDecoder { Encoding = null, Fallback = latin1 }; _decoder = new IrcTokens.StatefulDecoder {Encoding = null, Fallback = latin1};
var lines = _decoder.Push(latin1.GetBytes("PRIVMSG #channel hélló\r\n")); var lines = _decoder.Push(latin1.GetBytes("PRIVMSG #channel hélló\r\n"));
Assert.AreEqual(1, lines.Count); Assert.AreEqual(1, lines.Count);
Assert.IsTrue(new Line("PRIVMSG #channel hélló").Equals(lines[0])); Assert.IsTrue(new Line("PRIVMSG #channel hélló").Equals(lines[0]));

View File

@ -1,5 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Text;
using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace IrcTokens.Tests namespace IrcTokens.Tests
{ {
@ -51,6 +51,19 @@ namespace IrcTokens.Tests
Assert.AreEqual(0, lines.Count); Assert.AreEqual(0, lines.Count);
} }
[TestMethod]
public void TestPopMultipleLines()
{
var line1 = new Line("PRIVMSG #channel1 hello");
_encoder.Push(line1);
var line2 = new Line("PRIVMSG #channel2 hello");
_encoder.Push(line2);
var lines = _encoder.Pop(_encoder.Pending().Length);
Assert.AreEqual(2, lines.Count);
Assert.AreEqual(string.Empty, _encoder.Pending());
}
[TestMethod] [TestMethod]
public void TestClear() public void TestClear()
{ {
@ -63,7 +76,7 @@ namespace IrcTokens.Tests
public void TestEncoding() public void TestEncoding()
{ {
var iso8859 = Encoding.GetEncoding("iso-8859-1"); var iso8859 = Encoding.GetEncoding("iso-8859-1");
_encoder = new IrcTokens.StatefulEncoder { Encoding = iso8859 }; _encoder = new IrcTokens.StatefulEncoder {Encoding = iso8859};
_encoder.Push(new Line("PRIVMSG #channel :hello Ç")); _encoder.Push(new Line("PRIVMSG #channel :hello Ç"));
CollectionAssert.AreEqual(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"), _encoder.PendingBytes); CollectionAssert.AreEqual(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"), _encoder.PendingBytes);
} }

View File

@ -1,5 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic;
using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace IrcTokens.Tests namespace IrcTokens.Tests
{ {
@ -87,14 +87,14 @@ namespace IrcTokens.Tests
public void TestParamsTrailing() public void TestParamsTrailing()
{ {
var line = new Line("PRIVMSG #channel :hello world"); var line = new Line("PRIVMSG #channel :hello world");
CollectionAssert.AreEqual(new List<string> { "#channel", "hello world" }, line.Params); CollectionAssert.AreEqual(new List<string> {"#channel", "hello world"}, line.Params);
} }
[TestMethod] [TestMethod]
public void TestParamsOnlyTrailing() public void TestParamsOnlyTrailing()
{ {
var line = new Line("PRIVMSG :hello world"); var line = new Line("PRIVMSG :hello world");
CollectionAssert.AreEqual(new List<string> { "hello world" }, line.Params); CollectionAssert.AreEqual(new List<string> {"hello world"}, line.Params);
} }
[TestMethod] [TestMethod]
@ -109,10 +109,10 @@ namespace IrcTokens.Tests
public void TestAllTokens() public void TestAllTokens()
{ {
var line = new Line("@id=123 :nick!user@host PRIVMSG #channel :hello world"); var line = new Line("@id=123 :nick!user@host PRIVMSG #channel :hello world");
CollectionAssert.AreEqual(new Dictionary<string, string> { { "id", "123" } }, line.Tags); CollectionAssert.AreEqual(new Dictionary<string, string> {{"id", "123"}}, line.Tags);
Assert.AreEqual("nick!user@host", line.Source); Assert.AreEqual("nick!user@host", line.Source);
Assert.AreEqual("PRIVMSG", line.Command); Assert.AreEqual("PRIVMSG", line.Command);
CollectionAssert.AreEqual(new List<string> { "#channel", "hello world" }, line.Params); CollectionAssert.AreEqual(new List<string> {"#channel", "hello world"}, line.Params);
} }
} }
} }