SEBA-282 xosgenx filtering by app or model;
provide a default argument class for XOSProcessor;
remove tabs;
rename unit tests missed by nose2

Change-Id: I07b782982b6733f8828b8a5a72807326f430b1a5
diff --git a/lib/xos-genx/xosgenx/generator.py b/lib/xos-genx/xosgenx/generator.py
index dc1ff9e..3355fb5 100644
--- a/lib/xos-genx/xosgenx/generator.py
+++ b/lib/xos-genx/xosgenx/generator.py
@@ -26,6 +26,43 @@
 loader = jinja2.PackageLoader(__name__, 'templates')
 env = jinja2.Environment(loader=loader)
 
+class XOSProcessorArgs:
+    """ Helper class for use cases that want to call XOSProcessor directly, rather than executing xosgenx from the
+        command line.
+    """
+
+    default_rev = False
+    default_output = None
+    default_attic = None
+    default_kvpairs = None
+    default_write_to_file = None
+    default_dest_file = None
+    default_dest_extension = None
+    default_target = None
+    default_checkers = None
+    default_verbosity = 0         # Higher numbers = more verbosity, lower numbers = less verbosity
+    default_include_models = []   # If neither include_models nor include_apps is specified, then all models will
+    default_include_apps = []     # be included.
+
+    def __init__(self, **kwargs):
+        # set defaults
+        self.rev = XOSProcessorArgs.default_rev
+        self.output = XOSProcessorArgs.default_output
+        self.attic = XOSProcessorArgs.default_attic
+        self.kvpairs = XOSProcessorArgs.default_kvpairs
+        self.verbosity = XOSProcessorArgs.default_verbosity
+        self.write_to_file = XOSProcessorArgs.default_write_to_file
+        self.default_dest_file = XOSProcessorArgs.default_dest_file
+        self.default_dest_extension = XOSProcessorArgs.default_dest_extension
+        self.default_target = XOSProcessorArgs.default_target
+        self.default_checkers = XOSProcessorArgs.default_target
+        self.include_models = XOSProcessorArgs.default_include_models
+        self.include_apps = XOSProcessorArgs.default_include_apps
+
+        # override defaults with kwargs
+        for (k,v) in kwargs.items():
+            setattr(self, k, v)
+
 class XOSProcessor:
 
     @staticmethod
@@ -225,7 +262,7 @@
             if start == 0:
                 beginning = ''
             else:
-                beginning = inputs[ptr:start-1] 
+                beginning = inputs[ptr:start-1]
 
             line_end_char = inputs[start+end:].find('\n')
             line_end = inputs[line_end_char]
@@ -242,6 +279,19 @@
 
         v = XOSProcessor._attach_parser(ast, args)
 
+        if args.include_models or args.include_apps:
+            for message in v.messages:
+                message["is_included"] = False
+                if message["name"] in args.include_models:
+                    message["is_included"] = True
+                else:
+                    app_label = message.get("options", {}).get("app_label").strip('"')
+                    if app_label in args.include_apps:
+                        message["is_included"] = True
+        else:
+            for message in v.messages:
+                message["is_included"] = True
+
         if args.output is not None and args.write_to_file == "model":
             rendered = {}
             for i, model in enumerate(v.models):
@@ -255,7 +305,7 @@
                             'message_table': models,
                             'messages': messages,
                             'policies': v.policies,
-                            'message_names': [m['name'] for m in messages]
+                            'message_names': [m['name'] for m in v.messages]
                         },
                         "context": context,
                         "options": v.options
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions/base.py b/lib/xos-genx/xosgenx/jinja2_extensions/base.py
index 3bfcd70..af1c241 100644
--- a/lib/xos-genx/xosgenx/jinja2_extensions/base.py
+++ b/lib/xos-genx/xosgenx/jinja2_extensions/base.py
@@ -164,6 +164,9 @@
 
     # The "id" field is a special field. Every model has one. Put it up front and pretend it's part of the
 
+    if not fields:
+        raise Exception("Model %s has no fields. Check for missing base class." % m["name"])
+
     id_field = {'type': 'int32', 'name': 'id', 'options': {}, "id": "1", "accessor": fields[0]["accessor"]}
 
     fields = [id_field] + fields
diff --git a/lib/xos-genx/xosgenx/targets/protoapi.xtarget b/lib/xos-genx/xosgenx/targets/protoapi.xtarget
index 2311b98..e87e7d8 100644
--- a/lib/xos-genx/xosgenx/targets/protoapi.xtarget
+++ b/lib/xos-genx/xosgenx/targets/protoapi.xtarget
@@ -11,7 +11,7 @@
 // fields that are set by the caller, regardless if they are set to a default
 // value. XOS uses this to know when to apply a default value.
 
