tidy up, work on stateful
This commit is contained in:
parent
933a4f8560
commit
80afa2c0ae
|
@ -17,10 +17,7 @@ namespace IrcStates
|
|||
|
||||
private static string Replace(string s, string upper, string lower)
|
||||
{
|
||||
for (var i = 0; i < upper.Length; ++i)
|
||||
{
|
||||
s = s.Replace(upper[i], lower[i]);
|
||||
}
|
||||
for (var i = 0; i < upper.Length; ++i) s = s.Replace(upper[i], lower[i]);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
@ -28,14 +25,12 @@ namespace IrcStates
|
|||
public static string CaseFold(CaseMapping mapping, string s)
|
||||
{
|
||||
if (s != null)
|
||||
{
|
||||
return mapping switch
|
||||
{
|
||||
CaseMapping.Rfc1459 => Replace(s, Rfc1459UpperChars, Rfc1459LowerChars),
|
||||
CaseMapping.Ascii => Replace(s, AsciiUpperChars, AsciiLowerChars),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mapping), mapping, null)
|
||||
CaseMapping.Ascii => Replace(s, AsciiUpperChars, AsciiLowerChars),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(mapping), mapping, null)
|
||||
};
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace IrcStates
|
||||
{
|
||||
public static class Numeric
|
||||
{
|
||||
#pragma warning disable CA1707 // Identifiers should not contain underscores
|
||||
public const string RPL_WELCOME = "001";
|
||||
public const string RPL_ISUPPORT = "005";
|
||||
public const string RPL_MOTD = "372";
|
||||
|
@ -45,5 +47,6 @@ namespace IrcStates
|
|||
public const string RPL_ENDOFWHOIS = "318";
|
||||
|
||||
public const string ERR_NOSUCHCHANNEL = "403";
|
||||
#pragma warning restore CA1707 // Identifiers should not contain underscores
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,79 @@
|
|||
namespace IrcStates
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using IrcTokens;
|
||||
|
||||
namespace IrcStates
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace IrcStates
|
||||
{
|
||||
public class ServerDisconnectedException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace IrcStates
|
||||
{
|
||||
public class ServerException : Exception
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -21,7 +21,7 @@ namespace IrcStates
|
|||
|
||||
public void SetNickName(string nick, string nickLower)
|
||||
{
|
||||
NickName = nick;
|
||||
NickName = nick;
|
||||
NickNameLower = nickLower;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,47 +3,15 @@
|
|||
namespace IrcTokens
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the three parts of a hostmask. Parse with the constructor.
|
||||
/// Represents the three parts of a hostmask. Parse with the constructor.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
public Hostmask(string source)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (source == null) return;
|
||||
|
||||
_source = source;
|
||||
|
||||
|
@ -66,5 +34,31 @@ namespace IrcTokens
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,83 +2,34 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace IrcTokens
|
||||
{
|
||||
/// <summary>
|
||||
/// Tools to represent, parse, and format IRC lines
|
||||
/// Tools to represent, parse, and format IRC lines
|
||||
/// </summary>
|
||||
public class Line : IEquatable<Line>
|
||||
{
|
||||
public Dictionary<string, string> Tags { get; set; }
|
||||
public string Source { get; set; }
|
||||
public string Command { get; set; }
|
||||
public List<string> Params { get; set; }
|
||||
private static readonly string[] TagUnescaped = {"\\", " ", ";", "\r", "\n"};
|
||||
|
||||
private static readonly string[] TagEscaped = {"\\\\", "\\s", "\\:", "\\r", "\\n"};
|
||||
|
||||
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>
|
||||
/// 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>
|
||||
/// <param name="line">irc line to parse</param>
|
||||
public Line(string line)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(line));
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(line)) throw new ArgumentNullException(nameof(line));
|
||||
|
||||
string[] split;
|
||||
|
||||
|
@ -91,24 +42,22 @@ namespace IrcTokens
|
|||
line = string.Join(" ", split.Skip(1));
|
||||
|
||||
foreach (var part in messageTags.Substring(1).Split(';'))
|
||||
{
|
||||
if (part.Contains('=', StringComparison.Ordinal))
|
||||
{
|
||||
split = part.Split('=', 2);
|
||||
Tags[split[0]] = Protocol.UnescapeTag(split[1]);
|
||||
split = part.Split('=', 2);
|
||||
Tags[split[0]] = UnescapeTag(split[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Tags[part] = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string trailing;
|
||||
if (line.Contains(" :", StringComparison.Ordinal))
|
||||
{
|
||||
split = line.Split(" :", 2);
|
||||
line = split[0];
|
||||
split = line.Split(" :", 2);
|
||||
line = split[0];
|
||||
trailing = split[1];
|
||||
}
|
||||
else
|
||||
|
@ -118,7 +67,7 @@ namespace IrcTokens
|
|||
|
||||
Params = line.Contains(' ', StringComparison.Ordinal)
|
||||
? line.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList()
|
||||
: new List<string> { line };
|
||||
: new List<string> {line};
|
||||
|
||||
if (Params[0].StartsWith(':'))
|
||||
{
|
||||
|
@ -132,14 +81,102 @@ namespace IrcTokens
|
|||
Params.RemoveAt(0);
|
||||
}
|
||||
|
||||
if (trailing != null)
|
||||
{
|
||||
Params.Add(trailing);
|
||||
}
|
||||
if (trailing != null) 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>
|
||||
/// 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>
|
||||
/// <returns>formatted irc line</returns>
|
||||
public string Format()
|
||||
|
@ -149,16 +186,15 @@ namespace IrcTokens
|
|||
if (Tags != null && Tags.Any())
|
||||
{
|
||||
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();
|
||||
|
||||
outs.Add($"@{string.Join(";", tags)}");
|
||||
}
|
||||
|
||||
if (Source != null)
|
||||
{
|
||||
outs.Add($":{Source}");
|
||||
}
|
||||
if (Source != null) outs.Add($":{Source}");
|
||||
|
||||
outs.Add(Command);
|
||||
|
||||
|
@ -170,21 +206,17 @@ namespace IrcTokens
|
|||
foreach (var p in Params)
|
||||
{
|
||||
if (p.Contains(' ', StringComparison.Ordinal))
|
||||
{
|
||||
throw new ArgumentException(@"non-last parameters cannot have spaces", p);
|
||||
}
|
||||
|
||||
if (p.StartsWith(':'))
|
||||
{
|
||||
throw new ArgumentException(@"non-last parameters cannot start with colon", p);
|
||||
}
|
||||
}
|
||||
|
||||
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}";
|
||||
}
|
||||
|
||||
outs.Add(last);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,17 +11,20 @@ namespace IrcTokens
|
|||
private Encoding _encoding;
|
||||
private Encoding _fallback;
|
||||
|
||||
public StatefulDecoder()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public Encoding Encoding
|
||||
{
|
||||
get => _encoding ?? Encoding.GetEncoding(Encoding.UTF8.CodePage, EncoderFallback.ExceptionFallback,
|
||||
DecoderFallback.ExceptionFallback);
|
||||
DecoderFallback.ExceptionFallback);
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
_encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback,
|
||||
DecoderFallback.ReplacementFallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,20 +35,13 @@ namespace IrcTokens
|
|||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
_fallback = Encoding.GetEncoding(value.CodePage, EncoderFallback.ReplacementFallback,
|
||||
DecoderFallback.ReplacementFallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Pending => Encoding.GetString(_buffer);
|
||||
|
||||
public StatefulDecoder()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_buffer = Array.Empty<byte>();
|
||||
|
@ -58,16 +54,13 @@ namespace IrcTokens
|
|||
|
||||
public List<Line> Push(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (data == null || data.Length == 0) return null;
|
||||
|
||||
_buffer = _buffer.Concat(data).ToArray();
|
||||
|
||||
// simulate string.Split('\n') before decoding
|
||||
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)
|
||||
{
|
||||
|
@ -99,7 +92,6 @@ namespace IrcTokens
|
|||
|
||||
var decodeLines = new List<string>();
|
||||
foreach (var line in listLines.Select(l => l.ToArray()))
|
||||
{
|
||||
try
|
||||
{
|
||||
decodeLines.Add(Encoding.GetString(line));
|
||||
|
@ -108,7 +100,6 @@ namespace IrcTokens
|
|||
{
|
||||
decodeLines.Add(Fallback.GetString(line));
|
||||
}
|
||||
}
|
||||
|
||||
return decodeLines
|
||||
.Select(l => new Line(l))
|
||||
|
|
|
@ -7,8 +7,14 @@ namespace IrcTokens
|
|||
{
|
||||
public class StatefulEncoder
|
||||
{
|
||||
private Queue<Line> _bufferedLines;
|
||||
private Encoding _encoding;
|
||||
|
||||
public StatefulEncoder()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public Encoding Encoding
|
||||
{
|
||||
get => _encoding ?? Encoding.GetEncoding(Encoding.UTF8.CodePage, EncoderFallback.ExceptionFallback,
|
||||
|
@ -16,15 +22,11 @@ namespace IrcTokens
|
|||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
_encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback,
|
||||
DecoderFallback.ExceptionFallback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Queue<Line> _bufferedLines;
|
||||
|
||||
public byte[] PendingBytes { get; private set; }
|
||||
|
||||
public string Pending()
|
||||
|
@ -40,23 +42,15 @@ namespace IrcTokens
|
|||
}
|
||||
}
|
||||
|
||||
public StatefulEncoder()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
PendingBytes = Array.Empty<byte>();
|
||||
PendingBytes = Array.Empty<byte>();
|
||||
_bufferedLines = new Queue<Line>();
|
||||
}
|
||||
|
||||
public void Push(Line line)
|
||||
{
|
||||
if (line == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(line));
|
||||
}
|
||||
if (line == null) throw new ArgumentNullException(nameof(line));
|
||||
|
||||
PendingBytes = PendingBytes.Concat(Encoding.GetBytes($"{line.Format()}\r\n")).ToArray();
|
||||
_bufferedLines.Enqueue(line);
|
||||
|
@ -66,8 +60,8 @@ namespace IrcTokens
|
|||
{
|
||||
var sent = PendingBytes.Take(byteCount).Count(c => c == '\n');
|
||||
|
||||
PendingBytes = PendingBytes.Skip(byteCount).ToArray();
|
||||
_bufferedLines = new Queue<Line>(_bufferedLines.Skip(sent));
|
||||
PendingBytes = PendingBytes.Skip(byteCount).ToArray();
|
||||
_bufferedLines = new Queue<Line>(_bufferedLines.Take(sent));
|
||||
|
||||
return Enumerable.Range(0, sent)
|
||||
.Select(_ => _bufferedLines.Dequeue())
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace IrcTokens.Tests
|
||||
{
|
||||
|
@ -13,8 +13,8 @@ namespace IrcTokens.Tests
|
|||
var line = new Line
|
||||
{
|
||||
Command = "PRIVMSG",
|
||||
Params = new List<string> { "#channel", "hello" },
|
||||
Tags = new Dictionary<string, string> { { "id", "\\" + " " + ";" + "\r\n" } }
|
||||
Params = new List<string> {"#channel", "hello"},
|
||||
Tags = new Dictionary<string, string> {{"id", "\\" + " " + ";" + "\r\n"}}
|
||||
}.Format();
|
||||
|
||||
Assert.AreEqual("@id=\\\\\\s\\:\\r\\n PRIVMSG #channel hello", line);
|
||||
|
@ -23,11 +23,7 @@ namespace IrcTokens.Tests
|
|||
[TestMethod]
|
||||
public void TestMissingTag()
|
||||
{
|
||||
var line = new Line
|
||||
{
|
||||
Command = "PRIVMSG",
|
||||
Params = new List<string> { "#channel", "hello" }
|
||||
}.Format();
|
||||
var line = new Line {Command = "PRIVMSG", Params = new List<string> {"#channel", "hello"}}.Format();
|
||||
|
||||
Assert.AreEqual("PRIVMSG #channel hello", line);
|
||||
}
|
||||
|
@ -38,8 +34,8 @@ namespace IrcTokens.Tests
|
|||
var line = new Line
|
||||
{
|
||||
Command = "PRIVMSG",
|
||||
Params = new List<string> { "#channel", "hello" },
|
||||
Tags = new Dictionary<string, string> { { "a", null } }
|
||||
Params = new List<string> {"#channel", "hello"},
|
||||
Tags = new Dictionary<string, string> {{"a", null}}
|
||||
}.Format();
|
||||
|
||||
Assert.AreEqual("@a PRIVMSG #channel hello", line);
|
||||
|
@ -51,8 +47,8 @@ namespace IrcTokens.Tests
|
|||
var line = new Line
|
||||
{
|
||||
Command = "PRIVMSG",
|
||||
Params = new List<string> { "#channel", "hello" },
|
||||
Tags = new Dictionary<string, string> { { "a", "" } }
|
||||
Params = new List<string> {"#channel", "hello"},
|
||||
Tags = new Dictionary<string, string> {{"a", ""}}
|
||||
}.Format();
|
||||
|
||||
Assert.AreEqual("@a PRIVMSG #channel hello", line);
|
||||
|
@ -63,9 +59,7 @@ namespace IrcTokens.Tests
|
|||
{
|
||||
var line = new Line
|
||||
{
|
||||
Command = "PRIVMSG",
|
||||
Params = new List<string> { "#channel", "hello" },
|
||||
Source = "nick!user@host"
|
||||
Command = "PRIVMSG", Params = new List<string> {"#channel", "hello"}, Source = "nick!user@host"
|
||||
}.Format();
|
||||
|
||||
Assert.AreEqual(":nick!user@host PRIVMSG #channel hello", line);
|
||||
|
@ -74,25 +68,21 @@ namespace IrcTokens.Tests
|
|||
[TestMethod]
|
||||
public void TestCommandLowercase()
|
||||
{
|
||||
var line = new Line { Command = "privmsg" }.Format();
|
||||
var line = new Line {Command = "privmsg"}.Format();
|
||||
Assert.AreEqual("privmsg", line);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestCommandUppercase()
|
||||
{
|
||||
var line = new Line { Command = "PRIVMSG" }.Format();
|
||||
var line = new Line {Command = "PRIVMSG"}.Format();
|
||||
Assert.AreEqual("PRIVMSG", line);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestTrailingSpace()
|
||||
{
|
||||
var line = new Line
|
||||
{
|
||||
Command = "PRIVMSG",
|
||||
Params = new List<string> { "#channel", "hello world" }
|
||||
}.Format();
|
||||
var line = new Line {Command = "PRIVMSG", Params = new List<string> {"#channel", "hello world"}}.Format();
|
||||
|
||||
Assert.AreEqual("PRIVMSG #channel :hello world", line);
|
||||
}
|
||||
|
@ -100,11 +90,7 @@ namespace IrcTokens.Tests
|
|||
[TestMethod]
|
||||
public void TestTrailingNoSpace()
|
||||
{
|
||||
var line = new Line
|
||||
{
|
||||
Command = "PRIVMSG",
|
||||
Params = new List<string> { "#channel", "helloworld" }
|
||||
}.Format();
|
||||
var line = new Line {Command = "PRIVMSG", Params = new List<string> {"#channel", "helloworld"}}.Format();
|
||||
|
||||
Assert.AreEqual("PRIVMSG #channel helloworld", line);
|
||||
}
|
||||
|
@ -112,11 +98,7 @@ namespace IrcTokens.Tests
|
|||
[TestMethod]
|
||||
public void TestTrailingDoubleColon()
|
||||
{
|
||||
var line = new Line
|
||||
{
|
||||
Command = "PRIVMSG",
|
||||
Params = new List<string> { "#channel", ":helloworld" }
|
||||
}.Format();
|
||||
var line = new Line {Command = "PRIVMSG", Params = new List<string> {"#channel", ":helloworld"}}.Format();
|
||||
|
||||
Assert.AreEqual("PRIVMSG #channel ::helloworld", line);
|
||||
}
|
||||
|
@ -126,11 +108,7 @@ namespace IrcTokens.Tests
|
|||
{
|
||||
Assert.ThrowsException<ArgumentException>(() =>
|
||||
{
|
||||
new Line
|
||||
{
|
||||
Command = "USER",
|
||||
Params = new List<string> { "user", "0 *", "real name" }
|
||||
}.Format();
|
||||
new Line {Command = "USER", Params = new List<string> {"user", "0 *", "real name"}}.Format();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -139,11 +117,7 @@ namespace IrcTokens.Tests
|
|||
{
|
||||
Assert.ThrowsException<ArgumentException>(() =>
|
||||
{
|
||||
new Line
|
||||
{
|
||||
Command = "PRIVMSG",
|
||||
Params = new List<string> { ":#channel", "hello" }
|
||||
}.Format();
|
||||
new Line {Command = "PRIVMSG", Params = new List<string> {":#channel", "hello"}}.Format();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ namespace IrcTokens.Tests
|
|||
[TestMethod]
|
||||
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");
|
||||
Assert.AreEqual(hostmask.ToString(), line.Hostmask.ToString());
|
||||
Assert.AreEqual("nick", line.Hostmask.NickName);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
using IrcTokens.Tests.Data;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using IrcTokens.Tests.Data;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using YamlDotNet.Serialization;
|
||||
using YamlDotNet.Serialization.NamingConventions;
|
||||
|
||||
|
@ -26,13 +26,14 @@ namespace IrcTokens.Tests
|
|||
foreach (var test in LoadYaml<SplitModel>("Tests/Data/msg-split.yaml").Tests)
|
||||
{
|
||||
var tokens = new Line(test.Input);
|
||||
var atoms = test.Atoms;
|
||||
var atoms = test.Atoms;
|
||||
|
||||
Assert.AreEqual(atoms.Verb.ToUpper(CultureInfo.InvariantCulture), tokens.Command,
|
||||
$"command 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.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 line = new Line
|
||||
{
|
||||
Command = atoms.Verb,
|
||||
Params = atoms.Params,
|
||||
Source = atoms.Source,
|
||||
Tags = atoms.Tags
|
||||
Command = atoms.Verb, Params = atoms.Params, Source = atoms.Source, Tags = atoms.Tags
|
||||
}.Format();
|
||||
|
||||
Assert.IsTrue(test.Matches.Contains(line), test.Description);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace IrcTokens.Tests
|
||||
{
|
||||
|
@ -25,7 +25,7 @@ namespace IrcTokens.Tests
|
|||
Assert.AreEqual(1, lines.Count);
|
||||
|
||||
var line = new Line("PRIVMSG #channel hello");
|
||||
CollectionAssert.AreEqual(new List<Line> { line }, lines);
|
||||
CollectionAssert.AreEqual(new List<Line> {line}, lines);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -44,9 +44,9 @@ namespace IrcTokens.Tests
|
|||
public void TestEncoding()
|
||||
{
|
||||
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 line = new Line("PRIVMSG #channel :hello Ç");
|
||||
var line = new Line("PRIVMSG #channel :hello Ç");
|
||||
Assert.IsTrue(line.Equals(lines[0]));
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ namespace IrcTokens.Tests
|
|||
public void TestEncodingFallback()
|
||||
{
|
||||
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"));
|
||||
Assert.AreEqual(1, lines.Count);
|
||||
Assert.IsTrue(new Line("PRIVMSG #channel hélló").Equals(lines[0]));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace IrcTokens.Tests
|
||||
{
|
||||
|
@ -51,6 +51,19 @@ namespace IrcTokens.Tests
|
|||
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]
|
||||
public void TestClear()
|
||||
{
|
||||
|
@ -63,7 +76,7 @@ namespace IrcTokens.Tests
|
|||
public void TestEncoding()
|
||||
{
|
||||
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 Ç"));
|
||||
CollectionAssert.AreEqual(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"), _encoder.PendingBytes);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
@ -87,14 +87,14 @@ namespace IrcTokens.Tests
|
|||
public void TestParamsTrailing()
|
||||
{
|
||||
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]
|
||||
public void TestParamsOnlyTrailing()
|
||||
{
|
||||
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]
|
||||
|
@ -109,10 +109,10 @@ namespace IrcTokens.Tests
|
|||
public void TestAllTokens()
|
||||
{
|
||||
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("PRIVMSG", line.Command);
|
||||
CollectionAssert.AreEqual(new List<string> { "#channel", "hello world" }, line.Params);
|
||||
CollectionAssert.AreEqual(new List<string> {"#channel", "hello world"}, line.Params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue