about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBen Harris <ben@tilde.team>2020-04-22 16:28:51 -0400
committerBen Harris <ben@tilde.team>2020-04-22 16:28:51 -0400
commit933a4f85604e21445c9bac8272d64cf3e6f65e00 (patch)
tree5b300ca37beff5cf11ed67a8b6e3550d24cf18a3
parent338763fde71ba2dc0de8ea5e2437d24ee365874b (diff)
rename to IrcSharp
also tidy up formatting with vs tools
-rw-r--r--.editorconfig57
-rw-r--r--IrcSharp.sln (renamed from IrcTokens.sln)19
-rw-r--r--IrcStates/Casemap.cs43
-rw-r--r--IrcStates/Channel.cs69
-rw-r--r--IrcStates/Emit.cs6
-rw-r--r--IrcStates/ISupport.cs7
-rw-r--r--IrcStates/IrcStates.csproj21
-rw-r--r--IrcStates/Numeric.cs49
-rw-r--r--IrcStates/Server.cs6
-rw-r--r--IrcStates/Tests/Cap.cs9
-rw-r--r--IrcStates/Tests/Casemap.cs9
-rw-r--r--IrcStates/Tests/Channel.cs9
-rw-r--r--IrcStates/Tests/Emit.cs9
-rw-r--r--IrcStates/Tests/ISupport.cs10
-rw-r--r--IrcStates/Tests/Mode.cs9
-rw-r--r--IrcStates/Tests/Motd.cs9
-rw-r--r--IrcStates/Tests/User.cs9
-rw-r--r--IrcStates/Tests/Who.cs9
-rw-r--r--IrcStates/User.cs28
-rw-r--r--IrcTokens/Hostmask.cs30
-rw-r--r--IrcTokens/Line.cs47
-rw-r--r--IrcTokens/Protocol.cs2
-rw-r--r--IrcTokens/StatefulDecoder.cs8
-rw-r--r--IrcTokens/StatefulEncoder.cs4
-rw-r--r--IrcTokens/Tests/Format.cs (renamed from IrcTokens/Tests/FormatTests.cs)36
-rw-r--r--IrcTokens/Tests/Hostmask.cs (renamed from IrcTokens/Tests/HostmaskTests.cs)12
-rw-r--r--IrcTokens/Tests/Parser.cs (renamed from IrcTokens/Tests/ParserTests.cs)10
-rw-r--r--IrcTokens/Tests/StatefulDecoder.cs (renamed from IrcTokens/Tests/StatefulDecoderTests.cs)17
-rw-r--r--IrcTokens/Tests/StatefulEncoder.cs (renamed from IrcTokens/Tests/StatefulEncoderTests.cs)11
-rw-r--r--IrcTokens/Tests/Tokenization.cs (renamed from IrcTokens/Tests/TokenizationTests.cs)14
-rw-r--r--Sample/Client.cs19
-rw-r--r--Sample/Program.cs8
-rw-r--r--Sample/TokensSample.csproj (renamed from Sample/Sample.csproj)0
-rw-r--r--StatesSample/Program.cs12
-rw-r--r--StatesSample/StatesSample.csproj12
35 files changed, 548 insertions, 81 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c81e008
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,57 @@
+
+[*.proto]
+indent_style=tab
+indent_size=tab
+tab_width=4
+
+[*.{asax,ascx,aspx,cs,cshtml,css,htm,html,js,jsx,master,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}]
+indent_style=space
+indent_size=4
+tab_width=4
+
+[*.{appxmanifest,axml,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]
+indent_style=space
+indent_size=2
+tab_width=2
+
+[*]
+
+# Microsoft .NET properties
+csharp_new_line_before_members_in_object_initializers=true
+csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
+csharp_style_var_elsewhere=true:suggestion
+csharp_style_var_for_built_in_types=true:suggestion
+csharp_style_var_when_type_is_apparent=true:suggestion
+dotnet_style_parentheses_in_arithmetic_binary_operators=never_if_unnecessary:none
+dotnet_style_parentheses_in_other_binary_operators=never_if_unnecessary:none
+dotnet_style_parentheses_in_relational_binary_operators=never_if_unnecessary:none
+dotnet_style_predefined_type_for_locals_parameters_members=true:suggestion
+dotnet_style_predefined_type_for_member_access=true:suggestion
+dotnet_style_qualification_for_event=false:suggestion
+dotnet_style_qualification_for_field=false:suggestion
+dotnet_style_qualification_for_method=false:suggestion
+dotnet_style_qualification_for_property=false:suggestion
+dotnet_style_require_accessibility_modifiers=for_non_interface_members:suggestion
+
+# ReSharper inspection severities
+resharper_arrange_redundant_parentheses_highlighting=hint
+resharper_arrange_this_qualifier_highlighting=hint
+resharper_arrange_type_member_modifiers_highlighting=hint
+resharper_arrange_type_modifiers_highlighting=hint
+resharper_built_in_type_reference_style_for_member_access_highlighting=hint
+resharper_built_in_type_reference_style_highlighting=hint
+resharper_redundant_base_qualifier_highlighting=warning
+resharper_suggest_var_or_type_built_in_types_highlighting=hint
+resharper_suggest_var_or_type_elsewhere_highlighting=hint
+resharper_suggest_var_or_type_simple_types_highlighting=hint
+
+# ReSharper properties
+resharper_csharp_insert_final_newline=true
+resharper_int_align_assignments=true
+resharper_int_align_switch_expressions=true
+resharper_int_align_switch_sections=true
+resharper_int_align_variables=true
+resharper_keep_existing_embedded_arrangement=false
+resharper_keep_existing_switch_expression_arrangement=false
+resharper_place_simple_case_statement_on_same_line=if_owner_is_single_line
+resharper_xmldoc_max_blank_lines_between_tags=1
diff --git a/IrcTokens.sln b/IrcSharp.sln
index c9b3d28..030a2af 100644
--- a/IrcTokens.sln
+++ b/IrcSharp.sln
@@ -5,7 +5,16 @@ VisualStudioVersion = 16.0.30011.22
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IrcTokens", "IrcTokens\IrcTokens.csproj", "{9E812F45-B2CD-42D2-8378-EBEBF8697905}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sample", "Sample\Sample.csproj", "{A45DA39B-6B47-4713-8049-3B36E0235B67}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokensSample", "Sample\TokensSample.csproj", "{A45DA39B-6B47-4713-8049-3B36E0235B67}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IrcStates", "IrcStates\IrcStates.csproj", "{233E3CB4-61F1-4368-9139-7E9F4A58ED2D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatesSample", "StatesSample\StatesSample.csproj", "{BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A85EB22-D7B4-417F-AC3B-DAFD97DDEA08}"
+	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
+	EndProjectSection
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -21,6 +30,14 @@ Global
 		{A45DA39B-6B47-4713-8049-3B36E0235B67}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{A45DA39B-6B47-4713-8049-3B36E0235B67}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{A45DA39B-6B47-4713-8049-3B36E0235B67}.Release|Any CPU.Build.0 = Release|Any CPU
+		{233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{233E3CB4-61F1-4368-9139-7E9F4A58ED2D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BC9F6696-9D83-4F7A-9E15-CE4D3626C1AF}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/IrcStates/Casemap.cs b/IrcStates/Casemap.cs
new file mode 100644
index 0000000..484c490
--- /dev/null
+++ b/IrcStates/Casemap.cs
@@ -0,0 +1,43 @@
+using System;
+
+namespace IrcStates
+{
+    public static class Casemap
+    {
+        public enum CaseMapping
+        {
+            Rfc1459,
+            Ascii
+        }
+
+        private const string AsciiUpperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+        private const string AsciiLowerChars = "abcdefghijklmnopqrstuvwxyz";
+        private const string Rfc1459UpperChars = AsciiUpperChars + @"[]~\";
+        private const string Rfc1459LowerChars = AsciiLowerChars + @"{}^|";
+
+        private static string Replace(string s, string upper, string lower)
+        {
+            for (var i = 0; i < upper.Length; ++i)
+            {
+                s = s.Replace(upper[i], lower[i]);
+            }
+
+            return s;
+        }
+
+        public static string CaseFold(CaseMapping mapping, string s)
+        {
+            if (s != null)
+            {
+                return mapping switch
+                {
+                    CaseMapping.Rfc1459 => Replace(s, Rfc1459UpperChars, Rfc1459LowerChars),
+                    CaseMapping.Ascii => Replace(s, AsciiUpperChars, AsciiLowerChars),
+                    _ => throw new ArgumentOutOfRangeException(nameof(mapping), mapping, null)
+                };
+            }
+
+            return string.Empty;
+        }
+    }
+}
diff --git a/IrcStates/Channel.cs b/IrcStates/Channel.cs
new file mode 100644
index 0000000..1850e51
--- /dev/null
+++ b/IrcStates/Channel.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace IrcStates
+{
+    public class Channel
+    {
+        public string Name { get; set; }
+        public string NameLower { get; set; }
+        public Dictionary<string, User> Users { get; set; }
+        public string Topic { get; set; }
+        public string TopicSetter { get; set; }
+        public DateTime TopicTime { get; set; }
+        public DateTime Created { get; set; }
+        public Dictionary<string, List<string>> ListModes { get; set; }
+        public Dictionary<string, string> Modes { get; set; }
+
+        public override string ToString()
+        {
+            return $"Channel(name={Name})";
+        }
+
+        public void SetName(string name, string nameLower)
+        {
+            Name = name;
+            NameLower = nameLower;
+        }
+
+        public void AddMode(string ch, string param, bool listMode)
+        {
+            if (listMode)
+            {
+                if (!ListModes.ContainsKey(ch))
+                {
+                    ListModes[ch] = new List<string>();
+                }
+
+                if (ListModes[ch].Contains(param))
+                {
+                    ListModes[ch].Add(param ?? string.Empty);
+                }
+            }
+            else
+            {
+                Modes[ch] = param;
+            }
+        }
+
+        public void RemoveMode(string ch, string param)
+        {
+            if (ListModes.ContainsKey(ch))
+            {
+                if (ListModes[ch].Contains(param))
+                {
+                    ListModes[ch].Remove(param);
+                    if (!ListModes[ch].Any())
+                    {
+                        ListModes.Remove(ch);
+                    }
+                }
+            }
+            else if (Modes.ContainsKey(ch))
+            {
+                Modes.Remove(ch);
+            }
+        }
+    }
+}
diff --git a/IrcStates/Emit.cs b/IrcStates/Emit.cs
new file mode 100644
index 0000000..7fc41ae
--- /dev/null
+++ b/IrcStates/Emit.cs
@@ -0,0 +1,6 @@
+namespace IrcStates
+{
+    public class Emit
+    {
+    }
+}
diff --git a/IrcStates/ISupport.cs b/IrcStates/ISupport.cs
new file mode 100644
index 0000000..91e6d6d
--- /dev/null
+++ b/IrcStates/ISupport.cs
@@ -0,0 +1,7 @@
+// ReSharper disable InconsistentNaming
+namespace IrcStates
+{
+    public class ISupport
+    {
+    }
+}
diff --git a/IrcStates/IrcStates.csproj b/IrcStates/IrcStates.csproj
new file mode 100644
index 0000000..7500c8b
--- /dev/null
+++ b/IrcStates/IrcStates.csproj
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.0" />
+    <PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
+    <PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\IrcTokens\IrcTokens.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/IrcStates/Numeric.cs b/IrcStates/Numeric.cs
new file mode 100644
index 0000000..f5525f6
--- /dev/null
+++ b/IrcStates/Numeric.cs
@@ -0,0 +1,49 @@
+// ReSharper disable InconsistentNaming
+namespace IrcStates
+{
+    public static class Numeric
+    {
+        public const string RPL_WELCOME = "001";
+        public const string RPL_ISUPPORT = "005";
+        public const string RPL_MOTD = "372";
+        public const string RPL_MOTDSTART = "375";
+        public const string RPL_UMODEIS = "221";
+        public const string RPL_VISIBLEHOST = "396";
+
+        public const string RPL_CHANNELMODEIS = "324";
+        public const string RPL_CREATIONTIME = "329";
+        public const string RPL_TOPIC = "332";
+        public const string RPL_TOPICWHOTIME = "333";
+
+        public const string RPL_WHOREPLY = "352";
+        public const string RPL_WHOSPCRPL = "354";
+        public const string RPL_ENDOFWHO = "315";
+        public const string RPL_NAMREPLY = "353";
+        public const string RPL_ENDOFNAMES = "366";
+
+        public const string RPL_BANLIST = "367";
+        public const string RPL_ENDOFBANLIST = "368";
+        public const string RPL_QUIETLIST = "728";
+        public const string RPL_ENDOFQUIETLIST = "729";
+
+        public const string RPL_SASLSUCCESS = "903";
+        public const string ERR_SASLFAIL = "904";
+        public const string ERR_SASLTOOLONG = "905";
+        public const string ERR_SASLABORTED = "906";
+        public const string ERR_SASLALREADY = "907";
+        public const string RPL_SASLMECHS = "908";
+
+        public const string RPL_WHOISUSER = "311";
+        public const string RPL_WHOISSERVER = "312";
+        public const string RPL_WHOISOPERATOR = "313";
+        public const string RPL_WHOISIDLE = "317";
+        public const string RPL_WHOISCHANNELS = "319";
+        public const string RPL_WHOISACCOUNT = "330";
+        public const string RPL_WHOISHOST = "378";
+        public const string RPL_WHOISMODES = "379";
+        public const string RPL_WHOISSECURE = "671";
+        public const string RPL_ENDOFWHOIS = "318";
+
+        public const string ERR_NOSUCHCHANNEL = "403";
+    }
+}
diff --git a/IrcStates/Server.cs b/IrcStates/Server.cs
new file mode 100644
index 0000000..9f97f47
--- /dev/null
+++ b/IrcStates/Server.cs
@@ -0,0 +1,6 @@
+namespace IrcStates
+{
+    public class Server
+    {
+    }
+}
diff --git a/IrcStates/Tests/Cap.cs b/IrcStates/Tests/Cap.cs
new file mode 100644
index 0000000..dcd0e50
--- /dev/null
+++ b/IrcStates/Tests/Cap.cs
@@ -0,0 +1,9 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IrcStates.Tests
+{
+    [TestClass]
+    public class Cap
+    {
+    }
+}
diff --git a/IrcStates/Tests/Casemap.cs b/IrcStates/Tests/Casemap.cs
new file mode 100644
index 0000000..fef9a31
--- /dev/null
+++ b/IrcStates/Tests/Casemap.cs
@@ -0,0 +1,9 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IrcStates.Tests
+{
+    [TestClass]
+    public class Casemap
+    {
+    }
+}
diff --git a/IrcStates/Tests/Channel.cs b/IrcStates/Tests/Channel.cs
new file mode 100644
index 0000000..0de5f37
--- /dev/null
+++ b/IrcStates/Tests/Channel.cs
@@ -0,0 +1,9 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IrcStates.Tests
+{
+    [TestClass]
+    public class Channel
+    {
+    }
+}
diff --git a/IrcStates/Tests/Emit.cs b/IrcStates/Tests/Emit.cs
new file mode 100644
index 0000000..f4495c5
--- /dev/null
+++ b/IrcStates/Tests/Emit.cs
@@ -0,0 +1,9 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IrcStates.Tests
+{
+    [TestClass]
+    public class Emit
+    {
+    }
+}
diff --git a/IrcStates/Tests/ISupport.cs b/IrcStates/Tests/ISupport.cs
new file mode 100644
index 0000000..aa6260a
--- /dev/null
+++ b/IrcStates/Tests/ISupport.cs
@@ -0,0 +1,10 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+// ReSharper disable InconsistentNaming
+
+namespace IrcStates.Tests
+{
+    [TestClass]
+    public class ISupport
+    {
+    }
+}
diff --git a/IrcStates/Tests/Mode.cs b/IrcStates/Tests/Mode.cs
new file mode 100644
index 0000000..e7b70f4
--- /dev/null
+++ b/IrcStates/Tests/Mode.cs
@@ -0,0 +1,9 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IrcStates.Tests
+{
+    [TestClass]
+    public class Mode
+    {
+    }
+}
diff --git a/IrcStates/Tests/Motd.cs b/IrcStates/Tests/Motd.cs
new file mode 100644
index 0000000..8ca7f07
--- /dev/null
+++ b/IrcStates/Tests/Motd.cs
@@ -0,0 +1,9 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IrcStates.Tests
+{
+    [TestClass]
+    public class Motd
+    {
+    }
+}
diff --git a/IrcStates/Tests/User.cs b/IrcStates/Tests/User.cs
new file mode 100644
index 0000000..540d31f
--- /dev/null
+++ b/IrcStates/Tests/User.cs
@@ -0,0 +1,9 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IrcStates.Tests
+{
+    [TestClass]
+    public class User
+    {
+    }
+}
diff --git a/IrcStates/Tests/Who.cs b/IrcStates/Tests/Who.cs
new file mode 100644
index 0000000..359991c
--- /dev/null
+++ b/IrcStates/Tests/Who.cs
@@ -0,0 +1,9 @@
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IrcStates.Tests
+{
+    [TestClass]
+    public class Who
+    {
+    }
+}
diff --git a/IrcStates/User.cs b/IrcStates/User.cs
new file mode 100644
index 0000000..9bc5ce8
--- /dev/null
+++ b/IrcStates/User.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+
+namespace IrcStates
+{
+    public class User
+    {
+        private string NickName;
+        private string NickNameLower;
+
+        public string UserName { get; set; }
+        public string HostName { get; set; }
+        public string RealName { get; set; }
+        public string Account { get; set; }
+        public string Away { get; set; }
+        public HashSet<string> Channels { get; set; }
+
+        public override string ToString()
+        {
+            return $"User(nickname={NickName})";
+        }
+
+        public void SetNickName(string nick, string nickLower)
+        {
+            NickName = nick;
+            NickNameLower = nickLower;
+        }
+    }
+}
diff --git a/IrcTokens/Hostmask.cs b/IrcTokens/Hostmask.cs
index 0b07f80..01fe7d5 100644
--- a/IrcTokens/Hostmask.cs
+++ b/IrcTokens/Hostmask.cs
@@ -5,29 +5,45 @@ namespace IrcTokens
     /// <summary>
     /// Represents the three parts of a hostmask. Parse with the constructor.
     /// </summary>
-    public class Hostmask
+    public class Hostmask : IEquatable<Hostmask>
     {
         public string NickName { get; set; }
         public string UserName { get; set; }
         public string HostName { get; set; }
 
-        public override string ToString() => _source;
+        public override string ToString()
+        {
+            return _source;
+        }
 
-        public override int GetHashCode() => _source.GetHashCode(StringComparison.Ordinal);
+        public override int GetHashCode()
+        {
+            return _source.GetHashCode(StringComparison.Ordinal);
+        }
 
-        public override bool Equals(object obj)
+        public bool Equals(Hostmask other)
         {
-            if (obj == null || GetType() != obj.GetType())
+            if (other == null)
+            {
                 return false;
+            }
 
-            return _source == ((Hostmask) obj)._source;
+            return _source == other._source;
+        }
+
+        public override bool Equals(object obj)
+        {
+            return Equals(obj as Hostmask);
         }
 
         private readonly string _source;
 
         public Hostmask(string source)
         {
-            if (source == null) return;
+            if (source == null)
+            {
+                return;
+            }
 
             _source = source;
 
diff --git a/IrcTokens/Line.cs b/IrcTokens/Line.cs
index 9056097..24efe4a 100644
--- a/IrcTokens/Line.cs
+++ b/IrcTokens/Line.cs
@@ -8,7 +8,7 @@ namespace IrcTokens
     /// <summary>
     /// Tools to represent, parse, and format IRC lines
     /// </summary>
-    public class Line
+    public class Line : IEquatable<Line>
     {
         public Dictionary<string, string> Tags { get; set; }
         public string Source { get; set; }
@@ -16,32 +16,52 @@ namespace IrcTokens
         public List<string> Params { get; set; }
 
         private Hostmask _hostmask;
-        private readonly string _rawLine;
 
         public override string ToString()
         {
             var vars = new List<string>();
 
             if (Command != null)
+            {
                 vars.Add($"command={Command}");
+            }
+
             if (Source != null)
+            {
                 vars.Add($"source={Source}");
+            }
+
             if (Params != null && Params.Any())
+            {
                 vars.Add($"params=[{string.Join(",", Params)}]");
+            }
+
             if (Tags != null && Tags.Any())
+            {
                 vars.Add($"tags=[{string.Join(";", Tags.Select(kvp => $"{kvp.Key}={kvp.Value}"))}]");
+            }
 
             return $"Line({string.Join(", ", vars)})";
         }
 
-        public override int GetHashCode() => Format().GetHashCode(StringComparison.Ordinal);
+        public override int GetHashCode()
+        {
+            return Format().GetHashCode(StringComparison.Ordinal);
+        }
 
-        public override bool Equals(object obj)
+        public bool Equals(Line other)
         {
-            if (obj == null || GetType() != obj.GetType())
+            if (other == null)
+            {
                 return false;
+            }
 
-            return Format() == ((Line) obj).Format();
+            return Format() == other.Format();
+        }
+
+        public override bool Equals(object obj)
+        {
+            return Equals(obj as Line);
         }
 
         public Hostmask Hostmask =>
@@ -56,9 +76,10 @@ namespace IrcTokens
         public Line(string line)
         {
             if (string.IsNullOrWhiteSpace(line))
+            {
                 throw new ArgumentNullException(nameof(line));
+            }
 
-            _rawLine = line;
             string[] split;
 
             if (line.StartsWith('@'))
@@ -97,7 +118,7 @@ namespace IrcTokens
 
             Params = line.Contains(' ', StringComparison.Ordinal)
                 ? line.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList()
-                : new List<string> {line};
+                : new List<string> { line };
 
             if (Params[0].StartsWith(':'))
             {
@@ -135,7 +156,9 @@ namespace IrcTokens
             }
 
             if (Source != null)
+            {
                 outs.Add($":{Source}");
+            }
 
             outs.Add(Command);
 
@@ -147,14 +170,22 @@ namespace IrcTokens
                 foreach (var p in Params)
                 {
                     if (p.Contains(' ', StringComparison.Ordinal))
+                    {
                         throw new ArgumentException(@"non-last parameters cannot have spaces", p);
+                    }
+
                     if (p.StartsWith(':'))
+                    {
                         throw new ArgumentException(@"non-last parameters cannot start with colon", p);
+                    }
                 }
                 outs.AddRange(Params);
 
                 if (string.IsNullOrWhiteSpace(last) || last.Contains(' ', StringComparison.Ordinal) || last.StartsWith(':'))
+                {
                     last = $":{last}";
+                }
+
                 outs.Add(last);
             }
 
diff --git a/IrcTokens/Protocol.cs b/IrcTokens/Protocol.cs
index 3769ea3..6ddb079 100644
--- a/IrcTokens/Protocol.cs
+++ b/IrcTokens/Protocol.cs
@@ -50,7 +50,9 @@ namespace IrcTokens
                     }
                 }
                 else
+                {
                     unescaped.Append(current);
+                }
             }
 
             return unescaped.ToString();
diff --git a/IrcTokens/StatefulDecoder.cs b/IrcTokens/StatefulDecoder.cs
index 2304431..47106d9 100644
--- a/IrcTokens/StatefulDecoder.cs
+++ b/IrcTokens/StatefulDecoder.cs
@@ -18,8 +18,10 @@ namespace IrcTokens
             set
             {
                 if (value != null)
+                {
                     _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback,
                         DecoderFallback.ReplacementFallback);
+                }
             }
         }
 
@@ -30,8 +32,10 @@ namespace IrcTokens
             set
             {
                 if (value != null)
-                    _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ReplacementFallback,
+                {
+                    _fallback = Encoding.GetEncoding(value.CodePage, EncoderFallback.ReplacementFallback,
                         DecoderFallback.ReplacementFallback);
+                }
             }
         }
 
