예제 #1
0
        /// <summary>
        /// Assigns the given <paramref name="value"/> to the <paramref name="verbInstance"/>
        /// for the given <paramref name="option"/>.
        /// </summary>
        private static void AssignValue(object verbInstance, OptionInfo option, object value)
        {
            PropertyInfo property      = option.GetProperty();
            Type         propertyType  = property.PropertyType;
            object       typeSafeValue = value;

            if (typeSafeValue != null && propertyType.IsEnum)
            {
                typeSafeValue = Enum.Parse(propertyType, typeSafeValue.ToString(), true);
            }

            if (typeSafeValue != null && propertyType != typeSafeValue.GetType())
            {
                typeSafeValue = Convert.ChangeType(typeSafeValue, propertyType);
            }

            property.SetValue(verbInstance, typeSafeValue);
        }
예제 #2
0
        /// <summary>
        /// Returns the option information of the verb option from the defined
        /// <paramref name="options"/> set that matches the specified <paramref name="name"/>.
        /// When <paramref name="shortName"/> is set, the look-up uses the short option names.
        /// Returns NULL if no match is found.
        /// </summary>
        private static OptionInfo GetOption(OptionInfo[] options, string name, bool shortName)
        {
            if (options == null)
            {
                return(null);
            }
            for (int i = -1; ++i != options.Length;)
            {
                OptionInfo option     = options[i];
                string     optionName = shortName ? option.GetShortName() : option.GetFullName();
                if (!string.Equals(name, optionName, StringComparison.InvariantCultureIgnoreCase))
                {
                    continue;
                }
                return(option);
            }

            return(null);
        }
예제 #3
0
        /// <summary> Return the verb information of the given <paramref name="optionType"/>. </summary>
        private static VerbInfo GetVerb(Type optionType, VerbAttribute verb)
        {
            PropertyInfo[] optionPropertySet = optionType.GetProperties();
            OptionInfo[]   optionSet         = new OptionInfo[optionPropertySet.Length];
            int            p = 0;

            for (int i = -1; ++i != optionPropertySet.Length;)
            {
                PropertyInfo    property = optionPropertySet[i];
                OptionAttribute optAttr  = property.GetCustomAttribute <OptionAttribute>();
                if (optAttr == null)
                {
                    continue;
                }
                optionSet[p++] = new OptionInfo(property, optAttr);
            }

            Array.Resize(ref optionSet, p);
            return(new VerbInfo(optionType, verb, optionSet));
        }
