diff options
author | Aaron Seigo <aseigo@kde.org> | 2015-12-23 10:47:32 +0100 |
---|---|---|
committer | Aaron Seigo <aseigo@kde.org> | 2015-12-23 10:47:32 +0100 |
commit | 255d73af197faf8437343abc10bd98cca2057a1e (patch) | |
tree | f16bdbdda08a450235230781eec558303c6442a1 | |
parent | b259728a4f63e022526ef86e6b5d6c62d9938d13 (diff) | |
download | sink-255d73af197faf8437343abc10bd98cca2057a1e.tar.gz sink-255d73af197faf8437343abc10bd98cca2057a1e.zip |
initial import of the new cli app
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | akonadi2_cli/CMakeLists.txt | 16 | ||||
-rw-r--r-- | akonadi2_cli/main.cpp | 71 | ||||
-rw-r--r-- | akonadi2_cli/module.cpp | 195 | ||||
-rw-r--r-- | akonadi2_cli/module.h | 61 | ||||
-rw-r--r-- | akonadi2_cli/modules/exit/exit.cpp | 39 | ||||
-rw-r--r-- | akonadi2_cli/modules/exit/exit.h | 37 | ||||
-rw-r--r-- | akonadi2_cli/modules/help/help.cpp | 80 | ||||
-rw-r--r-- | akonadi2_cli/modules/help/help.h | 37 | ||||
-rw-r--r-- | akonadi2_cli/repl/repl.cpp | 78 | ||||
-rw-r--r-- | akonadi2_cli/repl/repl.h | 34 | ||||
-rw-r--r-- | akonadi2_cli/repl/replStates.cpp | 155 | ||||
-rw-r--r-- | akonadi2_cli/repl/replStates.h | 85 | ||||
-rw-r--r-- | akonadi2_cli/state.cpp | 28 | ||||
-rw-r--r-- | akonadi2_cli/state.h | 29 | ||||
-rw-r--r-- | cmake/modules/FindReadline.cmake | 47 |
16 files changed, 995 insertions, 0 deletions
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) | |||
57 | # some tests | 57 | # some tests |
58 | add_subdirectory(tests) | 58 | add_subdirectory(tests) |
59 | 59 | ||
60 | # cli | ||
61 | add_subdirectory(akonadi2_cli) | ||
62 | |||
60 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) | 63 | 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 @@ | |||
1 | project(akonadi2_cli) | ||
2 | |||
3 | find_package(Readline REQUIRED) | ||
4 | |||
5 | |||
6 | set(akonadi2_SRCS | ||
7 | main.cpp | ||
8 | module.cpp | ||
9 | modules/exit/exit.cpp | ||
10 | modules/help/help.cpp | ||
11 | repl/repl.cpp | ||
12 | repl/replStates.cpp | ||
13 | state.cpp) | ||
14 | |||
15 | add_executable(${PROJECT_NAME} ${akonadi2_SRCS}) | ||
16 | |||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include <unistd.h> | ||
21 | |||
22 | #include <QCoreApplication> | ||
23 | #include <QDebug> | ||
24 | |||
25 | #include "module.h" | ||
26 | // #include "jsonlistener.h" | ||
27 | #include "repl/repl.h" | ||
28 | |||
29 | /* | ||
30 | * modes of operation: | ||
31 | * | ||
32 | * 1. called with no commands: start the REPL and listen for JSON on stin | ||
33 | * 2. called with -: listen for JSON on stdin | ||
34 | * 3. called with commands: try to match to syntx | ||
35 | */ | ||
36 | |||
37 | int main(int argc, char *argv[]) | ||
38 | { | ||
39 | // load all modules | ||
40 | Module::loadModules(); | ||
41 | |||
42 | const bool interactive = isatty(fileno(stdin)); | ||
43 | const bool startRepl = (argc == 1) && interactive; | ||
44 | //TODO: make a json command parse cause that would be awesomesauce | ||
45 | const bool startJsonListener = !startRepl && | ||
46 | (argc == 2 && qstrcmp(argv[1], "-") == 0); | ||
47 | qDebug() << "state at startup is" << interactive << startRepl << startJsonListener; | ||
48 | |||
49 | QCoreApplication app(argc, argv); | ||
50 | app.setApplicationName("funq"); | ||
51 | |||
52 | if (startRepl || startJsonListener) { | ||
53 | if (startRepl) { | ||
54 | Repl *repl = new Repl; | ||
55 | QObject::connect(repl, &QStateMachine::finished, | ||
56 | repl, &QObject::deleteLater); | ||
57 | QObject::connect(repl, &QStateMachine::finished, | ||
58 | &app, &QCoreApplication::quit); | ||
59 | } | ||
60 | |||
61 | if (startJsonListener) { | ||
62 | // JsonListener listener(syntax); | ||
63 | } | ||
64 | |||
65 | return app.exec(); | ||
66 | } | ||
67 | |||
68 | QStringList commands = app.arguments(); | ||
69 | commands.removeFirst(); | ||
70 | return Module::run(commands); | ||
71 | } | ||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "module.h" | ||
21 | |||
22 | #include <QCoreApplication> | ||
23 | #include <QDebug> | ||
24 | |||
25 | // TODO: needs a proper registry; making "core" modules plugins is | ||
26 | // almost certainly overkill, but this is not the way either | ||
27 | #include "modules/exit/exit.h" | ||
28 | #include "modules/help/help.h" | ||
29 | |||
30 | QList<Module> Module::s_modules; | ||
31 | State Module::s_state; | ||
32 | |||
33 | Module::Syntax::Syntax() | ||
34 | { | ||
35 | } | ||
36 | |||
37 | Module::Syntax::Syntax(const QString &k, std::function<bool(const QStringList &, State &)> l, const QString &helpText, bool e) | ||
38 | : keyword(k), | ||
39 | lambda(l), | ||
40 | help(helpText), | ||
41 | eventDriven(e) | ||
42 | { | ||
43 | } | ||
44 | |||
45 | Module::Module() | ||
46 | { | ||
47 | } | ||
48 | |||
49 | void Module::loadModules() | ||
50 | { | ||
51 | addModule(CLI::Exit()); | ||
52 | addModule(CLI::Help()); | ||
53 | } | ||
54 | |||
55 | void Module::addModule(const Module &module) | ||
56 | { | ||
57 | s_modules.append(module); | ||
58 | } | ||
59 | |||
60 | QList<Module> Module::modules() | ||
61 | { | ||
62 | return s_modules; | ||
63 | } | ||
64 | |||
65 | Module::Command Module::match(const QStringList &commands) | ||
66 | { | ||
67 | Command command; | ||
68 | for (const Module &module: s_modules) { | ||
69 | command = module.matches(commands); | ||
70 | if (command.first) { | ||
71 | break; | ||
72 | } | ||
73 | } | ||
74 | |||
75 | return command; | ||
76 | } | ||
77 | |||
78 | Module::Syntax Module::syntax() const | ||
79 | { | ||
80 | return m_syntax; | ||
81 | } | ||
82 | |||
83 | void Module::setSyntax(const Syntax &syntax) | ||
84 | { | ||
85 | m_syntax = syntax; | ||
86 | } | ||
87 | |||
88 | bool Module::run(const QStringList &commands) | ||
89 | { | ||
90 | Command command = match(commands); | ||
91 | if (command.first && command.first->lambda) { | ||
92 | bool rv = command.first->lambda(command.second, s_state); | ||
93 | if (rv && command.first->eventDriven) { | ||
94 | return QCoreApplication::instance()->exec(); | ||
95 | } | ||
96 | |||
97 | return rv; | ||
98 | } | ||
99 | |||
100 | return false; | ||
101 | } | ||
102 | |||
103 | Module::Command Module::matches(const QStringList &commandLine) const | ||
104 | { | ||
105 | if (commandLine.isEmpty()) { | ||
106 | return Command(); | ||
107 | } | ||
108 | |||
109 | QStringListIterator commandLineIt(commandLine); | ||
110 | |||
111 | if (commandLineIt.next() != m_syntax.keyword) { | ||
112 | return Command(); | ||
113 | } | ||
114 | |||
115 | QListIterator<Syntax> syntaxIt(m_syntax.children); | ||
116 | const Syntax *syntax = &m_syntax; | ||
117 | QStringList tailCommands; | ||
118 | while (commandLineIt.hasNext() && syntaxIt.hasNext()) { | ||
119 | const QString word = commandLineIt.next(); | ||
120 | while (syntaxIt.hasNext()) { | ||
121 | const Syntax &child = syntaxIt.next(); | ||
122 | if (word == child.keyword) { | ||
123 | syntax = &child; | ||
124 | syntaxIt = child.children; | ||
125 | } | ||
126 | } | ||
127 | |||
128 | if (!syntaxIt.hasNext()) { | ||
129 | tailCommands << word; | ||
130 | break; | ||
131 | } | ||
132 | } | ||
133 | |||
134 | if (syntax && syntax->lambda) { | ||
135 | while (commandLineIt.hasNext()) { | ||
136 | tailCommands << commandLineIt.next(); | ||
137 | } | ||
138 | |||
139 | return std::make_pair(syntax, tailCommands); | ||
140 | } | ||
141 | |||
142 | return Command(); | ||
143 | } | ||
144 | |||
145 | QVector<Module::Syntax> Module::nearestSyntax(const QStringList &words, const QString &fragment) | ||
146 | { | ||
147 | QVector<Module::Syntax> matches; | ||
148 | |||
149 | //qDebug() << "words are" << words; | ||
150 | if (words.isEmpty()) { | ||
151 | for (const Module &module: s_modules) { | ||
152 | if (module.syntax().keyword.startsWith(fragment)) { | ||
153 | matches.push_back(module.syntax()); | ||
154 | } | ||
155 | } | ||
156 | } else { | ||
157 | QStringListIterator wordIt(words); | ||
158 | QString word = wordIt.next(); | ||
159 | Syntax lastFullSyntax; | ||
160 | |||
161 | for (const Module &module: s_modules) { | ||
162 | if (module.syntax().keyword == word) { | ||
163 | lastFullSyntax = module.syntax(); | ||
164 | QListIterator<Syntax> syntaxIt(module.syntax().children); | ||
165 | while (wordIt.hasNext()) { | ||
166 | word = wordIt.next(); | ||
167 | while (syntaxIt.hasNext()) { | ||
168 | const Syntax &child = syntaxIt.next(); | ||
169 | if (word == child.keyword) { | ||
170 | lastFullSyntax = child; | ||
171 | syntaxIt = child.children; | ||
172 | } | ||
173 | } | ||
174 | } | ||
175 | |||
176 | break; | ||
177 | } | ||
178 | } | ||
179 | |||
180 | //qDebug() << "exiting with" << lastFullSyntax.keyword << words.last(); | ||
181 | if (lastFullSyntax.keyword == words.last()) { | ||
182 | QListIterator<Syntax> syntaxIt(lastFullSyntax.children); | ||
183 | while (syntaxIt.hasNext()) { | ||
184 | Syntax syntax = syntaxIt.next(); | ||
185 | if (fragment.isEmpty() || syntax.keyword.startsWith(fragment)) { | ||
186 | matches.push_back(syntax); | ||
187 | } | ||
188 | } | ||
189 | } | ||
190 | } | ||
191 | |||
192 | return matches; | ||
193 | } | ||
194 | |||
195 | |||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include "state.h" | ||
23 | |||
24 | #include <QStringList> | ||
25 | #include <QVector> | ||
26 | |||
27 | class Module | ||
28 | { | ||
29 | public: | ||
30 | struct Syntax | ||
31 | { | ||
32 | Syntax(); | ||
33 | Syntax(const QString &keyword, std::function<bool(const QStringList &, State &)> lambda = std::function<bool(const QStringList &, State &)>(), const QString &helpText = QString(), bool eventDriven = false); | ||
34 | QString keyword; | ||
35 | std::function<bool(const QStringList &, State &)> lambda; | ||
36 | QList<Syntax> children; | ||
37 | QString help; | ||
38 | bool eventDriven; | ||
39 | }; | ||
40 | |||
41 | typedef std::pair<const Syntax *, QStringList> Command; | ||
42 | |||
43 | static void addModule(const Module &module); | ||
44 | static QList<Module> modules(); | ||
45 | static Command match(const QStringList &commands); | ||
46 | static bool run(const QStringList &commands); | ||
47 | static void loadModules(); | ||
48 | static QVector<Syntax>nearestSyntax(const QStringList &words, const QString &fragment); | ||
49 | |||
50 | Module(); | ||
51 | Module::Syntax syntax() const; | ||
52 | void setSyntax(const Syntax &syntax); | ||
53 | |||
54 | private: | ||
55 | Command matches(const QStringList &commands) const; | ||
56 | |||
57 | Syntax m_syntax; | ||
58 | static QList<Module> s_modules; | ||
59 | static State s_state; | ||
60 | }; | ||
61 | |||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "exit.h" | ||
21 | |||
22 | #include <QObject> | ||
23 | |||
24 | namespace CLI | ||
25 | { | ||
26 | |||
27 | Exit::Exit() | ||
28 | { | ||
29 | setSyntax(Syntax("exit", &Exit::exit, QObject::tr("Exits the application. Ctrl-d also works!"))); | ||
30 | } | ||
31 | |||
32 | bool Exit::exit(const QStringList &, State &) | ||
33 | { | ||
34 | ::exit(0); | ||
35 | return true; | ||
36 | } | ||
37 | |||
38 | } // namespace CLI | ||
39 | |||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include "module.h" | ||
23 | |||
24 | namespace CLI | ||
25 | { | ||
26 | |||
27 | class Exit : public Module | ||
28 | { | ||
29 | public: | ||
30 | Exit(); | ||
31 | |||
32 | private: | ||
33 | static bool exit(const QStringList &commands, State &state); | ||
34 | }; | ||
35 | |||
36 | } // namespace CLI | ||
37 | |||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "help.h" | ||
21 | |||
22 | #include <QObject> | ||
23 | #include <QSet> | ||
24 | #include <QTextStream> | ||
25 | |||
26 | #include "module.h" | ||
27 | |||
28 | namespace CLI | ||
29 | { | ||
30 | |||
31 | Help::Help() | ||
32 | { | ||
33 | Syntax topLevel = Syntax(QObject::tr("help"), &Help::showHelp, QObject::tr("Print command information: help [command]")); | ||
34 | setSyntax(topLevel); | ||
35 | } | ||
36 | |||
37 | bool Help::showHelp(const QStringList &commands, State &) | ||
38 | { | ||
39 | Module::Command command = Module::match(commands); | ||
40 | QTextStream stream(stdout); | ||
41 | if (commands.isEmpty()) { | ||
42 | stream << QObject::tr("Welcome to the Akonadi2 command line tool!") << "\n"; | ||
43 | stream << QObject::tr("Top-level commands:") << "\n"; | ||
44 | QSet<QString> sorted; | ||
45 | for (auto module: Module::modules()) { | ||
46 | sorted.insert(module.syntax().keyword); | ||
47 | } | ||
48 | |||
49 | for (auto keyword: sorted) { | ||
50 | stream << "\t" << keyword << "\n"; | ||
51 | } | ||
52 | } else if (const Syntax *syntax = command.first) { | ||
53 | //TODO: get parent! | ||
54 | stream << QObject::tr("Command `%1`").arg(syntax->keyword); | ||
55 | |||
56 | if (!syntax->help.isEmpty()) { | ||
57 | stream << ": " << syntax->help; | ||
58 | } | ||
59 | stream << "\n"; | ||
60 | |||
61 | if (!syntax->children.isEmpty()) { | ||
62 | stream << "\tSub-commands:\n"; | ||
63 | QSet<QString> sorted; | ||
64 | for (auto childSyntax: syntax->children) { | ||
65 | sorted.insert(childSyntax.keyword); | ||
66 | } | ||
67 | |||
68 | for (auto keyword: sorted) { | ||
69 | stream << "\t" << keyword << "\n"; | ||
70 | } | ||
71 | } | ||
72 | } else { | ||
73 | stream << "Unknown command: " << commands.join(" ") << "\n"; | ||
74 | } | ||
75 | |||
76 | return true; | ||
77 | } | ||
78 | |||
79 | } // namespace CLI | ||
80 | |||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include "module.h" | ||
23 | |||
24 | namespace CLI | ||
25 | { | ||
26 | |||
27 | class Help : public Module | ||
28 | { | ||
29 | public: | ||
30 | Help(); | ||
31 | |||
32 | private: | ||
33 | static bool showHelp(const QStringList &commands, State &state); | ||
34 | }; | ||
35 | |||
36 | } // namespace CLI | ||
37 | |||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "repl.h" | ||
21 | |||
22 | #include <readline/history.h> | ||
23 | |||
24 | #include <QDir> | ||
25 | #include <QFile> | ||
26 | #include <QFinalState> | ||
27 | #include <QStandardPaths> | ||
28 | |||
29 | #include "replStates.h" | ||
30 | |||
31 | Repl::Repl(QObject *parent) | ||
32 | : QStateMachine(parent) | ||
33 | { | ||
34 | // readline history setup | ||
35 | using_history(); | ||
36 | read_history(commandHistoryPath().toLocal8Bit()); | ||
37 | |||
38 | // create all states | ||
39 | ReadState *read = new ReadState(this); | ||
40 | UnfinishedReadState *unfinishedRead = new UnfinishedReadState(this); | ||
41 | EvalState *eval = new EvalState(this); | ||
42 | PrintState *print = new PrintState(this); | ||
43 | QFinalState *final = new QFinalState(this); | ||
44 | |||
45 | // connect the transitions | ||
46 | read->addTransition(read, SIGNAL(command(QString)), eval); | ||
47 | read->addTransition(read, SIGNAL(exitRequested()), final); | ||
48 | |||
49 | unfinishedRead->addTransition(unfinishedRead, SIGNAL(command(QString)), eval); | ||
50 | unfinishedRead->addTransition(unfinishedRead, SIGNAL(exitRequested()), final); | ||
51 | |||
52 | eval->addTransition(eval, SIGNAL(completed()), read); | ||
53 | eval->addTransition(eval, SIGNAL(continueInput()), unfinishedRead); | ||
54 | eval->addTransition(eval, SIGNAL(output(QString)), print); | ||
55 | |||
56 | print->addTransition(print, SIGNAL(completed()), eval); | ||
57 | |||
58 | setInitialState(read); | ||
59 | start(); | ||
60 | } | ||
61 | |||
62 | Repl::~Repl() | ||
63 | { | ||
64 | // readline history writing | ||
65 | write_history(commandHistoryPath().toLocal8Bit()); | ||
66 | } | ||
67 | |||
68 | QString Repl::commandHistoryPath() | ||
69 | { | ||
70 | const QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); | ||
71 | if (!QFile::exists(path)) { | ||
72 | QDir dir; | ||
73 | dir.mkpath(path); | ||
74 | } | ||
75 | return path + "/repl_history"; | ||
76 | } | ||
77 | |||
78 | #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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QStateMachine> | ||
23 | |||
24 | class Repl : public QStateMachine | ||
25 | { | ||
26 | Q_OBJECT | ||
27 | |||
28 | public: | ||
29 | Repl(QObject *parent = 0); | ||
30 | ~Repl(); | ||
31 | |||
32 | private: | ||
33 | static QString commandHistoryPath(); | ||
34 | }; | ||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "replStates.h" | ||
21 | |||
22 | #include <unistd.h> | ||
23 | #include <iostream> | ||
24 | |||
25 | #include <readline/readline.h> | ||
26 | #include <readline/history.h> | ||
27 | |||
28 | #include <QDebug> | ||
29 | #include <QEvent> | ||
30 | #include <QStateMachine> | ||
31 | |||
32 | #include "module.h" | ||
33 | |||
34 | static char *akonadi2_cli_next_tab_complete_match(const char *text, int state); | ||
35 | static char ** akonadi2_cli_tab_completion(const char *text, int start, int end); | ||
36 | |||
37 | ReadState::ReadState(QState *parent) | ||
38 | : QState(parent) | ||
39 | { | ||
40 | rl_completion_entry_function = akonadi2_cli_next_tab_complete_match; | ||
41 | rl_attempted_completion_function = akonadi2_cli_tab_completion; | ||
42 | } | ||
43 | |||
44 | void ReadState::onEntry(QEvent *event) | ||
45 | { | ||
46 | Q_UNUSED(event) | ||
47 | char *line = readline(prompt()); | ||
48 | |||
49 | if (!line) { | ||
50 | std::cout << std::endl; | ||
51 | emit exitRequested(); | ||
52 | return; | ||
53 | } | ||
54 | |||
55 | // we have actual data, so let's wait for a full line of text | ||
56 | QByteArray input(line); | ||
57 | const QString text = QString(line).simplified(); | ||
58 | //qDebug() << "Line is ... " << text; | ||
59 | |||
60 | if (text.length() > 0) { | ||
61 | add_history(line); | ||
62 | } | ||
63 | |||
64 | free(line); | ||
65 | emit command(text); | ||
66 | } | ||
67 | |||
68 | const char *ReadState::prompt() const | ||
69 | { | ||
70 | return "> "; | ||
71 | } | ||
72 | |||
73 | UnfinishedReadState::UnfinishedReadState(QState *parent) | ||
74 | : ReadState(parent) | ||
75 | { | ||
76 | } | ||
77 | |||
78 | const char *UnfinishedReadState::prompt() const | ||
79 | { | ||
80 | return " "; | ||
81 | } | ||
82 | |||
83 | EvalState::EvalState(QState *parent) | ||
84 | : QState(parent), | ||
85 | m_complete(false) | ||
86 | { | ||
87 | } | ||
88 | |||
89 | void EvalState::onEntry(QEvent *event) | ||
90 | { | ||
91 | QStateMachine::SignalEvent *e = dynamic_cast<QStateMachine::SignalEvent*>(event); | ||
92 | |||
93 | if (!e || e->arguments().isEmpty()) { | ||
94 | if (m_complete) { | ||
95 | emit completed(); | ||
96 | } else { | ||
97 | emit continueInput(); | ||
98 | } | ||
99 | return; | ||
100 | } | ||
101 | |||
102 | //TODO: properly tokenize (e.g. "foo bar" should not become ['"foo', 'bar"'] | ||
103 | const QString command = e->arguments()[0].toString(); | ||
104 | |||
105 | if (!command.isEmpty()) { | ||
106 | m_complete = command.right(1) != "\\"; | ||
107 | if (m_complete) { | ||
108 | //emit output("Processing ... " + command); | ||
109 | const QStringList commands = command.split(" "); | ||
110 | Module::run(commands); | ||
111 | emit completed(); | ||
112 | } | ||
113 | } | ||
114 | } | ||
115 | |||
116 | PrintState::PrintState(QState *parent) | ||
117 | : QState(parent) | ||
118 | { | ||
119 | } | ||
120 | |||
121 | void PrintState::onEntry(QEvent *event) | ||
122 | { | ||
123 | QStateMachine::SignalEvent *e = dynamic_cast<QStateMachine::SignalEvent*>(event); | ||
124 | |||
125 | if (e && !e->arguments().isEmpty()) { | ||
126 | const QString command = e->arguments()[0].toString(); | ||
127 | QTextStream stream(stdout); | ||
128 | stream << command << "\n"; | ||
129 | } | ||
130 | |||
131 | emit completed(); | ||
132 | } | ||
133 | |||
134 | static QStringList tab_completion_full_state; | ||
135 | static bool tab_completion_at_root = false; | ||
136 | |||
137 | static char **akonadi2_cli_tab_completion(const char *text, int start, int end) | ||
138 | { | ||
139 | tab_completion_at_root = start == 0; | ||
140 | tab_completion_full_state = QString(rl_line_buffer).remove(start, end - start).split(" ", QString::SkipEmptyParts); | ||
141 | return NULL; | ||
142 | } | ||
143 | |||
144 | static char *akonadi2_cli_next_tab_complete_match(const char *text, int state) | ||
145 | { | ||
146 | QVector<Module::Syntax> nearest = Module::nearestSyntax(tab_completion_full_state, QString(text)); | ||
147 | |||
148 | if (nearest.size() > state) { | ||
149 | return qstrdup(nearest[state].keyword.toUtf8()); | ||
150 | } | ||
151 | |||
152 | return rl_filename_completion_function(text, state); | ||
153 | } | ||
154 | |||
155 | #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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QState> | ||
23 | |||
24 | class QSocketNotifier; | ||
25 | |||
26 | class ReadState : public QState | ||
27 | { | ||
28 | Q_OBJECT | ||
29 | |||
30 | public: | ||
31 | ReadState(QState *parent = 0); | ||
32 | |||
33 | Q_SIGNALS: | ||
34 | void command(const QString &command); | ||
35 | void exitRequested(); | ||
36 | |||
37 | protected: | ||
38 | void onEntry(QEvent *event); | ||
39 | virtual const char *prompt() const; | ||
40 | }; | ||
41 | |||
42 | class UnfinishedReadState : public ReadState | ||
43 | { | ||
44 | Q_OBJECT | ||
45 | |||
46 | public: | ||
47 | UnfinishedReadState(QState *parent = 0); | ||
48 | |||
49 | protected: | ||
50 | const char *prompt() const; | ||
51 | }; | ||
52 | |||
53 | class EvalState : public QState | ||
54 | { | ||
55 | Q_OBJECT | ||
56 | |||
57 | public: | ||
58 | EvalState(QState *parent = 0); | ||
59 | |||
60 | Q_SIGNALS: | ||
61 | void completed(); | ||
62 | void continueInput(); | ||
63 | void output(const QString &output); | ||
64 | |||
65 | protected: | ||
66 | void onEntry(QEvent *event); | ||
67 | |||
68 | private: | ||
69 | bool m_complete; | ||
70 | }; | ||
71 | |||
72 | class PrintState : public QState | ||
73 | { | ||
74 | Q_OBJECT | ||
75 | |||
76 | public: | ||
77 | PrintState(QState *parent = 0); | ||
78 | |||
79 | Q_SIGNALS: | ||
80 | void completed(); | ||
81 | |||
82 | protected: | ||
83 | void onEntry(QEvent *event); | ||
84 | }; | ||
85 | |||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #include "state.h" | ||
21 | |||
22 | #include <QDebug> | ||
23 | #include <QDir> | ||
24 | |||
25 | State::State() | ||
26 | { | ||
27 | } | ||
28 | |||
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 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Aaron Seigo <aseigo@kde.org> | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License as published by | ||
6 | * the Free Software Foundation; either version 2 of the License, or | ||
7 | * (at your option) any later version. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, | ||
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | * GNU General Public License for more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License | ||
15 | * along with this program; if not, write to the | ||
16 | * Free Software Foundation, Inc., | ||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
18 | */ | ||
19 | |||
20 | #pragma once | ||
21 | |||
22 | #include <QStringList> | ||
23 | |||
24 | class State | ||
25 | { | ||
26 | public: | ||
27 | State(); | ||
28 | }; | ||
29 | |||
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 @@ | |||
1 | # - Try to find readline include dirs and libraries | ||
2 | # | ||
3 | # Usage of this module as follows: | ||
4 | # | ||
5 | # find_package(Readline) | ||
6 | # | ||
7 | # Variables used by this module, they can change the default behaviour and need | ||
8 | # to be set before calling find_package: | ||
9 | # | ||
10 | # Readline_ROOT_DIR Set this variable to the root installation of | ||
11 | # readline if the module has problems finding the | ||
12 | # proper installation path. | ||
13 | # | ||
14 | # Variables defined by this module: | ||
15 | # | ||
16 | # READLINE_FOUND System has readline, include and lib dirs found | ||
17 | # Readline_INCLUDE_DIR The readline include directories. | ||
18 | # Readline_LIBRARY The readline library. | ||
19 | |||
20 | find_path(Readline_ROOT_DIR | ||
21 | NAMES include/readline/readline.h | ||
22 | ) | ||
23 | |||
24 | find_path(Readline_INCLUDE_DIR | ||
25 | NAMES readline/readline.h | ||
26 | HINTS ${Readline_ROOT_DIR}/include | ||
27 | ) | ||
28 | |||
29 | find_library(Readline_LIBRARY | ||
30 | NAMES readline | ||
31 | HINTS ${Readline_ROOT_DIR}/lib | ||
32 | ) | ||
33 | |||
34 | if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) | ||
35 | set(READLINE_FOUND TRUE) | ||
36 | else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) | ||
37 | FIND_LIBRARY(Readline_LIBRARY NAMES readline) | ||
38 | include(FindPackageHandleStandardArgs) | ||
39 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY ) | ||
40 | MARK_AS_ADVANCED(Readline_INCLUDE_DIR Readline_LIBRARY) | ||
41 | endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY) | ||
42 | |||
43 | mark_as_advanced( | ||
44 | Readline_ROOT_DIR | ||
45 | Readline_INCLUDE_DIR | ||
46 | Readline_LIBRARY | ||
47 | ) | ||