[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [akonadi-next/feature/new_cli] /: initial import of the new cli app
From:       Aaron Seigo <aseigo () kde ! org>
Date:       2015-12-23 11:09:11
Message-ID: E1aBhI7-0008IN-Sv () scm ! kde ! org
[Download RAW message or body]

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 <aseigo@kde.org>
+ *
+ *   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 <unistd.h>
+
+#include <QCoreApplication>
+#include <QDebug>
+
+#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 = isatty(fileno(stdin));
+    const bool startRepl = (argc == 1) && interactive;
+    //TODO: make a json command parse cause that would be awesomesauce
+    const bool startJsonListener = !startRepl &&
+                                   (argc == 2 && qstrcmp(argv[1], "-") == 0);
+    qDebug() << "state at startup is" << interactive << startRepl << \
startJsonListener; +
+    QCoreApplication app(argc, argv);
+    app.setApplicationName("funq");
+
+    if (startRepl || startJsonListener) {
+        if (startRepl) {
+            Repl *repl = 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 = 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 <aseigo@kde.org>
+ *
+ *   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 <QCoreApplication>
+#include <QDebug>
+
+// 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> Module::s_modules;
+State Module::s_state;
+
+Module::Syntax::Syntax()
+{
+}
+
+Module::Syntax::Syntax(const QString &k, std::function<bool(const QStringList &, \
State &)> 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> Module::modules()
+{
+    return s_modules;
+}
+
+Module::Command Module::match(const QStringList &commands)
+{
+    Command command;
+    for (const Module &module: s_modules) {
+        command = 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 = 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) {
+            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() != m_syntax.keyword) {
+        return Command();
+    }
+
+    QListIterator<Syntax> syntaxIt(m_syntax.children);
+    const Syntax *syntax = &m_syntax;
+    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;
+            }
+        }
+
+        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::Syntax> Module::nearestSyntax(const QStringList &words, const \
QString &fragment) +{
+    QVector<Module::Syntax> 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 = wordIt.next();
+        Syntax lastFullSyntax;
+
+        for (const Module &module: s_modules) {
+            if (module.syntax().keyword == word) {
+                lastFullSyntax = module.syntax();
+                QListIterator<Syntax> 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;
+                        }
+                    }
+                }
+
+                break;
+            }
+        }
+
+        //qDebug() << "exiting with" << lastFullSyntax.keyword << words.last();
+        if (lastFullSyntax.keyword == words.last()) {
+            QListIterator<Syntax> syntaxIt(lastFullSyntax.children);
+            while (syntaxIt.hasNext()) {
+                Syntax syntax = syntaxIt.next();
+                if (fragment.isEmpty() || syntax.keyword.startsWith(fragment)) {
+                    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 <aseigo@kde.org>
+ *
+ *   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 <QStringList>
+#include <QVector>
+
+class Module
+{
+public:
+    struct Syntax
+    {
+        Syntax();
+        Syntax(const QString &keyword, std::function<bool(const QStringList &, State \
&)> lambda = std::function<bool(const QStringList &, State &)>(), const QString \
&helpText = QString(), bool eventDriven = false); +        QString keyword;
+        std::function<bool(const QStringList &, State &)> lambda;
+        QList<Syntax> children;
+        QString help;
+        bool eventDriven;
+    };
+
+    typedef std::pair<const Syntax *, QStringList> Command;
+
+    static void addModule(const Module &module);
+    static QList<Module> modules();
+    static Command match(const QStringList &commands);
+    static bool run(const QStringList &commands);
+    static void loadModules();
+    static QVector<Syntax>nearestSyntax(const QStringList &words, const QString \
&fragment); +
+    Module();
+    Module::Syntax syntax() const;
+    void setSyntax(const Syntax &syntax);
+
+private:
+    Command matches(const QStringList &commands) const;
+
+    Syntax m_syntax;
+    static QList<Module> 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 <aseigo@kde.org>
+ *
+ *   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 <QObject>
+
+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
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 <aseigo@kde.org>
+ *
+ *   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 <aseigo@kde.org>
+ *
+ *   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 <QObject>
+#include <QSet>
+#include <QTextStream>
+
+#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<QString> 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<QString> 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
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 <aseigo@kde.org>
+ *
+ *   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 <aseigo@kde.org>
+ *
+ *   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 <readline/history.h>
+
+#include <QDir>
+#include <QFile>
+#include <QFinalState>
+#include <QStandardPaths>
+
+#include "replStates.h"
+
+Repl::Repl(QObject *parent)
+    : QStateMachine(parent)
+{
+    // readline history setup
+    using_history();
+    read_history(commandHistoryPath().toLocal8Bit());
+
+    // create all states
+    ReadState *read = new ReadState(this);
+    UnfinishedReadState *unfinishedRead = new UnfinishedReadState(this);
+    EvalState *eval = new EvalState(this);
+    PrintState *print = new PrintState(this);
+    QFinalState *final = 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 = \
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 <aseigo@kde.org>
+ *
+ *   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 <QStateMachine>
+
+class Repl : public QStateMachine
+{
+    Q_OBJECT
+
+public:
+    Repl(QObject *parent = 0);
+    ~Repl();
+
+private:
+    static QString commandHistoryPath();
+};
diff --git a/akonadi2_cli/repl/replStates.cpp b/akonadi2_cli/repl/replStates.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 <aseigo@kde.org>
+ *
+ *   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 <unistd.h>
+#include <iostream>
+
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include <QDebug>
+#include <QEvent>
+#include <QStateMachine>
+
+#include "module.h"
+
+static char *akonadi2_cli_next_tab_complete_match(const char *text, int state);
+static char ** akonadi2_cli_tab_completion(const char *text, int start, int end);
+
+ReadState::ReadState(QState *parent)
+    : QState(parent)
+{
+    rl_completion_entry_function = akonadi2_cli_next_tab_complete_match;
+    rl_attempted_completion_function = akonadi2_cli_tab_completion;
+}
+
+void ReadState::onEntry(QEvent *event)
+{
+    Q_UNUSED(event)
+    char *line = 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 = 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 = \
dynamic_cast<QStateMachine::SignalEvent*>(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 = e->arguments()[0].toString();
+
+    if (!command.isEmpty()) {
+        m_complete = command.right(1) != "\\";
+        if (m_complete) {
+            //emit output("Processing ... " + command);
+            const QStringList commands = command.split(" ");
+            Module::run(commands);
+            emit completed();
+        }
+    }
+}
+
+PrintState::PrintState(QState *parent)
+    : QState(parent)
+{
+}
+
+void PrintState::onEntry(QEvent *event)
+{
+    QStateMachine::SignalEvent *e = \
dynamic_cast<QStateMachine::SignalEvent*>(event); +
+    if (e && !e->arguments().isEmpty()) {
+        const QString command = e->arguments()[0].toString();
+        QTextStream stream(stdout);
+        stream << command << "\n";
+    }
+
+    emit completed();
+}
+
+static QStringList tab_completion_full_state;
+static bool tab_completion_at_root = false;
+
+static char **akonadi2_cli_tab_completion(const char *text, int start, int end)
+{
+    tab_completion_at_root = start == 0;
+    tab_completion_full_state = QString(rl_line_buffer).remove(start, end - \
start).split(" ", QString::SkipEmptyParts); +    return NULL;
+}
+
+static char *akonadi2_cli_next_tab_complete_match(const char *text, int state)
+{
+    QVector<Module::Syntax> nearest = \
Module::nearestSyntax(tab_completion_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 <aseigo@kde.org>
+ *
+ *   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 <QState>
+
+class QSocketNotifier;
+
+class ReadState : public QState
+{
+    Q_OBJECT
+
+public:
+    ReadState(QState *parent = 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 = 0);
+
+protected:
+    const char *prompt() const;
+};
+
+class EvalState : public QState
+{
+    Q_OBJECT
+
+public:
+    EvalState(QState *parent = 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 = 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 <aseigo@kde.org>
+ *
+ *   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 <QDebug>
+#include <QDir>
+
+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 <aseigo@kde.org>
+ *
+ *   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 <QStringList>
+
+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 the
+#                            proper installation path.
+#
+# Variables defined by this module:
+#
+#  READLINE_FOUND            System has readline, include and lib dirs found
+#  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
+   )


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic