From 071f4ef0122a8bfceeda9a10b41e85ad9a34a28d Mon Sep 17 00:00:00 2001 From: Aaron Seigo Date: Wed, 23 Dec 2015 11:43:37 +0100 Subject: vastly simplify by getting rid of Module as a base class just a move slightly more towards functional --- akonadi2_cli/CMakeLists.txt | 11 ++-- akonadi2_cli/main.cpp | 5 +- akonadi2_cli/module.cpp | 116 +++++++++++++---------------------- akonadi2_cli/module.h | 41 ++++++++----- akonadi2_cli/modules/core_syntax.cpp | 85 +++++++++++++++++++++++++ akonadi2_cli/modules/core_syntax.h | 30 +++++++++ akonadi2_cli/modules/exit/exit.cpp | 39 ------------ akonadi2_cli/modules/exit/exit.h | 37 ----------- akonadi2_cli/modules/help/help.cpp | 80 ------------------------ akonadi2_cli/modules/help/help.h | 37 ----------- akonadi2_cli/repl/replStates.cpp | 4 +- 11 files changed, 191 insertions(+), 294 deletions(-) create mode 100644 akonadi2_cli/modules/core_syntax.cpp create mode 100644 akonadi2_cli/modules/core_syntax.h delete mode 100644 akonadi2_cli/modules/exit/exit.cpp delete mode 100644 akonadi2_cli/modules/exit/exit.h delete mode 100644 akonadi2_cli/modules/help/help.cpp delete mode 100644 akonadi2_cli/modules/help/help.h diff --git a/akonadi2_cli/CMakeLists.txt b/akonadi2_cli/CMakeLists.txt index b5bdb46..a07140e 100644 --- a/akonadi2_cli/CMakeLists.txt +++ b/akonadi2_cli/CMakeLists.txt @@ -3,14 +3,17 @@ project(akonadi2_cli) find_package(Readline REQUIRED) -set(akonadi2_SRCS +set(akonadi2_cli_SRCS main.cpp module.cpp - modules/exit/exit.cpp - modules/help/help.cpp + modules/core_syntax.cpp repl/repl.cpp repl/replStates.cpp state.cpp) -add_executable(${PROJECT_NAME} ${akonadi2_SRCS}) +include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +add_executable(${PROJECT_NAME} ${akonadi2_cli_SRCS}) +target_link_libraries(${PROJECT_NAME} Qt5::Core ${Readline_LIBRARY}) +install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/akonadi2_cli/main.cpp b/akonadi2_cli/main.cpp index 1a036d0..d23e070 100644 --- a/akonadi2_cli/main.cpp +++ b/akonadi2_cli/main.cpp @@ -36,9 +36,6 @@ int main(int argc, char *argv[]) { - // load all modules - Module::loadModules(); - const bool interactive = isatty(fileno(stdin)); const bool startRepl = (argc == 1) && interactive; //TODO: make a json command parse cause that would be awesomesauce @@ -67,5 +64,5 @@ int main(int argc, char *argv[]) QStringList commands = app.arguments(); commands.removeFirst(); - return Module::run(commands); + return Module::self()->run(commands); } diff --git a/akonadi2_cli/module.cpp b/akonadi2_cli/module.cpp index 0b83d98..dfc58c8 100644 --- a/akonadi2_cli/module.cpp +++ b/akonadi2_cli/module.cpp @@ -24,83 +24,61 @@ // TODO: needs a proper registry; making "core" modules plugins is // almost certainly overkill, but this is not the way either -#include "modules/exit/exit.h" -#include "modules/help/help.h" +#include "modules/core_syntax.h" -QList Module::s_modules; -State Module::s_state; +Module *Module::s_module = 0; Module::Syntax::Syntax() { } -Module::Syntax::Syntax(const QString &k, std::function l, const QString &helpText, bool e) +Module::Syntax::Syntax(const QString &k, const QString &helpText, std::function l, Interactivity inter) : keyword(k), - lambda(l), help(helpText), - eventDriven(e) + interactivity(inter), + lambda(l) { } Module::Module() { + QVector > syntaxModules; + syntaxModules << &CoreSyntax::syntax; + for (auto syntaxModule: syntaxModules) { + m_syntax += syntaxModule(); + } } -void Module::loadModules() -{ - addModule(CLI::Exit()); - addModule(CLI::Help()); -} - -void Module::addModule(const Module &module) -{ - s_modules.append(module); -} - -QList Module::modules() -{ - return s_modules; -} - -Module::Command Module::match(const QStringList &commands) +Module *Module::self() { - Command command; - for (const Module &module: s_modules) { - command = module.matches(commands); - if (command.first) { - break; - } + if (!s_module) { + s_module = new Module; } - return command; + return s_module; } -Module::Syntax Module::syntax() const +Module::SyntaxList Module::syntax() const { return m_syntax; } -void Module::setSyntax(const Syntax &syntax) -{ - m_syntax = syntax; -} - bool Module::run(const QStringList &commands) { Command command = match(commands); if (command.first && command.first->lambda) { - bool rv = command.first->lambda(command.second, s_state); - if (rv && command.first->eventDriven) { + command.first->lambda(command.second, m_state); + if (command.first->interactivity == Syntax::EventDriven) { return QCoreApplication::instance()->exec(); } - return rv; + return true; } return false; } -Module::Command Module::matches(const QStringList &commandLine) const +Module::Command Module::match(const QStringList &commandLine) const { if (commandLine.isEmpty()) { return Command(); @@ -108,20 +86,16 @@ Module::Command Module::matches(const QStringList &commandLine) const QStringListIterator commandLineIt(commandLine); - if (commandLineIt.next() != m_syntax.keyword) { - return Command(); - } - - QListIterator syntaxIt(m_syntax.children); - const Syntax *syntax = &m_syntax; + QVectorIterator syntaxIt(m_syntax); + const Syntax *lastFullSyntax = 0; QStringList tailCommands; while (commandLineIt.hasNext() && syntaxIt.hasNext()) { const QString word = commandLineIt.next(); while (syntaxIt.hasNext()) { - const Syntax &child = syntaxIt.next(); - if (word == child.keyword) { - syntax = &child; - syntaxIt = child.children; + const Syntax &syntax = syntaxIt.next(); + if (word == syntax.keyword) { + lastFullSyntax = &syntax; + syntaxIt = syntax.children; } } @@ -131,55 +105,47 @@ Module::Command Module::matches(const QStringList &commandLine) const } } - if (syntax && syntax->lambda) { + if (lastFullSyntax && lastFullSyntax->lambda) { while (commandLineIt.hasNext()) { tailCommands << commandLineIt.next(); } - return std::make_pair(syntax, tailCommands); + return std::make_pair(lastFullSyntax, tailCommands); } return Command(); } -QVector Module::nearestSyntax(const QStringList &words, const QString &fragment) +Module::SyntaxList Module::nearestSyntax(const QStringList &words, const QString &fragment) const { - QVector matches; + SyntaxList matches; //qDebug() << "words are" << words; if (words.isEmpty()) { - for (const Module &module: s_modules) { - if (module.syntax().keyword.startsWith(fragment)) { - matches.push_back(module.syntax()); + for (const Syntax &syntax: m_syntax) { + if (syntax.keyword.startsWith(fragment)) { + matches.push_back(syntax); } } } else { QStringListIterator wordIt(words); - QString word = wordIt.next(); + QVectorIterator syntaxIt(m_syntax); Syntax lastFullSyntax; - for (const Module &module: s_modules) { - if (module.syntax().keyword == word) { - lastFullSyntax = module.syntax(); - QListIterator syntaxIt(module.syntax().children); - while (wordIt.hasNext()) { - word = wordIt.next(); - while (syntaxIt.hasNext()) { - const Syntax &child = syntaxIt.next(); - if (word == child.keyword) { - lastFullSyntax = child; - syntaxIt = child.children; - } - } + while (wordIt.hasNext()) { + QString word = wordIt.next(); + while (syntaxIt.hasNext()) { + const Syntax &syntax = syntaxIt.next(); + if (word == syntax.keyword) { + lastFullSyntax = syntax; + syntaxIt = syntax.children; } - - break; } } //qDebug() << "exiting with" << lastFullSyntax.keyword << words.last(); if (lastFullSyntax.keyword == words.last()) { - QListIterator syntaxIt(lastFullSyntax.children); + syntaxIt = lastFullSyntax.children; while (syntaxIt.hasNext()) { Syntax syntax = syntaxIt.next(); if (fragment.isEmpty() || syntax.keyword.startsWith(fragment)) { diff --git a/akonadi2_cli/module.h b/akonadi2_cli/module.h index 4f426b8..d2745d0 100644 --- a/akonadi2_cli/module.h +++ b/akonadi2_cli/module.h @@ -29,33 +29,42 @@ class Module public: struct Syntax { + enum Interactivity { + NotInteractive = 0, + EventDriven + }; + Syntax(); - Syntax(const QString &keyword, std::function lambda = std::function(), const QString &helpText = QString(), bool eventDriven = false); + Syntax(const QString &keyword, + const QString &helpText = QString(), + std::function lambda = std::function(), + Interactivity interactivity = NotInteractive); + QString keyword; - std::function lambda; - QList children; QString help; - bool eventDriven; + Interactivity interactivity; + std::function lambda; + + QVector children; }; typedef std::pair Command; + typedef QVector SyntaxList; - static void addModule(const Module &module); - static QList modules(); - static Command match(const QStringList &commands); - static bool run(const QStringList &commands); - static void loadModules(); - static QVectornearestSyntax(const QStringList &words, const QString &fragment); + static Module *self(); - Module(); - Module::Syntax syntax() const; - void setSyntax(const Syntax &syntax); + SyntaxList syntax() const; + Command match(const QStringList &commands) const; + SyntaxList nearestSyntax(const QStringList &words, const QString &fragment) const; + + bool run(const QStringList &commands); private: + Module(); Command matches(const QStringList &commands) const; - Syntax m_syntax; - static QList s_modules; - static State s_state; + SyntaxList m_syntax; + State m_state; + static Module *s_module; }; diff --git a/akonadi2_cli/modules/core_syntax.cpp b/akonadi2_cli/modules/core_syntax.cpp new file mode 100644 index 0000000..8324c31 --- /dev/null +++ b/akonadi2_cli/modules/core_syntax.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 Aaron Seigo + * + * 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 "core_syntax.h" + +#include // tr() +#include +#include + +namespace CoreSyntax +{ + +Module::SyntaxList syntax() +{ + Module::SyntaxList syntax; + syntax << Module::Syntax("exit", QObject::tr("Exits the application. Ctrl-d also works!"), &CoreSyntax::exit); + syntax << Module::Syntax(QObject::tr("help"), QObject::tr("Print command information: help [command]"), &CoreSyntax::showHelp); + return syntax; +} + +bool exit(const QStringList &, State &) +{ + ::exit(0); + return true; +} + +bool showHelp(const QStringList &commands, State &) +{ + Module::Command command = Module::self()->match(commands); + QTextStream stream(stdout); + if (commands.isEmpty()) { + stream << QObject::tr("Welcome to the Akonadi2 command line tool!") << "\n"; + stream << QObject::tr("Top-level commands:") << "\n"; + QSet sorted; + for (auto syntax: Module::self()->syntax()) { + sorted.insert(syntax.keyword); + } + + for (auto keyword: sorted) { + stream << "\t" << keyword << "\n"; + } + } else if (const Module::Syntax *syntax = command.first) { + //TODO: get parent! + stream << QObject::tr("Command `%1`").arg(syntax->keyword); + + if (!syntax->help.isEmpty()) { + stream << ": " << syntax->help; + } + stream << "\n"; + + if (!syntax->children.isEmpty()) { + stream << "\tSub-commands:\n"; + QSet sorted; + for (auto childSyntax: syntax->children) { + sorted.insert(childSyntax.keyword); + } + + for (auto keyword: sorted) { + stream << "\t" << keyword << "\n"; + } + } + } else { + stream << "Unknown command: " << commands.join(" ") << "\n"; + } + return true; +} + +} // namespace CoreSyntax + diff --git a/akonadi2_cli/modules/core_syntax.h b/akonadi2_cli/modules/core_syntax.h new file mode 100644 index 0000000..beb8528 --- /dev/null +++ b/akonadi2_cli/modules/core_syntax.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 Aaron Seigo + * + * 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. + */ + +#pragma once + +#include "module.h" + +namespace CoreSyntax +{ + Module::SyntaxList syntax(); + bool exit(const QStringList &commands, State &state); + bool showHelp(const QStringList &commands, State &); +} + diff --git a/akonadi2_cli/modules/exit/exit.cpp b/akonadi2_cli/modules/exit/exit.cpp deleted file mode 100644 index 64828be..0000000 --- a/akonadi2_cli/modules/exit/exit.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2014 Aaron Seigo - * - * 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 "exit.h" - -#include - -namespace CLI -{ - -Exit::Exit() -{ - setSyntax(Syntax("exit", &Exit::exit, QObject::tr("Exits the application. Ctrl-d also works!"))); -} - -bool Exit::exit(const QStringList &, State &) -{ - ::exit(0); - return true; -} - -} // namespace CLI - diff --git a/akonadi2_cli/modules/exit/exit.h b/akonadi2_cli/modules/exit/exit.h deleted file mode 100644 index 5ed4174..0000000 --- a/akonadi2_cli/modules/exit/exit.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2014 Aaron Seigo - * - * 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. - */ - -#pragma once - -#include "module.h" - -namespace CLI -{ - -class Exit : public Module -{ -public: - Exit(); - -private: - static bool exit(const QStringList &commands, State &state); -}; - -} // namespace CLI - diff --git a/akonadi2_cli/modules/help/help.cpp b/akonadi2_cli/modules/help/help.cpp deleted file mode 100644 index aaff6fb..0000000 --- a/akonadi2_cli/modules/help/help.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2014 Aaron Seigo - * - * 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 "help.h" - -#include -#include -#include - -#include "module.h" - -namespace CLI -{ - -Help::Help() -{ - Syntax topLevel = Syntax(QObject::tr("help"), &Help::showHelp, QObject::tr("Print command information: help [command]")); - setSyntax(topLevel); -} - -bool Help::showHelp(const QStringList &commands, State &) -{ - Module::Command command = Module::match(commands); - QTextStream stream(stdout); - if (commands.isEmpty()) { - stream << QObject::tr("Welcome to the Akonadi2 command line tool!") << "\n"; - stream << QObject::tr("Top-level commands:") << "\n"; - QSet sorted; - for (auto module: Module::modules()) { - sorted.insert(module.syntax().keyword); - } - - for (auto keyword: sorted) { - stream << "\t" << keyword << "\n"; - } - } else if (const Syntax *syntax = command.first) { - //TODO: get parent! - stream << QObject::tr("Command `%1`").arg(syntax->keyword); - - if (!syntax->help.isEmpty()) { - stream << ": " << syntax->help; - } - stream << "\n"; - - if (!syntax->children.isEmpty()) { - stream << "\tSub-commands:\n"; - QSet sorted; - for (auto childSyntax: syntax->children) { - sorted.insert(childSyntax.keyword); - } - - for (auto keyword: sorted) { - stream << "\t" << keyword << "\n"; - } - } - } else { - stream << "Unknown command: " << commands.join(" ") << "\n"; - } - - return true; -} - -} // namespace CLI - diff --git a/akonadi2_cli/modules/help/help.h b/akonadi2_cli/modules/help/help.h deleted file mode 100644 index df1cfc2..0000000 --- a/akonadi2_cli/modules/help/help.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2014 Aaron Seigo - * - * 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. - */ - -#pragma once - -#include "module.h" - -namespace CLI -{ - -class Help : public Module -{ -public: - Help(); - -private: - static bool showHelp(const QStringList &commands, State &state); -}; - -} // namespace CLI - diff --git a/akonadi2_cli/repl/replStates.cpp b/akonadi2_cli/repl/replStates.cpp index e87dd5f..efa1353 100644 --- a/akonadi2_cli/repl/replStates.cpp +++ b/akonadi2_cli/repl/replStates.cpp @@ -107,7 +107,7 @@ void EvalState::onEntry(QEvent *event) if (m_complete) { //emit output("Processing ... " + command); const QStringList commands = command.split(" "); - Module::run(commands); + Module::self()->run(commands); emit completed(); } } @@ -143,7 +143,7 @@ static char **akonadi2_cli_tab_completion(const char *text, int start, int end) static char *akonadi2_cli_next_tab_complete_match(const char *text, int state) { - QVector nearest = Module::nearestSyntax(tab_completion_full_state, QString(text)); + QVector nearest = Module::self()->nearestSyntax(tab_completion_full_state, QString(text)); if (nearest.size() > state) { return qstrdup(nearest[state].keyword.toUtf8()); -- cgit v1.2.3