예제 #4
0
        /// <summary>
        /// Parses the given set of <paramref name="args"/> based on the information
        /// of the given <paramref name="optionTypes"/>. If the arguments are valid,
        /// the declared <paramref name="onSuccess"/> method is invoked with the result
        /// values. Returns TRUE if the arguments could be parsed.
        /// </summary>
        public static bool Run(string[] args, Action <string, object> onSuccess, params Type[] optionTypes)
        {
            if (optionTypes == null || optionTypes.Length == 0)
            {
                throw new ArgumentNullException(nameof(optionTypes));
            }
            if (args == null || args.Length == 0)
            {
                return(PrintUsageWithError("Missing arguments.", optionTypes, null));
            }

            const char whitespaceTk = ' ';
            const char dblQuoteTk   = '"';
            const char minusTk      = '-';

            string argLine;

            // See explanation at property
            if (MarvelMode)
            {
                string originalCl = Environment.CommandLine;

                // The first part is always the way the programm has been started, for instance
                //   (i) "c:\program.exe" ...
                //  (ii) program.exe ...
                // (iii) program ...
                // The first non-whitespace character separates the first part from the rest
                int  k          = 0;
                bool escapeMode = false;
                char d;
                while (k != originalCl.Length && ((d = originalCl[k++]) != whitespaceTk || escapeMode))
                {
                    if (d == dblQuoteTk)
                    {
                        escapeMode = !escapeMode;
                    }
                }

                // Find the first non-whitespace character
                while (k != originalCl.Length && originalCl[k++] == whitespaceTk)
                {
                    // Just go ahead
                }

                argLine = originalCl.Substring(k - 1);
            }
            else
            {
                argLine = string.Join(" ", args);
            }

            if (string.IsNullOrEmpty(argLine))
            {
                return(PrintUsageWithError("Missing arguments.", optionTypes, null));
            }

            char[] argLineChars = argLine.ToCharArray();
            int    maxChars     = argLineChars.Length;

            // Modes
            //   (i) No-verb mode starting with -
            //  (ii) No-verb mode starting with --
            // (iii) Verb mode followed by key-value pairs

            int    offset = 0;
            string verb   = null;

            // We expect a verb
            if (argLineChars[0] != minusTk)
            {
                char[] verbBuffer = new char[100];
                int    l          = -1;
                char   c;
                while (++l != maxChars && (c = argLineChars[l]) != whitespaceTk)
                {
                    verbBuffer[l] = c;
                }

                verb   = new string(verbBuffer, 0, l);
                offset = l + 1;
            }

            // Verbs require at least 2 options
            bool verbMode = optionTypes.Length > 1;

            // Do we have a verb, but verb mode is no configured?
            if (verb != null && !verbMode)
            {
                return(PrintUsageWithError($"Verb '{verb}' is not supported.", optionTypes, null));
            }
            if (verb == null && verbMode)
            {
                return(PrintUsageWithError("Verb required.", optionTypes, null));
            }

            VerbInfo verbNfo = GetVerb(verb, optionTypes);

            if (verbNfo == null)
            {
                return(PrintUsageWithError($"Verb '{verb}' is unknown.", optionTypes, null));
            }

            object verbInstance = verbNfo.GetVerbInstance();

            OptionInfo[]            verbOptions = verbNfo.GetOptions();
            LinkedList <OptionInfo> requiredSet = new LinkedList <OptionInfo>();

            // Apply default values to options and select required
            for (int l = -1; ++l != verbOptions.Length;)
            {
                OptionInfo option = verbOptions[l];
                AssignValue(verbInstance, option, option.GetDefaultValue());

                if (option.IsRequired())
                {
                    requiredSet.AddLast(option);
                }
            }

            if (offset >= maxChars && requiredSet.Count != 0)
            {
                return(PrintUsageWithError($"Missing required arguments for verb '{verb}'.", optionTypes, verbNfo));
            }

            char[] buffer = new char[300];
            int    p;
            int    i = offset;

            while (i != maxChars)
            {
                // -(-)KEY VALUE -(-)KEY "VALUE" -(-)KEY|flag -(-)KEY VALUE ...

                char c         = argLineChars[i];
                bool shortName = true;

                // Parse the key
                if (c != minusTk)
                {
                    return(PrintUsageWithError($"Invalid syntax.", optionTypes, verbNfo));
                }
                int j = i + 1;
                if (j == maxChars)
                {
                    return(PrintUsageWithError($"Invalid syntax.", optionTypes, verbNfo));
                }
                if (argLineChars[j] == minusTk)
                {
                    j        += 1;
                    shortName = false;
                }

                i = j;
                p = 0;
                char d;
                while (i != maxChars && (d = argLineChars[i++]) != whitespaceTk)
                {
                    buffer[p++] = d;
                }

                string key = new string(buffer, 0, p);
                if (string.IsNullOrEmpty(key))
                {
                    return(PrintUsageWithError($"Invalid syntax.", optionTypes, verbNfo));
                }

                // Get the option info for that key
                OptionInfo optionNfo = GetOption(verbOptions, key, shortName);
                if (optionNfo == null)
                {
                    return(PrintUsageWithError($"Option '{key}' is not supported for that verb.", optionTypes, verbNfo));
                }

                // Boolean keys do not require a value
                if (optionNfo.IsBool())
                {
                    AssignValue(verbInstance, optionNfo, true);
                    requiredSet.Remove(optionNfo);
                    continue;
                }

                // Parse the value
                if (i == maxChars)
                {
                    return(PrintUsageWithError($"Invalid syntax.", optionTypes, verbNfo));
                }

                d = argLineChars[i];
                if (d == minusTk)
                {
                    return(PrintUsageWithError($"Invalid syntax.", optionTypes, verbNfo));
                }

                bool escapeMode = false;
                p = 0;
                while (i != maxChars && ((d = argLineChars[i++]) != whitespaceTk || escapeMode))
                {
                    if (d == dblQuoteTk)
                    {
                        escapeMode = !escapeMode;
                        continue; // Strip
                    }

                    buffer[p++] = d;
                }

                string value = new string(buffer, 0, p);
                AssignValue(verbInstance, optionNfo, value);
                requiredSet.Remove(optionNfo);
            }

            // All required options must be set
            if (requiredSet.Count != 0)
            {
                OptionInfo missReqOpt = requiredSet.First.Value;
                return(PrintUsageWithError($"Missing value for required option '{missReqOpt.GetFullName()}'.", optionTypes, verbNfo));
            }

            onSuccess(verb, verbInstance);
            return(true);
        }
