From 9306cb60c32082c5403931de0823a9fd5daa196c Mon Sep 17 00:00:00 2001 From: Jason Katz-Brown Date: Sun, 25 Aug 2013 02:17:13 -0700 Subject: Initial git commit. --- quackleio/.gitignore | 6 + quackleio/LICENSE | 5 + quackleio/README | 6 + quackleio/TODO | 5 + quackleio/dict.cpp | 130 +++++++ quackleio/dict.h | 91 +++++ quackleio/dictfactory.cpp | 36 ++ quackleio/dictfactory.h | 40 ++ quackleio/dictimplementation.cpp | 104 ++++++ quackleio/dictimplementation.h | 49 +++ quackleio/flexiblealphabet.cpp | 131 +++++++ quackleio/flexiblealphabet.h | 49 +++ quackleio/froggetopt.cpp | 660 +++++++++++++++++++++++++++++++++ quackleio/froggetopt.h | 121 ++++++ quackleio/gcgio.cpp | 377 +++++++++++++++++++ quackleio/gcgio.h | 47 +++ quackleio/iotest/.gitignore | 6 + quackleio/iotest/capp.gcg | 34 ++ quackleio/iotest/iotest.cpp | 78 ++++ quackleio/iotest/iotest.pro | 29 ++ quackleio/iotest/trademarkedboards.cpp | 148 ++++++++ quackleio/iotest/trademarkedboards.h | 40 ++ quackleio/logania.h | 61 +++ quackleio/quackleio.pro | 34 ++ quackleio/queenie.cpp | 87 +++++ quackleio/queenie.h | 62 ++++ quackleio/streamingreporter.cpp | 53 +++ quackleio/streamingreporter.h | 50 +++ quackleio/util.cpp | 212 +++++++++++ quackleio/util.h | 96 +++++ 30 files changed, 2847 insertions(+) create mode 100644 quackleio/.gitignore create mode 100644 quackleio/LICENSE create mode 100644 quackleio/README create mode 100644 quackleio/TODO create mode 100644 quackleio/dict.cpp create mode 100644 quackleio/dict.h create mode 100644 quackleio/dictfactory.cpp create mode 100644 quackleio/dictfactory.h create mode 100644 quackleio/dictimplementation.cpp create mode 100644 quackleio/dictimplementation.h create mode 100644 quackleio/flexiblealphabet.cpp create mode 100644 quackleio/flexiblealphabet.h create mode 100644 quackleio/froggetopt.cpp create mode 100644 quackleio/froggetopt.h create mode 100644 quackleio/gcgio.cpp create mode 100644 quackleio/gcgio.h create mode 100644 quackleio/iotest/.gitignore create mode 100644 quackleio/iotest/capp.gcg create mode 100644 quackleio/iotest/iotest.cpp create mode 100644 quackleio/iotest/iotest.pro create mode 100644 quackleio/iotest/trademarkedboards.cpp create mode 100644 quackleio/iotest/trademarkedboards.h create mode 100644 quackleio/logania.h create mode 100644 quackleio/quackleio.pro create mode 100644 quackleio/queenie.cpp create mode 100644 quackleio/queenie.h create mode 100644 quackleio/streamingreporter.cpp create mode 100644 quackleio/streamingreporter.h create mode 100644 quackleio/util.cpp create mode 100644 quackleio/util.h (limited to 'quackleio') diff --git a/quackleio/.gitignore b/quackleio/.gitignore new file mode 100644 index 0000000..f878785 --- /dev/null +++ b/quackleio/.gitignore @@ -0,0 +1,6 @@ +Makefile +obj +Makefile.Debug +Makefile.Release +debug +release diff --git a/quackleio/LICENSE b/quackleio/LICENSE new file mode 100644 index 0000000..8233446 --- /dev/null +++ b/quackleio/LICENSE @@ -0,0 +1,5 @@ +Copyright (c) 2005-2006, Jason Katz-Brown and John O'Laughlin. + +libquackleio, the Quackle input/output library, is under the same license +as libquackle in the parent directory. Please see quackle/LICENSE for license +information. diff --git a/quackleio/README b/quackleio/README new file mode 100644 index 0000000..d7ed61e --- /dev/null +++ b/quackleio/README @@ -0,0 +1,6 @@ +IO library for saving and loading Quackle data structures. + +Supported formats: + +- GCG + See http://www.poslfit.com/scrabble/gcg diff --git a/quackleio/TODO b/quackleio/TODO new file mode 100644 index 0000000..01e57b2 --- /dev/null +++ b/quackleio/TODO @@ -0,0 +1,5 @@ +GCG: + +challenge bonus +internation-rules endgame score adjustment +time penalties diff --git a/quackleio/dict.cpp b/quackleio/dict.cpp new file mode 100644 index 0000000..e949ef0 --- /dev/null +++ b/quackleio/dict.cpp @@ -0,0 +1,130 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2006 Jason Katz-Brown and John O'Laughlin. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include + +#include "dict.h" +#include "util.h" + +using namespace std; +using namespace Dict; + +WordList::SortType WordList::sortType = Playability; + +WordList::WordList() +{ +} + +WordList::~WordList() +{ +} + +void WordList::setSortBy(SortType _sortType) +{ + sortType = _sortType; +} + +bool operator<(const Dict::Word &word1, const Dict::Word &word2) +{ + switch (Dict::WordList::sortType) + { + case Dict::WordList::Alphabetical: + return word1.word < word2.word; + + case Dict::WordList::Playability: + if (word1.playability != word2.playability) + return word1.playability > word2.playability; + + if (word1.word.length() != word2.word.length()) + return word1.word.length() < word2.word.length(); + + // fall through + + case Dict::WordList::Probability: + return word1.probability > word2.probability; + + case Dict::WordList::Length: + case Dict::WordList::LengthLongestFirst: + { + bool ret; + if (word1.word.length() != word2.word.length()) + ret = word1.word.length() < word2.word.length(); + else + ret = word1.word < word2.word; + + if (Dict::WordList::sortType == Dict::WordList::LengthLongestFirst) + return !ret; + return ret; + } + } + + return false; +} + +ExtensionList Word::extensionsByLength(int length, const ExtensionList &list) +{ + ExtensionList ret; + + for (ExtensionList::const_iterator it = list.begin(); it != list.end(); ++it) + { + if ((*it).extensionLetterString.length() == length) + { + Extension extension(*it); + extension.word = QuackleIO::Util::letterStringToQString((*it).extensionLetterString); + ret.append(extension); + } + } + + return ret; +} + +ExtensionList Word::getFrontExtensionList() const +{ + return getExtensionList(true); +} + +ExtensionList Word::getBackExtensionList() const +{ + return getExtensionList(false); +} + +ExtensionList Word::getExtensionList(bool front) const +{ + ExtensionList ret; + + const vector *list = front? &frontExtensions : &backExtensions; + + for (vector::const_iterator it = list->begin(); it != list->end(); ++it) + { + Extension extension(*it); + extension.word = QuackleIO::Util::letterStringToQString((*it).extensionLetterString); + ret.append(extension); + } + + return ret; +} + +Extension::Extension(const Quackle::ExtensionWithInfo extensionWithInfo) +{ + playability = extensionWithInfo.playability; + probability = extensionWithInfo.probability; + british = extensionWithInfo.british; +} diff --git a/quackleio/dict.h b/quackleio/dict.h new file mode 100644 index 0000000..4935e4b --- /dev/null +++ b/quackleio/dict.h @@ -0,0 +1,91 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2006 Jason Katz-Brown and John O'Laughlin. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef QUACKLEIO_DICT_H +#define QUACKLEIO_DICT_H + +#include + +#include + +#include +#include + +namespace Dict +{ + +class Extension : public Quackle::ExtensionWithInfo +{ +public: + Extension() {} + Extension(const Quackle::ExtensionWithInfo extensionWithInfo); + QString word; +}; + +typedef QList ExtensionList; + +class Word : public Quackle::WordWithInfo +{ +public: + QString word; + + static ExtensionList extensionsByLength(int length, const ExtensionList &list); + + ExtensionList getFrontExtensionList() const; + ExtensionList getBackExtensionList() const; + +private: + ExtensionList getExtensionList(bool front) const; +}; + +class WordList : public QList +{ +public: + WordList(); + ~WordList(); + + enum SortType { Alphabetical, Playability, Length, LengthLongestFirst, Probability }; + + void setSortBy(SortType sortType); + static SortType sortType; +}; + +typedef QList WordListList; + +class Querier +{ +public: + virtual ~Querier() {}; + + enum QueryFlags { None = 0x0000, WithExtensions = 0x0001, NoRequireAllLetters = 0x0002, CallUpdate = 0x0004 }; + + virtual WordList query(const QString &query, int flags = None) = 0; + virtual QString alphagram(const QString &letters) const = 0; + virtual bool isBritish(const Quackle::LetterString &word) = 0; + virtual bool isLoaded() const = 0; +}; + +} + +// Returns true if word1 is less playable than word2; +// otherwise returns false. +bool operator<(const Dict::Word &word1, const Dict::Word &word2); + +#endif diff --git a/quackleio/dictfactory.cpp b/quackleio/dictfactory.cpp new file mode 100644 index 0000000..1166390 --- /dev/null +++ b/quackleio/dictfactory.cpp @@ -0,0 +1,36 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2006 Jason Katz-Brown and John O'Laughlin. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "dictfactory.h" +#include "dictimplementation.h" + +using namespace QuackleIO; + +Dict::Querier *DictFactory::m_querier = 0; + +Dict::Querier *DictFactory::querier() +{ + if (!m_querier) + { + m_querier = new DictImplementation; + } + + return m_querier; +} diff --git a/quackleio/dictfactory.h b/quackleio/dictfactory.h new file mode 100644 index 0000000..9693179 --- /dev/null +++ b/quackleio/dictfactory.h @@ -0,0 +1,40 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2006 Jason Katz-Brown and John O'Laughlin. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef QUACKLE_DICTFACTORY_H +#define QUACKLE_DICTFACTORY_H + +#include "dict.h" + +namespace QuackleIO +{ + +class DictFactory +{ +public: + static Dict::Querier *querier(); + +private: + static Dict::Querier *m_querier; +}; + +} + +#endif diff --git a/quackleio/dictimplementation.cpp b/quackleio/dictimplementation.cpp new file mode 100644 index 0000000..5ea2e15 --- /dev/null +++ b/quackleio/dictimplementation.cpp @@ -0,0 +1,104 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2006 Jason Katz-Brown and John O'Laughlin. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include +#include +#include +#include +#include + +#include "dictimplementation.h" + +QuackleIO::DictImplementation::DictImplementation() +{ +} + +QuackleIO::DictImplementation::~DictImplementation() +{ +} + +Dict::WordList QuackleIO::DictImplementation::query(const QString &query, int flags) +{ + QString modifiedQuery = query; + modifiedQuery.replace(".", "?"); + + int anagramFlags = Quackle::Generator::ClearBlanknesses; + + if (flags & Dict::Querier::NoRequireAllLetters) + anagramFlags |= Quackle::Generator::NoRequireAllLetters; + + QRegExp wildcardRegexp("[\\*/]"); + if (wildcardRegexp.indexIn(modifiedQuery) >= 0) + { + if (!(flags & Dict::Querier::NoRequireAllLetters)) + anagramFlags |= Quackle::Generator::AddAnyLetters; + + modifiedQuery.replace(wildcardRegexp, QString()); + } + + vector words(m_generator.anagramLetters(QuackleIO::Util::encode(modifiedQuery), anagramFlags)); + Dict::WordList ret; + + vector::const_iterator end = words.end(); + for (vector::const_iterator it = words.begin(); it != end; ++it) + { + Dict::Word dictWord; + dictWord.word = QuackleIO::Util::letterStringToQString(*it); + dictWord.wordLetterString = (*it); + m_generator.storeWordInfo(&dictWord); + + if (flags & WithExtensions) + m_generator.storeExtensions(&dictWord); + + ret.push_back(dictWord); + } + + if (flags & NoRequireAllLetters) + { + ret.setSortBy(Dict::WordList::LengthLongestFirst); + } + else + { + ret.setSortBy(Dict::WordList::Alphabetical); + } + + qSort(ret); + + return ret; +} + +QString QuackleIO::DictImplementation::alphagram(const QString &letters) const +{ + return QuackleIO::Util::letterStringToQString(QuackleIO::Util::alphagram(QuackleIO::Util::encode(letters))); +} + +bool QuackleIO::DictImplementation::isLoaded() const +{ + return QUACKLE_LEXICON_PARAMETERS->hasSomething(); +} + +bool QuackleIO::DictImplementation::isBritish(const Quackle::LetterString &word) +{ + Quackle::WordWithInfo wordWithInfo; + wordWithInfo.wordLetterString = word; + m_generator.storeWordInfo(&wordWithInfo); + return wordWithInfo.british; +} + diff --git a/quackleio/dictimplementation.h b/quackleio/dictimplementation.h new file mode 100644 index 0000000..8773f6f --- /dev/null +++ b/quackleio/dictimplementation.h @@ -0,0 +1,49 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2006 Jason Katz-Brown and John O'Laughlin. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef QUACKLE_DICTIMPLEMENTATION_H +#define QUACKLE_DICTIMPLEMENTATION_H + +#include + +#include "dict.h" + +namespace QuackleIO +{ + +class DictImplementation : public Dict::Querier +{ +public: + DictImplementation(); + virtual ~DictImplementation(); + + virtual Dict::WordList query(const QString &query, int flags = None); + virtual QString alphagram(const QString &letters) const; + virtual bool isBritish(const Quackle::LetterString &word); + virtual bool isLoaded() const; + +private: + Quackle::Generator m_generator; +}; + +} + +#endif + diff --git a/quackleio/flexiblealphabet.cpp b/quackleio/flexiblealphabet.cpp new file mode 100644 index 0000000..aca6e00 --- /dev/null +++ b/quackleio/flexiblealphabet.cpp @@ -0,0 +1,131 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2006 Jason Katz-Brown and John O'Laughlin. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include + +#include "flexiblealphabet.h" +#include "util.h" + +using namespace QuackleIO; + +FlexibleAlphabetParameters::FlexibleAlphabetParameters() +{ +} + +bool FlexibleAlphabetParameters::load(const QString &filename) +{ + QFile file(filename); + + if (!file.exists()) + { + UVcout << "alphabet parameters do not exist: " << QuackleIO::Util::qstringToString(filename) << endl; + return false; + } + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + UVcout << "Could not open " << QuackleIO::Util::qstringToString(filename) << endl; + return false; + } + + QTextStream stream(&file); + stream.setCodec(QTextCodec::codecForName("UTF-8")); + + QString line; + Quackle::Letter letter = QUACKLE_FIRST_LETTER; + while (!stream.atEnd()) + { + line = stream.readLine().simplified(); + QStringList strings = line.split(QRegExp("\\s+")); + + if (line.startsWith("#")) + continue; + + if (strings.isEmpty()) + continue; + + QString text = strings.front(); + const UVString textUV = QuackleIO::Util::qstringToString(text); + strings.pop_front(); + + const bool isBlank = text.startsWith("blank", Qt::CaseInsensitive); + + if (isBlank) + { + if (strings.size() < 2) + { + UVcerr << "Error reading in alphabet: Blank specification does not specify count and score."; + continue; + } + } + else + { + if (strings.size() < 4) + { + UVcerr << "Error reading in alphabet: Letter specification does specify blank text, count, score, and vowelness."; + continue; + } + } + + QString blankText; + if (!isBlank) + { + blankText = strings.front(); + strings.pop_front(); + } + + + bool ok = false; + int score = strings.takeFirst().toInt(&ok); + if (!ok) + UVcerr << "Score of letter " << textUV << " is unparsable."; + + int count = strings.takeFirst().toInt(&ok); + if (!ok) + UVcerr << "Count of letter " << textUV << " is unparsable."; + + bool isVowel = false; + if (!isBlank) + { + isVowel = strings.takeFirst().toInt(&ok); + if (!ok) + UVcerr << "Vowelness of letter " << textUV << " is unparsable. (Must be 0 or 1.)"; + } + + if (isBlank) + { + setCount(QUACKLE_BLANK_MARK, count); + setScore(QUACKLE_BLANK_MARK, score); + } + else + { + const UVString blankTextUV = QuackleIO::Util::qstringToString(blankText); + + setLetterParameter(letter, Quackle::LetterParameter(letter, textUV, blankTextUV, score, count, isVowel)); + ++letter; + + //UVcout << "New letter " << textUV << " [" << (int)letter << "]" << endl; + } + } + + file.close(); + return true; +} + diff --git a/quackleio/flexiblealphabet.h b/quackleio/flexiblealphabet.h new file mode 100644 index 0000000..cd4abae --- /dev/null +++ b/quackleio/flexiblealphabet.h @@ -0,0 +1,49 @@ +/* + * Quackle -- Crossword game artificial intelligence and analysis tool + * Copyright (C) 2005-2006 Jason Katz-Brown and John O'Laughlin. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef QUACKLE_FLEXIBLEALPHABET_H +#define QUACKLE_FLEXIBLEALPHABET_H + +#include "alphabetparameters.h" + +class QString; + +namespace QuackleIO +{ + +class FlexibleAlphabetParameters : public Quackle::AlphabetParameters +{ +public: + FlexibleAlphabetParameters(); + + // Loads alphabet in our format. + // Lines of file should have format: + // + // OR + // blank + // OR + // # some comment + bool load(const QString &filename); +}; + +} + +#endif + diff --git a/quackleio/froggetopt.cpp b/quackleio/froggetopt.cpp new file mode 100644 index 0000000..68f20fe --- /dev/null +++ b/quackleio/froggetopt.cpp @@ -0,0 +1,660 @@ +/********************************************************************** + * Copyright (c) 2003, 2004, froglogic Porten & Stadlbauer GbR + * 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. + * + * * Neither the name of the froglogic nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * + * 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 OWNER 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. + * + **********************************************************************/ + +#define QT_NO_CAST_ASCII +#define QT_NO_ASCII_CAST + +#include + +#include +#include + +#include "froggetopt.h" + +/** + \class GetOpt + + \brief A command line option parser. + + This class helps to overcome the repetitive, tedious and + error-prone task of parsing the command line options passed to your + application by the user. Specify the acceptable syntax with a + minimum of statements in a readable way, check it against the + actual arguments passed and find the retrieved values in variables + of your program. The name \em GetOpt is based on similar utilities + build into the Unix shell and other languages. + + A command line that a user might have entered is: + + \code + app -v --config=my.cnf -Wall input.dat + \endcode + + The typical usage has three stages: + + -# Construct a parser specifying what arguments to parse + -# Set up the list of allowed and required options + -# Run the parser + + For the first step there are three different constructors that + either take arguments directly from \c main(), \c QApplication or a + user specified list. Setting up the accepted syntax is done by a + set of \c add functions like addSwitch(). The final step of running + the parser is simply done by calling parse(). + + A short example implementing a \c --verbose switch: + + \code + int main(int argc, char **argv) + { + GetOpt opts(argc, argv); + bool verbose; + opts.addSwitch("verbose", &verbose); + if (!opts.parse()) + return 1; + if (verbose) + cout << "VERBOSE mode on" << endl; + ... + \endcode + + For a better understanding of the function names we'll better + define some terms used in the API and its documentation: + + - \em Argument An argument is a plain text token like e.g. a file + name one typically passes to an editor when invoking it. + - \em Switch A switch is an on/off kind of argument without the need + of additional information. Example: \c --debug. + - \em Option An option is a normally optional argument with a key-value + syntax like \c --output=out.txt or \c -I/usr/include. + - \em Short \em Option A short option is a one letter option with a + preceding dash. Like \c -v. + - \em Long \em Option A long option has a more verbose, + multi-letter name like \c --debug. + . + + \author froglogic GbR + */ + + +/** + Constructs a command line parser from the arguments stored in a + previously created QApplication instance. + + Example usage: + \code + QApplication a(argc, argv); + + GetOpt opt; + \endcode + + This constructor is probably the most convenient one to use in a + regular Qt application. Note that QApplication may already have + removed Qt (or X11) specific arguments. Also see + QApplication::argv() and QApplication::argc(). + */ +GetOpt::GetOpt() +{ + if ( !QCoreApplication::instance() ) + qFatal( "GetOpt: requires a QApplication instance to be constructed first" ); + + init( QCoreApplication::instance()->argc(), QCoreApplication::instance()->argv(), 1 ); +} + +/** + \internal + */ +GetOpt::GetOpt( int offset ) +{ + if ( !QCoreApplication::instance() ) + qFatal( "GetOpt: requires a QApplication instance to be constructed first" ); + + init( QCoreApplication::instance()->argc(), QCoreApplication::instance()->argv(), offset ); +} + +/** + Construct a command line parser from the array \a argv of string + pointers with the size \a argc. Those parameters have the form + typically found in the \c main() function. That means that you can + simply pass on the arguments specified by the user of your + application. + + Example usage: + + \code + int main(int argc, char **argv) { + GetOpt opt(argc, argv); + ... + } + \endcode + */ +GetOpt::GetOpt( int argc, char *argv[] ) +{ + init( argc, argv ); +} + +/** + Construct a command line parser from the arguments specified in the + list of arguments \a a. This constructor is convenient in those + cases where you want to parse a command line assembled on-the-fly + instead of relying on the \c argc and \c arg parameters passed to + the \c main() function. + */ + GetOpt::GetOpt( const QStringList &a ) +: args( a ) +{ + init( 0, 0 ); +} + +/** + \internal + */ +void GetOpt::init( int argc, char *argv[], int offset ) +{ + numReqArgs = numOptArgs = 0; + currArg = 1; // appname is not part of the arguments + if ( argc ) { + // application name + aname = QFileInfo( QString::fromUtf8( argv[0] ) ).fileName(); + // arguments + for ( int i = offset; i < argc; ++i ) + args.append( QString::fromUtf8( argv[i] ) ); + } +} + +/** + \fn bool GetOpt::parse() + + Parse the command line arguments specified in the constructor under + the conditions set by the various \c add*() functions. On success, + the given variable reference will be initialized with their + respective values and true will be returned. Returns false + otherwise. + + In the future there'll be a way to retrieve an error message. In + the current version the message will be printed to \c stderr. + */ + +/** + \internal + */ +bool GetOpt::parse( bool untilFirstSwitchOnly ) +{ + // qDebug( "parse(%s)", args.join( QString( "," ) ).toLocal8Bit().constData() ); + // push all arguments as we got them on a stack + // more pushes might following when parsing condensed arguments + // like --key=value. + QStack stack; + + if (!args.empty()) + { + QStringList::const_iterator it = args.end(); + while ( it != args.begin() ) { + --it; + stack.push( *it ); + } + } + + const OptionConstIterator obegin = options.begin(); + const OptionConstIterator oend = options.end(); + enum { StartState, ExpectingState, OptionalState } state = StartState; + Option currOpt; + enum TokenType { LongOpt, ShortOpt, Arg, End } t, currType = End; + bool extraLoop = true; // we'll do an extra round. fake an End argument + while ( !stack.isEmpty() || extraLoop ) { + QString a; + QString origA; + // identify argument type + if ( !stack.isEmpty() ) { + a = stack.pop(); + currArg++; + origA = a; + // qDebug( "popped %s", a.toLocal8Bit().constData() ); + if ( a.startsWith( QString::fromLatin1( "--" ) ) ) { + // recognized long option + a = a.mid( 2 ); + if ( a.isEmpty() ) { + qWarning( "'--' feature not supported, yet" ); + exit( 2 ); + } + t = LongOpt; + // split key=value style arguments + int equal = a.indexOf( '=' ); + if ( equal >= 0 ) { + stack.push( a.mid( equal + 1 ) ); + currArg--; + a = a.left( equal ); + } + } else if ( a.length() == 1 ) { + t = Arg; + } else if ( a[0] == '-' ) { +#if 0 // compat mode for -long style options + if ( a.length() == 2 ) { + t = ShortOpt; + a = a[1]; + } else { + a = a.mid( 1 ); + t = LongOpt; + // split key=value style arguments + int equal = a.find( '=' ); + if ( equal >= 0 ) { + stack.push( a.mid( equal + 1 ) ); + currArg--; + a = a.left( equal ); + } + } +#else + // short option + t = ShortOpt; + // followed by an argument ? push it for later processing. + if ( a.length() > 2 ) { + stack.push( a.mid( 2 ) ); + currArg--; + } + a = a[1]; +#endif + } else { + t = Arg; + } + } else { + // faked closing argument + t = End; + } + // look up among known list of options + Option opt; + if ( t != End ) { + OptionConstIterator oit = obegin; + while ( oit != oend ) { + const Option &o = *oit; + if ( ( t == LongOpt && a == o.lname ) || // ### check state + ( t == ShortOpt && a[0].unicode() == o.sname ) ) { + opt = o; + break; + } + ++oit; + } + if ( t == LongOpt && opt.type == OUnknown ) { + if ( currOpt.type != OVarLen ) { + qWarning( "Unknown option --%s", a.toLocal8Bit().constData() ); + return false; + } else { + // VarLength options support arguments starting with '-' + t = Arg; + } + } else if ( t == ShortOpt && opt.type == OUnknown ) { + if ( currOpt.type != OVarLen ) { + qWarning( "Unknown option -%c", a[0].unicode() ); + return false; + } else { + // VarLength options support arguments starting with '-' + t = Arg; + } + } + + } else { + opt = Option( OEnd ); + } + + // interpret result + switch ( state ) { + case StartState: + if ( opt.type == OSwitch ) { + setSwitch( opt ); + setOptions.insert( opt.lname, 1 ); + setOptions.insert( QString( QChar( opt.sname ) ), 1 ); + } else if ( opt.type == OArg1 || opt.type == ORepeat ) { + state = ExpectingState; + currOpt = opt; + currType = t; + setOptions.insert( opt.lname, 1 ); + setOptions.insert( QString( QChar( opt.sname ) ), 1 ); + } else if ( opt.type == OOpt || opt.type == OVarLen ) { + state = OptionalState; + currOpt = opt; + currType = t; + setOptions.insert( opt.lname, 1 ); + setOptions.insert( QString( QChar( opt.sname ) ), 1 ); + } else if ( opt.type == OEnd ) { + // we're done + } else if ( opt.type == OUnknown && t == Arg ) { + if ( numReqArgs > 0 ) { + if ( reqArg.stringValue->isNull() ) { // ### + *reqArg.stringValue = a; + } else { + qWarning( "Too many arguments" ); + return false; + } + } else if ( numOptArgs > 0 ) { + if ( optArg.stringValue->isNull() ) { // ### + *optArg.stringValue = a; + } else { + qWarning( "Too many arguments" ); + return false; + } + } + } else { + qFatal( "unhandled StartState case %d", opt.type ); + } + break; + case ExpectingState: + if ( t == Arg ) { + if ( currOpt.type == OArg1 ) { + *currOpt.stringValue = a; + state = StartState; + } else if ( currOpt.type == ORepeat ) { + currOpt.listValue->append( a ); + state = StartState; + } else { + abort(); + } + } else { + QString n = currType == LongOpt ? + currOpt.lname : QString( QChar( currOpt.sname ) ); + qWarning( "Expected an argument after '%s' option", n.toLocal8Bit().constData() ); + return false; + } + break; + case OptionalState: + if ( t == Arg ) { + if ( currOpt.type == OOpt ) { + *currOpt.stringValue = a; + state = StartState; + } else if ( currOpt.type == OVarLen ) { + currOpt.listValue->append( origA ); + // remain in this state + } else { + abort(); + } + } else { + // optional argument not specified + if ( currOpt.type == OOpt ) + *currOpt.stringValue = currOpt.def; + if ( t != End ) { + // re-evaluate current argument + stack.push( origA ); + currArg--; + } + state = StartState; + } + break; + } + + if ( untilFirstSwitchOnly && opt.type == OSwitch ) + return true; + + // are we in the extra loop ? if so, flag the final end + if ( t == End ) + extraLoop = false; + } + + if ( numReqArgs > 0 && reqArg.stringValue->isNull() ) { + qWarning( "Lacking required argument" ); + return false; + } + + return true; +} + +/** + \internal + */ +void GetOpt::addOption( Option o ) +{ + // ### check for conflicts + options.append( o ); +} + +/** + Adds a switch with the long name \a lname. If the switch is found + during parsing the bool \a *b will bet set to true. Otherwise the + bool will be initialized to false. + +Example: + +\code +GetOpt opt; +bool verbose; +opt.addSwitch("verbose", &verbose); +\endcode + +The boolean flag \c verbose will be set to true if \c --verbose has +been specified in the command line; false otherwise. + */ +void GetOpt::addSwitch( const QString &lname, bool *b ) +{ + Option opt( OSwitch, 0, lname ); + opt.boolValue = b; + addOption( opt ); + // ### could do all inits at the beginning of parse() + *b = false; +} + +/** + \internal + */ +void GetOpt::setSwitch( const Option &o ) +{ + assert( o.type == OSwitch ); + *o.boolValue = true; +} + +/** + Registers an option with the short name \a s and long name \a l to + the parser. If this option is found during parsing the value will + be stored in the string pointed to by \a v. By default \a *v will + be initialized to \c QString::null. + */ +void GetOpt::addOption( char s, const QString &l, QString *v ) +{ + Option opt( OArg1, s, l ); + opt.stringValue = v; + addOption( opt ); + *v = QString::null; +} + +/** + Registers a long option \a l that can have a variable number of + corresponding value parameters. As there currently is no way to + tell the end of the value list the only sensible use of this option + is at the end of the command line. + +Example: + +\code +QStringList args; +opt.addVarLengthOption("exec", &args); +\endcode + +Above code will lead to "-f" and "test.txt" being stored in \a args +upon + +\code +myapp --exec otherapp -f test.txt +\endcode + */ +void GetOpt::addVarLengthOption( const QString &l, QStringList *v ) +{ + Option opt( OVarLen, 0, l ); + opt.listValue = v; + addOption( opt ); + *v = QStringList(); +} + +/** + Registers an option with the short name \a s that can be specified + repeatedly in the command line. The option values will be stored in + the list pointed to by \a v. If no \a s option is found \a *v will + remain at its default value of an empty QStringList instance. + +Example: + +To parse the \c -I options in a command line like +\code +myapp -I/usr/include -I/usr/local/include +\endcode + +you can use code like this: + +\code +GetOpt opt; +QStringList includes; +opt.addRepeatableOption('I', &includes); +opt.parse(); +\endcode + */ +void GetOpt::addRepeatableOption( char s, QStringList *v ) +{ + Option opt( ORepeat, s, QString::null ); + opt.listValue = v; + addOption( opt ); + *v = QStringList(); +} + +/** + Registers an option with the long name \a l that can be specified + repeatedly in the command line. + + \sa addRepeatableOption( char, QStringList* ) + */ +void GetOpt::addRepeatableOption( const QString &l, QStringList *v ) +{ + Option opt( ORepeat, 0, l ); + opt.listValue = v; + addOption( opt ); + *v = QStringList(); +} + +/** + Adds a long option \a l that has an optional value parameter. If + the value is not specified by the user it will be set to \a def. + +Example: + +\code +GetOpt opt; +QString file; +opt.addOptionalOption("dump", &file, ""); +\endcode + +\sa addOption + */ +void GetOpt::addOptionalOption( const QString &l, QString *v, + const QString &def ) +{ + addOptionalOption( 0, l, v, def ); +} + +/** + Adds a short option \a s that has an optional value parameter. If + the value is not specified by the user it will be set to \a def. + */ +void GetOpt::addOptionalOption( char s, const QString &l, + QString *v, const QString &def ) +{ + Option opt( OOpt, s, l ); + opt.stringValue = v; + opt.def = def; + addOption( opt ); + *v = QString::null; +} + +/** + Registers a required command line argument \a name. If the argument + is missing parse() will return false to indicate an error and \a *v + will remain with its default QString::null value. Otherwise \a *v + will be set to the value of the argument. + +Example: + +To accept simple arguments like + +\code +myeditor letter.txt +\endcode + +use a call like: + +\code +QString &file; +opt.addArgument("file", &file); +\endcode + +Note: the \a name parameter has a rather descriptive meaning for +now. It might be used for generating a usage or error message in +the future. Right now, the only current use is in relation with the +isSet() function. + */ +void GetOpt::addArgument( const QString &name, QString *v ) +{ + Option opt( OUnknown, 0, name ); + opt.stringValue = v; + reqArg = opt; + ++numReqArgs; + *v = QString::null; +} + +/** + Registers an optional command line argument \a name. For a more + detailed description see the addArgument() documentation. + + */ +void GetOpt::addOptionalArgument( const QString &name, QString *v ) +{ + Option opt( OUnknown, 0, name ); + opt.stringValue = v; + optArg = opt; + ++numOptArgs; + *v = QString::null; +} + +/** + Returns true if the (long) option or switch \a name has been found + in the command line; returns false otherwise. Leading hyphens are + not part of the name. + + As the set/not set decision can also be made depending on the value + of the variable reference used in the respective \c add*() call + there's generally little use for this function. + */ + +bool GetOpt::isSet( const QString &name ) const +{ + return setOptions.find( name ) != setOptions.end(); +} + +/** + \fn int GetOpt::currentArgument() const + \internal + */ diff --git a/quackleio/froggetopt.h b/quackleio/froggetopt.h new file mode 100644 index 0000000..7376dd4 --- /dev/null +++ b/quackleio/froggetopt.h @@ -0,0 +1,121 @@ +/********************************************************************** + * Copyright (c) 2003, 2004, froglogic Porten & Stadlbauer GbR + * 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. + * + * * Neither the name of the froglogic nor the names of its + * contributors may be used to endorse or promote products + * derived from this software without specific prior written + * permission. + * + * 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 OWNER 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. + * + **********************************************************************/ + +#ifndef FROGGETOPT_H +#define FROGGETOPT_H + +#include +#include + +class GetOpt { +public: + GetOpt(); + GetOpt( int offset ); + GetOpt( int argc, char *argv[] ); + GetOpt( const QStringList &a ); + + QString appName() const { return aname; } + + // switch (no arguments) + void addSwitch( const QString &lname, bool *b ); + + // options (with arguments, sometimes optional) + void addOption( char s, const QString &l, QString *v ); + void addVarLengthOption( const QString &l, QStringList *v ); + void addRepeatableOption( char s, QStringList *v ); + void addRepeatableOption( const QString &l, QStringList *v ); + void addOptionalOption( const QString &l, QString *v, + const QString &def ); + void addOptionalOption( char s, const QString &l, + QString *v, const QString &def ); + + // bare arguments + void addArgument( const QString &name, QString *v ); + void addOptionalArgument( const QString &name, QString *v ); + + bool parse( bool untilFirstSwitchOnly ); + bool parse() { return parse( false ); } + + bool isSet( const QString &name ) const; + + int currentArgument() const { return currArg; } + +private: + enum OptionType { OUnknown, OEnd, OSwitch, OArg1, OOpt, ORepeat, OVarLen }; + + struct Option; + friend struct Option; + + struct Option { + Option( OptionType t = OUnknown, + char s = 0, const QString &l = QString::null ) + : type( t ), + sname( s ), + lname( l ), + boolValue( 0 ) { } + + OptionType type; + char sname; // short option name (0 if none) + QString lname; // long option name (null if none) + union { + bool *boolValue; + QString *stringValue; + QStringList *listValue; + }; + QString def; + }; + + QList