sdbus++: generate header for server bindings

Add a 'server-header' command to sdbus++ that generates a class
definition for the server bindings.  This class defines static
functions for registering as sd-bus callbacks and virtual C++
functions to implement the method behavior.

It is expected that a server implementation will create a class,
which inherits from this generated class, and implement all of
the method behaviors.  Instances of the class will then register
on construction with sd-bus.

Signals and properties are not yet supported.

Change-Id: If0ec37b2acb6f8d528358004ec91dbe979644df7
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
diff --git a/tools/sdbus++ b/tools/sdbus++
index e68685d..6606d07 100755
--- a/tools/sdbus++
+++ b/tools/sdbus++
@@ -5,7 +5,8 @@
 
 def main():
     valid_types = { 'interface': sdbusplus.Interface }
-    valid_processes = { 'markdown' : "markdown" }
+    valid_processes = { 'markdown' : "markdown",
+                        'server-header' : "server_header" }
 
     parser = argparse.ArgumentParser(description='Process sdbus++ YAML files.')
 
diff --git a/tools/sdbusplus/interface.py b/tools/sdbusplus/interface.py
index 0cdb1a0..fa56eb9 100644
--- a/tools/sdbusplus/interface.py
+++ b/tools/sdbusplus/interface.py
@@ -30,3 +30,6 @@
 
     def markdown(self, loader):
         return self.render(loader, "interface.mako.md", interface=self)
+
+    def server_header(self, loader):
+        return self.render(loader, "interface.mako.server.hpp", interface=self)
diff --git a/tools/sdbusplus/method.py b/tools/sdbusplus/method.py
index f8e2efd..84d6095 100644
--- a/tools/sdbusplus/method.py
+++ b/tools/sdbusplus/method.py
@@ -14,3 +14,7 @@
 
     def markdown(self, loader):
         return self.render(loader, "method.mako.md", method=self)
+
+    def cpp_prototype(self, loader, interface, ptype):
+        return self.render(loader, "method.mako.prototype.hpp", method=self,
+                           interface=interface, ptype=ptype, post=str.rstrip)
diff --git a/tools/templates/interface.mako.server.hpp b/tools/templates/interface.mako.server.hpp
new file mode 100644
index 0000000..ff18548
--- /dev/null
+++ b/tools/templates/interface.mako.server.hpp
@@ -0,0 +1,37 @@
+#pragma once
+#include <tuple>
+#include <systemd/sd-bus.h>
+#include <sdbusplus/vtable.hpp>
+    <%
+        namespaces = interface.name.split('.')
+        classname = namespaces.pop()
+    %>
+namespace sdbusplus
+{
+namespace server
+{
+    % for s in namespaces:
+namespace ${s}
+{
+    % endfor
+
+class ${classname}
+{
+    public:
+    % for m in interface.methods:
+${ m.cpp_prototype(loader, interface=interface, ptype='header') }
+    % endfor
+
+    private:
+    % for m in interface.methods:
+${ m.cpp_prototype(loader, interface=interface, ptype='callback-header') }
+    % endfor
+
+        static const sdbusplus::vtable::vtable_t _vtable[];
+};
+
+    % for s in namespaces:
+} // namespace ${s}
+    % endfor
+} // namespace server
+} // namespace sdbusplus
diff --git a/tools/templates/method.mako.prototype.hpp b/tools/templates/method.mako.prototype.hpp
new file mode 100644
index 0000000..5055dbb
--- /dev/null
+++ b/tools/templates/method.mako.prototype.hpp
@@ -0,0 +1,57 @@
+<%
+    def cpp_return_type():
+        if len(method.returns) == 0:
+            return "void"
+        elif len(method.returns) == 1:
+            return method.returns[0].typeName
+        else:
+            return "std::tuple<" + \
+                   ", ".join([ r.typeName for r in method.returns ]) + \
+                   ">"
+
+    def parameters(defaultValue=False):
+        return ",\n            ".\
+            join([ parameter(p, defaultValue) for p in method.parameters ])
+
+    def parameter(p, defaultValue=False):
+        r = "%s %s" % (p.typeName, p.name)
+        if defaultValue:
+            r += default_value(p)
+        return r
+
+    def default_value(p):
+        if p.defaultValue != None:
+            return " = " + str(p.defaultValue)
+        else:
+            return ""
+%>
+###
+### Emit 'header'
+###
+    % if ptype == 'header':
+        /** @brief Implementation for ${ method.name }
+         *  ${ method.description.strip() }
+    % if len(method.parameters) != 0:
+         *
+        % for p in method.parameters:
+         *  @param[in] ${p.name} - ${p.description.strip()}
+        % endfor
+    % endif
+    % if len(method.returns) != 0:
+         *
+        % for r in method.returns:
+         *  @return ${r.name}[${r.typeName}] - ${r.description.strip()}
+        % endfor
+    % endif
+         */
+        virtual ${cpp_return_type()} ${ method.name }(
+            ${ parameters() }) = 0;
+###
+### Emit 'callback-header'
+###
+    % elif ptype == 'callback-header':
+        /** @brief sd-bus callback for ${ method.name }
+         */
+        static int _callback_${ method.name }(
+            sd_bus_message*, void*, sd_bus_error*);
+    % endif