<commit_before>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using CommandLineParser.Attributes;
using CommandLineParser.Helpers;

namespace CommandLineParser
{
    public class CommandParser
    {
        private const string HELP_KEY_REGEX = @"^(--help)|(-h)|(-\?)|(\\\?)$";
        private const string KEY_REGEX = @"^--?(\w|\?)+$";
        private string[] _args { get; set; }

        public CommandParser()
        {
        }

        public CommandParser(string[] _args = null)
        {
            this._args = _args;
        }

        public Dictionary<string, string> GetDictionary(string[] args = null, Type targetType = null)
        {
            args = args ?? this._args ?? new string[] { };
            this._args = args;
            var ret = new Dictionary<string, string>();
            string key = string.Empty;
            foreach (string arg in args)
            {
                if (KeyDetection.GetShortKeyDetector().IsKey(arg))
                {
                    if (KeyDetection.GetShortKeyDetector().IsJoinedToValue(arg))
                    {
                        string[] split = arg.Split('=');
                        foreach(char potentialKey in split.First().AsEnumerable().Skip(1))
                        {
                            key = "-" + potentialKey;
                            ret[key] = string.Empty;
                        }
                        ret[key] = split.Last(); //to hold the value of the key before the equals sign incase of aggregation like -asu=mykeels
                    }
                    else if (KeyDetection.GetShortKeyDetector().IsAggregated(arg, _getShortKeys(targetType))) //works on aggregate short keys like -sa
                    {
                        foreach (string potentialKey in KeyDetection.GetShortKeyDetector().GetAggregatedKeys(arg, _getShortKeys(targetType)))
                        {
                            ret[potentialKey] = string.Empty;
                            key = potentialKey;
                        }
                    }
                    else if (KeyDetection.GetShortKeyDetector().IsFollowedByValue(arg))
                    {
                        var result = KeyDetection.GetShortKeyDetector().IsFollowedByValue(arg, _getShortKeys(targetType));
                        string[] keys = result.Item2.Key;
                        string value = result.Item2.Value;
                        foreach (string potentialKey in keys)
                        {
                            key = potentialKey;
                            ret[key] = string.Empty;
                        }
                        ret[key] = value;
                    }
                    else
                    {
                        ret[arg] = string.Empty; //works for normal short keys like -u
                        key = arg;
                    }
                }
                else if (KeyDetection.GetLongKeyDetector().IsKey(arg))
                {
                    if (KeyDetection.GetLongKeyDetector().IsJoinedToValue(arg))
                    {
                        string[] split = arg.Split('=');
                        key = split.First();
                        ret[key] = split.Last();
                    }
                    else
                    {
                        ret[arg] = string.Empty;
                        key = arg;
                    }
                }
                else
                {
                    ret[key] += arg;
                }
                /*isKey = Regex.IsMatch(arg, KEY_REGEX);
                if (isKey)
                {
                    ret[arg] = string.Empty;
                    key = arg;
                }
                else
                {
                    ret[key] += arg;
                }*/
            }
            return ret;
        }

        private char[] _getShortKeys(Type targetType)
        {
            List<string> ret = new List<string>();
            PropertyInfo[] properties = targetType.GetProperties();
            foreach (var property in properties)
            {
                var flag = property.GetCustomAttribute<FlagAttribute>();
                ret.Add(flag.ShortName);
            }
            return ret.Select((key) => key.ElementAt(0)).ToArray();
        }

