blob: 650e6881dddd63338c5b9320891456f8a2339fd5 [file] [log] [blame]
Matteo Scandolod2044a42017-08-07 16:08:28 -07001
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16
Matteo Scandolo67654fa2017-06-09 09:33:17 -070017import plyxproto.parser as plyxproto
18import jinja2
19import os
20from xos2jinja import XOS2Jinja
21from proto2xproto import Proto2XProto
22import jinja2_extensions
23import yaml
Sapan Bhatia85b71012018-01-12 12:11:19 -050024from colorama import Fore
Matteo Scandolo67654fa2017-06-09 09:33:17 -070025
26loader = jinja2.PackageLoader(__name__, 'templates')
27env = jinja2.Environment(loader=loader)
28
29class XOSGenerator:
30
31 @staticmethod
32 def _read_input_from_files(files):
33 input = ''
34 for fname in files:
35 with open(fname) as infile:
36 input += infile.read()
37 return input
38
39 @staticmethod
40 def _attach_parser(ast, args):
41 if hasattr(args, 'rev') and args.rev:
42 v = Proto2XProto()
43 ast.accept(v)
Sapan Bhatia4c835602017-07-14 01:13:17 -040044
45 v = XOS2Jinja()
46 ast.accept(v)
Matteo Scandolo67654fa2017-06-09 09:33:17 -070047 return v
48
49 @staticmethod
50 def _get_template(target):
51 if not os.path.isabs(target):
52 return os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/targets/' + target)
53 return target
54
55 @staticmethod
56 def _file_exists(attic):
57 # NOTE this method can be used in the jinja template
58 def file_exists2(name):
59 if attic is not None:
60 path = attic + '/' + name
61 else:
62 path = name
63 return (os.path.exists(path))
64
65 return file_exists2
66
67 @staticmethod
68 def _include_file(attic):
69 # NOTE this method can be used in the jinja template
70 def include_file2(name):
71 if attic is not None:
72 path = attic + '/' + name
73 else:
74 path = name
75 return open(path).read()
76 return include_file2
77
78 @staticmethod
79 def _load_jinja2_extensions(os_template_env, attic):
80
81 os_template_env.globals['include_file'] = XOSGenerator._include_file(attic) # Generates a function
82 os_template_env.globals['file_exists'] = XOSGenerator._file_exists(attic) # Generates a function
83
84 os_template_env.filters['yaml'] = yaml.dump
85 for f in dir(jinja2_extensions):
86 if f.startswith('xproto'):
87 os_template_env.globals[f] = getattr(jinja2_extensions, f)
88 return os_template_env
89
90 @staticmethod
91 def _add_context(args):
92 if not hasattr(args, 'kv') or not args.kv:
93 return
94 try:
95 context = {}
96 for s in args.kv.split(','):
97 k, val = s.split(':')
98 context[k] = val
99 return context
100 except Exception, e:
101 print e.message
102
103 @staticmethod
104 def _write_single_file(rendered, dir, dest_file, quiet):
105
106 file_name = "%s/%s" % (dir, dest_file)
107 file = open(file_name, 'w')
108 file.write(rendered)
109 file.close()
110 if quiet == False:
111 print "Saved: %s" % file_name
112
113 @staticmethod
114 def _write_file_per_model(rendered, dir, extension, quiet):
115 for m in rendered:
116
117 file_name = "%s/%s.%s" % (dir, m.lower(), extension)
118 if not rendered[m]:
119 if quiet == False:
120 print "Not saving %s as it is empty" % file_name
121 else:
122 file = open(file_name, 'w')
123 file.write(rendered[m])
124 file.close()
125 if quiet == False:
126 print "Saved: %s" % file_name
127
128 @staticmethod
129 def _write_split_target(rendered, dir, quiet):
130
131 lines = rendered.splitlines()
132 current_buffer = []
133 for l in lines:
134 if (l.startswith('+++')):
135
136 if dir:
137 path = dir + '/' + l[4:].lower()
138
139 fil = open(path, 'w')
140 buf = '\n'.join(current_buffer)
141
142 obuf = buf
143
144 fil.write(obuf)
145 fil.close()
146
147 if quiet == False:
148 print "Save file to: %s" % path
149
150 current_buffer = []
151 else:
152 current_buffer.append(l)
153
154 @staticmethod
155 def _find_message_by_model_name(messages, model):
156 return next((x for x in messages if x['name'] == model), None)
157
158 @staticmethod
Sapan Bhatia85b71012018-01-12 12:11:19 -0500159 def _find_last_nonempty_line(text, pointer):
160 ne_pointer = pointer
161 found = False
162 while ne_pointer!=0 and not found:
163 ne_pointer = text[:(ne_pointer-1)].rfind('\n')
164 if ne_pointer<0: ne_pointer = 0
165 if text[ne_pointer-1]!='\n':
166 found = True
167
168 return ne_pointer
169
170 @staticmethod
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700171 def generate(args):
172
173 # Setting defaults
174 if not hasattr(args, 'attic'):
175 args.attic = None
176 if not hasattr(args, 'write_to_file'):
177 args.write_to_file = None
178 if not hasattr(args, 'dest_file'):
179 args.dest_file = None
180 if not hasattr(args, 'dest_extension'):
181 args.dest_extension = None
182 if not hasattr(args, 'output'):
183 args.output = None
184 if not hasattr(args, 'quiet'):
185 args.quiet = True
186
187 # Validating
188 if args.write_to_file == 'single' and args.dest_file is None:
189 raise Exception("[XosGenX] write_to_file option is specified as 'single' but no dest_file is provided")
190 if args.write_to_file == 'model' and args.dest_extension is None:
191 raise Exception("[XosGenX] write_to_file option is specified as 'model' but no dest_extension is provided")
192
193 if args.output is not None and not os.path.isabs(args.output):
194 raise Exception("[XosGenX] The output dir must be an absolute path!")
195 if args.output is not None and not os.path.isdir(args.output):
196 raise Exception("[XosGenX] The output dir must be a directory!")
197
198 if hasattr(args, 'files'):
199 inputs = XOSGenerator._read_input_from_files(args.files)
200 elif hasattr(args, 'inputs'):
201 inputs = args.inputs
202 else:
203 raise Exception("[XosGenX] No inputs provided!")
204
205 template_path = XOSGenerator._get_template(args.target)
206 [template_folder, template_name] = os.path.split(template_path)
207 os_template_loader = jinja2.FileSystemLoader(searchpath=[template_folder])
208 os_template_env = jinja2.Environment(loader=os_template_loader)
209 os_template_env = XOSGenerator._load_jinja2_extensions(os_template_env, args.attic)
210 template = os_template_env.get_template(template_name)
211 context = XOSGenerator._add_context(args)
212
213 parser = plyxproto.ProtobufAnalyzer()
Sapan Bhatia85b71012018-01-12 12:11:19 -0500214 try:
215 ast = parser.parse_string(inputs, debug=0)
216 except plyxproto.ParsingError, e:
217 line, start, end = e.error_range
218
219 ptr = XOSGenerator._find_last_nonempty_line(inputs, start)
220
221 if start == 0:
222 beginning = ''
223 else:
224 beginning = inputs[ptr:start-1]
225
226 line_end_char = inputs[start+end:].find('\n')
227 line_end = inputs[line_end_char]
228
229 if e.message:
230 error = e.message
231 else:
232 error = "xproto parsing error"
233
234 print error + "\n" + Fore.YELLOW + "Line %d:"%line + Fore.WHITE
235 print beginning + Fore.YELLOW + inputs[start-1:start+end] + Fore.WHITE + line_end
236 exit(1)
237
238
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700239 v = XOSGenerator._attach_parser(ast, args)
240
241 if args.output is not None and args.write_to_file == "model":
242 rendered = {}
243 for i, model in enumerate(v.models):
244 models = {}
245 models[model] = v.models[model]
246 messages = [XOSGenerator._find_message_by_model_name(v.messages, model)]
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700247
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700248 rendered[model] = template.render(
249 {"proto":
250 {
251 'message_table': models,
252 'messages': messages,
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700253 'policies': v.policies,
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700254 'message_names': [m['name'] for m in messages]
255 },
256 "context": context,
257 "options": v.options
258 }
259 )
260 XOSGenerator._write_file_per_model(rendered, args.output, args.dest_extension, args.quiet)
261 else:
262 rendered = template.render(
263 {"proto":
264 {
265 'message_table': v.models,
266 'messages': v.messages,
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700267 'policies': v.policies,
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700268 'message_names': [m['name'] for m in v.messages]
269 },
270 "context": context,
271 "options": v.options
272 }
273 )
274 if args.output is not None and args.write_to_file == "target":
275 XOSGenerator._write_split_target(rendered, args.output, args.quiet)
276 elif args.output is not None and args.write_to_file == "single":
277 XOSGenerator._write_single_file(rendered, args.output, args.dest_file, args.quiet)
278
Sapan Bhatia4c835602017-07-14 01:13:17 -0400279 return rendered