blob: 239a42a60c7983cd52617ca813d1f4953aa7436a [file] [log] [blame]
Matteo Scandolo67654fa2017-06-09 09:33:17 -07001import plyxproto.parser as plyxproto
2import jinja2
3import os
4from xos2jinja import XOS2Jinja
5from proto2xproto import Proto2XProto
6import jinja2_extensions
7import yaml
8
9loader = jinja2.PackageLoader(__name__, 'templates')
10env = jinja2.Environment(loader=loader)
11
12class XOSGenerator:
13
14 @staticmethod
15 def _read_input_from_files(files):
16 input = ''
17 for fname in files:
18 with open(fname) as infile:
19 input += infile.read()
20 return input
21
22 @staticmethod
23 def _attach_parser(ast, args):
24 if hasattr(args, 'rev') and args.rev:
25 v = Proto2XProto()
26 ast.accept(v)
27 else:
28 v = XOS2Jinja()
29 ast.accept(v)
30 return v
31
32 @staticmethod
33 def _get_template(target):
34 if not os.path.isabs(target):
35 return os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/targets/' + target)
36 return target
37
38 @staticmethod
39 def _file_exists(attic):
40 # NOTE this method can be used in the jinja template
41 def file_exists2(name):
42 if attic is not None:
43 path = attic + '/' + name
44 else:
45 path = name
46 return (os.path.exists(path))
47
48 return file_exists2
49
50 @staticmethod
51 def _include_file(attic):
52 # NOTE this method can be used in the jinja template
53 def include_file2(name):
54 if attic is not None:
55 path = attic + '/' + name
56 else:
57 path = name
58 return open(path).read()
59 return include_file2
60
61 @staticmethod
62 def _load_jinja2_extensions(os_template_env, attic):
63
64 os_template_env.globals['include_file'] = XOSGenerator._include_file(attic) # Generates a function
65 os_template_env.globals['file_exists'] = XOSGenerator._file_exists(attic) # Generates a function
66
67 os_template_env.filters['yaml'] = yaml.dump
68 for f in dir(jinja2_extensions):
69 if f.startswith('xproto'):
70 os_template_env.globals[f] = getattr(jinja2_extensions, f)
71 return os_template_env
72
73 @staticmethod
74 def _add_context(args):
75 if not hasattr(args, 'kv') or not args.kv:
76 return
77 try:
78 context = {}
79 for s in args.kv.split(','):
80 k, val = s.split(':')
81 context[k] = val
82 return context
83 except Exception, e:
84 print e.message
85
86 @staticmethod
87 def _write_single_file(rendered, dir, dest_file, quiet):
88
89 file_name = "%s/%s" % (dir, dest_file)
90 file = open(file_name, 'w')
91 file.write(rendered)
92 file.close()
93 if quiet == False:
94 print "Saved: %s" % file_name
95
96 @staticmethod
97 def _write_file_per_model(rendered, dir, extension, quiet):
98 for m in rendered:
99
100 file_name = "%s/%s.%s" % (dir, m.lower(), extension)
101 if not rendered[m]:
102 if quiet == False:
103 print "Not saving %s as it is empty" % file_name
104 else:
105 file = open(file_name, 'w')
106 file.write(rendered[m])
107 file.close()
108 if quiet == False:
109 print "Saved: %s" % file_name
110
111 @staticmethod
112 def _write_split_target(rendered, dir, quiet):
113
114 lines = rendered.splitlines()
115 current_buffer = []
116 for l in lines:
117 if (l.startswith('+++')):
118
119 if dir:
120 path = dir + '/' + l[4:].lower()
121
122 fil = open(path, 'w')
123 buf = '\n'.join(current_buffer)
124
125 obuf = buf
126
127 fil.write(obuf)
128 fil.close()
129
130 if quiet == False:
131 print "Save file to: %s" % path
132
133 current_buffer = []
134 else:
135 current_buffer.append(l)
136
137 @staticmethod
138 def _find_message_by_model_name(messages, model):
139 return next((x for x in messages if x['name'] == model), None)
140
141 @staticmethod
142 def generate(args):
143
144 # Setting defaults
145 if not hasattr(args, 'attic'):
146 args.attic = None
147 if not hasattr(args, 'write_to_file'):
148 args.write_to_file = None
149 if not hasattr(args, 'dest_file'):
150 args.dest_file = None
151 if not hasattr(args, 'dest_extension'):
152 args.dest_extension = None
153 if not hasattr(args, 'output'):
154 args.output = None
155 if not hasattr(args, 'quiet'):
156 args.quiet = True
157
158 # Validating
159 if args.write_to_file == 'single' and args.dest_file is None:
160 raise Exception("[XosGenX] write_to_file option is specified as 'single' but no dest_file is provided")
161 if args.write_to_file == 'model' and args.dest_extension is None:
162 raise Exception("[XosGenX] write_to_file option is specified as 'model' but no dest_extension is provided")
163
164 if args.output is not None and not os.path.isabs(args.output):
165 raise Exception("[XosGenX] The output dir must be an absolute path!")
166 if args.output is not None and not os.path.isdir(args.output):
167 raise Exception("[XosGenX] The output dir must be a directory!")
168
169 if hasattr(args, 'files'):
170 inputs = XOSGenerator._read_input_from_files(args.files)
171 elif hasattr(args, 'inputs'):
172 inputs = args.inputs
173 else:
174 raise Exception("[XosGenX] No inputs provided!")
175
176 template_path = XOSGenerator._get_template(args.target)
177 [template_folder, template_name] = os.path.split(template_path)
178 os_template_loader = jinja2.FileSystemLoader(searchpath=[template_folder])
179 os_template_env = jinja2.Environment(loader=os_template_loader)
180 os_template_env = XOSGenerator._load_jinja2_extensions(os_template_env, args.attic)
181 template = os_template_env.get_template(template_name)
182 context = XOSGenerator._add_context(args)
183
184 parser = plyxproto.ProtobufAnalyzer()
185 ast = parser.parse_string(inputs, debug=0)
186 v = XOSGenerator._attach_parser(ast, args)
187
188 if args.output is not None and args.write_to_file == "model":
189 rendered = {}
190 for i, model in enumerate(v.models):
191 models = {}
192 models[model] = v.models[model]
193 messages = [XOSGenerator._find_message_by_model_name(v.messages, model)]
194 rendered[model] = template.render(
195 {"proto":
196 {
197 'message_table': models,
198 'messages': messages,
199 'message_names': [m['name'] for m in messages]
200 },
201 "context": context,
202 "options": v.options
203 }
204 )
205 XOSGenerator._write_file_per_model(rendered, args.output, args.dest_extension, args.quiet)
206 else:
207 rendered = template.render(
208 {"proto":
209 {
210 'message_table': v.models,
211 'messages': v.messages,
212 'message_names': [m['name'] for m in v.messages]
213 },
214 "context": context,
215 "options": v.options
216 }
217 )
218 if args.output is not None and args.write_to_file == "target":
219 XOSGenerator._write_split_target(rendered, args.output, args.quiet)
220 elif args.output is not None and args.write_to_file == "single":
221 XOSGenerator._write_single_file(rendered, args.output, args.dest_file, args.quiet)
222
223 return rendered