blob: cc00742cc666934dada55ffc16483b7fe0883150 [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
Scott Bakera33ccb02018-01-26 13:03:28 -0800114 def _write_file_per_model(rendered, dir, suffix, quiet):
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700115 for m in rendered:
Scott Bakera33ccb02018-01-26 13:03:28 -0800116 file_name = "%s/%s%s" % (dir, m.lower(), suffix)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700117 if not rendered[m]:
118 if quiet == False:
119 print "Not saving %s as it is empty" % file_name
120 else:
121 file = open(file_name, 'w')
122 file.write(rendered[m])
123 file.close()
124 if quiet == False:
125 print "Saved: %s" % file_name
126
127 @staticmethod
128 def _write_split_target(rendered, dir, quiet):
129
130 lines = rendered.splitlines()
131 current_buffer = []
132 for l in lines:
133 if (l.startswith('+++')):
134
135 if dir:
136 path = dir + '/' + l[4:].lower()
137
138 fil = open(path, 'w')
139 buf = '\n'.join(current_buffer)
140
141 obuf = buf
142
143 fil.write(obuf)
144 fil.close()
145
146 if quiet == False:
147 print "Save file to: %s" % path
148
149 current_buffer = []
150 else:
151 current_buffer.append(l)
152
153 @staticmethod
154 def _find_message_by_model_name(messages, model):
155 return next((x for x in messages if x['name'] == model), None)
156
157 @staticmethod
Sapan Bhatia85b71012018-01-12 12:11:19 -0500158 def _find_last_nonempty_line(text, pointer):
159 ne_pointer = pointer
160 found = False
161 while ne_pointer!=0 and not found:
162 ne_pointer = text[:(ne_pointer-1)].rfind('\n')
163 if ne_pointer<0: ne_pointer = 0
164 if text[ne_pointer-1]!='\n':
165 found = True
166
167 return ne_pointer
168
169 @staticmethod
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800170 def process(args, operator = None):
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700171 # Setting defaults
172 if not hasattr(args, 'attic'):
173 args.attic = None
174 if not hasattr(args, 'write_to_file'):
175 args.write_to_file = None
176 if not hasattr(args, 'dest_file'):
177 args.dest_file = None
178 if not hasattr(args, 'dest_extension'):
179 args.dest_extension = None
180 if not hasattr(args, 'output'):
181 args.output = None
182 if not hasattr(args, 'quiet'):
183 args.quiet = True
184
185 # Validating
186 if args.write_to_file == 'single' and args.dest_file is None:
187 raise Exception("[XosGenX] write_to_file option is specified as 'single' but no dest_file is provided")
Scott Bakera33ccb02018-01-26 13:03:28 -0800188 if args.write_to_file == 'model' and (args.dest_extension is None):
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700189 raise Exception("[XosGenX] write_to_file option is specified as 'model' but no dest_extension is provided")
190
191 if args.output is not None and not os.path.isabs(args.output):
192 raise Exception("[XosGenX] The output dir must be an absolute path!")
193 if args.output is not None and not os.path.isdir(args.output):
194 raise Exception("[XosGenX] The output dir must be a directory!")
195
196 if hasattr(args, 'files'):
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800197 inputs = XOSProcessor._read_input_from_files(args.files)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700198 elif hasattr(args, 'inputs'):
199 inputs = args.inputs
200 else:
201 raise Exception("[XosGenX] No inputs provided!")
202
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800203 if not operator:
204 operator = args.target
205 template_path = XOSProcessor._get_template(operator)
206 else:
207 template_path = operator
208
209
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700210 [template_folder, template_name] = os.path.split(template_path)
211 os_template_loader = jinja2.FileSystemLoader(searchpath=[template_folder])
212 os_template_env = jinja2.Environment(loader=os_template_loader)
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800213 os_template_env = XOSProcessor._load_jinja2_extensions(os_template_env, args.attic)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700214 template = os_template_env.get_template(template_name)
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800215 context = XOSProcessor._add_context(args)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700216
217 parser = plyxproto.ProtobufAnalyzer()
Sapan Bhatia85b71012018-01-12 12:11:19 -0500218 try:
219 ast = parser.parse_string(inputs, debug=0)
220 except plyxproto.ParsingError, e:
221 line, start, end = e.error_range
222
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800223 ptr = XOSProcessor._find_last_nonempty_line(inputs, start)
Sapan Bhatia85b71012018-01-12 12:11:19 -0500224
225 if start == 0:
226 beginning = ''
227 else:
228 beginning = inputs[ptr:start-1]
229
230 line_end_char = inputs[start+end:].find('\n')
231 line_end = inputs[line_end_char]
232
233 if e.message:
234 error = e.message
235 else:
236 error = "xproto parsing error"
237
238 print error + "\n" + Fore.YELLOW + "Line %d:"%line + Fore.WHITE
239 print beginning + Fore.YELLOW + inputs[start-1:start+end] + Fore.WHITE + line_end
240 exit(1)
241
242
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800243 v = XOSProcessor._attach_parser(ast, args)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700244
245 if args.output is not None and args.write_to_file == "model":
246 rendered = {}
247 for i, model in enumerate(v.models):
248 models = {}
249 models[model] = v.models[model]
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800250 messages = [XOSProcessor._find_message_by_model_name(v.messages, model)]
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700251
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700252 rendered[model] = template.render(
253 {"proto":
254 {
255 'message_table': models,
256 'messages': messages,
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700257 'policies': v.policies,
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700258 'message_names': [m['name'] for m in messages]
259 },
260 "context": context,
261 "options": v.options
262 }
263 )
Scott Bakera33ccb02018-01-26 13:03:28 -0800264 if (str(v.options.get("legacy", "false")).strip('"').lower() == "true"):
265 suffix = "_decl." + args.dest_extension
266 else:
267 suffix = "." + args.dest_extension
268 XOSProcessor._write_file_per_model(rendered, args.output, suffix, args.quiet)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700269 else:
270 rendered = template.render(
271 {"proto":
272 {
273 'message_table': v.models,
274 'messages': v.messages,
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700275 'policies': v.policies,
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700276 'message_names': [m['name'] for m in v.messages]
277 },
278 "context": context,
279 "options": v.options
280 }
281 )
282 if args.output is not None and args.write_to_file == "target":
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800283 XOSProcessor._write_split_target(rendered, args.output, args.quiet)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700284 elif args.output is not None and args.write_to_file == "single":
Sapan Bhatiabfb233a2018-02-09 14:53:09 -0800285 XOSProcessor._write_single_file(rendered, args.output, args.dest_file, args.quiet)
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700286
Sapan Bhatia4c835602017-07-14 01:13:17 -0400287 return rendered