Allow immediately executing RPC with no input

Story: https://tree.taiga.io/project/jktjkt-netconf-cli/us/189
Change-Id: I876437b798e995c4484e97b409117a2bb207e864
diff --git a/src/ast_commands.cpp b/src/ast_commands.cpp
index 5b99c6a..fa2b56c 100644
--- a/src/ast_commands.cpp
+++ b/src/ast_commands.cpp
@@ -51,3 +51,8 @@
 {
     return this->m_path == other.m_path;
 }
+
+bool exec_::operator==(const exec_& other) const
+{
+    return this->m_path == other.m_path;
+}
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index db78611..9ec2740 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -258,14 +258,20 @@
     static constexpr auto name = "exec";
     static constexpr auto shortHelp = "exec - Execute RPC/action.";
     static constexpr auto longHelp = R"(
-    exec
+    exec [path]
 
     This command executes the RPC/action you have previously initiated via the
     `prepare` command.
 
+    If the RPC/action has no input parameters, it can be directly execute via
+    `exec` without usgin `prepare`.
+
     Usage:
-        /> exec)";
+        /> exec
+        /> exec /mod:myRpc)";
     bool operator==(const exec_& other) const;
+
+    boost::optional<dataPath_> m_path;
 };
 
 struct cancel_ : x3::position_tagged {
@@ -332,3 +338,4 @@
 BOOST_FUSION_ADAPT_STRUCT(move_, m_source, m_destination)
 BOOST_FUSION_ADAPT_STRUCT(dump_, m_format)
 BOOST_FUSION_ADAPT_STRUCT(prepare_, m_path)
+BOOST_FUSION_ADAPT_STRUCT(exec_, m_path)
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 02da179..2ff6e7a 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -259,10 +259,10 @@
 } const dump_args;
 
 auto const prepare_def =
-    prepare_::name > space_separator > rpcActionPath;
+    prepare_::name > space_separator > as<dataPath_>[RpcActionPath<AllowInput::Yes>{}];
 
 auto const exec_def =
-    exec_::name >> x3::attr(exec_{});
+    exec_::name > -(space_separator > as<dataPath_>[RpcActionPath<AllowInput::No>{}]);
 
 auto const cancel_def =
     cancel_::name >> x3::attr(cancel_{});
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index acb3f0b..ac958dc 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -220,9 +220,12 @@
     m_parser.changeNode(prepare.m_path);
 }
 
-void Interpreter::operator()(const exec_&) const
+void Interpreter::operator()(const exec_& exec) const
 {
     m_parser.changeNode({Scope::Absolute, {}});
+    if (exec.m_path) {
+        m_datastore.initiate(pathToString(toCanonicalPath(*exec.m_path)));
+    }
     auto output = m_datastore.execute();
     std::cout << "RPC/action output:\n";
     printTree(output);
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index 58d174e..c3df931 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -428,12 +428,28 @@
 
 } const writableLeafPath;
 
