sdbus++: add stubs to generate event files

Add options to `sdbus++` to generate the files for `events.yaml` files:
header, cpp, markdown.  Create simple stubs for these that generate
empty files.  Enable them in `sdbus++-gen-meson` and add an example
for the Calculator.

Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: Ia1df9ca02e1de5fc3f6dadfd409d646e1341a3d6
diff --git a/example/gen/meson.build b/example/gen/meson.build
index b550c79..c63d829 100644
--- a/example/gen/meson.build
+++ b/example/gen/meson.build
@@ -5,10 +5,10 @@
     check: true,
 ).stdout().strip().split('\n')[0]
 
-if sdbuspp_gen_meson_ver != 'sdbus++-gen-meson version 7'
+if sdbuspp_gen_meson_ver != 'sdbus++-gen-meson version 8'
     warning('Generated meson files from wrong version of sdbus++-gen-meson.')
     warning(
-        'Expected "sdbus++-gen-meson version 7", got:',
+        'Expected "sdbus++-gen-meson version 8", got:',
         sdbuspp_gen_meson_ver
     )
 endif
diff --git a/example/gen/net/poettering/Calculator/meson.build b/example/gen/net/poettering/Calculator/meson.build
index 128a6ca..5869d1d 100644
--- a/example/gen/net/poettering/Calculator/meson.build
+++ b/example/gen/net/poettering/Calculator/meson.build
@@ -2,7 +2,7 @@
 generated_sources += custom_target(
     'net/poettering/Calculator__cpp'.underscorify(),
     input: [ '../../../../yaml/net/poettering/Calculator.interface.yaml',  ],
-    output: [ 'error.cpp', 'error.hpp', 'common.hpp', 'server.cpp', 'server.hpp', 'aserver.hpp', 'client.hpp',  ],
+    output: [ 'error.cpp', 'error.hpp', 'event.cpp', 'event.hpp', 'common.hpp', 'server.cpp', 'server.hpp', 'aserver.hpp', 'client.hpp',  ],
     depend_files: sdbusplusplus_depfiles,
     command: [
         sdbuspp_gen_meson_prog, '--command', 'cpp',
diff --git a/example/gen/net/poettering/meson.build b/example/gen/net/poettering/meson.build
index 3aa2a36..c88566e 100644
--- a/example/gen/net/poettering/meson.build
+++ b/example/gen/net/poettering/meson.build
@@ -2,7 +2,7 @@
 subdir('Calculator')
 generated_others += custom_target(
     'net/poettering/Calculator__markdown'.underscorify(),
-    input: [ '../../../yaml/net/poettering/Calculator.errors.yaml', '../../../yaml/net/poettering/Calculator.interface.yaml',  ],
+    input: [ '../../../yaml/net/poettering/Calculator.errors.yaml', '../../../yaml/net/poettering/Calculator.events.yaml', '../../../yaml/net/poettering/Calculator.interface.yaml',  ],
     output: [ 'Calculator.md' ],
     depend_files: sdbusplusplus_depfiles,
     command: [
diff --git a/example/yaml/net/poettering/Calculator.events.yaml b/example/yaml/net/poettering/Calculator.events.yaml
new file mode 100644
index 0000000..8372d70
--- /dev/null
+++ b/example/yaml/net/poettering/Calculator.events.yaml
@@ -0,0 +1,20 @@
+version: 0.0.0
+
+errors:
+    - name: DivisionByZero
+      severity: warning
+      errno: EDOM
+      en:
+          message: Attempted to divide by zero.
+
+    - name: PermissionDenied
+      severity: warning
+      errno: EPERM
+      en:
+          message: Insufficient privileges for operation.
+
+events:
+    - name: Cleared
+      en:
+          description: The calculator is cleared.
+          message: The calculator is cleared.
diff --git a/test/gen/meson.build b/test/gen/meson.build
index b550c79..c63d829 100644
--- a/test/gen/meson.build
+++ b/test/gen/meson.build
@@ -5,10 +5,10 @@
     check: true,
 ).stdout().strip().split('\n')[0]
 
-if sdbuspp_gen_meson_ver != 'sdbus++-gen-meson version 7'
+if sdbuspp_gen_meson_ver != 'sdbus++-gen-meson version 8'
     warning('Generated meson files from wrong version of sdbus++-gen-meson.')
     warning(
-        'Expected "sdbus++-gen-meson version 7", got:',
+        'Expected "sdbus++-gen-meson version 8", got:',
         sdbuspp_gen_meson_ver
     )
 endif
diff --git a/tools/meson.build b/tools/meson.build
index 4da16d6..ea17050 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -3,6 +3,7 @@
   'sdbusplus/__init__.py',
   'sdbusplus/enum.py',
   'sdbusplus/error.py',
+  'sdbusplus/event.py',
   'sdbusplus/interface.py',
   'sdbusplus/main.py',
   'sdbusplus/method.py',
diff --git a/tools/sdbus++-gen-meson b/tools/sdbus++-gen-meson
index 665c50e..1807108 100755
--- a/tools/sdbus++-gen-meson
+++ b/tools/sdbus++-gen-meson
@@ -36,7 +36,7 @@
 ## if a repository contains old copies of the generated meson.build files and
 ## needs an update.  We should increment the version number whenever the
 ## resulting meson.build would change.
-tool_version="sdbus++-gen-meson version 7"
+tool_version="sdbus++-gen-meson version 8"
 function show_version() {
     echo "${tool_version}"
 }
@@ -187,6 +187,10 @@
                 outputs="${outputs}'error.cpp', 'error.hpp', "
                 ;;
 
+            events.yaml)
+                outputs="${outputs}'event.cpp', 'event.hpp', "
+                ;;
+
             interface.yaml)
                 outputs="${outputs}'common.hpp', "
                 outputs="${outputs}'server.cpp', 'server.hpp', "
@@ -256,7 +260,7 @@
 ## Handle command=meson by generating the tree of meson.build files.
 function cmd_meson() {
     # Find and sort all the YAML files
-    yamls="$(find "${rootdir}" -name '*.interface.yaml' -o -name '*.errors.yaml')"
+    yamls="$(find "${rootdir}" -name '*.interface.yaml' -o -name '*.errors.yaml' -o -name '*.events.yaml')"
     yamls="$(echo "${yamls}" | sort)"
 
     # Assign the YAML files into the hash-table by interface name.
@@ -293,7 +297,8 @@
     fi
 
     if [[ ! -e "${rootdir}/$1.interface.yaml" ]] &&
-    [[ ! -e "${rootdir}/$1.errors.yaml" ]]; then
+    [[ ! -e "${rootdir}/$1.errors.yaml" ]] &&
+    [[ ! -e "${rootdir}/$1.events.yaml" ]]; then
         echo "Missing YAML for $1."
         exit 1
     fi
@@ -315,6 +320,11 @@
         ${sdbusppcmd} error exception-header "${intf}" > "${outputdir}/error.hpp"
         ${sdbusppcmd} error exception-cpp "${intf}" > "${outputdir}/error.cpp"
     fi
+
+    if [[ -e "${rootdir}/$1.events.yaml" ]]; then
+        ${sdbusppcmd} event exception-header "${intf}" > "${outputdir}/event.hpp"
+        ${sdbusppcmd} event exception-cpp "${intf}" > "${outputdir}/event.cpp"
+    fi
 }
 
 ## Handle command=markdown by calling sdbus++ as appropriate.
@@ -329,7 +339,8 @@
     fi
 
     if [[ ! -e "${rootdir}/$1.interface.yaml" ]] &&
-    [[ ! -e "${rootdir}/$1.errors.yaml" ]]; then
+    [[ ! -e "${rootdir}/$1.errors.yaml" ]] &&
+    [[ ! -e "${rootdir}/$1.events.yaml" ]]; then
         echo "Missing YAML for $1."
         exit 1
     fi
@@ -348,6 +359,10 @@
     if [[ -e "${rootdir}/$1.errors.yaml" ]]; then
         ${sdbusppcmd} error markdown "${intf}" >> "${outputdir}/${base}.md"
     fi
+
+    if [[ -e "${rootdir}/$1.events.yaml" ]]; then
+        ${sdbusppcmd} event markdown "${intf}" >> "${outputdir}/${base}.md"
+    fi
 }
 
 ## Handle command=version.
