Add ls

Change-Id: I86f799ff9577940c7df8e01cda6c6b7867de8e7e
diff --git a/CMakeLists.txt b/CMakeLists.txt
index cae063a..df49648 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -114,6 +114,7 @@
         target_link_libraries(test_${fname} TestCatchIntegration)
     endmacro()
     cli_test(cd)
+    cli_test(ls)
     cli_test(presence_containers)
     cli_test(leaf_editing)
 
diff --git a/src/ast_commands.cpp b/src/ast_commands.cpp
index 9ef9044..89e2f74 100644
--- a/src/ast_commands.cpp
+++ b/src/ast_commands.cpp
@@ -24,6 +24,11 @@
     return this->m_path == b.m_path;
 }
 
+bool ls_::operator==(const ls_& b) const
+{
+    return this->m_path == b.m_path;
+}
+
 bool enum_::operator==(const enum_& b) const
 {
     return this->m_value == b.m_value;
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index 90af8ea..d7d47c9 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -28,6 +28,11 @@
 
 using keyValue_ = std::pair<std::string, std::string>;
 
+struct ls_ : x3::position_tagged {
+    bool operator==(const ls_& b) const;
+    boost::optional<path_> m_path;
+};
+
 struct cd_ : x3::position_tagged {
     bool operator==(const cd_& b) const;
     path_ m_path;
@@ -63,8 +68,9 @@
     leaf_data_ m_data;
 };
 
-using command_ = boost::variant<cd_, create_, delete_, set_>;
+using command_ = boost::variant<ls_, cd_, create_, delete_, set_>;
 
+BOOST_FUSION_ADAPT_STRUCT(ls_, m_path)
 BOOST_FUSION_ADAPT_STRUCT(cd_, m_path)
 BOOST_FUSION_ADAPT_STRUCT(create_, m_path)
 BOOST_FUSION_ADAPT_STRUCT(delete_, m_path)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index b58613c..2953dad 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -204,6 +204,8 @@
 };
 
 
+struct ls_class;
+
 struct cd_class {
     template <typename Iterator, typename Exception, typename Context>
     x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const& x, Context const& context)
diff --git a/src/grammars.hpp b/src/grammars.hpp
index ed21e31..06e1bf8 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -34,6 +34,7 @@
 x3::rule<leaf_data_uint_class, uint32_t> const leaf_data_uint = "leaf_data_uint";
 x3::rule<leaf_data_string_class, std::string> const leaf_data_string = "leaf_data_string";
 
+x3::rule<ls_class, ls_> const ls = "ls";
 x3::rule<cd_class, cd_> const cd = "cd";
 x3::rule<set_class, set_> const set = "set";
 x3::rule<create_class, create_> const create = "create";
@@ -125,6 +126,9 @@
 auto const space_separator =
         x3::omit[x3::no_skip[space]];
 
+auto const ls_def =
+        lit("ls") >> -path;
+
 auto const cd_def =
         lit("cd") >> space_separator > path;
 
@@ -138,7 +142,7 @@
         lit("set") >> space_separator > leafPath > leaf_data;
 
 auto const command_def =
-        x3::expect[cd | create | delete_rule | set] >> x3::eoi;
+        x3::expect[cd | create | delete_rule | set | ls] >> x3::eoi;
 
 #if __clang__
 #pragma GCC diagnostic pop
@@ -165,6 +169,7 @@
 BOOST_SPIRIT_DEFINE(leaf_data_uint)
 BOOST_SPIRIT_DEFINE(leaf_data_string)
 BOOST_SPIRIT_DEFINE(set)
+BOOST_SPIRIT_DEFINE(ls)
 BOOST_SPIRIT_DEFINE(cd)
 BOOST_SPIRIT_DEFINE(create)
 BOOST_SPIRIT_DEFINE(delete_rule)
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index 19a163b..f71c12f 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -45,6 +45,14 @@
     std::cout << "Presence container " << cont.m_name << " deleted." << std::endl;
 }
 
+void Interpreter::operator()(const ls_& ls) const
+{
+    std::cout << "Possible nodes:" << std::endl;
+
+    for (const auto& it : m_parser.availableNodes(ls.m_path))
+        std::cout << it << std::endl;
+}
+
 Interpreter::Interpreter(Parser& parser, Schema&)
     : m_parser(parser)
 {
diff --git a/src/interpreter.hpp b/src/interpreter.hpp
index 64cc0e9..8acf8bd 100644
--- a/src/interpreter.hpp
+++ b/src/interpreter.hpp
@@ -18,6 +18,7 @@
     void operator()(const cd_&) const;
     void operator()(const create_&) const;
     void operator()(const delete_&) const;
+    void operator()(const ls_&) const;
 
 private:
     Parser& m_parser;
diff --git a/src/parser.cpp b/src/parser.cpp
index ac3ba84..0eb94fd 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -53,3 +53,11 @@
 {
     return pathToDataString(m_curDir);
 }
+
+std::set<std::string> Parser::availableNodes(const boost::optional<path_>& path) const
+{
+    auto pathArg = m_curDir;
+    if (path)
+        pathArg.m_nodes.insert(pathArg.m_nodes.end(), path->m_nodes.begin(), path->m_nodes.end());
+    return m_schema->childNodes(pathArg);
+}
diff --git a/src/parser.hpp b/src/parser.hpp
index 05e636e..0f1fd79 100644
--- a/src/parser.hpp
+++ b/src/parser.hpp
@@ -30,6 +30,7 @@
     command_ parseCommand(const std::string& line, std::ostream& errorStream);
     void changeNode(const path_& name);
     std::string currentNode() const;
+    std::set<std::string> availableNodes(const boost::optional<path_>& path) const;
 
 private:
     const std::shared_ptr<const Schema> m_schema;
diff --git a/tests/ls.cpp b/tests/ls.cpp
new file mode 100644
index 0000000..c2cc5fe
--- /dev/null
+++ b/tests/ls.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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 "ast_commands.hpp"
+#include "parser.hpp"
+#include "static_schema.hpp"
+
+TEST_CASE("ls")
+{
+    auto schema = std::make_shared<StaticSchema>();
+    schema->addModule("example");
+    schema->addModule("second");
+    schema->addContainer("", "example:a");
+    schema->addContainer("", "second:a");
+    schema->addContainer("", "example:b");
+    schema->addContainer("example:a", "example:a2");
+    schema->addContainer("example:b", "example:b2");
+    schema->addContainer("example:a/example:a2", "example:a3");
+    schema->addContainer("example:b/example:b2", "example:b3");
+    schema->addList("", "example:list", {"number"});
+    schema->addContainer("example:list", "example:contInList");
+    schema->addList("", "example:twoKeyList", {"number", "name"});
+    Parser parser(schema);
+    std::string input;
+    std::ostringstream errorStream;
+
+    SECTION("valid input")
+    {
+        ls_ expected;
+
+        SECTION("no arguments")
+        {
+            input = "ls";
+        }
+
+        SECTION("with path argument")
+        {
+            input = "ls example:a";
+            expected.m_path = path_{{node_(module_{"example"}, container_{"a"})}};
+        }
+
+        command_ command = parser.parseCommand(input, errorStream);
+        REQUIRE(command.type() == typeid(ls_));
+        REQUIRE(boost::get<ls_>(command) == expected);
+    }
+    SECTION("invalid input")
+    {
+        SECTION("invalid path")
+        {
+            input = "ls example:nonexistent";
+        }
+
+        REQUIRE_THROWS(parser.parseCommand(input, errorStream));
+    }
+}