-struct RpcActionPath : x3::parser<RpcActionPath> {
+enum class AllowInput {
+    Yes,
+    No
+};
+
+template <AllowInput ALLOW_INPUT>
+struct RpcActionPath : x3::parser<RpcActionPath<ALLOW_INPUT>> {
     using attribute_type = dataPath_;
     template <typename It, typename Ctx, typename RCtx, typename Attr>
     static bool parse(It& begin, It end, Ctx const& ctx, RCtx& rctx, Attr& attr)
     {
-        bool res = dataPath.parse(begin, end, ctx, rctx, attr);
+        auto grammar = PathParser<PathParserMode::DataPath, CompletionMode::Data>{[] (const Schema& schema, const std::string& path) {
+            if constexpr (ALLOW_INPUT == AllowInput::No) {
+                auto nodeType = schema.nodeType(path);
+                if (nodeType == yang::NodeTypes::Rpc || nodeType == yang::NodeTypes::Action) {
+                    return !schema.hasInputNodes(path);
+                }
+            }
+
+            return true;
+        }};
+        bool res = grammar.parse(begin, end, ctx, rctx, attr);
         if (!res) {
             return false;
         }
@@ -449,8 +465,6 @@
     }
 };
 
-auto const rpcActionPath = as<dataPath_>[RpcActionPath()];
-
 auto const noRpcOrAction = [](const Schema& schema, const std::string& path) {
     auto nodeType = schema.nodeType(path);
     return nodeType != yang::NodeTypes::Rpc && nodeType != yang::NodeTypes::Action;
diff --git a/src/schema.hpp b/src/schema.hpp
index 00586b5..6955686 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -68,6 +68,7 @@
     [[nodiscard]] virtual std::string leafrefPath(const std::string& leafrefPath) const = 0;
     [[nodiscard]] virtual std::optional<std::string> description(const std::string& location) const = 0;
     [[nodiscard]] virtual yang::Status status(const std::string& location) const = 0;
+    [[nodiscard]] virtual bool hasInputNodes(const std::string& path) const = 0;
 
     [[nodiscard]] virtual std::set<ModuleNodePair> availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const = 0;
 };
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index a75f471..c1d4417 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -41,6 +41,8 @@
     //create a new set of children for the new node
     std::string key = joinPaths(location, name);
     m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
+    m_nodes.emplace(joinPaths(key, "input"), std::unordered_map<std::string, NodeInfo>());
+    m_nodes.emplace(joinPaths(key, "output"), std::unordered_map<std::string, NodeInfo>());
 }
 
 void StaticSchema::addAction(const std::string& location, const std::string& name)
@@ -272,6 +274,15 @@
     throw std::runtime_error{"Internal error: StaticSchema::status(std::string) not implemented. The tests should not have called this overload."};
 }
 
+bool StaticSchema::hasInputNodes(const std::string& path) const
+{
+    if (nodeType(path) != yang::NodeTypes::Action && nodeType(path) != yang::NodeTypes::Rpc) {
+        throw std::logic_error("StaticSchema::hasInputNodes called with non-RPC/action path");
+    }
+
+    return m_nodes.at(joinPaths(path, "input")).size() != 0;
+}
+
 yang::NodeTypes StaticSchema::nodeType(const std::string& path) const
 {
     auto locationString = stripLastNodeFromPath(path);
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index 3009d7e..0b9e59e 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -80,6 +80,7 @@
     std::set<ModuleNodePair> availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const override;
     std::optional<std::string> description(const std::string& path) const override;
     yang::Status status(const std::string& location) const override;
+    bool hasInputNodes(const std::string& path) const override;
 
     /** A helper for making tests a little bit easier. It returns all
      * identities which are based on the argument passed and which can then be
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 7f27711..d6c0bd8 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -485,6 +485,17 @@
     }
 }
 
+bool YangSchema::hasInputNodes(const std::string& path) const
+{
+    auto node = getSchemaNode(path.c_str());
+    if (auto type = node->nodetype(); type != LYS_ACTION && type != LYS_RPC) {
+        throw std::logic_error("StaticSchema::hasInputNodes called with non-RPC/action path");
+    }
+
+    // The first child gives the /input node and then I check whether it has a child.
+    return node->child()->child().get();
+}
+
 bool YangSchema::isConfig(const std::string& path) const
 {
     auto node = getSchemaNode(path.c_str());
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index 632f538..dc45097 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -47,6 +47,7 @@
     [[nodiscard]] std::set<ModuleNodePair> availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const override;
     [[nodiscard]] std::optional<std::string> description(const std::string& path) const override;
     [[nodiscard]] yang::Status status(const std::string& location) const override;
+    [[nodiscard]] bool hasInputNodes(const std::string& path) const override;
 
     void registerModuleCallback(const std::function<std::string(const char*, const char*, const char*, const char*)>& clb);