diff --git a/tools/sdbusplus/__init__.py b/tools/sdbusplus/__init__.py
index 1f509e4..1d91530 100644
--- a/tools/sdbusplus/__init__.py
+++ b/tools/sdbusplus/__init__.py
@@ -1,4 +1,5 @@
 from sdbusplus.error import Error
+from sdbusplus.event import Event
 from sdbusplus.interface import Interface
 
-__all__ = ["Error", "Interface"]
+__all__ = ["Error", "Event", "Interface"]
diff --git a/tools/sdbusplus/error.py b/tools/sdbusplus/error.py
index 7af1809..d606deb 100644
--- a/tools/sdbusplus/error.py
+++ b/tools/sdbusplus/error.py
@@ -14,7 +14,7 @@
 
 class Error(NamedElement, Renderer):
     @staticmethod
-    def load(name, rootdir="."):
+    def load(name, rootdir, schemadir):
         filename = os.path.join(
             rootdir, name.replace(".", "/") + ".errors.yaml"
         )
diff --git a/tools/sdbusplus/event.py b/tools/sdbusplus/event.py
new file mode 100644
index 0000000..d8b0ac5
--- /dev/null
+++ b/tools/sdbusplus/event.py
@@ -0,0 +1,46 @@
+import os
+
+import jsonschema
+import yaml
+
+from .namedelement import NamedElement
+from .renderer import Renderer
+
+
+class Event(NamedElement, Renderer):
+    @staticmethod
+    def load(name, rootdir, schemadir):
+        schemafile = os.path.join(schemadir, "events.schema.yaml")
+        with open(schemafile) as f:
+            data = f.read()
+            schema = yaml.safe_load(data)
+
+            spec = jsonschema.Draft202012Validator
+            spec.check_schema(schema)
+
+            validator = spec(schema)
+
+        filename = os.path.join(
+            rootdir, name.replace(".", "/") + ".events.yaml"
+        )
+
+        with open(filename) as f:
+            data = f.read()
+            y = yaml.safe_load(data)
+
+            validator.validate(y)
+
+            y["name"] = name
+            return Event(**y)
+
+    def __init__(self, **kwargs):
+        super(Event, self).__init__(**kwargs)
+
+    def markdown(self, loader):
+        return ""
+
+    def exception_header(self, loader):
+        return ""
+
+    def exception_cpp(self, loader):
+        return ""
diff --git a/tools/sdbusplus/interface.py b/tools/sdbusplus/interface.py
index 933e51f..d801814 100644
--- a/tools/sdbusplus/interface.py
+++ b/tools/sdbusplus/interface.py
@@ -14,7 +14,7 @@
 
 class Interface(NamedElement, Renderer):
     @staticmethod
