summaryrefslogtreecommitdiff
path: root/source/app/ProgramOption.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/app/ProgramOption.c')
-rw-r--r--source/app/ProgramOption.c534
1 files changed, 534 insertions, 0 deletions
diff --git a/source/app/ProgramOption.c b/source/app/ProgramOption.c
new file mode 100644
index 0000000..075b033
--- /dev/null
+++ b/source/app/ProgramOption.c
@@ -0,0 +1,534 @@
+//
+// ProgramOption.c - MrsWatson
+// Created by Nik Reiman on 1/2/12.
+// Copyright (c) 2012 Teragon Audio. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+// POSSIBILITY OF SUCH DAMAGE.
+//
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "app/ProgramOption.h"
+#include "base/File.h"
+#include "logging/EventLogger.h"
+
+CharString _programOptionGetString(const ProgramOption self);
+float _programOptionGetNumber(const ProgramOption self);
+
+ProgramOption newProgramOption(void)
+{
+ return newProgramOptionWithName(-1, EMPTY_STRING, EMPTY_STRING, false,
+ kProgramOptionTypeNumber, kProgramOptionArgumentTypeInvalid);
+}
+
+ProgramOption newProgramOptionWithName(const int optionIndex, const char *name,
+ const char *help, boolByte hasShortForm, ProgramOptionType type,
+ ProgramOptionArgumentType argumentType)
+{
+ ProgramOption option = (ProgramOption)malloc(sizeof(ProgramOptionMembers));
+
+ option->index = (unsigned int)optionIndex;
+ option->name = newCharStringWithCString(name);
+ option->help = newCharStringWithCString(help);
+ option->hasShortForm = hasShortForm;
+ option->hideInHelp = false;
+
+ option->type = type;
+
+ switch (type) {
+ case kProgramOptionTypeEmpty:
+ // Nothing needed here
+ break;
+
+ case kProgramOptionTypeString:
+ option->_data.string = newCharString();
+ break;
+
+ case kProgramOptionTypeNumber:
+ option->_data.number = 0.0f;
+ break;
+
+ case kProgramOptionTypeList:
+ option->_data.list = newLinkedList();
+ break;
+
+ default:
+ logInternalError("ProgramOption with invalid type");
+ break;
+ }
+
+ option->argumentType = argumentType;
+ option->enabled = false;
+
+ return option;
+}
+
+void _programOptionPrintDefaultValue(const ProgramOption self)
+{
+ CharString stringValue;
+
+ switch (self->type) {
+ case kProgramOptionTypeString:
+ stringValue = _programOptionGetString(self);
+
+ if (stringValue != NULL && !charStringIsEmpty(stringValue)) {
+ printf(", default value '%s'", stringValue->data);
+ }
+
+ break;
+
+ case kProgramOptionTypeNumber:
+ printf(", default value: %.0f", _programOptionGetNumber(self));
+ break;
+
+ default:
+ break;
+ }
+}
+
+void programOptionPrintHelp(const ProgramOption self, boolByte withFullHelp, int indentSize, int initialIndent)
+{
+ CharString wrappedHelpString;
+ int i;
+
+ if (self == NULL) {
+ logError("Can't find help for that option. Try running with --help to see all options\n");
+ return;
+ }
+
+ // Initial argument indent
+ for (i = 0; i < initialIndent; i ++) {
+ printf(" ");
+ }
+
+ // All arguments have a long form, so that will always be printed
+ printf("--%s", self->name->data);
+
+ if (self->hasShortForm) {
+ printf(" (or -%c)", self->name->data[0]);
+ }
+
+ switch (self->argumentType) {
+ case kProgramOptionArgumentTypeRequired:
+ printf(" <argument>");
+ break;
+
+ case kProgramOptionArgumentTypeOptional:
+ printf(" [argument]");
+ break;
+
+ case kProgramOptionArgumentTypeNone:
+ default:
+ break;
+ }
+
+ _programOptionPrintDefaultValue(self);
+
+ if (withFullHelp) {
+ // Newline and indentation before help
+ wrappedHelpString = charStringWrap(self->help, (unsigned int)(initialIndent + indentSize));
+ printf("\n%s\n\n", wrappedHelpString->data);
+ freeCharString(wrappedHelpString);
+ } else {
+ printf("\n");
+ }
+}
+
+CharString _programOptionGetString(const ProgramOption self)
+{
+ return self->type == kProgramOptionTypeString ? self->_data.string : NULL;
+}
+
+float _programOptionGetNumber(const ProgramOption self)
+{
+ return self->type == kProgramOptionTypeNumber ? self->_data.number : -1.0f;
+}
+
+static LinkedList _programOptionGetList(const ProgramOption self)
+{
+ return self->type == kProgramOptionTypeList ? self->_data.list : NULL;
+}
+
+static void _programOptionSetString(ProgramOption self, const CharString value)
+{
+ if (self->type == kProgramOptionTypeString) {
+ charStringCopy(self->_data.string, value);
+ }
+}
+
+static void _programOptionSetCString(ProgramOption self, const char *value)
+{
+ CharString valueString = newCharStringWithCString(value);
+ _programOptionSetString(self, valueString);
+ freeCharString(valueString);
+}
+
+static void _programOptionSetNumber(ProgramOption self, const float value)
+{
+ if (self->type == kProgramOptionTypeNumber) {
+ self->_data.number = value;
+ }
+}
+
+static void _programOptionSetListItem(ProgramOption self, void *value)
+{
+ if (self->type == kProgramOptionTypeList) {
+ linkedListAppend(self->_data.list, value);
+ }
+}
+
+static void _programOptionSetData(ProgramOption self, const char *data)
+{
+ if (data == NULL) {
+ return;
+ }
+
+ switch (self->type) {
+ case kProgramOptionTypeString:
+ _programOptionSetCString(self, data);
+ break;
+
+ case kProgramOptionTypeNumber:
+ // Windows doesn't do strtof :(
+ _programOptionSetNumber(self, (float)strtod(data, NULL));
+ break;
+
+ case kProgramOptionTypeList:
+ _programOptionSetListItem(self, (void *)data);
+ break;
+
+ default:
+ logInternalError("Set ProgramOption with invalid type");
+ break;
+ }
+}
+
+void freeProgramOption(ProgramOption self)
+{
+ if (self != NULL) {
+ freeCharString(self->name);
+ freeCharString(self->help);
+
+ switch (self->type) {
+ case kProgramOptionTypeString:
+ freeCharString(self->_data.string);
+ break;
+
+ case kProgramOptionTypeList:
+ // Note: This will not actually free the associated strings for this
+ // option. This is ok if the list items are parsed from argv/argc, but
+ // otherwise memory could be leaked here.
+ freeLinkedList(self->_data.list);
+ break;
+
+ default:
+ break;
+ }
+
+ free(self);
+ }
+}
+
+
+ProgramOptions newProgramOptions(int numOptions)
+{
+ ProgramOptions options = (ProgramOptions)malloc(sizeof(ProgramOptionsMembers));
+ options->numOptions = (unsigned int)numOptions;
+ options->options = (ProgramOption *)malloc(sizeof(ProgramOption) * numOptions);
+ memset(options->options, 0, sizeof(ProgramOption) * numOptions);
+ return options;
+}
+
+boolByte programOptionsAdd(const ProgramOptions self, const ProgramOption option)
+{
+ if (option != NULL && option->index < self->numOptions) {
+ self->options[option->index] = option;
+ return true;
+ }
+
+ return false;
+}
+
+static boolByte _isStringShortOption(const char *testString)
+{
+ return (boolByte)(testString != NULL && strlen(testString) == 2 && testString[0] == '-');
+}
+
+static boolByte _isStringLongOption(const char *testString)
+{
+ return (boolByte)(testString != NULL && strlen(testString) > 2 && testString[0] == '-' && testString[1] == '-');
+}
+
+static ProgramOption _findProgramOption(ProgramOptions self, const char *name)
+{
+ ProgramOption potentialMatchOption, optionMatch;
+ CharString optionStringWithoutDashes;
+ unsigned int i;
+
+ if (_isStringShortOption(name)) {
+ for (i = 0; i < self->numOptions; i++) {
+ potentialMatchOption = self->options[i];
+
+ if (potentialMatchOption->hasShortForm && potentialMatchOption->name->data[0] == name[1]) {
+ return potentialMatchOption;
+ }
+ }
+ }
+
+ if (_isStringLongOption(name)) {
+ optionMatch = NULL;
+ optionStringWithoutDashes = newCharStringWithCapacity(kCharStringLengthShort);
+ strncpy(optionStringWithoutDashes->data, name + 2, strlen(name) - 2);
+
+ for (i = 0; i < self->numOptions; i++) {
+ potentialMatchOption = self->options[i];
+
+ if (charStringIsEqualTo(potentialMatchOption->name, optionStringWithoutDashes, false)) {
+ optionMatch = potentialMatchOption;
+ break;
+ }
+ }
+
+ freeCharString(optionStringWithoutDashes);
+ return optionMatch;
+ }
+
+ // If no option was found, then return null
+ return NULL;
+}
+
+static boolByte _fillOptionArgument(ProgramOption self, int *currentArgc, int argc, char **argv)
+{
+ if (self->argumentType == kProgramOptionArgumentTypeNone) {
+ return true;
+ } else if (self->argumentType == kProgramOptionArgumentTypeOptional) {
+ int potentialNextArgc = *currentArgc + 1;
+
+ if (potentialNextArgc >= argc) {
+ return true;
+ } else {
+ char *potentialNextArg = argv[potentialNextArgc];
+
+ // If the next string in the sequence is NOT an argument, we assume it is the optional argument
+ if (!_isStringShortOption(potentialNextArg) && !_isStringLongOption(potentialNextArg)) {
+ _programOptionSetData(self, potentialNextArg);
+ (*currentArgc)++;
+ return true;
+ } else {
+ // Otherwise, it is another option, but that's ok
+ return true;
+ }
+ }
+ } else if (self->argumentType == kProgramOptionArgumentTypeRequired) {
+ int nextArgc = *currentArgc + 1;
+
+ if (nextArgc >= argc) {
+ logCritical("Option '%s' requires an argument, but none was given", self->name->data);
+ return false;
+ } else {
+ char *nextArg = argv[nextArgc];
+
+ if (_isStringShortOption(nextArg) || _isStringLongOption(nextArg)) {
+ logCritical("Option '%s' requires an argument, but '%s' is not valid", self->name->data, nextArg);
+ return false;
+ } else {
+ _programOptionSetData(self, nextArg);
+ (*currentArgc)++;
+ return true;
+ }
+ }
+ } else {
+ logInternalError("Unknown argument type '%d'", self->argumentType);
+ return false;
+ }
+}
+
+boolByte programOptionsParseArgs(ProgramOptions self, int argc, char **argv)
+{
+ int argumentIndex;
+
+ for (argumentIndex = 1; argumentIndex < argc; argumentIndex++) {
+ const ProgramOption option = _findProgramOption(self, argv[argumentIndex]);
+
+ if (option == NULL) {
+ logCritical("Invalid option '%s'", argv[argumentIndex]);
+ return false;
+ } else {
+ if (_fillOptionArgument(option, &argumentIndex, argc, argv)) {
+ option->enabled = true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ // If we make it to here, return true
+ return true;
+}
+
+boolByte programOptionsParseConfigFile(ProgramOptions self, const CharString filename)
+{
+ boolByte result = false;
+ File configFile = NULL;
+ LinkedList configFileLines = NULL;
+ CharString *argvCharStrings;
+ int argc;
+ char **argv;
+ int i;
+
+ if (filename == NULL || charStringIsEmpty(filename)) {
+ logCritical("Cannot read options from empty filename");
+ return false;
+ }
+
+ configFile = newFileWithPath(filename);
+
+ if (configFile == NULL || configFile->fileType != kFileTypeFile) {
+ logCritical("Cannot read options from non-existent file '%s'", filename->data);
+ freeFile(configFile);
+ return false;
+ }
+
+ configFileLines = fileReadLines(configFile);
+
+ if (configFileLines == NULL) {
+ logInternalError("Could not split config file lines");
+ return false;
+ } else if (linkedListLength(configFileLines) == 0) {
+ logInfo("Config file '%s' is empty", filename->data);
+ freeLinkedList(configFileLines);
+ freeFile(configFile);
+ return true;
+ } else {
+ // Don't need the file anymore, it can be freed here
+ freeFile(configFile);
+ }
+
+ argvCharStrings = (CharString *)linkedListToArray(configFileLines);
+ argc = linkedListLength(configFileLines);
+ argv = (char **)malloc(sizeof(char *) * (argc + 1));
+ // Normally this would be the application name, don't care about it here
+ argv[0] = NULL;
+
+ for (i = 0; i < argc; i++) {
+ argv[i + 1] = argvCharStrings[i]->data;
+ }
+
+ argc++;
+ result = programOptionsParseArgs(self, argc, argv);
+
+ freeLinkedListAndItems(configFileLines, (LinkedListFreeItemFunc)freeCharString);
+ free(argvCharStrings);
+ free(argv);
+ return result;
+}
+
+void programOptionsPrintHelp(const ProgramOptions self, boolByte withFullHelp, int indentSize)
+{
+ unsigned int i;
+
+ for (i = 0; i < self->numOptions; i++) {
+ if (!self->options[i]->hideInHelp) {
+ programOptionPrintHelp(self->options[i], withFullHelp, indentSize, indentSize);
+ }
+ }
+}
+
+ProgramOption programOptionsFind(const ProgramOptions self, const CharString string)
+{
+ unsigned int i;
+
+ for (i = 0; i < self->numOptions; i++) {
+ if (charStringIsEqualTo(string, self->options[i]->name, true)) {
+ return self->options[i];
+ }
+ }
+
+ return NULL;
+}
+
+void programOptionsPrintHelpForOption(const ProgramOptions self, const CharString string,
+ boolByte withFullHelp, int indentSize)
+{
+ programOptionPrintHelp(programOptionsFind(self, string), withFullHelp, indentSize, 0);
+}
+
+const CharString programOptionsGetString(const ProgramOptions self, const unsigned int index)
+{
+ return index < self->numOptions ? _programOptionGetString(self->options[index]) : NULL;
+}
+
+float programOptionsGetNumber(const ProgramOptions self, const unsigned int index)
+{
+ return index < self->numOptions ? _programOptionGetNumber(self->options[index]) : -1.0f;
+}
+
+const LinkedList programOptionsGetList(const ProgramOptions self, const unsigned int index)
+{
+ return index < self->numOptions ? _programOptionGetList(self->options[index]) : NULL;
+}
+
+void programOptionsSetCString(ProgramOptions self, const unsigned int index, const char *value)
+{
+ if (index < self->numOptions) {
+ _programOptionSetCString(self->options[index], value);
+ }
+}
+
+void programOptionsSetString(ProgramOptions self, const unsigned int index, const CharString value)
+{
+ if (index < self->numOptions) {
+ _programOptionSetString(self->options[index], value);
+ }
+}
+
+void programOptionsSetNumber(ProgramOptions self, const unsigned int index, const float value)
+{
+ if (index < self->numOptions) {
+ _programOptionSetNumber(self->options[index], value);
+ }
+}
+
+void programOptionsSetListItem(ProgramOptions self, const unsigned int index, void *value)
+{
+ if (index < self->numOptions) {
+ _programOptionSetListItem(self->options[index], value);
+ }
+}
+
+void freeProgramOptions(ProgramOptions self)
+{
+ unsigned int i;
+
+ if (self == NULL) {
+ return;
+ }
+
+ for (i = 0; i < self->numOptions; i++) {
+ freeProgramOption(self->options[i]);
+ }
+
+ free(self->options);
+ free(self);
+}