Add recursive ls

Change-Id: Ifab8c9577c01cf7a96cda8d79fe232b12b5538bd
diff --git a/src/ast_commands.cpp b/src/ast_commands.cpp
index 89e2f74..fe2fa84 100644
--- a/src/ast_commands.cpp
+++ b/src/ast_commands.cpp
@@ -26,7 +26,7 @@
 
 bool ls_::operator==(const ls_& b) const
 {
-    return this->m_path == b.m_path;
+    return this->m_path == b.m_path && this->m_options == b.m_options;
 }
 
 bool enum_::operator==(const enum_& b) const
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index 969515e..966f170 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -29,8 +29,13 @@
 
 using keyValue_ = std::pair<std::string, std::string>;
 
+enum class LsOption {
+    Recursive
+};
+
 struct ls_ : x3::position_tagged {
     bool operator==(const ls_& b) const;
+    std::vector<LsOption> m_options;
     boost::optional<path_> m_path;
 };
 
@@ -66,7 +71,7 @@
 
 using command_ = boost::variant<ls_, cd_, create_, delete_, set_, commit_, get_>;
 
-BOOST_FUSION_ADAPT_STRUCT(ls_, m_path)
+BOOST_FUSION_ADAPT_STRUCT(ls_, m_options, 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/grammars.hpp b/src/grammars.hpp
index 5a3db0f..fddd409 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -134,8 +134,16 @@
 auto const space_separator =
         x3::omit[x3::no_skip[space]];
 
+struct ls_options_table : x3::symbols<LsOption> {
+    ls_options_table()
+    {
+        add
+            ("--recursive", LsOption::Recursive);
+    }
+} const ls_options;
+
 auto const ls_def =
-        lit("ls") >> -path;
+        lit("ls") >> *(space_separator >> ls_options) >> -(space_separator >> path);
 
 auto const cd_def =
         lit("cd") >> space_separator > path;
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index 4e5d665..bdeecf9 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -60,8 +60,13 @@
 void Interpreter::operator()(const ls_& ls) const
 {
     std::cout << "Possible nodes:" << std::endl;
+    auto recursion{Recursion::NonRecursive};
+    for (auto it : ls.m_options) {
+        if (it == LsOption::Recursive)
+            recursion = Recursion::Recursive;
+    }
 
-    for (const auto& it : m_parser.availableNodes(ls.m_path))
+    for (const auto& it : m_parser.availableNodes(ls.m_path, recursion))
         std::cout << it << std::endl;
 }
 
diff --git a/src/parser.cpp b/src/parser.cpp
index c56b561..f25f1a3 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -58,10 +58,10 @@
     return "/" + pathToDataString(m_curDir);
 }
 
-std::set<std::string> Parser::availableNodes(const boost::optional<path_>& path) const
+std::set<std::string> Parser::availableNodes(const boost::optional<path_>& path, const Recursion& option) 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);
+    return m_schema->childNodes(pathArg, option);
 }
diff --git a/src/parser.hpp b/src/parser.hpp
index 0f1fd79..0318733 100644
--- a/src/parser.hpp
+++ b/src/parser.hpp
@@ -23,14 +23,13 @@
     ~TooManyArgumentsException() override;
 };
 
-
 class Parser {
 public:
     Parser(const std::shared_ptr<const Schema> schema);
     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;
+    std::set<std::string> availableNodes(const boost::optional<path_>& path, const Recursion& option) const;
 
 private:
     const std::shared_ptr<const Schema> m_schema;
diff --git a/src/schema.hpp b/src/schema.hpp
index 96176ab..e5827de 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -45,6 +45,10 @@
 };
 }
 
+enum class Recursion {
+    NonRecursive,
+    Recursive
+};
 
 using NodeType = boost::variant<yang::container, yang::list, yang::leaf, yang::module>;
 
@@ -75,7 +79,7 @@
     virtual bool nodeExists(const std::string& location, const std::string& node) const = 0;
     virtual const std::set<std::string> listKeys(const path_& location, const ModuleNodePair& node) const = 0;
     virtual yang::LeafDataTypes leafType(const path_& location, const ModuleNodePair& node) const = 0;
-    virtual std::set<std::string> childNodes(const path_& path) const = 0;
+    virtual std::set<std::string> childNodes(const path_& path, const Recursion recursion) const = 0;
 
 private:
     const std::unordered_map<std::string, NodeType>& children(const std::string& name) const;
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index 1778c68..ce94218 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -55,7 +55,6 @@
     m_nodes.emplace(key, std::unordered_map<std::string, NodeType>());
 }
 
-
 bool StaticSchema::listHasKey(const path_& location, const ModuleNodePair& node, const std::string& key) const
 {
     std::string locationString = pathToAbsoluteSchemaString(location);
@@ -146,7 +145,9 @@
     return boost::get<yang::leaf>(children(locationString).at(fullNodeName(location, node))).m_type;
 }
 
