IRC parsing, tokenization, and state handling in C#

1.5.0: .net10 and TUnit (#2)

Reviewed-on: https://tildegit.org/irctokens/ircsharp/pulls/2

+3326 -3348
+9 -10
.drone.yml
··· 1 - --- 2 - kind: pipeline 3 - name: run 4 - 5 - steps: 6 - - name: run 7 - image: mcr.microsoft.com/dotnet/sdk:latest 8 - commands: 9 - - dotnet test -l 'console;verbosity=detailed' 10 - 1 + --- 2 + kind: pipeline 3 + name: run 4 + 5 + steps: 6 + - name: run 7 + image: mcr.microsoft.com/dotnet/sdk:10.0 8 + commands: 9 + - dotnet test --configuration Release
+19
Directory.Build.props
··· 1 + <Project> 2 + <PropertyGroup> 3 + <ImplicitUsings>true</ImplicitUsings> 4 + <PackageReadmeFile>README.md</PackageReadmeFile> 5 + <TargetFramework>net10.0</TargetFramework> 6 + <PackageProjectUrl>https://irctokens.hmm.st/</PackageProjectUrl> 7 + <PackageLicenseExpression>MIT</PackageLicenseExpression> 8 + <Version>1.5.0</Version> 9 + <Authors>Ben Harris</Authors> 10 + <Company>tildeverse.org</Company> 11 + <RepositoryUrl>https://tildegit.org/irctokens/ircsharp</RepositoryUrl> 12 + <PublishRepositoryUrl>true</PublishRepositoryUrl> 13 + <EmbedUntrackedSources>true</EmbedUntrackedSources> 14 + <RepositoryType>git</RepositoryType> 15 + <PackageTags>irc</PackageTags> 16 + <PackageVersion>$(Version)</PackageVersion> 17 + <LangVersion>latest</LangVersion> 18 + </PropertyGroup> 19 + </Project>
+10
Directory.Packages.props
··· 1 + <Project> 2 + <PropertyGroup> 3 + <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> 4 + </PropertyGroup> 5 + <ItemGroup> 6 + <PackageVersion Include="Microsoft.SourceLink.Gitea" Version="8.0.0" /> 7 + <PackageVersion Include="TUnit" Version="1.2.11" /> 8 + <PackageVersion Include="YamlDotNet" Version="16.3.0" /> 9 + </ItemGroup> 10 + </Project>
+23 -10
Examples/States/Client.cs
··· 1 - using System; 2 1 using System.Net.Sockets; 3 - using System.Threading; 4 2 using IRCStates; 5 3 using IRCTokens; 4 + 6 5 // ReSharper disable StringLiteralTypo 7 6 8 7 namespace States; ··· 16 15 17 16 private void Send(string raw) 18 17 { 19 - _encoder.Push(new(raw)); 18 + _encoder.Push(new Line(raw)); 20 19 } 21 20 22 21 public void Start() 23 22 { 24 23 _socket.Connect(host, port); 25 - while (!_socket.Connected) Thread.Sleep(1000); 24 + while (!_socket.Connected) 25 + { 26 + Thread.Sleep(1000); 27 + } 26 28 27 29 Send("USER test 0 * test"); 28 30 Send($"NICK {nick}"); ··· 32 34 while (_encoder.PendingBytes.Length != 0) 33 35 { 34 36 foreach (var line in _encoder.Pop(_socket.Send(_encoder.PendingBytes))) 37 + { 35 38 Console.WriteLine($"> {line.Format()}"); 39 + } 36 40 } 37 41 38 42 var bytesReceived = _socket.Receive(_bytes); ··· 53 57 { 54 58 case Commands.Privmsg: 55 59 if (line.Params[1].Contains(_server.NickName)) 60 + { 56 61 Send($"PRIVMSG {line.Params[0]} :hi {line.Hostmask.NickName}!"); 62 + } 63 + 57 64 break; 58 - case "PING": 59 - Send($"PONG :{line.Params[0]}"); 60 - break; 65 + case "PING": Send($"PONG :{line.Params[0]}"); break; 61 66 case Numeric.RPL_WELCOME: 62 - if (!_server.HasChannel("#irctokens")) Send("JOIN #irctokens"); 67 + if (!_server.HasChannel("#irctokens")) 68 + { 69 + Send("JOIN #irctokens"); 70 + } 71 + 63 72 break; 64 73 case "INVITE": 65 74 var c = line.Params[1]; 66 - if (!_server.HasChannel(c)) Send($"JOIN {c}"); 75 + if (!_server.HasChannel(c)) 76 + { 77 + Send($"JOIN {c}"); 78 + } 79 + 67 80 break; 68 81 } 69 82 } 70 83 } 71 84 } 72 - } 85 + }
+1
Examples/States/Program.cs
··· 1 1 using States; 2 + 2 3 // ReSharper disable StringLiteralTypo 3 4 4 5 var client = new Client("localhost", 6667, "statesbot");
-1
Examples/States/States.csproj
··· 2 2 3 3 <PropertyGroup> 4 4 <OutputType>Exe</OutputType> 5 - <TargetFramework>net9.0</TargetFramework> 6 5 <IsPackable>false</IsPackable> 7 6 </PropertyGroup> 8 7
+13 -15
Examples/Tokens/Client.cs
··· 1 - using System; 2 1 using System.Net.Sockets; 3 - using System.Threading; 4 2 using IRCTokens; 3 + 5 4 // ReSharper disable StringLiteralTypo 6 5 7 6 namespace Tokens; ··· 16 15 public void Start() 17 16 { 18 17 _socket.Connect("127.0.0.1", 6667); 19 - while (!_socket.Connected) Thread.Sleep(1000); 18 + while (!_socket.Connected) 19 + { 20 + Thread.Sleep(1000); 21 + } 20 22 21 - Send(new("NICK", "tokensbot")); 22 - Send(new("USER", "tokensbot", "0", "*", "real name")); 23 + Send(new Line("NICK", "tokensbot")); 24 + Send(new Line("USER", "tokensbot", "0", "*", "real name")); 23 25 24 26 while (true) 25 27 { ··· 41 43 42 44 switch (line.Command) 43 45 { 44 - case "PING": 45 - Send(new("PONG", line.Params[0])); 46 - break; 47 - case "001": 48 - Send(new("JOIN", "#irctokens")); 49 - break; 50 - case "PRIVMSG": 51 - Send(new("PRIVMSG", line.Params[0], "hello there")); 52 - break; 46 + case "PING": Send(new Line("PONG", line.Params[0])); break; 47 + case "001": Send(new Line("JOIN", "#irctokens")); break; 48 + case "PRIVMSG": Send(new Line("PRIVMSG", line.Params[0], "hello there")); break; 53 49 } 54 50 } 55 51 } ··· 60 56 Console.WriteLine($"> {line.Format()}"); 61 57 _encoder.Push(line); 62 58 while (_encoder.PendingBytes.Length > 0) 59 + { 63 60 _encoder.Pop(_socket.Send(_encoder.PendingBytes, SocketFlags.None)); 61 + } 64 62 } 65 - } 63 + }
-1
Examples/Tokens/Program.cs
··· 1 1 // tokenization 2 2 3 - using System; 4 3 using IRCTokens; 5 4 using Tokens; 6 5
-1
Examples/Tokens/Tokens.csproj
··· 2 2 3 3 <PropertyGroup> 4 4 <OutputType>Exe</OutputType> 5 - <TargetFramework>net9.0</TargetFramework> 6 5 <IsPackable>false</IsPackable> 7 6 </PropertyGroup> 8 7
+5 -11
IRCSharp.Tests/IRCSharp.Tests.csproj
··· 1 - <Project Sdk="MSTest.Sdk/3.6.3"> 2 - 1 + <Project Sdk="Microsoft.NET.Sdk"> 3 2 <PropertyGroup> 4 - <TargetFramework>net9.0</TargetFramework> 5 - <ImplicitUsings>true</ImplicitUsings> 3 + <OutputType>Exe</OutputType> 6 4 <IsPackable>false</IsPackable> 5 + <TestingPlatformCommandLineArguments>--disable-logo</TestingPlatformCommandLineArguments> 7 6 </PropertyGroup> 8 - 9 7 <ItemGroup> 10 - <PackageReference Include="YamlDotNet" Version="16.2.0" /> 8 + <PackageReference Include="TUnit" /> 9 + <PackageReference Include="YamlDotNet" /> 11 10 </ItemGroup> 12 - 13 11 <ItemGroup> 14 12 <ProjectReference Include="..\IRCStates\IRCStates.csproj" /> 15 13 <ProjectReference Include="..\IRCTokens\IRCTokens.csproj" /> 16 14 </ItemGroup> 17 - 18 15 <ItemGroup> 19 16 <Using Include="IRCTokens" /> 20 17 <Using Include="IRCStates" /> 21 18 <Using Include="System.Text" /> 22 - <Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" /> 23 19 </ItemGroup> 24 - 25 20 <ItemGroup> 26 21 <None Update="Tokenization\Data\msg-*.yaml"> 27 22 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> 28 23 </None> 29 24 </ItemGroup> 30 - 31 25 </Project>
+79 -83
IRCSharp.Tests/State/Cap.cs
··· 1 1 namespace IRCSharp.Tests.State; 2 2 3 - [TestClass] 4 3 public class Cap 5 4 { 6 - private Server _server; 5 + private readonly Server _server = new("test"); 7 6 8 - [TestInitialize] 9 - public void TestInitialize() 7 + [Test] 8 + public async Task LSOneLine() 10 9 { 11 - _server = new("test"); 10 + await Assert.That(_server.HasCap).IsFalse(); 11 + await Assert.That(_server.AvailableCaps).IsEmpty(); 12 + _server.Parse(new Line("CAP * LS :a b")); 13 + await Assert.That(_server.AvailableCaps).ContainsKey("a").And.ContainsKey("b"); 14 + await Assert.That(_server.AvailableCaps) 15 + .IsEquivalentTo(new Dictionary<string, string> { { "a", "" }, { "b", "" } }); 12 16 } 13 17 14 - [TestMethod] 15 - public void LSOneLine() 18 + [Test] 19 + public async Task LsTwoLines() 16 20 { 17 - Assert.IsFalse(_server.HasCap); 18 - CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps); 19 - _server.Parse(new("CAP * LS :a b")); 20 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}}, _server.AvailableCaps); 21 + _server.Parse(new Line("CAP * LS * :a b")); 22 + await Assert.That(_server.AvailableCaps).IsEmpty(); 23 + _server.Parse(new Line("CAP * LS :c")); 24 + await Assert.That(_server.AvailableCaps).ContainsKey("a") 25 + .And.ContainsKey("b") 26 + .And.ContainsKey("c"); 21 27 } 22 28 23 - [TestMethod] 24 - public void LsTwoLines() 29 + [Test] 30 + public async Task LsValues() 25 31 { 26 - _server.Parse(new("CAP * LS * :a b")); 27 - CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps); 28 - _server.Parse(new("CAP * LS :c")); 29 - Assert.IsTrue(_server.AvailableCaps.ContainsKey("a")); 30 - Assert.IsTrue(_server.AvailableCaps.ContainsKey("b")); 31 - Assert.IsTrue(_server.AvailableCaps.ContainsKey("c")); 32 + _server.Parse(new Line("CAP * LS :a b= c=1")); 33 + await Assert.That(_server.AvailableCaps) 34 + .IsEquivalentTo(new Dictionary<string, string> { { "a", "" }, { "b", "" }, { "c", "1" } }); 32 35 } 33 36 34 - [TestMethod] 35 - public void LsValues() 36 - { 37 - _server.Parse(new("CAP * LS :a b= c=1")); 38 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}, {"c", "1"}}, 39 - _server.AvailableCaps); 40 - } 41 - 42 - [TestMethod] 43 - public void ACKOneLine() 37 + [Test] 38 + public async Task ACKOneLine() 44 39 { 45 - _server.Parse(new("CAP * LS :a b")); 46 - _server.Parse(new("CAP * ACK :a b")); 47 - CollectionAssert.AreEqual(new List<string> {"a", "b"}, _server.AgreedCaps); 40 + _server.Parse(new Line("CAP * LS :a b")); 41 + _server.Parse(new Line("CAP * ACK :a b")); 42 + await Assert.That(_server.AgreedCaps).Contains("a").And.Contains("b"); 48 43 } 49 44 50 - [TestMethod] 51 - public void ACKTwoLines() 45 + [Test] 46 + public async Task ACKTwoLines() 52 47 { 53 - _server.Parse(new("CAP * LS :a b c")); 54 - _server.Parse(new("CAP * ACK * :a b")); 55 - _server.Parse(new("CAP * ACK :c")); 56 - CollectionAssert.AreEqual(new List<string> {"a", "b", "c"}, _server.AgreedCaps); 48 + _server.Parse(new Line("CAP * LS :a b c")); 49 + _server.Parse(new Line("CAP * ACK * :a b")); 50 + _server.Parse(new Line("CAP * ACK :c")); 51 + await Assert.That(_server.AgreedCaps).IsEquivalentTo(["a", "b", "c"]); 57 52 } 58 53 59 - [TestMethod] 60 - public void ACKNotLs() 54 + [Test] 55 + public async Task ACKNotLs() 61 56 { 62 - _server.Parse(new("CAP * LS a")); 63 - _server.Parse(new("CAP * ACK b")); 64 - CollectionAssert.AreEqual(new List<string>(), _server.AgreedCaps); 57 + _server.Parse(new Line("CAP * LS a")); 58 + _server.Parse(new Line("CAP * ACK b")); 59 + await Assert.That(_server.AgreedCaps).IsEmpty(); 65 60 } 66 61 67 - [TestMethod] 68 - public void NewNoLs() 62 + [Test] 63 + public async Task NewNoLs() 69 64 { 70 - _server.Parse(new("CAP * NEW :a")); 71 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}}, _server.AvailableCaps); 65 + _server.Parse(new Line("CAP * NEW :a")); 66 + await Assert.That(_server.AvailableCaps).IsEquivalentTo(new Dictionary<string, string> { { "a", "" } }); 72 67 } 73 68 74 - [TestMethod] 75 - public void NewOneLine() 69 + [Test] 70 + public async Task NewOneLine() 76 71 { 77 - _server.Parse(new("CAP * LS :a")); 78 - _server.Parse(new("CAP * NEW :b")); 79 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}}, _server.AvailableCaps); 72 + _server.Parse(new Line("CAP * LS :a")); 73 + _server.Parse(new Line("CAP * NEW :b")); 74 + await Assert.That(_server.AvailableCaps) 75 + .IsEquivalentTo(new Dictionary<string, string> { { "a", "" }, { "b", "" } }); 80 76 } 81 77 82 - [TestMethod] 83 - public void NewTwoLines() 78 + [Test] 79 + public async Task NewTwoLines() 84 80 { 85 - _server.Parse(new("CAP * LS :a")); 86 - _server.Parse(new("CAP * NEW :b c")); 87 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"a", ""}, {"b", ""}, {"c", ""}}, 88 - _server.AvailableCaps); 81 + _server.Parse(new Line("CAP * LS :a")); 82 + _server.Parse(new Line("CAP * NEW :b c")); 83 + await Assert.That(_server.AvailableCaps) 84 + .IsEquivalentTo(new Dictionary<string, string> { { "a", "" }, { "b", "" }, { "c", "" } }); 89 85 } 90 86 91 - [TestMethod] 92 - public void DelNotAcked() 87 + [Test] 88 + public async Task DelNotAcked() 93 89 { 94 - _server.Parse(new("CAP * DEL a")); 90 + await Assert.That(() => _server.Parse(new Line("CAP * DEL a"))).ThrowsNothing(); 95 91 } 96 92 97 - [TestMethod] 98 - public void DELOneLS() 93 + [Test] 94 + public async Task DELOneLS() 99 95 { 100 - _server.Parse(new("CAP * LS :a")); 101 - _server.Parse(new("CAP * ACK :a")); 102 - _server.Parse(new("CAP * DEL :a")); 103 - CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps); 104 - CollectionAssert.AreEqual(new List<string>(), _server.AgreedCaps); 96 + _server.Parse(new Line("CAP * LS :a")); 97 + _server.Parse(new Line("CAP * ACK :a")); 98 + _server.Parse(new Line("CAP * DEL :a")); 99 + await Assert.That(_server.AvailableCaps).IsEmpty(); 100 + await Assert.That(_server.AgreedCaps).IsEmpty(); 105 101 } 106 102 107 - [TestMethod] 108 - public void DelTwoLs() 103 + [Test] 104 + public async Task DelTwoLs() 109 105 { 110 - _server.Parse(new("CAP * LS :a b")); 111 - _server.Parse(new("CAP * ACK :a b")); 112 - _server.Parse(new("CAP * DEL :a")); 113 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"b", ""}}, _server.AvailableCaps); 114 - CollectionAssert.AreEqual(new List<string> {"b"}, _server.AgreedCaps); 106 + _server.Parse(new Line("CAP * LS :a b")); 107 + _server.Parse(new Line("CAP * ACK :a b")); 108 + _server.Parse(new Line("CAP * DEL :a")); 109 + await Assert.That(_server.AvailableCaps).IsEquivalentTo(new Dictionary<string, string> { { "b", "" } }); 110 + await Assert.That(_server.AgreedCaps).IsEquivalentTo(["b"]); 115 111 } 116 112 117 - [TestMethod] 118 - public void DelTwoDel() 113 + [Test] 114 + public async Task DelTwoDel() 119 115 { 120 - _server.Parse(new("CAP * LS :a b")); 121 - _server.Parse(new("CAP * ACK :a b")); 122 - _server.Parse(new("CAP * DEL :a b")); 123 - CollectionAssert.AreEqual(new Dictionary<string, string>(), _server.AvailableCaps); 124 - CollectionAssert.AreEqual(new List<string>(), _server.AgreedCaps); 116 + _server.Parse(new Line("CAP * LS :a b")); 117 + _server.Parse(new Line("CAP * ACK :a b")); 118 + _server.Parse(new Line("CAP * DEL :a b")); 119 + await Assert.That(_server.AvailableCaps).IsEmpty(); 120 + await Assert.That(_server.AgreedCaps).IsEmpty(); 125 121 } 126 - } 122 + }
+32 -31
IRCSharp.Tests/State/Casemap.cs
··· 1 1 // ReSharper disable IdentifierTypo 2 2 // ReSharper disable StringLiteralTypo 3 + 3 4 namespace IRCSharp.Tests.State; 4 5 5 - [TestClass] 6 6 public class Casemap 7 7 { 8 - [TestMethod] 9 - public void Rfc1459() 8 + [Test] 9 + public async Task Rfc1459() 10 10 { 11 11 var lower = IRCStates.Casemap.CaseFold(IRCStates.Casemap.CaseMapping.Rfc1459, @"ÀTEST[]~\"); 12 - Assert.AreEqual("Àtest{}^|", lower); 12 + await Assert.That(lower).IsEqualTo("Àtest{}^|"); 13 13 } 14 14 15 - [TestMethod] 16 - public void Ascii() 15 + [Test] 16 + public async Task Ascii() 17 17 { 18 18 var lower = IRCStates.Casemap.CaseFold(IRCStates.Casemap.CaseMapping.Ascii, @"ÀTEST[]~\"); 19 - Assert.AreEqual(@"Àtest[]~\", lower); 19 + await Assert.That(lower).IsEqualTo(@"Àtest[]~\"); 20 20 } 21 21 22 - [TestMethod] 23 - public void CommandJoin() 22 + [Test] 23 + public async Task CommandJoin() 24 24 { 25 25 var server = new Server("test"); 26 - server.Parse(new("001 nickname")); 27 - server.Parse(new(":Nickname JOIN #Chan")); 28 - server.Parse(new(":Other JOIN #Chan")); 26 + server.Parse(new Line("001 nickname")); 27 + server.Parse(new Line(":Nickname JOIN #Chan")); 28 + server.Parse(new Line(":Other JOIN #Chan")); 29 29 30 - Assert.IsTrue(server.Users.ContainsKey("nickname")); 31 - Assert.IsFalse(server.Users.ContainsKey("Nickname")); 32 - Assert.IsTrue(server.Users.ContainsKey("other")); 33 - Assert.IsFalse(server.Users.ContainsKey("Other")); 34 - Assert.IsTrue(server.Channels.ContainsKey("#chan")); 35 - Assert.IsFalse(server.Channels.ContainsKey("#Chan")); 30 + await Assert.That(server.Users).ContainsKey("nickname"); 31 + await Assert.That(server.Users).ContainsKey("nickname"); 32 + await Assert.That(server.Users).DoesNotContainKey("Nickname"); 33 + await Assert.That(server.Users).ContainsKey("other"); 34 + await Assert.That(server.Users).DoesNotContainKey("Other"); 35 + await Assert.That(server.Channels).ContainsKey("#chan"); 36 + await Assert.That(server.Channels).DoesNotContainKey("#Chan"); 36 37 37 38 var channel = server.Channels["#chan"]; 38 - Assert.AreEqual("#Chan", channel.Name); 39 + await Assert.That(channel.Name).IsEqualTo("#Chan"); 39 40 } 40 41 41 - [TestMethod] 42 - public void CommandNick() 42 + [Test] 43 + public async Task CommandNick() 43 44 { 44 45 var server = new Server("test"); 45 - server.Parse(new("001 nickname")); 46 - server.Parse(new(":nickname JOIN #chan")); 46 + server.Parse(new Line("001 nickname")); 47 + server.Parse(new Line(":nickname JOIN #chan")); 47 48 var user = server.Users["nickname"]; 48 - server.Parse(new(":nickname NICK NewNickname")); 49 - Assert.AreEqual(1, server.Users.Count); 50 - Assert.IsTrue(server.Users.ContainsKey("newnickname")); 51 - Assert.AreEqual("NewNickname", user.NickName); 52 - Assert.AreEqual("newnickname", user.NickNameLower); 53 - Assert.AreEqual("NewNickname", server.NickName); 54 - Assert.AreEqual("newnickname", server.NickNameLower); 49 + server.Parse(new Line(":nickname NICK NewNickname")); 50 + await Assert.That(server.Users).HasCount(1); 51 + await Assert.That(server.Users).ContainsKey("newnickname"); 52 + await Assert.That(user.NickName).IsEqualTo("NewNickname"); 53 + await Assert.That(user.NickNameLower).IsEqualTo("newnickname"); 54 + await Assert.That(server.NickName).IsEqualTo("NewNickname"); 55 + await Assert.That(server.NickNameLower).IsEqualTo("newnickname"); 55 56 } 56 - } 57 + }
+105 -104
IRCSharp.Tests/State/Channel.cs
··· 1 1 namespace IRCSharp.Tests.State; 2 2 3 - [TestClass] 4 3 public class Channel 5 4 { 6 - private Server _server; 5 + private readonly Server _server = new("test"); 7 6 8 - [TestInitialize] 9 - public void TestInitialize() 7 + public Channel() 10 8 { 11 - _server = new("test"); 12 - _server.Parse(new("001 nickname")); 13 - _server.Parse(new(":nickname JOIN #chan")); 9 + _server.Parse(new Line("001 nickname")); 10 + _server.Parse(new Line(":nickname JOIN #chan")); 14 11 } 15 12 16 - [TestMethod] 17 - public void JoinSelf() 13 + [Test] 14 + public async Task JoinSelf() 18 15 { 19 - Assert.IsTrue(_server.Channels.ContainsKey("#chan")); 20 - Assert.IsTrue(_server.Users.ContainsKey("nickname")); 21 - Assert.AreEqual(1, _server.Channels.Count); 22 - Assert.AreEqual(1, _server.Users.Count); 16 + await Assert.That(_server.Channels).ContainsKey("#chan"); 17 + await Assert.That(_server.Users).ContainsKey("nickname"); 18 + await Assert.That(_server.Channels).HasCount(1); 19 + await Assert.That(_server.Users).HasCount(1); 23 20 24 21 var user = _server.Users["nickname"]; 25 22 var chan = _server.Channels["#chan"]; 26 - Assert.IsTrue(chan.Users.ContainsKey(user.NickNameLower)); 27 - CollectionAssert.AreEqual(new List<string> {chan.NameLower}, user.Channels.ToList()); 23 + await Assert.That(chan.Users).ContainsKey(user.NickNameLower).And.HasCount(1); 24 + await Assert.That(user.Channels).Contains(chan.NameLower).And.HasCount(1); 28 25 } 29 26 30 - [TestMethod] 31 - public void JoinOther() 27 + [Test] 28 + public async Task JoinOther() 32 29 { 33 - _server.Parse(new(":other JOIN #chan")); 30 + _server.Parse(new Line(":other JOIN #chan")); 34 31 35 - Assert.AreEqual(2, _server.Users.Count); 36 - Assert.IsTrue(_server.Users.ContainsKey("other")); 32 + await Assert.That(_server.Users).HasCount(2); 33 + await Assert.That(_server.Users).ContainsKey("other"); 37 34 38 35 var channel = _server.Channels["#chan"]; 39 - Assert.AreEqual(2, channel.Users.Count); 36 + await Assert.That(channel.Users).HasCount(2); 40 37 41 38 var user = _server.Users["other"]; 42 - CollectionAssert.AreEqual(new List<string> {channel.NameLower}, user.Channels.ToList()); 39 + await Assert.That(user.Channels).Contains(channel.NameLower).And.HasCount(1); 43 40 } 44 41 45 - [TestMethod] 46 - public void PartSelf() 42 + [Test] 43 + public async Task PartSelf() 47 44 { 48 - _server.Parse(new(":nickname PART #chan")); 45 + _server.Parse(new Line(":nickname PART #chan")); 49 46 50 - Assert.AreEqual(0, _server.Users.Count); 51 - Assert.AreEqual(0, _server.Channels.Count); 47 + await Assert.That(_server.Users).IsEmpty(); 48 + await Assert.That(_server.Channels).IsEmpty(); 52 49 } 53 50 54 - [TestMethod] 55 - public void PartOther() 51 + [Test] 52 + public async Task PartOther() 56 53 { 57 - _server.Parse(new(":other JOIN #chan")); 58 - _server.Parse(new(":other PART #chan")); 54 + _server.Parse(new Line(":other JOIN #chan")); 55 + _server.Parse(new Line(":other PART #chan")); 59 56 60 57 var user = _server.Users["nickname"]; 61 58 var channel = _server.Channels["#chan"]; 62 59 var chanUser = channel.Users[user.NickNameLower]; 63 60 64 - Assert.AreEqual(channel.NameLower, user.Channels.Single()); 65 - CollectionAssert.AreEqual(new Dictionary<string, IRCStates.User> {{"nickname", user}}, _server.Users); 66 - CollectionAssert.AreEqual(new Dictionary<string, IRCStates.Channel> {{"#chan", channel}}, _server.Channels); 67 - CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{"nickname", chanUser}}, channel.Users); 61 + await Assert.That(user.Channels).Contains(channel.NameLower).And.HasCount(1); 62 + await Assert.That(_server.Users) 63 + .IsEquivalentTo(new Dictionary<string, IRCStates.User> { { "nickname", user } }); 64 + await Assert.That(_server.Channels) 65 + .IsEquivalentTo(new Dictionary<string, IRCStates.Channel> { { "#chan", channel } }); 66 + await Assert.That(channel.Users) 67 + .IsEquivalentTo(new Dictionary<string, ChannelUser> { { "nickname", chanUser } }); 68 68 } 69 69 70 - [TestMethod] 71 - public void KickSelf() 70 + [Test] 71 + public async Task KickSelf() 72 72 { 73 - _server.Parse(new(":nickname KICK #chan nickname")); 73 + _server.Parse(new Line(":nickname KICK #chan nickname")); 74 74 75 - Assert.AreEqual(0, _server.Users.Count); 76 - Assert.AreEqual(0, _server.Channels.Count); 75 + await Assert.That(_server.Users).IsEmpty(); 76 + await Assert.That(_server.Channels).IsEmpty(); 77 77 } 78 78 79 - [TestMethod] 80 - public void KickOther() 79 + [Test] 80 + public async Task KickOther() 81 81 { 82 - _server.Parse(new(":other JOIN #chan")); 83 - _server.Parse(new(":nickname KICK #chan other")); 82 + _server.Parse(new Line(":other JOIN #chan")); 83 + _server.Parse(new Line(":nickname KICK #chan other")); 84 84 85 85 var user = _server.Users["nickname"]; 86 86 var channel = _server.Channels["#chan"]; 87 87 var chanUser = channel.Users[user.NickNameLower]; 88 88 89 - Assert.AreEqual(1, _server.Users.Count); 90 - Assert.AreEqual(1, _server.Channels.Count); 91 - Assert.AreEqual(channel.NameLower, user.Channels.Single()); 92 - CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{user.NickNameLower, chanUser}}, 93 - channel.Users); 89 + await Assert.That(_server.Users).HasCount(1); 90 + await Assert.That(_server.Channels).HasCount(1); 91 + await Assert.That(user.Channels).Contains(channel.NameLower).And.HasCount(1); 92 + await Assert.That(channel.Users) 93 + .IsEquivalentTo(new Dictionary<string, ChannelUser> { { user.NickNameLower, chanUser } }); 94 94 } 95 95 96 - [TestMethod] 97 - public void QuitSelf() 96 + [Test] 97 + public async Task QuitSelf() 98 98 { 99 - _server.Parse(new("QUIT :i'm outta here")); 100 - Assert.AreEqual(0, _server.Users.Count); 101 - Assert.AreEqual(0, _server.Channels.Count); 99 + _server.Parse(new Line("QUIT :i'm outta here")); 100 + await Assert.That(_server.Users).IsEmpty(); 101 + await Assert.That(_server.Channels).IsEmpty(); 102 102 } 103 103 104 - [TestMethod] 105 - public void QuitSelfWithSource() 104 + [Test] 105 + public async Task QuitSelfWithSource() 106 106 { 107 - _server.Parse(new(":nickname QUIT :i'm outta here")); 108 - Assert.AreEqual(0, _server.Users.Count); 109 - Assert.AreEqual(0, _server.Channels.Count); 107 + _server.Parse(new Line(":nickname QUIT :i'm outta here")); 108 + await Assert.That(_server.Users).IsEmpty(); 109 + await Assert.That(_server.Channels).IsEmpty(); 110 110 } 111 111 112 - [TestMethod] 113 - public void QuitOther() 112 + [Test] 113 + public async Task QuitOther() 114 114 { 115 - _server.Parse(new(":other JOIN #chan")); 116 - _server.Parse(new(":other QUIT :see ya")); 117 - Assert.IsFalse(_server.Users.ContainsKey("other")); 115 + _server.Parse(new Line(":other JOIN #chan")); 116 + _server.Parse(new Line(":other QUIT :see ya")); 117 + await Assert.That(_server.Users).DoesNotContainKey("other"); 118 118 } 119 119 120 - [TestMethod] 121 - public void TopicText() 120 + [Test] 121 + public async Task TopicText() 122 122 { 123 - _server.Parse(new("332 * #chan :test")); 124 - Assert.AreEqual("test", _server.Channels["#chan"].Topic); 123 + _server.Parse(new Line("332 * #chan :test")); 124 + await Assert.That(_server.Channels["#chan"].Topic).IsEqualTo("test"); 125 125 } 126 126 127 - [TestMethod] 128 - public void TopicSetByAt() 127 + [Test] 128 + public async Task TopicSetByAt() 129 129 { 130 130 var dt = DateTimeOffset.FromUnixTimeSeconds(1584023277).DateTime; 131 - _server.Parse(new("333 * #chan other 1584023277")); 131 + _server.Parse(new Line("333 * #chan other 1584023277")); 132 132 133 133 var channel = _server.Channels["#chan"]; 134 134 135 - Assert.AreEqual("other", channel.TopicSetter); 136 - Assert.AreEqual(dt, channel.TopicTime); 135 + await Assert.That(channel.TopicSetter).IsEqualTo("other"); 136 + await Assert.That(channel.TopicTime).IsEqualTo(dt); 137 137 } 138 138 139 - [TestMethod] 140 - public void TopicCommand() 139 + [Test] 140 + public async Task TopicCommand() 141 141 { 142 - _server.Parse(new("TOPIC #chan :hello there")); 143 - Assert.AreEqual("hello there", _server.Channels["#chan"].Topic); 142 + _server.Parse(new Line("TOPIC #chan :hello there")); 143 + await Assert.That(_server.Channels["#chan"].Topic).IsEqualTo("hello there"); 144 144 } 145 145 146 - [TestMethod] 147 - public void CreationDate() 146 + [Test] 147 + public async Task CreationDate() 148 148 { 149 - _server.Parse(new("329 * #chan 1584041889")); 150 - Assert.AreEqual(DateTimeOffset.FromUnixTimeSeconds(1584041889).DateTime, _server.Channels["#chan"].Created); 149 + var dt = DateTimeOffset.FromUnixTimeSeconds(1584041889).DateTime; 150 + _server.Parse(new Line("329 * #chan 1584041889")); 151 + await Assert.That(_server.Channels["#chan"].Created).IsEqualTo(dt); 151 152 } 152 153 153 - [TestMethod] 154 - public void NamesCommand() 154 + [Test] 155 + public async Task NamesCommand() 155 156 { 156 - _server.Parse(new("353 * * #chan :nickname @+other")); 157 - Assert.IsTrue(_server.Users.ContainsKey("nickname")); 158 - Assert.IsTrue(_server.Users.ContainsKey("other")); 157 + _server.Parse(new Line("353 * * #chan :nickname @+other")); 158 + await Assert.That(_server.Users).ContainsKey("nickname"); 159 + await Assert.That(_server.Users).ContainsKey("other"); 159 160 160 161 var user = _server.Users["other"]; 161 162 var channel = _server.Channels["#chan"]; 162 163 var chanUser1 = channel.Users[user.NickNameLower]; 163 164 var chanUser2 = channel.Users[_server.NickNameLower]; 164 165 165 - Assert.AreEqual(2, channel.Users.Count); 166 - CollectionAssert.AreEqual(chanUser1.Modes, channel.Users[user.NickNameLower].Modes); 167 - CollectionAssert.AreEqual(chanUser2.Modes, channel.Users[_server.NickNameLower].Modes); 168 - CollectionAssert.AreEqual(new List<string> {"o", "v"}, chanUser1.Modes); 169 - Assert.AreEqual(channel.NameLower, user.Channels.Single()); 166 + await Assert.That(channel.Users).HasCount(2); 167 + await Assert.That(channel.Users[user.NickNameLower].Modes).IsEqualTo(chanUser1.Modes); 168 + await Assert.That(channel.Users[_server.NickNameLower].Modes).IsEqualTo(chanUser2.Modes); 169 + await Assert.That(chanUser1.Modes).IsEquivalentTo(["o", "v"]); 170 + await Assert.That(user.Channels).Contains(channel.NameLower).And.HasCount(1); 170 171 } 171 172 172 - [TestMethod] 173 - public void UserhostInNames() 173 + [Test] 174 + public async Task UserhostInNames() 174 175 { 175 - _server.Parse(new("353 * * #chan :nickname!user@host other!user2@host2")); 176 - Assert.AreEqual("user", _server.UserName); 177 - Assert.AreEqual("host", _server.HostName); 176 + _server.Parse(new Line("353 * * #chan :nickname!user@host other!user2@host2")); 177 + await Assert.That(_server.UserName).IsEqualTo("user"); 178 + await Assert.That(_server.HostName).IsEqualTo("host"); 178 179 179 180 var user = _server.Users["other"]; 180 - Assert.AreEqual("user2", user.UserName); 181 - Assert.AreEqual("host2", user.HostName); 181 + await Assert.That(user.UserName).IsEqualTo("user2"); 182 + await Assert.That(user.HostName).IsEqualTo("host2"); 182 183 } 183 184 184 - [TestMethod] 185 - public void NickAfterJoin() 185 + [Test] 186 + public async Task NickAfterJoin() 186 187 { 187 188 var user = _server.Users["nickname"]; 188 189 var channel = _server.Channels["#chan"]; 189 190 var chanUser = channel.Users[user.NickNameLower]; 190 - _server.Parse(new(":nickname NICK nickname2")); 191 - CollectionAssert.AreEqual(new Dictionary<string, ChannelUser> {{user.NickNameLower, chanUser}}, 192 - channel.Users); 191 + _server.Parse(new Line(":nickname NICK nickname2")); 192 + await Assert.That(channel.Users) 193 + .IsEquivalentTo(new Dictionary<string, ChannelUser> { { user.NickNameLower, chanUser } }); 193 194 } 194 - } 195 + }
+64 -76
IRCSharp.Tests/State/Emit.cs
··· 1 1 namespace IRCSharp.Tests.State; 2 2 3 - [TestClass] 4 3 public class Emit 5 4 { 6 - private Server _server; 5 + private readonly Server _server = new("test"); 7 6 8 - [TestInitialize] 9 - public void TestInitialize() 7 + public Emit() 10 8 { 11 - _server = new("test"); 12 - _server.Parse(new("001 nickname")); 9 + _server.Parse(new Line("001 nickname")); 13 10 } 14 11 15 - [TestMethod] 16 - public void EmitJoin() 12 + [Test] 13 + public async Task EmitJoin() 17 14 { 18 - var emit = _server.Parse(new(":nickname JOIN #chan")); 15 + var emit = _server.Parse(new Line(":nickname JOIN #chan")); 19 16 20 - Assert.AreEqual("JOIN", emit.Command); 21 - Assert.IsTrue(emit.Self); 22 - Assert.AreEqual(_server.Users["nickname"], emit.User); 23 - Assert.AreEqual(_server.Channels["#chan"], emit.Channel); 17 + await Assert.That(emit.Command).IsEqualTo("JOIN"); 18 + await Assert.That(emit.Self).IsTrue(); 19 + await Assert.That(emit.User).IsEqualTo(_server.Users["nickname"]); 20 + await Assert.That(emit.Channel).IsEqualTo(_server.Channels["#chan"]); 24 21 25 - emit = _server.Parse(new(":other JOIN #chan")); 26 - Assert.IsNotNull(emit); 27 - Assert.AreEqual("JOIN", emit.Command); 28 - Assert.IsFalse(emit.Self); 29 - Assert.AreEqual(_server.Users["other"], emit.User); 30 - Assert.AreEqual(_server.Channels["#chan"], emit.Channel); 22 + emit = _server.Parse(new Line(":other JOIN #chan")); 23 + await Assert.That(emit).IsNotNull(); 24 + await Assert.That(emit.Command).IsEqualTo("JOIN"); 25 + await Assert.That(emit.Self).IsFalse(); 26 + await Assert.That(emit.User).IsEqualTo(_server.Users["other"]); 27 + await Assert.That(emit.Channel).IsEqualTo(_server.Channels["#chan"]); 31 28 } 32 29 33 - [TestMethod] 34 - public void EmitPrivmsg() 30 + [Test] 31 + public async Task EmitPrivmsg() 35 32 { 36 - _server.Parse(new(":nickname JOIN #chan")); 37 - var emit = _server.Parse(new(":nickname PRIVMSG #chan :hello")); 33 + _server.Parse(new Line(":nickname JOIN #chan")); 34 + var emit = _server.Parse(new Line(":nickname PRIVMSG #chan :hello")); 38 35 39 - Assert.IsNotNull(emit); 40 - Assert.AreEqual("PRIVMSG", emit.Command); 41 - Assert.AreEqual("hello", emit.Text); 42 - Assert.IsTrue(emit.SelfSource); 43 - Assert.AreEqual(_server.Users["nickname"], emit.User); 44 - Assert.AreEqual(_server.Channels["#chan"], emit.Channel); 36 + await Assert.That(emit).IsNotNull(); 37 + await Assert.That(emit.Command).IsEqualTo("PRIVMSG"); 38 + await Assert.That(emit.Text).IsEqualTo("hello"); 39 + await Assert.That(emit.SelfSource).IsTrue(); 40 + await Assert.That(emit.User).IsEqualTo(_server.Users["nickname"]); 41 + await Assert.That(emit.Channel).IsEqualTo(_server.Channels["#chan"]); 45 42 46 - _server.Parse(new(":other JOIN #chan")); 47 - emit = _server.Parse(new(":other PRIVMSG #chan :hello2")); 43 + _server.Parse(new Line(":other JOIN #chan")); 44 + emit = _server.Parse(new Line(":other PRIVMSG #chan :hello2")); 48 45 49 - Assert.IsNotNull(emit); 50 - Assert.AreEqual("PRIVMSG", emit.Command); 51 - Assert.AreEqual("hello2", emit.Text); 52 - Assert.IsFalse(emit.SelfSource); 53 - Assert.AreEqual(_server.Users["other"], emit.User); 54 - Assert.AreEqual(_server.Channels["#chan"], emit.Channel); 46 + await Assert.That(emit).IsNotNull(); 47 + await Assert.That(emit.Command).IsEqualTo("PRIVMSG"); 48 + await Assert.That(emit.Text).IsEqualTo("hello2"); 49 + await Assert.That(emit.SelfSource).IsFalse(); 50 + await Assert.That(emit.User).IsEqualTo(_server.Users["other"]); 51 + await Assert.That(emit.Channel).IsEqualTo(_server.Channels["#chan"]); 55 52 } 56 53 57 - [TestMethod] 58 - public void EmitPrivmsgNoJoin() 54 + [Test] 55 + public async Task EmitPrivmsgNoJoin() 59 56 { 60 - _server.Parse(new(":nickname JOIN #chan")); 61 - var emit = _server.Parse(new(":other PRIVMSG #chan :hello")); 57 + _server.Parse(new Line(":nickname JOIN #chan")); 58 + var emit = _server.Parse(new Line(":other PRIVMSG #chan :hello")); 62 59 63 - Assert.IsNotNull(emit); 64 - Assert.AreEqual("PRIVMSG", emit.Command); 65 - Assert.AreEqual("hello", emit.Text); 66 - Assert.IsFalse(emit.SelfSource); 67 - Assert.IsNotNull(emit.User); 60 + await Assert.That(emit).IsNotNull(); 61 + await Assert.That(emit.Command).IsEqualTo("PRIVMSG"); 62 + await Assert.That(emit.Text).IsEqualTo("hello"); 63 + await Assert.That(emit.SelfSource).IsFalse(); 64 + await Assert.That(emit.User).IsNotNull(); 68 65 69 66 var channel = _server.Channels["#chan"]; 70 - Assert.AreEqual(channel, emit.Channel); 67 + await Assert.That(emit.Channel).IsEqualTo(channel); 71 68 } 72 69 73 - [TestMethod] 74 - public void EmitKick() 70 + [Test] 71 + public async Task EmitKick() 75 72 { 76 - _server.Parse(new(":nickname JOIN #chan")); 73 + _server.Parse(new Line(":nickname JOIN #chan")); 77 74 78 75 var user = _server.Users["nickname"]; 79 76 var channel = _server.Channels["#chan"]; 80 - _server.Parse(new(":other JOIN #chan")); 77 + _server.Parse(new Line(":other JOIN #chan")); 81 78 var userOther = _server.Users["other"]; 82 - var emit = _server.Parse(new(":nickname KICK #chan other :reason")); 79 + var emit = _server.Parse(new Line(":nickname KICK #chan other :reason")); 83 80 84 - Assert.IsNotNull(emit); 85 - Assert.AreEqual("KICK", emit.Command); 86 - Assert.AreEqual("reason", emit.Text); 87 - Assert.IsTrue(emit.SelfSource); 88 - Assert.AreEqual(user, emit.UserSource); 89 - Assert.AreEqual(userOther, emit.UserTarget); 90 - Assert.AreEqual(channel, emit.Channel); 81 + await Assert.That(emit).IsNotNull(); 82 + await Assert.That(emit.Command).IsEqualTo("KICK"); 83 + await Assert.That(emit.Text).IsEqualTo("reason"); 84 + await Assert.That(emit.SelfSource).IsTrue(); 85 + await Assert.That(emit.UserSource).IsEqualTo(user); 86 + await Assert.That(emit.UserTarget).IsEqualTo(userOther); 87 + await Assert.That(emit.Channel).IsEqualTo(channel); 91 88 } 92 89 93 - [TestMethod] 94 - public void EmitMode() 90 + [Test] 91 + public async Task EmitMode() 95 92 { 96 - var emit = _server.Parse(new("MODE nickname x+i-i+wi-wi")); 93 + var emit = _server.Parse(new Line("MODE nickname x+i-i+wi-wi")); 97 94 98 - Assert.IsNotNull(emit); 99 - Assert.AreEqual("MODE", emit.Command); 100 - Assert.IsTrue(emit.SelfTarget); 101 - CollectionAssert.AreEqual(new List<string> 102 - { 103 - "+x", 104 - "+i", 105 - "-i", 106 - "+w", 107 - "+i", 108 - "-w", 109 - "-i" 110 - }, emit.Tokens); 95 + await Assert.That(emit).IsNotNull(); 96 + await Assert.That(emit.Command).IsEqualTo("MODE"); 97 + await Assert.That(emit.SelfTarget).IsTrue(); 98 + await Assert.That(emit.Tokens).IsEquivalentTo(["+x", "+i", "-i", "+w", "+i", "-w", "-i"]); 111 99 } 112 - } 100 + }
+108 -131
IRCSharp.Tests/State/ISupport.cs
··· 1 1 // ReSharper disable InconsistentNaming 2 2 // ReSharper disable StringLiteralTypo 3 + 3 4 namespace IRCSharp.Tests.State; 4 5 5 - [TestClass] 6 6 public class ISupport 7 7 { 8 - private Server _server; 8 + private readonly Server _server = new("test"); 9 9 10 - [TestInitialize] 11 - public void TestInitialize() 10 + public ISupport() 12 11 { 13 - _server = new("test"); 14 - _server.Parse(new("001 nickname")); 12 + _server.Parse(new Line("001 nickname")); 15 13 } 16 14 17 - [TestMethod] 18 - public void ChanModes() 15 + [Test] 16 + public async Task ChanModes() 19 17 { 20 - CollectionAssert.AreEqual(new List<string> {"b"}, _server.ISupport.ChanModes.ListModes); 21 - CollectionAssert.AreEqual(new List<string> {"k"}, _server.ISupport.ChanModes.SettingBModes); 22 - CollectionAssert.AreEqual(new List<string> {"l"}, _server.ISupport.ChanModes.SettingCModes); 23 - CollectionAssert.AreEqual(new List<string> 24 - { 25 - "i", 26 - "m", 27 - "n", 28 - "p", 29 - "s", 30 - "t" 31 - }, _server.ISupport.ChanModes.SettingDModes); 18 + await Assert.That(_server.ISupport.ChanModes.ListModes).IsEquivalentTo(["b"]); 19 + await Assert.That(_server.ISupport.ChanModes.SettingBModes).IsEquivalentTo(["k"]); 20 + await Assert.That(_server.ISupport.ChanModes.SettingCModes).IsEquivalentTo(["l"]); 21 + await Assert.That(_server.ISupport.ChanModes.SettingDModes).IsEquivalentTo(["i", "m", "n", "p", "s", "t"]); 32 22 33 - _server.Parse(new("005 * CHANMODES=a,b,c,d *")); 23 + _server.Parse(new Line("005 * CHANMODES=a,b,c,d *")); 34 24 35 - CollectionAssert.AreEqual(new List<string> {"a"}, _server.ISupport.ChanModes.ListModes); 36 - CollectionAssert.AreEqual(new List<string> {"b"}, _server.ISupport.ChanModes.SettingBModes); 37 - CollectionAssert.AreEqual(new List<string> {"c"}, _server.ISupport.ChanModes.SettingCModes); 38 - CollectionAssert.AreEqual(new List<string> {"d"}, _server.ISupport.ChanModes.SettingDModes); 25 + await Assert.That(_server.ISupport.ChanModes.ListModes).IsEquivalentTo(["a"]); 26 + await Assert.That(_server.ISupport.ChanModes.SettingBModes).IsEquivalentTo(["b"]); 27 + await Assert.That(_server.ISupport.ChanModes.SettingCModes).IsEquivalentTo(["c"]); 28 + await Assert.That(_server.ISupport.ChanModes.SettingDModes).IsEquivalentTo(["d"]); 39 29 } 40 30 41 - [TestMethod] 42 - public void Prefix() 31 + [Test] 32 + public async Task Prefix() 43 33 { 44 - CollectionAssert.AreEqual(new List<string> {"o", "v"}, _server.ISupport.Prefix.Modes); 45 - CollectionAssert.AreEqual(new List<string> {"@", "+"}, _server.ISupport.Prefix.Prefixes); 34 + await Assert.That(_server.ISupport.Prefix.Modes).IsEquivalentTo(["o", "v"]); 35 + await Assert.That(_server.ISupport.Prefix.Prefixes).IsEquivalentTo(["@", "+"]); 46 36 47 - Assert.AreEqual("@", _server.ISupport.Prefix.FromMode("o")); 48 - Assert.IsNull(_server.ISupport.Prefix.FromMode("a")); 49 - Assert.AreEqual("o", _server.ISupport.Prefix.FromPrefix("@")); 50 - Assert.IsNull(_server.ISupport.Prefix.FromPrefix("&")); 37 + await Assert.That(_server.ISupport.Prefix.FromMode("o")).IsEqualTo("@"); 38 + await Assert.That(_server.ISupport.Prefix.FromMode("a")).IsNull(); 39 + await Assert.That(_server.ISupport.Prefix.FromPrefix("@")).IsEqualTo("o"); 40 + await Assert.That(_server.ISupport.Prefix.FromPrefix("&")).IsNull(); 51 41 52 - _server.Parse(new("005 * PREFIX=(qaohv)~&@%+ *")); 53 - CollectionAssert.AreEqual(new List<string> 54 - { 55 - "q", 56 - "a", 57 - "o", 58 - "h", 59 - "v" 60 - }, _server.ISupport.Prefix.Modes); 61 - CollectionAssert.AreEqual(new List<string> 62 - { 63 - "~", 64 - "&", 65 - "@", 66 - "%", 67 - "+" 68 - }, _server.ISupport.Prefix.Prefixes); 69 - Assert.AreEqual("&", _server.ISupport.Prefix.FromMode("a")); 70 - Assert.AreEqual("a", _server.ISupport.Prefix.FromPrefix("&")); 42 + _server.Parse(new Line("005 * PREFIX=(qaohv)~&@%+ *")); 43 + await Assert.That(_server.ISupport.Prefix.Modes).IsEquivalentTo(["q", "a", "o", "h", "v"]); 44 + await Assert.That(_server.ISupport.Prefix.Prefixes).IsEquivalentTo(["~", "&", "@", "%", "+"]); 45 + 46 + await Assert.That(_server.ISupport.Prefix.FromMode("a")).IsEqualTo("&"); 47 + await Assert.That(_server.ISupport.Prefix.FromPrefix("&")).IsEqualTo("a"); 71 48 } 72 49 73 - [TestMethod] 74 - public void ChanTypes() 50 + [Test] 51 + public async Task ChanTypes() 75 52 { 76 - CollectionAssert.AreEqual(new List<string> {"#"}, _server.ISupport.ChanTypes); 77 - _server.Parse(new("005 * CHANTYPES=#& *")); 78 - CollectionAssert.AreEqual(new List<string> {"#", "&"}, _server.ISupport.ChanTypes); 53 + await Assert.That(_server.ISupport.ChanTypes).IsEquivalentTo(["#"]); 54 + _server.Parse(new Line("005 * CHANTYPES=#& *")); 55 + await Assert.That(_server.ISupport.ChanTypes).IsEquivalentTo(["#", "&"]); 79 56 } 80 57 81 - [TestMethod] 82 - public void Modes() 58 + [Test] 59 + public async Task Modes() 83 60 { 84 - Assert.AreEqual(3, _server.ISupport.Modes); 61 + await Assert.That(_server.ISupport.Modes).IsEqualTo(3); 85 62 86 - _server.Parse(new("005 * MODES *")); 87 - Assert.AreEqual(-1, _server.ISupport.Modes); 63 + _server.Parse(new Line("005 * MODES *")); 64 + await Assert.That(_server.ISupport.Modes).IsEqualTo(-1); 88 65 89 - _server.Parse(new("005 * MODES=5 *")); 90 - Assert.AreEqual(5, _server.ISupport.Modes); 66 + _server.Parse(new Line("005 * MODES=5 *")); 67 + await Assert.That(_server.ISupport.Modes).IsEqualTo(5); 91 68 } 92 69 93 - [TestMethod] 94 - public void Rfc1459() 70 + [Test] 71 + public async Task Rfc1459() 95 72 { 96 - Assert.AreEqual(IRCStates.Casemap.CaseMapping.Rfc1459, _server.ISupport.CaseMapping); 97 - _server.Parse(new("005 * CASEMAPPING=rfc1459 *")); 98 - Assert.AreEqual(IRCStates.Casemap.CaseMapping.Rfc1459, _server.ISupport.CaseMapping); 73 + await Assert.That(_server.ISupport.CaseMapping).IsEqualTo(IRCStates.Casemap.CaseMapping.Rfc1459); 74 + _server.Parse(new Line("005 * CASEMAPPING=rfc1459 *")); 75 + await Assert.That(_server.ISupport.CaseMapping).IsEqualTo(IRCStates.Casemap.CaseMapping.Rfc1459); 99 76 var lower = _server.CaseFold(@"ÀTEST[]~\"); 100 - Assert.AreEqual("Àtest{}^|", lower); 77 + await Assert.That(lower).IsEqualTo("Àtest{}^|"); 101 78 } 102 79 103 - [TestMethod] 104 - public void Ascii() 80 + [Test] 81 + public async Task Ascii() 105 82 { 106 - _server.Parse(new("005 * CASEMAPPING=ascii *")); 107 - Assert.AreEqual(IRCStates.Casemap.CaseMapping.Ascii, _server.ISupport.CaseMapping); 83 + _server.Parse(new Line("005 * CASEMAPPING=ascii *")); 84 + await Assert.That(_server.ISupport.CaseMapping).IsEqualTo(IRCStates.Casemap.CaseMapping.Ascii); 108 85 var lower = _server.CaseFold(@"ÀTEST[]~\"); 109 - Assert.AreEqual(@"Àtest[]~\", lower); 86 + await Assert.That(lower).IsEqualTo(@"Àtest[]~\"); 110 87 } 111 88 112 - [TestMethod] 113 - public void FallbackToRfc1459() 89 + [Test] 90 + public async Task FallbackToRfc1459() 114 91 { 115 - _server.Parse(new("005 * CASEMAPPING=nonexistent *")); 116 - Assert.AreEqual(IRCStates.Casemap.CaseMapping.Rfc1459, _server.ISupport.CaseMapping); 92 + _server.Parse(new Line("005 * CASEMAPPING=nonexistent *")); 93 + await Assert.That(_server.ISupport.CaseMapping).IsEqualTo(IRCStates.Casemap.CaseMapping.Rfc1459); 117 94 var lower = _server.CaseFold(@"ÀTEST[]~\"); 118 - Assert.AreEqual("Àtest{}^|", lower); 95 + await Assert.That(lower).IsEqualTo("Àtest{}^|"); 119 96 } 120 97 121 - [TestMethod] 122 - public void Network() 98 + [Test] 99 + public async Task Network() 123 100 { 124 - Assert.IsNull(_server.ISupport.Network); 125 - _server.Parse(new("005 * NETWORK=testnet *")); 126 - Assert.AreEqual("testnet", _server.ISupport.Network); 101 + await Assert.That(_server.ISupport.Network).IsNull(); 102 + _server.Parse(new Line("005 * NETWORK=testnet *")); 103 + await Assert.That(_server.ISupport.Network).IsEqualTo("testnet"); 127 104 } 128 105 129 - [TestMethod] 130 - public void StatusMsg() 106 + [Test] 107 + public async Task StatusMsg() 131 108 { 132 - CollectionAssert.AreEqual(new List<string>(), _server.ISupport.StatusMsg); 133 - _server.Parse(new("005 * STATUSMSG=&@ *")); 134 - CollectionAssert.AreEqual(new List<string> {"&", "@"}, _server.ISupport.StatusMsg); 109 + await Assert.That(_server.ISupport.StatusMsg).IsEmpty(); 110 + _server.Parse(new Line("005 * STATUSMSG=&@ *")); 111 + await Assert.That(_server.ISupport.StatusMsg).IsEquivalentTo(["&", "@"]); 135 112 } 136 113 137 - [TestMethod] 138 - public void CallerId() 114 + [Test] 115 + public async Task CallerId() 139 116 { 140 - Assert.IsNull(_server.ISupport.CallerId); 117 + await Assert.That(_server.ISupport.CallerId).IsNull(); 141 118 142 - _server.Parse(new("005 * CALLERID=U *")); 143 - Assert.AreEqual("U", _server.ISupport.CallerId); 119 + _server.Parse(new Line("005 * CALLERID=U *")); 120 + await Assert.That(_server.ISupport.CallerId).IsEqualTo("U"); 144 121 145 - _server.Parse(new("005 * CALLERID *")); 146 - Assert.AreEqual("g", _server.ISupport.CallerId); 122 + _server.Parse(new Line("005 * CALLERID *")); 123 + await Assert.That(_server.ISupport.CallerId).IsEqualTo("g"); 147 124 } 148 125 149 - [TestMethod] 150 - public void Excepts() 126 + [Test] 127 + public async Task Excepts() 151 128 { 152 - Assert.IsNull(_server.ISupport.Excepts); 129 + await Assert.That(_server.ISupport.Excepts).IsNull(); 153 130 154 - _server.Parse(new("005 * EXCEPTS=U *")); 155 - Assert.AreEqual("U", _server.ISupport.Excepts); 131 + _server.Parse(new Line("005 * EXCEPTS=U *")); 132 + await Assert.That(_server.ISupport.Excepts).IsEqualTo("U"); 156 133 157 - _server.Parse(new("005 * EXCEPTS *")); 158 - Assert.AreEqual("e", _server.ISupport.Excepts); 134 + _server.Parse(new Line("005 * EXCEPTS *")); 135 + await Assert.That(_server.ISupport.Excepts).IsEqualTo("e"); 159 136 } 160 137 161 - [TestMethod] 162 - public void Invex() 138 + [Test] 139 + public async Task Invex() 163 140 { 164 - Assert.IsNull(_server.ISupport.Invex); 141 + await Assert.That(_server.ISupport.Invex).IsNull(); 165 142 166 - _server.Parse(new("005 * INVEX=U *")); 167 - Assert.AreEqual("U", _server.ISupport.Invex); 143 + _server.Parse(new Line("005 * INVEX=U *")); 144 + await Assert.That(_server.ISupport.Invex).IsEqualTo("U"); 168 145 169 - _server.Parse(new("005 * INVEX *")); 170 - Assert.AreEqual("I", _server.ISupport.Invex); 146 + _server.Parse(new Line("005 * INVEX *")); 147 + await Assert.That(_server.ISupport.Invex).IsEqualTo("I"); 171 148 } 172 149 173 - [TestMethod] 174 - public void Whox() 150 + [Test] 151 + public async Task Whox() 175 152 { 176 - Assert.IsFalse(_server.ISupport.Whox); 153 + await Assert.That(_server.ISupport.Whox).IsFalse(); 177 154 178 - _server.Parse(new("005 * WHOX *")); 179 - Assert.IsTrue(_server.ISupport.Whox); 155 + _server.Parse(new Line("005 * WHOX *")); 156 + await Assert.That(_server.ISupport.Whox).IsTrue(); 180 157 } 181 158 182 - [TestMethod] 183 - public void Monitor() 159 + [Test] 160 + public async Task Monitor() 184 161 { 185 - Assert.IsNull(_server.ISupport.Monitor); 162 + await Assert.That(_server.ISupport.Monitor).IsNull(); 186 163 187 - _server.Parse(new("005 * MONITOR=123 *")); 188 - Assert.AreEqual(123, _server.ISupport.Monitor); 164 + _server.Parse(new Line("005 * MONITOR=123 *")); 165 + await Assert.That(_server.ISupport.Monitor).IsEqualTo(123); 189 166 190 - _server.Parse(new("005 * MONITOR *")); 191 - Assert.AreEqual(-1, _server.ISupport.Monitor); 167 + _server.Parse(new Line("005 * MONITOR *")); 168 + await Assert.That(_server.ISupport.Monitor).IsEqualTo(-1); 192 169 } 193 170 194 - [TestMethod] 195 - public void Watch() 171 + [Test] 172 + public async Task Watch() 196 173 { 197 - Assert.IsNull(_server.ISupport.Watch); 174 + await Assert.That(_server.ISupport.Watch).IsNull(); 198 175 199 - _server.Parse(new("005 * WATCH=123 *")); 200 - Assert.AreEqual(123, _server.ISupport.Watch); 176 + _server.Parse(new Line("005 * WATCH=123 *")); 177 + await Assert.That(_server.ISupport.Watch).IsEqualTo(123); 201 178 202 - _server.Parse(new("005 * WATCH *")); 203 - Assert.AreEqual(-1, _server.ISupport.Watch); 179 + _server.Parse(new Line("005 * WATCH *")); 180 + await Assert.That(_server.ISupport.Watch).IsEqualTo(-1); 204 181 } 205 - } 182 + }
+91 -92
IRCSharp.Tests/State/Mode.cs
··· 1 1 // ReSharper disable StringLiteralTypo 2 + 2 3 namespace IRCSharp.Tests.State; 3 4 4 - [TestClass] 5 5 public class Mode 6 6 { 7 - private Server _server; 7 + private readonly Server _server = new("test"); 8 8 9 - [TestInitialize] 10 - public void TestInitialize() 9 + public Mode() 11 10 { 12 - _server = new("test"); 13 - _server.Parse(new("001 nickname")); 11 + _server.Parse(new Line("001 nickname")); 14 12 } 15 13 16 - [TestMethod] 17 - public void UModeAdd() 14 + [Test] 15 + public async Task UModeAdd() 18 16 { 19 - _server.Parse(new("MODE nickname +i")); 20 - CollectionAssert.AreEqual(new List<string> {"i"}, _server.Modes); 17 + _server.Parse(new Line("MODE nickname +i")); 18 + await Assert.That(_server.Modes).IsEquivalentTo(["i"]); 21 19 } 22 20 23 - [TestMethod] 24 - public void UModeRemove() 21 + [Test] 22 + public async Task UModeRemove() 25 23 { 26 - _server.Parse(new("MODE nickname +i")); 27 - _server.Parse(new("MODE nickname -i")); 28 - CollectionAssert.AreEqual(new List<string>(), _server.Modes); 24 + _server.Parse(new Line("MODE nickname +i")); 25 + _server.Parse(new Line("MODE nickname -i")); 26 + await Assert.That(_server.Modes).IsEmpty(); 29 27 } 30 28 31 - [TestMethod] 32 - public void PrefixAdd() 29 + [Test] 30 + public async Task PrefixAdd() 33 31 { 34 - _server.Parse(new(":nickname JOIN #chan")); 35 - _server.Parse(new("MODE #chan +ov nickname nickname")); 32 + _server.Parse(new Line(":nickname JOIN #chan")); 33 + _server.Parse(new Line("MODE #chan +ov nickname nickname")); 36 34 37 35 var user = _server.Users["nickname"]; 38 36 var channel = _server.Channels["#chan"]; 39 37 var channelUser = channel.Users[user.NickNameLower]; 40 - CollectionAssert.AreEqual(new List<string> {"o", "v"}, channelUser.Modes); 38 + await Assert.That(channelUser.Modes).IsEquivalentTo(["o", "v"]); 41 39 } 42 40 43 - [TestMethod] 44 - public void PrefixRemove() 41 + [Test] 42 + public async Task PrefixRemove() 45 43 { 46 - _server.Parse(new(":nickname JOIN #chan")); 47 - _server.Parse(new("MODE #chan +ov nickname nickname")); 48 - _server.Parse(new("MODE #chan -ov nickname nickname")); 44 + _server.Parse(new Line(":nickname JOIN #chan")); 45 + _server.Parse(new Line("MODE #chan +ov nickname nickname")); 46 + _server.Parse(new Line("MODE #chan -ov nickname nickname")); 49 47 50 48 var user = _server.Users["nickname"]; 51 49 var channel = _server.Channels["#chan"]; 52 50 var channelUser = channel.Users[user.NickNameLower]; 53 - CollectionAssert.AreEqual(new List<string>(), channelUser.Modes); 51 + await Assert.That(channelUser.Modes).IsEmpty(); 54 52 } 55 53 56 - [TestMethod] 57 - public void ChannelListAdd() 54 + [Test] 55 + public async Task ChannelListAdd() 58 56 { 59 - _server.Parse(new(":nickname JOIN #chan")); 60 - _server.Parse(new("MODE #chan +b asd!*@*")); 57 + _server.Parse(new Line(":nickname JOIN #chan")); 58 + _server.Parse(new Line("MODE #chan +b asd!*@*")); 61 59 62 60 var channel = _server.Channels["#chan"]; 63 - CollectionAssert.AreEqual(new List<string> {"asd!*@*"}, channel.ListModes["b"]); 61 + await Assert.That(channel.ListModes["b"]).IsEquivalentTo(["asd!*@*"]); 64 62 } 65 63 66 - [TestMethod] 67 - public void ChannelListRemove() 64 + [Test] 65 + public async Task ChannelListRemove() 68 66 { 69 - _server.Parse(new(":nickname JOIN #chan")); 70 - _server.Parse(new("MODE #chan +b asd!*@*")); 71 - _server.Parse(new("MODE #chan -b asd!*@*")); 67 + _server.Parse(new Line(":nickname JOIN #chan")); 68 + _server.Parse(new Line("MODE #chan +b asd!*@*")); 69 + _server.Parse(new Line("MODE #chan -b asd!*@*")); 72 70 73 71 var channel = _server.Channels["#chan"]; 74 - CollectionAssert.AreEqual(new Dictionary<string, List<string>>(), channel.ListModes); 72 + await Assert.That(channel.ListModes).IsEmpty(); 75 73 } 76 74 77 - [TestMethod] 78 - public void ChannelTypeBAdd() 75 + [Test] 76 + public async Task ChannelTypeBAdd() 79 77 { 80 - _server.Parse(new(":nickname JOIN #chan")); 81 - _server.Parse(new("MODE #chan +k password")); 78 + _server.Parse(new Line(":nickname JOIN #chan")); 79 + _server.Parse(new Line("MODE #chan +k password")); 82 80 83 81 var channel = _server.Channels["#chan"]; 84 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"k", "password"}}, channel.Modes); 82 + await Assert.That(channel.Modes).IsEquivalentTo(new Dictionary<string, string> { { "k", "password" } }); 85 83 } 86 84 87 - [TestMethod] 88 - public void ChannelTypeBRemove() 85 + [Test] 86 + public async Task ChannelTypeBRemove() 89 87 { 90 - _server.Parse(new(":nickname JOIN #chan")); 91 - _server.Parse(new("MODE #chan +k password")); 92 - _server.Parse(new("MODE #chan -k *")); 88 + _server.Parse(new Line(":nickname JOIN #chan")); 89 + _server.Parse(new Line("MODE #chan +k password")); 90 + _server.Parse(new Line("MODE #chan -k *")); 93 91 94 92 var channel = _server.Channels["#chan"]; 95 - CollectionAssert.AreEqual(new Dictionary<string, string>(), channel.Modes); 93 + await Assert.That(channel.Modes).IsEmpty(); 96 94 } 97 95 98 - [TestMethod] 99 - public void ChannelTypeCAdd() 96 + [Test] 97 + public async Task ChannelTypeCAdd() 100 98 { 101 - _server.Parse(new(":nickname JOIN #chan")); 102 - _server.Parse(new("MODE #chan +l 100")); 99 + _server.Parse(new Line(":nickname JOIN #chan")); 100 + _server.Parse(new Line("MODE #chan +l 100")); 103 101 104 102 var channel = _server.Channels["#chan"]; 105 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"l", "100"}}, channel.Modes); 103 + await Assert.That(channel.Modes).IsEquivalentTo(new Dictionary<string, string> { { "l", "100" } }); 106 104 } 107 105 108 - [TestMethod] 109 - public void ChannelTypeCRemove() 106 + [Test] 107 + public async Task ChannelTypeCRemove() 110 108 { 111 - _server.Parse(new(":nickname JOIN #chan")); 112 - _server.Parse(new("MODE #chan +l 100")); 113 - _server.Parse(new("MODE #chan -l")); 109 + _server.Parse(new Line(":nickname JOIN #chan")); 110 + _server.Parse(new Line("MODE #chan +l 100")); 111 + _server.Parse(new Line("MODE #chan -l")); 114 112 115 113 var channel = _server.Channels["#chan"]; 116 - CollectionAssert.AreEqual(new Dictionary<string, string>(), channel.Modes); 114 + await Assert.That(channel.Modes).IsEmpty(); 117 115 } 118 116 119 - [TestMethod] 120 - public void ChannelTypeDAdd() 117 + [Test] 118 + public async Task ChannelTypeDAdd() 121 119 { 122 - _server.Parse(new(":nickname JOIN #chan")); 123 - _server.Parse(new("MODE #chan +i")); 120 + _server.Parse(new Line(":nickname JOIN #chan")); 121 + _server.Parse(new Line("MODE #chan +i")); 124 122 125 123 var channel = _server.Channels["#chan"]; 126 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"i", null}}, channel.Modes); 124 + await Assert.That(channel.Modes).IsEquivalentTo(new Dictionary<string, string> { { "i", null } }); 127 125 } 128 126 129 - [TestMethod] 130 - public void ChannelTypeDRemove() 127 + [Test] 128 + public async Task ChannelTypeDRemove() 131 129 { 132 - _server.Parse(new(":nickname JOIN #chan")); 133 - _server.Parse(new("MODE #chan +i")); 134 - _server.Parse(new("MODE #chan -i")); 130 + _server.Parse(new Line(":nickname JOIN #chan")); 131 + _server.Parse(new Line("MODE #chan +i")); 132 + _server.Parse(new Line("MODE #chan -i")); 135 133 136 134 var channel = _server.Channels["#chan"]; 137 - CollectionAssert.AreEqual(new Dictionary<string, string>(), channel.Modes); 135 + await Assert.That(channel.Modes).IsEmpty(); 138 136 } 139 137 140 - [TestMethod] 141 - public void ChannelNumeric() 138 + [Test] 139 + public async Task ChannelNumeric() 142 140 { 143 - _server.Parse(new(":nickname JOIN #chan")); 144 - _server.Parse(new("324 * #chan +bkli *!*@* pass 10")); 141 + _server.Parse(new Line(":nickname JOIN #chan")); 142 + _server.Parse(new Line("324 * #chan +bkli *!*@* pass 10")); 143 + var channel = _server.Channels["#chan"]; 145 144 146 - var channel = _server.Channels["#chan"]; 147 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"k", "pass"}, {"l", "10"}, {"i", null}}, 148 - channel.Modes); 149 - CollectionAssert.AreEqual(new List<string> {"*!*@*"}, channel.ListModes["b"]); 145 + var expectedModes = new Dictionary<string, string> { { "k", "pass" }, { "l", "10" }, { "i", null } }; 146 + await Assert.That(channel.Modes).IsEquivalentTo(expectedModes); 147 + await Assert.That(channel.ListModes["b"]).IsEquivalentTo(["*!*@*"]); 150 148 } 151 149 152 - [TestMethod] 153 - public void ChannelNumericWithoutPlus() 150 + [Test] 151 + public async Task ChannelNumericWithoutPlus() 154 152 { 155 - _server.Parse(new(":nickname JOIN #chan")); 156 - _server.Parse(new("324 * #chan il 10")); 153 + _server.Parse(new Line(":nickname JOIN #chan")); 154 + _server.Parse(new Line("324 * #chan il 10")); 157 155 158 156 var channel = _server.Channels["#chan"]; 159 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"i", null}, {"l", "10"}}, channel.Modes); 157 + await Assert.That(channel.Modes) 158 + .IsEquivalentTo(new Dictionary<string, string> { { "i", null }, { "l", "10" } }); 160 159 } 161 160 162 - [TestMethod] 163 - public void UserNumeric() 161 + [Test] 162 + public async Task UserNumeric() 164 163 { 165 - _server.Parse(new("221 * +iw")); 166 - CollectionAssert.AreEqual(new List<string> {"i", "w"}, _server.Modes); 164 + _server.Parse(new Line("221 * +iw")); 165 + await Assert.That(_server.Modes).IsEquivalentTo(["i", "w"]); 167 166 } 168 167 169 - [TestMethod] 170 - public void UserNumericWithoutPlus() 168 + [Test] 169 + public async Task UserNumericWithoutPlus() 171 170 { 172 - _server.Parse(new("221 * iw")); 173 - CollectionAssert.AreEqual(new List<string> {"i", "w"}, _server.Modes); 171 + _server.Parse(new Line("221 * iw")); 172 + await Assert.That(_server.Modes).IsEquivalentTo(["i", "w"]); 174 173 } 175 - } 174 + }
+8 -10
IRCSharp.Tests/State/Motd.cs
··· 1 1 namespace IRCSharp.Tests.State; 2 2 3 - [TestClass] 4 3 public class Motd 5 4 { 6 - [TestMethod] 7 - public void MessageOfTheDay() 5 + [Test] 6 + public async Task MessageOfTheDay() 8 7 { 9 8 var server = new Server("test"); 10 - server.Parse(new("001 nickname")); 11 - server.Parse(new("375 * :start of motd")); 12 - server.Parse(new("372 * :first line of motd")); 13 - server.Parse(new("372 * :second line of motd")); 9 + server.Parse(new Line("001 nickname")); 10 + server.Parse(new Line("375 * :start of motd")); 11 + server.Parse(new Line("372 * :first line of motd")); 12 + server.Parse(new Line("372 * :second line of motd")); 14 13 15 - CollectionAssert.AreEqual(new List<string> {"start of motd", "first line of motd", "second line of motd"}, 16 - server.Motd); 14 + await Assert.That(server.Motd).IsEquivalentTo(["start of motd", "first line of motd", "second line of motd"]); 17 15 } 18 - } 16 + }
+17 -20
IRCSharp.Tests/State/Sasl.cs
··· 1 1 namespace IRCSharp.Tests.State; 2 2 3 - [TestClass] 4 3 public class Sasl 5 4 { 6 - private Server _server; 5 + private readonly Server _server = new("test"); 7 6 8 - [TestInitialize] 9 - public void TestInitialize() 7 + public Sasl() 10 8 { 11 - _server = new("test"); 12 - _server.Parse(new("900 * nick!user@host account")); 9 + _server.Parse(new Line("900 * nick!user@host account")); 13 10 } 14 11 15 - [TestMethod] 16 - public void LoggedIn() 12 + [Test] 13 + public async Task LoggedIn() 17 14 { 18 - Assert.AreEqual("nick", _server.NickName); 19 - Assert.AreEqual("user", _server.UserName); 20 - Assert.AreEqual("host", _server.HostName); 21 - Assert.AreEqual("account", _server.Account); 15 + await Assert.That(_server.NickName).IsEqualTo("nick"); 16 + await Assert.That(_server.UserName).IsEqualTo("user"); 17 + await Assert.That(_server.HostName).IsEqualTo("host"); 18 + await Assert.That(_server.Account).IsEqualTo("account"); 22 19 } 23 20 24 - [TestMethod] 25 - public void LoggedOut() 21 + [Test] 22 + public async Task LoggedOut() 26 23 { 27 - _server.Parse(new("901 * nick1!user1@host1")); 24 + _server.Parse(new Line("901 * nick1!user1@host1")); 28 25 29 - Assert.AreEqual("nick1", _server.NickName); 30 - Assert.AreEqual("user1", _server.UserName); 31 - Assert.AreEqual("host1", _server.HostName); 32 - Assert.IsTrue(string.IsNullOrEmpty(_server.Account)); 26 + await Assert.That(_server.NickName).IsEqualTo("nick1"); 27 + await Assert.That(_server.UserName).IsEqualTo("user1"); 28 + await Assert.That(_server.HostName).IsEqualTo("host1"); 29 + await Assert.That(_server.Account).IsNull(); 33 30 } 34 - } 31 + }
+184 -187
IRCSharp.Tests/State/User.cs
··· 1 1 namespace IRCSharp.Tests.State; 2 2 3 - [TestClass] 4 3 public class User 5 4 { 6 - private Server _server; 5 + private readonly Server _server = new("test"); 7 6 8 - [TestInitialize] 9 - public void TestInitialize() 7 + public User() 10 8 { 11 - _server = new("test"); 12 - _server.Parse(new("001 nickname")); 9 + _server.Parse(new Line("001 nickname")); 13 10 } 14 11 15 - [TestMethod] 16 - public void Welcome() 12 + [Test] 13 + public async Task Welcome() 17 14 { 18 - Assert.AreEqual("test", _server.Name); 19 - Assert.AreEqual("nickname", _server.NickName); 15 + await Assert.That(_server.Name).IsEqualTo("test"); 16 + await Assert.That(_server.NickName).IsEqualTo("nickname"); 20 17 } 21 18 22 - [TestMethod] 23 - public void NicknameChange() 19 + [Test] 20 + public async Task NicknameChange() 24 21 { 25 - _server.Parse(new(":nickname NICK nickname2")); 26 - Assert.AreEqual("nickname2", _server.NickName); 22 + _server.Parse(new Line(":nickname NICK nickname2")); 23 + await Assert.That(_server.NickName).IsEqualTo("nickname2"); 27 24 28 - _server.Parse(new(":nickname2 JOIN #chan")); 29 - _server.Parse(new(":other JOIN #chan")); 30 - Assert.IsTrue(_server.Users.ContainsKey("other")); 25 + _server.Parse(new Line(":nickname2 JOIN #chan")); 26 + _server.Parse(new Line(":other JOIN #chan")); 27 + await Assert.That(_server.Users).ContainsKey("other"); 31 28 32 - _server.Parse(new(":other NICK other2")); 33 - Assert.IsFalse(_server.Users.ContainsKey("other")); 34 - Assert.IsTrue(_server.Users.ContainsKey("other2")); 29 + _server.Parse(new Line(":other NICK other2")); 30 + await Assert.That(_server.Users).DoesNotContainKey("other"); 31 + await Assert.That(_server.Users).ContainsKey("other2"); 35 32 } 36 33 37 - [TestMethod] 38 - public void HostmaskJoinBoth() 34 + [Test] 35 + public async Task HostmaskJoinBoth() 39 36 { 40 - _server.Parse(new(":nickname!user@host JOIN #chan")); 41 - Assert.AreEqual("user", _server.UserName); 42 - Assert.AreEqual("host", _server.HostName); 37 + _server.Parse(new Line(":nickname!user@host JOIN #chan")); 38 + await Assert.That(_server.UserName).IsEqualTo("user"); 39 + await Assert.That(_server.HostName).IsEqualTo("host"); 43 40 44 - _server.Parse(new(":other!user@host JOIN #chan")); 41 + _server.Parse(new Line(":other!user@host JOIN #chan")); 45 42 var user = _server.Users["other"]; 46 - Assert.AreEqual("user", user.UserName); 47 - Assert.AreEqual("host", user.HostName); 43 + await Assert.That(user.UserName).IsEqualTo("user"); 44 + await Assert.That(user.HostName).IsEqualTo("host"); 48 45 } 49 46 50 - [TestMethod] 51 - public void HostmaskJoinUser() 47 + [Test] 48 + public async Task HostmaskJoinUser() 52 49 { 53 - _server.Parse(new(":nickname!user JOIN #chan")); 54 - Assert.AreEqual("user", _server.UserName); 55 - Assert.IsNull(_server.HostName); 50 + _server.Parse(new Line(":nickname!user JOIN #chan")); 51 + await Assert.That(_server.UserName).IsEqualTo("user"); 52 + await Assert.That(_server.HostName).IsNull(); 56 53 57 - _server.Parse(new(":other!user JOIN #chan")); 54 + _server.Parse(new Line(":other!user JOIN #chan")); 58 55 var user = _server.Users["other"]; 59 - Assert.AreEqual("user", user.UserName); 60 - Assert.IsNull(user.HostName); 56 + await Assert.That(user.UserName).IsEqualTo("user"); 57 + await Assert.That(user.HostName).IsNull(); 61 58 } 62 59 63 - [TestMethod] 64 - public void HostmaskJoinHost() 60 + [Test] 61 + public async Task HostmaskJoinHost() 65 62 { 66 - _server.Parse(new(":nickname@host JOIN #chan")); 67 - Assert.IsNull(_server.UserName); 68 - Assert.AreEqual("host", _server.HostName); 63 + _server.Parse(new Line(":nickname@host JOIN #chan")); 64 + await Assert.That(_server.UserName).IsNull(); 65 + await Assert.That(_server.HostName).IsEqualTo("host"); 69 66 70 - _server.Parse(new(":other@host JOIN #chan")); 67 + _server.Parse(new Line(":other@host JOIN #chan")); 71 68 var user = _server.Users["other"]; 72 - Assert.IsNull(user.UserName); 73 - Assert.AreEqual("host", user.HostName); 69 + await Assert.That(user.UserName).IsNull(); 70 + await Assert.That(user.HostName).IsEqualTo("host"); 74 71 } 75 72 76 - [TestMethod] 77 - public void ExtendedJoinWithoutExtendedJoin() 73 + [Test] 74 + public async Task ExtendedJoinWithoutExtendedJoin() 78 75 { 79 - _server.Parse(new(":nickname JOIN #chan")); 80 - Assert.IsNull(_server.Account); 81 - Assert.IsNull(_server.RealName); 76 + _server.Parse(new Line(":nickname JOIN #chan")); 77 + await Assert.That(_server.Account).IsNull(); 78 + await Assert.That(_server.RealName).IsNull(); 82 79 83 - _server.Parse(new(":other JOIN #chan")); 80 + _server.Parse(new Line(":other JOIN #chan")); 84 81 var user = _server.Users["other"]; 85 - Assert.IsNull(user.Account); 86 - Assert.IsNull(user.RealName); 82 + await Assert.That(user.Account).IsNull(); 83 + await Assert.That(user.RealName).IsNull(); 87 84 } 88 85 89 - [TestMethod] 90 - public void ExtendedJoinWithAccount() 86 + [Test] 87 + public async Task ExtendedJoinWithAccount() 91 88 { 92 - _server.Parse(new(":nickname JOIN #chan acc :realname")); 93 - Assert.AreEqual("acc", _server.Account); 94 - Assert.AreEqual("realname", _server.RealName); 89 + _server.Parse(new Line(":nickname JOIN #chan acc :realname")); 90 + await Assert.That(_server.Account).IsEqualTo("acc"); 91 + await Assert.That(_server.RealName).IsEqualTo("realname"); 95 92 96 - _server.Parse(new(":other JOIN #chan acc2 :realname2")); 93 + _server.Parse(new Line(":other JOIN #chan acc2 :realname2")); 97 94 var user = _server.Users["other"]; 98 - Assert.AreEqual("acc2", user.Account); 99 - Assert.AreEqual("realname2", user.RealName); 95 + await Assert.That(user.Account).IsEqualTo("acc2"); 96 + await Assert.That(user.RealName).IsEqualTo("realname2"); 100 97 } 101 98 102 - [TestMethod] 103 - public void ExtendedJoinWithoutAccount() 99 + [Test] 100 + public async Task ExtendedJoinWithoutAccount() 104 101 { 105 - _server.Parse(new(":nickname JOIN #chan * :realname")); 106 - Assert.AreEqual("", _server.Account); 107 - Assert.AreEqual("realname", _server.RealName); 102 + _server.Parse(new Line(":nickname JOIN #chan * :realname")); 103 + await Assert.That(_server.Account).IsEqualTo(""); 104 + await Assert.That(_server.RealName).IsEqualTo("realname"); 108 105 109 - _server.Parse(new(":other JOIN #chan * :realname2")); 106 + _server.Parse(new Line(":other JOIN #chan * :realname2")); 110 107 var user = _server.Users["other"]; 111 - Assert.AreEqual("", user.Account); 112 - Assert.AreEqual("realname2", user.RealName); 108 + await Assert.That(user.Account).IsEqualTo(""); 109 + await Assert.That(user.RealName).IsEqualTo("realname2"); 113 110 } 114 111 115 - [TestMethod] 116 - public void AccountNotifyWithAccount() 112 + [Test] 113 + public async Task AccountNotifyWithAccount() 117 114 { 118 - _server.Parse(new(":nickname JOIN #chan")); 119 - _server.Parse(new(":nickname ACCOUNT acc")); 120 - Assert.AreEqual("acc", _server.Account); 115 + _server.Parse(new Line(":nickname JOIN #chan")); 116 + _server.Parse(new Line(":nickname ACCOUNT acc")); 117 + await Assert.That(_server.Account).IsEqualTo("acc"); 121 118 122 - _server.Parse(new(":other JOIN #chan")); 123 - _server.Parse(new(":other ACCOUNT acc2")); 119 + _server.Parse(new Line(":other JOIN #chan")); 120 + _server.Parse(new Line(":other ACCOUNT acc2")); 124 121 var user = _server.Users["other"]; 125 - Assert.AreEqual("acc2", user.Account); 122 + await Assert.That(user.Account).IsEqualTo("acc2"); 126 123 } 127 124 128 - [TestMethod] 129 - public void AccountNotifyWithoutAccount() 125 + [Test] 126 + public async Task AccountNotifyWithoutAccount() 130 127 { 131 - _server.Parse(new(":nickname JOIN #chan")); 132 - _server.Parse(new(":nickname ACCOUNT *")); 133 - Assert.AreEqual("", _server.Account); 128 + _server.Parse(new Line(":nickname JOIN #chan")); 129 + _server.Parse(new Line(":nickname ACCOUNT *")); 130 + await Assert.That(_server.Account).IsEqualTo(""); 134 131 135 - _server.Parse(new(":other JOIN #chan")); 136 - _server.Parse(new(":other ACCOUNT *")); 132 + _server.Parse(new Line(":other JOIN #chan")); 133 + _server.Parse(new Line(":other ACCOUNT *")); 137 134 var user = _server.Users["other"]; 138 - Assert.AreEqual("", user.Account); 135 + await Assert.That(user.Account).IsEqualTo(""); 139 136 } 140 137 141 - [TestMethod] 142 - public void HostmaskPrivmsgBoth() 138 + [Test] 139 + public async Task HostmaskPrivmsgBoth() 143 140 { 144 - _server.Parse(new(":nickname JOIN #chan")); 145 - _server.Parse(new(":nickname!user@host PRIVMSG #chan :hi")); 146 - Assert.AreEqual("user", _server.UserName); 147 - Assert.AreEqual("host", _server.HostName); 141 + _server.Parse(new Line(":nickname JOIN #chan")); 142 + _server.Parse(new Line(":nickname!user@host PRIVMSG #chan :hi")); 143 + await Assert.That(_server.UserName).IsEqualTo("user"); 144 + await Assert.That(_server.HostName).IsEqualTo("host"); 148 145 149 - _server.Parse(new(":other!user@host PRIVMSG #chan :hi")); 146 + _server.Parse(new Line(":other!user@host PRIVMSG #chan :hi")); 150 147 var user = _server.Users["other"]; 151 - Assert.AreEqual("user", user.UserName); 152 - Assert.AreEqual("host", user.HostName); 148 + await Assert.That(user.UserName).IsEqualTo("user"); 149 + await Assert.That(user.HostName).IsEqualTo("host"); 153 150 } 154 151 155 - [TestMethod] 156 - public void HostmaskPrivmsgUser() 152 + [Test] 153 + public async Task HostmaskPrivmsgUser() 157 154 { 158 - _server.Parse(new(":nickname JOIN #chan")); 159 - _server.Parse(new(":nickname!user PRIVMSG #chan :hi")); 160 - Assert.AreEqual("user", _server.UserName); 161 - Assert.IsNull(_server.HostName); 155 + _server.Parse(new Line(":nickname JOIN #chan")); 156 + _server.Parse(new Line(":nickname!user PRIVMSG #chan :hi")); 157 + await Assert.That(_server.UserName).IsEqualTo("user"); 158 + await Assert.That(_server.HostName).IsNull(); 162 159 163 - _server.Parse(new(":other!user PRIVMSG #chan :hi")); 160 + _server.Parse(new Line(":other!user PRIVMSG #chan :hi")); 164 161 var user = _server.Users["other"]; 165 - Assert.AreEqual("user", user.UserName); 166 - Assert.IsNull(user.HostName); 162 + await Assert.That(user.UserName).IsEqualTo("user"); 163 + await Assert.That(user.HostName).IsNull(); 167 164 } 168 165 169 - [TestMethod] 170 - public void HostmaskPrivmsgHost() 166 + [Test] 167 + public async Task HostmaskPrivmsgHost() 171 168 { 172 - _server.Parse(new(":nickname JOIN #chan")); 173 - _server.Parse(new(":nickname@host PRIVMSG #chan :hi")); 174 - Assert.IsNull(_server.UserName); 175 - Assert.AreEqual("host", _server.HostName); 169 + _server.Parse(new Line(":nickname JOIN #chan")); 170 + _server.Parse(new Line(":nickname@host PRIVMSG #chan :hi")); 171 + await Assert.That(_server.UserName).IsNull(); 172 + await Assert.That(_server.HostName).IsEqualTo("host"); 176 173 177 - _server.Parse(new(":other@host PRIVMSG #chan :hi")); 174 + _server.Parse(new Line(":other@host PRIVMSG #chan :hi")); 178 175 var user = _server.Users["other"]; 179 - Assert.IsNull(user.UserName); 180 - Assert.AreEqual("host", user.HostName); 176 + await Assert.That(user.UserName).IsNull(); 177 + await Assert.That(user.HostName).IsEqualTo("host"); 181 178 } 182 179 183 - [TestMethod] 184 - public void VisibleHostWithoutUsername() 180 + [Test] 181 + public async Task VisibleHostWithoutUsername() 185 182 { 186 - _server.Parse(new("396 * hostname")); 187 - Assert.IsNull(_server.UserName); 188 - Assert.AreEqual("hostname", _server.HostName); 183 + _server.Parse(new Line("396 * hostname")); 184 + await Assert.That(_server.UserName).IsNull(); 185 + await Assert.That(_server.HostName).IsEqualTo("hostname"); 189 186 } 190 187 191 - [TestMethod] 192 - public void VisibleHostWithUsername() 188 + [Test] 189 + public async Task VisibleHostWithUsername() 193 190 { 194 - _server.Parse(new("396 * username@hostname")); 195 - Assert.AreEqual("username", _server.UserName); 196 - Assert.AreEqual("hostname", _server.HostName); 191 + _server.Parse(new Line("396 * username@hostname")); 192 + await Assert.That(_server.UserName).IsEqualTo("username"); 193 + await Assert.That(_server.HostName).IsEqualTo("hostname"); 197 194 } 198 195 199 - [TestMethod] 200 - public void Who() 196 + [Test] 197 + public async Task Who() 201 198 { 202 - _server.Parse(new(":nickname JOIN #chan")); 203 - _server.Parse(new(":other JOIN #chan")); 204 - _server.Parse(new("352 * #chan user host * nickname * :0 real")); 205 - _server.Parse(new("352 * #chan user2 host2 * other * :0 real2")); 199 + _server.Parse(new Line(":nickname JOIN #chan")); 200 + _server.Parse(new Line(":other JOIN #chan")); 201 + _server.Parse(new Line("352 * #chan user host * nickname * :0 real")); 202 + _server.Parse(new Line("352 * #chan user2 host2 * other * :0 real2")); 206 203 207 - Assert.AreEqual("user", _server.UserName); 208 - Assert.AreEqual("host", _server.HostName); 209 - Assert.AreEqual("real", _server.RealName); 204 + await Assert.That(_server.UserName).IsEqualTo("user"); 205 + await Assert.That(_server.HostName).IsEqualTo("host"); 206 + await Assert.That(_server.RealName).IsEqualTo("real"); 210 207 211 208 var user = _server.Users["other"]; 212 - Assert.AreEqual("user2", user.UserName); 213 - Assert.AreEqual("host2", user.HostName); 214 - Assert.AreEqual("real2", user.RealName); 209 + await Assert.That(user.UserName).IsEqualTo("user2"); 210 + await Assert.That(user.HostName).IsEqualTo("host2"); 211 + await Assert.That(user.RealName).IsEqualTo("real2"); 215 212 } 216 213 217 - [TestMethod] 218 - public void Chghost() 214 + [Test] 215 + public async Task Chghost() 219 216 { 220 - _server.Parse(new(":nickname!user@host JOIN #chan")); 221 - _server.Parse(new(":nickname CHGHOST u h")); 222 - Assert.AreEqual("u", _server.UserName); 223 - Assert.AreEqual("h", _server.HostName); 217 + _server.Parse(new Line(":nickname!user@host JOIN #chan")); 218 + _server.Parse(new Line(":nickname CHGHOST u h")); 219 + await Assert.That(_server.UserName).IsEqualTo("u"); 220 + await Assert.That(_server.HostName).IsEqualTo("h"); 224 221 225 - _server.Parse(new(":other!user2@host2 JOIN #chan")); 226 - _server.Parse(new(":other CHGHOST u2 h2")); 222 + _server.Parse(new Line(":other!user2@host2 JOIN #chan")); 223 + _server.Parse(new Line(":other CHGHOST u2 h2")); 227 224 var user = _server.Users["other"]; 228 - Assert.AreEqual("u2", user.UserName); 229 - Assert.AreEqual("h2", user.HostName); 225 + await Assert.That(user.UserName).IsEqualTo("u2"); 226 + await Assert.That(user.HostName).IsEqualTo("h2"); 230 227 } 231 228 232 - [TestMethod] 233 - public void Whois() 229 + [Test] 230 + public async Task Whois() 234 231 { 235 - _server.Parse(new(":nickname JOIN #chan")); 236 - _server.Parse(new("311 * nickname u h * :r")); 237 - Assert.AreEqual("u", _server.UserName); 238 - Assert.AreEqual("h", _server.HostName); 239 - Assert.AreEqual("r", _server.RealName); 232 + _server.Parse(new Line(":nickname JOIN #chan")); 233 + _server.Parse(new Line("311 * nickname u h * :r")); 234 + await Assert.That(_server.UserName).IsEqualTo("u"); 235 + await Assert.That(_server.HostName).IsEqualTo("h"); 236 + await Assert.That(_server.RealName).IsEqualTo("r"); 240 237 241 - _server.Parse(new(":other JOIN #chan")); 242 - _server.Parse(new(":other CHGHOST u2 h2")); 243 - _server.Parse(new("311 * other u2 h2 * :r2")); 238 + _server.Parse(new Line(":other JOIN #chan")); 239 + _server.Parse(new Line(":other CHGHOST u2 h2")); 240 + _server.Parse(new Line("311 * other u2 h2 * :r2")); 244 241 var user = _server.Users["other"]; 245 - Assert.AreEqual("u2", user.UserName); 246 - Assert.AreEqual("h2", user.HostName); 247 - Assert.AreEqual("r2", user.RealName); 242 + await Assert.That(user.UserName).IsEqualTo("u2"); 243 + await Assert.That(user.HostName).IsEqualTo("h2"); 244 + await Assert.That(user.RealName).IsEqualTo("r2"); 248 245 } 249 246 250 - [TestMethod] 251 - public void AwaySet() 247 + [Test] 248 + public async Task AwaySet() 252 249 { 253 - _server.Parse(new(":nickname JOIN #chan")); 254 - _server.Parse(new(":other JOIN #chan")); 250 + _server.Parse(new Line(":nickname JOIN #chan")); 251 + _server.Parse(new Line(":other JOIN #chan")); 255 252 var user = _server.Users["other"]; 256 - Assert.IsNull(_server.Away); 257 - Assert.IsNull(user.Away); 253 + await Assert.That(_server.Away).IsNull(); 254 + await Assert.That(user.Away).IsNull(); 258 255 259 - _server.Parse(new(":nickname AWAY :bye bye")); 260 - _server.Parse(new(":other AWAY :ich geh weg")); 261 - Assert.AreEqual("bye bye", _server.Away); 262 - Assert.AreEqual("ich geh weg", user.Away); 256 + _server.Parse(new Line(":nickname AWAY :bye bye")); 257 + _server.Parse(new Line(":other AWAY :ich geh weg")); 258 + await Assert.That(_server.Away).IsEqualTo("bye bye"); 259 + await Assert.That(user.Away).IsEqualTo("ich geh weg"); 263 260 } 264 261 265 - [TestMethod] 266 - public void AwayUnset() 262 + [Test] 263 + public async Task AwayUnset() 267 264 { 268 - _server.Parse(new(":nickname JOIN #chan")); 269 - _server.Parse(new(":other JOIN #chan")); 270 - _server.Parse(new(":nickname AWAY :bye bye")); 271 - _server.Parse(new(":nickname AWAY")); 272 - _server.Parse(new(":other AWAY :ich geh weg")); 273 - _server.Parse(new(":other AWAY")); 265 + _server.Parse(new Line(":nickname JOIN #chan")); 266 + _server.Parse(new Line(":other JOIN #chan")); 267 + _server.Parse(new Line(":nickname AWAY :bye bye")); 268 + _server.Parse(new Line(":nickname AWAY")); 269 + _server.Parse(new Line(":other AWAY :ich geh weg")); 270 + _server.Parse(new Line(":other AWAY")); 274 271 275 272 var user = _server.Users["other"]; 276 - Assert.IsNull(_server.Away); 277 - Assert.IsNull(user.Away); 273 + await Assert.That(_server.Away).IsNull(); 274 + await Assert.That(user.Away).IsNull(); 278 275 } 279 276 280 - [TestMethod] 281 - public void Setname() 277 + [Test] 278 + public async Task Setname() 282 279 { 283 - _server.Parse(new(":nickname JOIN #chan")); 284 - _server.Parse(new(":other JOIN #chan")); 280 + _server.Parse(new Line(":nickname JOIN #chan")); 281 + _server.Parse(new Line(":other JOIN #chan")); 285 282 var user = _server.Users["other"]; 286 - Assert.IsNull(user.RealName); 287 - Assert.IsNull(_server.RealName); 283 + await Assert.That(user.RealName).IsNull(); 284 + await Assert.That(_server.RealName).IsNull(); 288 285 289 - _server.Parse(new(":nickname SETNAME :new now know how")); 290 - _server.Parse(new(":other SETNAME :tyrannosaurus hex")); 291 - Assert.AreEqual("new now know how", _server.RealName); 292 - Assert.AreEqual("tyrannosaurus hex", user.RealName); 286 + _server.Parse(new Line(":nickname SETNAME :new now know how")); 287 + _server.Parse(new Line(":other SETNAME :tyrannosaurus hex")); 288 + await Assert.That(_server.RealName).IsEqualTo("new now know how"); 289 + await Assert.That(user.RealName).IsEqualTo("tyrannosaurus hex"); 293 290 } 294 - } 291 + }
+31 -33
IRCSharp.Tests/State/Who.cs
··· 1 1 // ReSharper disable StringLiteralTypo 2 + 2 3 namespace IRCSharp.Tests.State; 3 4 4 - [TestClass] 5 5 public class Who 6 6 { 7 - private Server _server; 7 + private readonly Server _server = new("test"); 8 8 9 - [TestInitialize] 10 - public void TestInitialize() 9 + public Who() 11 10 { 12 - _server = new("test"); 13 - _server.Parse(new("001 nickname")); 14 - _server.Parse(new(":nickname JOIN #chan")); 11 + _server.Parse(new Line("001 nickname")); 12 + _server.Parse(new Line(":nickname JOIN #chan")); 15 13 } 16 14 17 - [TestMethod] 18 - public void WhoResponse() 15 + [Test] 16 + public async Task WhoResponse() 19 17 { 20 - _server.Parse(new("352 * #chan user host server nickname * :0 real")); 18 + _server.Parse(new Line("352 * #chan user host server nickname * :0 real")); 21 19 var user = _server.Users["nickname"]; 22 20 23 - Assert.AreEqual("user", user.UserName); 24 - Assert.AreEqual("host", _server.HostName); 25 - Assert.AreEqual("real", user.RealName); 21 + await Assert.That(user.UserName).IsEqualTo("user"); 22 + await Assert.That(_server.HostName).IsEqualTo("host"); 23 + await Assert.That(user.RealName).IsEqualTo("real"); 26 24 27 - Assert.AreEqual(user.UserName, _server.UserName); 28 - Assert.AreEqual(user.HostName, _server.HostName); 29 - Assert.AreEqual(user.RealName, _server.RealName); 25 + await Assert.That(_server.UserName).IsEqualTo(user.UserName); 26 + await Assert.That(_server.HostName).IsEqualTo(user.HostName); 27 + await Assert.That(_server.RealName).IsEqualTo(user.RealName); 30 28 } 31 29 32 - [TestMethod] 33 - public void Whox() 30 + [Test] 31 + public async Task Whox() 34 32 { 35 - _server.Parse(new($"354 * {Server.WhoType} user realip host nickname account :real")); 33 + _server.Parse(new Line($"354 * {Server.WhoType} user realip host nickname account :real")); 36 34 var user = _server.Users["nickname"]; 37 35 38 - Assert.AreEqual("user", user.UserName); 39 - Assert.AreEqual("host", user.HostName); 40 - Assert.AreEqual("real", user.RealName); 41 - Assert.AreEqual("account", user.Account); 36 + await Assert.That(user.UserName).IsEqualTo("user"); 37 + await Assert.That(user.HostName).IsEqualTo("host"); 38 + await Assert.That(user.RealName).IsEqualTo("real"); 39 + await Assert.That(user.Account).IsEqualTo("account"); 42 40 43 - Assert.AreEqual(user.UserName, _server.UserName); 44 - Assert.AreEqual(user.HostName, _server.HostName); 45 - Assert.AreEqual(user.RealName, _server.RealName); 46 - Assert.AreEqual(user.Account, _server.Account); 41 + await Assert.That(_server.UserName).IsEqualTo(user.UserName); 42 + await Assert.That(_server.HostName).IsEqualTo(user.HostName); 43 + await Assert.That(_server.RealName).IsEqualTo(user.RealName); 44 + await Assert.That(_server.Account).IsEqualTo(user.Account); 47 45 } 48 46 49 - [TestMethod] 50 - public void WhoxNoAccount() 47 + [Test] 48 + public async Task WhoxNoAccount() 51 49 { 52 - _server.Parse(new($"354 * {Server.WhoType} user realip host nickname 0 :real")); 50 + _server.Parse(new Line($"354 * {Server.WhoType} user realip host nickname 0 :real")); 53 51 var user = _server.Users["nickname"]; 54 52 55 - Assert.IsNull(user.Account); 56 - Assert.AreEqual(user.Account, _server.Account); 53 + await Assert.That(user.Account).IsNull(); 54 + await Assert.That(_server.Account).IsEqualTo(user.Account); 57 55 } 58 - } 56 + }
-28
IRCSharp.Tests/Tokenization/Data/JoinModel.cs
··· 1 - using YamlDotNet.Serialization; 2 - 3 - namespace IRCSharp.Tests.Tokenization.Data; 4 - 5 - public class JoinModel 6 - { 7 - public List<Test> Tests { get; set; } 8 - 9 - public class Test 10 - { 11 - [YamlMember(Alias = "desc")] public string Description { get; set; } 12 - 13 - public Atoms Atoms { get; set; } 14 - 15 - public List<string> Matches { get; set; } 16 - } 17 - 18 - public class Atoms 19 - { 20 - public Dictionary<string, string> Tags { get; set; } 21 - 22 - public string Source { get; set; } 23 - 24 - public string Verb { get; set; } 25 - 26 - public List<string> Params { get; set; } 27 - } 28 - }
-12
IRCSharp.Tests/Tokenization/Data/SplitModel.cs
··· 1 - namespace IRCSharp.Tests.Tokenization.Data; 2 - 3 - public class SplitModel 4 - { 5 - public List<Test> Tests { get; set; } 6 - 7 - public class Test 8 - { 9 - public string Input { get; set; } 10 - public JoinModel.Atoms Atoms { get; set; } 11 - } 12 - }
+181 -182
IRCSharp.Tests/Tokenization/Data/msg-join.yaml
··· 18 18 # some of the tests here originate from SaberUK's test vectors, which he's indicated I am free to include here 19 19 # https://github.com/SaberUK/ircparser/tree/master/test 20 20 21 - tests: 22 - # the desc string holds a description of the test, if it exists 21 + # the desc string holds a description of the test, if it exists 23 22 24 - # the atoms dict has the keys: 25 - # * tags: tags dict 26 - # tags with no value are an empty string 27 - # * source: source string, without single leading colon 28 - # * verb: verb string 29 - # * params: params split up as a list 30 - # if the params key does not exist, assume it is empty 31 - # if any other keys do no exist, assume they are null 32 - # a key that is null does not exist or is not specified with the 33 - # given input string 23 + # the atoms dict has the keys: 24 + # * tags: tags dict 25 + # tags with no value are an empty string 26 + # * source: source string, without single leading colon 27 + # * verb: verb string 28 + # * params: params split up as a list 29 + # if the params key does not exist, assume it is empty 30 + # if any other keys do no exist, assume they are null 31 + # a key that is null does not exist or is not specified with the 32 + # given input string 34 33 35 - # matches is a list of messages that match 34 + # matches is a list of messages that match 36 35 37 - # simple tests 38 - - desc: Simple test with verb and params. 39 - atoms: 40 - verb: "foo" 41 - params: 42 - - "bar" 43 - - "baz" 44 - - "asdf" 45 - matches: 46 - - "foo bar baz asdf" 47 - - "foo bar baz :asdf" 36 + # simple tests 37 + - desc: Simple test with verb and params. 38 + atoms: 39 + verb: "foo" 40 + params: 41 + - "bar" 42 + - "baz" 43 + - "asdf" 44 + matches: 45 + - "foo bar baz asdf" 46 + - "foo bar baz :asdf" 48 47 49 - # with no regular params 50 - - desc: Simple test with source and no params. 51 - atoms: 52 - source: "src" 53 - verb: "AWAY" 54 - matches: 55 - - ":src AWAY" 48 + # with no regular params 49 + - desc: Simple test with source and no params. 50 + atoms: 51 + source: "src" 52 + verb: "AWAY" 53 + matches: 54 + - ":src AWAY" 56 55 57 - - desc: Simple test with source and empty trailing param. 58 - atoms: 59 - source: "src" 60 - verb: "AWAY" 61 - params: 62 - - "" 63 - matches: 64 - - ":src AWAY :" 56 + - desc: Simple test with source and empty trailing param. 57 + atoms: 58 + source: "src" 59 + verb: "AWAY" 60 + params: 61 + - "" 62 + matches: 63 + - ":src AWAY :" 65 64 66 - # with source 67 - - desc: Simple test with source. 68 - atoms: 69 - source: "coolguy" 70 - verb: "foo" 71 - params: 72 - - "bar" 73 - - "baz" 74 - - "asdf" 75 - matches: 76 - - ":coolguy foo bar baz asdf" 77 - - ":coolguy foo bar baz :asdf" 65 + # with source 66 + - desc: Simple test with source. 67 + atoms: 68 + source: "coolguy" 69 + verb: "foo" 70 + params: 71 + - "bar" 72 + - "baz" 73 + - "asdf" 74 + matches: 75 + - ":coolguy foo bar baz asdf" 76 + - ":coolguy foo bar baz :asdf" 78 77 79 - # with trailing param 80 - - desc: Simple test with trailing param. 81 - atoms: 82 - verb: "foo" 83 - params: 84 - - "bar" 85 - - "baz" 86 - - "asdf quux" 87 - matches: 88 - - "foo bar baz :asdf quux" 78 + # with trailing param 79 + - desc: Simple test with trailing param. 80 + atoms: 81 + verb: "foo" 82 + params: 83 + - "bar" 84 + - "baz" 85 + - "asdf quux" 86 + matches: 87 + - "foo bar baz :asdf quux" 89 88 90 - - desc: Simple test with empty trailing param. 91 - atoms: 92 - verb: "foo" 93 - params: 94 - - "bar" 95 - - "baz" 96 - - "" 97 - matches: 98 - - "foo bar baz :" 89 + - desc: Simple test with empty trailing param. 90 + atoms: 91 + verb: "foo" 92 + params: 93 + - "bar" 94 + - "baz" 95 + - "" 96 + matches: 97 + - "foo bar baz :" 99 98 100 - - desc: Simple test with trailing param containing colon. 101 - atoms: 102 - verb: "foo" 103 - params: 104 - - "bar" 105 - - "baz" 106 - - ":asdf" 107 - matches: 108 - - "foo bar baz ::asdf" 99 + - desc: Simple test with trailing param containing colon. 100 + atoms: 101 + verb: "foo" 102 + params: 103 + - "bar" 104 + - "baz" 105 + - ":asdf" 106 + matches: 107 + - "foo bar baz ::asdf" 109 108 110 - # with source and trailing param 111 - - desc: Test with source and trailing param. 112 - atoms: 113 - source: "coolguy" 114 - verb: "foo" 115 - params: 116 - - "bar" 117 - - "baz" 118 - - "asdf quux" 119 - matches: 120 - - ":coolguy foo bar baz :asdf quux" 109 + # with source and trailing param 110 + - desc: Test with source and trailing param. 111 + atoms: 112 + source: "coolguy" 113 + verb: "foo" 114 + params: 115 + - "bar" 116 + - "baz" 117 + - "asdf quux" 118 + matches: 119 + - ":coolguy foo bar baz :asdf quux" 121 120 122 - - desc: Test with trailing containing beginning+end whitespace. 123 - atoms: 124 - source: "coolguy" 125 - verb: "foo" 126 - params: 127 - - "bar" 128 - - "baz" 129 - - " asdf quux " 130 - matches: 131 - - ":coolguy foo bar baz : asdf quux " 121 + - desc: Test with trailing containing beginning+end whitespace. 122 + atoms: 123 + source: "coolguy" 124 + verb: "foo" 125 + params: 126 + - "bar" 127 + - "baz" 128 + - " asdf quux " 129 + matches: 130 + - ":coolguy foo bar baz : asdf quux " 132 131 133 - - desc: Test with trailing containing what looks like another trailing param. 134 - atoms: 135 - source: "coolguy" 136 - verb: "PRIVMSG" 137 - params: 138 - - "bar" 139 - - "lol :) " 140 - matches: 141 - - ":coolguy PRIVMSG bar :lol :) " 132 + - desc: Test with trailing containing what looks like another trailing param. 133 + atoms: 134 + source: "coolguy" 135 + verb: "PRIVMSG" 136 + params: 137 + - "bar" 138 + - "lol :) " 139 + matches: 140 + - ":coolguy PRIVMSG bar :lol :) " 142 141 143 - - desc: Simple test with source and empty trailing. 144 - atoms: 145 - source: "coolguy" 146 - verb: "foo" 147 - params: 148 - - "bar" 149 - - "baz" 150 - - "" 151 - matches: 152 - - ":coolguy foo bar baz :" 142 + - desc: Simple test with source and empty trailing. 143 + atoms: 144 + source: "coolguy" 145 + verb: "foo" 146 + params: 147 + - "bar" 148 + - "baz" 149 + - "" 150 + matches: 151 + - ":coolguy foo bar baz :" 153 152 154 - - desc: Trailing contains only spaces. 155 - atoms: 156 - source: "coolguy" 157 - verb: "foo" 158 - params: 159 - - "bar" 160 - - "baz" 161 - - " " 162 - matches: 163 - - ":coolguy foo bar baz : " 153 + - desc: Trailing contains only spaces. 154 + atoms: 155 + source: "coolguy" 156 + verb: "foo" 157 + params: 158 + - "bar" 159 + - "baz" 160 + - " " 161 + matches: 162 + - ":coolguy foo bar baz : " 164 163 165 - - desc: Param containing tab (tab is not considered SPACE for message splitting). 166 - atoms: 167 - source: "coolguy" 168 - verb: "foo" 169 - params: 170 - - "b\tar" 171 - - "baz" 172 - matches: 173 - - ":coolguy foo b\tar baz" 174 - - ":coolguy foo b\tar :baz" 164 + - desc: Param containing tab (tab is not considered SPACE for message splitting). 165 + atoms: 166 + source: "coolguy" 167 + verb: "foo" 168 + params: 169 + - "b\tar" 170 + - "baz" 171 + matches: 172 + - ":coolguy foo b\tar baz" 173 + - ":coolguy foo b\tar :baz" 175 174 176 - # with tags 177 - - desc: Tag with no value and space-filled trailing. 178 - atoms: 179 - tags: 180 - "asd": "" 181 - source: "coolguy" 182 - verb: "foo" 183 - params: 184 - - "bar" 185 - - "baz" 186 - - " " 187 - matches: 188 - - "@asd :coolguy foo bar baz : " 175 + # with tags 176 + - desc: Tag with no value and space-filled trailing. 177 + atoms: 178 + tags: 179 + "asd": "" 180 + source: "coolguy" 181 + verb: "foo" 182 + params: 183 + - "bar" 184 + - "baz" 185 + - " " 186 + matches: 187 + - "@asd :coolguy foo bar baz : " 189 188 190 - - desc: Tags with escaped values. 191 - atoms: 192 - verb: "foo" 193 - tags: 194 - "a": "b\\and\nk" 195 - "d": "gh;764" 196 - matches: 197 - - "@a=b\\\\and\\nk;d=gh\\:764 foo" 198 - - "@d=gh\\:764;a=b\\\\and\\nk foo" 189 + - desc: Tags with escaped values. 190 + atoms: 191 + verb: "foo" 192 + tags: 193 + "a": "b\\and\nk" 194 + "d": "gh;764" 195 + matches: 196 + - "@a=b\\\\and\\nk;d=gh\\:764 foo" 197 + - "@d=gh\\:764;a=b\\\\and\\nk foo" 199 198 200 - - desc: Tags with escaped values and params. 201 - atoms: 202 - verb: "foo" 203 - tags: 204 - "a": "b\\and\nk" 205 - "d": "gh;764" 206 - params: 207 - - "par1" 208 - - "par2" 209 - matches: 210 - - "@a=b\\\\and\\nk;d=gh\\:764 foo par1 par2" 211 - - "@a=b\\\\and\\nk;d=gh\\:764 foo par1 :par2" 212 - - "@d=gh\\:764;a=b\\\\and\\nk foo par1 par2" 213 - - "@d=gh\\:764;a=b\\\\and\\nk foo par1 :par2" 199 + - desc: Tags with escaped values and params. 200 + atoms: 201 + verb: "foo" 202 + tags: 203 + "a": "b\\and\nk" 204 + "d": "gh;764" 205 + params: 206 + - "par1" 207 + - "par2" 208 + matches: 209 + - "@a=b\\\\and\\nk;d=gh\\:764 foo par1 par2" 210 + - "@a=b\\\\and\\nk;d=gh\\:764 foo par1 :par2" 211 + - "@d=gh\\:764;a=b\\\\and\\nk foo par1 par2" 212 + - "@d=gh\\:764;a=b\\\\and\\nk foo par1 :par2" 214 213 215 - - desc: Tag with long, strange values (including LF and newline). 216 - atoms: 217 - tags: 218 - foo: "\\\\;\\s \r\n" 219 - verb: "COMMAND" 220 - matches: 221 - - "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND" 214 + - desc: Tag with long, strange values (including LF and newline). 215 + atoms: 216 + tags: 217 + foo: "\\\\;\\s \r\n" 218 + verb: "COMMAND" 219 + matches: 220 + - "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND"
+281 -282
IRCSharp.Tests/Tokenization/Data/msg-split.yaml
··· 23 23 # separated by one (or more) ASCII space character(s) (0x20). 24 24 # because doing it as RFC2812 says (strictly as a single ascii space) isn't sane 25 25 26 - tests: 27 - # input is the string coming directly from the server to parse 26 + # input is the string coming directly from the server to parse 28 27 29 - # the atoms dict has the keys: 30 - # * tags: tags dict 31 - # tags with no value are an empty string 32 - # * source: source string, without single leading colon 33 - # * verb: verb string 34 - # * params: params split up as a list 35 - # if the params key does not exist, assume it is empty 36 - # if any other keys do no exist, assume they are null 37 - # a key that is null does not exist or is not specified with the 38 - # given input string 28 + # the atoms dict has the keys: 29 + # * tags: tags dict 30 + # tags with no value are an empty string 31 + # * source: source string, without single leading colon 32 + # * verb: verb string 33 + # * params: params split up as a list 34 + # if the params key does not exist, assume it is empty 35 + # if any other keys do no exist, assume they are null 36 + # a key that is null does not exist or is not specified with the 37 + # given input string 39 38 40 - # simple 41 - - input: "foo bar baz asdf" 42 - atoms: 43 - verb: "foo" 44 - params: 45 - - "bar" 46 - - "baz" 47 - - "asdf" 39 + # simple 40 + - input: "foo bar baz asdf" 41 + atoms: 42 + verb: "foo" 43 + params: 44 + - "bar" 45 + - "baz" 46 + - "asdf" 48 47 49 - # with source 50 - - input: ":coolguy foo bar baz asdf" 51 - atoms: 52 - source: "coolguy" 53 - verb: "foo" 54 - params: 55 - - "bar" 56 - - "baz" 57 - - "asdf" 48 + # with source 49 + - input: ":coolguy foo bar baz asdf" 50 + atoms: 51 + source: "coolguy" 52 + verb: "foo" 53 + params: 54 + - "bar" 55 + - "baz" 56 + - "asdf" 58 57 59 - # with trailing param 60 - - input: "foo bar baz :asdf quux" 61 - atoms: 62 - verb: "foo" 63 - params: 64 - - "bar" 65 - - "baz" 66 - - "asdf quux" 58 + # with trailing param 59 + - input: "foo bar baz :asdf quux" 60 + atoms: 61 + verb: "foo" 62 + params: 63 + - "bar" 64 + - "baz" 65 + - "asdf quux" 67 66 68 - - input: "foo bar baz :" 69 - atoms: 70 - verb: "foo" 71 - params: 72 - - "bar" 73 - - "baz" 74 - - "" 67 + - input: "foo bar baz :" 68 + atoms: 69 + verb: "foo" 70 + params: 71 + - "bar" 72 + - "baz" 73 + - "" 75 74 76 - - input: "foo bar baz ::asdf" 77 - atoms: 78 - verb: "foo" 79 - params: 80 - - "bar" 81 - - "baz" 82 - - ":asdf" 75 + - input: "foo bar baz ::asdf" 76 + atoms: 77 + verb: "foo" 78 + params: 79 + - "bar" 80 + - "baz" 81 + - ":asdf" 83 82 84 - # with source and trailing param 85 - - input: ":coolguy foo bar baz :asdf quux" 86 - atoms: 87 - source: "coolguy" 88 - verb: "foo" 89 - params: 90 - - "bar" 91 - - "baz" 92 - - "asdf quux" 83 + # with source and trailing param 84 + - input: ":coolguy foo bar baz :asdf quux" 85 + atoms: 86 + source: "coolguy" 87 + verb: "foo" 88 + params: 89 + - "bar" 90 + - "baz" 91 + - "asdf quux" 93 92 94 - - input: ":coolguy foo bar baz : asdf quux " 95 - atoms: 96 - source: "coolguy" 97 - verb: "foo" 98 - params: 99 - - "bar" 100 - - "baz" 101 - - " asdf quux " 93 + - input: ":coolguy foo bar baz : asdf quux " 94 + atoms: 95 + source: "coolguy" 96 + verb: "foo" 97 + params: 98 + - "bar" 99 + - "baz" 100 + - " asdf quux " 102 101 103 - - input: ":coolguy PRIVMSG bar :lol :) " 104 - atoms: 105 - source: "coolguy" 106 - verb: "PRIVMSG" 107 - params: 108 - - "bar" 109 - - "lol :) " 102 + - input: ":coolguy PRIVMSG bar :lol :) " 103 + atoms: 104 + source: "coolguy" 105 + verb: "PRIVMSG" 106 + params: 107 + - "bar" 108 + - "lol :) " 110 109 111 - - input: ":coolguy foo bar baz :" 112 - atoms: 113 - source: "coolguy" 114 - verb: "foo" 115 - params: 116 - - "bar" 117 - - "baz" 118 - - "" 110 + - input: ":coolguy foo bar baz :" 111 + atoms: 112 + source: "coolguy" 113 + verb: "foo" 114 + params: 115 + - "bar" 116 + - "baz" 117 + - "" 119 118 120 - - input: ":coolguy foo bar baz : " 121 - atoms: 122 - source: "coolguy" 123 - verb: "foo" 124 - params: 125 - - "bar" 126 - - "baz" 127 - - " " 119 + - input: ":coolguy foo bar baz : " 120 + atoms: 121 + source: "coolguy" 122 + verb: "foo" 123 + params: 124 + - "bar" 125 + - "baz" 126 + - " " 128 127 129 - # with tags 130 - - input: "@a=b;c=32;k;rt=ql7 foo" 131 - atoms: 132 - verb: "foo" 133 - tags: 134 - "a": "b" 135 - "c": "32" 136 - "k": 137 - "rt": "ql7" 128 + # with tags 129 + - input: "@a=b;c=32;k;rt=ql7 foo" 130 + atoms: 131 + verb: "foo" 132 + tags: 133 + "a": "b" 134 + "c": "32" 135 + "k": 136 + "rt": "ql7" 138 137 139 - # with escaped tags 140 - - input: "@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo" 141 - atoms: 142 - verb: "foo" 143 - tags: 144 - "a": "b\\and\nk" 145 - "c": "72 45" 146 - "d": "gh;764" 138 + # with escaped tags 139 + - input: "@a=b\\\\and\\nk;c=72\\s45;d=gh\\:764 foo" 140 + atoms: 141 + verb: "foo" 142 + tags: 143 + "a": "b\\and\nk" 144 + "c": "72 45" 145 + "d": "gh;764" 147 146 148 - # with tags and source 149 - - input: "@c;h=;a=b :quux ab cd" 150 - atoms: 151 - tags: 152 - "c": 153 - "h": "" 154 - "a": "b" 155 - source: "quux" 156 - verb: "ab" 157 - params: 158 - - "cd" 147 + # with tags and source 148 + - input: "@c;h=;a=b :quux ab cd" 149 + atoms: 150 + tags: 151 + "c": 152 + "h": "" 153 + "a": "b" 154 + source: "quux" 155 + verb: "ab" 156 + params: 157 + - "cd" 159 158 160 - # different forms of last param 161 - - input: ":src JOIN #chan" 162 - atoms: 163 - source: "src" 164 - verb: "JOIN" 165 - params: 166 - - "#chan" 159 + # different forms of last param 160 + - input: ":src JOIN #chan" 161 + atoms: 162 + source: "src" 163 + verb: "JOIN" 164 + params: 165 + - "#chan" 167 166 168 - - input: ":src JOIN :#chan" 169 - atoms: 170 - source: "src" 171 - verb: "JOIN" 172 - params: 173 - - "#chan" 167 + - input: ":src JOIN :#chan" 168 + atoms: 169 + source: "src" 170 + verb: "JOIN" 171 + params: 172 + - "#chan" 174 173 175 - # with and without last param 176 - - input: ":src AWAY" 177 - atoms: 178 - source: "src" 179 - verb: "AWAY" 174 + # with and without last param 175 + - input: ":src AWAY" 176 + atoms: 177 + source: "src" 178 + verb: "AWAY" 180 179 181 - - input: ":src AWAY " 182 - atoms: 183 - source: "src" 184 - verb: "AWAY" 180 + - input: ":src AWAY " 181 + atoms: 182 + source: "src" 183 + verb: "AWAY" 185 184 186 - # tab is not considered <SPACE> 187 - - input: ":cool\tguy foo bar baz" 188 - atoms: 189 - source: "cool\tguy" 190 - verb: "foo" 191 - params: 192 - - "bar" 193 - - "baz" 185 + # tab is not considered <SPACE> 186 + - input: ":cool\tguy foo bar baz" 187 + atoms: 188 + source: "cool\tguy" 189 + verb: "foo" 190 + params: 191 + - "bar" 192 + - "baz" 194 193 195 - # with weird control codes in the source 196 - - input: ":coolguy!ag@net\x035w\x03ork.admin PRIVMSG foo :bar baz" 197 - atoms: 198 - source: "coolguy!ag@net\x035w\x03ork.admin" 199 - verb: "PRIVMSG" 200 - params: 201 - - "foo" 202 - - "bar baz" 194 + # with weird control codes in the source 195 + - input: ":coolguy!ag@net\x035w\x03ork.admin PRIVMSG foo :bar baz" 196 + atoms: 197 + source: "coolguy!ag@net\x035w\x03ork.admin" 198 + verb: "PRIVMSG" 199 + params: 200 + - "foo" 201 + - "bar baz" 203 202 204 - - input: ":coolguy!~ag@n\x02et\x0305w\x0fork.admin PRIVMSG foo :bar baz" 205 - atoms: 206 - source: "coolguy!~ag@n\x02et\x0305w\x0fork.admin" 207 - verb: "PRIVMSG" 208 - params: 209 - - "foo" 210 - - "bar baz" 203 + - input: ":coolguy!~ag@n\x02et\x0305w\x0fork.admin PRIVMSG foo :bar baz" 204 + atoms: 205 + source: "coolguy!~ag@n\x02et\x0305w\x0fork.admin" 206 + verb: "PRIVMSG" 207 + params: 208 + - "foo" 209 + - "bar baz" 211 210 212 - - input: "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= :irc.example.com COMMAND param1 param2 :param3 param3" 213 - atoms: 214 - tags: 215 - tag1: "value1" 216 - tag2: 217 - vendor1/tag3: "value2" 218 - vendor2/tag4: "" 219 - source: "irc.example.com" 220 - verb: "COMMAND" 221 - params: 222 - - "param1" 223 - - "param2" 224 - - "param3 param3" 211 + - input: "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4= :irc.example.com COMMAND param1 param2 :param3 param3" 212 + atoms: 213 + tags: 214 + tag1: "value1" 215 + tag2: 216 + vendor1/tag3: "value2" 217 + vendor2/tag4: "" 218 + source: "irc.example.com" 219 + verb: "COMMAND" 220 + params: 221 + - "param1" 222 + - "param2" 223 + - "param3 param3" 225 224 226 - - input: ":irc.example.com COMMAND param1 param2 :param3 param3" 227 - atoms: 228 - source: "irc.example.com" 229 - verb: "COMMAND" 230 - params: 231 - - "param1" 232 - - "param2" 233 - - "param3 param3" 225 + - input: ":irc.example.com COMMAND param1 param2 :param3 param3" 226 + atoms: 227 + source: "irc.example.com" 228 + verb: "COMMAND" 229 + params: 230 + - "param1" 231 + - "param2" 232 + - "param3 param3" 234 233 235 - - input: "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4 COMMAND param1 param2 :param3 param3" 236 - atoms: 237 - tags: 238 - tag1: "value1" 239 - tag2: 240 - vendor1/tag3: "value2" 241 - vendor2/tag4: 242 - verb: "COMMAND" 243 - params: 244 - - "param1" 245 - - "param2" 246 - - "param3 param3" 234 + - input: "@tag1=value1;tag2;vendor1/tag3=value2;vendor2/tag4 COMMAND param1 param2 :param3 param3" 235 + atoms: 236 + tags: 237 + tag1: "value1" 238 + tag2: 239 + vendor1/tag3: "value2" 240 + vendor2/tag4: 241 + verb: "COMMAND" 242 + params: 243 + - "param1" 244 + - "param2" 245 + - "param3 param3" 247 246 248 - - input: "COMMAND" 249 - atoms: 250 - verb: "COMMAND" 247 + - input: "COMMAND" 248 + atoms: 249 + verb: "COMMAND" 251 250 252 - # yaml encoding + slashes is fun 253 - - input: "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND" 254 - atoms: 255 - tags: 256 - foo: "\\\\;\\s \r\n" 257 - verb: "COMMAND" 251 + # yaml encoding + slashes is fun 252 + - input: "@foo=\\\\\\\\\\:\\\\s\\s\\r\\n COMMAND" 253 + atoms: 254 + tags: 255 + foo: "\\\\;\\s \r\n" 256 + verb: "COMMAND" 258 257 259 - # broken messages from unreal 260 - - input: ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters" 261 - atoms: 262 - source: "gravel.mozilla.org" 263 - verb: "432" 264 - params: 265 - - "#momo" 266 - - "Erroneous Nickname: Illegal characters" 258 + # broken messages from unreal 259 + - input: ":gravel.mozilla.org 432 #momo :Erroneous Nickname: Illegal characters" 260 + atoms: 261 + source: "gravel.mozilla.org" 262 + verb: "432" 263 + params: 264 + - "#momo" 265 + - "Erroneous Nickname: Illegal characters" 267 266 268 - - input: ":gravel.mozilla.org MODE #tckk +n " 269 - atoms: 270 - source: "gravel.mozilla.org" 271 - verb: "MODE" 272 - params: 273 - - "#tckk" 274 - - "+n" 267 + - input: ":gravel.mozilla.org MODE #tckk +n " 268 + atoms: 269 + source: "gravel.mozilla.org" 270 + verb: "MODE" 271 + params: 272 + - "#tckk" 273 + - "+n" 275 274 276 - - input: ":services.esper.net MODE #foo-bar +o foobar " 277 - atoms: 278 - source: "services.esper.net" 279 - verb: "MODE" 280 - params: 281 - - "#foo-bar" 282 - - "+o" 283 - - "foobar" 275 + - input: ":services.esper.net MODE #foo-bar +o foobar " 276 + atoms: 277 + source: "services.esper.net" 278 + verb: "MODE" 279 + params: 280 + - "#foo-bar" 281 + - "+o" 282 + - "foobar" 284 283 285 - # tag values should be parsed char-at-a-time to prevent wayward replacements. 286 - - input: "@tag1=value\\\\ntest COMMAND" 287 - atoms: 288 - tags: 289 - tag1: "value\\ntest" 290 - verb: "COMMAND" 284 + # tag values should be parsed char-at-a-time to prevent wayward replacements. 285 + - input: "@tag1=value\\\\ntest COMMAND" 286 + atoms: 287 + tags: 288 + tag1: "value\\ntest" 289 + verb: "COMMAND" 291 290 292 - # If a tag value has a slash followed by a character which doesn't need 293 - # to be escaped, the slash should be dropped. 294 - - input: "@tag1=value\\1 COMMAND" 295 - atoms: 296 - tags: 297 - tag1: "value1" 298 - verb: "COMMAND" 291 + # If a tag value has a slash followed by a character which doesn't need 292 + # to be escaped, the slash should be dropped. 293 + - input: "@tag1=value\\1 COMMAND" 294 + atoms: 295 + tags: 296 + tag1: "value1" 297 + verb: "COMMAND" 299 298 300 - # A slash at the end of a tag value should be dropped 301 - - input: "@tag1=value1\\ COMMAND" 302 - atoms: 303 - tags: 304 - tag1: "value1" 305 - verb: "COMMAND" 299 + # A slash at the end of a tag value should be dropped 300 + - input: "@tag1=value1\\ COMMAND" 301 + atoms: 302 + tags: 303 + tag1: "value1" 304 + verb: "COMMAND" 306 305 307 - # Duplicate tags: Parsers SHOULD disregard all but the final occurence 308 - - input: "@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND" 309 - atoms: 310 - tags: 311 - tag1: "5" 312 - tag2: "3" 313 - tag3: "4" 314 - verb: "COMMAND" 306 + # Duplicate tags: Parsers SHOULD disregard all but the final occurence 307 + - input: "@tag1=1;tag2=3;tag3=4;tag1=5 COMMAND" 308 + atoms: 309 + tags: 310 + tag1: "5" 311 + tag2: "3" 312 + tag3: "4" 313 + verb: "COMMAND" 315 314 316 - # vendored tags can have the same name as a non-vendored tag 317 - - input: "@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND" 318 - atoms: 319 - tags: 320 - tag1: "5" 321 - tag2: "3" 322 - tag3: "4" 323 - vendor/tag2: "8" 324 - verb: "COMMAND" 315 + # vendored tags can have the same name as a non-vendored tag 316 + - input: "@tag1=1;tag2=3;tag3=4;tag1=5;vendor/tag2=8 COMMAND" 317 + atoms: 318 + tags: 319 + tag1: "5" 320 + tag2: "3" 321 + tag3: "4" 322 + vendor/tag2: "8" 323 + verb: "COMMAND" 325 324 326 - # Some parsers handle /MODE in a special way, make sure they do it right 327 - - input: ":SomeOp MODE #channel :+i" 328 - atoms: 329 - source: "SomeOp" 330 - verb: "MODE" 331 - params: 332 - - "#channel" 333 - - "+i" 325 + # Some parsers handle /MODE in a special way, make sure they do it right 326 + - input: ":SomeOp MODE #channel :+i" 327 + atoms: 328 + source: "SomeOp" 329 + verb: "MODE" 330 + params: 331 + - "#channel" 332 + - "+i" 334 333 335 - - input: ":SomeOp MODE #channel +oo SomeUser :AnotherUser" 336 - atoms: 337 - source: "SomeOp" 338 - verb: "MODE" 339 - params: 340 - - "#channel" 341 - - "+oo" 342 - - "SomeUser" 343 - - "AnotherUser" 334 + - input: ":SomeOp MODE #channel +oo SomeUser :AnotherUser" 335 + atoms: 336 + source: "SomeOp" 337 + verb: "MODE" 338 + params: 339 + - "#channel" 340 + - "+oo" 341 + - "SomeUser" 342 + - "AnotherUser"
+51 -51
IRCSharp.Tests/Tokenization/Format.cs
··· 1 1 // ReSharper disable StringLiteralTypo 2 + 2 3 namespace IRCSharp.Tests.Tokenization; 3 4 4 - [TestClass] 5 5 public class Format 6 6 { 7 - [TestMethod] 8 - public void Tags() 7 + [Test] 8 + public async Task Tags() 9 9 { 10 10 var line = new Line("PRIVMSG", "#channel", "hello") 11 11 { 12 - Tags = new() {{"id", "\\" + " " + ";" + "\r\n"}} 13 - }.Format(); 12 + Tags = new Dictionary<string, string> { { "id", "\\ ;\r\n" } } 13 + }; 14 14 15 - Assert.AreEqual(@"@id=\\\s\:\r\n PRIVMSG #channel hello", line); 15 + await Assert.That(line.Format()).IsEqualTo(@"@id=\\\s\:\r\n PRIVMSG #channel hello"); 16 16 } 17 17 18 - [TestMethod] 19 - public void MissingTag() 18 + [Test] 19 + public async Task MissingTag() 20 20 { 21 - var line = new Line("PRIVMSG", "#channel", "hello").Format(); 21 + var line = new Line("PRIVMSG", "#channel", "hello"); 22 22 23 - Assert.AreEqual("PRIVMSG #channel hello", line); 23 + await Assert.That(line.Format()).IsEqualTo("PRIVMSG #channel hello"); 24 24 } 25 25 26 - [TestMethod] 27 - public void NullTag() 26 + [Test] 27 + public async Task NullTag() 28 28 { 29 - var line = new Line("PRIVMSG", "#channel", "hello") {Tags = new() {{"a", null}}} 30 - .Format(); 29 + var line = new Line("PRIVMSG", "#channel", "hello") { Tags = new Dictionary<string, string> { { "a", null } } }; 31 30 32 - Assert.AreEqual("@a PRIVMSG #channel hello", line); 31 + await Assert.That(line.Format()).IsEqualTo("@a PRIVMSG #channel hello"); 33 32 } 34 33 35 - [TestMethod] 36 - public void EmptyTag() 34 + [Test] 35 + public async Task EmptyTag() 37 36 { 38 - var line = new Line("PRIVMSG", "#channel", "hello") {Tags = new() {{"a", ""}}} 39 - .Format(); 37 + var line = new Line("PRIVMSG", "#channel", "hello") { Tags = new Dictionary<string, string> { { "a", "" } } }; 40 38 41 - Assert.AreEqual("@a PRIVMSG #channel hello", line); 39 + await Assert.That(line.Format()).IsEqualTo("@a PRIVMSG #channel hello"); 42 40 } 43 41 44 - [TestMethod] 45 - public void Source() 42 + [Test] 43 + public async Task Source() 46 44 { 47 - var line = new Line("PRIVMSG", "#channel", "hello") {Source = "nick!user@host"}.Format(); 45 + var line = new Line("PRIVMSG", "#channel", "hello") { Source = "nick!user@host" }; 48 46 49 - Assert.AreEqual(":nick!user@host PRIVMSG #channel hello", line); 47 + await Assert.That(line.Format()).IsEqualTo(":nick!user@host PRIVMSG #channel hello"); 50 48 } 51 49 52 - [TestMethod] 53 - public void CommandLowercase() 50 + [Test] 51 + public async Task CommandLowercase() 54 52 { 55 - var line = new Line {Command = "privmsg"}.Format(); 56 - Assert.AreEqual("privmsg", line); 53 + var line = new Line { Command = "privmsg" }; 54 + 55 + await Assert.That(line.Format()).IsEqualTo("privmsg"); 57 56 } 58 57 59 - [TestMethod] 60 - public void CommandUppercase() 58 + [Test] 59 + public async Task CommandUppercase() 61 60 { 62 - var line = new Line {Command = "PRIVMSG"}.Format(); 63 - Assert.AreEqual("PRIVMSG", line); 61 + var line = new Line { Command = "PRIVMSG" }; 62 + 63 + await Assert.That(line.Format()).IsEqualTo("PRIVMSG"); 64 64 } 65 65 66 - [TestMethod] 67 - public void TrailingSpace() 66 + [Test] 67 + public async Task TrailingSpace() 68 68 { 69 - var line = new Line("PRIVMSG", "#channel", "hello world").Format(); 69 + var line = new Line("PRIVMSG", "#channel", "hello world"); 70 70 71 - Assert.AreEqual("PRIVMSG #channel :hello world", line); 71 + await Assert.That(line.Format()).IsEqualTo("PRIVMSG #channel :hello world"); 72 72 } 73 73 74 - [TestMethod] 75 - public void TrailingNoSpace() 74 + [Test] 75 + public async Task TrailingNoSpace() 76 76 { 77 - var line = new Line("PRIVMSG", "#channel", "helloworld").Format(); 77 + var line = new Line("PRIVMSG", "#channel", "helloworld"); 78 78 79 - Assert.AreEqual("PRIVMSG #channel helloworld", line); 79 + await Assert.That(line.Format()).IsEqualTo("PRIVMSG #channel helloworld"); 80 80 } 81 81 82 - [TestMethod] 83 - public void TrailingDoubleColon() 82 + [Test] 83 + public async Task TrailingDoubleColon() 84 84 { 85 - var line = new Line("PRIVMSG", "#channel", ":helloworld").Format(); 85 + var line = new Line("PRIVMSG", "#channel", ":helloworld"); 86 86 87 - Assert.AreEqual("PRIVMSG #channel ::helloworld", line); 87 + await Assert.That(line.Format()).IsEqualTo("PRIVMSG #channel ::helloworld"); 88 88 } 89 89 90 - [TestMethod] 91 - public void InvalidNonLastSpace() 90 + [Test] 91 + public async Task InvalidNonLastSpace() 92 92 { 93 - Assert.ThrowsException<ArgumentException>(() => { new Line("USER", "user", "0 *", "real name").Format(); }); 93 + await Assert.That(() => new Line("USER", "user", "0 *", "real name").Format()).Throws<ArgumentException>(); 94 94 } 95 95 96 - [TestMethod] 97 - public void InvalidNonLastColon() 96 + [Test] 97 + public async Task InvalidNonLastColon() 98 98 { 99 - Assert.ThrowsException<ArgumentException>(() => { new Line("PRIVMSG", ":#channel", "hello").Format(); }); 99 + await Assert.That(() => new Line("PRIVMSG", ":#channel", "hello").Format()).Throws<ArgumentException>(); 100 100 } 101 - } 101 + }
+32 -33
IRCSharp.Tests/Tokenization/Hostmask.cs
··· 1 1 namespace IRCSharp.Tests.Tokenization; 2 2 3 - [TestClass] 4 3 public class Hostmask 5 4 { 6 - [TestMethod] 7 - public void FullHostmask() 5 + [Test] 6 + public async Task FullHostmask() 8 7 { 9 8 var hostmask = new IRCTokens.Hostmask("nick!user@host"); 10 - Assert.AreEqual("nick", hostmask.NickName); 11 - Assert.AreEqual("user", hostmask.UserName); 12 - Assert.AreEqual("host", hostmask.HostName); 9 + await Assert.That(hostmask.NickName).IsEqualTo("nick"); 10 + await Assert.That(hostmask.UserName).IsEqualTo("user"); 11 + await Assert.That(hostmask.HostName).IsEqualTo("host"); 13 12 } 14 13 15 - [TestMethod] 16 - public void NoHostName() 14 + [Test] 15 + public async Task NoHostName() 17 16 { 18 17 var hostmask = new IRCTokens.Hostmask("nick!user"); 19 - Assert.AreEqual("nick", hostmask.NickName); 20 - Assert.AreEqual("user", hostmask.UserName); 21 - Assert.IsNull(hostmask.HostName); 18 + await Assert.That(hostmask.NickName).IsEqualTo("nick"); 19 + await Assert.That(hostmask.UserName).IsEqualTo("user"); 20 + await Assert.That(hostmask.HostName).IsNull(); 22 21 } 23 22 24 - [TestMethod] 25 - public void NoUserName() 23 + [Test] 24 + public async Task NoUserName() 26 25 { 27 26 var hostmask = new IRCTokens.Hostmask("nick@host"); 28 - Assert.AreEqual("nick", hostmask.NickName); 29 - Assert.IsNull(hostmask.UserName); 30 - Assert.AreEqual("host", hostmask.HostName); 27 + await Assert.That(hostmask.NickName).IsEqualTo("nick"); 28 + await Assert.That(hostmask.UserName).IsNull(); 29 + await Assert.That(hostmask.HostName).IsEqualTo("host"); 31 30 } 32 31 33 - [TestMethod] 34 - public void OnlyNickName() 32 + [Test] 33 + public async Task OnlyNickName() 35 34 { 36 35 var hostmask = new IRCTokens.Hostmask("nick"); 37 - Assert.AreEqual("nick", hostmask.NickName); 38 - Assert.IsNull(hostmask.UserName); 39 - Assert.IsNull(hostmask.HostName); 36 + await Assert.That(hostmask.NickName).IsEqualTo("nick"); 37 + await Assert.That(hostmask.UserName).IsNull(); 38 + await Assert.That(hostmask.HostName).IsNull(); 40 39 } 41 40 42 - [TestMethod] 43 - public void HostmaskFromLine() 41 + [Test] 42 + public async Task HostmaskFromLine() 44 43 { 45 44 var line = new Line(":nick!user@host PRIVMSG #channel hello"); 46 45 var hostmask = new IRCTokens.Hostmask("nick!user@host"); 47 - Assert.AreEqual(hostmask.ToString(), line.Hostmask.ToString()); 48 - Assert.AreEqual("nick", line.Hostmask.NickName); 49 - Assert.AreEqual("user", line.Hostmask.UserName); 50 - Assert.AreEqual("host", line.Hostmask.HostName); 46 + await Assert.That(line.Hostmask.ToString()).IsEqualTo(hostmask.ToString()); 47 + await Assert.That(line.Hostmask.NickName).IsEqualTo("nick"); 48 + await Assert.That(line.Hostmask.UserName).IsEqualTo("user"); 49 + await Assert.That(line.Hostmask.HostName).IsEqualTo("host"); 51 50 } 52 51 53 - [TestMethod] 54 - public void EmptyHostmaskFromLine() 52 + [Test] 53 + public async Task EmptyHostmaskFromLine() 55 54 { 56 55 var line = new Line("PRIVMSG #channel hello"); 57 - Assert.IsNull(line.Hostmask.HostName); 58 - Assert.IsNull(line.Hostmask.UserName); 59 - Assert.IsNull(line.Hostmask.NickName); 56 + await Assert.That(line.Hostmask.HostName).IsNull(); 57 + await Assert.That(line.Hostmask.UserName).IsNull(); 58 + await Assert.That(line.Hostmask.NickName).IsNull(); 60 59 } 61 - } 60 + }
+71 -34
IRCSharp.Tests/Tokenization/Parser.cs
··· 1 - using System.Globalization; 2 - using IRCSharp.Tests.Tokenization.Data; 3 - using YamlDotNet.Serialization; 1 + using YamlDotNet.Serialization; 4 2 using YamlDotNet.Serialization.NamingConventions; 3 + 4 + // ReSharper disable CollectionNeverUpdated.Global 5 + // ReSharper disable ClassNeverInstantiated.Global 5 6 6 7 namespace IRCSharp.Tests.Tokenization; 7 8 8 - [TestClass] 9 9 public class Parser 10 10 { 11 - private static T LoadYaml<T>(string path) 11 + [Test] 12 + [YamlDataGenerator<TestCase>("Tokenization/Data/msg-split.yaml")] 13 + [ArgumentDisplayFormatter<TestCase>] 14 + public async Task Split(TestCase test) 12 15 { 13 - var deserializer = new DeserializerBuilder() 14 - .WithNamingConvention(CamelCaseNamingConvention.Instance) 15 - .Build(); 16 + var atoms = test.Atoms; 17 + var line = new Line(test.Input); 18 + 19 + await Assert.That(line.Command).IsEqualTo(atoms.Verb.ToUpperInvariant()) 20 + .Because($"command failed on: '{test.Input}'"); 21 + 22 + await Assert.That(line.Source).IsEqualTo(atoms.Source) 23 + .Because($"source failed on: '{test.Input}'"); 24 + 25 + await Assert.That(line.Params).IsEquivalentTo(atoms.Params ?? []) 26 + .Because($"params failed on: '{test.Input}'"); 16 27 17 - return deserializer.Deserialize<T>(File.ReadAllText(path)); 28 + if (atoms.Tags is null) 29 + { 30 + await Assert.That(line.Tags).IsNull() 31 + .Because("tags should be null"); 32 + } 33 + else 34 + { 35 + await Assert.That(line.Tags).IsEquivalentTo(atoms.Tags) 36 + .Because($"tags failed on: '{test.Input}'"); 37 + } 18 38 } 19 39 20 - [TestMethod] 21 - public void Split() 40 + [Test] 41 + [YamlDataGenerator<TestCase>("Tokenization/Data/msg-join.yaml")] 42 + [ArgumentDisplayFormatter<TestCase>] 43 + public async Task Join(TestCase test) 22 44 { 23 - foreach (var test in LoadYaml<SplitModel>("Tokenization/Data/msg-split.yaml").Tests) 24 - { 25 - var tokens = new Line(test.Input); 26 - var atoms = test.Atoms; 45 + var atoms = test.Atoms; 46 + var line = new Line { Command = atoms.Verb, Params = atoms.Params, Source = atoms.Source, Tags = atoms.Tags }; 27 47 28 - Assert.AreEqual(atoms.Verb.ToUpper(CultureInfo.InvariantCulture), tokens.Command, 29 - $"command failed on: '{test.Input}'"); 30 - Assert.AreEqual(atoms.Source, tokens.Source, $"source failed on: '{test.Input}'"); 31 - CollectionAssert.AreEqual(atoms.Tags, tokens.Tags, $"tags failed on: '{test.Input}'"); 32 - CollectionAssert.AreEqual(atoms.Params ?? [], tokens.Params, 33 - $"params failed on: '{test.Input}'"); 34 - } 48 + await Assert.That(test.Matches).Contains(line.Format()).Because(test.Description); 35 49 } 50 + } 36 51 37 - [TestMethod] 38 - public void Join() 52 + public class YamlDataGeneratorAttribute<T>(string yamlPath) : AsyncDataSourceGeneratorAttribute<T> 53 + where T : class 54 + { 55 + private readonly IDeserializer _deserializer = new DeserializerBuilder() 56 + .WithNamingConvention(CamelCaseNamingConvention.Instance) 57 + .Build(); 58 + 59 + protected override async IAsyncEnumerable<Func<Task<T>>> GenerateDataSourcesAsync( 60 + DataGeneratorMetadata dataGeneratorMetadata) 39 61 { 40 - foreach (var test in LoadYaml<JoinModel>("Tokenization/Data/msg-join.yaml").Tests) 41 - { 42 - var atoms = test.Atoms; 43 - var line = new Line 44 - { 45 - Command = atoms.Verb, Params = atoms.Params, Source = atoms.Source, Tags = atoms.Tags 46 - }.Format(); 62 + foreach (var item in _deserializer.Deserialize<List<T>>(await File.ReadAllTextAsync(yamlPath))) 63 + yield return () => Task.FromResult(item); 64 + } 65 + } 47 66 48 - Assert.IsTrue(test.Matches.Contains(line), test.Description); 49 - } 67 + public class TestCase : ArgumentDisplayFormatter 68 + { 69 + [YamlMember(Alias = "desc")] 70 + public string Description { get; set; } 71 + public Atoms Atoms { get; set; } 72 + public List<string> Matches { get; set; } 73 + public string Input { get; set; } 74 + public override bool CanHandle(object value) => value is TestCase; 75 + public override string FormatValue(object value) 76 + { 77 + var testCase = (TestCase)value!; 78 + return testCase.Input ?? testCase.Description; 50 79 } 51 - } 80 + } 81 + 82 + public class Atoms 83 + { 84 + public Dictionary<string, string> Tags { get; set; } 85 + public string Source { get; set; } 86 + public string Verb { get; set; } 87 + public List<string> Params { get; set; } 88 + }
+37 -46
IRCSharp.Tests/Tokenization/StatefulDecoder.cs
··· 1 1 namespace IRCSharp.Tests.Tokenization; 2 2 3 - [TestClass] 4 3 public class StatefulDecoder 5 4 { 6 - private IRCTokens.StatefulDecoder _decoder; 5 + private readonly IRCTokens.StatefulDecoder _decoder = new(); 7 6 8 - [TestInitialize] 9 - public void Initialize() 10 - { 11 - _decoder = new(); 12 - } 13 - 14 - [TestMethod] 15 - public void Partial() 7 + [Test] 8 + public async Task Partial() 16 9 { 17 10 var lines = _decoder.Push("PRIVMSG "); 18 - Assert.AreEqual(0, lines.Count); 11 + await Assert.That(lines).IsEmpty(); 19 12 20 13 lines = _decoder.Push("#channel hello\r\n"); 21 - Assert.AreEqual(1, lines.Count); 14 + await Assert.That(lines).HasCount(1); 22 15 23 16 var line = new Line("PRIVMSG #channel hello"); 24 - CollectionAssert.AreEqual(new List<Line> {line}, lines); 17 + await Assert.That(lines).Contains(line); 25 18 } 26 19 27 - [TestMethod] 28 - public void Multiple() 20 + [Test] 21 + public async Task Multiple() 29 22 { 30 23 var lines = _decoder.Push("PRIVMSG #channel1 hello\r\nPRIVMSG #channel2 hello\r\n"); 31 - Assert.AreEqual(2, lines.Count); 24 + await Assert.That(lines).HasCount(2); 32 25 33 - var line1 = new Line("PRIVMSG #channel1 hello"); 34 - var line2 = new Line("PRIVMSG #channel2 hello"); 35 - Assert.AreEqual(line1, lines[0]); 36 - Assert.AreEqual(line2, lines[1]); 26 + await Assert.That(lines[0]).IsEqualTo(new Line("PRIVMSG #channel1 hello")); 27 + await Assert.That(lines[1]).IsEqualTo(new Line("PRIVMSG #channel2 hello")); 37 28 } 38 29 39 - [TestMethod] 40 - public void EncodingIso8859() 30 + [Test] 31 + public async Task EncodingIso8859() 41 32 { 42 - var iso8859 = Encoding.GetEncoding("iso-8859-1"); 43 - _decoder = new() {Encoding = iso8859}; 44 - var bytes = iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"); 45 - var lines = _decoder.Push(bytes, bytes.Length); 46 - var line = new Line("PRIVMSG #channel :hello Ç"); 47 - Assert.IsTrue(line.Equals(lines[0])); 33 + var iso8859 = Encoding.Latin1; 34 + var decoder = new IRCTokens.StatefulDecoder {Encoding = iso8859}; 35 + var bytes = iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"); 36 + var lines = decoder.Push(bytes, bytes.Length); 37 + var line = new Line("PRIVMSG #channel :hello Ç"); 38 + await Assert.That(lines[0]).IsEqualTo(line); 48 39 } 49 40 50 - [TestMethod] 51 - public void EncodingFallback() 41 + [Test] 42 + public async Task EncodingFallback() 52 43 { 53 - var latin1 = Encoding.GetEncoding("iso-8859-1"); 54 - _decoder = new() {Encoding = null, Fallback = latin1}; 55 - var bytes = latin1.GetBytes("PRIVMSG #channel hélló\r\n"); 56 - var lines = _decoder.Push(bytes, bytes.Length); 57 - Assert.AreEqual(1, lines.Count); 58 - Assert.IsTrue(new Line("PRIVMSG #channel hélló").Equals(lines[0])); 44 + var latin1 = Encoding.Latin1; 45 + var decoder = new IRCTokens.StatefulDecoder {Encoding = null, Fallback = latin1}; 46 + var bytes = latin1.GetBytes("PRIVMSG #channel hélló\r\n"); 47 + var lines = decoder.Push(bytes, bytes.Length); 48 + await Assert.That(lines).HasCount(1); 49 + await Assert.That(lines[0]).IsEqualTo(new Line("PRIVMSG #channel hélló")); 59 50 } 60 51 61 - [TestMethod] 62 - public void Empty() 52 + [Test] 53 + public async Task Empty() 63 54 { 64 55 var lines = _decoder.Push(string.Empty); 65 - Assert.AreEqual(0, lines.Count); 56 + await Assert.That(lines).IsEmpty(); 66 57 } 67 58 68 - [TestMethod] 69 - public void BufferUnfinished() 59 + [Test] 60 + public async Task BufferUnfinished() 70 61 { 71 62 _decoder.Push("PRIVMSG #channel hello"); 72 63 var lines = _decoder.Push(string.Empty); 73 - Assert.AreEqual(0, lines.Count); 64 + await Assert.That(lines).IsEmpty(); 74 65 } 75 66 76 - [TestMethod] 77 - public void Clear() 67 + [Test] 68 + public async Task Clear() 78 69 { 79 70 _decoder.Push("PRIVMSG "); 80 71 _decoder.Clear(); 81 - Assert.AreEqual(string.Empty, _decoder.Pending); 72 + await Assert.That(_decoder.Pending).IsEmpty(); 82 73 } 83 - } 74 + }
+29 -36
IRCSharp.Tests/Tokenization/StatefulEncoder.cs
··· 1 1 namespace IRCSharp.Tests.Tokenization; 2 2 3 - [TestClass] 4 3 public class StatefulEncoder 5 4 { 6 - private IRCTokens.StatefulEncoder _encoder; 5 + private readonly IRCTokens.StatefulEncoder _encoder = new(); 7 6 8 - [TestInitialize] 9 - public void Initialize() 10 - { 11 - _encoder = new(); 12 - } 13 - 14 - [TestMethod] 15 - public void Push() 7 + [Test] 8 + public async Task Push() 16 9 { 17 10 var line = new Line("PRIVMSG #channel hello"); 18 11 _encoder.Push(line); 19 - Assert.AreEqual("PRIVMSG #channel hello\r\n", _encoder.Pending()); 12 + await Assert.That(_encoder.Pending()).IsEqualTo("PRIVMSG #channel hello\r\n"); 20 13 } 21 14 22 - [TestMethod] 23 - public void PopPartial() 15 + [Test] 16 + public async Task PopPartial() 24 17 { 25 18 var line = new Line("PRIVMSG #channel hello"); 26 19 _encoder.Push(line); 27 20 _encoder.Pop("PRIVMSG #channel hello".Length); 28 - Assert.AreEqual("\r\n", _encoder.Pending()); 21 + await Assert.That(_encoder.Pending()).IsEqualTo("\r\n"); 29 22 } 30 23 31 - [TestMethod] 32 - public void TestPopReturned() 24 + [Test] 25 + public async Task TestPopReturned() 33 26 { 34 27 var line = new Line("PRIVMSG #channel hello"); 35 28 _encoder.Push(line); 36 29 _encoder.Push(line); 37 30 var lines = _encoder.Pop("PRIVMSG #channel hello\r\n".Length); 38 - Assert.AreEqual(1, lines.Count); 39 - Assert.AreEqual(line, lines[0]); 31 + await Assert.That(lines).HasCount(1); 32 + await Assert.That(lines[0]).IsEqualTo(line); 40 33 } 41 34 42 - [TestMethod] 43 - public void PopNoneReturned() 35 + [Test] 36 + public async Task PopNoneReturned() 44 37 { 45 38 var line = new Line("PRIVMSG #channel hello"); 46 39 _encoder.Push(line); 47 40 var lines = _encoder.Pop(1); 48 - Assert.AreEqual(0, lines.Count); 41 + await Assert.That(lines).IsEmpty(); 49 42 } 50 43 51 - [TestMethod] 52 - public void PopMultipleLines() 44 + [Test] 45 + public async Task PopMultipleLines() 53 46 { 54 47 var line1 = new Line("PRIVMSG #channel1 hello"); 55 48 _encoder.Push(line1); ··· 57 50 _encoder.Push(line2); 58 51 59 52 var lines = _encoder.Pop(_encoder.Pending().Length); 60 - Assert.AreEqual(2, lines.Count); 61 - Assert.AreEqual(string.Empty, _encoder.Pending()); 53 + await Assert.That(lines).HasCount(2); 54 + await Assert.That(_encoder.Pending()).IsEmpty(); 62 55 } 63 56 64 - [TestMethod] 65 - public void Clear() 57 + [Test] 58 + public async Task Clear() 66 59 { 67 - _encoder.Push(new("PRIVMSG #channel hello")); 60 + _encoder.Push(new Line("PRIVMSG #channel hello")); 68 61 _encoder.Clear(); 69 - Assert.AreEqual(string.Empty, _encoder.Pending()); 62 + await Assert.That(_encoder.Pending()).IsEmpty(); 70 63 } 71 64 72 - [TestMethod] 73 - public void EncodingIso8859() 65 + [Test] 66 + public async Task EncodingIso8859() 74 67 { 75 - var iso8859 = Encoding.GetEncoding("iso-8859-1"); 76 - _encoder = new() {Encoding = iso8859}; 77 - _encoder.Push(new("PRIVMSG #channel :hello Ç")); 78 - CollectionAssert.AreEqual(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"), _encoder.PendingBytes); 68 + var encoder = new IRCTokens.StatefulEncoder { Encoding = Encoding.Latin1 }; 69 + encoder.Push(new Line("PRIVMSG #channel :hello Ç")); 70 + await Assert.That(encoder.PendingBytes) 71 + .IsEquivalentTo(Encoding.Latin1.GetBytes("PRIVMSG #channel :hello Ç\r\n")); 79 72 } 80 - } 73 + }
+56 -60
IRCSharp.Tests/Tokenization/Tokenization.cs
··· 1 1 namespace IRCSharp.Tests.Tokenization; 2 2 3 - [TestClass] 4 3 public class Tokenization 5 4 { 6 - [TestMethod] 7 - public void TagsMissing() 5 + [Test] 6 + public async Task TagsMissing() 8 7 { 9 8 var line = new Line("PRIVMSG #channel"); 10 - Assert.IsNull(line.Tags); 9 + await Assert.That(line.Tags).IsNull(); 11 10 } 12 11 13 - [TestMethod] 14 - public void TagsMissingValue() 12 + [Test] 13 + public async Task TagsMissingValue() 15 14 { 16 15 var line = new Line("@id= PRIVMSG #channel"); 17 - Assert.AreEqual(string.Empty, line.Tags["id"]); 16 + await Assert.That(line.Tags["id"]).IsEmpty(); 18 17 } 19 18 20 - [TestMethod] 21 - public void TagsMissingEqual() 19 + [Test] 20 + public async Task TagsMissingEqual() 22 21 { 23 22 var line = new Line("@id PRIVMSG #channel"); 24 - Assert.IsNull(line.Tags["id"]); 23 + await Assert.That(line.Tags["id"]).IsNull(); 25 24 } 26 25 27 - [TestMethod] 28 - public void TagsUnescape() 26 + [Test] 27 + public async Task TagsUnescape() 29 28 { 30 29 var line = new Line(@"@id=1\\\:\r\n\s2 PRIVMSG #channel"); 31 - Assert.AreEqual("1\\;\r\n 2", line.Tags["id"]); 30 + await Assert.That(line.Tags["id"]).IsEqualTo("1\\;\r\n 2"); 32 31 } 33 32 34 - [TestMethod] 35 - public void TagsOverlap() 33 + [Test] 34 + public async Task TagsOverlap() 36 35 { 37 36 var line = new Line(@"@id=1\\\s\\s PRIVMSG #channel"); 38 - Assert.AreEqual(@"1\ \s", line.Tags["id"]); 37 + await Assert.That(line.Tags["id"]).IsEqualTo(@"1\ \s"); 39 38 } 40 39 41 - [TestMethod] 42 - public void TagsLoneEndSlash() 40 + [Test] 41 + public async Task TagsLoneEndSlash() 43 42 { 44 43 var line = new Line("@id=1\\ PRIVMSG #channel"); 45 - Assert.AreEqual("1", line.Tags["id"]); 44 + await Assert.That(line.Tags["id"]).IsEqualTo("1"); 46 45 } 47 46 48 - [TestMethod] 49 - public void SourceWithoutTags() 47 + [Test] 48 + public async Task SourceWithoutTags() 50 49 { 51 50 var line = new Line(":nick!user@host PRIVMSG #channel"); 52 - Assert.AreEqual("nick!user@host", line.Source); 51 + await Assert.That(line.Source).IsEqualTo("nick!user@host"); 53 52 } 54 53 55 - [TestMethod] 56 - public void SourceWithTags() 54 + [Test] 55 + public async Task SourceWithTags() 57 56 { 58 57 var line = new Line("@id=123 :nick!user@host PRIVMSG #channel"); 59 - Assert.AreEqual("nick!user@host", line.Source); 58 + await Assert.That(line.Source).IsEqualTo("nick!user@host"); 60 59 } 61 60 62 - [TestMethod] 63 - public void SourceMissingWithoutTags() 61 + [Test] 62 + public async Task SourceMissingWithoutTags() 64 63 { 65 64 var line = new Line("PRIVMSG #channel"); 66 - Assert.IsNull(line.Source); 65 + await Assert.That(line.Source).IsNull(); 67 66 } 68 67 69 - [TestMethod] 70 - public void SourceMissingWithTags() 68 + [Test] 69 + public async Task SourceMissingWithTags() 71 70 { 72 71 var line = new Line("@id=123 PRIVMSG #channel"); 73 - Assert.IsNull(line.Source); 72 + await Assert.That(line.Source).IsNull(); 74 73 } 75 74 76 - [TestMethod] 77 - public void Command() 75 + [Test] 76 + public async Task Command() 78 77 { 79 78 var line = new Line("privmsg #channel"); 80 - Assert.AreEqual("PRIVMSG", line.Command); 79 + await Assert.That(line.Command).IsEqualTo("PRIVMSG"); 81 80 } 82 81 83 - [TestMethod] 84 - public void ParamsTrailing() 82 + [Test] 83 + public async Task ParamsTrailing() 85 84 { 86 85 var line = new Line("PRIVMSG #channel :hello world"); 87 - CollectionAssert.AreEqual(new List<string> {"#channel", "hello world"}, line.Params); 86 + await Assert.That(line.Params).IsEquivalentTo(["#channel", "hello world"]); 88 87 } 89 88 90 - [TestMethod] 91 - public void ParamsOnlyTrailing() 89 + [Test] 90 + public async Task ParamsOnlyTrailing() 92 91 { 93 92 var line = new Line("PRIVMSG :hello world"); 94 - CollectionAssert.AreEqual(new List<string> {"hello world"}, line.Params); 93 + await Assert.That(line.Params).IsEquivalentTo(["hello world"]); 95 94 } 96 95 97 - [TestMethod] 98 - public void ParamsMissing() 96 + [Test] 97 + public async Task ParamsMissing() 99 98 { 100 99 var line = new Line("PRIVMSG"); 101 - Assert.AreEqual("PRIVMSG", line.Command); 102 - CollectionAssert.AreEqual(new List<string>(), line.Params); 100 + await Assert.That(line.Command).IsEqualTo("PRIVMSG"); 101 + await Assert.That(line.Params).IsEmpty(); 103 102 } 104 103 105 - [TestMethod] 106 - public void AllTokens() 104 + [Test] 105 + public async Task AllTokens() 107 106 { 108 107 var line = new Line("@id=123 :nick!user@host PRIVMSG #channel :hello world"); 109 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"id", "123"}}, line.Tags); 110 - Assert.AreEqual("nick!user@host", line.Source); 111 - Assert.AreEqual("PRIVMSG", line.Command); 112 - CollectionAssert.AreEqual(new List<string> {"#channel", "hello world"}, line.Params); 108 + await Assert.That(line.Tags).IsEquivalentTo(new Dictionary<string, string> { { "id", "123" } }); 109 + await Assert.That(line.Source).IsEqualTo("nick!user@host"); 110 + await Assert.That(line.Command).IsEqualTo("PRIVMSG"); 111 + await Assert.That(line.Params).IsEquivalentTo(["#channel", "hello world"]); 113 112 } 114 113 115 - [TestMethod] 116 - public void NulByte() 114 + [Test] 115 + public async Task NulByte() 117 116 { 118 117 var decoder = new IRCTokens.StatefulDecoder(); 119 - var bytes = ":nick!user@host PRIVMSG #channel :hello"u8.ToArray() 120 - .Concat("\0"u8.ToArray()) 121 - .Concat("world"u8.ToArray()) 122 - .ToArray(); 123 - var line = decoder.Push(bytes, bytes.Length).First(); 124 - 125 - CollectionAssert.AreEqual(new List<string> {"#channel", "hello"}, line.Params); 118 + var bytes = ":nick!user@host PRIVMSG #channel :hello\0world"u8; 119 + var line = decoder.Push(bytes.ToArray(), bytes.Length).First(); 120 + 121 + await Assert.That(line.Params).IsEquivalentTo(["#channel", "hello"]); 126 122 } 127 - } 123 + }
-60
IRCSharp.sln
··· 1 -  2 - Microsoft Visual Studio Solution File, Format Version 12.00 3 - # Visual Studio Version 16 4 - VisualStudioVersion = 16.0.30011.22 5 - MinimumVisualStudioVersion = 10.0.40219.1 6 - Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRCTokens", "IRCTokens\IRCTokens.csproj", "{9E812F45-B2CD-42D2-8378-EBEBF8697905}" 7 - EndProject 8 - Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tokens", "Examples\Tokens\Tokens.csproj", "{A45DA39B-6B47-4713-8049-3B36E0235B67}" 9 - EndProject 10 - Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IRCStates", "IRCStates\IRCStates.csproj", "{233E3CB4-61F1-4368-9139-7E9F4A58ED2D}" 11 - EndProject 12 - Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "States", "Examples\States\States.csproj", "{BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}" 13 - EndProject 14 - Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A85EB22-D7B4-417F-AC3B-DAFD97DDEA08}" 15 - ProjectSection(SolutionItems) = preProject 16 - .editorconfig = .editorconfig 17 - EndProjectSection 18 - EndProject 19 - Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IRCSharp.Tests", "IRCSharp.Tests\IRCSharp.Tests.csproj", "{B420F0F3-1ED0-4FD3-9E91-2E7F96F9FF7F}" 20 - EndProject 21 - Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{4260E03C-6E28-4519-8943-5B477841A75A}" 22 - EndProject 23 - Global 24 - GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 - Debug|Any CPU = Debug|Any CPU 26 - Release|Any CPU = Release|Any CPU 27 - EndGlobalSection 28 - GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 - {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 - {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 - {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 - {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Release|Any CPU.Build.0 = Release|Any CPU 33 - {A45DA39B-6B47-4713-8049-3B36E0235B67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 - {A45DA39B-6B47-4713-8049-3B36E0235B67}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 - {A45DA39B-6B47-4713-8049-3B36E0235B67}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 - {A45DA39B-6B47-4713-8049-3B36E0235B67}.Release|Any CPU.Build.0 = Release|Any CPU 37 - {233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 - {233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 - {233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 - {233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Release|Any CPU.Build.0 = Release|Any CPU 41 - {BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 - {BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 - {BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 - {BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Release|Any CPU.Build.0 = Release|Any CPU 45 - {B420F0F3-1ED0-4FD3-9E91-2E7F96F9FF7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 - {B420F0F3-1ED0-4FD3-9E91-2E7F96F9FF7F}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 - {B420F0F3-1ED0-4FD3-9E91-2E7F96F9FF7F}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 - {B420F0F3-1ED0-4FD3-9E91-2E7F96F9FF7F}.Release|Any CPU.Build.0 = Release|Any CPU 49 - EndGlobalSection 50 - GlobalSection(SolutionProperties) = preSolution 51 - HideSolutionNode = FALSE 52 - EndGlobalSection 53 - GlobalSection(NestedProjects) = preSolution 54 - {A45DA39B-6B47-4713-8049-3B36E0235B67} = {4260E03C-6E28-4519-8943-5B477841A75A} 55 - {BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF} = {4260E03C-6E28-4519-8943-5B477841A75A} 56 - EndGlobalSection 57 - GlobalSection(ExtensibilityGlobals) = postSolution 58 - SolutionGuid = {0B91F0EA-8564-4318-8EEC-ED0640475141} 59 - EndGlobalSection 60 - EndGlobal
+2 -1
IRCSharp.sln.DotSettings
··· 1 - <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:schemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> 1 + <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:schemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> 2 2 <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ACK/@EntryIndexedValue">ACK</s:String> 3 3 <s:Boolean x:Key="/Default/UserDictionary/Words/=CHGHOST/@EntryIndexedValue">True</s:Boolean> 4 4 <s:Boolean x:Key="/Default/UserDictionary/Words/=Invex/@EntryIndexedValue">True</s:Boolean> 5 + <s:Boolean x:Key="/Default/UserDictionary/Words/=privmsg/@EntryIndexedValue">True</s:Boolean> 5 6 <s:Boolean x:Key="/Default/UserDictionary/Words/=realname/@EntryIndexedValue">True</s:Boolean> 6 7 <s:Boolean x:Key="/Default/UserDictionary/Words/=SETNAME/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
+9
IRCSharp.slnx
··· 1 + <Solution> 2 + <Folder Name="/Examples/"> 3 + <Project Path="Examples/States/States.csproj" /> 4 + <Project Path="Examples/Tokens/Tokens.csproj" /> 5 + </Folder> 6 + <Project Path="IRCSharp.Tests/IRCSharp.Tests.csproj" /> 7 + <Project Path="IRCStates/IRCStates.csproj" /> 8 + <Project Path="IRCTokens/IRCTokens.csproj" /> 9 + </Solution>
+30 -27
IRCStates/Casemap.cs
··· 1 - using System; 2 1 // ReSharper disable IdentifierTypo 3 2 4 - namespace IRCStates 3 + using System.Text; 4 + 5 + namespace IRCStates; 6 + 7 + public static class Casemap 5 8 { 6 - public static class Casemap 9 + public enum CaseMapping 7 10 { 8 - public enum CaseMapping 9 - { 10 - Rfc1459, 11 - Ascii 12 - } 11 + Rfc1459, 12 + Ascii 13 + } 13 14 14 - private const string AsciiUpperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 15 - private const string AsciiLowerChars = "abcdefghijklmnopqrstuvwxyz"; 16 - private const string Rfc1459UpperChars = AsciiUpperChars + @"[]~\"; 17 - private const string Rfc1459LowerChars = AsciiLowerChars + "{}^|"; 15 + private const string AsciiUpperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 16 + private const string AsciiLowerChars = "abcdefghijklmnopqrstuvwxyz"; 17 + private const string Rfc1459UpperChars = AsciiUpperChars + @"[]~\"; 18 + private const string Rfc1459LowerChars = AsciiLowerChars + "{}^|"; 18 19 19 - private static string Replace(string s, string upper, string lower) 20 + private static string Replace(string s, string upperSet, string lowerSet) 21 + { 22 + StringBuilder sb = new(s); 23 + for (var i = 0; i < upperSet.Length; i++) 20 24 { 21 - for (var i = 0; i < upper.Length; ++i) s = s.Replace(upper[i], lower[i]); 22 - 23 - return s; 25 + sb.Replace(upperSet[i], lowerSet[i]); 24 26 } 25 27 26 - public static string CaseFold(CaseMapping mapping, string s) 27 - { 28 - if (s != null) 29 - return mapping switch 30 - { 31 - CaseMapping.Rfc1459 => Replace(s, Rfc1459UpperChars, Rfc1459LowerChars), 32 - CaseMapping.Ascii => Replace(s, AsciiUpperChars, AsciiLowerChars), 33 - _ => throw new ArgumentOutOfRangeException(nameof(mapping), mapping, null) 34 - }; 28 + return sb.ToString(); 29 + } 35 30 36 - return string.Empty; 37 - } 31 + public static string CaseFold(CaseMapping mapping, string s) 32 + { 33 + return s == null 34 + ? string.Empty 35 + : mapping switch 36 + { 37 + CaseMapping.Rfc1459 => Replace(s, Rfc1459UpperChars, Rfc1459LowerChars), 38 + CaseMapping.Ascii => Replace(s, AsciiUpperChars, AsciiLowerChars), 39 + _ => throw new ArgumentOutOfRangeException(nameof(mapping), mapping, null) 40 + }; 38 41 } 39 42 }
+45 -40
IRCStates/Channel.cs
··· 1 - using System; 2 - using System.Collections.Generic; 3 - using System.Linq; 4 1 // ReSharper disable AutoPropertyCanBeMadeGetOnly.Local 5 2 6 - namespace IRCStates 3 + namespace IRCStates; 4 + 5 + public class Channel 7 6 { 8 - public class Channel 9 - { 10 - public string Name { get; private set; } 11 - public string NameLower { get; private set; } 12 - public Dictionary<string, ChannelUser> Users { get; private set; } = new Dictionary<string, ChannelUser>(); 13 - public string Topic { get; set; } 14 - public string TopicSetter { get; set; } 15 - public DateTime TopicTime { get; set; } 16 - public DateTime Created { get; set; } 17 - public Dictionary<string, List<string>> ListModes { get; private set; } = new Dictionary<string, List<string>>(); 18 - public Dictionary<string, string> Modes { get; private set; } = new Dictionary<string, string>(); 7 + public string Name { get; private set; } 8 + public string NameLower { get; private set; } 9 + public Dictionary<string, ChannelUser> Users { get; private set; } = new(); 10 + public string Topic { get; set; } 11 + public string TopicSetter { get; set; } 12 + public DateTime TopicTime { get; set; } 13 + public DateTime Created { get; set; } 14 + public Dictionary<string, List<string>> ListModes { get; private set; } = []; 15 + public Dictionary<string, string> Modes { get; private set; } = []; 19 16 20 - public override string ToString() 21 - { 22 - return $"Channel(name={Name})"; 23 - } 17 + public override string ToString() => $"Channel(name={Name})"; 24 18 25 - public void SetName(string name, string nameLower) 26 - { 27 - Name = name; 28 - NameLower = nameLower; 29 - } 19 + public void SetName(string name, string nameLower) 20 + { 21 + Name = name; 22 + NameLower = nameLower; 23 + } 30 24 31 - public void AddMode(string ch, string param, bool listMode) 25 + public void AddMode(string ch, string param, bool listMode) 26 + { 27 + if (listMode) 32 28 { 33 - if (listMode) 29 + if (!ListModes.ContainsKey(ch)) 34 30 { 35 - if (!ListModes.ContainsKey(ch)) ListModes[ch] = new List<string>(); 31 + ListModes[ch] = []; 32 + } 36 33 37 - if (!ListModes[ch].Contains(param)) ListModes[ch].Add(param ?? string.Empty); 38 - } 39 - else 34 + if (!ListModes[ch].Contains(param)) 40 35 { 41 - Modes[ch] = param; 36 + ListModes[ch].Add(param ?? string.Empty); 42 37 } 43 38 } 39 + else 40 + { 41 + Modes[ch] = param; 42 + } 43 + } 44 44 45 - public void RemoveMode(string ch, string param) 45 + public void RemoveMode(string ch, string param) 46 + { 47 + if (ListModes.ContainsKey(ch)) 46 48 { 47 - if (ListModes.ContainsKey(ch)) 49 + if (!ListModes[ch].Contains(param)) 48 50 { 49 - if (ListModes[ch].Contains(param)) 50 - { 51 - ListModes[ch].Remove(param); 52 - if (!ListModes[ch].Any()) ListModes.Remove(ch); 53 - } 51 + return; 54 52 } 55 - else if (Modes.ContainsKey(ch)) 53 + 54 + ListModes[ch].Remove(param); 55 + 56 + if (!ListModes[ch].Any()) 56 57 { 57 - Modes.Remove(ch); 58 + ListModes.Remove(ch); 58 59 } 60 + } 61 + else 62 + { 63 + Modes.Remove(ch); 59 64 } 60 65 } 61 66 }
+14 -15
IRCStates/ChannelUser.cs
··· 1 - using System.Collections.Generic; 1 + namespace IRCStates; 2 2 3 - namespace IRCStates 3 + public class ChannelUser 4 4 { 5 - public class ChannelUser 5 + public List<string> Modes { get; } = []; 6 + 7 + private bool Equals(ChannelUser other) => other != null && Equals(Modes, other.Modes); 8 + 9 + public override bool Equals(object obj) 6 10 { 7 - public List<string> Modes { get; } = new List<string>(); 8 - 9 - private bool Equals(ChannelUser other) 11 + if (ReferenceEquals(null, obj)) 10 12 { 11 - return other != null && Equals(Modes, other.Modes); 13 + return false; 12 14 } 13 15 14 - public override bool Equals(object obj) 16 + if (ReferenceEquals(this, obj)) 15 17 { 16 - if (ReferenceEquals(null, obj)) return false; 17 - if (ReferenceEquals(this, obj)) return true; 18 - return obj.GetType() == GetType() && Equals((ChannelUser) obj); 18 + return true; 19 19 } 20 20 21 - public override int GetHashCode() 22 - { 23 - return Modes != null ? Modes.GetHashCode() : 0; 24 - } 21 + return obj.GetType() == GetType() && Equals((ChannelUser) obj); 25 22 } 23 + 24 + public override int GetHashCode() => Modes != null ? Modes.GetHashCode() : 0; 26 25 }
+20 -21
IRCStates/Commands.cs
··· 1 - // ReSharper disable IdentifierTypo 1 + // ReSharper disable IdentifierTypo 2 2 3 - namespace IRCStates 3 + namespace IRCStates; 4 + 5 + public static class Commands 4 6 { 5 - public static class Commands 6 - { 7 - public const string Nick = "NICK"; 8 - public const string Join = "JOIN"; 9 - public const string Mode = "MODE"; 10 - public const string Part = "PART"; 11 - public const string Kick = "KICK"; 12 - public const string Quit = "QUIT"; 13 - public const string Error = "ERROR"; 14 - public const string Topic = "TOPIC"; 15 - public const string Privmsg = "PRIVMSG"; 16 - public const string Notice = "NOTICE"; 17 - public const string Tagmsg = "TAGMSG"; 18 - public const string Chghost = "CHGHOST"; 19 - public const string Setname = "SETNAME"; 20 - public const string Away = "AWAY"; 21 - public const string Account = "ACCOUNT"; 22 - public const string Cap = "CAP"; 23 - } 7 + public const string Nick = "NICK"; 8 + public const string Join = "JOIN"; 9 + public const string Mode = "MODE"; 10 + public const string Part = "PART"; 11 + public const string Kick = "KICK"; 12 + public const string Quit = "QUIT"; 13 + public const string Error = "ERROR"; 14 + public const string Topic = "TOPIC"; 15 + public const string Privmsg = "PRIVMSG"; 16 + public const string Notice = "NOTICE"; 17 + public const string Tagmsg = "TAGMSG"; 18 + public const string Chghost = "CHGHOST"; 19 + public const string Setname = "SETNAME"; 20 + public const string Away = "AWAY"; 21 + public const string Account = "ACCOUNT"; 22 + public const string Cap = "CAP"; 24 23 }
+19 -21
IRCStates/Emit.cs
··· 1 - using System.Collections.Generic; 2 1 // ReSharper disable UnusedMember.Global 3 2 4 - namespace IRCStates 3 + namespace IRCStates; 4 + 5 + public class Emit 5 6 { 6 - public class Emit 7 - { 8 - public string Command { get; set; } 9 - public string Subcommand { get; set; } 10 - public string Text { get; set; } 11 - public List<string> Tokens { get; set; } 12 - public bool Finished { get; set; } 13 - public bool Self { get; set; } 14 - public bool SelfSource { get; set; } 15 - public bool SelfTarget { get; set; } 16 - public User User { get; set; } 17 - public User UserSource { get; set; } 18 - public User UserTarget { get; set; } 19 - public List<User> Users { get; set; } 20 - public Channel Channel { get; set; } 21 - public Channel ChannelSource { get; set; } 22 - public Channel ChannelTarget { get; set; } 23 - public string Target { get; set; } 24 - } 7 + public string Command { get; set; } 8 + public string Subcommand { get; set; } 9 + public string Text { get; set; } 10 + public List<string> Tokens { get; set; } 11 + public bool Finished { get; set; } 12 + public bool Self { get; set; } 13 + public bool SelfSource { get; set; } 14 + public bool SelfTarget { get; set; } 15 + public User User { get; set; } 16 + public User UserSource { get; set; } 17 + public User UserTarget { get; set; } 18 + public List<User> Users { get; set; } 19 + public Channel Channel { get; set; } 20 + public Channel ChannelSource { get; set; } 21 + public Channel ChannelTarget { get; set; } 22 + public string Target { get; set; } 25 23 }
+16 -14
IRCStates/Extensions.cs
··· 1 - using System.Collections.Generic; 2 - using System.Linq; 1 + namespace IRCStates; 3 2 4 - namespace IRCStates 3 + public static class Extensions 5 4 { 6 - public static class Extensions 5 + /// <summary> 6 + /// Update the dictionary with <see cref="other"/>'s keys and values 7 + /// </summary> 8 + /// <param name="dict"></param> 9 + /// <param name="other"></param> 10 + /// <typeparam name="TKey"></typeparam> 11 + /// <typeparam name="TValue"></typeparam> 12 + public static void UpdateWith<TKey, TValue>(this Dictionary<TKey, TValue> dict, Dictionary<TKey, TValue> other) 7 13 { 8 - /// <summary> 9 - /// Update the dictionary with <see cref="other"/>'s keys and values 10 - /// </summary> 11 - /// <param name="dict"></param> 12 - /// <param name="other"></param> 13 - /// <typeparam name="TKey"></typeparam> 14 - /// <typeparam name="TValue"></typeparam> 15 - public static void UpdateWith<TKey, TValue>(this Dictionary<TKey, TValue> dict, Dictionary<TKey, TValue> other) 14 + if (dict == null || other == null || !other.Any()) 16 15 { 17 - if (dict == null || other == null || !other.Any()) return; 16 + return; 17 + } 18 18 19 - foreach (var (key, value) in other) dict[key] = value; 19 + foreach (var v in other) 20 + { 21 + dict[v.Key] = v.Value; 20 22 } 21 23 } 22 24 }
+2 -19
IRCStates/IRCStates.csproj
··· 1 1 <Project Sdk="Microsoft.NET.Sdk"> 2 - 3 2 <PropertyGroup> 4 - <TargetFramework>netstandard2.1</TargetFramework> 3 + <TargetFramework>netstandard2.0</TargetFramework> 5 4 <PackageId>IRCStates</PackageId> 6 - <Version>1.4.0</Version> 7 - <Authors>Ben Harris</Authors> 8 - <Company>tildeverse.org</Company> 9 5 <IsPackable>true</IsPackable> 10 6 <GeneratePackageOnBuild>true</GeneratePackageOnBuild> 11 - <PackageProjectUrl>https://tildegit.org/irctokens/ircsharp</PackageProjectUrl> 12 - <PackageLicenseExpression>MIT</PackageLicenseExpression> 13 - <RepositoryUrl>https://tildegit.org/irctokens/ircsharp/src/branch/main/IRCStates</RepositoryUrl> 14 - <PublishRepositoryUrl>true</PublishRepositoryUrl> 15 - <EmbedUntrackedSources>true</EmbedUntrackedSources> 16 - <RepositoryType>git</RepositoryType> 17 - <PackageTags>irc</PackageTags> 18 - <PackageVersion>1.4.0</PackageVersion> 19 - <PackageReadmeFile>README.md</PackageReadmeFile> 20 7 <DebugType>embedded</DebugType> 21 8 </PropertyGroup> 22 - 23 9 <ItemGroup> 24 10 <ProjectReference Include="..\IRCTokens\IRCTokens.csproj" /> 25 11 </ItemGroup> 26 - 27 12 <ItemGroup> 28 13 <None Include="README.md" Pack="true" PackagePath="\" /> 29 14 </ItemGroup> 30 - 31 15 <ItemGroup> 32 - <PackageReference Include="Microsoft.SourceLink.Gitea" Version="8.0.0" PrivateAssets="All"/> 16 + <PackageReference Include="Microsoft.SourceLink.Gitea" PrivateAssets="All" /> 33 17 </ItemGroup> 34 - 35 18 </Project>
+94 -89
IRCStates/ISupport.cs
··· 1 - using System; 2 - using System.Collections.Generic; 1 + using IRCTokens; 3 2 using System.Globalization; 4 - using System.Linq; 5 3 6 4 // ReSharper disable InconsistentNaming 7 5 // ReSharper disable StringLiteralTypo 8 6 9 - namespace IRCStates 7 + namespace IRCStates; 8 + 9 + public class ISupport 10 10 { 11 - public class ISupport 11 + public Dictionary<string, string> Raw { get; } = []; 12 + public string Network { get; private set; } 13 + public ISupportChanModes ChanModes { get; private set; } = new("b,k,l,imnpst"); 14 + public ISupportPrefix Prefix { get; private set; } = new("(ov)@+"); 15 + public int? Modes { get; private set; } = 3; 16 + public Casemap.CaseMapping CaseMapping { get; private set; } = Casemap.CaseMapping.Rfc1459; 17 + public List<string> ChanTypes { get; private set; } = ["#"]; 18 + public List<string> StatusMsg { get; private set; } = []; 19 + public string CallerId { get; private set; } 20 + public string Excepts { get; private set; } 21 + public string Invex { get; private set; } 22 + public int? Monitor { get; private set; } 23 + public int? Watch { get; private set; } 24 + public bool Whox { get; private set; } 25 + 26 + /// <summary> 27 + /// Parse the ISupport values from the line's parameters 28 + /// </summary> 29 + /// <param name="tokens"></param> 30 + public void Parse(IEnumerable<string> tokens) 12 31 { 13 - public Dictionary<string, string> Raw { get; } = new Dictionary<string, string>(); 14 - public string Network { get; private set; } 15 - public ISupportChanModes ChanModes { get; private set; } = new ISupportChanModes("b,k,l,imnpst"); 16 - public ISupportPrefix Prefix { get; private set; } = new ISupportPrefix("(ov)@+"); 17 - public int? Modes { get; private set; } = 3; 18 - public Casemap.CaseMapping CaseMapping { get; private set; } = Casemap.CaseMapping.Rfc1459; 19 - public List<string> ChanTypes { get; private set; } = new List<string> { "#" }; 20 - public List<string> StatusMsg { get; private set; } = new List<string>(); 21 - public string CallerId { get; private set; } 22 - public string Excepts { get; private set; } 23 - public string Invex { get; private set; } 24 - public int? Monitor { get; private set; } 25 - public int? Watch { get; private set; } 26 - public bool Whox { get; private set; } 32 + if (tokens == null) 33 + { 34 + return; 35 + } 27 36 28 - /// <summary> 29 - /// Parse the ISupport values from the line's parameters 30 - /// </summary> 31 - /// <param name="tokens"></param> 32 - public void Parse(IEnumerable<string> tokens) 37 + // remove first and last 38 + tokens = tokens.Skip(1).SkipLast(1); 39 + 40 + foreach (var token in tokens) 33 41 { 34 - if (tokens == null) return; 42 + var split = token.Split(['='], 2); 43 + var key = split[0]; 35 44 36 - // remove first and last 37 - tokens = tokens.Skip(1).SkipLast(1); 45 + var value = string.Empty; 46 + if (split.Length > 1) 47 + { 48 + value = split[1]; 49 + Raw[key] = value; 50 + } 38 51 39 - foreach (var token in tokens) 52 + switch (split[0]) 40 53 { 41 - var split = token.Split('=', 2); 42 - var key = split[0]; 54 + case "NETWORK": Network = value; break; 55 + case "CHANMODES": ChanModes = new ISupportChanModes(value); break; 56 + case "PREFIX": Prefix = new ISupportPrefix(value); break; 57 + case "STATUSMSG": 58 + StatusMsg = []; 59 + StatusMsg.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture))); 60 + break; 61 + case "MODES": 62 + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var modes)) 63 + { 64 + Modes = modes; 65 + } 66 + else 67 + { 68 + Modes = -1; 69 + } 70 + 71 + break; 72 + case "MONITOR": 73 + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var monitor)) 74 + { 75 + Monitor = monitor; 76 + } 77 + else 78 + { 79 + Monitor = -1; 80 + } 43 81 44 - var value = string.Empty; 45 - if (split.Length > 1) 46 - { 47 - value = split[1]; 48 - Raw[key] = value; 49 - } 82 + break; 83 + case "WATCH": 84 + if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var watch)) 85 + { 86 + Watch = watch; 87 + } 88 + else 89 + { 90 + Watch = -1; 91 + } 50 92 51 - switch (split[0]) 52 - { 53 - case "NETWORK": 54 - Network = value; 55 - break; 56 - case "CHANMODES": 57 - ChanModes = new ISupportChanModes(value); 58 - break; 59 - case "PREFIX": 60 - Prefix = new ISupportPrefix(value); 61 - break; 62 - case "STATUSMSG": 63 - StatusMsg = new List<string>(); 64 - StatusMsg.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture))); 65 - break; 66 - case "MODES": 67 - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var modes)) 68 - Modes = modes; 69 - else 70 - Modes = -1; 71 - break; 72 - case "MONITOR": 73 - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var monitor)) 74 - Monitor = monitor; 75 - else 76 - Monitor = -1; 77 - break; 78 - case "WATCH": 79 - if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var watch)) 80 - Watch = watch; 81 - else 82 - Watch = -1; 83 - break; 84 - case "CASEMAPPING": 85 - if (Enum.TryParse(value, true, out Casemap.CaseMapping caseMapping)) CaseMapping = caseMapping; 86 - break; 87 - case "CHANTYPES": 88 - ChanTypes = new List<string>(); 89 - ChanTypes.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture))); 90 - break; 91 - case "CALLERID": 92 - CallerId = string.IsNullOrEmpty(value) ? "g" : value; 93 - break; 94 - case "EXCEPTS": 95 - Excepts = string.IsNullOrEmpty(value) ? "e" : value; 96 - break; 97 - case "INVEX": 98 - Invex = string.IsNullOrEmpty(value) ? "I" : value; 99 - break; 100 - case "WHOX": 101 - Whox = true; 102 - break; 103 - } 93 + break; 94 + case "CASEMAPPING": 95 + if (Enum.TryParse(value, true, out Casemap.CaseMapping caseMapping)) 96 + { 97 + CaseMapping = caseMapping; 98 + } 99 + 100 + break; 101 + case "CHANTYPES": 102 + ChanTypes = []; 103 + ChanTypes.AddRange(value.Select(c => c.ToString(CultureInfo.InvariantCulture))); 104 + break; 105 + case "CALLERID": CallerId = string.IsNullOrEmpty(value) ? "g" : value; break; 106 + case "EXCEPTS": Excepts = string.IsNullOrEmpty(value) ? "e" : value; break; 107 + case "INVEX": Invex = string.IsNullOrEmpty(value) ? "I" : value; break; 108 + case "WHOX": Whox = true; break; 104 109 } 105 110 } 106 111 }
+22 -29
IRCStates/ISupportChanModes.cs
··· 1 - using System.Collections.Generic; 2 1 using System.Globalization; 3 - using System.Linq; 2 + 4 3 // ReSharper disable CommentTypo 5 4 6 - namespace IRCStates 5 + namespace IRCStates; 6 + 7 + // ReSharper disable once InconsistentNaming 8 + public class ISupportChanModes 7 9 { 8 - // ReSharper disable once InconsistentNaming 9 - public class ISupportChanModes 10 + /// <summary> 11 + /// Split the chanmodes and add to our known <see cref="ListModes"/> 12 + /// </summary> 13 + /// <param name="splitVal"></param> 14 + public ISupportChanModes(string splitVal) 10 15 { 11 - /// <summary> 12 - /// Split the chanmodes and add to our known <see cref="ListModes"/> 13 - /// </summary> 14 - /// <param name="splitVal"></param> 15 - public ISupportChanModes(string splitVal) 16 + if (splitVal == null) 16 17 { 17 - if (splitVal == null) return; 18 - 19 - var split = splitVal.Split(',', 4); 20 - 21 - ListModes = new List<string>(); 22 - ListModes.AddRange(split[0].Select(c => c.ToString(CultureInfo.InvariantCulture))); 23 - 24 - SettingBModes = new List<string>(); 25 - SettingBModes.AddRange(split[1].Select(c => c.ToString(CultureInfo.InvariantCulture))); 26 - 27 - SettingCModes = new List<string>(); 28 - SettingCModes.AddRange(split[2].Select(c => c.ToString(CultureInfo.InvariantCulture))); 29 - 30 - SettingDModes = new List<string>(); 31 - SettingDModes.AddRange(split[3].Select(c => c.ToString(CultureInfo.InvariantCulture))); 18 + return; 32 19 } 33 20 34 - public List<string> ListModes { get; } 35 - public List<string> SettingBModes { get; } 36 - public List<string> SettingCModes { get; } 37 - public List<string> SettingDModes { get; } 21 + var split = splitVal.Split([','], 4); 22 + ListModes = split[0].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList(); 23 + SettingBModes = split[1].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList(); 24 + SettingCModes = split[2].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList(); 25 + SettingDModes = split[3].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList(); 38 26 } 27 + 28 + public List<string> ListModes { get; } 29 + public List<string> SettingBModes { get; } 30 + public List<string> SettingCModes { get; } 31 + public List<string> SettingDModes { get; } 39 32 }
+28 -39
IRCStates/ISupportPrefix.cs
··· 1 - using System; 2 - using System.Collections.Generic; 3 1 using System.Globalization; 4 - using System.Linq; 2 + 3 + namespace IRCStates; 5 4 6 - namespace IRCStates 5 + // ReSharper disable once InconsistentNaming 6 + public class ISupportPrefix 7 7 { 8 - // ReSharper disable once InconsistentNaming 9 - public class ISupportPrefix 8 + /// <summary> 9 + /// Split the prefix value and add them to our known <see cref="Modes"/> and <see cref="Prefixes"/> 10 + /// </summary> 11 + /// <param name="splitVal"></param> 12 + /// <exception cref="ArgumentNullException"></exception> 13 + public ISupportPrefix(string splitVal) 10 14 { 11 - /// <summary> 12 - /// Split the prefix value and add them to our known <see cref="Modes"/> and <see cref="Prefixes"/> 13 - /// </summary> 14 - /// <param name="splitVal"></param> 15 - /// <exception cref="ArgumentNullException"></exception> 16 - public ISupportPrefix(string splitVal) 15 + if (splitVal == null) 17 16 { 18 - if (splitVal == null) throw new ArgumentNullException(nameof(splitVal)); 17 + throw new ArgumentNullException(nameof(splitVal)); 18 + } 19 19 20 - var split = splitVal[1..].Split(')', 2); 21 - Modes = new List<string>(); 22 - Modes.AddRange(split[0].Select(c => c.ToString(CultureInfo.InvariantCulture))); 23 - Prefixes = new List<string>(); 24 - Prefixes.AddRange(split[1].Select(c => c.ToString(CultureInfo.InvariantCulture))); 25 - } 20 + var split = splitVal.Substring(1).Split([')'], 2); 21 + Modes = split[0].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList(); 22 + Prefixes = split[1].Select(c => c.ToString(CultureInfo.InvariantCulture)).ToList(); 23 + } 26 24 27 - public List<string> Modes { get; } 28 - public List<string> Prefixes { get; } 25 + public List<string> Modes { get; } 26 + public List<string> Prefixes { get; } 29 27 30 - // ReSharper disable once UnusedMember.Global 31 - public string FromMode(char mode) 32 - { 33 - return FromMode(mode.ToString(CultureInfo.InvariantCulture)); 34 - } 28 + // ReSharper disable once UnusedMember.Global 29 + public string FromMode(char mode) => 30 + FromMode(mode.ToString(CultureInfo.InvariantCulture)); 35 31 36 - public string FromMode(string mode) 37 - { 38 - return Modes.Contains(mode) ? Prefixes[Modes.IndexOf(mode)] : null; 39 - } 32 + public string FromMode(string mode) => 33 + Modes.Contains(mode) ? Prefixes[Modes.IndexOf(mode)] : null; 40 34 41 - public string FromPrefix(char prefix) 42 - { 43 - return FromPrefix(prefix.ToString(CultureInfo.InvariantCulture)); 44 - } 35 + public string FromPrefix(char prefix) => 36 + FromPrefix(prefix.ToString(CultureInfo.InvariantCulture)); 45 37 46 - public string FromPrefix(string prefix) 47 - { 48 - return Prefixes.Contains(prefix) ? Modes[Prefixes.IndexOf(prefix)] : null; 49 - } 50 - } 38 + public string FromPrefix(string prefix) => 39 + Prefixes.Contains(prefix) ? Modes[Prefixes.IndexOf(prefix)] : null; 51 40 }
+76 -76
IRCStates/Numeric.cs
··· 1 - // ReSharper disable InconsistentNaming 1 + // ReSharper disable InconsistentNaming 2 2 // ReSharper disable UnusedMember.Global 3 3 // ReSharper disable IdentifierTypo 4 - namespace IRCStates 4 + 5 + namespace IRCStates; 6 + 7 + /// <summary> 8 + /// Known numeric response codes 9 + /// </summary> 10 + public static class Numeric 5 11 { 6 - /// <summary> 7 - /// Known numeric response codes 8 - /// </summary> 9 - public static class Numeric 10 - { 11 12 #pragma warning disable CA1707 // Identifiers should not contain underscores 12 - public const string RPL_WELCOME = "001"; 13 - public const string RPL_ISUPPORT = "005"; 14 - public const string RPL_MOTD = "372"; 15 - public const string RPL_MOTDSTART = "375"; 16 - public const string RPL_ENDOFMOTD = "376"; 17 - public const string ERR_NOMOTD = "422"; 18 - public const string RPL_UMODEIS = "221"; 19 - public const string RPL_VISIBLEHOST = "396"; 20 - public const string RPL_TRYAGAIN = "263"; 21 - public const string RPL_YOUREOPER = "381"; 13 + public const string RPL_WELCOME = "001"; 14 + public const string RPL_ISUPPORT = "005"; 15 + public const string RPL_MOTD = "372"; 16 + public const string RPL_MOTDSTART = "375"; 17 + public const string RPL_ENDOFMOTD = "376"; 18 + public const string ERR_NOMOTD = "422"; 19 + public const string RPL_UMODEIS = "221"; 20 + public const string RPL_VISIBLEHOST = "396"; 21 + public const string RPL_TRYAGAIN = "263"; 22 + public const string RPL_YOUREOPER = "381"; 22 23 23 - public const string ERR_NOSUCHNICK = "401"; 24 - public const string ERR_NOSUCHSERVER = "402"; 24 + public const string ERR_NOSUCHNICK = "401"; 25 + public const string ERR_NOSUCHSERVER = "402"; 25 26 26 - public const string RPL_CHANNELMODEIS = "324"; 27 - public const string RPL_CREATIONTIME = "329"; 28 - public const string RPL_TOPIC = "332"; 29 - public const string RPL_TOPICWHOTIME = "333"; 27 + public const string RPL_CHANNELMODEIS = "324"; 28 + public const string RPL_CREATIONTIME = "329"; 29 + public const string RPL_TOPIC = "332"; 30 + public const string RPL_TOPICWHOTIME = "333"; 30 31 31 - public const string RPL_WHOREPLY = "352"; 32 - public const string RPL_WHOSPCRPL = "354"; 33 - public const string RPL_ENDOFWHO = "315"; 34 - public const string RPL_NAMREPLY = "353"; 35 - public const string RPL_ENDOFNAMES = "366"; 32 + public const string RPL_WHOREPLY = "352"; 33 + public const string RPL_WHOSPCRPL = "354"; 34 + public const string RPL_ENDOFWHO = "315"; 35 + public const string RPL_NAMREPLY = "353"; 36 + public const string RPL_ENDOFNAMES = "366"; 36 37 37 - public const string RPL_WHOWASUSER = "314"; 38 - public const string RPL_ENDOFWHOWAS = "369"; 38 + public const string RPL_WHOWASUSER = "314"; 39 + public const string RPL_ENDOFWHOWAS = "369"; 39 40 40 - public const string RPL_BANLIST = "367"; 41 - public const string RPL_ENDOFBANLIST = "368"; 42 - public const string RPL_QUIETLIST = "728"; 43 - public const string RPL_ENDOFQUIETLIST = "729"; 41 + public const string RPL_BANLIST = "367"; 42 + public const string RPL_ENDOFBANLIST = "368"; 43 + public const string RPL_QUIETLIST = "728"; 44 + public const string RPL_ENDOFQUIETLIST = "729"; 44 45 45 - public const string RPL_LOGGEDIN = "900"; 46 - public const string RPL_LOGGEDOUT = "901"; 47 - public const string RPL_SASLSUCCESS = "903"; 48 - public const string ERR_SASLFAIL = "904"; 49 - public const string ERR_SASLTOOLONG = "905"; 50 - public const string ERR_SASLABORTED = "906"; 51 - public const string ERR_SASLALREADY = "907"; 52 - public const string RPL_SASLMECHS = "908"; 46 + public const string RPL_LOGGEDIN = "900"; 47 + public const string RPL_LOGGEDOUT = "901"; 48 + public const string RPL_SASLSUCCESS = "903"; 49 + public const string ERR_SASLFAIL = "904"; 50 + public const string ERR_SASLTOOLONG = "905"; 51 + public const string ERR_SASLABORTED = "906"; 52 + public const string ERR_SASLALREADY = "907"; 53 + public const string RPL_SASLMECHS = "908"; 53 54 54 - public const string RPL_WHOISUSER = "311"; 55 - public const string RPL_WHOISSERVER = "312"; 56 - public const string RPL_WHOISOPERATOR = "313"; 57 - public const string RPL_WHOISIDLE = "317"; 58 - public const string RPL_WHOISCHANNELS = "319"; 59 - public const string RPL_WHOISACCOUNT = "330"; 60 - public const string RPL_WHOISHOST = "378"; 61 - public const string RPL_WHOISMODES = "379"; 62 - public const string RPL_WHOISSECURE = "671"; 63 - public const string RPL_AWAY = "301"; 64 - public const string RPL_ENDOFWHOIS = "318"; 55 + public const string RPL_WHOISUSER = "311"; 56 + public const string RPL_WHOISSERVER = "312"; 57 + public const string RPL_WHOISOPERATOR = "313"; 58 + public const string RPL_WHOISIDLE = "317"; 59 + public const string RPL_WHOISCHANNELS = "319"; 60 + public const string RPL_WHOISACCOUNT = "330"; 61 + public const string RPL_WHOISHOST = "378"; 62 + public const string RPL_WHOISMODES = "379"; 63 + public const string RPL_WHOISSECURE = "671"; 64 + public const string RPL_AWAY = "301"; 65 + public const string RPL_ENDOFWHOIS = "318"; 65 66 66 - public const string ERR_ERRONEUSNICKNAME = "432"; 67 - public const string ERR_NICKNAMEINUSE = "433"; 68 - public const string ERR_BANNICKCHANGE = "435"; 69 - public const string ERR_UNAVAILRESOURCE = "437"; 70 - public const string ERR_NICKTOOFAST = "438"; 71 - public const string ERR_CANTCHANGENICK = "447"; 67 + public const string ERR_ERRONEUSNICKNAME = "432"; 68 + public const string ERR_NICKNAMEINUSE = "433"; 69 + public const string ERR_BANNICKCHANGE = "435"; 70 + public const string ERR_UNAVAILRESOURCE = "437"; 71 + public const string ERR_NICKTOOFAST = "438"; 72 + public const string ERR_CANTCHANGENICK = "447"; 72 73 73 - public const string ERR_NOSUCHCHANNEL = "403"; 74 - public const string ERR_TOOMANYCHANNELS = "405"; 75 - public const string ERR_USERONCHANNEL = "443"; 76 - public const string ERR_LINKCHANNEL = "470"; 77 - public const string ERR_BADCHANNAME = "479"; 78 - public const string ERR_BADCHANNEL = "926"; 74 + public const string ERR_NOSUCHCHANNEL = "403"; 75 + public const string ERR_TOOMANYCHANNELS = "405"; 76 + public const string ERR_USERONCHANNEL = "443"; 77 + public const string ERR_LINKCHANNEL = "470"; 78 + public const string ERR_BADCHANNAME = "479"; 79 + public const string ERR_BADCHANNEL = "926"; 79 80 80 - public const string ERR_BANNEDFROMCHAN = "474"; 81 - public const string ERR_INVITEONLYCHAN = "473"; 82 - public const string ERR_BADCHANNELKEY = "475"; 83 - public const string ERR_CHANNELISFULL = "471"; 84 - public const string ERR_NEEDREGGEDNICK = "477"; 85 - public const string ERR_THROTTLE = "480"; 81 + public const string ERR_BANNEDFROMCHAN = "474"; 82 + public const string ERR_INVITEONLYCHAN = "473"; 83 + public const string ERR_BADCHANNELKEY = "475"; 84 + public const string ERR_CHANNELISFULL = "471"; 85 + public const string ERR_NEEDREGGEDNICK = "477"; 86 + public const string ERR_THROTTLE = "480"; 86 87 87 - public const string RPL_LOGOFF = "601"; 88 - public const string RPL_MONOFFLINE = "731"; 88 + public const string RPL_LOGOFF = "601"; 89 + public const string RPL_MONOFFLINE = "731"; 89 90 90 - public const string RPL_RSACHALLENGE2 = "740"; 91 - public const string RPL_ENDOFRSACHALLENGE2 = "741"; 91 + public const string RPL_RSACHALLENGE2 = "740"; 92 + public const string RPL_ENDOFRSACHALLENGE2 = "741"; 92 93 #pragma warning restore CA1707 // Identifiers should not contain underscores 93 - } 94 94 }
+1 -1
IRCStates/README.md
··· 1 - # IRCStates 1 + # IRCStates 2 2 3 3 port of [jesopo/ircstates](https://github.com/jesopo/ircstates) 4 4
+944 -898
IRCStates/Server.cs
··· 1 - using System; 2 - using System.Collections.Generic; 3 1 using System.Globalization; 4 - using System.Linq; 5 2 using IRCTokens; 3 + // ReSharper disable InvertIf 4 + 6 5 // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global 7 6 // ReSharper disable MemberCanBePrivate.Global 8 7 // ReSharper disable UnusedAutoPropertyAccessor.Global 9 8 // ReSharper disable CommentTypo 10 9 // ReSharper disable IdentifierTypo 11 10 12 - namespace IRCStates 11 + namespace IRCStates; 12 + 13 + public class Server(string name) 13 14 { 14 - public class Server 15 - { 16 - public const string WhoType = "525"; // randomly generated 17 - private readonly StatefulDecoder _decoder; 15 + public const string WhoType = "525"; // randomly generated 16 + private readonly StatefulDecoder _decoder = new(); 18 17 19 - private readonly Dictionary<string, string> _tempCaps; 18 + private readonly Dictionary<string, string> _tempCaps = []; 20 19 21 - public Server(string name) 22 - { 23 - Name = name; 24 - Registered = false; 25 - Modes = new List<string>(); 26 - Motd = new List<string>(); 27 - _decoder = new StatefulDecoder(); 28 - Users = new Dictionary<string, User>(); 29 - Channels = new Dictionary<string, Channel>(); 30 - ISupport = new ISupport(); 31 - HasCap = false; 32 - _tempCaps = new Dictionary<string, string>(); 33 - AvailableCaps = new Dictionary<string, string>(); 34 - AgreedCaps = new List<string>(); 35 - } 20 + public string Name { get; set; } = name; 21 + public string NickName { get; set; } 22 + public string NickNameLower { get; set; } 23 + public string UserName { get; set; } 24 + public string HostName { get; set; } 25 + public string RealName { get; set; } 26 + public string Account { get; set; } 27 + public string Away { get; set; } 36 28 37 - public string Name { get; set; } 38 - public string NickName { get; set; } 39 - public string NickNameLower { get; set; } 40 - public string UserName { get; set; } 41 - public string HostName { get; set; } 42 - public string RealName { get; set; } 43 - public string Account { get; set; } 44 - public string Away { get; set; } 29 + public bool Registered { get; set; } 30 + public List<string> Modes { get; set; } = []; 31 + public List<string> Motd { get; set; } = []; 32 + public Dictionary<string, User> Users { get; set; } = []; 33 + public Dictionary<string, Channel> Channels { get; set; } = []; 34 + public Dictionary<string, string> AvailableCaps { get; set; } = []; 35 + public List<string> AgreedCaps { get; set; } = []; 45 36 46 - public bool Registered { get; set; } 47 - public List<string> Modes { get; set; } 48 - public List<string> Motd { get; set; } 49 - public Dictionary<string, User> Users { get; set; } 50 - public Dictionary<string, Channel> Channels { get; set; } 51 - public Dictionary<string, string> AvailableCaps { get; set; } 52 - public List<string> AgreedCaps { get; set; } 37 + // ReSharper disable once InconsistentNaming 38 + public ISupport ISupport { get; set; } = new(); 39 + public bool HasCap { get; set; } 53 40 54 - // ReSharper disable once InconsistentNaming 55 - public ISupport ISupport { get; set; } 56 - public bool HasCap { get; set; } 41 + public override string ToString() => $"Server(name={Name})"; 57 42 58 - public override string ToString() 59 - { 60 - return $"Server(name={Name})"; 61 - } 43 + /// <summary> 44 + /// Use <see cref="ISupport"/>'s case mapping to convert to lowercase 45 + /// </summary> 46 + /// <param name="str"></param> 47 + /// <returns></returns> 48 + public string CaseFold(string str) => Casemap.CaseFold(ISupport.CaseMapping, str); 62 49 63 - /// <summary> 64 - /// Use <see cref="ISupport"/>'s case mapping to convert to lowercase 65 - /// </summary> 66 - /// <param name="str"></param> 67 - /// <returns></returns> 68 - public string CaseFold(string str) 69 - { 70 - return Casemap.CaseFold(ISupport.CaseMapping, str); 71 - } 50 + /// <summary> 51 + /// Is the current nickname this client? 52 + /// </summary> 53 + /// <param name="nickname"></param> 54 + /// <returns></returns> 55 + private bool IsMe(string nickname) => CaseFold(nickname) == NickNameLower; 56 + 57 + /// <summary> 58 + /// Check for a user - not case-sensitive 59 + /// </summary> 60 + /// <param name="nickname"></param> 61 + /// <returns></returns> 62 + private bool HasUser(string nickname) => Users.ContainsKey(CaseFold(nickname)); 72 63 73 - /// <summary> 74 - /// Is the current nickname this client? 75 - /// </summary> 76 - /// <param name="nickname"></param> 77 - /// <returns></returns> 78 - private bool IsMe(string nickname) 79 - { 80 - return CaseFold(nickname) == NickNameLower; 81 - } 64 + /// <summary> 65 + /// Get existing user by case-insensitive nickname 66 + /// </summary> 67 + /// <param name="nickname"></param> 68 + /// <returns></returns> 69 + private User GetUser(string nickname) => HasUser(nickname) ? Users[CaseFold(nickname)] : null; 82 70 83 - /// <summary> 84 - /// Check for a user - not case-sensitive 85 - /// </summary> 86 - /// <param name="nickname"></param> 87 - /// <returns></returns> 88 - private bool HasUser(string nickname) 89 - { 90 - return Users.ContainsKey(CaseFold(nickname)); 91 - } 71 + /// <summary> 72 + /// Create and add user 73 + /// </summary> 74 + /// <param name="nickname"></param> 75 + /// <returns></returns> 76 + private User AddUser(string nickname) 77 + { 78 + var user = CreateUser(nickname); 79 + Users[CaseFold(nickname)] = user; 80 + return user; 81 + } 92 82 93 - /// <summary> 94 - /// Get existing user by case-insensitive nickname 95 - /// </summary> 96 - /// <param name="nickname"></param> 97 - /// <returns></returns> 98 - private User GetUser(string nickname) 99 - { 100 - return HasUser(nickname) ? Users[CaseFold(nickname)] : null; 101 - } 83 + /// <summary> 84 + /// Build a new <see cref="User"/> and update correct case-mapped nick 85 + /// </summary> 86 + /// <param name="nickname"></param> 87 + /// <returns></returns> 88 + private User CreateUser(string nickname) 89 + { 90 + var user = new User(); 91 + user.SetNickName(nickname, CaseFold(nickname)); 92 + return user; 93 + } 102 94 103 - /// <summary> 104 - /// Create and add user 105 - /// </summary> 106 - /// <param name="nickname"></param> 107 - /// <returns></returns> 108 - private User AddUser(string nickname) 109 - { 110 - var user = CreateUser(nickname); 111 - Users[CaseFold(nickname)] = user; 112 - return user; 113 - } 95 + /// <summary> 96 + /// Is the channel a valid ISupport type? 97 + /// </summary> 98 + /// <param name="target"></param> 99 + /// <returns></returns> 100 + private bool IsChannel(string target) => 101 + !string.IsNullOrEmpty(target) && 102 + ISupport.ChanTypes.Contains(target[0].ToString(CultureInfo.InvariantCulture)); 114 103 115 - /// <summary> 116 - /// Build a new <see cref="User"/> and update correct case-mapped nick 117 - /// </summary> 118 - /// <param name="nickname"></param> 119 - /// <returns></returns> 120 - private User CreateUser(string nickname) 121 - { 122 - var user = new User(); 123 - user.SetNickName(nickname, CaseFold(nickname)); 124 - return user; 125 - } 104 + /// <summary> 105 + /// Is the channel known to this client? 106 + /// </summary> 107 + /// <param name="name"></param> 108 + /// <returns></returns> 109 + public bool HasChannel(string name) => IsChannel(name) && Channels.ContainsKey(CaseFold(name)); 126 110 127 - /// <summary> 128 - /// Is the channel a valid ISupport type? 129 - /// </summary> 130 - /// <param name="target"></param> 131 - /// <returns></returns> 132 - private bool IsChannel(string target) 133 - { 134 - return !string.IsNullOrEmpty(target) && 135 - ISupport.ChanTypes.Contains(target[0].ToString(CultureInfo.InvariantCulture)); 136 - } 111 + /// <summary> 112 + /// Get the channel if it's known to us 113 + /// </summary> 114 + /// <param name="name"></param> 115 + /// <returns></returns> 116 + private Channel GetChannel(string name) => HasChannel(name) ? Channels[CaseFold(name)] : null; 137 117 138 - /// <summary> 139 - /// Is the channel known to this client? 140 - /// </summary> 141 - /// <param name="name"></param> 142 - /// <returns></returns> 143 - public bool HasChannel(string name) 144 - { 145 - return IsChannel(name) && Channels.ContainsKey(CaseFold(name)); 146 - } 118 + /// <summary> 119 + /// Add a <see cref="User"/> to a <see cref="Channel"/> 120 + /// </summary> 121 + /// <param name="channel"></param> 122 + /// <param name="user"></param> 123 + /// <returns>the <see cref="ChannelUser"/> that was added</returns> 124 + private ChannelUser UserJoin(Channel channel, User user) 125 + { 126 + var channelUser = new ChannelUser(); 127 + user.Channels.Add(CaseFold(channel.Name)); 128 + channel.Users[user.NickNameLower] = channelUser; 129 + return channelUser; 130 + } 147 131 148 - /// <summary> 149 - /// Get the channel if it's known to us 150 - /// </summary> 151 - /// <param name="name"></param> 152 - /// <returns></returns> 153 - private Channel GetChannel(string name) 132 + /// <summary> 133 + /// Set own <see cref="NickName"/>, <see cref="UserName"/>, and <see cref="HostName"/> 134 + /// from a given <see cref="Hostmask"/> 135 + /// </summary> 136 + /// <param name="hostmask"></param> 137 + private void SelfHostmask(Hostmask hostmask) 138 + { 139 + NickName = hostmask.NickName; 140 + if (hostmask.UserName != null) 154 141 { 155 - return HasChannel(name) ? Channels[CaseFold(name)] : null; 142 + UserName = hostmask.UserName; 156 143 } 157 144 158 - /// <summary> 159 - /// Add a <see cref="User"/> to a <see cref="Channel"/> 160 - /// </summary> 161 - /// <param name="channel"></param> 162 - /// <param name="user"></param> 163 - /// <returns>the <see cref="ChannelUser"/> that was added</returns> 164 - private ChannelUser UserJoin(Channel channel, User user) 145 + if (hostmask.HostName != null) 165 146 { 166 - var channelUser = new ChannelUser(); 167 - user.Channels.Add(CaseFold(channel.Name)); 168 - channel.Users[user.NickNameLower] = channelUser; 169 - return channelUser; 147 + HostName = hostmask.HostName; 170 148 } 149 + } 171 150 172 - /// <summary> 173 - /// Set own <see cref="NickName"/>, <see cref="UserName"/>, and <see cref="HostName"/> 174 - /// from a given <see cref="Hostmask"/> 175 - /// </summary> 176 - /// <param name="hostmask"></param> 177 - private void SelfHostmask(Hostmask hostmask) 178 - { 179 - NickName = hostmask.NickName; 180 - if (hostmask.UserName != null) UserName = hostmask.UserName; 181 - if (hostmask.HostName != null) HostName = hostmask.HostName; 182 - } 151 + private void SelfHostmask(string raw) 152 + { 153 + SelfHostmask(new Hostmask(raw)); 154 + } 183 155 184 - private void SelfHostmask(string raw) 156 + /// <summary> 157 + /// Remove a user from a channel. Used to handle PART and KICK 158 + /// </summary> 159 + /// <param name="line"></param> 160 + /// <param name="nickName"></param> 161 + /// <param name="channelName"></param> 162 + /// <param name="reasonIndex"></param> 163 + /// <returns></returns> 164 + private (Emit, User) UserPart(Line line, string nickName, string channelName, int reasonIndex) 165 + { 166 + var emit = new Emit(); 167 + var channelLower = CaseFold(channelName); 168 + if (line.Params.Count >= reasonIndex + 1) 185 169 { 186 - SelfHostmask(new Hostmask(raw)); 170 + emit.Text = line.Params[reasonIndex]; 187 171 } 188 172 189 - /// <summary> 190 - /// Remove a user from a channel. Used to handle PART and KICK 191 - /// </summary> 192 - /// <param name="line"></param> 193 - /// <param name="nickName"></param> 194 - /// <param name="channelName"></param> 195 - /// <param name="reasonIndex"></param> 196 - /// <returns></returns> 197 - private (Emit, User) UserPart(Line line, string nickName, string channelName, int reasonIndex) 173 + User user = null; 174 + if (HasChannel(channelName)) 198 175 { 199 - var emit = new Emit(); 200 - var channelLower = CaseFold(channelName); 201 - if (line.Params.Count >= reasonIndex + 1) emit.Text = line.Params[reasonIndex]; 202 - 203 - User user = null; 204 - if (HasChannel(channelName)) 176 + var channel = GetChannel(channelName); 177 + emit.Channel = channel; 178 + var nickLower = CaseFold(nickName); 179 + if (HasUser(nickLower)) 205 180 { 206 - var channel = GetChannel(channelName); 207 - emit.Channel = channel; 208 - var nickLower = CaseFold(nickName); 209 - if (HasUser(nickLower)) 181 + user = Users[nickLower]; 182 + user.Channels.Remove(channelLower); 183 + channel.Users.Remove(nickLower); 184 + if (!user.Channels.Any()) 210 185 { 211 - user = Users[nickLower]; 212 - user.Channels.Remove(channelLower); 213 - channel.Users.Remove(nickLower); 214 - if (!user.Channels.Any()) Users.Remove(nickLower); 186 + Users.Remove(nickLower); 215 187 } 188 + } 216 189 217 - if (IsMe(nickName)) 190 + if (IsMe(nickName)) 191 + { 192 + Channels.Remove(channelLower); 193 + foreach (var userToRemove in channel.Users.Keys.Select(u => Users[u])) 218 194 { 219 - Channels.Remove(channelLower); 220 - foreach (var userToRemove in channel.Users.Keys.Select(u => Users[u])) 195 + userToRemove.Channels.Remove(channelLower); 196 + if (!userToRemove.Channels.Any()) 221 197 { 222 - userToRemove.Channels.Remove(channelLower); 223 - if (!userToRemove.Channels.Any()) Users.Remove(userToRemove.NickNameLower); 198 + Users.Remove(userToRemove.NickNameLower); 224 199 } 225 200 } 226 201 } 227 - 228 - return (emit, user); 229 202 } 230 203 231 - /// <summary> 232 - /// Update modes on a <see cref="Channel"/> given modes and parameters 233 - /// </summary> 234 - /// <param name="channel"></param> 235 - /// <param name="modes"></param> 236 - /// <param name="parameters"></param> 237 - private void SetChannelModes(Channel channel, IEnumerable<(bool, string)> modes, IList<string> parameters) 204 + return (emit, user); 205 + } 206 + 207 + /// <summary> 208 + /// Update modes on a <see cref="Channel"/> given modes and parameters 209 + /// </summary> 210 + /// <param name="channel"></param> 211 + /// <param name="modes"></param> 212 + /// <param name="parameters"></param> 213 + private void SetChannelModes(Channel channel, IEnumerable<(bool, string)> modes, IList<string> parameters) 214 + { 215 + foreach (var (add, c) in modes) 238 216 { 239 - foreach (var (add, c) in modes) 217 + var listMode = ISupport.ChanModes.ListModes.Contains(c); 218 + if (ISupport.Prefix.Modes.Contains(c)) 240 219 { 241 - var listMode = ISupport.ChanModes.ListModes.Contains(c); 242 - if (ISupport.Prefix.Modes.Contains(c)) 220 + var nicknameLower = CaseFold(parameters.First()); 221 + parameters.RemoveAt(0); 222 + if (!HasUser(nicknameLower)) 243 223 { 244 - var nicknameLower = CaseFold(parameters.First()); 245 - parameters.RemoveAt(0); 246 - if (!HasUser(nicknameLower)) continue; 224 + continue; 225 + } 247 226 248 - var channelUser = channel.Users[nicknameLower]; 249 - if (add) 227 + var channelUser = channel.Users[nicknameLower]; 228 + if (add) 229 + { 230 + if (!channelUser.Modes.Contains(c)) 250 231 { 251 - if (!channelUser.Modes.Contains(c)) channelUser.Modes.Add(c); 252 - } 253 - else if (channelUser.Modes.Contains(c)) 254 - { 255 - channelUser.Modes.Remove(c); 232 + channelUser.Modes.Add(c); 256 233 } 257 234 } 258 - else if (add && (listMode || 259 - ISupport.ChanModes.SettingBModes.Contains(c) || 260 - ISupport.ChanModes.SettingCModes.Contains(c))) 235 + else if (channelUser.Modes.Contains(c)) 261 236 { 262 - channel.AddMode(c, parameters.First(), listMode); 263 - parameters.RemoveAt(0); 237 + channelUser.Modes.Remove(c); 264 238 } 265 - else if (!add && (listMode || ISupport.ChanModes.SettingBModes.Contains(c))) 266 - { 267 - channel.RemoveMode(c, parameters.First()); 268 - parameters.RemoveAt(0); 269 - } 270 - else if (add) 271 - { 272 - channel.AddMode(c, null, false); 273 - } 274 - else 275 - { 276 - channel.RemoveMode(c, null); 277 - } 239 + } 240 + else if (add && (listMode || 241 + ISupport.ChanModes.SettingBModes.Contains(c) || 242 + ISupport.ChanModes.SettingCModes.Contains(c))) 243 + { 244 + channel.AddMode(c, parameters.First(), listMode); 245 + parameters.RemoveAt(0); 246 + } 247 + else if (!add && (listMode || ISupport.ChanModes.SettingBModes.Contains(c))) 248 + { 249 + channel.RemoveMode(c, parameters.First()); 250 + parameters.RemoveAt(0); 251 + } 252 + else if (add) 253 + { 254 + channel.AddMode(c, null, false); 255 + } 256 + else 257 + { 258 + channel.RemoveMode(c, null); 278 259 } 279 260 } 261 + } 280 262 281 - /// <summary> 282 - /// Handle incoming bytes 283 - /// </summary> 284 - /// <param name="data"></param> 285 - /// <param name="length"></param> 286 - /// <returns>parsed lines and emits</returns> 287 - /// <exception cref="ServerDisconnectedException"></exception> 288 - public IEnumerable<(Line, Emit)> Receive(byte[] data, int length) 263 + /// <summary> 264 + /// Handle incoming bytes 265 + /// </summary> 266 + /// <param name="data"></param> 267 + /// <param name="length"></param> 268 + /// <returns>parsed lines and emits</returns> 269 + /// <exception cref="ServerDisconnectedException"></exception> 270 + public IEnumerable<(Line, Emit)> Receive(byte[] data, int length) 271 + { 272 + if (data == null) 289 273 { 290 - if (data == null) return null; 274 + return null; 275 + } 291 276 292 - var lines = _decoder.Push(data, length); 293 - if (lines == null) throw new ServerDisconnectedException(); 277 + var lines = _decoder.Push(data, length); 278 + if (lines == null) 279 + { 280 + throw new ServerDisconnectedException(); 281 + } 294 282 295 - return lines.Select(l => (l, Parse(l))); 283 + return lines.Select(l => (l, Parse(l))); 284 + } 285 + 286 + /// <summary> 287 + /// Delegate a <see cref="Line"/> to the correct handler 288 + /// </summary> 289 + /// <param name="line"></param> 290 + /// <returns></returns> 291 + public Emit Parse(Line line) 292 + { 293 + if (line == null) 294 + { 295 + return null; 296 296 } 297 297 298 - /// <summary> 299 - /// Delegate a <see cref="Line"/> to the correct handler 300 - /// </summary> 301 - /// <param name="line"></param> 302 - /// <returns></returns> 303 - public Emit Parse(Line line) 298 + var emit = line.Command switch 304 299 { 305 - if (line == null) return null; 300 + Numeric.RPL_WELCOME => HandleWelcome(line), 301 + Numeric.RPL_ISUPPORT => HandleISupport(line), 302 + Numeric.RPL_MOTDSTART => HandleMotd(line), 303 + Numeric.RPL_MOTD => HandleMotd(line), 304 + Commands.Nick => HandleNick(line), 305 + Commands.Join => HandleJoin(line), 306 + Commands.Part => HandlePart(line), 307 + Commands.Kick => HandleKick(line), 308 + Commands.Quit => HandleQuit(line), 309 + Commands.Error => HandleError(line), 310 + Numeric.RPL_NAMREPLY => HandleNames(line), 311 + Numeric.RPL_CREATIONTIME => HandleCreationTime(line), 312 + Commands.Topic => HandleTopic(line), 313 + Numeric.RPL_TOPIC => HandleTopicNumeric(line), 314 + Numeric.RPL_TOPICWHOTIME => HandleTopicTime(line), 315 + Commands.Mode => HandleMode(line), 316 + Numeric.RPL_CHANNELMODEIS => HandleChannelModeIs(line), 317 + Numeric.RPL_UMODEIS => HandleUModeIs(line), 318 + Commands.Privmsg => HandleMessage(line), 319 + Commands.Notice => HandleMessage(line), 320 + Commands.Tagmsg => HandleMessage(line), 321 + Numeric.RPL_VISIBLEHOST => HandleVisibleHost(line), 322 + Numeric.RPL_WHOREPLY => HandleWhoReply(line), 323 + Numeric.RPL_WHOSPCRPL => HandleWhox(line), 324 + Numeric.RPL_WHOISUSER => HandleWhoIsUser(line), 325 + Commands.Chghost => HandleChghost(line), 326 + Commands.Setname => HandleSetname(line), 327 + Commands.Away => HandleAway(line), 328 + Commands.Account => HandleAccount(line), 329 + Commands.Cap => HandleCap(line), 330 + Numeric.RPL_LOGGEDIN => HandleLoggedIn(line), 331 + Numeric.RPL_LOGGEDOUT => HandleLoggedOut(line), 332 + _ => null 333 + }; 334 + 335 + if (emit != null) 336 + { 337 + emit.Command = line.Command; 338 + } 339 + else 340 + { 341 + emit = new Emit(); 342 + } 306 343 307 - var emit = line.Command switch 308 - { 309 - Numeric.RPL_WELCOME => HandleWelcome(line), 310 - Numeric.RPL_ISUPPORT => HandleISupport(line), 311 - Numeric.RPL_MOTDSTART => HandleMotd(line), 312 - Numeric.RPL_MOTD => HandleMotd(line), 313 - Commands.Nick => HandleNick(line), 314 - Commands.Join => HandleJoin(line), 315 - Commands.Part => HandlePart(line), 316 - Commands.Kick => HandleKick(line), 317 - Commands.Quit => HandleQuit(line), 318 - Commands.Error => HandleError(line), 319 - Numeric.RPL_NAMREPLY => HandleNames(line), 320 - Numeric.RPL_CREATIONTIME => HandleCreationTime(line), 321 - Commands.Topic => HandleTopic(line), 322 - Numeric.RPL_TOPIC => HandleTopicNumeric(line), 323 - Numeric.RPL_TOPICWHOTIME => HandleTopicTime(line), 324 - Commands.Mode => HandleMode(line), 325 - Numeric.RPL_CHANNELMODEIS => HandleChannelModeIs(line), 326 - Numeric.RPL_UMODEIS => HandleUModeIs(line), 327 - Commands.Privmsg => HandleMessage(line), 328 - Commands.Notice => HandleMessage(line), 329 - Commands.Tagmsg => HandleMessage(line), 330 - Numeric.RPL_VISIBLEHOST => HandleVisibleHost(line), 331 - Numeric.RPL_WHOREPLY => HandleWhoReply(line), 332 - Numeric.RPL_WHOSPCRPL => HandleWhox(line), 333 - Numeric.RPL_WHOISUSER => HandleWhoIsUser(line), 334 - Commands.Chghost => HandleChghost(line), 335 - Commands.Setname => HandleSetname(line), 336 - Commands.Away => HandleAway(line), 337 - Commands.Account => HandleAccount(line), 338 - Commands.Cap => HandleCap(line), 339 - Numeric.RPL_LOGGEDIN => HandleLoggedIn(line), 340 - Numeric.RPL_LOGGEDOUT => HandleLoggedOut(line), 341 - _ => null 342 - }; 344 + return emit; 345 + } 343 346 344 - if (emit != null) 345 - emit.Command = line.Command; 346 - else 347 - emit = new Emit(); 347 + /// <summary> 348 + /// Handles SETNAME command 349 + /// </summary> 350 + /// <param name="line"></param> 351 + /// <returns></returns> 352 + private Emit HandleSetname(Line line) 353 + { 354 + var emit = new Emit(); 355 + var realname = line.Params[0]; 356 + var nicknameLower = CaseFold(line.Hostmask.NickName); 348 357 349 - return emit; 358 + if (IsMe(nicknameLower)) 359 + { 360 + emit.Self = true; 361 + RealName = realname; 350 362 } 351 363 352 - /// <summary> 353 - /// Handles SETNAME command 354 - /// </summary> 355 - /// <param name="line"></param> 356 - /// <returns></returns> 357 - private Emit HandleSetname(Line line) 364 + if (Users.TryGetValue(nicknameLower, out var user)) 358 365 { 359 - var emit = new Emit(); 360 - var realname = line.Params[0]; 361 - var nicknameLower = CaseFold(line.Hostmask.NickName); 366 + emit.User = user; 367 + user.RealName = realname; 368 + } 362 369 363 - if (IsMe(nicknameLower)) 364 - { 365 - emit.Self = true; 366 - RealName = realname; 367 - } 370 + return emit; 371 + } 368 372 369 - if (Users.TryGetValue(nicknameLower, out var user)) 370 - { 371 - emit.User = user; 372 - user.RealName = realname; 373 - } 373 + /// <summary> 374 + /// Handles AWAY command 375 + /// </summary> 376 + /// <param name="line"></param> 377 + /// <returns></returns> 378 + private Emit HandleAway(Line line) 379 + { 380 + var emit = new Emit(); 381 + var away = line.Params.FirstOrDefault(); 382 + var nicknameLower = CaseFold(line.Hostmask.NickName); 374 383 375 - return emit; 384 + if (IsMe(nicknameLower)) 385 + { 386 + emit.Self = true; 387 + Away = away; 376 388 } 377 389 378 - /// <summary> 379 - /// Handles AWAY command 380 - /// </summary> 381 - /// <param name="line"></param> 382 - /// <returns></returns> 383 - private Emit HandleAway(Line line) 390 + if (Users.TryGetValue(nicknameLower, out var user)) 384 391 { 385 - var emit = new Emit(); 386 - var away = line.Params.FirstOrDefault(); 387 - var nicknameLower = CaseFold(line.Hostmask.NickName); 392 + emit.User = user; 393 + user.Away = away; 394 + } 388 395 389 - if (IsMe(nicknameLower)) 390 - { 391 - emit.Self = true; 392 - Away = away; 393 - } 396 + return emit; 397 + } 394 398 395 - if (Users.TryGetValue(nicknameLower, out var user)) 396 - { 397 - emit.User = user; 398 - user.Away = away; 399 - } 399 + /// <summary> 400 + /// Handles ACCOUNT command 401 + /// </summary> 402 + /// <param name="line"></param> 403 + /// <returns></returns> 404 + private Emit HandleAccount(Line line) 405 + { 406 + var emit = new Emit(); 407 + var account = line.Params[0].Trim('*'); 408 + var nicknameLower = CaseFold(line.Hostmask.NickName); 400 409 401 - return emit; 410 + if (IsMe(nicknameLower)) 411 + { 412 + emit.Self = true; 413 + Account = account; 402 414 } 403 415 404 - /// <summary> 405 - /// Handles ACCOUNT command 406 - /// </summary> 407 - /// <param name="line"></param> 408 - /// <returns></returns> 409 - private Emit HandleAccount(Line line) 416 + if (Users.TryGetValue(nicknameLower, out var user)) 410 417 { 411 - var emit = new Emit(); 412 - var account = line.Params[0].Trim('*'); 413 - var nicknameLower = CaseFold(line.Hostmask.NickName); 414 - 415 - if (IsMe(nicknameLower)) 416 - { 417 - emit.Self = true; 418 - Account = account; 419 - } 418 + emit.User = user; 419 + user.Account = account; 420 + } 420 421 421 - if (Users.TryGetValue(nicknameLower, out var user)) 422 - { 423 - emit.User = user; 424 - user.Account = account; 425 - } 422 + return emit; 423 + } 426 424 427 - return emit; 428 - } 425 + /// <summary> 426 + /// Handles CAP command 427 + /// </summary> 428 + /// <param name="line"></param> 429 + /// <returns></returns> 430 + private Emit HandleCap(Line line) 431 + { 432 + HasCap = true; 433 + var subcommand = line.Params[1].ToUpperInvariant(); 434 + var multiline = line.Params[2] == "*"; 435 + var caps = line.Params[multiline ? 3 : 2]; 429 436 430 - /// <summary> 431 - /// Handles CAP command 432 - /// </summary> 433 - /// <param name="line"></param> 434 - /// <returns></returns> 435 - private Emit HandleCap(Line line) 437 + var tokens = new Dictionary<string, string>(); 438 + var tokensStr = new List<string>(); 439 + foreach (var cap in caps.Split([' '], StringSplitOptions.RemoveEmptyEntries)) 436 440 { 437 - HasCap = true; 438 - var subcommand = line.Params[1].ToUpperInvariant(); 439 - var multiline = line.Params[2] == "*"; 440 - var caps = line.Params[multiline ? 3 : 2]; 441 + tokensStr.Add(cap); 442 + var kv = cap.Split(['='], 2); 443 + tokens[kv[0]] = kv.Length > 1 ? kv[1] : string.Empty; 444 + } 441 445 442 - var tokens = new Dictionary<string, string>(); 443 - var tokensStr = new List<string>(); 444 - foreach (var cap in caps.Split(' ', StringSplitOptions.RemoveEmptyEntries)) 445 - { 446 - tokensStr.Add(cap); 447 - var kv = cap.Split('=', 2); 448 - tokens[kv[0]] = kv.Length > 1 ? kv[1] : string.Empty; 449 - } 446 + var emit = new Emit { Subcommand = subcommand, Finished = !multiline, Tokens = tokensStr }; 450 447 451 - var emit = new Emit {Subcommand = subcommand, Finished = !multiline, Tokens = tokensStr}; 448 + switch (subcommand) 449 + { 450 + case "LS": 451 + _tempCaps.UpdateWith(tokens); 452 + if (!multiline) 453 + { 454 + AvailableCaps.UpdateWith(_tempCaps); 455 + _tempCaps.Clear(); 456 + } 452 457 453 - switch (subcommand) 454 - { 455 - case "LS": 456 - _tempCaps.UpdateWith(tokens); 457 - if (!multiline) 458 + break; 459 + case "NEW": AvailableCaps.UpdateWith(tokens); break; 460 + case "DEL": 461 + foreach (var key in tokens.Keys.Where(key => AvailableCaps.ContainsKey(key))) 462 + { 463 + AvailableCaps.Remove(key); 464 + if (AgreedCaps.Contains(key)) 458 465 { 459 - AvailableCaps.UpdateWith(_tempCaps); 460 - _tempCaps.Clear(); 466 + AgreedCaps.Remove(key); 461 467 } 468 + } 462 469 463 - break; 464 - case "NEW": 465 - AvailableCaps.UpdateWith(tokens); 466 - break; 467 - case "DEL": 468 - foreach (var key in tokens.Keys.Where(key => AvailableCaps.ContainsKey(key))) 470 + break; 471 + case "ACK": 472 + foreach (var key in tokens.Keys) 473 + if (key.StartsWith("-")) 469 474 { 470 - AvailableCaps.Remove(key); 471 - if (AgreedCaps.Contains(key)) AgreedCaps.Remove(key); 472 - } 473 - 474 - break; 475 - case "ACK": 476 - foreach (var key in tokens.Keys) 477 - if (key.StartsWith('-')) 475 + var k = key.Substring(1); 476 + if (AgreedCaps.Contains(k)) 478 477 { 479 - var k = key[1..]; 480 - if (AgreedCaps.Contains(k)) AgreedCaps.Remove(k); 478 + AgreedCaps.Remove(k); 481 479 } 482 - else if (!AgreedCaps.Contains(key) && AvailableCaps.ContainsKey(key)) 483 - { 484 - AgreedCaps.Add(key); 485 - } 480 + } 481 + else if (!AgreedCaps.Contains(key) && AvailableCaps.ContainsKey(key)) 482 + { 483 + AgreedCaps.Add(key); 484 + } 486 485 487 - break; 488 - } 489 - 490 - return emit; 491 - } 492 - 493 - /// <summary> 494 - /// Handles RPL_LOGGEDIN numeric 495 - /// </summary> 496 - /// <param name="line"></param> 497 - /// <returns></returns> 498 - private Emit HandleLoggedIn(Line line) 499 - { 500 - SelfHostmask(new Hostmask(line.Params[1])); 501 - Account = line.Params[2]; 502 - return new Emit(); 486 + break; 503 487 } 504 488 505 - /// <summary> 506 - /// Handles CHGHOST command 507 - /// </summary> 508 - /// <param name="line"></param> 509 - /// <returns></returns> 510 - private Emit HandleChghost(Line line) 511 - { 512 - var emit = new Emit(); 513 - var username = line.Params[0]; 514 - var hostname = line.Params[1]; 515 - var nicknameLower = CaseFold(line.Hostmask.NickName); 489 + return emit; 490 + } 516 491 517 - if (IsMe(nicknameLower)) 518 - { 519 - emit.Self = true; 520 - UserName = username; 521 - HostName = hostname; 522 - } 492 + /// <summary> 493 + /// Handles RPL_LOGGEDIN numeric 494 + /// </summary> 495 + /// <param name="line"></param> 496 + /// <returns></returns> 497 + private Emit HandleLoggedIn(Line line) 498 + { 499 + SelfHostmask(new Hostmask(line.Params[1])); 500 + Account = line.Params[2]; 501 + return new Emit(); 502 + } 523 503 524 - if (Users.TryGetValue(nicknameLower, out var user)) 525 - { 526 - emit.User = user; 527 - user.UserName = username; 528 - user.HostName = hostname; 529 - } 504 + /// <summary> 505 + /// Handles CHGHOST command 506 + /// </summary> 507 + /// <param name="line"></param> 508 + /// <returns></returns> 509 + private Emit HandleChghost(Line line) 510 + { 511 + var emit = new Emit(); 512 + var username = line.Params[0]; 513 + var hostname = line.Params[1]; 514 + var nicknameLower = CaseFold(line.Hostmask.NickName); 530 515 531 - return emit; 516 + if (IsMe(nicknameLower)) 517 + { 518 + emit.Self = true; 519 + UserName = username; 520 + HostName = hostname; 532 521 } 533 522 534 - /// <summary> 535 - /// Handles RPL_WHOISUSER numeric 536 - /// </summary> 537 - /// <param name="line"></param> 538 - /// <returns></returns> 539 - private Emit HandleWhoIsUser(Line line) 523 + if (Users.TryGetValue(nicknameLower, out var user)) 540 524 { 541 - var emit = new Emit(); 542 - var nickname = line.Params[1]; 543 - var username = line.Params[2]; 544 - var hostname = line.Params[3]; 545 - var realname = line.Params[5]; 525 + emit.User = user; 526 + user.UserName = username; 527 + user.HostName = hostname; 528 + } 546 529 547 - if (IsMe(nickname)) 548 - { 549 - emit.Self = true; 550 - UserName = username; 551 - HostName = hostname; 552 - RealName = realname; 553 - } 530 + return emit; 531 + } 554 532 555 - if (HasUser(nickname)) 556 - { 557 - var user = Users[CaseFold(nickname)]; 558 - emit.User = user; 559 - user.UserName = username; 560 - user.HostName = hostname; 561 - user.RealName = realname; 562 - } 533 + /// <summary> 534 + /// Handles RPL_WHOISUSER numeric 535 + /// </summary> 536 + /// <param name="line"></param> 537 + /// <returns></returns> 538 + private Emit HandleWhoIsUser(Line line) 539 + { 540 + var emit = new Emit(); 541 + var nickname = line.Params[1]; 542 + var username = line.Params[2]; 543 + var hostname = line.Params[3]; 544 + var realname = line.Params[5]; 563 545 564 - return emit; 546 + if (IsMe(nickname)) 547 + { 548 + emit.Self = true; 549 + UserName = username; 550 + HostName = hostname; 551 + RealName = realname; 565 552 } 566 553 567 - /// <summary> 568 - /// Handles RPL_WHOSPCRPL numeric 569 - /// </summary> 570 - /// <param name="line"></param> 571 - /// <returns></returns> 572 - private Emit HandleWhox(Line line) 554 + if (HasUser(nickname)) 573 555 { 574 - var emit = new Emit(); 575 - if (line.Params[1] == WhoType && line.Params.Count == 8) 576 - { 577 - var nickname = line.Params[5]; 578 - var username = line.Params[2]; 579 - var hostname = line.Params[4]; 580 - var realname = line.Params[7]; 581 - var account = line.Params[6] == "0" ? null : line.Params[6]; 582 - 583 - if (IsMe(nickname)) 584 - { 585 - emit.Self = true; 586 - UserName = username; 587 - HostName = hostname; 588 - RealName = realname; 589 - Account = account; 590 - } 591 - 592 - if (HasUser(nickname)) 593 - { 594 - var user = Users[CaseFold(nickname)]; 595 - emit.User = user; 596 - user.UserName = username; 597 - user.HostName = hostname; 598 - user.RealName = realname; 599 - user.Account = account; 600 - } 601 - } 602 - 603 - return emit; 556 + var user = Users[CaseFold(nickname)]; 557 + emit.User = user; 558 + user.UserName = username; 559 + user.HostName = hostname; 560 + user.RealName = realname; 604 561 } 605 562 606 - /// <summary> 607 - /// Handles RPL_WHOREPLY numeric 608 - /// </summary> 609 - /// <param name="line"></param> 610 - /// <returns></returns> 611 - private Emit HandleWhoReply(Line line) 563 + return emit; 564 + } 565 + 566 + /// <summary> 567 + /// Handles RPL_WHOSPCRPL numeric 568 + /// </summary> 569 + /// <param name="line"></param> 570 + /// <returns></returns> 571 + private Emit HandleWhox(Line line) 572 + { 573 + var emit = new Emit(); 574 + if (line.Params[1] == WhoType && line.Params.Count == 8) 612 575 { 613 - var emit = new Emit {Target = line.Params[1]}; 614 576 var nickname = line.Params[5]; 615 577 var username = line.Params[2]; 616 - var hostname = line.Params[3]; 617 - var realname = line.Params[7].Split(' ', 2)[1]; 578 + var hostname = line.Params[4]; 579 + var realname = line.Params[7]; 580 + var account = line.Params[6] == "0" ? null : line.Params[6]; 618 581 619 582 if (IsMe(nickname)) 620 583 { ··· 622 585 UserName = username; 623 586 HostName = hostname; 624 587 RealName = realname; 588 + Account = account; 625 589 } 626 590 627 591 if (HasUser(nickname)) ··· 631 595 user.UserName = username; 632 596 user.HostName = hostname; 633 597 user.RealName = realname; 598 + user.Account = account; 634 599 } 635 - 636 - return emit; 637 600 } 638 601 639 - /// <summary> 640 - /// Handles RPL_VISIBLEHOST numeric 641 - /// </summary> 642 - /// <param name="line"></param> 643 - /// <returns></returns> 644 - private Emit HandleVisibleHost(Line line) 645 - { 646 - var split = line.Params[1].Split('@', 2); 647 - switch (split.Length) 648 - { 649 - case 1: 650 - HostName = split[0]; 651 - break; 652 - case 2: 653 - HostName = split[1]; 654 - UserName = split[0]; 655 - break; 656 - } 602 + return emit; 603 + } 604 + 605 + /// <summary> 606 + /// Handles RPL_WHOREPLY numeric 607 + /// </summary> 608 + /// <param name="line"></param> 609 + /// <returns></returns> 610 + private Emit HandleWhoReply(Line line) 611 + { 612 + var emit = new Emit { Target = line.Params[1] }; 613 + var nickname = line.Params[5]; 614 + var username = line.Params[2]; 615 + var hostname = line.Params[3]; 616 + var realname = line.Params[7].Split([' '], 2)[1]; 657 617 658 - return new Emit(); 618 + if (IsMe(nickname)) 619 + { 620 + emit.Self = true; 621 + UserName = username; 622 + HostName = hostname; 623 + RealName = realname; 659 624 } 660 625 661 - /// <summary> 662 - /// Handles PRIVMSG, NOTICE, and TAGMSG commands 663 - /// </summary> 664 - /// <param name="line"></param> 665 - /// <returns></returns> 666 - private Emit HandleMessage(Line line) 626 + if (HasUser(nickname)) 667 627 { 668 - var emit = new Emit(); 669 - var message = line.Params.Count > 1 ? line.Params[1] : null; 670 - if (message != null) emit.Text = message; 628 + var user = Users[CaseFold(nickname)]; 629 + emit.User = user; 630 + user.UserName = username; 631 + user.HostName = hostname; 632 + user.RealName = realname; 633 + } 671 634 672 - var nick = CaseFold(line.Hostmask.NickName); 673 - if (IsMe(nick)) 674 - { 675 - emit.SelfSource = true; 676 - SelfHostmask(line.Hostmask); 677 - } 635 + return emit; 636 + } 678 637 679 - var user = GetUser(nick) ?? AddUser(nick); 680 - emit.User = user; 638 + /// <summary> 639 + /// Handles RPL_VISIBLEHOST numeric 640 + /// </summary> 641 + /// <param name="line"></param> 642 + /// <returns></returns> 643 + private Emit HandleVisibleHost(Line line) 644 + { 645 + var split = line.Params[1].Split(['@'], 2); 646 + switch (split.Length) 647 + { 648 + case 1: HostName = split[0]; break; 649 + case 2: 650 + HostName = split[1]; 651 + UserName = split[0]; 652 + break; 653 + } 681 654 682 - if (line.Hostmask.UserName != null) user.UserName = line.Hostmask.UserName; 683 - if (line.Hostmask.HostName != null) user.HostName = line.Hostmask.HostName; 655 + return new Emit(); 656 + } 684 657 685 - var target = line.Params[0]; 686 - var statusMsg = new List<string>(); 687 - while (target.Length > 0) 688 - { 689 - var t = target[0].ToString(CultureInfo.InvariantCulture); 690 - if (ISupport.StatusMsg.Contains(t)) 691 - { 692 - statusMsg.Add(t); 693 - target = target[1..]; 694 - } 695 - else 696 - { 697 - break; 698 - } 699 - } 658 + /// <summary> 659 + /// Handles PRIVMSG, NOTICE, and TAGMSG commands 660 + /// </summary> 661 + /// <param name="line"></param> 662 + /// <returns></returns> 663 + private Emit HandleMessage(Line line) 664 + { 665 + var emit = new Emit(); 666 + var message = line.Params.Count > 1 ? line.Params[1] : null; 667 + if (message != null) 668 + { 669 + emit.Text = message; 670 + } 700 671 701 - emit.Target = line.Params[0]; 672 + var nick = CaseFold(line.Hostmask.NickName); 673 + if (IsMe(nick)) 674 + { 675 + emit.SelfSource = true; 676 + SelfHostmask(line.Hostmask); 677 + } 702 678 703 - if (IsChannel(target) && HasChannel(target)) 704 - emit.Channel = GetChannel(target); 705 - else if (IsMe(target)) emit.SelfTarget = true; 679 + var user = GetUser(nick) ?? AddUser(nick); 680 + emit.User = user; 706 681 707 - return emit; 682 + if (line.Hostmask.UserName != null) 683 + { 684 + user.UserName = line.Hostmask.UserName; 708 685 } 709 686 710 - /// <summary> 711 - /// Handles RPL_UMODEIS numeric 712 - /// </summary> 713 - /// <param name="line"></param> 714 - /// <returns></returns> 715 - private Emit HandleUModeIs(Line line) 687 + if (line.Hostmask.HostName != null) 716 688 { 717 - foreach (var c in line.Params[1] 718 - .TrimStart('+') 719 - .Select(m => m.ToString(CultureInfo.InvariantCulture)) 720 - .Where(m => !Modes.Contains(m))) 721 - Modes.Add(c); 722 - 723 - return new Emit(); 689 + user.HostName = line.Hostmask.HostName; 724 690 } 725 691 726 - /// <summary> 727 - /// Handles RPL_CHANNELMODEIS numeric 728 - /// </summary> 729 - /// <param name="line"></param> 730 - /// <returns></returns> 731 - private Emit HandleChannelModeIs(Line line) 692 + var target = line.Params[0]; 693 + var statusMsg = new List<string>(); 694 + while (target.Length > 0) 732 695 { 733 - var emit = new Emit(); 734 - if (HasChannel(line.Params[1])) 696 + var t = target[0].ToString(CultureInfo.InvariantCulture); 697 + if (ISupport.StatusMsg.Contains(t)) 735 698 { 736 - var channel = GetChannel(line.Params[1]); 737 - emit.Channel = channel; 738 - var modes = line.Params[2] 739 - .TrimStart('+') 740 - .Select(p => (true, p.ToString(CultureInfo.InvariantCulture))); 741 - var parameters = line.Params.Skip(3).ToList(); 742 - SetChannelModes(channel, modes, parameters); 699 + statusMsg.Add(t); 700 + target = target.Substring(1); 743 701 } 702 + else 703 + { 704 + break; 705 + } 706 + } 744 707 745 - return emit; 708 + emit.Target = line.Params[0]; 709 + 710 + if (IsChannel(target) && HasChannel(target)) 711 + { 712 + emit.Channel = GetChannel(target); 713 + } 714 + else if (IsMe(target)) 715 + { 716 + emit.SelfTarget = true; 746 717 } 747 718 748 - /// <summary> 749 - /// Handles MODE command 750 - /// </summary> 751 - /// <param name="line"></param> 752 - /// <returns></returns> 753 - private Emit HandleMode(Line line) 719 + return emit; 720 + } 721 + 722 + /// <summary> 723 + /// Handles RPL_UMODEIS numeric 724 + /// </summary> 725 + /// <param name="line"></param> 726 + /// <returns></returns> 727 + private Emit HandleUModeIs(Line line) 728 + { 729 + foreach (var c in line.Params[1] 730 + .TrimStart('+') 731 + .Select(m => m.ToString(CultureInfo.InvariantCulture)) 732 + .Where(m => !Modes.Contains(m))) 733 + Modes.Add(c); 734 + 735 + return new Emit(); 736 + } 737 + 738 + /// <summary> 739 + /// Handles RPL_CHANNELMODEIS numeric 740 + /// </summary> 741 + /// <param name="line"></param> 742 + /// <returns></returns> 743 + private Emit HandleChannelModeIs(Line line) 744 + { 745 + var emit = new Emit(); 746 + if (HasChannel(line.Params[1])) 754 747 { 755 - var emit = new Emit(); 756 - var target = line.Params[0]; 757 - var modeString = line.Params[1]; 758 - var parameters = line.Params.Skip(2).ToList(); 748 + var channel = GetChannel(line.Params[1]); 749 + emit.Channel = channel; 750 + var modes = line.Params[2] 751 + .TrimStart('+') 752 + .Select(p => (true, p.ToString(CultureInfo.InvariantCulture))); 753 + var parameters = line.Params.Skip(3).ToList(); 754 + SetChannelModes(channel, modes, parameters); 755 + } 759 756 760 - var modifier = '+'; 761 - var modes = new List<(bool, string)>(); 762 - var tokens = new List<string>(); 757 + return emit; 758 + } 763 759 764 - foreach (var c in modeString) 765 - if (new[] {'+', '-'}.Contains(c)) 766 - { 767 - modifier = c; 768 - } 769 - else 770 - { 771 - modes.Add((modifier == '+', c.ToString(CultureInfo.InvariantCulture))); 772 - tokens.Add($"{modifier}{c}"); 773 - } 760 + /// <summary> 761 + /// Handles MODE command 762 + /// </summary> 763 + /// <param name="line"></param> 764 + /// <returns></returns> 765 + private Emit HandleMode(Line line) 766 + { 767 + var emit = new Emit(); 768 + var target = line.Params[0]; 769 + var modeString = line.Params[1]; 770 + var parameters = line.Params.Skip(2).ToList(); 774 771 775 - emit.Tokens = tokens; 772 + var modifier = '+'; 773 + var modes = new List<(bool, string)>(); 774 + var tokens = new List<string>(); 776 775 777 - if (IsMe(target)) 776 + foreach (var c in modeString) 777 + if (new[] { '+', '-' }.Contains(c)) 778 778 { 779 - emit.SelfTarget = true; 780 - foreach (var (add, c) in modes) 781 - if (add && !Modes.Contains(c)) 782 - Modes.Add(c); 783 - else if (Modes.Contains(c)) Modes.Remove(c); 779 + modifier = c; 784 780 } 785 - else if (HasChannel(target)) 781 + else 786 782 { 787 - var channel = GetChannel(CaseFold(target)); 788 - emit.Channel = channel; 789 - SetChannelModes(channel, modes, parameters); 783 + modes.Add((modifier == '+', c.ToString(CultureInfo.InvariantCulture))); 784 + tokens.Add($"{modifier}{c}"); 790 785 } 791 786 792 - return emit; 793 - } 787 + emit.Tokens = tokens; 794 788 795 - /// <summary> 796 - /// Handles RPL_TOPICWHOTIME numeric 797 - /// </summary> 798 - /// <param name="line"></param> 799 - /// <returns></returns> 800 - private Emit HandleTopicTime(Line line) 789 + if (IsMe(target)) 801 790 { 802 - var emit = new Emit(); 803 - if (HasChannel(line.Params[1])) 791 + emit.SelfTarget = true; 792 + foreach (var (add, c) in modes) 804 793 { 805 - var channel = GetChannel(line.Params[1]); 806 - emit.Channel = channel; 807 - channel.TopicSetter = line.Params[2]; 808 - channel.TopicTime = DateTimeOffset 809 - .FromUnixTimeSeconds(int.Parse(line.Params[3], CultureInfo.InvariantCulture)).DateTime; 794 + if (add && !Modes.Contains(c)) 795 + { 796 + Modes.Add(c); 797 + } 798 + else if (Modes.Contains(c)) 799 + { 800 + Modes.Remove(c); 801 + } 810 802 } 803 + } 804 + else if (HasChannel(target)) 805 + { 806 + var channel = GetChannel(CaseFold(target)); 807 + emit.Channel = channel; 808 + SetChannelModes(channel, modes, parameters); 809 + } 810 + 811 + return emit; 812 + } 811 813 812 - return emit; 814 + /// <summary> 815 + /// Handles RPL_TOPICWHOTIME numeric 816 + /// </summary> 817 + /// <param name="line"></param> 818 + /// <returns></returns> 819 + private Emit HandleTopicTime(Line line) 820 + { 821 + var emit = new Emit(); 822 + if (HasChannel(line.Params[1])) 823 + { 824 + var channel = GetChannel(line.Params[1]); 825 + emit.Channel = channel; 826 + channel.TopicSetter = line.Params[2]; 827 + channel.TopicTime = DateTimeOffset 828 + .FromUnixTimeSeconds(int.Parse(line.Params[3], CultureInfo.InvariantCulture)).DateTime; 813 829 } 814 830 815 - /// <summary> 816 - /// Handles RPL_TOPIC numeric 817 - /// </summary> 818 - /// <param name="line"></param> 819 - /// <returns></returns> 820 - private Emit HandleTopicNumeric(Line line) 831 + return emit; 832 + } 833 + 834 + /// <summary> 835 + /// Handles RPL_TOPIC numeric 836 + /// </summary> 837 + /// <param name="line"></param> 838 + /// <returns></returns> 839 + private Emit HandleTopicNumeric(Line line) 840 + { 841 + var emit = new Emit(); 842 + if (HasChannel(line.Params[1])) 821 843 { 822 - var emit = new Emit(); 823 - if (HasChannel(line.Params[1])) 824 - { 825 - var channel = GetChannel(line.Params[1]); 826 - emit.Channel = channel; 827 - channel.Topic = line.Params[2]; 828 - } 844 + var channel = GetChannel(line.Params[1]); 845 + emit.Channel = channel; 846 + channel.Topic = line.Params[2]; 847 + } 848 + 849 + return emit; 850 + } 829 851 830 - return emit; 852 + /// <summary> 853 + /// Handles TOPIC command 854 + /// </summary> 855 + /// <param name="line"></param> 856 + /// <returns></returns> 857 + private Emit HandleTopic(Line line) 858 + { 859 + var emit = new Emit(); 860 + if (HasChannel(line.Params[0])) 861 + { 862 + var channel = GetChannel(line.Params[0]); 863 + emit.Channel = channel; 864 + channel.Topic = line.Params[1]; 865 + channel.TopicSetter = line.Hostmask.ToString(); 866 + channel.TopicTime = DateTime.UtcNow; 831 867 } 832 868 833 - /// <summary> 834 - /// Handles TOPIC command 835 - /// </summary> 836 - /// <param name="line"></param> 837 - /// <returns></returns> 838 - private Emit HandleTopic(Line line) 869 + return emit; 870 + } 871 + 872 + /// <summary> 873 + /// Handles RPL_CREATIONTIME numeric 874 + /// </summary> 875 + /// <param name="line"></param> 876 + /// <returns></returns> 877 + private Emit HandleCreationTime(Line line) 878 + { 879 + var emit = new Emit(); 880 + if (HasChannel(line.Params[1])) 839 881 { 840 - var emit = new Emit(); 841 - if (HasChannel(line.Params[0])) 842 - { 843 - var channel = GetChannel(line.Params[0]); 844 - emit.Channel = channel; 845 - channel.Topic = line.Params[1]; 846 - channel.TopicSetter = line.Hostmask.ToString(); 847 - channel.TopicTime = DateTime.UtcNow; 848 - } 849 - 850 - return emit; 882 + var channel = GetChannel(line.Params[1]); 883 + emit.Channel = channel; 884 + channel.Created = DateTimeOffset 885 + .FromUnixTimeSeconds(int.Parse(line.Params[2], CultureInfo.InvariantCulture)).DateTime; 851 886 } 852 887 853 - /// <summary> 854 - /// Handles RPL_CREATIONTIME numeric 855 - /// </summary> 856 - /// <param name="line"></param> 857 - /// <returns></returns> 858 - private Emit HandleCreationTime(Line line) 888 + return emit; 889 + } 890 + 891 + /// <summary> 892 + /// Handles RPL_NAMREPLY numeric 893 + /// </summary> 894 + /// <param name="line"></param> 895 + /// <returns></returns> 896 + private Emit HandleNames(Line line) 897 + { 898 + var emit = new Emit(); 899 + if (!HasChannel(line.Params[2])) 859 900 { 860 - var emit = new Emit(); 861 - if (HasChannel(line.Params[1])) 862 - { 863 - var channel = GetChannel(line.Params[1]); 864 - emit.Channel = channel; 865 - channel.Created = DateTimeOffset 866 - .FromUnixTimeSeconds(int.Parse(line.Params[2], CultureInfo.InvariantCulture)).DateTime; 867 - } 868 - 869 901 return emit; 870 902 } 871 903 872 - /// <summary> 873 - /// Handles RPL_NAMREPLY numeric 874 - /// </summary> 875 - /// <param name="line"></param> 876 - /// <returns></returns> 877 - private Emit HandleNames(Line line) 904 + var channel = GetChannel(line.Params[2]); 905 + emit.Channel = channel; 906 + var nicknames = line.Params[3].Split([' '], StringSplitOptions.RemoveEmptyEntries); 907 + var users = new List<User>(); 908 + emit.Users = users; 909 + 910 + foreach (var nick in nicknames) 878 911 { 879 - var emit = new Emit(); 880 - if (!HasChannel(line.Params[2])) return emit; 912 + var modes = nick.Select(c => ISupport.Prefix.FromPrefix(c)).TakeWhile(m => m != null).ToList(); 913 + var hostmask = new Hostmask(nick.Substring(modes.Count)); 914 + var user = GetUser(hostmask.NickName) ?? AddUser(hostmask.NickName); 881 915 882 - var channel = GetChannel(line.Params[2]); 883 - emit.Channel = channel; 884 - var nicknames = line.Params[3].Split(' ', StringSplitOptions.RemoveEmptyEntries); 885 - var users = new List<User>(); 886 - emit.Users = users; 916 + users.Add(user); 917 + var channelUser = UserJoin(channel, user); 887 918 888 - foreach (var nick in nicknames) 919 + if (hostmask.UserName != null) 889 920 { 890 - var modes = ""; 891 - foreach (var c in nick) 892 - { 893 - var mode = ISupport.Prefix.FromPrefix(c); 894 - if (mode != null) 895 - modes += mode; 896 - else 897 - break; 898 - } 921 + user.UserName = hostmask.UserName; 922 + } 899 923 900 - var hostmask = new Hostmask(nick[modes.Length..]); 901 - var user = GetUser(hostmask.NickName) ?? AddUser(hostmask.NickName); 924 + if (hostmask.HostName != null) 925 + { 926 + user.HostName = hostmask.HostName; 927 + } 902 928 903 - users.Add(user); 904 - var channelUser = UserJoin(channel, user); 929 + if (IsMe(hostmask.NickName)) 930 + { 931 + SelfHostmask(hostmask); 932 + } 905 933 906 - if (hostmask.UserName != null) user.UserName = hostmask.UserName; 907 - if (hostmask.HostName != null) user.HostName = hostmask.HostName; 934 + channelUser.Modes.AddRange( 935 + modes.Select(c => c.ToString(CultureInfo.InvariantCulture)).Except(channelUser.Modes)); 936 + } 908 937 909 - if (IsMe(hostmask.NickName)) SelfHostmask(hostmask); 938 + return emit; 939 + } 910 940 911 - foreach (var mode in modes.Select(c => c.ToString(CultureInfo.InvariantCulture))) 912 - if (!channelUser.Modes.Contains(mode)) 913 - channelUser.Modes.Add(mode); 914 - } 941 + /// <summary> 942 + /// Handles ERROR command 943 + /// </summary> 944 + /// <param name="line"></param> 945 + /// <returns></returns> 946 + private Emit HandleError(Line line) 947 + { 948 + Users.Clear(); 949 + Channels.Clear(); 950 + return new Emit(); 951 + } 915 952 916 - return emit; 953 + /// <summary> 954 + /// Handles QUIT command 955 + /// </summary> 956 + /// <param name="line"></param> 957 + /// <returns></returns> 958 + private Emit HandleQuit(Line line) 959 + { 960 + var emit = new Emit(); 961 + var nick = line.Hostmask.NickName; 962 + if (line.Params.Any()) 963 + { 964 + emit.Text = line.Params[0]; 917 965 } 918 966 919 - /// <summary> 920 - /// Handles ERROR command 921 - /// </summary> 922 - /// <param name="line"></param> 923 - /// <returns></returns> 924 - private Emit HandleError(Line line) 967 + if (IsMe(nick) || line.Source == null) 925 968 { 969 + emit.Self = true; 926 970 Users.Clear(); 927 971 Channels.Clear(); 928 - return new Emit(); 972 + } 973 + else if (HasUser(nick)) 974 + { 975 + var user = GetUser(nick); 976 + Users.Remove(user.NickNameLower); 977 + emit.User = user; 978 + foreach (var channel in user.Channels.Select(c => Channels[c])) 979 + { 980 + channel.Users.Remove(user.NickNameLower); 981 + } 929 982 } 930 983 931 - /// <summary> 932 - /// Handles QUIT command 933 - /// </summary> 934 - /// <param name="line"></param> 935 - /// <returns></returns> 936 - private Emit HandleQuit(Line line) 937 - { 938 - var emit = new Emit(); 939 - var nick = line.Hostmask.NickName; 940 - if (line.Params.Any()) emit.Text = line.Params[0]; 984 + return emit; 985 + } 941 986 942 - if (IsMe(nick) || line.Source == null) 987 + /// <summary> 988 + /// Handles RPL_LOGGEDOUT numeric 989 + /// </summary> 990 + /// <param name="line"></param> 991 + /// <returns></returns> 992 + private Emit HandleLoggedOut(Line line) 993 + { 994 + Account = null; 995 + SelfHostmask(line.Params[1]); 996 + return new Emit(); 997 + } 998 + 999 + /// <summary> 1000 + /// Handles KICK command 1001 + /// </summary> 1002 + /// <param name="line"></param> 1003 + /// <returns></returns> 1004 + private Emit HandleKick(Line line) 1005 + { 1006 + var (emit, kicked) = UserPart(line, line.Params[1], line.Params[0], 2); 1007 + if (kicked != null) 1008 + { 1009 + emit.UserTarget = kicked; 1010 + if (IsMe(kicked.NickName)) 943 1011 { 944 1012 emit.Self = true; 945 - Users.Clear(); 946 - Channels.Clear(); 947 1013 } 948 - else if (HasUser(nick)) 1014 + 1015 + var kicker = line.Hostmask.NickName; 1016 + if (IsMe(kicker)) 949 1017 { 950 - var user = GetUser(nick); 951 - Users.Remove(user.NickNameLower); 952 - emit.User = user; 953 - foreach (var channel in user.Channels.Select(c => Channels[c])) 954 - channel.Users.Remove(user.NickNameLower); 1018 + emit.SelfSource = true; 955 1019 } 956 1020 957 - return emit; 1021 + emit.UserSource = GetUser(kicker) ?? CreateUser(kicker); 958 1022 } 959 1023 960 - /// <summary> 961 - /// Handles RPL_LOGGEDOUT numeric 962 - /// </summary> 963 - /// <param name="line"></param> 964 - /// <returns></returns> 965 - private Emit HandleLoggedOut(Line line) 1024 + return emit; 1025 + } 1026 + 1027 + /// <summary> 1028 + /// Handles PART command 1029 + /// </summary> 1030 + /// <param name="line"></param> 1031 + /// <returns></returns> 1032 + private Emit HandlePart(Line line) 1033 + { 1034 + var (emit, user) = UserPart(line, line.Hostmask.NickName, line.Params[0], 1); 1035 + if (user != null) 966 1036 { 967 - Account = null; 968 - SelfHostmask(line.Params[1]); 969 - return new Emit(); 1037 + emit.User = user; 1038 + emit.Self = IsMe(user.NickName); 970 1039 } 971 1040 972 - /// <summary> 973 - /// Handles KICK command 974 - /// </summary> 975 - /// <param name="line"></param> 976 - /// <returns></returns> 977 - private Emit HandleKick(Line line) 978 - { 979 - var (emit, kicked) = UserPart(line, line.Params[1], line.Params[0], 2); 980 - if (kicked != null) 981 - { 982 - emit.UserTarget = kicked; 983 - if (IsMe(kicked.NickName)) emit.Self = true; 984 - 985 - var kicker = line.Hostmask.NickName; 986 - if (IsMe(kicker)) emit.SelfSource = true; 1041 + return emit; 1042 + } 987 1043 988 - emit.UserSource = GetUser(kicker) ?? CreateUser(kicker); 989 - } 1044 + /// <summary> 1045 + /// Handles JOIN command 1046 + /// </summary> 1047 + /// <param name="line"></param> 1048 + /// <returns></returns> 1049 + private Emit HandleJoin(Line line) 1050 + { 1051 + var extended = line.Params.Count == 3; 1052 + var account = extended ? line.Params[1].Trim('*') : null; 1053 + var realname = extended ? line.Params[2] : null; 1054 + var emit = new Emit(); 990 1055 991 - return emit; 992 - } 1056 + var channelName = line.Params[0]; 1057 + var nick = line.Hostmask.NickName; 993 1058 994 - /// <summary> 995 - /// Handles PART command 996 - /// </summary> 997 - /// <param name="line"></param> 998 - /// <returns></returns> 999 - private Emit HandlePart(Line line) 1059 + // handle own join 1060 + if (IsMe(nick)) 1000 1061 { 1001 - var (emit, user) = UserPart(line, line.Hostmask.NickName, line.Params[0], 1); 1002 - if (user != null) 1062 + emit.Self = true; 1063 + if (!HasChannel(channelName)) 1003 1064 { 1004 - emit.User = user; 1005 - emit.Self = IsMe(user.NickName); 1065 + var channel = new Channel(); 1066 + channel.SetName(channelName, CaseFold(channelName)); 1067 + Channels[CaseFold(channelName)] = channel; 1006 1068 } 1007 1069 1008 - return emit; 1070 + SelfHostmask(line.Hostmask); 1071 + if (extended) 1072 + { 1073 + Account = account; 1074 + RealName = realname; 1075 + } 1009 1076 } 1010 1077 1011 - /// <summary> 1012 - /// Handles JOIN command 1013 - /// </summary> 1014 - /// <param name="line"></param> 1015 - /// <returns></returns> 1016 - private Emit HandleJoin(Line line) 1078 + if (HasChannel(channelName)) 1017 1079 { 1018 - var extended = line.Params.Count == 3; 1019 - var account = extended ? line.Params[1].Trim('*') : null; 1020 - var realname = extended ? line.Params[2] : null; 1021 - var emit = new Emit(); 1080 + var channel = GetChannel(channelName); 1081 + emit.Channel = channel; 1022 1082 1023 - var channelName = line.Params[0]; 1024 - var nick = line.Hostmask.NickName; 1083 + if (!HasUser(nick)) 1084 + { 1085 + AddUser(nick); 1086 + } 1025 1087 1026 - // handle own join 1027 - if (IsMe(nick)) 1088 + var user = GetUser(nick); 1089 + emit.User = user; 1090 + if (line.Hostmask.UserName != null) 1028 1091 { 1029 - emit.Self = true; 1030 - if (!HasChannel(channelName)) 1031 - { 1032 - var channel = new Channel(); 1033 - channel.SetName(channelName, CaseFold(channelName)); 1034 - Channels[CaseFold(channelName)] = channel; 1035 - } 1092 + user.UserName = line.Hostmask.UserName; 1093 + } 1036 1094 1037 - SelfHostmask(line.Hostmask); 1038 - if (extended) 1039 - { 1040 - Account = account; 1041 - RealName = realname; 1042 - } 1095 + if (line.Hostmask.HostName != null) 1096 + { 1097 + user.HostName = line.Hostmask.HostName; 1043 1098 } 1044 1099 1045 - if (HasChannel(channelName)) 1100 + if (extended) 1046 1101 { 1047 - var channel = GetChannel(channelName); 1048 - emit.Channel = channel; 1049 - 1050 - if (!HasUser(nick)) AddUser(nick); 1051 - 1052 - var user = GetUser(nick); 1053 - emit.User = user; 1054 - if (line.Hostmask.UserName != null) user.UserName = line.Hostmask.UserName; 1055 - if (line.Hostmask.HostName != null) user.HostName = line.Hostmask.HostName; 1056 - if (extended) 1057 - { 1058 - user.Account = account; 1059 - user.RealName = realname; 1060 - } 1061 - 1062 - UserJoin(channel, user); 1102 + user.Account = account; 1103 + user.RealName = realname; 1063 1104 } 1064 1105 1065 - return emit; 1106 + UserJoin(channel, user); 1066 1107 } 1067 1108 1068 - /// <summary> 1069 - /// Handles NICK command 1070 - /// </summary> 1071 - /// <param name="line"></param> 1072 - /// <returns></returns> 1073 - private Emit HandleNick(Line line) 1074 - { 1075 - var newNick = line.Params[0]; 1076 - var oldNick = line.Hostmask.NickName; 1109 + return emit; 1110 + } 1077 1111 1078 - var emit = new Emit(); 1112 + /// <summary> 1113 + /// Handles NICK command 1114 + /// </summary> 1115 + /// <param name="line"></param> 1116 + /// <returns></returns> 1117 + private Emit HandleNick(Line line) 1118 + { 1119 + var newNick = line.Params[0]; 1120 + var oldNick = line.Hostmask.NickName; 1079 1121 1080 - if (HasUser(oldNick)) 1081 - { 1082 - var user = GetUser(oldNick); 1083 - var oldNickLower = user.NickNameLower; 1084 - var newNickLower = CaseFold(newNick); 1122 + var emit = new Emit(); 1085 1123 1086 - emit.User = user; 1087 - Users.Remove(oldNickLower); 1088 - Users[newNickLower] = user; 1089 - user.SetNickName(newNick, newNickLower); 1124 + if (HasUser(oldNick)) 1125 + { 1126 + var user = GetUser(oldNick); 1127 + var oldNickLower = user.NickNameLower; 1128 + var newNickLower = CaseFold(newNick); 1090 1129 1091 - foreach (var channelLower in user.Channels) 1092 - { 1093 - var channel = GetChannel(channelLower); 1094 - var channelUser = channel.Users[oldNickLower]; 1095 - channel.Users.Remove(oldNickLower); 1096 - channel.Users[newNickLower] = channelUser; 1097 - } 1098 - } 1130 + emit.User = user; 1131 + Users.Remove(oldNickLower); 1132 + Users[newNickLower] = user; 1133 + user.SetNickName(newNick, newNickLower); 1099 1134 1100 - if (IsMe(oldNick)) 1135 + foreach (var channelLower in user.Channels) 1101 1136 { 1102 - emit.Self = true; 1103 - NickName = newNick; 1104 - NickNameLower = CaseFold(newNick); 1137 + var channel = GetChannel(channelLower); 1138 + var channelUser = channel.Users[oldNickLower]; 1139 + channel.Users.Remove(oldNickLower); 1140 + channel.Users[newNickLower] = channelUser; 1105 1141 } 1106 - 1107 - return emit; 1108 1142 } 1109 1143 1110 - /// <summary> 1111 - /// Handles RPL_MOTDSTART and RPL_MOTD numerics 1112 - /// </summary> 1113 - /// <param name="line"></param> 1114 - /// <returns></returns> 1115 - private Emit HandleMotd(Line line) 1144 + if (IsMe(oldNick)) 1116 1145 { 1117 - if (line.Command == Numeric.RPL_MOTDSTART) Motd.Clear(); 1118 - 1119 - var emit = new Emit {Text = line.Params[1]}; 1120 - Motd.Add(line.Params[1]); 1121 - return emit; 1146 + emit.Self = true; 1147 + NickName = newNick; 1148 + NickNameLower = CaseFold(newNick); 1122 1149 } 1123 1150 1124 - /// <summary> 1125 - /// Handles RPL_ISUPPORT numeric 1126 - /// </summary> 1127 - /// <param name="line"></param> 1128 - /// <returns></returns> 1129 - private Emit HandleISupport(Line line) 1151 + return emit; 1152 + } 1153 + 1154 + /// <summary> 1155 + /// Handles RPL_MOTDSTART and RPL_MOTD numerics 1156 + /// </summary> 1157 + /// <param name="line"></param> 1158 + /// <returns></returns> 1159 + private Emit HandleMotd(Line line) 1160 + { 1161 + if (line.Command == Numeric.RPL_MOTDSTART) 1130 1162 { 1131 - ISupport = new ISupport(); 1132 - ISupport.Parse(line.Params); 1133 - return new Emit(); 1163 + Motd.Clear(); 1134 1164 } 1135 1165 1136 - /// <summary> 1137 - /// Handles RPL_WELCOME numeric 1138 - /// </summary> 1139 - /// <param name="line"></param> 1140 - /// <returns></returns> 1141 - private Emit HandleWelcome(Line line) 1142 - { 1143 - NickName = line.Params[0]; 1144 - NickNameLower = CaseFold(line.Params[0]); 1145 - Registered = true; 1146 - return new Emit(); 1147 - } 1166 + var emit = new Emit { Text = line.Params[1] }; 1167 + Motd.Add(line.Params[1]); 1168 + return emit; 1169 + } 1170 + 1171 + /// <summary> 1172 + /// Handles RPL_ISUPPORT numeric 1173 + /// </summary> 1174 + /// <param name="line"></param> 1175 + /// <returns></returns> 1176 + private Emit HandleISupport(Line line) 1177 + { 1178 + ISupport = new ISupport(); 1179 + ISupport.Parse(line.Params); 1180 + return new Emit(); 1181 + } 1182 + 1183 + /// <summary> 1184 + /// Handles RPL_WELCOME numeric 1185 + /// </summary> 1186 + /// <param name="line"></param> 1187 + /// <returns></returns> 1188 + private Emit HandleWelcome(Line line) 1189 + { 1190 + NickName = line.Params[0]; 1191 + NickNameLower = CaseFold(line.Params[0]); 1192 + Registered = true; 1193 + return new Emit(); 1148 1194 } 1149 1195 }
+9 -12
IRCStates/ServerDisconnectedException.cs
··· 1 - using System; 1 + namespace IRCStates; 2 2 3 - namespace IRCStates 3 + public class ServerDisconnectedException : Exception 4 4 { 5 - public class ServerDisconnectedException : Exception 5 + public ServerDisconnectedException(string message) : base(message) 6 6 { 7 - public ServerDisconnectedException(string message) : base(message) 8 - { 9 - } 7 + } 10 8 11 - public ServerDisconnectedException(string message, Exception innerException) : base(message, innerException) 12 - { 13 - } 9 + public ServerDisconnectedException(string message, Exception innerException) : base(message, innerException) 10 + { 11 + } 14 12 15 - public ServerDisconnectedException() 16 - { 17 - } 13 + public ServerDisconnectedException() 14 + { 18 15 } 19 16 }
+15 -21
IRCStates/User.cs
··· 1 - using System.Collections.Generic; 1 + namespace IRCStates; 2 2 3 - namespace IRCStates 3 + public class User 4 4 { 5 - public class User 6 - { 7 - public string NickName { get; private set; } 8 - public string NickNameLower { get; private set; } 5 + public string NickName { get; private set; } 6 + public string NickNameLower { get; private set; } 9 7 10 - public string UserName { get; set; } 11 - public string HostName { get; set; } 12 - public string RealName { get; set; } 13 - public string Account { get; set; } 14 - public string Away { get; set; } 15 - public HashSet<string> Channels { get; private set; } = new HashSet<string>(); 8 + public string UserName { get; set; } 9 + public string HostName { get; set; } 10 + public string RealName { get; set; } 11 + public string Account { get; set; } 12 + public string Away { get; set; } 13 + public HashSet<string> Channels { get; private set; } = []; 16 14 17 - public override string ToString() 18 - { 19 - return $"User(nickname={NickName})"; 20 - } 15 + public override string ToString() => $"User(nickname={NickName})"; 21 16 22 - public void SetNickName(string nick, string nickLower) 23 - { 24 - NickName = nick; 25 - NickNameLower = nickLower; 26 - } 17 + public void SetNickName(string nick, string nickLower) 18 + { 19 + NickName = nick; 20 + NickNameLower = nickLower; 27 21 } 28 22 }
+92 -42
IRCTokens/EnumerableExtensions.cs
··· 1 - using System; 2 - using System.Collections.Generic; 3 - using System.Linq; 1 + namespace IRCTokens; 4 2 5 - namespace IRCTokens 3 + public static class EnumerableExtensions 6 4 { 7 - public static class EnumerableExtensions 5 + public static IEnumerable<byte[]> Split(this byte[] bytes, byte separator) 8 6 { 9 - public static IEnumerable<byte[]> Split(this byte[] bytes, byte separator) 7 + if (bytes == null || bytes.Length == 0) 10 8 { 11 - if (bytes == null || bytes.Length == 0) return new List<byte[]>(); 9 + return []; 10 + } 12 11 13 - var newLineIndices = bytes.Select((b, i) => b == separator ? i : -1).Where(i => i != -1).ToArray(); 14 - var lines = new byte[newLineIndices.Length + 1][]; 15 - var currentIndex = 0; 16 - var arrIndex = 0; 12 + var newLineIndices = bytes.Select((b, i) => b == separator ? i : -1).Where(i => i != -1).ToArray(); 13 + var lines = new byte[newLineIndices.Length + 1][]; 14 + var currentIndex = 0; 15 + var arrIndex = 0; 17 16 18 - for (var i = 0; i < newLineIndices.Length && currentIndex < bytes.Length; ++i) 19 - { 20 - var n = new byte[newLineIndices[i] - currentIndex]; 21 - Array.Copy(bytes, currentIndex, n, 0, newLineIndices[i] - currentIndex); 22 - currentIndex = newLineIndices[i] + 1; 23 - lines[arrIndex++] = n; 24 - } 17 + for (var i = 0; i < newLineIndices.Length && currentIndex < bytes.Length; ++i) 18 + { 19 + var n = new byte[newLineIndices[i] - currentIndex]; 20 + Array.Copy(bytes, currentIndex, n, 0, newLineIndices[i] - currentIndex); 21 + currentIndex = newLineIndices[i] + 1; 22 + lines[arrIndex++] = n; 23 + } 24 + 25 + // Handle the last string at the end of the array if there is one. 26 + if (currentIndex < bytes.Length) 27 + { 28 + lines[arrIndex] = bytes.Skip(currentIndex).ToArray(); 29 + } 30 + // We had a separator character at the end of a string. Rather than just allowing 31 + // a null character, we'll replace the last element in the array with an empty string. 32 + else if (arrIndex == newLineIndices.Length) 33 + { 34 + lines[arrIndex] = []; 35 + } 25 36 26 - // Handle the last string at the end of the array if there is one. 27 - if (currentIndex < bytes.Length) 28 - lines[arrIndex] = bytes.Skip(currentIndex).ToArray(); 29 - else if (arrIndex == newLineIndices.Length) 30 - // We had a separator character at the end of a string. Rather than just allowing 31 - // a null character, we'll replace the last element in the array with an empty string. 32 - lines[arrIndex] = Array.Empty<byte>(); 37 + return lines.ToArray(); 38 + } 33 39 34 - return lines.ToArray(); 40 + public static byte[] Trim(this IEnumerable<byte> bytes, byte separator) 41 + { 42 + if (bytes == null) 43 + { 44 + return []; 35 45 } 36 46 37 - public static byte[] Trim(this IEnumerable<byte> bytes, byte separator) 47 + var byteList = new List<byte>(bytes); 48 + var i = 0; 49 + 50 + if (!byteList.Any()) 38 51 { 39 - if (bytes == null) return Array.Empty<byte>(); 52 + return byteList.ToArray(); 53 + } 54 + 55 + while (byteList[i] == separator) 56 + { 57 + byteList.RemoveAt(i); 58 + i++; 59 + } 60 + 61 + i = byteList.Count - 1; 62 + while (byteList[i] == separator) 63 + { 64 + byteList.RemoveAt(i); 65 + i--; 66 + } 67 + 68 + return byteList.ToArray(); 69 + } 70 + 71 + #if !(REFERENCE_ASSEMBLY && (NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER)) 72 + /// <summary> 73 + /// Bypasses a specified number of contiguous elements from the end of the sequence and returns the remaining elements. 74 + /// Backported from <a href="https://github.com/dotnet/reactive/blob/ebab5fc37e8c0888f7b96107852a8794e9af1735/Ix.NET/Source/System.Interactive/System/Linq/Operators/SkipLast.cs">netcore</a> 75 + /// </summary> 76 + /// <typeparam name="TSource">Source sequence element type.</typeparam> 77 + /// <param name="source">Source sequence.</param> 78 + /// <param name="count"> 79 + /// The number of elements to skip from the end of the sequence before returning the remaining elements. 80 + /// </param> 81 + /// <returns>Sequence bypassing the specified number of elements counting from the end of the source sequence.</returns> 82 + public static IEnumerable<TSource> SkipLast<TSource>(this IEnumerable<TSource> source, int count) 83 + { 84 + if (source == null) 85 + { 86 + throw new ArgumentNullException(nameof(source)); 87 + } 88 + 89 + if (count < 0) 90 + { 91 + throw new ArgumentOutOfRangeException(nameof(count)); 92 + } 40 93 41 - var byteList = new List<byte>(bytes); 42 - var i = 0; 94 + return SkipLastCore(source, count); 95 + } 43 96 44 - if (!byteList.Any()) return byteList.ToArray(); 97 + private static IEnumerable<TSource> SkipLastCore<TSource>(this IEnumerable<TSource> source, int count) 98 + { 99 + var q = new Queue<TSource>(); 45 100 46 - while (byteList[i] == separator) 47 - { 48 - byteList.RemoveAt(i); 49 - i++; 50 - } 101 + foreach (var x in source) 102 + { 103 + q.Enqueue(x); 51 104 52 - i = byteList.Count - 1; 53 - while (byteList[i] == separator) 105 + if (q.Count > count) 54 106 { 55 - byteList.RemoveAt(i); 56 - i--; 107 + yield return q.Dequeue(); 57 108 } 58 - 59 - return byteList.ToArray(); 60 109 } 61 110 } 111 + #endif 62 112 }
+36 -44
IRCTokens/Hostmask.cs
··· 1 - using System; 1 + namespace IRCTokens; 2 2 3 - namespace IRCTokens 3 + /// <summary>Represents the three parts of a hostmask. Parse with the constructor.</summary> 4 + public class Hostmask : IEquatable<Hostmask> 4 5 { 5 - /// <summary> 6 - /// Represents the three parts of a hostmask. Parse with the constructor. 7 - /// </summary> 8 - public class Hostmask : IEquatable<Hostmask> 9 - { 10 - private readonly string _source; 6 + private readonly string _source; 11 7 12 - public Hostmask(string source) 8 + public Hostmask(string source) 9 + { 10 + if (source == null) 13 11 { 14 - if (source == null) return; 15 - 16 - _source = source; 17 - 18 - if (source.Contains('@', StringComparison.Ordinal)) 19 - { 20 - var split = source.Split('@'); 21 - 22 - NickName = split[0]; 23 - HostName = split[1]; 24 - } 25 - else 26 - { 27 - NickName = source; 28 - } 29 - 30 - if (NickName.Contains('!', StringComparison.Ordinal)) 31 - { 32 - var userSplit = NickName.Split('!'); 33 - NickName = userSplit[0]; 34 - UserName = userSplit[1]; 35 - } 12 + return; 36 13 } 37 14 38 - public string NickName { get; set; } 39 - public string UserName { get; set; } 40 - public string HostName { get; set; } 15 + _source = source; 41 16 42 - public bool Equals(Hostmask other) 17 + if (source.Contains('@')) 43 18 { 44 - if (other == null) return false; 19 + var split = source.Split('@'); 45 20 46 - return _source == other._source; 21 + NickName = split[0]; 22 + HostName = split[1]; 47 23 } 48 - 49 - public override string ToString() 24 + else 50 25 { 51 - return _source; 26 + NickName = source; 52 27 } 53 28 54 - public override int GetHashCode() 29 + if (NickName.Contains('!')) 55 30 { 56 - return _source.GetHashCode(StringComparison.Ordinal); 31 + var userSplit = NickName.Split('!'); 32 + NickName = userSplit[0]; 33 + UserName = userSplit[1]; 57 34 } 35 + } 58 36 59 - public override bool Equals(object obj) 37 + public string NickName { get; set; } 38 + public string UserName { get; set; } 39 + public string HostName { get; set; } 40 + 41 + public bool Equals(Hostmask other) 42 + { 43 + if (other == null) 60 44 { 61 - return Equals(obj as Hostmask); 45 + return false; 62 46 } 47 + 48 + return _source == other._source; 63 49 } 50 + 51 + public override string ToString() => _source; 52 + 53 + public override int GetHashCode() => _source.GetHashCode(); 54 + 55 + public override bool Equals(object obj) => Equals(obj as Hostmask); 64 56 }
+4 -20
IRCTokens/IRCTokens.csproj
··· 1 - <Project Sdk="Microsoft.NET.Sdk"> 2 - 1 + <Project Sdk="Microsoft.NET.Sdk"> 3 2 <PropertyGroup> 4 - <TargetFramework>netstandard2.1</TargetFramework> 3 + <TargetFramework>netstandard2.0</TargetFramework> 5 4 <PackageId>IRCTokens</PackageId> 6 - <Version>1.4.0</Version> 7 - <Authors>Ben Harris</Authors> 8 - <Company>tildeverse.org</Company> 9 5 <IsPackable>true</IsPackable> 10 6 <GeneratePackageOnBuild>true</GeneratePackageOnBuild> 11 - <PackageProjectUrl>https://tildegit.org/irctokens/ircsharp</PackageProjectUrl> 12 - <PackageLicenseExpression>MIT</PackageLicenseExpression> 13 - <RepositoryUrl>https://tildegit.org/irctokens/ircsharp/src/branch/main/IRCTokens</RepositoryUrl> 14 - <PublishRepositoryUrl>true</PublishRepositoryUrl> 15 - <EmbedUntrackedSources>true</EmbedUntrackedSources> 16 - <RepositoryType>git</RepositoryType> 17 - <PackageTags>irc</PackageTags> 18 - <PackageVersion>1.4.0</PackageVersion> 19 - <PackageReadmeFile>README.md</PackageReadmeFile> 20 7 <DebugType>embedded</DebugType> 21 8 </PropertyGroup> 22 - 23 9 <ItemGroup> 24 - <None Include="README.md" Pack="true" PackagePath="\"/> 10 + <None Include="README.md" Pack="true" PackagePath="\" /> 25 11 </ItemGroup> 26 - 27 12 <ItemGroup> 28 - <PackageReference Include="Microsoft.SourceLink.Gitea" Version="8.0.0" PrivateAssets="all"/> 13 + <PackageReference Include="Microsoft.SourceLink.Gitea" PrivateAssets="all" /> 29 14 </ItemGroup> 30 - 31 15 </Project>
+182 -168
IRCTokens/Line.cs
··· 1 - using System; 2 - using System.Collections.Generic; 3 1 using System.Globalization; 4 - using System.Linq; 5 2 using System.Text; 3 + 4 + // ReSharper disable ReplaceWithFieldKeyword 6 5 // ReSharper disable CommentTypo 7 6 8 - namespace IRCTokens 7 + namespace IRCTokens; 8 + 9 + /// <summary>Tools to represent, parse, and format IRC lines</summary> 10 + public class Line : IEquatable<Line> 9 11 { 10 - /// <summary> 11 - /// Tools to represent, parse, and format IRC lines 12 - /// </summary> 13 - public class Line : IEquatable<Line> 14 - { 15 - private static readonly string[] TagUnescaped = {"\\", " ", ";", "\r", "\n"}; 12 + private static readonly string[] TagUnescaped = ["\\", " ", ";", "\r", "\n"]; 16 13 17 - private static readonly string[] TagEscaped = {@"\\", "\\s", "\\:", "\\r", "\\n"}; 14 + private static readonly string[] TagEscaped = [@"\\", "\\s", "\\:", "\\r", "\\n"]; 15 + private Hostmask _hostmask; 18 16 19 - private Hostmask _hostmask; 17 + public Line() 18 + { 19 + } 20 20 21 - public Line() 22 - { 23 - } 21 + public Line(string command, params string[] parameters) 22 + { 23 + Command = command.ToUpperInvariant(); 24 + Params = parameters.ToList(); 25 + } 24 26 25 - public Line(string command, params string[] parameters) 27 + /// <summary>Parse a <see cref="Line" /> from a string. Analogous to <c>irctokens.tokenise()</c>.</summary> 28 + /// <param name="line">irc line to parse</param> 29 + public Line(string line) 30 + { 31 + if (string.IsNullOrWhiteSpace(line)) 26 32 { 27 - Command = command.ToUpperInvariant(); 28 - Params = parameters.ToList(); 33 + throw new ArgumentNullException(nameof(line)); 29 34 } 30 35 31 - /// <summary> 32 - /// Build new <see cref="Line" /> object parsed from 33 - /// <param name="line">a string</param> 34 - /// . Analogous to irctokens.tokenise() 35 - /// </summary> 36 - /// <param name="line">irc line to parse</param> 37 - public Line(string line) 36 + string[] split; 37 + 38 + if (line.StartsWith("@")) 38 39 { 39 - if (string.IsNullOrWhiteSpace(line)) throw new ArgumentNullException(nameof(line)); 40 + Tags = []; 40 41 41 - string[] split; 42 + split = line.Split([' '], 2); 43 + var messageTags = split[0]; 44 + line = split[1]; 42 45 43 - if (line.StartsWith('@')) 46 + foreach (var part in messageTags.Substring(1).Split(';')) 44 47 { 45 - Tags = new Dictionary<string, string>(); 46 - 47 - split = line.Split(" ", 2); 48 - var messageTags = split[0]; 49 - line = split[1]; 50 - 51 - foreach (var part in messageTags[1..].Split(';')) 52 - if (part.Contains('=', StringComparison.Ordinal)) 53 - { 54 - split = part.Split('=', 2); 55 - Tags[split[0]] = UnescapeTag(split[1]); 56 - } 57 - else 58 - { 59 - Tags[part] = null; 60 - } 48 + if (part.Contains('=')) 49 + { 50 + split = part.Split(['='], 2); 51 + Tags[split[0]] = UnescapeTag(split[1]); 52 + } 53 + else 54 + { 55 + Tags[part] = null; 56 + } 61 57 } 58 + } 62 59 63 - string trailing; 64 - if (line.Contains(" :", StringComparison.Ordinal)) 65 - { 66 - split = line.Split(" :", 2); 67 - line = split[0]; 68 - trailing = split[1]; 69 - } 70 - else 71 - { 72 - trailing = null; 73 - } 60 + string trailing; 61 + if (line.Contains(" :")) 62 + { 63 + split = line.Split([" :"], 2, StringSplitOptions.None); 64 + line = split[0]; 65 + trailing = split[1]; 66 + } 67 + else 68 + { 69 + trailing = null; 70 + } 74 71 75 - Params = line.Contains(' ', StringComparison.Ordinal) 76 - ? line.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList() 77 - : new List<string> {line}; 72 + Params = line.Contains(' ') 73 + ? [.. line.Split([" "], StringSplitOptions.RemoveEmptyEntries)] 74 + : [line]; 78 75 79 - if (Params[0].StartsWith(':')) 80 - { 81 - Source = Params[0][1..]; 82 - Params.RemoveAt(0); 83 - } 76 + if (Params[0].StartsWith(":")) 77 + { 78 + Source = Params[0].Substring(1); 79 + Params.RemoveAt(0); 80 + } 84 81 85 - if (Params.Count > 0) 86 - { 87 - Command = Params[0].ToUpperInvariant(); 88 - Params.RemoveAt(0); 89 - } 82 + if (Params.Count > 0) 83 + { 84 + Command = Params[0].ToUpperInvariant(); 85 + Params.RemoveAt(0); 86 + } 90 87 91 - if (trailing != null) Params.Add(trailing); 88 + if (trailing != null) 89 + { 90 + Params.Add(trailing); 92 91 } 92 + } 93 93 94 - public Dictionary<string, string> Tags { get; set; } 95 - public string Source { get; set; } 96 - public string Command { get; set; } 97 - public List<string> Params { get; set; } 94 + public Dictionary<string, string> Tags { get; set; } 95 + public string Source { get; set; } 96 + public string Command { get; set; } 97 + public List<string> Params { get; set; } 98 98 99 - public Hostmask Hostmask => 100 - _hostmask ??= new Hostmask(Source); 99 + public Hostmask Hostmask => _hostmask ??= new Hostmask(Source); 101 100 102 - public bool Equals(Line other) 101 + public bool Equals(Line other) 102 + { 103 + if (other == null) 103 104 { 104 - if (other == null) return false; 105 - 106 - return Format() == other.Format(); 105 + return false; 107 106 } 108 107 109 - /// <summary> 110 - /// Unescape ircv3 tag 111 - /// </summary> 112 - /// <param name="val">escaped string</param> 113 - /// <returns>unescaped string</returns> 114 - private static string UnescapeTag(string val) 115 - { 116 - var unescaped = new StringBuilder(); 108 + return Format() == other.Format(); 109 + } 117 110 118 - var graphemeIterator = StringInfo.GetTextElementEnumerator(val); 119 - graphemeIterator.Reset(); 120 - 121 - while (graphemeIterator.MoveNext()) 122 - { 123 - var current = graphemeIterator.GetTextElement(); 124 - 125 - if (current == @"\") 126 - try 127 - { 128 - graphemeIterator.MoveNext(); 129 - var next = graphemeIterator.GetTextElement(); 130 - var pair = current + next; 131 - unescaped.Append(TagEscaped.Contains(pair) 132 - ? TagUnescaped[Array.IndexOf(TagEscaped, pair)] 133 - : next); 134 - } 135 - catch (InvalidOperationException) 136 - { 137 - // ignored 138 - } 139 - else 140 - unescaped.Append(current); 141 - } 111 + /// <summary>Unescape ircv3 tag</summary> 112 + /// <param name="val">escaped string</param> 113 + /// <returns>unescaped string</returns> 114 + private static string UnescapeTag(string val) 115 + { 116 + var unescaped = new StringBuilder(); 142 117 143 - return unescaped.ToString(); 144 - } 118 + var graphemeIterator = StringInfo.GetTextElementEnumerator(val); 119 + graphemeIterator.Reset(); 145 120 146 - /// <summary> 147 - /// Escape strings for use in ircv3 tags 148 - /// </summary> 149 - /// <param name="val">string to escape</param> 150 - /// <returns>escaped string</returns> 151 - private static string EscapeTag(string val) 121 + while (graphemeIterator.MoveNext()) 152 122 { 153 - for (var i = 0; i < TagUnescaped.Length; ++i) 154 - val = val?.Replace(TagUnescaped[i], TagEscaped[i], StringComparison.Ordinal); 123 + var current = graphemeIterator.GetTextElement(); 155 124 156 - return val; 125 + if (current == @"\") 126 + { 127 + try 128 + { 129 + graphemeIterator.MoveNext(); 130 + var next = graphemeIterator.GetTextElement(); 131 + var pair = current + next; 132 + unescaped.Append(TagEscaped.Contains(pair) 133 + ? TagUnescaped[Array.IndexOf(TagEscaped, pair)] 134 + : next); 135 + } 136 + catch (InvalidOperationException) 137 + { 138 + // ignored 139 + } 140 + } 141 + else 142 + { 143 + unescaped.Append(current); 144 + } 157 145 } 158 146 159 - public override string ToString() 147 + return unescaped.ToString(); 148 + } 149 + 150 + /// <summary>Escape strings for use in ircv3 tags</summary> 151 + /// <param name="val">string to escape</param> 152 + /// <returns>escaped string</returns> 153 + private static string EscapeTag(string val) 154 + { 155 + StringBuilder sb = new(val); 156 + for (var i = 0; i < TagUnescaped.Length; ++i) 160 157 { 161 - var vars = new List<string>(); 158 + sb.Replace(TagUnescaped[i], TagEscaped[i]); 159 + } 162 160 163 - if (Command != null) vars.Add($"command={Command}"); 161 + return sb.ToString(); 162 + } 164 163 165 - if (Source != null) vars.Add($"source={Source}"); 164 + public override string ToString() 165 + { 166 + var vars = new List<string>(); 166 167 167 - if (Params != null && Params.Any()) vars.Add($"params=[{string.Join(",", Params)}]"); 168 + if (Command != null) 169 + { 170 + vars.Add($"command={Command}"); 171 + } 168 172 169 - if (Tags != null && Tags.Any()) 170 - vars.Add($"tags=[{string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}]"); 171 - 172 - return $"Line({string.Join(", ", vars)})"; 173 + if (Source != null) 174 + { 175 + vars.Add($"source={Source}"); 173 176 } 174 177 175 - public override int GetHashCode() 178 + if (Params != null && Params.Any()) 176 179 { 177 - return Format().GetHashCode(StringComparison.Ordinal); 180 + vars.Add($"params=[{string.Join(",", Params)}]"); 178 181 } 179 182 180 - public override bool Equals(object obj) 183 + if (Tags != null && Tags.Any()) 181 184 { 182 - return Equals(obj as Line); 185 + vars.Add($"tags=[{string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}]"); 183 186 } 184 187 185 - /// <summary> 186 - /// Format a <see cref="Line" /> as a standards-compliant IRC line 187 - /// </summary> 188 - /// <returns>formatted irc line</returns> 189 - public string Format() 188 + return $"Line({string.Join(", ", vars)})"; 189 + } 190 + 191 + public override int GetHashCode() => Format().GetHashCode(); 192 + 193 + public override bool Equals(object obj) => Equals(obj as Line); 194 + 195 + /// <summary>Format a <see cref="Line" /> as a standards-compliant IRC line</summary> 196 + /// <returns>formatted irc line</returns> 197 + public string Format() 198 + { 199 + var outs = new List<string>(); 200 + 201 + if (Tags != null && Tags.Any()) 190 202 { 191 - var outs = new List<string>(); 203 + var tags = Tags.Keys 204 + .OrderBy(k => k) 205 + .Select(key => string.IsNullOrWhiteSpace(Tags[key]) ? key : $"{key}={EscapeTag(Tags[key])}") 206 + .ToList(); 192 207 193 - if (Tags != null && Tags.Any()) 194 - { 195 - var tags = Tags.Keys 196 - .OrderBy(k => k) 197 - .Select(key => 198 - string.IsNullOrWhiteSpace(Tags[key]) ? key : $"{key}={EscapeTag(Tags[key])}") 199 - .ToList(); 208 + outs.Add($"@{string.Join(";", tags)}"); 209 + } 200 210 201 - outs.Add($"@{string.Join(";", tags)}"); 202 - } 211 + if (Source != null) 212 + { 213 + outs.Add($":{Source}"); 214 + } 203 215 204 - if (Source != null) outs.Add($":{Source}"); 216 + outs.Add(Command); 205 217 206 - outs.Add(Command); 218 + if (Params != null && Params.Any()) 219 + { 220 + var last = Params.Last(); 221 + var withoutLast = Params.SkipLast(1).ToList(); 207 222 208 - if (Params != null && Params.Any()) 223 + foreach (var p in withoutLast) 209 224 { 210 - var last = Params[^1]; 211 - var withoutLast = Params.SkipLast(1).ToList(); 225 + if (p.Contains(' ')) 226 + { 227 + throw new ArgumentException("non-last parameters cannot have spaces", p); 228 + } 212 229 213 - foreach (var p in withoutLast) 230 + if (p.StartsWith(":")) 214 231 { 215 - if (p.Contains(' ', StringComparison.Ordinal)) 216 - throw new ArgumentException("non-last parameters cannot have spaces", p); 217 - 218 - if (p.StartsWith(':')) 219 - throw new ArgumentException("non-last parameters cannot start with colon", p); 232 + throw new ArgumentException("non-last parameters cannot start with colon", p); 220 233 } 234 + } 221 235 222 - outs.AddRange(withoutLast); 236 + outs.AddRange(withoutLast); 223 237 224 - if (string.IsNullOrWhiteSpace(last) || last.Contains(' ', StringComparison.Ordinal) || 225 - last.StartsWith(':')) 226 - last = $":{last}"; 227 - 228 - outs.Add(last); 238 + if (string.IsNullOrWhiteSpace(last) || last.Contains(' ') || last.StartsWith(":")) 239 + { 240 + last = $":{last}"; 229 241 } 230 242 231 - return string.Join(" ", outs); 243 + outs.Add(last); 232 244 } 245 + 246 + return string.Join(" ", outs); 233 247 } 234 248 }
+72 -60
IRCTokens/StatefulDecoder.cs
··· 1 - using System; 2 - using System.Collections.Generic; 3 - using System.Linq; 4 1 using System.Text; 5 2 6 - namespace IRCTokens 3 + // ReSharper disable ReplaceWithFieldKeyword 4 + 5 + namespace IRCTokens; 6 + 7 + public class StatefulDecoder 7 8 { 8 - public class StatefulDecoder 9 + private byte[] _buffer; 10 + private Encoding _encoding; 11 + private Encoding _fallback; 12 + 13 + public StatefulDecoder() 9 14 { 10 - private byte[] _buffer; 11 - private Encoding _encoding; 12 - private Encoding _fallback; 13 - 14 - public StatefulDecoder() 15 - { 16 - Clear(); 17 - } 15 + Clear(); 16 + } 18 17 19 - public Encoding Encoding 18 + public Encoding Encoding 19 + { 20 + get => _encoding ?? Encoding.GetEncoding( 21 + Encoding.UTF8.CodePage, 22 + EncoderFallback.ExceptionFallback, 23 + DecoderFallback.ExceptionFallback); 24 + set 20 25 { 21 - get => _encoding ?? Encoding.GetEncoding(Encoding.UTF8.CodePage, EncoderFallback.ExceptionFallback, 22 - DecoderFallback.ExceptionFallback); 23 - set 26 + if (value != null) 24 27 { 25 - if (value != null) 26 - _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback, 27 - DecoderFallback.ReplacementFallback); 28 + _encoding = Encoding.GetEncoding( 29 + value.CodePage, 30 + EncoderFallback.ExceptionFallback, 31 + DecoderFallback.ReplacementFallback); 28 32 } 29 33 } 34 + } 30 35 31 - public Encoding Fallback 36 + public Encoding Fallback 37 + { 38 + get => _fallback ?? Encoding.GetEncoding( 39 + Encoding.GetEncoding("iso-8859-1").CodePage, 40 + EncoderFallback.ExceptionFallback, 41 + DecoderFallback.ExceptionFallback); 42 + set 32 43 { 33 - get => _fallback ?? Encoding.GetEncoding(Encoding.GetEncoding("iso-8859-1").CodePage, 34 - EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback); 35 - set 44 + if (value != null) 36 45 { 37 - if (value != null) 38 - _fallback = Encoding.GetEncoding(value.CodePage, EncoderFallback.ReplacementFallback, 39 - DecoderFallback.ReplacementFallback); 46 + _fallback = Encoding.GetEncoding( 47 + value.CodePage, 48 + EncoderFallback.ReplacementFallback, 49 + DecoderFallback.ReplacementFallback); 40 50 } 41 51 } 52 + } 42 53 43 - public string Pending => Encoding.GetString(_buffer); 54 + public string Pending => Encoding.GetString(_buffer); 55 + 56 + public void Clear() => _buffer = []; 57 + 58 + public List<Line> Push(string data) 59 + { 60 + var bytes = Encoding.GetBytes(data); 61 + return Push(bytes, bytes.Length); 62 + } 44 63 45 - public void Clear() 64 + public List<Line> Push(byte[] data, int bytesReceived) 65 + { 66 + if (data == null) 46 67 { 47 - _buffer = Array.Empty<byte>(); 68 + return null; 48 69 } 49 70 50 - public List<Line> Push(string data) 71 + _buffer = _buffer == null ? [] : _buffer.Concat(data.Take(bytesReceived)).ToArray(); 72 + 73 + // truncate message at NUL if found 74 + if (_buffer.Contains((byte)0)) 51 75 { 52 - var bytes = Encoding.GetBytes(data); 53 - return Push(bytes, bytes.Length); 76 + _buffer = _buffer 77 + .Take(Array.IndexOf(_buffer, (byte)0)) 78 + .Concat([(byte)'\n']) 79 + .ToArray(); 54 80 } 55 81 56 - public List<Line> Push(byte[] data, int bytesReceived) 57 - { 58 - if (data == null) return null; 59 - 60 - _buffer = _buffer == null ? Array.Empty<byte>() : _buffer.Concat(data.Take(bytesReceived)).ToArray(); 82 + var listLines = _buffer.Split((byte)'\n').Select(l => l.Trim((byte)'\r')).ToList(); 83 + _buffer = listLines.LastOrDefault() ?? []; 61 84 62 - // truncate message at NUL if found 63 - if (_buffer.Contains((byte) 0)) 85 + var decodeLines = new List<Line>(); 86 + foreach (var line in listLines.SkipLast(1).Select(l => l.ToArray())) 87 + { 88 + try 89 + { 90 + decodeLines.Add(new Line(Encoding.GetString(line))); 91 + } 92 + catch (DecoderFallbackException) 64 93 { 65 - _buffer = _buffer 66 - .Take(Array.IndexOf(_buffer, (byte) 0)) 67 - .Concat(new []{(byte) '\n'}) 68 - .ToArray(); 94 + decodeLines.Add(new Line(Fallback.GetString(line))); 69 95 } 96 + } 70 97 71 - var listLines = _buffer.Split((byte) '\n').Select(l => l.Trim((byte) '\r')).ToList(); 72 - _buffer = listLines.LastOrDefault() ?? Array.Empty<byte>(); 73 - 74 - var decodeLines = new List<Line>(); 75 - foreach (var line in listLines.SkipLast(1).Select(l => l.ToArray())) 76 - try 77 - { 78 - decodeLines.Add(new Line(Encoding.GetString(line))); 79 - } 80 - catch (DecoderFallbackException) 81 - { 82 - decodeLines.Add(new Line(Fallback.GetString(line))); 83 - } 84 - 85 - return decodeLines; 86 - } 98 + return decodeLines; 87 99 } 88 100 }
+54 -47
IRCTokens/StatefulEncoder.cs
··· 1 - using System; 2 - using System.Collections.Generic; 3 - using System.Linq; 4 1 using System.Text; 5 2 6 - namespace IRCTokens 3 + // ReSharper disable ReplaceWithFieldKeyword 4 + 5 + namespace IRCTokens; 6 + 7 + public class StatefulEncoder 7 8 { 8 - public class StatefulEncoder 9 + private List<Line> _bufferedLines; 10 + private Encoding _encoding; 11 + 12 + public StatefulEncoder() 9 13 { 10 - private List<Line> _bufferedLines; 11 - private Encoding _encoding; 14 + Clear(); 15 + } 12 16 13 - public StatefulEncoder() 17 + public Encoding Encoding 18 + { 19 + get => _encoding ?? Encoding.GetEncoding( 20 + Encoding.UTF8.CodePage, 21 + EncoderFallback.ExceptionFallback, 22 + DecoderFallback.ExceptionFallback); 23 + set 14 24 { 15 - Clear(); 16 - } 17 - 18 - public Encoding Encoding 19 - { 20 - get => _encoding ?? Encoding.GetEncoding(Encoding.UTF8.CodePage, EncoderFallback.ExceptionFallback, 21 - DecoderFallback.ExceptionFallback); 22 - set 25 + if (value != null) 23 26 { 24 - if (value != null) 25 - _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback, 26 - DecoderFallback.ExceptionFallback); 27 + _encoding = Encoding.GetEncoding( 28 + value.CodePage, 29 + EncoderFallback.ExceptionFallback, 30 + DecoderFallback.ExceptionFallback); 27 31 } 28 32 } 33 + } 29 34 30 - public byte[] PendingBytes { get; private set; } 35 + public byte[] PendingBytes { get; private set; } 31 36 32 - public string Pending() 37 + public string Pending() 38 + { 39 + try 33 40 { 34 - try 35 - { 36 - return Encoding.GetString(PendingBytes); 37 - } 38 - catch (DecoderFallbackException e) 39 - { 40 - Console.WriteLine(e); 41 - throw; 42 - } 41 + return Encoding.GetString(PendingBytes); 43 42 } 44 - 45 - public void Clear() 43 + catch (DecoderFallbackException e) 46 44 { 47 - PendingBytes = Array.Empty<byte>(); 48 - _bufferedLines = new List<Line>(); 45 + Console.WriteLine(e); 46 + throw; 49 47 } 48 + } 50 49 51 - public void Push(Line line) 52 - { 53 - if (line == null) throw new ArgumentNullException(nameof(line)); 50 + public void Clear() 51 + { 52 + PendingBytes = []; 53 + _bufferedLines = []; 54 + } 54 55 55 - PendingBytes = PendingBytes.Concat(Encoding.GetBytes($"{line.Format()}\r\n")).ToArray(); 56 - _bufferedLines.Add(line); 56 + public void Push(Line line) 57 + { 58 + if (line == null) 59 + { 60 + throw new ArgumentNullException(nameof(line)); 57 61 } 58 62 59 - public List<Line> Pop(int byteCount) 60 - { 61 - var sent = PendingBytes.Take(byteCount).Count(c => c == '\n'); 63 + PendingBytes = PendingBytes.Concat(Encoding.GetBytes($"{line.Format()}\r\n")).ToArray(); 64 + _bufferedLines.Add(line); 65 + } 66 + 67 + public List<Line> Pop(int byteCount) 68 + { 69 + var sent = PendingBytes.Take(byteCount).Count(c => c == '\n'); 62 70 63 - PendingBytes = PendingBytes.Skip(byteCount).ToArray(); 71 + PendingBytes = PendingBytes.Skip(byteCount).ToArray(); 64 72 65 - var sentLines = _bufferedLines.Take(sent).ToList(); 66 - _bufferedLines = _bufferedLines.Skip(sent).ToList(); 73 + var sentLines = _bufferedLines.Take(sent).ToList(); 74 + _bufferedLines = _bufferedLines.Skip(sent).ToList(); 67 75 68 - return sentLines; 69 - } 76 + return sentLines; 70 77 } 71 78 }
+3 -4
global.json
··· 1 1 { 2 - "sdk": { 3 - "version": "9.0.0", 4 - "rollForward": "latestMajor" 2 + "test": { 3 + "runner": "Microsoft.Testing.Platform" 5 4 } 6 - } 5 + }