Allow configuring CLI backend
Change-Id: I5c0ec73b72c5b4ae96bf3f60f99692cbd1678b03
diff --git a/src/cli.cpp b/src/cli.cpp
new file mode 100644
index 0000000..7384f33
--- /dev/null
+++ b/src/cli.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+#include <docopt.h>
+#include <iostream>
+#include <optional>
+#include <replxx.hxx>
+#include <sstream>
+#include "NETCONF_CLI_VERSION.h"
+#include "interpreter.hpp"
+#if defined(SYSREPO_CLI)
+#include "sysrepo_access.hpp"
+#define PROGRAM_NAME "sysrepo-cli"
+#define PROGRAM_DESCRIPTION R"(CLI interface to sysrepo \
+\
+Usage: \
+ sysrepo-cli \
+ sysrepo-cli (-h | --help) \
+ sysrepo-cli --version \
+)"
+#else
+#error "Unknown CLI backend"
+#endif
+
+
+
+
+const auto HISTORY_FILE_NAME = PROGRAM_NAME "_history";
+
+static const char usage[] = PROGRAM_DESCRIPTION;
+
+
+int main(int argc, char* argv[])
+{
+ auto args = docopt::docopt(usage,
+ {argv + 1, argv + argc},
+ true,
+ PROGRAM_NAME " " NETCONF_CLI_VERSION,
+ true);
+ std::cout << "Welcome to " PROGRAM_NAME << std::endl;
+
+#if defined(SYSREPO_CLI)
+ SysrepoAccess datastore(PROGRAM_NAME);
+#else
+#error "Unknown CLI backend"
+#endif
+
+ auto dataQuery = std::make_shared<DataQuery>(datastore);
+ Parser parser(datastore.schema(), dataQuery);
+
+ using replxx::Replxx;
+
+ Replxx lineEditor;
+
+ lineEditor.bind_key(Replxx::KEY::meta(Replxx::KEY::BACKSPACE), std::bind(&Replxx::invoke, &lineEditor, Replxx::ACTION::KILL_TO_BEGINING_OF_WORD, std::placeholders::_1));
+ lineEditor.bind_key(Replxx::KEY::control('W'), std::bind(&Replxx::invoke, &lineEditor, Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT, std::placeholders::_1));
+
+ lineEditor.set_word_break_characters("\t _[]/:'\"=-%");
+
+ lineEditor.set_completion_callback([&parser](const std::string& input, int& context) {
+ std::stringstream stream;
+ auto completions = parser.completeCommand(input, stream);
+
+ std::vector<replxx::Replxx::Completion> res;
+ std::copy(completions.m_completions.begin(), completions.m_completions.end(), std::back_inserter(res));
+ context = completions.m_contextLength;
+ return res;
+ });
+
+ std::optional<std::string> historyFile;
+ if (auto xdgHome = getenv("XDG_DATA_HOME")) {
+ historyFile = std::string(xdgHome) + "/" + HISTORY_FILE_NAME;
+ } else if (auto home = getenv("HOME")) {
+ historyFile = std::string(home) + "/.local/share/" + HISTORY_FILE_NAME;
+ }
+
+ if (historyFile)
+ lineEditor.history_load(historyFile.value());
+
+ while (true) {
+ auto line = lineEditor.input(parser.currentNode() + "> ");
+ if (!line) {
+ // If user pressed CTRL-C to abort the line, errno gets set to EAGAIN.
+ // If user pressed CTRL-D (for EOF), errno doesn't get set to EAGAIN, so we exit the program.
+ // I have no idea why replxx uses errno for this.
+ if (errno == EAGAIN) {
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ std::locale C_locale("C");
+ std::string_view view{line};
+ if (std::all_of(view.begin(), view.end(),
+ [C_locale](const auto c) { return std::isspace(c, C_locale);})) {
+ continue;
+ }
+
+ try {
+ command_ cmd = parser.parseCommand(line, std::cout);
+ boost::apply_visitor(Interpreter(parser, datastore), cmd);
+ } catch (InvalidCommandException& ex) {
+ std::cerr << ex.what() << std::endl;
+ } catch (DatastoreException& ex) {
+ std::cerr << ex.what() << std::endl;
+ }
+
+ lineEditor.history_add(line);
+ }
+
+ if (historyFile)
+ lineEditor.history_save(historyFile.value());
+
+ return 0;
+}