blob: 33e96b1e945146f537cb496368e9124a3a9b7b74 [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
Sapan Bhatiabfb233a2018-02-09 14:53:09 -080029class XOSProcessor:
Matteo Scandolo67654fa2017-06-09 09:33:17 -070030
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
Sapan Bhatiabfb233a2018-02-09 14:53:09 -080081 os_template_env.globals['include_file'] = XOSProcessor._include_file(attic) # Generates a function
82 os_template_env.globals['file_exists'] = XOSProcessor._file_exists(attic) # Generates a function
Matteo Scandolo67654fa2017-06-09 09:33:17 -070083
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
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800171 def process(args, operator = None):
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700172 # Setting defaults
173 if not hasattr(args, 'attic'):
174 args.attic = None
175 if not hasattr(args, 'write_to_file'):
176 args.write_to_file = None
177 if not hasattr(args, 'dest_file'):
178 args.dest_file = None
179 if not hasattr(args, 'dest_extension'):
180 args.dest_extension = None
181 if not hasattr(args, 'output'):
182 args.output = None
183 if not hasattr(args, 'quiet'):
184 args.quiet = True
185
186 # Validating
187 if args.write_to_file == 'single' and args.dest_file is None:
188 raise Exception("[XosGenX] write_to_file option is specified as 'single' but no dest_file is provided")
189 if args.write_to_file == 'model' and args.dest_extension is None:
190 raise Exception("[XosGenX] write_to_file option is specified as 'model' but no dest_extension is provided")
191
192 if args.output is not None and not os.path.isabs(args.output):
193 raise Exception("[XosGenX] The output dir must be an absolute path!")
194 if args.output is not None and not os.path.isdir(args.output):
195 raise Exception("[XosGenX] The output dir must be a directory!")
196
197 if hasattr(args, 'files'):
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800198 inputs = XOSProcessor._read_input_from_files(args.files)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700199 elif hasattr(args, 'inputs'):
200 inputs = args.inputs
201 else:
202 raise Exception("[XosGenX] No inputs provided!")
203
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800204 if not operator:
205 operator = args.target
206 template_path = XOSProcessor._get_template(operator)
207 else:
208 template_path = operator
209
210
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700211 [template_folder, template_name] = os.path.split(template_path)
212 os_template_loader = jinja2.FileSystemLoader(searchpath=[template_folder])
213 os_template_env = jinja2.Environment(loader=os_template_loader)
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800214 os_template_env = XOSProcessor._load_jinja2_extensions(os_template_env, args.attic)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700215 template = os_template_env.get_template(template_name)
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800216 context = XOSProcessor._add_context(args)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700217
218 parser = plyxproto.ProtobufAnalyzer()
Sapan Bhatia85b71012018-01-12 12:11:19 -0500219 try:
220 ast = parser.parse_string(inputs, debug=0)
221 except plyxproto.ParsingError, e:
222 line, start, end = e.error_range
223
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800224 ptr = XOSProcessor._find_last_nonempty_line(inputs, start)
Sapan Bhatia85b71012018-01-12 12:11:19 -0500225
226 if start == 0:
227 beginning = ''
228 else:
229 beginning = inputs[ptr:start-1]
230
231 line_end_char = inputs[start+end:].find('\n')
232 line_end = inputs[line_end_char]
233
234 if e.message:
235 error = e.message
236 else:
237 error = "xproto parsing error"
238
239 print error + "\n" + Fore.YELLOW + "Line %d:"%line + Fore.WHITE
240 print beginning + Fore.YELLOW + inputs[start-1:start+end] + Fore.WHITE + line_end
241 exit(1)
242
243
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800244 v = XOSProcessor._attach_parser(ast, args)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700245
246 if args.output is not None and args.write_to_file == "model":
247 rendered = {}
248 for i, model in enumerate(v.models):
249 models = {}
250 models[model] = v.models[model]
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800251 messages = [XOSProcessor._find_message_by_model_name(v.messages, model)]
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700252
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700253 rendered[model] = template.render(
254 {"proto":
255 {
256 'message_table': models,
257 'messages': messages,
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700258 'policies': v.policies,
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700259 'message_names': [m['name'] for m in messages]
260 },
261 "context": context,
262 "options": v.options
263 }
264 )
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800265 XOSProcessor._write_file_per_model(rendered, args.output, args.dest_extension, args.quiet)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700266 else:
267 rendered = template.render(
268 {"proto":
269 {
270 'message_table': v.models,
271 'messages': v.messages,
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700272 'policies': v.policies,
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700273 'message_names': [m['name'] for m in v.messages]
274 },
275 "context": context,
276 "options": v.options
277 }
278 )
279 if args.output is not None and args.write_to_file == "target":
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800280 XOSProcessor._write_split_target(rendered, args.output, args.quiet)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700281 elif args.output is not None and args.write_to_file == "single":
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800282 XOSProcessor._write_single_file(rendered, args.output, args.dest_file, args.quiet)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700283
Sapan Bhatia4c835602017-07-14 01:13:17 -0400284 return rendered