        public TData Parse<TData>(string[] args = null)
        {
            args = args ?? this._args ?? new string[] { };
            this._args = args;
            var _dict = GetDictionary(args, typeof(TData));
            TData ret = System.Activator.CreateInstance<TData>();
            System.Reflection.PropertyInfo[] properties = ret.GetType().GetProperties();
            foreach (var property in properties)
            {
                var flag = property.GetCustomAttribute<FlagAttribute>();
                var help = property.GetCustomAttribute<HelpAttribute>();
                var transform = property.GetCustomAttribute<TransformAttribute>();
                flag = flag ?? new FlagAttribute(property.Name);
                string keyPattern = $@"(^-{flag.ShortName}$)|(^--{flag.Name}$)";
                string key = _dict.Keys.FirstOrDefault(_key => Regex.IsMatch(_key, keyPattern, RegexOptions.IgnoreCase));
                if (key == null && flag.Required)
                {
                    throw new Exception($"Could not find argument key for --{property.Name} which is required");
                }
                else if (key != null)
                {   
                    switch (property.PropertyType.Name)
                    {
                        case "String":
                            try { property.SetValue(ret, _dict[key]); } catch { throw new Exception($"Could not convert argument value of \"{_dict[key]}\" to String"); }
                            break;
                        case "Int32":
                            try { property.SetValue(ret, Convert.ToInt32(_dict[key])); } catch { throw new Exception($"Could not convert argument value of \"{_dict[key]}\" to Int32"); }
                            break;
                        case "Int64":
                            try { property.SetValue(ret, Convert.ToInt64(_dict[key])); } catch { throw new Exception($"Could not convert argument value of \"{_dict[key]}\" to Int64"); }
                            break;
                        case "DateTime":
                            try { property.SetValue(ret, Convert.ToDateTime(_dict[key])); } catch { throw new Exception($"Could not convert argument value of \"{_dict[key]}\" to DateTime"); }
                            break;
                        case "Boolean":
                            if (string.IsNullOrWhiteSpace(_dict[key])) property.SetValue(ret, true); //default to true if no value is specified cos it's a boolean
                            else try { property.SetValue(ret, Convert.ToBoolean(_dict[key])); } catch { throw new Exception($"Could not convert argument value of \"{_dict[key]}\" to Boolean"); }
                            break;
                        default:
                            if (transform != null)
                            {
                                property.SetValue(ret, transform.Execute.DynamicInvoke(_dict[key]));
                            }
                            else
                            {
                                if (property.PropertyType.BaseType.Name == "Enum")
                                {
                                    try { property.SetValue(ret, Enum.Parse(property.PropertyType, _dict[key])); } catch { throw new Exception($"Could not convert argument value of \"{_dict[key]}\" to Enum"); }
                                }
                                else throw new Exception($"Type of [{property.Name}] is not recognized");
                            }
                            break;
                    }
                }
            }
            return ret;
        }

        private bool _matchesCliKey(PropertyInfo property, string key)
        {
            var flag = property.GetCustomAttribute<FlagAttribute>();
            flag = flag ?? new FlagAttribute(property.Name);
            string keyPattern = $@"(^-{flag.ShortName}$)|(^--{flag.Name}$)";
            return Regex.IsMatch(key, keyPattern);
        }

