IRC parsing, tokenization, and state handling in C#

rename to IrcSharp

also tidy up formatting with vs tools

+578 -111
+57
.editorconfig
···
··· 1 + 2 + [*.proto] 3 + indent_style=tab 4 + indent_size=tab 5 + tab_width=4 6 + 7 + [*.{asax,ascx,aspx,cs,cshtml,css,htm,html,js,jsx,master,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}] 8 + indent_style=space 9 + indent_size=4 10 + tab_width=4 11 + 12 + [*.{appxmanifest,axml,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}] 13 + indent_style=space 14 + indent_size=2 15 + tab_width=2 16 + 17 + [*] 18 + 19 + # Microsoft .NET properties 20 + csharp_new_line_before_members_in_object_initializers=true 21 + csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion 22 + csharp_style_var_elsewhere=true:suggestion 23 + csharp_style_var_for_built_in_types=true:suggestion 24 + csharp_style_var_when_type_is_apparent=true:suggestion 25 + dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none 26 + dotnet_style_parentheses_in_other_binary_operators=never_if_unnecessary:none 27 + dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none 28 + dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion 29 + dotnet_style_predefined_type_for_member_access=true:suggestion 30 + dotnet_style_qualification_for_event=false:suggestion 31 + dotnet_style_qualification_for_field=false:suggestion 32 + dotnet_style_qualification_for_method=false:suggestion 33 + dotnet_style_qualification_for_property=false:suggestion 34 + dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion 35 + 36 + # ReSharper inspection severities 37 + resharper_arrange_redundant_parentheses_highlighting=hint 38 + resharper_arrange_this_qualifier_highlighting=hint 39 + resharper_arrange_type_member_modifiers_highlighting=hint 40 + resharper_arrange_type_modifiers_highlighting=hint 41 + resharper_built_in_type_reference_style_for_member_access_highlighting=hint 42 + resharper_built_in_type_reference_style_highlighting=hint 43 + resharper_redundant_base_qualifier_highlighting=warning 44 + resharper_suggest_var_or_type_built_in_types_highlighting=hint 45 + resharper_suggest_var_or_type_elsewhere_highlighting=hint 46 + resharper_suggest_var_or_type_simple_types_highlighting=hint 47 + 48 + # ReSharper properties 49 + resharper_csharp_insert_final_newline=true 50 + resharper_int_align_assignments=true 51 + resharper_int_align_switch_expressions=true 52 + resharper_int_align_switch_sections=true 53 + resharper_int_align_variables=true 54 + resharper_keep_existing_embedded_arrangement=false 55 + resharper_keep_existing_switch_expression_arrangement=false 56 + resharper_place_simple_case_statement_on_same_line=if_owner_is_single_line 57 + resharper_xmldoc_max_blank_lines_between_tags=1
+48
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}") = "TokensSample", "Sample\TokensSample.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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatesSample", "StatesSample\StatesSample.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 + Global 20 + GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 + Debug|Any CPU = Debug|Any CPU 22 + Release|Any CPU = Release|Any CPU 23 + EndGlobalSection 24 + GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 + {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 + {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 + {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 + {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Release|Any CPU.Build.0 = Release|Any CPU 29 + {A45DA39B-6B47-4713-8049-3B36E0235B67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 + {A45DA39B-6B47-4713-8049-3B36E0235B67}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 + {A45DA39B-6B47-4713-8049-3B36E0235B67}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 + {A45DA39B-6B47-4713-8049-3B36E0235B67}.Release|Any CPU.Build.0 = Release|Any CPU 33 + {233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 + {233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 + {233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 + {233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Release|Any CPU.Build.0 = Release|Any CPU 37 + {BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 + {BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 + {BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 + {BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Release|Any CPU.Build.0 = Release|Any CPU 41 + EndGlobalSection 42 + GlobalSection(SolutionProperties) = preSolution 43 + HideSolutionNode = FALSE 44 + EndGlobalSection 45 + GlobalSection(ExtensibilityGlobals) = postSolution 46 + SolutionGuid = {0B91F0EA-8564-4318-8EEC-ED0640475141} 47 + EndGlobalSection 48 + EndGlobal
+43
IrcStates/Casemap.cs
···
··· 1 + using System; 2 + 3 + namespace IrcStates 4 + { 5 + public static class Casemap 6 + { 7 + public enum CaseMapping 8 + { 9 + Rfc1459, 10 + Ascii 11 + } 12 + 13 + private const string AsciiUpperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 14 + private const string AsciiLowerChars = "abcdefghijklmnopqrstuvwxyz"; 15 + private const string Rfc1459UpperChars = AsciiUpperChars + @"[]~\"; 16 + private const string Rfc1459LowerChars = AsciiLowerChars + @"{}^|"; 17 + 18 + private static string Replace(string s, string upper, string lower) 19 + { 20 + for (var i = 0; i < upper.Length; ++i) 21 + { 22 + s = s.Replace(upper[i], lower[i]); 23 + } 24 + 25 + return s; 26 + } 27 + 28 + public static string CaseFold(CaseMapping mapping, string s) 29 + { 30 + if (s != null) 31 + { 32 + return mapping switch 33 + { 34 + CaseMapping.Rfc1459 => Replace(s, Rfc1459UpperChars, Rfc1459LowerChars), 35 + CaseMapping.Ascii => Replace(s, AsciiUpperChars, AsciiLowerChars), 36 + _ => throw new ArgumentOutOfRangeException(nameof(mapping), mapping, null) 37 + }; 38 + } 39 + 40 + return string.Empty; 41 + } 42 + } 43 + }
+69
IrcStates/Channel.cs
···
··· 1 + using System; 2 + using System.Collections.Generic; 3 + using System.Linq; 4 + 5 + namespace IrcStates 6 + { 7 + public class Channel 8 + { 9 + public string Name { get; set; } 10 + public string NameLower { get; set; } 11 + public Dictionary<string, User> Users { get; set; } 12 + public string Topic { get; set; } 13 + public string TopicSetter { get; set; } 14 + public DateTime TopicTime { get; set; } 15 + public DateTime Created { get; set; } 16 + public Dictionary<string, List<string>> ListModes { get; set; } 17 + public Dictionary<string, string> Modes { get; set; } 18 + 19 + public override string ToString() 20 + { 21 + return $"Channel(name={Name})"; 22 + } 23 + 24 + public void SetName(string name, string nameLower) 25 + { 26 + Name = name; 27 + NameLower = nameLower; 28 + } 29 + 30 + public void AddMode(string ch, string param, bool listMode) 31 + { 32 + if (listMode) 33 + { 34 + if (!ListModes.ContainsKey(ch)) 35 + { 36 + ListModes[ch] = new List<string>(); 37 + } 38 + 39 + if (ListModes[ch].Contains(param)) 40 + { 41 + ListModes[ch].Add(param ?? string.Empty); 42 + } 43 + } 44 + else 45 + { 46 + Modes[ch] = param; 47 + } 48 + } 49 + 50 + public void RemoveMode(string ch, string param) 51 + { 52 + if (ListModes.ContainsKey(ch)) 53 + { 54 + if (ListModes[ch].Contains(param)) 55 + { 56 + ListModes[ch].Remove(param); 57 + if (!ListModes[ch].Any()) 58 + { 59 + ListModes.Remove(ch); 60 + } 61 + } 62 + } 63 + else if (Modes.ContainsKey(ch)) 64 + { 65 + Modes.Remove(ch); 66 + } 67 + } 68 + } 69 + }
+6
IrcStates/Emit.cs
···
··· 1 + namespace IrcStates 2 + { 3 + public class Emit 4 + { 5 + } 6 + }
+7
IrcStates/ISupport.cs
···
··· 1 + // ReSharper disable InconsistentNaming 2 + namespace IrcStates 3 + { 4 + public class ISupport 5 + { 6 + } 7 + }
+21
IrcStates/IrcStates.csproj
···
··· 1 + <Project Sdk="Microsoft.NET.Sdk"> 2 + 3 + <PropertyGroup> 4 + <TargetFramework>netcoreapp3.1</TargetFramework> 5 + </PropertyGroup> 6 + 7 + <ItemGroup> 8 + <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8"> 9 + <PrivateAssets>all</PrivateAssets> 10 + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> 11 + </PackageReference> 12 + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.0" /> 13 + <PackageReference Include="MSTest.TestAdapter" Version="2.1.1" /> 14 + <PackageReference Include="MSTest.TestFramework" Version="2.1.1" /> 15 + </ItemGroup> 16 + 17 + <ItemGroup> 18 + <ProjectReference Include="..\IrcTokens\IrcTokens.csproj" /> 19 + </ItemGroup> 20 + 21 + </Project>
+49
IrcStates/Numeric.cs
···
··· 1 + // ReSharper disable InconsistentNaming 2 + namespace IrcStates 3 + { 4 + public static class Numeric 5 + { 6 + public const string RPL_WELCOME = "001"; 7 + public const string RPL_ISUPPORT = "005"; 8 + public const string RPL_MOTD = "372"; 9 + public const string RPL_MOTDSTART = "375"; 10 + public const string RPL_UMODEIS = "221"; 11 + public const string RPL_VISIBLEHOST = "396"; 12 + 13 + public const string RPL_CHANNELMODEIS = "324"; 14 + public const string RPL_CREATIONTIME = "329"; 15 + public const string RPL_TOPIC = "332"; 16 + public const string RPL_TOPICWHOTIME = "333"; 17 + 18 + public const string RPL_WHOREPLY = "352"; 19 + public const string RPL_WHOSPCRPL = "354"; 20 + public const string RPL_ENDOFWHO = "315"; 21 + public const string RPL_NAMREPLY = "353"; 22 + public const string RPL_ENDOFNAMES = "366"; 23 + 24 + public const string RPL_BANLIST = "367"; 25 + public const string RPL_ENDOFBANLIST = "368"; 26 + public const string RPL_QUIETLIST = "728"; 27 + public const string RPL_ENDOFQUIETLIST = "729"; 28 + 29 + public const string RPL_SASLSUCCESS = "903"; 30 + public const string ERR_SASLFAIL = "904"; 31 + public const string ERR_SASLTOOLONG = "905"; 32 + public const string ERR_SASLABORTED = "906"; 33 + public const string ERR_SASLALREADY = "907"; 34 + public const string RPL_SASLMECHS = "908"; 35 + 36 + public const string RPL_WHOISUSER = "311"; 37 + public const string RPL_WHOISSERVER = "312"; 38 + public const string RPL_WHOISOPERATOR = "313"; 39 + public const string RPL_WHOISIDLE = "317"; 40 + public const string RPL_WHOISCHANNELS = "319"; 41 + public const string RPL_WHOISACCOUNT = "330"; 42 + public const string RPL_WHOISHOST = "378"; 43 + public const string RPL_WHOISMODES = "379"; 44 + public const string RPL_WHOISSECURE = "671"; 45 + public const string RPL_ENDOFWHOIS = "318"; 46 + 47 + public const string ERR_NOSUCHCHANNEL = "403"; 48 + } 49 + }
+6
IrcStates/Server.cs
···
··· 1 + namespace IrcStates 2 + { 3 + public class Server 4 + { 5 + } 6 + }
+9
IrcStates/Tests/Cap.cs
···
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + 3 + namespace IrcStates.Tests 4 + { 5 + [TestClass] 6 + public class Cap 7 + { 8 + } 9 + }
+9
IrcStates/Tests/Casemap.cs
···
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + 3 + namespace IrcStates.Tests 4 + { 5 + [TestClass] 6 + public class Casemap 7 + { 8 + } 9 + }
+9
IrcStates/Tests/Channel.cs
···
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + 3 + namespace IrcStates.Tests 4 + { 5 + [TestClass] 6 + public class Channel 7 + { 8 + } 9 + }
+9
IrcStates/Tests/Emit.cs
···
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + 3 + namespace IrcStates.Tests 4 + { 5 + [TestClass] 6 + public class Emit 7 + { 8 + } 9 + }
+10
IrcStates/Tests/ISupport.cs
···
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + // ReSharper disable InconsistentNaming 3 + 4 + namespace IrcStates.Tests 5 + { 6 + [TestClass] 7 + public class ISupport 8 + { 9 + } 10 + }
+9
IrcStates/Tests/Mode.cs
···
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + 3 + namespace IrcStates.Tests 4 + { 5 + [TestClass] 6 + public class Mode 7 + { 8 + } 9 + }
+9
IrcStates/Tests/Motd.cs
···
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + 3 + namespace IrcStates.Tests 4 + { 5 + [TestClass] 6 + public class Motd 7 + { 8 + } 9 + }
+9
IrcStates/Tests/User.cs
···
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + 3 + namespace IrcStates.Tests 4 + { 5 + [TestClass] 6 + public class User 7 + { 8 + } 9 + }
+9
IrcStates/Tests/Who.cs
···
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + 3 + namespace IrcStates.Tests 4 + { 5 + [TestClass] 6 + public class Who 7 + { 8 + } 9 + }
+28
IrcStates/User.cs
···
··· 1 + using System.Collections.Generic; 2 + 3 + namespace IrcStates 4 + { 5 + public class User 6 + { 7 + private string NickName; 8 + private string NickNameLower; 9 + 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; set; } 16 + 17 + public override string ToString() 18 + { 19 + return $"User(nickname={NickName})"; 20 + } 21 + 22 + public void SetNickName(string nick, string nickLower) 23 + { 24 + NickName = nick; 25 + NickNameLower = nickLower; 26 + } 27 + } 28 + }
-31
IrcTokens.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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "Sample\Sample.csproj", "{A45DA39B-6B47-4713-8049-3B36E0235B67}" 9 - EndProject 10 - Global 11 - GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 - Debug|Any CPU = Debug|Any CPU 13 - Release|Any CPU = Release|Any CPU 14 - EndGlobalSection 15 - GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 - {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 - {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 - {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 - {9E812F45-B2CD-42D2-8378-EBEBF8697905}.Release|Any CPU.Build.0 = Release|Any CPU 20 - {A45DA39B-6B47-4713-8049-3B36E0235B67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 - {A45DA39B-6B47-4713-8049-3B36E0235B67}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 - {A45DA39B-6B47-4713-8049-3B36E0235B67}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 - {A45DA39B-6B47-4713-8049-3B36E0235B67}.Release|Any CPU.Build.0 = Release|Any CPU 24 - EndGlobalSection 25 - GlobalSection(SolutionProperties) = preSolution 26 - HideSolutionNode = FALSE 27 - EndGlobalSection 28 - GlobalSection(ExtensibilityGlobals) = postSolution 29 - SolutionGuid = {0B91F0EA-8564-4318-8EEC-ED0640475141} 30 - EndGlobalSection 31 - EndGlobal
···
+23 -7
IrcTokens/Hostmask.cs
··· 5 /// <summary> 6 /// Represents the three parts of a hostmask. Parse with the constructor. 7 /// </summary> 8 - public class Hostmask 9 { 10 public string NickName { get; set; } 11 public string UserName { get; set; } 12 public string HostName { get; set; } 13 14 - public override string ToString() => _source; 15 16 - public override int GetHashCode() => _source.GetHashCode(StringComparison.Ordinal); 17 18 - public override bool Equals(object obj) 19 { 20 - if (obj == null || GetType() != obj.GetType()) 21 return false; 22 23 - return _source == ((Hostmask) obj)._source; 24 } 25 26 private readonly string _source; 27 28 public Hostmask(string source) 29 { 30 - if (source == null) return; 31 32 _source = source; 33
··· 5 /// <summary> 6 /// Represents the three parts of a hostmask. Parse with the constructor. 7 /// </summary> 8 + public class Hostmask : IEquatable<Hostmask> 9 { 10 public string NickName { get; set; } 11 public string UserName { get; set; } 12 public string HostName { get; set; } 13 14 + public override string ToString() 15 + { 16 + return _source; 17 + } 18 19 + public override int GetHashCode() 20 + { 21 + return _source.GetHashCode(StringComparison.Ordinal); 22 + } 23 24 + public bool Equals(Hostmask other) 25 { 26 + if (other == null) 27 + { 28 return false; 29 + } 30 31 + return _source == other._source; 32 + } 33 + 34 + public override bool Equals(object obj) 35 + { 36 + return Equals(obj as Hostmask); 37 } 38 39 private readonly string _source; 40 41 public Hostmask(string source) 42 { 43 + if (source == null) 44 + { 45 + return; 46 + } 47 48 _source = source; 49
+39 -8
IrcTokens/Line.cs
··· 8 /// <summary> 9 /// Tools to represent, parse, and format IRC lines 10 /// </summary> 11 - public class Line 12 { 13 public Dictionary<string, string> Tags { get; set; } 14 public string Source { get; set; } ··· 16 public List<string> Params { get; set; } 17 18 private Hostmask _hostmask; 19 - private readonly string _rawLine; 20 21 public override string ToString() 22 { 23 var vars = new List<string>(); 24 25 if (Command != null) 26 vars.Add($"command={Command}"); 27 if (Source != null) 28 vars.Add($"source={Source}"); 29 if (Params != null && Params.Any()) 30 vars.Add($"params=[{string.Join(",", Params)}]"); 31 if (Tags != null && Tags.Any()) 32 vars.Add($"tags=[{string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}]"); 33 34 return $"Line({string.Join(", ", vars)})"; 35 } 36 37 - public override int GetHashCode() => Format().GetHashCode(StringComparison.Ordinal); 38 39 - public override bool Equals(object obj) 40 { 41 - if (obj == null || GetType() != obj.GetType()) 42 return false; 43 44 - return Format() == ((Line) obj).Format(); 45 } 46 47 public Hostmask Hostmask => ··· 56 public Line(string line) 57 { 58 if (string.IsNullOrWhiteSpace(line)) 59 throw new ArgumentNullException(nameof(line)); 60 61 - _rawLine = line; 62 string[] split; 63 64 if (line.StartsWith('@')) ··· 97 98 Params = line.Contains(' ', StringComparison.Ordinal) 99 ? line.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList() 100 - : new List<string> {line}; 101 102 if (Params[0].StartsWith(':')) 103 { ··· 135 } 136 137 if (Source != null) 138 outs.Add($":{Source}"); 139 140 outs.Add(Command); 141 ··· 147 foreach (var p in Params) 148 { 149 if (p.Contains(' ', StringComparison.Ordinal)) 150 throw new ArgumentException(@"non-last parameters cannot have spaces", p); 151 if (p.StartsWith(':')) 152 throw new ArgumentException(@"non-last parameters cannot start with colon", p); 153 } 154 outs.AddRange(Params); 155 156 if (string.IsNullOrWhiteSpace(last) || last.Contains(' ', StringComparison.Ordinal) || last.StartsWith(':')) 157 last = $":{last}"; 158 outs.Add(last); 159 } 160
··· 8 /// <summary> 9 /// Tools to represent, parse, and format IRC lines 10 /// </summary> 11 + public class Line : IEquatable<Line> 12 { 13 public Dictionary<string, string> Tags { get; set; } 14 public string Source { get; set; } ··· 16 public List<string> Params { get; set; } 17 18 private Hostmask _hostmask; 19 20 public override string ToString() 21 { 22 var vars = new List<string>(); 23 24 if (Command != null) 25 + { 26 vars.Add($"command={Command}"); 27 + } 28 + 29 if (Source != null) 30 + { 31 vars.Add($"source={Source}"); 32 + } 33 + 34 if (Params != null && Params.Any()) 35 + { 36 vars.Add($"params=[{string.Join(",", Params)}]"); 37 + } 38 + 39 if (Tags != null && Tags.Any()) 40 + { 41 vars.Add($"tags=[{string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}]"); 42 + } 43 44 return $"Line({string.Join(", ", vars)})"; 45 } 46 47 + public override int GetHashCode() 48 + { 49 + return Format().GetHashCode(StringComparison.Ordinal); 50 + } 51 52 + public bool Equals(Line other) 53 { 54 + if (other == null) 55 + { 56 return false; 57 + } 58 59 + return Format() == other.Format(); 60 + } 61 + 62 + public override bool Equals(object obj) 63 + { 64 + return Equals(obj as Line); 65 } 66 67 public Hostmask Hostmask => ··· 76 public Line(string line) 77 { 78 if (string.IsNullOrWhiteSpace(line)) 79 + { 80 throw new ArgumentNullException(nameof(line)); 81 + } 82 83 string[] split; 84 85 if (line.StartsWith('@')) ··· 118 119 Params = line.Contains(' ', StringComparison.Ordinal) 120 ? line.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList() 121 + : new List<string> { line }; 122 123 if (Params[0].StartsWith(':')) 124 { ··· 156 } 157 158 if (Source != null) 159 + { 160 outs.Add($":{Source}"); 161 + } 162 163 outs.Add(Command); 164 ··· 170 foreach (var p in Params) 171 { 172 if (p.Contains(' ', StringComparison.Ordinal)) 173 + { 174 throw new ArgumentException(@"non-last parameters cannot have spaces", p); 175 + } 176 + 177 if (p.StartsWith(':')) 178 + { 179 throw new ArgumentException(@"non-last parameters cannot start with colon", p); 180 + } 181 } 182 outs.AddRange(Params); 183 184 if (string.IsNullOrWhiteSpace(last) || last.Contains(' ', StringComparison.Ordinal) || last.StartsWith(':')) 185 + { 186 last = $":{last}"; 187 + } 188 + 189 outs.Add(last); 190 } 191
+2
IrcTokens/Protocol.cs
··· 50 } 51 } 52 else 53 unescaped.Append(current); 54 } 55 56 return unescaped.ToString();
··· 50 } 51 } 52 else 53 + { 54 unescaped.Append(current); 55 + } 56 } 57 58 return unescaped.ToString();
+7 -1
IrcTokens/StatefulDecoder.cs
··· 18 set 19 { 20 if (value != null) 21 _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback, 22 DecoderFallback.ReplacementFallback); 23 } 24 } 25 ··· 30 set 31 { 32 if (value != null) 33 - _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ReplacementFallback, 34 DecoderFallback.ReplacementFallback); 35 } 36 } 37 ··· 55 public List<Line> Push(byte[] data) 56 { 57 if (data == null || data.Length == 0) 58 return null; 59 60 _buffer = _buffer.Concat(data).ToArray(); 61
··· 18 set 19 { 20 if (value != null) 21 + { 22 _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback, 23 DecoderFallback.ReplacementFallback); 24 + } 25 } 26 } 27 ··· 32 set 33 { 34 if (value != null) 35 + { 36 + _fallback = Encoding.GetEncoding(value.CodePage, EncoderFallback.ReplacementFallback, 37 DecoderFallback.ReplacementFallback); 38 + } 39 } 40 } 41 ··· 59 public List<Line> Push(byte[] data) 60 { 61 if (data == null || data.Length == 0) 62 + { 63 return null; 64 + } 65 66 _buffer = _buffer.Concat(data).ToArray(); 67
+4
IrcTokens/StatefulEncoder.cs
··· 16 set 17 { 18 if (value != null) 19 _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback, 20 DecoderFallback.ExceptionFallback); 21 } 22 } 23 ··· 52 public void Push(Line line) 53 { 54 if (line == null) 55 throw new ArgumentNullException(nameof(line)); 56 57 PendingBytes = PendingBytes.Concat(Encoding.GetBytes($"{line.Format()}\r\n")).ToArray(); 58 _bufferedLines.Enqueue(line);
··· 16 set 17 { 18 if (value != null) 19 + { 20 _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback, 21 DecoderFallback.ExceptionFallback); 22 + } 23 } 24 } 25 ··· 54 public void Push(Line line) 55 { 56 if (line == null) 57 + { 58 throw new ArgumentNullException(nameof(line)); 59 + } 60 61 PendingBytes = PendingBytes.Concat(Encoding.GetBytes($"{line.Format()}\r\n")).ToArray(); 62 _bufferedLines.Enqueue(line);
+18 -18
IrcTokens/Tests/FormatTests.cs IrcTokens/Tests/Format.cs
··· 5 namespace IrcTokens.Tests 6 { 7 [TestClass] 8 - public class FormatTests 9 { 10 [TestMethod] 11 public void TestTags() 12 { 13 var line = new Line 14 { 15 - Command = "PRIVMSG", 16 - Params = new List<string> {"#channel", "hello"}, 17 - Tags = new Dictionary<string, string> {{"id", "\\" + " " + ";" + "\r\n"}} 18 }.Format(); 19 20 Assert.AreEqual("@id=\\\\\\s\\:\\r\\n PRIVMSG #channel hello", line); ··· 25 { 26 var line = new Line 27 { 28 - Command = "PRIVMSG", 29 - Params = new List<string> {"#channel", "hello"} 30 }.Format(); 31 32 Assert.AreEqual("PRIVMSG #channel hello", line); ··· 38 var line = new Line 39 { 40 Command = "PRIVMSG", 41 - Params = new List<string> {"#channel", "hello"}, 42 - Tags = new Dictionary<string, string> {{"a", null}} 43 }.Format(); 44 45 Assert.AreEqual("@a PRIVMSG #channel hello", line); ··· 51 var line = new Line 52 { 53 Command = "PRIVMSG", 54 - Params = new List<string> {"#channel", "hello"}, 55 - Tags = new Dictionary<string, string> {{"a", ""}} 56 }.Format(); 57 58 Assert.AreEqual("@a PRIVMSG #channel hello", line); ··· 64 var line = new Line 65 { 66 Command = "PRIVMSG", 67 - Params = new List<string> {"#channel", "hello"}, 68 Source = "nick!user@host" 69 }.Format(); 70 ··· 74 [TestMethod] 75 public void TestCommandLowercase() 76 { 77 - var line = new Line {Command = "privmsg"}.Format(); 78 Assert.AreEqual("privmsg", line); 79 } 80 81 [TestMethod] 82 public void TestCommandUppercase() 83 { 84 - var line = new Line {Command = "PRIVMSG"}.Format(); 85 Assert.AreEqual("PRIVMSG", line); 86 } 87 ··· 91 var line = new Line 92 { 93 Command = "PRIVMSG", 94 - Params = new List<string> {"#channel", "hello world"} 95 }.Format(); 96 97 Assert.AreEqual("PRIVMSG #channel :hello world", line); ··· 103 var line = new Line 104 { 105 Command = "PRIVMSG", 106 - Params = new List<string> {"#channel", "helloworld"} 107 }.Format(); 108 109 Assert.AreEqual("PRIVMSG #channel helloworld", line); ··· 115 var line = new Line 116 { 117 Command = "PRIVMSG", 118 - Params = new List<string> {"#channel", ":helloworld"} 119 }.Format(); 120 121 Assert.AreEqual("PRIVMSG #channel ::helloworld", line); ··· 129 new Line 130 { 131 Command = "USER", 132 - Params = new List<string> {"user", "0 *", "real name"} 133 }.Format(); 134 }); 135 } ··· 142 new Line 143 { 144 Command = "PRIVMSG", 145 - Params = new List<string> {":#channel", "hello"} 146 }.Format(); 147 }); 148 }
··· 5 namespace IrcTokens.Tests 6 { 7 [TestClass] 8 + public class Format 9 { 10 [TestMethod] 11 public void TestTags() 12 { 13 var line = new Line 14 { 15 + Command = "PRIVMSG", 16 + Params = new List<string> { "#channel", "hello" }, 17 + Tags = new Dictionary<string, string> { { "id", "\\" + " " + ";" + "\r\n" } } 18 }.Format(); 19 20 Assert.AreEqual("@id=\\\\\\s\\:\\r\\n PRIVMSG #channel hello", line); ··· 25 { 26 var line = new Line 27 { 28 + Command = "PRIVMSG", 29 + Params = new List<string> { "#channel", "hello" } 30 }.Format(); 31 32 Assert.AreEqual("PRIVMSG #channel hello", line); ··· 38 var line = new Line 39 { 40 Command = "PRIVMSG", 41 + Params = new List<string> { "#channel", "hello" }, 42 + Tags = new Dictionary<string, string> { { "a", null } } 43 }.Format(); 44 45 Assert.AreEqual("@a PRIVMSG #channel hello", line); ··· 51 var line = new Line 52 { 53 Command = "PRIVMSG", 54 + Params = new List<string> { "#channel", "hello" }, 55 + Tags = new Dictionary<string, string> { { "a", "" } } 56 }.Format(); 57 58 Assert.AreEqual("@a PRIVMSG #channel hello", line); ··· 64 var line = new Line 65 { 66 Command = "PRIVMSG", 67 + Params = new List<string> { "#channel", "hello" }, 68 Source = "nick!user@host" 69 }.Format(); 70 ··· 74 [TestMethod] 75 public void TestCommandLowercase() 76 { 77 + var line = new Line { Command = "privmsg" }.Format(); 78 Assert.AreEqual("privmsg", line); 79 } 80 81 [TestMethod] 82 public void TestCommandUppercase() 83 { 84 + var line = new Line { Command = "PRIVMSG" }.Format(); 85 Assert.AreEqual("PRIVMSG", line); 86 } 87 ··· 91 var line = new Line 92 { 93 Command = "PRIVMSG", 94 + Params = new List<string> { "#channel", "hello world" } 95 }.Format(); 96 97 Assert.AreEqual("PRIVMSG #channel :hello world", line); ··· 103 var line = new Line 104 { 105 Command = "PRIVMSG", 106 + Params = new List<string> { "#channel", "helloworld" } 107 }.Format(); 108 109 Assert.AreEqual("PRIVMSG #channel helloworld", line); ··· 115 var line = new Line 116 { 117 Command = "PRIVMSG", 118 + Params = new List<string> { "#channel", ":helloworld" } 119 }.Format(); 120 121 Assert.AreEqual("PRIVMSG #channel ::helloworld", line); ··· 129 new Line 130 { 131 Command = "USER", 132 + Params = new List<string> { "user", "0 *", "real name" } 133 }.Format(); 134 }); 135 } ··· 142 new Line 143 { 144 Command = "PRIVMSG", 145 + Params = new List<string> { ":#channel", "hello" } 146 }.Format(); 147 }); 148 }
+6 -6
IrcTokens/Tests/HostmaskTests.cs IrcTokens/Tests/Hostmask.cs
··· 3 namespace IrcTokens.Tests 4 { 5 [TestClass] 6 - public class HostmaskTests 7 { 8 [TestMethod] 9 public void TestHostmask() 10 { 11 - var hostmask = new Hostmask("nick!user@host"); 12 Assert.AreEqual("nick", hostmask.NickName); 13 Assert.AreEqual("user", hostmask.UserName); 14 Assert.AreEqual("host", hostmask.HostName); ··· 17 [TestMethod] 18 public void TestNoHostName() 19 { 20 - var hostmask = new Hostmask("nick!user"); 21 Assert.AreEqual("nick", hostmask.NickName); 22 Assert.AreEqual("user", hostmask.UserName); 23 Assert.IsNull(hostmask.HostName); ··· 26 [TestMethod] 27 public void TestNoUserName() 28 { 29 - var hostmask = new Hostmask("nick@host"); 30 Assert.AreEqual("nick", hostmask.NickName); 31 Assert.IsNull(hostmask.UserName); 32 Assert.AreEqual("host", hostmask.HostName); ··· 35 [TestMethod] 36 public void TestOnlyNickName() 37 { 38 - var hostmask = new Hostmask("nick"); 39 Assert.AreEqual("nick", hostmask.NickName); 40 Assert.IsNull(hostmask.UserName); 41 Assert.IsNull(hostmask.HostName); ··· 45 public void TestHostmaskFromLine() 46 { 47 var line = new Line(":nick!user@host PRIVMSG #channel hello"); 48 - var hostmask = new Hostmask("nick!user@host"); 49 Assert.AreEqual(hostmask.ToString(), line.Hostmask.ToString()); 50 Assert.AreEqual("nick", line.Hostmask.NickName); 51 Assert.AreEqual("user", line.Hostmask.UserName);
··· 3 namespace IrcTokens.Tests 4 { 5 [TestClass] 6 + public class Hostmask 7 { 8 [TestMethod] 9 public void TestHostmask() 10 { 11 + var hostmask = new IrcTokens.Hostmask("nick!user@host"); 12 Assert.AreEqual("nick", hostmask.NickName); 13 Assert.AreEqual("user", hostmask.UserName); 14 Assert.AreEqual("host", hostmask.HostName); ··· 17 [TestMethod] 18 public void TestNoHostName() 19 { 20 + var hostmask = new IrcTokens.Hostmask("nick!user"); 21 Assert.AreEqual("nick", hostmask.NickName); 22 Assert.AreEqual("user", hostmask.UserName); 23 Assert.IsNull(hostmask.HostName); ··· 26 [TestMethod] 27 public void TestNoUserName() 28 { 29 + var hostmask = new IrcTokens.Hostmask("nick@host"); 30 Assert.AreEqual("nick", hostmask.NickName); 31 Assert.IsNull(hostmask.UserName); 32 Assert.AreEqual("host", hostmask.HostName); ··· 35 [TestMethod] 36 public void TestOnlyNickName() 37 { 38 + var hostmask = new IrcTokens.Hostmask("nick"); 39 Assert.AreEqual("nick", hostmask.NickName); 40 Assert.IsNull(hostmask.UserName); 41 Assert.IsNull(hostmask.HostName); ··· 45 public void TestHostmaskFromLine() 46 { 47 var line = new Line(":nick!user@host PRIVMSG #channel hello"); 48 + var hostmask = new IrcTokens.Hostmask("nick!user@host"); 49 Assert.AreEqual(hostmask.ToString(), line.Hostmask.ToString()); 50 Assert.AreEqual("nick", line.Hostmask.NickName); 51 Assert.AreEqual("user", line.Hostmask.UserName);
+5 -5
IrcTokens/Tests/ParserTests.cs IrcTokens/Tests/Parser.cs
··· 1 - using System.Collections.Generic; 2 using System.Globalization; 3 using System.IO; 4 - using IrcTokens.Tests.Data; 5 - using Microsoft.VisualStudio.TestTools.UnitTesting; 6 using YamlDotNet.Serialization; 7 using YamlDotNet.Serialization.NamingConventions; 8 9 namespace IrcTokens.Tests 10 { 11 [TestClass] 12 - public class ParserTests 13 { 14 private static T LoadYaml<T>(string path) 15 { ··· 46 { 47 Command = atoms.Verb, 48 Params = atoms.Params, 49 - Source = atoms.Source ?? null, 50 Tags = atoms.Tags 51 }.Format(); 52
··· 1 + using IrcTokens.Tests.Data; 2 + using Microsoft.VisualStudio.TestTools.UnitTesting; 3 + using System.Collections.Generic; 4 using System.Globalization; 5 using System.IO; 6 using YamlDotNet.Serialization; 7 using YamlDotNet.Serialization.NamingConventions; 8 9 namespace IrcTokens.Tests 10 { 11 [TestClass] 12 + public class Parser 13 { 14 private static T LoadYaml<T>(string path) 15 { ··· 46 { 47 Command = atoms.Verb, 48 Params = atoms.Params, 49 + Source = atoms.Source, 50 Tags = atoms.Tags 51 }.Format(); 52
+8 -9
IrcTokens/Tests/StatefulDecoderTests.cs IrcTokens/Tests/StatefulDecoder.cs
··· 1 - using System.Collections.Generic; 2 - using System.Linq; 3 using System.Text; 4 - using Microsoft.VisualStudio.TestTools.UnitTesting; 5 6 namespace IrcTokens.Tests 7 { 8 [TestClass] 9 - public class StatefulDecoderTests 10 { 11 - private StatefulDecoder _decoder; 12 13 [TestInitialize] 14 public void TestInitialize() 15 { 16 - _decoder = new StatefulDecoder(); 17 } 18 19 [TestMethod] ··· 26 Assert.AreEqual(1, lines.Count); 27 28 var line = new Line("PRIVMSG #channel hello"); 29 - CollectionAssert.AreEqual(new List<Line> {line}, lines); 30 } 31 32 [TestMethod] ··· 45 public void TestEncoding() 46 { 47 var iso8859 = Encoding.GetEncoding("iso-8859-1"); 48 - _decoder = new StatefulDecoder {Encoding = iso8859}; 49 var lines = _decoder.Push(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n")); 50 var line = new Line("PRIVMSG #channel :hello Ç"); 51 Assert.IsTrue(line.Equals(lines[0])); ··· 55 public void TestEncodingFallback() 56 { 57 var latin1 = Encoding.GetEncoding("iso-8859-1"); 58 - _decoder = new StatefulDecoder {Encoding = null, Fallback = latin1}; 59 var lines = _decoder.Push(latin1.GetBytes("PRIVMSG #channel hélló\r\n")); 60 Assert.AreEqual(1, lines.Count); 61 Assert.IsTrue(new Line("PRIVMSG #channel hélló").Equals(lines[0]));
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + using System.Collections.Generic; 3 using System.Text; 4 5 namespace IrcTokens.Tests 6 { 7 [TestClass] 8 + public class StatefulDecoder 9 { 10 + private IrcTokens.StatefulDecoder _decoder; 11 12 [TestInitialize] 13 public void TestInitialize() 14 { 15 + _decoder = new IrcTokens.StatefulDecoder(); 16 } 17 18 [TestMethod] ··· 25 Assert.AreEqual(1, lines.Count); 26 27 var line = new Line("PRIVMSG #channel hello"); 28 + CollectionAssert.AreEqual(new List<Line> { line }, lines); 29 } 30 31 [TestMethod] ··· 44 public void TestEncoding() 45 { 46 var iso8859 = Encoding.GetEncoding("iso-8859-1"); 47 + _decoder = new IrcTokens.StatefulDecoder { Encoding = iso8859 }; 48 var lines = _decoder.Push(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n")); 49 var line = new Line("PRIVMSG #channel :hello Ç"); 50 Assert.IsTrue(line.Equals(lines[0])); ··· 54 public void TestEncodingFallback() 55 { 56 var latin1 = Encoding.GetEncoding("iso-8859-1"); 57 + _decoder = new IrcTokens.StatefulDecoder { Encoding = null, Fallback = latin1 }; 58 var lines = _decoder.Push(latin1.GetBytes("PRIVMSG #channel hélló\r\n")); 59 Assert.AreEqual(1, lines.Count); 60 Assert.IsTrue(new Line("PRIVMSG #channel hélló").Equals(lines[0]));
+5 -6
IrcTokens/Tests/StatefulEncoderTests.cs IrcTokens/Tests/StatefulEncoder.cs
··· 1 - using System.Linq; 2 using System.Text; 3 - using Microsoft.VisualStudio.TestTools.UnitTesting; 4 5 namespace IrcTokens.Tests 6 { 7 [TestClass] 8 - public class StatefulEncoderTests 9 { 10 - private StatefulEncoder _encoder; 11 12 [TestInitialize] 13 public void TestInitialize() 14 { 15 - _encoder = new StatefulEncoder(); 16 } 17 18 [TestMethod] ··· 64 public void TestEncoding() 65 { 66 var iso8859 = Encoding.GetEncoding("iso-8859-1"); 67 - _encoder = new StatefulEncoder {Encoding = iso8859}; 68 _encoder.Push(new Line("PRIVMSG #channel :hello Ç")); 69 CollectionAssert.AreEqual(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"), _encoder.PendingBytes); 70 }
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 using System.Text; 3 4 namespace IrcTokens.Tests 5 { 6 [TestClass] 7 + public class StatefulEncoder 8 { 9 + private IrcTokens.StatefulEncoder _encoder; 10 11 [TestInitialize] 12 public void TestInitialize() 13 { 14 + _encoder = new IrcTokens.StatefulEncoder(); 15 } 16 17 [TestMethod] ··· 63 public void TestEncoding() 64 { 65 var iso8859 = Encoding.GetEncoding("iso-8859-1"); 66 + _encoder = new IrcTokens.StatefulEncoder { Encoding = iso8859 }; 67 _encoder.Push(new Line("PRIVMSG #channel :hello Ç")); 68 CollectionAssert.AreEqual(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"), _encoder.PendingBytes); 69 }
+7 -7
IrcTokens/Tests/TokenizationTests.cs IrcTokens/Tests/Tokenization.cs
··· 1 - using System.Collections.Generic; 2 - using Microsoft.VisualStudio.TestTools.UnitTesting; 3 4 namespace IrcTokens.Tests 5 { 6 [TestClass] 7 - public class TokenizationTests 8 { 9 [TestMethod] 10 public void TestTagsMissing() ··· 87 public void TestParamsTrailing() 88 { 89 var line = new Line("PRIVMSG #channel :hello world"); 90 - CollectionAssert.AreEqual(new List<string> {"#channel", "hello world"}, line.Params); 91 } 92 93 [TestMethod] 94 public void TestParamsOnlyTrailing() 95 { 96 var line = new Line("PRIVMSG :hello world"); 97 - CollectionAssert.AreEqual(new List<string> {"hello world"}, line.Params); 98 } 99 100 [TestMethod] ··· 109 public void TestAllTokens() 110 { 111 var line = new Line("@id=123 :nick!user@host PRIVMSG #channel :hello world"); 112 - CollectionAssert.AreEqual(new Dictionary<string, string> {{"id", "123"}}, line.Tags); 113 Assert.AreEqual("nick!user@host", line.Source); 114 Assert.AreEqual("PRIVMSG", line.Command); 115 - CollectionAssert.AreEqual(new List<string> {"#channel", "hello world"}, line.Params); 116 } 117 } 118 }
··· 1 + using Microsoft.VisualStudio.TestTools.UnitTesting; 2 + using System.Collections.Generic; 3 4 namespace IrcTokens.Tests 5 { 6 [TestClass] 7 + public class Tokenization 8 { 9 [TestMethod] 10 public void TestTagsMissing() ··· 87 public void TestParamsTrailing() 88 { 89 var line = new Line("PRIVMSG #channel :hello world"); 90 + CollectionAssert.AreEqual(new List<string> { "#channel", "hello world" }, line.Params); 91 } 92 93 [TestMethod] 94 public void TestParamsOnlyTrailing() 95 { 96 var line = new Line("PRIVMSG :hello world"); 97 + CollectionAssert.AreEqual(new List<string> { "hello world" }, line.Params); 98 } 99 100 [TestMethod] ··· 109 public void TestAllTokens() 110 { 111 var line = new Line("@id=123 :nick!user@host PRIVMSG #channel :hello world"); 112 + CollectionAssert.AreEqual(new Dictionary<string, string> { { "id", "123" } }, line.Tags); 113 Assert.AreEqual("nick!user@host", line.Source); 114 Assert.AreEqual("PRIVMSG", line.Command); 115 + CollectionAssert.AreEqual(new List<string> { "#channel", "hello world" }, line.Params); 116 } 117 } 118 }
+10 -9
Sample/Client.cs
··· 1 - using System; 2 using System.Collections.Generic; 3 using System.Net.Sockets; 4 - using System.Text; 5 - using IrcTokens; 6 7 - namespace Sample 8 { 9 public class Client 10 { ··· 25 { 26 _socket.Connect("127.0.0.1", 6667); 27 28 - Send(new Line {Command = "USER", Params = new List<string> {"username", "0", "*", "real name"}}); 29 - Send(new Line {Command = "NICK", Params = new List<string> {"statefulbot"}}); 30 31 while (true) 32 { 33 var bytesReceived = _socket.Receive(_bytes); 34 var lines = _decoder.Push(_bytes); 35 36 - if (lines.Count == 0) 37 { 38 Console.WriteLine("! disconnected"); 39 _socket.Shutdown(SocketShutdown.Both); ··· 47 switch (line.Command) 48 { 49 case "PING": 50 - Send(new Line {Command = "PONG", Params = line.Params}); 51 break; 52 case "001": 53 - Send(new Line {Command = "JOIN", Params = new List<string> {"#channel"}}); 54 break; 55 } 56 } ··· 62 Console.WriteLine($"> {line.Format()}"); 63 _encoder.Push(line); 64 while (_encoder.PendingBytes.Length > 0) 65 _encoder.Pop(_socket.Send(_encoder.PendingBytes)); 66 } 67 } 68 }
··· 1 + using IrcTokens; 2 + using System; 3 using System.Collections.Generic; 4 using System.Net.Sockets; 5 6 + namespace TokensSample 7 { 8 public class Client 9 { ··· 24 { 25 _socket.Connect("127.0.0.1", 6667); 26 27 + Send(new Line { Command = "USER", Params = new List<string> { "username", "0", "*", "real name" } }); 28 + Send(new Line { Command = "NICK", Params = new List<string> { "tokensbot" } }); 29 30 while (true) 31 { 32 var bytesReceived = _socket.Receive(_bytes); 33 var lines = _decoder.Push(_bytes); 34 35 + if (bytesReceived == 0) 36 { 37 Console.WriteLine("! disconnected"); 38 _socket.Shutdown(SocketShutdown.Both); ··· 46 switch (line.Command) 47 { 48 case "PING": 49 + Send(new Line { Command = "PONG", Params = line.Params }); 50 break; 51 case "001": 52 + Send(new Line { Command = "JOIN", Params = new List<string> { "#channel" } }); 53 break; 54 } 55 } ··· 61 Console.WriteLine($"> {line.Format()}"); 62 _encoder.Push(line); 63 while (_encoder.PendingBytes.Length > 0) 64 + { 65 _encoder.Pop(_socket.Send(_encoder.PendingBytes)); 66 + } 67 } 68 } 69 }
+4 -4
Sample/Program.cs
··· 1 - using System; 2 using System.Collections.Generic; 3 - using IrcTokens; 4 5 - namespace Sample 6 { 7 public class Program 8 { ··· 17 var line2 = new Line 18 { 19 Command = "USER", 20 - Params = new List<string> {"user", "0", "*", "real name"} 21 }; 22 Console.WriteLine(line2); 23 Console.WriteLine(line2.Format());
··· 1 + using IrcTokens; 2 + using System; 3 using System.Collections.Generic; 4 5 + namespace TokensSample 6 { 7 public class Program 8 { ··· 17 var line2 = new Line 18 { 19 Command = "USER", 20 + Params = new List<string> { "user", "0", "*", "real name" } 21 }; 22 Console.WriteLine(line2); 23 Console.WriteLine(line2.Format());
Sample/Sample.csproj Sample/TokensSample.csproj
+12
StatesSample/Program.cs
···
··· 1 + using System; 2 + 3 + namespace StatesSample 4 + { 5 + public static class Program 6 + { 7 + private static void Main(string[] args) 8 + { 9 + Console.WriteLine("Hello World!"); 10 + } 11 + } 12 + }
+12
StatesSample/StatesSample.csproj
···
··· 1 + <Project Sdk="Microsoft.NET.Sdk"> 2 + 3 + <PropertyGroup> 4 + <OutputType>Exe</OutputType> 5 + <TargetFramework>netcoreapp3.1</TargetFramework> 6 + </PropertyGroup> 7 + 8 + <ItemGroup> 9 + <ProjectReference Include="..\IrcStates\IrcStates.csproj" /> 10 + </ItemGroup> 11 + 12 + </Project>