about summary refs log blame commit diff
path: root/IrcTokens/Line.cs
blob: 9056097a58a660289546da51bc05bb534d78f70d (plain) (tree)
1
2
3

                                 
                           














                                                            
                                         
 














                                                                                                     
 
                                                                                            







                                                          











                                                                                                                                  


                                                              












                                                                         
                                                                     
                     
                                                   









                                                                        
                                                              
             
                                            
                                
                                    





                                

                                                                                 









                                                
                                                                          


























                                                                                                                         
                                       









                                                  

                                                                                                  
                                          
                                                                                                       


                                      
                                                                                                                            







                                          
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace IrcTokens
{
    /// <summary>
    /// Tools to represent, parse, and format IRC lines
    /// </summary>
    public class Line
    {
        public Dictionary<string, string> Tags { get; set; }
        public string Source { get; set; }
        public string Command { get; set; }
        public List<string> Params { get; set; }

        private 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 bool Equals(object obj)
        {
            if (obj == null || GetType() != obj.GetType())
                return false;

            return Format() == ((Line) obj).Format();
        }

        public Hostmask Hostmask =>
            _hostmask ??= new Hostmask(Source);

        public Line() { }

        /// <summary>
        /// Build new <see cref="Line"/> object parsed from <param name="line">a string</param>. Analogous to irctokens.tokenise()
        /// </summary>
        /// <param name="line">irc line to parse</param>
        public Line(string line)
        {
            if (string.IsNullOrWhiteSpace(line))
                throw new ArgumentNullException(nameof(line));

            _rawLine = line;
            string[] split;

            if (line.StartsWith('@'))
            {
                Tags = new Dictionary<string, string>();

                split = line.Split(" ");
                var messageTags = split[0];
                line = string.Join(" ", split.Skip(1));

                foreach (var part in messageTags.Substring(1).Split(';'))
                {
                    if (part.Contains('=', StringComparison.Ordinal))
                    {
                        split = part.Split('=', 2);
                        Tags[split[0]] = Protocol.UnescapeTag(split[1]);
                    }
                    else
                    {
                        Tags[part] = string.Empty;
                    }
                }
            }

            string trailing;
            if (line.Contains(" :", StringComparison.Ordinal))
            {
                split = line.Split(" :", 2);
                line = split[0];
                trailing = split[1];
            }
            else
            {
                trailing = null;
            }

            Params = line.Contains(' ', StringComparison.Ordinal)
                ? line.Split(' ', StringSplitOptions.RemoveEmptyEntries).ToList()
                : new List<string> {line};

            if (Params[0].StartsWith(':'))
            {
                Source = Params[0].Substring(1);
                Params.RemoveAt(0);
            }

            if (Params.Count > 0)
            {
                Command = Params[0].ToUpper(CultureInfo.InvariantCulture);
                Params.RemoveAt(0);
            }

            if (trailing != null)
            {
                Params.Add(trailing);
            }
        }

        /// <summary>
        /// Format a <see cref="Line"/> as a standards-compliant IRC line
        /// </summary>
        /// <returns>formatted irc line</returns>
        public string Format()
        {
            var outs = new List<string>();

            if (Tags != null && Tags.Any())
            {
                var tags = Tags.Keys
                    .Select(key => string.IsNullOrWhiteSpace(Tags[key]) ? key : $"{key}={Protocol.EscapeTag(Tags[key])}")
                    .ToList();

                outs.Add($"@{string.Join(";", tags)}");
            }

            if (Source != null)
                outs.Add($":{Source}");

            outs.Add(Command);

            if (Params != null && Params.Any())
            {
                var last = Params[^1];
                Params.RemoveAt(Params.Count - 1);

                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);
            }

            return string.Join(" ", outs);
        }
    }
}