blob: 1fd6d57b5ada42d57b5568ffbdc91c94ce95c9d0 [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
24
25loader = jinja2.PackageLoader(__name__, 'templates')
26env = jinja2.Environment(loader=loader)
27
28class XOSGenerator:
29
30 @staticmethod
31 def _read_input_from_files(files):
32 input = ''
33 for fname in files:
34 with open(fname) as infile:
35 input += infile.read()
36 return input
37
38 @staticmethod
39 def _attach_parser(ast, args):
40 if hasattr(args, 'rev') and args.rev:
41 v = Proto2XProto()
42 ast.accept(v)
Sapan Bhatia4c835602017-07-14 01:13:17 -040043
44 v = XOS2Jinja()
45 ast.accept(v)
Matteo Scandolo67654fa2017-06-09 09:33:17 -070046 return v
47
48 @staticmethod
49 def _get_template(target):
50 if not os.path.isabs(target):
51 return os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + '/targets/' + target)
52 return target
53
54 @staticmethod
55 def _file_exists(attic):
56 # NOTE this method can be used in the jinja template
57 def file_exists2(name):
58 if attic is not None:
59 path = attic + '/' + name
60 else:
61 path = name
62 return (os.path.exists(path))
63
64 return file_exists2
65
66 @staticmethod
67 def _include_file(attic):
68 # NOTE this method can be used in the jinja template
69 def include_file2(name):
70 if attic is not None:
71 path = attic + '/' + name
72 else:
73 path = name
74 return open(path).read()
75 return include_file2
76
77 @staticmethod
78 def _load_jinja2_extensions(os_template_env, attic):
79
80 os_template_env.globals['include_file'] = XOSGenerator._include_file(attic) # Generates a function
81 os_template_env.globals['file_exists'] = XOSGenerator._file_exists(attic) # Generates a function
82
83 os_template_env.filters['yaml'] = yaml.dump
84 for f in dir(jinja2_extensions):
85 if f.startswith('xproto'):
86 os_template_env.globals[f] = getattr(jinja2_extensions, f)
87 return os_template_env
88
89 @staticmethod
90 def _add_context(args):
91 if not hasattr(args, 'kv') or not args.kv:
92 return
93 try:
94 context = {}
95 for s in args.kv.split(','):
96 k, val = s.split(':')
97 context[k] = val
98 return context
99 except Exception, e:
100 print e.message
101
102 @staticmethod
103 def _write_single_file(rendered, dir, dest_file, quiet):
104
105 file_name = "%s/%s" % (dir, dest_file)
106 file = open(file_name, 'w')
107 file.write(rendered)
108 file.close()
109 if quiet == False:
110 print "Saved: %s" % file_name
111
112 @staticmethod
113 def _write_file_per_model(rendered, dir, extension, quiet):
114 for m in rendered:
115
116 file_name = "%s/%s.%s" % (dir, m.lower(), extension)
117 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
158 def generate(args):
159
160 # Setting defaults
161 if not hasattr(args, 'attic'):
162 args.attic = None
163 if not hasattr(args, 'write_to_file'):
164 args.write_to_file = None
165 if not hasattr(args, 'dest_file'):
166 args.dest_file = None
167 if not hasattr(args, 'dest_extension'):
168 args.dest_extension = None
169 if not hasattr(args, 'output'):
170 args.output = None
171 if not hasattr(args, 'quiet'):
172 args.quiet = True
173
174 # Validating
175 if args.write_to_file == 'single' and args.dest_file is None:
176 raise Exception("[XosGenX] write_to_file option is specified as 'single' but no dest_file is provided")
177 if args.write_to_file == 'model' and args.dest_extension is None:
178 raise Exception("[XosGenX] write_to_file option is specified as 'model' but no dest_extension is provided")
179
180 if args.output is not None and not os.path.isabs(args.output):
181 raise Exception("[XosGenX] The output dir must be an absolute path!")
182 if args.output is not None and not os.path.isdir(args.output):
183 raise Exception("[XosGenX] The output dir must be a directory!")
184
185 if hasattr(args, 'files'):
186 inputs = XOSGenerator._read_input_from_files(args.files)
187 elif hasattr(args, 'inputs'):
188 inputs = args.inputs
189 else:
190 raise Exception("[XosGenX] No inputs provided!")
191
192 template_path = XOSGenerator._get_template(args.target)
193 [template_folder, template_name] = os.path.split(template_path)
194 os_template_loader = jinja2.FileSystemLoader(searchpath=[template_folder])
195 os_template_env = jinja2.Environment(loader=os_template_loader)
196 os_template_env = XOSGenerator._load_jinja2_extensions(os_template_env, args.attic)
197 template = os_template_env.get_template(template_name)
198 context = XOSGenerator._add_context(args)
199
200 parser = plyxproto.ProtobufAnalyzer()
201 ast = parser.parse_string(inputs, debug=0)
202 v = XOSGenerator._attach_parser(ast, args)
203
204 if args.output is not None and args.write_to_file == "model":
205 rendered = {}
206 for i, model in enumerate(v.models):
207 models = {}
208 models[model] = v.models[model]
209 messages = [XOSGenerator._find_message_by_model_name(v.messages, model)]
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700210
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700211 rendered[model] = template.render(
212 {"proto":
213 {
214 'message_table': models,
215 'messages': messages,
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700216 'policies': v.policies,
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700217 'message_names': [m['name'] for m in messages]
218 },
219 "context": context,
220 "options": v.options
221 }
222 )
223 XOSGenerator._write_file_per_model(rendered, args.output, args.dest_extension, args.quiet)
224 else:
225 rendered = template.render(
226 {"proto":
227 {
228 'message_table': v.models,
229 'messages': v.messages,
Sapan Bhatiadb183c22017-06-23 02:47:42 -0700230 'policies': v.policies,
Matteo Scandolo67654fa2017-06-09 09:33:17 -0700231 'message_names': [m['name'] for m in v.messages]
232 },
233 "context": context,
234 "options": v.options
235 }
236 )
237 if args.output is not None and args.write_to_file == "target":
238 XOSGenerator._write_split_target(rendered, args.output, args.quiet)
239 elif args.output is not None and args.write_to_file == "single":
240 XOSGenerator._write_single_file(rendered, args.output, args.dest_file, args.quiet)
241
Sapan Bhatia4c835602017-07-14 01:13:17 -0400242 return rendered