-{% for object in proto.messages|sort(attribute='name') %}
+{% for object in proto.messages|selectattr("is_included")|sort(attribute='name') %}
 {% if object.name != 'XOSBase' -%}
 message {{ object.name }} {
     {%- if object.name=='CordSubscriberRoot' %}
@@ -47,7 +47,7 @@
 {% endfor %}
 
 service xos {
-{% for object in proto.messages | sort(attribute='name')%}
+{% for object in proto.messages | selectattr("is_included") | sort(attribute='name')%}
 {% if object.name != 'XOSBase' -%}
   rpc List{{ object.name }}(google.protobuf.Empty) returns ({{ xproto_pluralize(object) }}) {
         option (googleapi.http) = {
diff --git a/lib/xos-genx/xosgenx/xosgen.py b/lib/xos-genx/xosgenx/xosgen.py
index 8259d58..27a7fb4 100755
--- a/lib/xos-genx/xosgenx/xosgen.py
+++ b/lib/xos-genx/xosgenx/xosgen.py
@@ -21,21 +21,36 @@
 from version import __version__
 
 parse = argparse.ArgumentParser(description='XOS Generative Toolchain')
-parse.add_argument('--rev', dest='rev', action='store_true',default=False, help='Convert proto to xproto')
-parse.add_argument('--output', dest='output', action='store',default=None, help='Destination dir')
-parse.add_argument('--attic', dest='attic', action='store',default=None, help='The location at which static files are stored')
-parse.add_argument('--kvpairs', dest='kv', action='store',default=None, help='Key value pairs to make available to the target')
-parse.add_argument('--write-to-file', dest='write_to_file', choices = ['single', 'model', 'target'], action='store',default=None, help='Single output file (single) or output file per model (model) or let target decide (target)')
+parse.add_argument('--rev', dest='rev', action='store_true',
+                   default=XOSProcessorArgs.default_rev, help='Convert proto to xproto')
+parse.add_argument('--output', dest='output', action='store',
+                   default=XOSProcessorArgs.default_output, help='Destination dir')
+parse.add_argument('--attic', dest='attic', action='store',
+                   default=XOSProcessorArgs.default_attic, help='The location at which static files are stored')
+parse.add_argument('--kvpairs', dest='kv', action='store',
+                   default=XOSProcessorArgs.default_kvpairs, help='Key value pairs to make available to the target')
+parse.add_argument('--write-to-file', dest='write_to_file', choices = ['single', 'model', 'target'], action='store',
+                   default=XOSProcessorArgs.default_write_to_file,
+                   help='Single output file (single) or output file per model (model) or let target decide (target)')
 parse.add_argument('--version', action='version', version=__version__)
-parse.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity")
+parse.add_argument("-v", "--verbosity", action="count",
+                   default=XOSProcessorArgs.default_verbosity, help="increase output verbosity")
+parse.add_argument("--include-models", dest="include_models", action="append",
+                   default=XOSProcessorArgs.default_include_models, help="list of models to include")
+parse.add_argument("--include-apps", dest="include_apps", action="append",
+                   default=XOSProcessorArgs.default_include_apps, help="list of models to include")
 
 group = parse.add_mutually_exclusive_group()
-group.add_argument('--dest-file', dest='dest_file', action='store',default=None, help='Output file name (if write-to-file is set to single)')
-group.add_argument('--dest-extension', dest='dest_extension', action='store',default=None, help='Output file extension (if write-to-file is set to single)')
+group.add_argument('--dest-file', dest='dest_file', action='store',
+                   default=XOSProcessorArgs.default_dest_file, help='Output file name (if write-to-file is set to single)')
+group.add_argument('--dest-extension', dest='dest_extension', action='store',
+                   default=XOSProcessorArgs.default_dest_extension, help='Output file extension (if write-to-file is set to single)')
 
 group = parse.add_mutually_exclusive_group(required=True)
-group.add_argument('--target', dest='target', action='store',default=None, help='Output format, corresponding to <output>.yaml file')
-group.add_argument('--checkers', dest='checkers', action='store', default=None, help='Comma-separated list of static checkers')
+group.add_argument('--target', dest='target', action='store',
+                   default=XOSProcessorArgs.default_target, help='Output format, corresponding to <output>.yaml file')
+group.add_argument('--checkers', dest='checkers', action='store',
+                   default=XOSProcessorArgs.default_checkers, help='Comma-separated list of static checkers')
 
 parse.add_argument('files', metavar='<input file>', nargs='+', action='store', help='xproto files to compile')