[CORD-1440] Moving the generative toolchain in a library
Change-Id: Ifa8e8f930ac34e1f8952099b7e34842a52f4664d
diff --git a/lib/xos-genx/xosgenx/generator.py b/lib/xos-genx/xosgenx/generator.py
new file mode 100755
index 0000000..239a42a
--- /dev/null
+++ b/lib/xos-genx/xosgenx/generator.py
@@ -0,0 +1,223 @@
+import plyxproto.parser as plyxproto
+import jinja2
+import os
+from xos2jinja import XOS2Jinja
+from proto2xproto import Proto2XProto
+import jinja2_extensions
+import yaml
+
+loader = jinja2.PackageLoader(__name__, 'templates')
+env = jinja2.Environment(loader=loader)
+
+class XOSGenerator:
+
+ @staticmethod
+ def _read_input_from_files(files):
+ input = ''
+ for fname in files:
+ with open(fname) as infile:
+ input += infile.read()
+ return input
+
+ @staticmethod
+ def _attach_parser(ast, args):
+ if hasattr(args, 'rev') and args.rev:
+ v = Proto2XProto()
+ ast.accept(v)
+ else:
+ v = XOS2Jinja()
+ ast.accept(v)
+ return v
+
+ @staticmethod
+ def _get_template(target):
+ if not os.path.isabs(target):
+ return os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/targets/' + target)
+ return target
+
+ @staticmethod
+ def _file_exists(attic):
+ # NOTE this method can be used in the jinja template
+ def file_exists2(name):
+ if attic is not None:
+ path = attic + '/' + name
+ else:
+ path = name
+ return (os.path.exists(path))
+
+ return file_exists2
+
+ @staticmethod
+ def _include_file(attic):
+ # NOTE this method can be used in the jinja template
+ def include_file2(name):
+ if attic is not None:
+ path = attic + '/' + name
+ else:
+ path = name
+ return open(path).read()
+ return include_file2
+
+ @staticmethod
+ def _load_jinja2_extensions(os_template_env, attic):
+
+ os_template_env.globals['include_file'] = XOSGenerator._include_file(attic) # Generates a function
+ os_template_env.globals['file_exists'] = XOSGenerator._file_exists(attic) # Generates a function
+
+ os_template_env.filters['yaml'] = yaml.dump
+ for f in dir(jinja2_extensions):
+ if f.startswith('xproto'):
+ os_template_env.globals[f] = getattr(jinja2_extensions, f)
+ return os_template_env
+
+ @staticmethod
+ def _add_context(args):
+ if not hasattr(args, 'kv') or not args.kv:
+ return
+ try:
+ context = {}
+ for s in args.kv.split(','):
+ k, val = s.split(':')
+ context[k] = val
+ return context
+ except Exception, e:
+ print e.message
+
+ @staticmethod
+ def _write_single_file(rendered, dir, dest_file, quiet):
+
+ file_name = "%s/%s" % (dir, dest_file)
+ file = open(file_name, 'w')
+ file.write(rendered)
+ file.close()
+ if quiet == False:
+ print "Saved: %s" % file_name
+
+ @staticmethod
+ def _write_file_per_model(rendered, dir, extension, quiet):
+ for m in rendered:
+
+ file_name = "%s/%s.%s" % (dir, m.lower(), extension)
+ if not rendered[m]:
+ if quiet == False:
+ print "Not saving %s as it is empty" % file_name
+ else:
+ file = open(file_name, 'w')
+ file.write(rendered[m])
+ file.close()
+ if quiet == False:
+ print "Saved: %s" % file_name
+
+ @staticmethod
+ def _write_split_target(rendered, dir, quiet):
+
+ lines = rendered.splitlines()
+ current_buffer = []
+ for l in lines:
+ if (l.startswith('+++')):
+
+ if dir:
+ path = dir + '/' + l[4:].lower()
+
+ fil = open(path, 'w')
+ buf = '\n'.join(current_buffer)
+
+ obuf = buf
+
+ fil.write(obuf)
+ fil.close()
+
+ if quiet == False:
+ print "Save file to: %s" % path
+
+ current_buffer = []
+ else:
+ current_buffer.append(l)
+
+ @staticmethod
+ def _find_message_by_model_name(messages, model):
+ return next((x for x in messages if x['name'] == model), None)
+
+ @staticmethod
+ def generate(args):
+
+ # Setting defaults
+ if not hasattr(args, 'attic'):
+ args.attic = None
+ if not hasattr(args, 'write_to_file'):
+ args.write_to_file = None
+ if not hasattr(args, 'dest_file'):
+ args.dest_file = None
+ if not hasattr(args, 'dest_extension'):
+ args.dest_extension = None
+ if not hasattr(args, 'output'):
+ args.output = None
+ if not hasattr(args, 'quiet'):
+ args.quiet = True
+
+ # Validating
+ if args.write_to_file == 'single' and args.dest_file is None:
+ raise Exception("[XosGenX] write_to_file option is specified as 'single' but no dest_file is provided")
+ if args.write_to_file == 'model' and args.dest_extension is None:
+ raise Exception("[XosGenX] write_to_file option is specified as 'model' but no dest_extension is provided")
+
+ if args.output is not None and not os.path.isabs(args.output):
+ raise Exception("[XosGenX] The output dir must be an absolute path!")
+ if args.output is not None and not os.path.isdir(args.output):
+ raise Exception("[XosGenX] The output dir must be a directory!")
+
+ if hasattr(args, 'files'):
+ inputs = XOSGenerator._read_input_from_files(args.files)
+ elif hasattr(args, 'inputs'):
+ inputs = args.inputs
+ else:
+ raise Exception("[XosGenX] No inputs provided!")
+
+ template_path = XOSGenerator._get_template(args.target)
+ [template_folder, template_name] = os.path.split(template_path)
+ os_template_loader = jinja2.FileSystemLoader(searchpath=[template_folder])
+ os_template_env = jinja2.Environment(loader=os_template_loader)
+ os_template_env = XOSGenerator._load_jinja2_extensions(os_template_env, args.attic)
+ template = os_template_env.get_template(template_name)
+ context = XOSGenerator._add_context(args)
+
+ parser = plyxproto.ProtobufAnalyzer()
+ ast = parser.parse_string(inputs, debug=0)
+ v = XOSGenerator._attach_parser(ast, args)
+
+ if args.output is not None and args.write_to_file == "model":
+ rendered = {}
+ for i, model in enumerate(v.models):
+ models = {}
+ models[model] = v.models[model]
+ messages = [XOSGenerator._find_message_by_model_name(v.messages, model)]
+ rendered[model] = template.render(
+ {"proto":
+ {
+ 'message_table': models,
+ 'messages': messages,
+ 'message_names': [m['name'] for m in messages]
+ },
+ "context": context,
+ "options": v.options
+ }
+ )
+ XOSGenerator._write_file_per_model(rendered, args.output, args.dest_extension, args.quiet)
+ else:
+ rendered = template.render(
+ {"proto":
+ {
+ 'message_table': v.models,
+ 'messages': v.messages,
+ 'message_names': [m['name'] for m in v.messages]
+ },
+ "context": context,
+ "options": v.options
+ }
+ )
+ if args.output is not None and args.write_to_file == "target":
+ XOSGenerator._write_split_target(rendered, args.output, args.quiet)
+ elif args.output is not None and args.write_to_file == "single":
+ XOSGenerator._write_single_file(rendered, args.output, args.dest_file, args.quiet)
+
+ return rendered
\ No newline at end of file