-std::set<std::string> StaticSchema::childNodes(const path_& path) const
+// We do not test StaticSchema, so we don't need to implement recursive childNodes
+// for this class.
+std::set<std::string> StaticSchema::childNodes(const path_& path, const Recursion) const
 {
     std::string locationString = pathToAbsoluteSchemaString(path);
     std::set<std::string> res;
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index 1c261c3..68f1401 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -33,7 +33,7 @@
     bool nodeExists(const std::string& location, const std::string& node) const override;
     const std::set<std::string> listKeys(const path_& location, const ModuleNodePair& node) const override;
     yang::LeafDataTypes leafType(const path_& location, const ModuleNodePair& node) const override;
-    std::set<std::string> childNodes(const path_& path) const override;
+    std::set<std::string> childNodes(const path_& path, const Recursion) const override;
 
     void addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence = yang::ContainerTraits::None);
     void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataTypes& type);
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 946162b..6f34ad1 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -202,25 +202,35 @@
     return res;
 }
 
-std::set<std::string> YangSchema::childNodes(const path_& path) const
+std::set<std::string> YangSchema::childNodes(const path_& path, const Recursion recursion) const
 {
     using namespace std::string_view_literals;
     std::set<std::string> res;
+    std::vector<libyang::S_Schema_Node> nodes;
+
     if (path.m_nodes.empty()) {
-        const auto& nodeVec = m_context->data_instantiables(0);
-        for (const auto it : nodeVec) {
-            if (it->module()->name() == "ietf-yang-library"sv)
-                continue;
-            res.insert(std::string(it->module()->name()) + ":" + it->name());
-        }
+        nodes = m_context->data_instantiables(0);
     } else {
         const auto absolutePath = "/" + pathToAbsoluteSchemaString(path);
         const auto set = m_context->find_path(absolutePath.c_str());
-        const auto& schemaSet = set->schema();
+        const auto schemaSet = set->schema();
         for (auto it = (*schemaSet.begin())->child(); it; it = it->next()) {
-            res.insert(std::string(it->module()->name()) + ":" + it->name());
+            nodes.push_back(it);
         }
     }
+
+    for (const auto node : nodes) {
+        if (node->module()->name() == "ietf-yang-library"sv)
+                continue;
+        if (recursion == Recursion::Recursive) {
+            for (auto it : node->tree_dfs()) {
+                res.insert(it->path(LYS_PATH_FIRST_PREFIX));
+            }
+        } else {
+            res.insert(std::string(node->module()->name()) + ":" + node->name());
+        }
+    }
+
     return res;
 }
 
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index d100302..c61232f 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -39,7 +39,7 @@
     bool nodeExists(const std::string& location, const std::string& node) const override;
     const std::set<std::string> listKeys(const path_& location, const ModuleNodePair& node) const override;
     yang::LeafDataTypes leafType(const path_& location, const ModuleNodePair& node) const override;
-    std::set<std::string> childNodes(const path_& path) const override;
+    std::set<std::string> childNodes(const path_& path, const Recursion recursion) const override;
 
     void registerModuleCallback(const std::function<std::string(const char*, const char*, const char*)>& clb);
 
diff --git a/tests/ls.cpp b/tests/ls.cpp
index 9356c69..e3287ee 100644
--- a/tests/ls.cpp
+++ b/tests/ls.cpp
@@ -36,7 +36,14 @@
 
         SECTION("no arguments")
         {
-            input = "ls";
+            SECTION("ls")
+                input = "ls";
+
+            SECTION("ls --recursive")
+            {
+                input = "ls --recursive";
+                expected.m_options.push_back(LsOption::Recursive);
+            }
         }
 
         SECTION("with path argument")
@@ -110,12 +117,23 @@
                 expected.m_path = path_{Scope::Absolute, {node_(module_{"example"}, container_{"a"}),
                                                           node_(module_{"example"}, container_{"a2"})}};
             }
+
+            SECTION("ls --recursive /example:a")
+            {
+                SECTION("cwd: /") {}
+                SECTION("cwd: /example:a") {parser.changeNode(path_{Scope::Relative, {node_(module_{"example"}, container_{"a"})}});}
+
+                input = "ls --recursive /example:a";
+                expected.m_options.push_back(LsOption::Recursive);
+                expected.m_path = path_{Scope::Absolute, {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")
@@ -129,13 +147,31 @@
             SECTION("ls /bad:nonexistent")
                 input = "ls /bad:nonexistent";
 
-            SECTION( "ls example:a/nonexistent")
+            SECTION("ls example:a/nonexistent")
                 input = "ls example:a/nonexistent";
 
-            SECTION( "ls /example:a/nonexistent")
+            SECTION("ls /example:a/nonexistent")
                 input = "ls /example:a/nonexistent";
         }
 
+        SECTION("whitespace before path")
+        {
+            SECTION("ls --recursive/")
+                input = "ls --recursive/";
+
+            SECTION("ls/")
+                input = "ls/";
+
+            SECTION("ls --recursive/example:a")
+                input = "ls --recursive/example:a";
+
+            SECTION("ls/example:a")
+                input = "ls/example:a";
+
+            SECTION("lssecond:a")
+                input = "lssecond:a";
+        }
+
         REQUIRE_THROWS(parser.parseCommand(input, errorStream));
     }
 }
diff --git a/tests/yang.cpp b/tests/yang.cpp
index 09bef03..6f63bfc 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -288,7 +288,7 @@
                 set = {"example-schema:a2", "example-schema:leafa"};
             }
 
-            REQUIRE(ys.childNodes(path) == set);
+            REQUIRE(ys.childNodes(path, Recursion::NonRecursive) == set);
         }
     }