예제 #5
0
        /// <summary>
        /// Writes the help (usage) information including the specified <paramref name="errorText"/>
        /// to the assigned <paramref name="stream"/> for the given set of <paramref name="optionTypes"/>.
        /// Note, the <paramref name="errorText"/> is only written when it's not NULL.
        /// Returns always FALSE for fluent signature.
        /// </summary>
        private static bool PrintUsageWithError(TextWriter stream, string errorText, Type[] optionTypes, VerbInfo verbNfo)
        {
            if (stream == null)
            {
                return(false);
            }
            const int    padRight   = 50;
            const string leftOffset = "  ";
            const string midOffset  = "     ";

            Assembly        myAssembly     = Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly();
            FileVersionInfo fileVersionNfo = FileVersionInfo.GetVersionInfo(myAssembly.Location);
            string          name           = fileVersionNfo.ProductName;
            string          version        = fileVersionNfo.ProductVersion;
            string          copyright      = fileVersionNfo.LegalCopyright;

            stream.WriteLine($"{name} {version}");
            stream.WriteLine(copyright);
            stream.WriteLine();

            if (!string.IsNullOrEmpty(errorText))
            {
                stream.WriteLine("ERROR(S)");
                stream.WriteLine($"  {errorText}");
                stream.WriteLine();
            }

            stream.WriteLine("ARGUMENT(S)");

            // No-verb mode?
            if (verbNfo == null && optionTypes.Length == 1)
            {
                verbNfo = GetVerb(optionTypes[0], null);
            }

            // Print verb information
            if (verbNfo == null)
            {
                int        maxVerbNameChars = 0;
                VerbInfo[] verbSet          = new VerbInfo[optionTypes.Length];
                int        p = 0;
                for (int i = -1; ++i != optionTypes.Length;)
                {
                    Type          optionType = optionTypes[i];
                    VerbAttribute verbAttr   = optionType.GetCustomAttribute <VerbAttribute>();
                    if (verbAttr == null)
                    {
                        continue;
                    }
                    VerbInfo optVerbNfo = GetVerb(optionType, verbAttr);
                    maxVerbNameChars = Math.Max(maxVerbNameChars, optVerbNfo.GetName().Length);
                    verbSet[p++]     = optVerbNfo;
                }

                Array.Resize(ref verbSet, p);

                for (int i = -1; ++i != p;)
                {
                    VerbInfo verbOpt  = verbSet[i];
                    string   fullline = string.Concat(
                        leftOffset,
                        verbOpt.GetName().PadLeft(maxVerbNameChars),
                        midOffset,
                        verbOpt.GetHelpText().PadRight(padRight)
                        );

                    stream.WriteLine(fullline);
                    stream.WriteLine();
                }

                stream.WriteLine("Unknown action. Check help!");
                return(false);
            }

            // Print option information
            OptionInfo[] options            = verbNfo.GetOptions();
            string[]     headlines          = new string[options.Length];
            string[]     bodylines          = new string[options.Length];
            int          maxOptionNameChars = 0;

            for (int i = -1; ++i != options.Length;)
            {
                OptionInfo option    = options[i];
                string     shortName = option.GetShortName();
                string     fullName  = option.GetFullName();
                string     required  = option.IsRequired() ? "(Required) " : string.Empty;
                string     deflt     = option.GetDefaultValue() != null ? $"(Default {option.GetDefaultValue()}) " : string.Empty;
                string     helpText  = option.GetHelpText();

                bool hasShortName = !string.IsNullOrEmpty(shortName);
                bool hasFullName  = !string.IsNullOrEmpty(fullName);

                string headline = string.Empty;

                if (hasShortName)
                {
                    headline = string.Concat(headline, "-", shortName);
                }

                if (hasShortName && hasFullName)
                {
                    headline = string.Concat(headline, ", ");
                }

                if (hasFullName)
                {
                    headline = string.Concat(headline, "--", fullName);
                }

                maxOptionNameChars = Math.Max(maxOptionNameChars, headline.Length);
                headlines[i]       = headline;

                string bodyline = string.Concat(required, deflt, helpText);
                bodylines[i] = bodyline;
            }

            for (int i = -1; ++i != headlines.Length;)
            {
                string headline = headlines[i];
                string bodyline = bodylines[i];
                string fullline = string.Concat(
                    leftOffset,
                    headline.PadLeft(maxOptionNameChars),
                    midOffset,
                    bodyline.PadRight(padRight)
                    );

                stream.WriteLine(fullline);
                stream.WriteLine();
            }

            stream.WriteLine("Missing arguments. Check help!");
            return(false);
        }