@@ -55,7 +59,9 @@ namespace IrcTokens
         public List<Line> Push(byte[] data)
         {
             if (data == null || data.Length == 0)
+            {
                 return null;
+            }
 
             _buffer = _buffer.Concat(data).ToArray();
 
diff --git a/IrcTokens/StatefulEncoder.cs b/IrcTokens/StatefulEncoder.cs
index 17295eb..c036400 100644
--- a/IrcTokens/StatefulEncoder.cs
+++ b/IrcTokens/StatefulEncoder.cs
@@ -16,8 +16,10 @@ namespace IrcTokens
             set
             {
                 if (value != null)
+                {
                     _encoding = Encoding.GetEncoding(value.CodePage, EncoderFallback.ExceptionFallback,
                         DecoderFallback.ExceptionFallback);
+                }
             }
         }
 
@@ -52,7 +54,9 @@ namespace IrcTokens
         public void Push(Line line)
         {
             if (line == null)
+            {
                 throw new ArgumentNullException(nameof(line));
+            }
 
             PendingBytes = PendingBytes.Concat(Encoding.GetBytes($"{line.Format()}\r\n")).ToArray();
             _bufferedLines.Enqueue(line);
diff --git a/IrcTokens/Tests/FormatTests.cs b/IrcTokens/Tests/Format.cs
index a804c1d..64f974a 100644
--- a/IrcTokens/Tests/FormatTests.cs
+++ b/IrcTokens/Tests/Format.cs
@@ -5,16 +5,16 @@ using System.Collections.Generic;
 namespace IrcTokens.Tests
 {
     [TestClass]
-    public class FormatTests
+    public class Format
     {
         [TestMethod]
         public void TestTags()
         {
             var line = new Line
             {
-                Command = "PRIVMSG", 
-                Params = new List<string> {"#channel", "hello"},
-                Tags = new Dictionary<string, string> {{"id", "\\" + " " + ";" + "\r\n"}}
+                Command = "PRIVMSG",
+                Params = new List<string> { "#channel", "hello" },
+                Tags = new Dictionary<string, string> { { "id", "\\" + " " + ";" + "\r\n" } }
             }.Format();
 
             Assert.AreEqual("@id=\\\\\\s\\:\\r\\n PRIVMSG #channel hello", line);
@@ -25,8 +25,8 @@ namespace IrcTokens.Tests
         {
             var line = new Line
             {
-                Command = "PRIVMSG", 
-                Params = new List<string> {"#channel", "hello"}
+                Command = "PRIVMSG",
+                Params = new List<string> { "#channel", "hello" }
             }.Format();
 
             Assert.AreEqual("PRIVMSG #channel hello", line);
@@ -38,8 +38,8 @@ namespace IrcTokens.Tests
             var line = new Line
             {
                 Command = "PRIVMSG",
-                Params = new List<string> {"#channel", "hello"},
-                Tags = new Dictionary<string, string> {{"a", null}}
+                Params = new List<string> { "#channel", "hello" },
+                Tags = new Dictionary<string, string> { { "a", null } }
             }.Format();
 
             Assert.AreEqual("@a PRIVMSG #channel hello", line);
@@ -51,8 +51,8 @@ namespace IrcTokens.Tests
             var line = new Line
             {
                 Command = "PRIVMSG",
-                Params = new List<string> {"#channel", "hello"},
-                Tags = new Dictionary<string, string> {{"a", ""}}
+                Params = new List<string> { "#channel", "hello" },
+                Tags = new Dictionary<string, string> { { "a", "" } }
             }.Format();
 
             Assert.AreEqual("@a PRIVMSG #channel hello", line);
@@ -64,7 +64,7 @@ namespace IrcTokens.Tests
             var line = new Line
             {
                 Command = "PRIVMSG",
-                Params = new List<string> {"#channel", "hello"},
+                Params = new List<string> { "#channel", "hello" },
                 Source = "nick!user@host"
             }.Format();
 
@@ -74,14 +74,14 @@ namespace IrcTokens.Tests
         [TestMethod]
         public void TestCommandLowercase()
         {
-            var line = new Line {Command = "privmsg"}.Format();
+            var line = new Line { Command = "privmsg" }.Format();
             Assert.AreEqual("privmsg", line);
         }
 
         [TestMethod]
         public void TestCommandUppercase()
         {
-            var line = new Line {Command = "PRIVMSG"}.Format();
+            var line = new Line { Command = "PRIVMSG" }.Format();
             Assert.AreEqual("PRIVMSG", line);
         }
 
@@ -91,7 +91,7 @@ namespace IrcTokens.Tests
             var line = new Line
             {
                 Command = "PRIVMSG",
-                Params = new List<string> {"#channel", "hello world"}
+                Params = new List<string> { "#channel", "hello world" }
             }.Format();
 
             Assert.AreEqual("PRIVMSG #channel :hello world", line);
@@ -103,7 +103,7 @@ namespace IrcTokens.Tests
             var line = new Line
             {
                 Command = "PRIVMSG",
-                Params = new List<string> {"#channel", "helloworld"}
+                Params = new List<string> { "#channel", "helloworld" }
             }.Format();
 
             Assert.AreEqual("PRIVMSG #channel helloworld", line);
@@ -115,7 +115,7 @@ namespace IrcTokens.Tests
             var line = new Line
             {
                 Command = "PRIVMSG",
-                Params = new List<string> {"#channel", ":helloworld"}
+                Params = new List<string> { "#channel", ":helloworld" }
             }.Format();
 
             Assert.AreEqual("PRIVMSG #channel ::helloworld", line);
@@ -129,7 +129,7 @@ namespace IrcTokens.Tests
                 new Line
                 {
                     Command = "USER",
-                    Params = new List<string> {"user", "0 *", "real name"}
+                    Params = new List<string> { "user", "0 *", "real name" }
                 }.Format();
             });
         }
@@ -142,7 +142,7 @@ namespace IrcTokens.Tests
                 new Line
                 {
                     Command = "PRIVMSG",
-                    Params = new List<string> {":#channel", "hello"}
+                    Params = new List<string> { ":#channel", "hello" }
                 }.Format();
             });
         }