-    def load(name, rootdir="."):
+    def load(name, rootdir, schemadir):
         filename = os.path.join(
             rootdir, name.replace(".", "/") + ".interface.yaml"
         )
diff --git a/tools/sdbusplus/main.py b/tools/sdbusplus/main.py
index fcab7b1..ac66f99 100644
--- a/tools/sdbusplus/main.py
+++ b/tools/sdbusplus/main.py
@@ -9,7 +9,11 @@
 def main():
     module_path = os.path.dirname(sdbusplus.__file__)
 
-    valid_types = {"interface": sdbusplus.Interface, "error": sdbusplus.Error}
+    valid_types = {
+        "error": sdbusplus.Error,
+        "event": sdbusplus.Event,
+        "interface": sdbusplus.Interface,
+    }
     valid_processes = {
         "aserver-header": "async_server_header",
         "client-header": "client_header",
@@ -40,6 +44,14 @@
         help="Location of templates files.",
     )
     parser.add_argument(
+        "-s",
+        "--schemadir",
+        dest="schemadir",
+        default=os.path.join(module_path, "schemas"),
+        type=str,
+        help="Location of schema files.",
+    )
+    parser.add_argument(
         "typeName",
         metavar="TYPE",
         type=str,
@@ -64,6 +76,8 @@
 
     lookup = mako.lookup.TemplateLookup(directories=[args.templatedir])
 
-    instance = valid_types[args.typeName].load(args.item, args.rootdir)
+    instance = valid_types[args.typeName].load(
+        args.item, args.rootdir, args.schemadir
+    )
     function = getattr(instance, valid_processes[args.process])
     print(function(lookup))