        private string _getPropKeyHelpInfo(PropertyInfo property, bool includeDescription = false)
        {<commit_msg>refactor: modified key-value dictionary to accept list of values for composite flags<commit_after>﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using CommandLineParser.Attributes;
using CommandLineParser.Helpers;

namespace CommandLineParser
{
    public class CommandParser
    {
        private const string HELP_KEY_REGEX = @"^(--help)|(-h)|(-\?)|(\\\?)$";
        private const string KEY_REGEX = @"^--?(\w|\?)+$";
        private string[] _args { get; set; }

        public CommandParser()
        {
        }

        public CommandParser(string[] _args = null)
        {
            this._args = _args;
        }

        public Dictionary<string, List<string>> GetDictionary(string[] args = null, Type targetType = null)
        {
            args = args ?? this._args ?? new string[] { };
            this._args = args;
            var ret = new Dictionary<string, List<string>>();
            string key = string.Empty;
            foreach (string arg in args)
            {
                if (KeyDetection.GetShortKeyDetector().IsKey(arg))
                {
                    if (KeyDetection.GetShortKeyDetector().IsJoinedToValue(arg))
                    {
                        string[] split = arg.Split('=');
                        foreach (char potentialKey in split.First().AsEnumerable().Skip(1))
                        {
                            key = "-" + potentialKey;
                            ret[key] = new List<string>();
                        }
                        ret[key].Add(split.Last()); //to hold the value of the key before the equals sign incase of aggregation like -asu=mykeels
                    }
                    else if (KeyDetection.GetShortKeyDetector().IsAggregated(arg, _getShortKeys(targetType))) //works on aggregate short keys like -sa
                    {
                        foreach (string potentialKey in KeyDetection.GetShortKeyDetector().GetAggregatedKeys(arg, _getShortKeys(targetType)))
                        {
                            ret[potentialKey] = new List<string>();
                            key = potentialKey;
                        }
                    }
                    else if (KeyDetection.GetShortKeyDetector().IsFollowedByValue(arg))
                    {
                        var result = KeyDetection.GetShortKeyDetector().IsFollowedByValue(arg, _getShortKeys(targetType));
                        string[] keys = result.Item2.Key;
                        string value = result.Item2.Value;
                        foreach (string potentialKey in keys)
                        {
                            key = potentialKey;
                            ret[key] = new List<string>();
                        }
                        ret[key].Add(value);
                    }
                    else
                    {
                        ret[arg] = new List<string>(); //works for normal short keys like -u
                        key = arg;
                    }
                }
                else if (KeyDetection.GetLongKeyDetector().IsKey(arg))
                {
                    if (KeyDetection.GetLongKeyDetector().IsJoinedToValue(arg))
                    {
                        string[] split = arg.Split('=');
                        key = split.First();
                        if (ret.ContainsKey(key)) ret[key].Add(split.Last());
                        else ret[key] = new List<string>() { split.Last() };
                    }
                    else
                    {
                        ret[arg] = new List<string>();
                        key = arg;
                    }
                }
                else
                {
                    if (ret.ContainsKey(key)) ret[key].Add(arg);
                    else ret[key] = new List<string>() { arg };
                }
            }
            return ret;
        }

        private char[] _getShortKeys(Type targetType)
        {
            List<string> ret = new List<string>();
            PropertyInfo[] properties = targetType.GetProperties();
            foreach (var property in properties)
            {
                var flag = property.GetCustomAttribute<FlagAttribute>();
                ret.Add(flag.ShortName);
            }
            return ret.Select((key) => key.ElementAt(0)).ToArray();
        }

        public TData Parse<TData>(string[] args = null)
        {
            args = args ?? this._args ?? new string[] { };
            this._args = args;
            var _dict = GetDictionary(args, typeof(TData));
            TData ret = System.Activator.CreateInstance<TData>();
            System.Reflection.PropertyInfo[] properties = ret.GetType().GetProperties();
            foreach (var property in properties)
            {
                var flag = property.GetCustomAttribute<FlagAttribute>();
                var help = property.GetCustomAttribute<HelpAttribute>();
                var transform = property.GetCustomAttribute<TransformAttribute>();
                flag = flag ?? new FlagAttribute(property.Name);
                string keyPattern = $@"(^-{flag.ShortName}$)|(^--{flag.Name}$)";
                string key = _dict.Keys.FirstOrDefault(_key => Regex.IsMatch(_key, keyPattern, RegexOptions.IgnoreCase));
                if (key == null && flag.Required)
                {
                    throw new Exception($"Could not find argument key for --{property.Name} which is required");
                }
                else if (key != null)
                {
                    _setPropertyValue<TData>(ret, property, _dict[key], transform);
                }
            }
            return ret;
        }

        private void _setPropertyValue<TData>(TData ret, PropertyInfo property, List<string> values, TransformAttribute transform = null)
        {
            string flagValue = string.Join(null, values);
            switch (property.PropertyType.Name)
            {
                case "String":
                    try { property.SetValue(ret, flagValue); } catch { throw new Exception($"Could not convert argument value of \"{flagValue}\" to String"); }
                    break;
                case "Int32":
                    try { property.SetValue(ret, Convert.ToInt32(flagValue)); } catch { throw new Exception($"Could not convert argument value of \"{flagValue}\" to Int32"); }
                    break;
                case "Int64":
                    try { property.SetValue(ret, Convert.ToInt64(flagValue)); } catch { throw new Exception($"Could not convert argument value of \"{flagValue}\" to Int64"); }
                    break;
                case "DateTime":
                    try { property.SetValue(ret, Convert.ToDateTime(flagValue)); } catch { throw new Exception($"Could not convert argument value of \"{flagValue}\" to DateTime"); }
                    break;
                case "Boolean":
                    if (string.IsNullOrWhiteSpace(flagValue)) property.SetValue(ret, true); //default to true if no value is specified cos it's a boolean
                    else try { property.SetValue(ret, Convert.ToBoolean(flagValue)); } catch { throw new Exception($"Could not convert argument value of \"{flagValue}\" to Boolean"); }
                    break;
                default:
                    if (transform != null)
                    {
                        property.SetValue(ret, transform.Execute.DynamicInvoke(flagValue));
                    }
                    else
                    {
                        if (property.PropertyType.BaseType.Name == "Enum")
                        {
                            try { property.SetValue(ret, Enum.Parse(property.PropertyType, flagValue)); } catch { throw new Exception($"Could not convert argument value of \"{flagValue}\" to Enum"); }
                        }
                        else throw new Exception($"Type of [{property.Name}] is not recognized");
                    }
                    break;
            }
        }

        private bool _matchesCliKey(PropertyInfo property, string key)
        {
            var flag = property.GetCustomAttribute<FlagAttribute>();
            flag = flag ?? new FlagAttribute(property.Name);
            string keyPattern = $@"(^-{flag.ShortName}$)|(^--{flag.Name}$)";
            return Regex.IsMatch(key, keyPattern);
        }

        private string _getPropKeyHelpInfo(PropertyInfo property, bool includeDescription = false)
        {
{'fraction_non_alphanumeric': 0.10415212840195394, 'fraction_numerical': 0.0018608978832286578, 'mean_word_length': 1.324233004460062, 'pattern_counts': {'":': 10, '<': 37, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4991445', 'n_tokens_mistral': 4119, 'n_tokens_neox': 3879, 'n_words': 962}