Tab completion for commands
Change-Id: Ia38120da7b45cb75effcb2c93eee148419c2fa09
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c03cb47..5656ee8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -175,6 +175,7 @@
target_include_directories(test_sysrepo PRIVATE ${PROJECT_SOURCE_DIR}/tests/mock)
cli_test(utils)
cli_test(path_completion)
+ cli_test(command_completion)
endif()
if(WITH_DOCS)
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index 787fb94..3a59e26 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -7,6 +7,7 @@
*/
#pragma once
+#include <boost/mpl/vector.hpp>
#include "ast_path.hpp"
#include "ast_values.hpp"
@@ -34,46 +35,57 @@
};
struct discard_ : x3::position_tagged {
+ static constexpr auto name = "discard";
bool operator==(const discard_& b) const;
};
struct ls_ : x3::position_tagged {
+ static constexpr auto name = "ls";
bool operator==(const ls_& b) const;
std::vector<LsOption> m_options;
boost::optional<boost::variant<dataPath_, schemaPath_>> m_path;
};
struct cd_ : x3::position_tagged {
+ static constexpr auto name = "cd";
bool operator==(const cd_& b) const;
dataPath_ m_path;
};
struct create_ : x3::position_tagged {
+ static constexpr auto name = "create";
bool operator==(const create_& b) const;
dataPath_ m_path;
};
struct delete_ : x3::position_tagged {
+ static constexpr auto name = "delete";
bool operator==(const delete_& b) const;
dataPath_ m_path;
};
struct set_ : x3::position_tagged {
+ static constexpr auto name = "set";
bool operator==(const set_& b) const;
dataPath_ m_path;
leaf_data_ m_data;
};
struct commit_ : x3::position_tagged {
+ static constexpr auto name = "commit";
bool operator==(const set_& b) const;
};
struct get_ : x3::position_tagged {
+ static constexpr auto name = "get";
bool operator==(const get_& b) const;
boost::optional<boost::variant<dataPath_, schemaPath_>> m_path;
};
-using command_ = boost::variant<discard_, ls_, cd_, create_, delete_, set_, commit_, get_>;
+// TODO: The usage of MPL won't be necessary after std::variant support is added to Spirit
+// https://github.com/boostorg/spirit/issues/270
+using CommandTypes = boost::mpl::vector<discard_, ls_, cd_, create_, delete_, set_, commit_, get_>;
+using command_ = boost::make_variant_over<CommandTypes>::type;
BOOST_FUSION_ADAPT_STRUCT(ls_, m_options, m_path)
BOOST_FUSION_ADAPT_STRUCT(cd_, m_path)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 5a55f36..f0bbf4c 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -8,6 +8,8 @@
#pragma once
+#include <boost/mpl/for_each.hpp>
+#include "ast_commands.hpp"
#include "parser_context.hpp"
#include "schema.hpp"
#include "utils.hpp"
@@ -534,3 +536,25 @@
}
}
};
+
+struct commandNamesVisitor {
+ template <typename T>
+ auto operator()(boost::type<T>)
+ {
+ return T::name;
+ }
+};
+
+struct createCommandSuggestions_class {
+ template <typename T, typename Iterator, typename Context>
+ void on_success(Iterator const& begin, Iterator const&, T&, Context const& context)
+ {
+ auto& parserContext = x3::get<parser_context_tag>(context);
+ parserContext.m_completionIterator = begin;
+
+ parserContext.m_suggestions.clear();
+ boost::mpl::for_each<CommandTypes, boost::type<boost::mpl::_>>([&parserContext](auto cmd) {
+ parserContext.m_suggestions.emplace(commandNamesVisitor()(cmd));
+ });
+ }
+};
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 2bf7be4..e532174 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -57,6 +57,7 @@
x3::rule<createPathSuggestions_class, x3::unused_type> const createPathSuggestions = "createPathSuggestions";
x3::rule<createKeySuggestions_class, x3::unused_type> const createKeySuggestions = "createKeySuggestions";
x3::rule<suggestKeysEnd_class, x3::unused_type> const suggestKeysEnd = "suggestKeysEnd";
+x3::rule<createCommandSuggestions_class, x3::unused_type> const createCommandSuggestions = "createCommandSuggestions";
#if __clang__
#pragma GCC diagnostic push
@@ -236,8 +237,11 @@
auto const discard_def =
lit("discard") >> x3::attr(discard_());
+auto const createCommandSuggestions_def =
+ x3::eps;
+
auto const command_def =
- x3::expect[cd | create | delete_rule | set | commit | get | ls | discard];
+ createCommandSuggestions >> x3::expect[cd | create | delete_rule | set | commit | get | ls | discard];
#if __clang__
#pragma GCC diagnostic pop
@@ -285,3 +289,4 @@
BOOST_SPIRIT_DEFINE(createPathSuggestions)
BOOST_SPIRIT_DEFINE(createKeySuggestions)
BOOST_SPIRIT_DEFINE(suggestKeysEnd)
+BOOST_SPIRIT_DEFINE(createCommandSuggestions)
diff --git a/tests/command_completion.cpp b/tests/command_completion.cpp
new file mode 100644
index 0000000..f3cc868
--- /dev/null
+++ b/tests/command_completion.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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 "trompeloeil_catch.h"
+#include "parser.hpp"
+#include "static_schema.hpp"
+
+TEST_CASE("command completion")
+{
+ auto schema = std::make_shared<StaticSchema>();
+ Parser parser(schema);
+ std::string input;
+ std::ostringstream errorStream;
+ std::set<std::string> expected;
+ SECTION("")
+ {
+ input = "";
+ expected = {"cd", "create", "delete", "set", "commit", "get", "ls", "discard"};
+ }
+
+ SECTION(" ")
+ {
+ input = " ";
+ expected = {"cd", "create", "delete", "set", "commit", "get", "ls", "discard"};
+ }
+
+ SECTION("c")
+ {
+ input = "c";
+ expected = {"d", "ommit", "reate"};
+ }
+
+ SECTION("d")
+ {
+ input = "d";
+ expected = {"elete", "iscard"};
+ }
+
+ SECTION("x")
+ {
+ input = "x";
+ expected = {};
+ }
+
+ SECTION("cd")
+ {
+ input = "cd";
+ // TODO: depending on how Readline works, this will have to be changed to include a space
+ expected = {""};
+ }
+
+ SECTION("create")
+ {
+ input = "create";
+ expected = {""};
+ }
+
+ REQUIRE(parser.completeCommand(input, errorStream) == expected);
+}