protected virtual string MatchPartialStringValue([NotNull] ITaskContext context, [NotNull] OptionAttribute attribute, [NotNull] PropertyInfo property, [NotNull] string value)
        {
            if (!attribute.HasOptions)
            {
                return(value);
            }

            var options = new Dictionary <string, string>();

            foreach (var method in property.DeclaringType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
            {
                var valuesAttribute = method.GetCustomAttribute <OptionValuesAttribute>();
                if (valuesAttribute == null || valuesAttribute.Name != property.Name)
                {
                    continue;
                }

                var tuples = (IEnumerable <(string Name, string Value)>)method.Invoke(this, new object[]
                {
                    context
                });

                foreach (var tuple in tuples)
                {
                    options[tuple.Name] = tuple.Value;
                }

                break;
            }

            if (!options.Any())
            {
                return(value);
            }

            var found      = 0;
            var partialKey = value;

            foreach (var option in options)
            {
                if (option.Key.StartsWith(partialKey, StringComparison.CurrentCultureIgnoreCase))
                {
                    value = option.Value;
                    found++;
                }
            }

            if (found > 1)
            {
                throw new InvalidOperationException("Ambiguous parameter: " + partialKey);
            }

            return(value);
        }
        protected virtual bool GetOptionBoolValue([NotNull] ITaskContext context, [NotNull] PropertyInfo property, [NotNull] OptionAttribute attribute)
        {
            string value;

            // get positional argument from command line
            if (attribute.PositionalArg > 0)
            {
                if (context.Configuration.TryGet("arg" + attribute.PositionalArg, out value))
                {
                    if (bool.TryParse(value, out var b))
                    {
                        return(b);
                    }
                }
            }

            // get from configuration
            if (context.Configuration.TryGet(attribute.Name, out value))
            {
                if (bool.TryParse(value, out var b))
                {
                    return(b);
                }
            }

            // get from configuration by alias
            if (!string.IsNullOrEmpty(attribute.Alias))
            {
                if (context.Configuration.TryGet(attribute.Alias, out value))
                {
                    if (bool.TryParse(value, out var b))
                    {
                        return(b);
                    }
                }
            }

            // get from configuration by configuration name
            if (!string.IsNullOrEmpty(attribute.ConfigurationName))
            {
                if (context.Configuration.TryGet(attribute.ConfigurationName, out value))
                {
                    if (bool.TryParse(value, out var b))
                    {
                        return(b);
                    }
                }
            }

            // use default value, if any
            if (attribute.DefaultValue != null)
            {
                return((bool)attribute.DefaultValue);
            }

            // get from user using console
            do
            {
                var b = context.Console.YesNo(attribute.PromptText + @": ", false);
                if (b != null)
                {
                    return(b == true);
                }
            } while (true);
        }
        protected virtual string GetOptionStringValue([NotNull] ITaskContext context, [NotNull] PropertyInfo property, [NotNull] OptionAttribute attribute)
        {
            string value;

            // get positional argument from command line
            if (attribute.PositionalArg > 0)
            {
                if (context.Configuration.TryGet("arg" + attribute.PositionalArg, out value))
                {
                    return(MatchPartialStringValue(context, attribute, property, value ?? string.Empty));
                }
            }

            // get from configuration
            if (context.Configuration.TryGet(attribute.Name, out value))
            {
                return(value ?? string.Empty);
            }

            // get from configuration by alias
            if (!string.IsNullOrEmpty(attribute.Alias))
            {
                if (context.Configuration.TryGet(attribute.Alias, out value))
                {
                    return(MatchPartialStringValue(context, attribute, property, value ?? string.Empty));
                }
            }

            // get from configuration by configuration name
            if (!string.IsNullOrEmpty(attribute.ConfigurationName))
            {
                if (context.Configuration.TryGet(attribute.ConfigurationName, out value))
                {
                    return(MatchPartialStringValue(context, attribute, property, value ?? string.Empty));
                }
            }

            // use default value, if any
            if (attribute.DefaultValue != null)
            {
                return((string)attribute.DefaultValue);
            }

            // get from user using pick list
            if (attribute.HasOptions)
            {
                var options = new Dictionary <string, string>();

                foreach (var method in property.DeclaringType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
                {
                    var valuesAttribute = method.GetCustomAttribute <OptionValuesAttribute>();
                    if (valuesAttribute == null || valuesAttribute.Name != property.Name)
                    {
                        continue;
                    }

                    var tuples = (IEnumerable <(string Name, string Value)>)method.Invoke(this, new object[]
                    {
                        context
                    });
                    foreach (var tuple in tuples)
                    {
                        options[tuple.Name] = tuple.Value;
                    }

                    break;
                }

                if (options.Any())
                {
                    do
                    {
                        value = context.Console.Pick(attribute.PromptText + @": ", options);
                        if (!string.IsNullOrEmpty(value) || !attribute.IsRequired)
                        {
                            return(value);
                        }
                    } while (true);
                }
            }

            // get from user using console
            do
            {
                value = context.Console.ReadLine(attribute.PromptText + @": ", string.Empty);
                if (!string.IsNullOrEmpty(value) || !attribute.IsRequired)
                {
                    return(MatchPartialStringValue(context, attribute, property, value));
                }
            } while (true);
        }