diff --git a/IrcTokens/Tests/HostmaskTests.cs b/IrcTokens/Tests/Hostmask.cs
index 78b8a54..51bc182 100644
--- a/IrcTokens/Tests/HostmaskTests.cs
+++ b/IrcTokens/Tests/Hostmask.cs
@@ -3,12 +3,12 @@
 namespace IrcTokens.Tests
 {
     [TestClass]
-    public class HostmaskTests
+    public class Hostmask
     {
         [TestMethod]
         public void TestHostmask()
         {
-            var hostmask = new Hostmask("nick!user@host");
+            var hostmask = new IrcTokens.Hostmask("nick!user@host");
             Assert.AreEqual("nick", hostmask.NickName);
             Assert.AreEqual("user", hostmask.UserName);
             Assert.AreEqual("host", hostmask.HostName);
@@ -17,7 +17,7 @@ namespace IrcTokens.Tests
         [TestMethod]
         public void TestNoHostName()
         {
-            var hostmask = new Hostmask("nick!user");
+            var hostmask = new IrcTokens.Hostmask("nick!user");
             Assert.AreEqual("nick", hostmask.NickName);
             Assert.AreEqual("user", hostmask.UserName);
             Assert.IsNull(hostmask.HostName);
@@ -26,7 +26,7 @@ namespace IrcTokens.Tests
         [TestMethod]
         public void TestNoUserName()
         {
-            var hostmask = new Hostmask("nick@host");
+            var hostmask = new IrcTokens.Hostmask("nick@host");
             Assert.AreEqual("nick", hostmask.NickName);
             Assert.IsNull(hostmask.UserName);
             Assert.AreEqual("host", hostmask.HostName);
@@ -35,7 +35,7 @@ namespace IrcTokens.Tests
         [TestMethod]
         public void TestOnlyNickName()
         {
-            var hostmask = new Hostmask("nick");
+            var hostmask = new IrcTokens.Hostmask("nick");
             Assert.AreEqual("nick", hostmask.NickName);
             Assert.IsNull(hostmask.UserName);
             Assert.IsNull(hostmask.HostName);
@@ -45,7 +45,7 @@ namespace IrcTokens.Tests
         public void TestHostmaskFromLine()
         {
             var line = new Line(":nick!user@host PRIVMSG #channel hello");
-            var hostmask = new Hostmask("nick!user@host");
+            var hostmask = new IrcTokens.Hostmask("nick!user@host");
             Assert.AreEqual(hostmask.ToString(), line.Hostmask.ToString());
             Assert.AreEqual("nick", line.Hostmask.NickName);
             Assert.AreEqual("user", line.Hostmask.UserName);
diff --git a/IrcTokens/Tests/ParserTests.cs b/IrcTokens/Tests/Parser.cs
index ad734cf..df70b92 100644
--- a/IrcTokens/Tests/ParserTests.cs
+++ b/IrcTokens/Tests/Parser.cs
@@ -1,15 +1,15 @@
-using System.Collections.Generic;
+using IrcTokens.Tests.Data;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
-using IrcTokens.Tests.Data;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
 using YamlDotNet.Serialization;
 using YamlDotNet.Serialization.NamingConventions;
 
 namespace IrcTokens.Tests
 {
     [TestClass]
-    public class ParserTests
+    public class Parser
     {
         private static T LoadYaml<T>(string path)
         {
@@ -46,7 +46,7 @@ namespace IrcTokens.Tests
                 {
                     Command = atoms.Verb,
                     Params = atoms.Params,
-                    Source = atoms.Source ?? null,
+                    Source = atoms.Source,
                     Tags = atoms.Tags
                 }.Format();
 
diff --git a/IrcTokens/Tests/StatefulDecoderTests.cs b/IrcTokens/Tests/StatefulDecoder.cs
index 3e6a078..209a6cf 100644
--- a/IrcTokens/Tests/StatefulDecoderTests.cs
+++ b/IrcTokens/Tests/StatefulDecoder.cs
@@ -1,19 +1,18 @@
-using System.Collections.Generic;
-using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Collections.Generic;
 using System.Text;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
 
 namespace IrcTokens.Tests
 {
     [TestClass]
-    public class StatefulDecoderTests
+    public class StatefulDecoder
     {
-        private StatefulDecoder _decoder;
+        private IrcTokens.StatefulDecoder _decoder;
 
         [TestInitialize]
         public void TestInitialize()
         {
-            _decoder = new StatefulDecoder();
+            _decoder = new IrcTokens.StatefulDecoder();
         }
 
         [TestMethod]
@@ -26,7 +25,7 @@ namespace IrcTokens.Tests
             Assert.AreEqual(1, lines.Count);
 
             var line = new Line("PRIVMSG #channel hello");
-            CollectionAssert.AreEqual(new List<Line> {line}, lines);
+            CollectionAssert.AreEqual(new List<Line> { line }, lines);
         }
 
         [TestMethod]
@@ -45,7 +44,7 @@ namespace IrcTokens.Tests
         public void TestEncoding()
         {
             var iso8859 = Encoding.GetEncoding("iso-8859-1");
-            _decoder = new StatefulDecoder {Encoding = iso8859};
+            _decoder = new IrcTokens.StatefulDecoder { Encoding = iso8859 };
             var lines = _decoder.Push(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"));
             var line = new Line("PRIVMSG #channel :hello Ç");
             Assert.IsTrue(line.Equals(lines[0]));
@@ -55,7 +54,7 @@ namespace IrcTokens.Tests
         public void TestEncodingFallback()
         {
             var latin1 = Encoding.GetEncoding("iso-8859-1");
-            _decoder = new StatefulDecoder {Encoding = null, Fallback = latin1};
+            _decoder = new IrcTokens.StatefulDecoder { Encoding = null, Fallback = latin1 };
             var lines = _decoder.Push(latin1.GetBytes("PRIVMSG #channel hélló\r\n"));
             Assert.AreEqual(1, lines.Count);
             Assert.IsTrue(new Line("PRIVMSG #channel hélló").Equals(lines[0]));
diff --git a/IrcTokens/Tests/StatefulEncoderTests.cs b/IrcTokens/Tests/StatefulEncoder.cs
index 477b38d..e3ed70d 100644
--- a/IrcTokens/Tests/StatefulEncoderTests.cs
+++ b/IrcTokens/Tests/StatefulEncoder.cs
@@ -1,18 +1,17 @@
-using System.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
 using System.Text;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
 
 namespace IrcTokens.Tests
 {
     [TestClass]
-    public class StatefulEncoderTests
+    public class StatefulEncoder
     {
-        private StatefulEncoder _encoder;
+        private IrcTokens.StatefulEncoder _encoder;
 
         [TestInitialize]
         public void TestInitialize()
         {
-            _encoder = new StatefulEncoder();
+            _encoder = new IrcTokens.StatefulEncoder();
         }
 
         [TestMethod]
@@ -64,7 +63,7 @@ namespace IrcTokens.Tests
         public void TestEncoding()
         {
             var iso8859 = Encoding.GetEncoding("iso-8859-1");
-            _encoder = new StatefulEncoder {Encoding = iso8859};
+            _encoder = new IrcTokens.StatefulEncoder { Encoding = iso8859 };
             _encoder.Push(new Line("PRIVMSG #channel :hello Ç"));
             CollectionAssert.AreEqual(iso8859.GetBytes("PRIVMSG #channel :hello Ç\r\n"), _encoder.PendingBytes);
         }
diff --git a/IrcTokens/Tests/TokenizationTests.cs b/IrcTokens/Tests/Tokenization.cs
index 6d8a69d..c4970ed 100644
--- a/IrcTokens/Tests/TokenizationTests.cs
+++ b/IrcTokens/Tests/Tokenization.cs
@@ -1,10 +1,10 @@
-using System.Collections.Generic;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.Collections.Generic;
 
 namespace IrcTokens.Tests
 {
     [TestClass]
-    public class TokenizationTests
+    public class Tokenization
     {
         [TestMethod]
         public void TestTagsMissing()
@@ -87,14 +87,14 @@ namespace IrcTokens.Tests
         public void TestParamsTrailing()
         {
             var line = new Line("PRIVMSG #channel :hello world");
-            CollectionAssert.AreEqual(new List<string> {"#channel", "hello world"}, line.Params);
+            CollectionAssert.AreEqual(new List<string> { "#channel", "hello world" }, line.Params);
         }
 
         [TestMethod]
         public void TestParamsOnlyTrailing()
         {
             var line = new Line("PRIVMSG :hello world");
-            CollectionAssert.AreEqual(new List<string> {"hello world"}, line.Params);
+            CollectionAssert.AreEqual(new List<string> { "hello world" }, line.Params);
         }
 
         [TestMethod]
@@ -109,10 +109,10 @@ namespace IrcTokens.Tests
         public void TestAllTokens()
         {
             var line = new Line("@id=123 :nick!user@host PRIVMSG #channel :hello world");
-            CollectionAssert.AreEqual(new Dictionary<string, string> {{"id", "123"}}, line.Tags);
+            CollectionAssert.AreEqual(new Dictionary<string, string> { { "id", "123" } }, line.Tags);
             Assert.AreEqual("nick!user@host", line.Source);
             Assert.AreEqual("PRIVMSG", line.Command);
-            CollectionAssert.AreEqual(new List<string> {"#channel", "hello world"}, line.Params);
+            CollectionAssert.AreEqual(new List<string> { "#channel", "hello world" }, line.Params);
         }
     }
 }
diff --git a/Sample/Client.cs b/Sample/Client.cs
index e9e286f..b756adf 100644
--- a/Sample/Client.cs
+++ b/Sample/Client.cs
@@ -1,10 +1,9 @@
-using System;
+using IrcTokens;
+using System;
 using System.Collections.Generic;
 using System.Net.Sockets;
-using System.Text;
-using IrcTokens;
 
-namespace Sample
+namespace TokensSample
 {
     public class Client
     {
@@ -25,15 +24,15 @@ namespace Sample
         {
             _socket.Connect("127.0.0.1", 6667);
 
-            Send(new Line {Command = "USER", Params = new List<string> {"username", "0", "*", "real name"}});
-            Send(new Line {Command = "NICK", Params = new List<string> {"statefulbot"}});
+            Send(new Line { Command = "USER", Params = new List<string> { "username", "0", "*", "real name" } });
+            Send(new Line { Command = "NICK", Params = new List<string> { "tokensbot" } });
 
             while (true)
             {
                 var bytesReceived = _socket.Receive(_bytes);
                 var lines = _decoder.Push(_bytes);
 
-                if (lines.Count == 0)
+                if (bytesReceived == 0)
                 {
                     Console.WriteLine("! disconnected");
                     _socket.Shutdown(SocketShutdown.Both);
@@ -47,10 +46,10 @@ namespace Sample
                     switch (line.Command)
                     {
                         case "PING":
-                            Send(new Line {Command = "PONG", Params = line.Params});
+                            Send(new Line { Command = "PONG", Params = line.Params });
                             break;
                         case "001":
-                            Send(new Line {Command = "JOIN", Params = new List<string> {"#channel"}});
+                            Send(new Line { Command = "JOIN", Params = new List<string> { "#channel" } });
                             break;
                     }
                 }
@@ -62,7 +61,9 @@ namespace Sample
             Console.WriteLine($"> {line.Format()}");
             _encoder.Push(line);
             while (_encoder.PendingBytes.Length > 0)
+            {
                 _encoder.Pop(_socket.Send(_encoder.PendingBytes));
+            }
         }
     }
 }
diff --git a/Sample/Program.cs b/Sample/Program.cs
index 6800179..eda312f 100644
--- a/Sample/Program.cs
+++ b/Sample/Program.cs
@@ -1,8 +1,8 @@
-using System;
+using IrcTokens;
+using System;
 using System.Collections.Generic;
-using IrcTokens;
 
-namespace Sample
+namespace TokensSample
 {
     public class Program
     {
@@ -17,7 +17,7 @@ namespace Sample
             var line2 = new Line
             {
                 Command = "USER",
-                Params = new List<string> {"user", "0", "*", "real name"}
+                Params = new List<string> { "user", "0", "*", "real name" }
             };
             Console.WriteLine(line2);
             Console.WriteLine(line2.Format());
diff --git a/Sample/Sample.csproj b/Sample/TokensSample.csproj
index 7c66734..7c66734 100644
--- a/Sample/Sample.csproj
+++ b/Sample/TokensSample.csproj
diff --git a/StatesSample/Program.cs b/StatesSample/Program.cs
new file mode 100644
index 0000000..8fc42b8
--- /dev/null
+++ b/StatesSample/Program.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace StatesSample
+{
+    public static class Program
+    {
+        private static void Main(string[] args)
+        {
+            Console.WriteLine("Hello World!");
+        }
+    }
+}
diff --git a/StatesSample/StatesSample.csproj b/StatesSample/StatesSample.csproj
new file mode 100644
index 0000000..3107344
--- /dev/null
+++ b/StatesSample/StatesSample.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\IrcStates\IrcStates.csproj" />
+  </ItemGroup>
+
+</Project>