From kde-commits Wed Dec 23 11:09:11 2015 From: Aaron Seigo Date: Wed, 23 Dec 2015 11:09:11 +0000 To: kde-commits Subject: [akonadi-next/feature/new_cli] /: initial import of the new cli app Message-Id: X-MARC-Message: https://marc.info/?l=kde-commits&m=145086896608014 Git commit 255d73af197faf8437343abc10bd98cca2057a1e by Aaron Seigo. Committed on 23/12/2015 at 09:47. Pushed by aseigo into branch 'feature/new_cli'. initial import of the new cli app M +3 -0 CMakeLists.txt A +16 -0 akonadi2_cli/CMakeLists.txt A +71 -0 akonadi2_cli/main.cpp [License: GPL (v2+)] A +195 -0 akonadi2_cli/module.cpp [License: GPL (v2+)] A +61 -0 akonadi2_cli/module.h [License: GPL (v2+)] A +39 -0 akonadi2_cli/modules/exit/exit.cpp [License: GPL (v2+)] A +37 -0 akonadi2_cli/modules/exit/exit.h [License: GPL (v2+)] A +80 -0 akonadi2_cli/modules/help/help.cpp [License: GPL (v2+)] A +37 -0 akonadi2_cli/modules/help/help.h [License: GPL (v2+)] A +78 -0 akonadi2_cli/repl/repl.cpp [License: GPL (v2+)] A +34 -0 akonadi2_cli/repl/repl.h [License: GPL (v2+)] A +155 -0 akonadi2_cli/repl/replStates.cpp [License: GPL (v2+)] A +85 -0 akonadi2_cli/repl/replStates.h [License: GPL (v2+)] A +28 -0 akonadi2_cli/state.cpp [License: GPL (v2+)] A +29 -0 akonadi2_cli/state.h [License: GPL (v2+)] A +47 -0 cmake/modules/FindReadline.cmake http://commits.kde.org/akonadi-next/255d73af197faf8437343abc10bd98cca2057a1e diff --git a/CMakeLists.txt b/CMakeLists.txt index c480ddd..d92f830 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,4 +57,7 @@ add_subdirectory(examples) # some tests add_subdirectory(tests) = +# cli +add_subdirectory(akonadi2_cli) + feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/akonadi2_cli/CMakeLists.txt b/akonadi2_cli/CMakeLists.txt new file mode 100644 index 0000000..b5bdb46 --- /dev/null +++ b/akonadi2_cli/CMakeLists.txt @@ -0,0 +1,16 @@ +project(akonadi2_cli) + +find_package(Readline REQUIRED) + + +set(akonadi2_SRCS + main.cpp + module.cpp + modules/exit/exit.cpp + modules/help/help.cpp + repl/repl.cpp + repl/replStates.cpp + state.cpp) + +add_executable(${PROJECT_NAME} ${akonadi2_SRCS}) + diff --git a/akonadi2_cli/main.cpp b/akonadi2_cli/main.cpp new file mode 100644 index 0000000..1a036d0 --- /dev/null +++ b/akonadi2_cli/main.cpp @@ -0,0 +1,71 @@ +/* + * 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 + +#include +#include + +#include "module.h" +// #include "jsonlistener.h" +#include "repl/repl.h" + +/* + * modes of operation: + * + * 1. called with no commands: start the REPL and listen for JSON on stin + * 2. called with -: listen for JSON on stdin + * 3. called with commands: try to match to syntx + */ + +int main(int argc, char *argv[]) +{ + // load all modules + Module::loadModules(); + + const bool interactive =3D isatty(fileno(stdin)); + const bool startRepl =3D (argc =3D=3D 1) && interactive; + //TODO: make a json command parse cause that would be awesomesauce + const bool startJsonListener =3D !startRepl && + (argc =3D=3D 2 && qstrcmp(argv[1], "-")= =3D=3D 0); + qDebug() << "state at startup is" << interactive << startRepl << start= JsonListener; + + QCoreApplication app(argc, argv); + app.setApplicationName("funq"); + + if (startRepl || startJsonListener) { + if (startRepl) { + Repl *repl =3D new Repl; + QObject::connect(repl, &QStateMachine::finished, + repl, &QObject::deleteLater); + QObject::connect(repl, &QStateMachine::finished, + &app, &QCoreApplication::quit); + } + + if (startJsonListener) { +// JsonListener listener(syntax); + } + + return app.exec(); + } + + QStringList commands =3D app.arguments(); + commands.removeFirst(); + return Module::run(commands); +} diff --git a/akonadi2_cli/module.cpp b/akonadi2_cli/module.cpp new file mode 100644 index 0000000..0b83d98 --- /dev/null +++ b/akonadi2_cli/module.cpp @@ -0,0 +1,195 @@ +/* + * 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 "module.h" + +#include +#include + +// 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" + +QList Module::s_modules; +State Module::s_state; + +Module::Syntax::Syntax() +{ +} + +Module::Syntax::Syntax(const QString &k, std::function l, const QString &helpText, bool e) + : keyword(k), + lambda(l), + help(helpText), + eventDriven(e) +{ +} + +Module::Module() +{ +} + +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) +{ + Command command; + for (const Module &module: s_modules) { + command =3D module.matches(commands); + if (command.first) { + break; + } + } + + return command; +} + +Module::Syntax Module::syntax() const +{ + return m_syntax; +} + +void Module::setSyntax(const Syntax &syntax) +{ + m_syntax =3D syntax; +} + +bool Module::run(const QStringList &commands) +{ + Command command =3D match(commands); + if (command.first && command.first->lambda) { + bool rv =3D command.first->lambda(command.second, s_state); + if (rv && command.first->eventDriven) { + return QCoreApplication::instance()->exec(); + } + + return rv; + } + + return false; +} + +Module::Command Module::matches(const QStringList &commandLine) const +{ + if (commandLine.isEmpty()) { + return Command(); + } + + QStringListIterator commandLineIt(commandLine); + + if (commandLineIt.next() !=3D m_syntax.keyword) { + return Command(); + } + + QListIterator syntaxIt(m_syntax.children); + const Syntax *syntax =3D &m_syntax; + QStringList tailCommands; + while (commandLineIt.hasNext() && syntaxIt.hasNext()) { + const QString word =3D commandLineIt.next(); + while (syntaxIt.hasNext()) { + const Syntax &child =3D syntaxIt.next(); + if (word =3D=3D child.keyword) { + syntax =3D &child; + syntaxIt =3D child.children; + } + } + + if (!syntaxIt.hasNext()) { + tailCommands << word; + break; + } + } + + if (syntax && syntax->lambda) { + while (commandLineIt.hasNext()) { + tailCommands << commandLineIt.next(); + } + + return std::make_pair(syntax, tailCommands); + } + + return Command(); +} + +QVector Module::nearestSyntax(const QStringList &words, co= nst QString &fragment) +{ + QVector 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()); + } + } + } else { + QStringListIterator wordIt(words); + QString word =3D wordIt.next(); + Syntax lastFullSyntax; + + for (const Module &module: s_modules) { + if (module.syntax().keyword =3D=3D word) { + lastFullSyntax =3D module.syntax(); + QListIterator syntaxIt(module.syntax().children); + while (wordIt.hasNext()) { + word =3D wordIt.next(); + while (syntaxIt.hasNext()) { + const Syntax &child =3D syntaxIt.next(); + if (word =3D=3D child.keyword) { + lastFullSyntax =3D child; + syntaxIt =3D child.children; + } + } + } + + break; + } + } + + //qDebug() << "exiting with" << lastFullSyntax.keyword << words.la= st(); + if (lastFullSyntax.keyword =3D=3D words.last()) { + QListIterator syntaxIt(lastFullSyntax.children); + while (syntaxIt.hasNext()) { + Syntax syntax =3D syntaxIt.next(); + if (fragment.isEmpty() || syntax.keyword.startsWith(fragme= nt)) { + matches.push_back(syntax); + } + } + } + } + + return matches; +} + + diff --git a/akonadi2_cli/module.h b/akonadi2_cli/module.h new file mode 100644 index 0000000..4f426b8 --- /dev/null +++ b/akonadi2_cli/module.h @@ -0,0 +1,61 @@ +/* + * 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 "state.h" + +#include +#include + +class Module +{ +public: + struct Syntax + { + Syntax(); + Syntax(const QString &keyword, std::function lambda =3D std::function= (), const QString &helpText =3D QString(), bool eventDriven =3D false); + QString keyword; + std::function lambda; + QList children; + QString help; + bool eventDriven; + }; + + typedef std::pair Command; + + 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 QS= tring &fragment); + + Module(); + Module::Syntax syntax() const; + void setSyntax(const Syntax &syntax); + +private: + Command matches(const QStringList &commands) const; + + Syntax m_syntax; + static QList s_modules; + static State s_state; +}; + diff --git a/akonadi2_cli/modules/exit/exit.cpp b/akonadi2_cli/modules/exit= /exit.cpp new file mode 100644 index 0000000..64828be --- /dev/null +++ b/akonadi2_cli/modules/exit/exit.cpp @@ -0,0 +1,39 @@ +/* + * 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 applicati= on. 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/e= xit.h new file mode 100644 index 0000000..5ed4174 --- /dev/null +++ b/akonadi2_cli/modules/exit/exit.h @@ -0,0 +1,37 @@ +/* + * 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 new file mode 100644 index 0000000..aaff6fb --- /dev/null +++ b/akonadi2_cli/modules/help/help.cpp @@ -0,0 +1,80 @@ +/* + * 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 =3D Syntax(QObject::tr("help"), &Help::showHelp, QObje= ct::tr("Print command information: help [command]")); + setSyntax(topLevel); +} + +bool Help::showHelp(const QStringList &commands, State &) +{ + Module::Command command =3D 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 =3D 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/h= elp.h new file mode 100644 index 0000000..df1cfc2 --- /dev/null +++ b/akonadi2_cli/modules/help/help.h @@ -0,0 +1,37 @@ +/* + * 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/repl.cpp b/akonadi2_cli/repl/repl.cpp new file mode 100644 index 0000000..395661e --- /dev/null +++ b/akonadi2_cli/repl/repl.cpp @@ -0,0 +1,78 @@ +/* + * 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 "repl.h" + +#include + +#include +#include +#include +#include + +#include "replStates.h" + +Repl::Repl(QObject *parent) + : QStateMachine(parent) +{ + // readline history setup + using_history(); + read_history(commandHistoryPath().toLocal8Bit()); + + // create all states + ReadState *read =3D new ReadState(this); + UnfinishedReadState *unfinishedRead =3D new UnfinishedReadState(this); + EvalState *eval =3D new EvalState(this); + PrintState *print =3D new PrintState(this); + QFinalState *final =3D new QFinalState(this); + + // connect the transitions + read->addTransition(read, SIGNAL(command(QString)), eval); + read->addTransition(read, SIGNAL(exitRequested()), final); + + unfinishedRead->addTransition(unfinishedRead, SIGNAL(command(QString))= , eval); + unfinishedRead->addTransition(unfinishedRead, SIGNAL(exitRequested()),= final); + + eval->addTransition(eval, SIGNAL(completed()), read); + eval->addTransition(eval, SIGNAL(continueInput()), unfinishedRead); + eval->addTransition(eval, SIGNAL(output(QString)), print); + + print->addTransition(print, SIGNAL(completed()), eval); + + setInitialState(read); + start(); +} + +Repl::~Repl() +{ + // readline history writing + write_history(commandHistoryPath().toLocal8Bit()); +} + +QString Repl::commandHistoryPath() +{ + const QString path =3D QStandardPaths::writableLocation(QStandardPaths= ::DataLocation); + if (!QFile::exists(path)) { + QDir dir; + dir.mkpath(path); + } + return path + "/repl_history"; +} + +#include "moc_repl.cpp" diff --git a/akonadi2_cli/repl/repl.h b/akonadi2_cli/repl/repl.h new file mode 100644 index 0000000..b76c66b --- /dev/null +++ b/akonadi2_cli/repl/repl.h @@ -0,0 +1,34 @@ +/* + * 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 + +class Repl : public QStateMachine +{ + Q_OBJECT + +public: + Repl(QObject *parent =3D 0); + ~Repl(); + +private: + static QString commandHistoryPath(); +}; diff --git a/akonadi2_cli/repl/replStates.cpp b/akonadi2_cli/repl/replState= s.cpp new file mode 100644 index 0000000..e87dd5f --- /dev/null +++ b/akonadi2_cli/repl/replStates.cpp @@ -0,0 +1,155 @@ +/* + * 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 "replStates.h" + +#include +#include + +#include +#include + +#include +#include +#include + +#include "module.h" + +static char *akonadi2_cli_next_tab_complete_match(const char *text, int st= ate); +static char ** akonadi2_cli_tab_completion(const char *text, int start, in= t end); + +ReadState::ReadState(QState *parent) + : QState(parent) +{ + rl_completion_entry_function =3D akonadi2_cli_next_tab_complete_match; + rl_attempted_completion_function =3D akonadi2_cli_tab_completion; +} + +void ReadState::onEntry(QEvent *event) +{ + Q_UNUSED(event) + char *line =3D readline(prompt()); + + if (!line) { + std::cout << std::endl; + emit exitRequested(); + return; + } + + // we have actual data, so let's wait for a full line of text + QByteArray input(line); + const QString text =3D QString(line).simplified(); + //qDebug() << "Line is ... " << text; + + if (text.length() > 0) { + add_history(line); + } + + free(line); + emit command(text); +} + +const char *ReadState::prompt() const +{ + return "> "; +} + +UnfinishedReadState::UnfinishedReadState(QState *parent) + : ReadState(parent) +{ +} + +const char *UnfinishedReadState::prompt() const +{ + return " "; +} + +EvalState::EvalState(QState *parent) + : QState(parent), + m_complete(false) +{ +} + +void EvalState::onEntry(QEvent *event) +{ + QStateMachine::SignalEvent *e =3D dynamic_cast(event); + + if (!e || e->arguments().isEmpty()) { + if (m_complete) { + emit completed(); + } else { + emit continueInput(); + } + return; + } + + //TODO: properly tokenize (e.g. "foo bar" should not become ['"foo', '= bar"'] + const QString command =3D e->arguments()[0].toString(); + + if (!command.isEmpty()) { + m_complete =3D command.right(1) !=3D "\\"; + if (m_complete) { + //emit output("Processing ... " + command); + const QStringList commands =3D command.split(" "); + Module::run(commands); + emit completed(); + } + } +} + +PrintState::PrintState(QState *parent) + : QState(parent) +{ +} + +void PrintState::onEntry(QEvent *event) +{ + QStateMachine::SignalEvent *e =3D dynamic_cast(event); + + if (e && !e->arguments().isEmpty()) { + const QString command =3D e->arguments()[0].toString(); + QTextStream stream(stdout); + stream << command << "\n"; + } + + emit completed(); +} + +static QStringList tab_completion_full_state; +static bool tab_completion_at_root =3D false; + +static char **akonadi2_cli_tab_completion(const char *text, int start, int= end) +{ + tab_completion_at_root =3D start =3D=3D 0; + tab_completion_full_state =3D QString(rl_line_buffer).remove(start, en= d - start).split(" ", QString::SkipEmptyParts); + return NULL; +} + +static char *akonadi2_cli_next_tab_complete_match(const char *text, int st= ate) +{ + QVector nearest =3D Module::nearestSyntax(tab_completi= on_full_state, QString(text)); + + if (nearest.size() > state) { + return qstrdup(nearest[state].keyword.toUtf8()); + } + + return rl_filename_completion_function(text, state); +} + +#include "moc_replStates.cpp" diff --git a/akonadi2_cli/repl/replStates.h b/akonadi2_cli/repl/replStates.h new file mode 100644 index 0000000..a019a37 --- /dev/null +++ b/akonadi2_cli/repl/replStates.h @@ -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. + */ + +#pragma once + +#include + +class QSocketNotifier; + +class ReadState : public QState +{ + Q_OBJECT + +public: + ReadState(QState *parent =3D 0); + +Q_SIGNALS: + void command(const QString &command); + void exitRequested(); + +protected: + void onEntry(QEvent *event); + virtual const char *prompt() const; +}; + +class UnfinishedReadState : public ReadState +{ + Q_OBJECT + +public: + UnfinishedReadState(QState *parent =3D 0); + +protected: + const char *prompt() const; +}; + +class EvalState : public QState +{ + Q_OBJECT + +public: + EvalState(QState *parent =3D 0); + +Q_SIGNALS: + void completed(); + void continueInput(); + void output(const QString &output); + +protected: + void onEntry(QEvent *event); + +private: + bool m_complete; +}; + +class PrintState : public QState +{ + Q_OBJECT + +public: + PrintState(QState *parent =3D 0); + +Q_SIGNALS: + void completed(); + +protected: + void onEntry(QEvent *event); +}; + diff --git a/akonadi2_cli/state.cpp b/akonadi2_cli/state.cpp new file mode 100644 index 0000000..bb21e0e --- /dev/null +++ b/akonadi2_cli/state.cpp @@ -0,0 +1,28 @@ +/* + * 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 "state.h" + +#include +#include + +State::State() +{ +} + diff --git a/akonadi2_cli/state.h b/akonadi2_cli/state.h new file mode 100644 index 0000000..10b2318 --- /dev/null +++ b/akonadi2_cli/state.h @@ -0,0 +1,29 @@ +/* + * 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 + +class State +{ +public: + State(); +}; + diff --git a/cmake/modules/FindReadline.cmake b/cmake/modules/FindReadline.= cmake new file mode 100644 index 0000000..883ad3f --- /dev/null +++ b/cmake/modules/FindReadline.cmake @@ -0,0 +1,47 @@ +# - Try to find readline include dirs and libraries = +# +# Usage of this module as follows: +# +# find_package(Readline) +# +# Variables used by this module, they can change the default behaviour and= need +# to be set before calling find_package: +# +# Readline_ROOT_DIR Set this variable to the root installation of +# readline if the module has problems finding t= he +# proper installation path. +# +# Variables defined by this module: +# +# READLINE_FOUND System has readline, include and lib dirs fou= nd +# Readline_INCLUDE_DIR The readline include directories. = +# Readline_LIBRARY The readline library. + +find_path(Readline_ROOT_DIR + NAMES include/readline/readline.h + ) + +find_path(Readline_INCLUDE_DIR + NAMES readline/readline.h + HINTS ${Readline_ROOT_DIR}/include + ) + +find_library(Readline_LIBRARY + NAMES readline + HINTS ${Readline_ROOT_DIR}/lib + ) + +if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + set(READLINE_FOUND TRUE) +else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + FIND_LIBRARY(Readline_LIBRARY NAMES readline) + include(FindPackageHandleStandardArgs) + FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE= _DIR Readline_LIBRARY ) + MARK_AS_ADVANCED(Readline_INCLUDE_DIR Readline_LIBRARY) +endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) + +mark_as_advanced( + Readline_ROOT_DIR + Readline_INCLUDE_DIR + Readline_LIBRARY + )