 determination. I really do attribute a lot of that to this TToT exercise. This past year I’ve read about the things people from different backgrounds are thankful for, and it makes my week better and has showed me that perhaps there still could be something good waiting out there for me.
So thank you to you and everyone here, that you list, who are a part of this linkup.
Lizzi spreads her thankfuls far and wide.
I thought I just typed FRIST, but now I don’t see it. Ugh. WordPress gremlins!
You’re always so humble. You don’t lack any of those qualities!
As for the deadly sins, it’s okay to be good at some of them, some of the time. Balance, you know?
Thanks for taking the time to write this lovely tribute, and including me with all these inspiring folks. By the way, Hasty really DOES embody humanity, I think. That’s a great one.
WordPress has SEVERE gremlin issues. It logged me out earlier, and I thought I’d lost everything, which made me very cross for a few moments. HOORAY for Hasty, and you, and all the virtues and people who show them so consistently.
Man I don’t even know what to say. I feel like you guys are talking about someone lovely. You are both so important to me.
Chairman of Universal Basic Education Commission, (UBEC) Dr. Mahmud Ahmed was released yesterday, April 30, by his abductors.
According to different sources and reports, sums of money ranging from twenty-six million naira to sixty million naira was paid for his release.
However, a source very close to the UBEC chairman disclosed that no money was paid for Mahmud and his daughter’s release.
Mahmud while receiving guests at his Kaduna home thanked God and Nigerians for their support during his ordeal.
Elendureports can confirm that Mahmud and his daughter had a medical check up after their release and were discharged this afternoon.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

namespace Asciis
{

    public interface IParamParser
    {
        List<string> CommandNames          { get; }
        Regex?       CommandPattern        { get; }
        string?      CommandPatternDisplay { get; }
        string?      CommandDescription    { get; }
        public Type  CommandType           { get; }

        bool   ParseArguments( object obj, string? text = null, List< string >? args = null, bool includeEnvironmentVariables = true);
        string Help();
    }
    
    /// <summary>
    /// Parses the metadata held in <see cref="T"/>
    /// Each implementation will have a usage like:
    ///    [options] ActionName [extra arguments] [options]
    ///
    /// example:
    ///   --verbose --language pirate PLAN battle
    /// what this means is:
    ///   --verbose           will set the `verbose` boolean parameter to true
    ///   --language pirate   will set the language parameter to `pirate`
    ///   PLAN sailing away   PLAN is the action and `sailing away` are the extras
    /// 
    /// example:
    ///   BATTLE --intensity=insane
    /// what this means is:
    ///   BATTLE              BATTLE is the action with no extras
    ///   --intensity=insane  Set the intensity parameter to `insane`
    /// </summary>
    public class ParamParser< T > : IParamParser 
        where T : new()
    {
        public List< string > CommandNames          { get; }
        public Regex?         CommandPattern        { get; }
        public string?        CommandPatternDisplay { get; }
        public string?        CommandDescription    { get; }
        public Type           CommandType           { get; } = typeof(T);

        private readonly List< PropertyMetadata > _properties = new();
        private readonly List< ActionMetadata >   _actions    = new();

        // ReSharper disable once StaticMemberInGenericType
        /// <summary>
        /// [flag][name][separator][value]
        /// flag      = --, -, /
        /// name      = name of parameter
        /// separator = :, = (optional)
        /// value     = value of parameter (optional) 
        /// </summary>
        private static readonly Regex FlagRegex = new( @"^(?<flag>--|-|/)(?<name>[^:=]+)((?<separator>[:=])(?<value>.*))?$" );

        /// <summary>
        /// May throw ArgumentException if <typeparamref name="T"/> if configured incorrectly
        /// </summary>
        public ParamParser()
        {
            var allNames = new List< string >();

            var commandAttribute = CommandType.GetCustomAttribute< CommandAttribute >();
            if (commandAttribute!= null)
            {
                CommandNames = commandAttribute.Names;
                if (!string.IsNullOrWhiteSpace( commandAttribute.Pattern ))
                {
                    // Command pattern can be either a valid regex or simple string replacement
                    CommandPatternDisplay = commandAttribute.Pattern;
                    CommandPattern = BuildRegex( commandAttribute.Pattern );
                }
                CommandDescription = commandAttribute.Description;
            }
            else
            {
                CommandNames       = new List< string > { CommandType.Name };
                CommandDescription = CommandType.GetCustomAttribute< DescriptionAttribute >()?.Description?? "";
            }

            var properties = CommandType.GetProperties();
            foreach (var property in properties)
            {
                var attribute = property.GetCustomAttribute< ParamAttribute >();
                if (attribute!= null)
                {
                    var names = attribute.Pattern.Split( '|' );
                    foreach (var name in names)
                    {
                        if (allNames.Contains( name )) throw new ArgumentException( $"Parameter name [{name}] has already been defined." );
                        allNames.Add( name );
                    }
                    _properties.Add( new PropertyMetadata { Property = property, Attribute = attribute, Names = names.ToList() } );
                }
            }

            var methods = CommandType.GetMethods();
            foreach (var methodInfo in methods)
            {
                var attribute = methodInfo.GetCustomAttribute< ActionAttribute >();
                if (attribute?.Pattern!= null)
                {
                    var names = attribute.Pattern.Split( '|' );
                    foreach (var name in names)
                    {
                        if (allNames.Contains( name )) throw new ArgumentException( $"Action name [{name}] has already been defined." );
                        allNames.Add( name );
                    }
                    _actions.Add( new ActionMetadata { Method = methodInfo, Attribute = attribute, Names = names.ToList() } );
                }
            }

            var hasHelp = allNames.Contains( "?" ) || allNames.Contains( "help" );
            if (!hasHelp)
            {
                _actions.Add( new ActionMetadata
                              {
                                  Method = null, 
                                  Attribute = new ActionAttribute( "?|help", "Display help" ), 
                                  Names = new List< string > { "?", "help" }
                              } );
            }
        }

        public static T Parse(string line, bool includeEnvironmentVariables = true)
        {
            var result = new T();
            new ParamParser<T>().ParseArguments(result, line, includeEnvironmentVariables: includeEnvironmentVariables);

            return result;
        }

        public static T Parse( IEnumerable< string > args, bool includeEnvironmentVariables = true )
        {
            var result = new T();
            new ParamParser<T>().ParseArguments(result, args: args.ToList(), includeEnvironmentVariables: includeEnvironmentVariables );

            return result;
        }

        private Regex BuildRegex( string str )
        {
            var properties = CommandType.GetProperties();
            foreach (var property in properties)
            {
                var attribute = property.GetCustomAttribute<ParamAttribute>();
                if (attribute!= null)
                {
                    var names = attribute.Pattern.Split('|');
                    foreach (var name in names)
                    {
                        // replace any pseudo regex with real regex
                        str = str.Replace( "{" + name + "}", $"(?<{name}>\\w+)" );
                    }
                }
            }

            return new Regex( str );
        }

        public bool ParseArguments( object obj, string? text = null, List< string >? args = null, bool includeEnvironmentVariables = true)
        {
            return ParseArguments( (T)obj, text, args );
        }

        public T ParseArguments( string? text = null, List< string >? args