blob: fc887a637e2e3201db0c0e2fb2c42be156c2476d [file] [log] [blame]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -05001#!/usr/bin/env python
2#
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -05003# Copyright 2017 the original author or authors.
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -05004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18"""protoc plugin to convert a protobuf schema to a yang schema
19
20 - basic support for message, fields. enumeration, service, method
21
22 - yang semantic rules needs to be implemented
23
24 - to run this plugin :
25
26 $ python -m grpc.tools.protoc -I.
27 --plugin=protoc-gen-custom=./proto2yang.py --custom_out=. <proto file>.proto
28
29 - the above will produce a ietf-<proto file>.yang file formatted for yang
30
31 - two examples of proto that can be used in the same directory are
32 yang.proto and addressbook.proto
33
34"""
35
36import sys
37
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050038from google.protobuf.compiler import plugin_pb2 as plugin
Khen Nursimulub4e71472017-01-06 18:05:47 -050039from google.protobuf.descriptor_pb2 import DescriptorProto, \
40 FieldDescriptorProto
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050041from descriptor_parser import DescriptorParser
Khen Nursimulu7626ce12016-12-21 11:51:46 -050042import copy
43import yang_options_pb2
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050044
45from google.protobuf.descriptor import FieldDescriptor
46
Khen Nursimulu9b9f1ad2017-01-10 15:43:32 -050047import jinja2
48env = jinja2.Environment(extensions=["jinja2.ext.do",], trim_blocks=True, lstrip_blocks=True)
49
50template_yang = env.from_string("""
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050051module ietf-{{ module.name }} {
52
53 {% macro set_module_prefix(type) %}
Khen Nursimulu9b9f1ad2017-01-10 15:43:32 -050054 {% set found = [] %}
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050055 {% for t in module.data_types %}
56 {% if t.type == type %}
57 {% if t.module != module.name %} {{ t.module }}:{{ type }};
58 {% else %} {{ type }};
59 {% endif %}
Khen Nursimulu9b9f1ad2017-01-10 15:43:32 -050060 {% do found.append(1) %}
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050061 {% endif %}
62 {% if loop.last %}
63 {% if not found %} {{ type }}; {% endif %}
64 {% endif %}
65 {% endfor %}
66 {% endmacro %}
67
Khen Nursimulub4e71472017-01-06 18:05:47 -050068
69 {% macro process_oneofs(oneofs, ref_msgs) %}
70
71 {% for key, value in oneofs.iteritems() %}
72 choice {{ key }} {
73 {% for field in value %}
74 case {{ field.name }} {
75 {% if field.type_ref %}
76 {% for dict_item in ref_msgs %}
77 {% if dict_item.name == field.type %}
78 container {{ field.name }} {
79 uses {{ set_module_prefix(field.type) }}
80 description
81 "{{ field.description }}";
82 {% endif %}
83 {% endfor %}
84 }
85 {% else %}
86 leaf {{ field.name }} {
87 {% if field.type == "decimal64" %}
88 type {{ field.type }} {
89 fraction-digits 5;
90 }
91 {% else %}
92 type {{ set_module_prefix(field.type) }}
93 {% endif %}
94 description
95 "{{ field.description }}";
96 }
97 {% endif %}
98 }
99 {% endfor %}
100 }
101 {% endfor %}
102 {% endmacro %}
103
104
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500105 namespace "urn:opencord:params:xml:ns:voltha:ietf-{{ module.name }}";
106 prefix {{ module.name }};
107
108 {% for imp in module.imports %}
109 import ietf-{{ imp.name }} { prefix {{ imp.name }} ; }
110 {% endfor %}
111
112 organization "CORD";
113 contact
114 " Any name";
115
116 description
117 "{{ module.description }}";
118
119 revision "2016-11-15" {
120 description "Initial revision.";
121 reference "reference";
122 }
123
124 {% for enum in module.enums %}
125 typedef {{ enum.name }} {
126 type enumeration {
127 {% for v in enum.value %}
128 enum {{ v.name }} {
129 description "{{ v.description }}";
130 }
131 {% endfor %}
132 }
133 description
134 "{{ enum.description }}";
135 }
136 {% endfor %}
137
138 {% for message in module.messages recursive %}
139 {% if message.name in module.referred_messages %}
140 grouping {{ message.name }} {
141 {% else %}
142 container {{ message.name }} {
143 {% endif %}
144 description
145 "{{ message.description }}";
146 {% for field in message.fields %}
147 {% if field.type_ref %}
148 {% for dict_item in module.referred_messages_with_keys %}
149 {% if dict_item.name == field.type %}
150 {% if not field.repeated %}
151 container {{ field.name }} {
152 {% else %}
153 list {{ field.name }} {
154 key "{{ dict_item.key }}";
155 {% if not field.repeated %}
156 max-elements 1;
157 {% endif %}
158 {% endif %}
159 uses {{ set_module_prefix(field.type) }}
160 description
161 "{{ field.description }}";
162 }
163 {% endif %}
164 {% endfor %}
165 {% elif field.repeated %}
166 list {{ field.name }} {
167 key "{{ field.name }}";
168 leaf {{ field.name }} {
169 {% if field.type == "decimal64" %}
170 type {{ field.type }} {
171 fraction-digits 5;
172 }
173 {% else %}
174 type {{ set_module_prefix(field.type) }}
175 {% endif %}
176 description
177 "{{ field.description }}";
178 }
179 description
180 "{{ field.description }}";
181 }
182 {% else %}
183 leaf {{ field.name }} {
184 {% if field.type == "decimal64" %}
185 type {{ field.type }} {
186 fraction-digits 5;
187 }
188 {% else %}
189 type {{ set_module_prefix(field.type) }}
190 {% endif %}
191 description
192 "{{ field.description }}";
193 }
194 {% endif %}
195
196 {% endfor %}
Khen Nursimulub4e71472017-01-06 18:05:47 -0500197
198 {% if message.oneofs %}
199 {{ process_oneofs(message.oneofs, module.referred_messages_with_keys) }}
200 {% endif %}
201
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500202 {% for enum_type in message.enums %}
203 typedef {{ enum_type.name }} {
204 type enumeration {
205 {% for v in enum_type.value %}
206 enum {{ v.name }} {
207 description "{{ v.description }}";
208 }
209 {% endfor %}
210 }
211 description
212 "{{ enum_type.description }}";
213 }
214
215 {% endfor %}
216 {% if message.messages %}
217 {{ loop (message.messages)|indent(4, false) }}
218 {% endif %}
219 }
220
221 {% endfor %}
222 {% for service in module.services %}
223 {% if service.description %}
224 /* {{ service.description }}" */
225 {% endif %}
226 {% for method in service.methods %}
227 rpc {{ service.service }}-{{ method.method }} {
228 description
229 "{{ method.description }}";
230 {% if method.input %}
231 input {
232 {% if method.input_ref %}
233 uses {{ set_module_prefix(method.input) }}
234 {% else %}
235 leaf {{ method.input }} {
236 type {{ set_module_prefix(method.input) }}
237 }
238 {% endif %}
239 }
240 {% endif %}
241 {% if method.output %}
242 output {
243 {% if method.output_ref %}
244 uses {{ set_module_prefix(method.output) }}
245 {% else %}
246 leaf {{ method.output }} {
247 type {{ set_module_prefix(method.output) }}
248 }
249 {% endif %}
250 }
251 {% endif %}
252 }
253
254 {% endfor %}
255
256 {% endfor %}
257}
Khen Nursimulu9b9f1ad2017-01-10 15:43:32 -0500258""")
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500259
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500260
261def traverse_field_options(fields, prefix):
262 field_options = []
263 for field in fields:
264 assert isinstance(field, FieldDescriptorProto)
265 full_name = prefix + '-' + field.name
266 option = None
267 if field.type == FieldDescriptor.TYPE_MESSAGE and field.label != \
268 FieldDescriptor.LABEL_REPEATED:
269 if field.options:
270 for fd, val in field.options.ListFields():
271 if fd.full_name == 'voltha.yang_inline_node':
272 field_options.append(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500273 {'name': full_name,
274 'option': fd.full_name,
275 'proto_name': val.id,
276 'proto_type': val.type
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500277 }
278 )
279 return field_options
280
281
282def traverse_message_options(message_types, prefix):
Khen Nursimulub4e71472017-01-06 18:05:47 -0500283 message_options = []
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500284 for message_type in message_types:
285 assert isinstance(message_type, DescriptorProto)
286 full_name = prefix + '-' + message_type.name
287 option_rules = []
288
289 options = message_type.options
290 if options:
291 for fd, val in options.ListFields():
292 if fd.full_name in ['voltha.yang_child_rule',
293 'voltha.yang_message_rule']:
294 option_rules.append({
Khen Nursimulub4e71472017-01-06 18:05:47 -0500295 'name': fd.full_name,
296 'value': val
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500297 })
298
299 # parse fields for options
300 field_options = traverse_field_options(message_type.field,
301 full_name)
302
303 # parse nested messages
304 nested_messages_options = []
305 nested = message_type.nested_type
306 if nested:
307 nested_messages_options = traverse_message_options(nested,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500308 full_name)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500309
310 if option_rules or nested_messages_options or field_options:
311 message_options.append(
312 {
313 'name': full_name,
314 'options': option_rules,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500315 'field_options': field_options,
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500316 'nested_options': nested_messages_options,
317 }
318 )
319 return message_options
320
321
322def get_message_options(name, options):
323 result = None
324 for opt in options:
325 if opt['name'] == name:
326 return opt['options']
327 if opt['nested_options']:
328 result = get_message_options(name, opt['nested_options'])
329 if result:
330 return result
331
Khen Nursimulub4e71472017-01-06 18:05:47 -0500332
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500333def get_field_options(name, options):
334 result = None
335 for opt in options:
336 if opt['field_options']:
337 for field_opt in opt['field_options']:
338 if field_opt['name'] == name:
339 result = field_opt
340 if opt['nested_options']:
341 result = get_field_options(name, opt['nested_options'])
342 if result:
343 return result
344
345
346def traverse_options(proto_file):
347 package = proto_file.name
348 prefix = package.replace('.proto', '')
349 if proto_file.message_type:
350 message_options = traverse_message_options(proto_file.message_type,
351 prefix)
352 return message_options
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500353
354
355def traverse_messages(message_types, prefix, referenced_messages):
356 messages = []
357 for message_type in message_types:
358 assert message_type['_type'] == 'google.protobuf.DescriptorProto'
359
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500360 full_name = prefix + '-' + message_type['name']
361 name = message_type['name']
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500362
363 # parse the fields
Khen Nursimulub4e71472017-01-06 18:05:47 -0500364 oneofs, fields = traverse_fields(message_type.get('field', []),
365 full_name, referenced_messages)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500366
367 # parse the enums
368 enums = traverse_enums(message_type.get('enum_type', []), full_name)
369
370 # parse nested messages
371 nested = message_type.get('nested_type', [])
372 nested_messages = traverse_messages(nested, full_name,
373 referenced_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500374
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500375 messages.append(
376 {
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500377 'full_name': full_name,
378 'name': name,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500379 'fields': fields,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500380 'oneofs': oneofs,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500381 'enums': enums,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500382 'messages': nested_messages,
383 'description': remove_unsupported_characters(
384 message_type.get('_description', '')),
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500385 }
386 )
387 return messages
388
389
390def traverse_fields(fields_desc, prefix, referenced_messages):
391 fields = []
Khen Nursimulub4e71472017-01-06 18:05:47 -0500392 oneofs = {}
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500393 for field in fields_desc:
Khen Nursimulub4e71472017-01-06 18:05:47 -0500394 # if field.get('oneof_index', None) >= 0:
395 # print '{},{}'.format(field.get('name', ''), field.get('number'))
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500396 assert field['_type'] == 'google.protobuf.FieldDescriptorProto'
397 yang_base_type = is_base_type(field['type'])
398 _type = get_yang_type(field)
399 if not yang_base_type:
400 referenced_messages.append(_type)
401 # add to referred messages also if it is an enumeration type
402 if is_enumeration(field['type']):
403 referenced_messages.append(_type)
404
Khen Nursimulub4e71472017-01-06 18:05:47 -0500405 if field.get('oneof_index', None) >= 0:
406 # Oneof fields
407 key = ''.join(['choice_', str(field['oneof_index'])])
408 if not oneofs.has_key(key):
409 oneofs[key] = []
410 oneofs[key].append(
411 {
412 'full_name': prefix + '-' + field.get('name', ''),
413 'oneof_index': field.get('oneof_index', None),
414 'name': field.get('name', ''),
415 'label': field.get('label', ''),
416 'repeated': field[
417 'label'] == FieldDescriptor.LABEL_REPEATED,
418 'number': field.get('number', ''),
419 'options': field.get('options', ''),
420 'type_name': field.get('type_name', ''),
421 'type': _type,
422 'type_ref': not yang_base_type,
423 'description': remove_unsupported_characters(field.get(
424 '_description', ''))
425 }
426 )
427 else:
428 fields.append(
429 {
430 'full_name': prefix + '-' + field.get('name', ''),
431 'name': field.get('name', ''),
432 'label': field.get('label', ''),
433 'repeated': field[
434 'label'] == FieldDescriptor.LABEL_REPEATED,
435 'number': field.get('number', ''),
436 'options': field.get('options', ''),
437 'type_name': field.get('type_name', ''),
438 'type': _type,
439 'type_ref': not yang_base_type,
440 'description': remove_unsupported_characters(field.get(
441 '_description', ''))
442 }
443 )
444 # print oneofs
445 return oneofs, fields
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500446
447
448def traverse_enums(enums_desc, prefix):
449 enums = []
450 for enum in enums_desc:
451 assert enum['_type'] == 'google.protobuf.EnumDescriptorProto'
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500452 full_name = prefix + '-' + enum.get('name', '')
453 name = enum.get('name', '')
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500454 enums.append(
455 {
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500456 'full_name': full_name,
457 'name': name,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500458 'value': enum.get('value', ''),
459 'description': remove_unsupported_characters(enum.get(
460 '_description', ''))
461 }
462 )
463 return enums
464
465
466def traverse_services(service_desc, referenced_messages):
467 services = []
468 for service in service_desc:
469 methods = []
470 for method in service.get('method', []):
471 assert method['_type'] == 'google.protobuf.MethodDescriptorProto'
472
473 input_name = method.get('input_type')
474 input_ref = False
475 if not is_base_type(input_name):
476 input_name = remove_first_character_if_match(input_name, '.')
477 # input_name = input_name.replace(".", "-")
478 input_name = input_name.split('.')[-1]
479 referenced_messages.append(input_name)
480 input_ref = True
481
482 output_name = method.get('output_type')
483 output_ref = False
484 if not is_base_type(output_name):
485 output_name = remove_first_character_if_match(output_name, '.')
486 # output_name = output_name.replace(".", "-")
487 output_name = output_name.split('.')[-1]
488 referenced_messages.append(output_name)
489 output_ref = True
490
491 methods.append(
492 {
493 'method': method.get('name', ''),
494 'input': input_name,
495 'input_ref': input_ref,
496 'output': output_name,
497 'output_ref': output_ref,
498 'description': remove_unsupported_characters(method.get(
499 '_description', '')),
500 'server_streaming': method.get('server_streaming',
501 False) == True
502 }
503 )
504 services.append(
505 {
506 'service': service.get('name', ''),
507 'methods': methods,
508 'description': remove_unsupported_characters(service.get(
509 '_description', '')),
510 }
511 )
512 return services
513
514
515def rchop(thestring, ending):
516 if thestring.endswith(ending):
517 return thestring[:-len(ending)]
518 return thestring
519
520
521def traverse_desc(descriptor):
522 referenced_messages = []
523 name = rchop(descriptor.get('name', ''), '.proto')
524 package = descriptor.get('package', '')
525 description = descriptor.get('_description', '')
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500526 messages = traverse_messages(descriptor.get('message_type', []),
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500527 name, referenced_messages)
528 enums = traverse_enums(descriptor.get('enum_type', []), name)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500529 services = traverse_services(descriptor.get('service', []),
530 referenced_messages)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500531
532 # Get a list of type definitions (messages, enums) defined in this
533 # descriptor
534 defined_types = [m['name'].split('/')[-1] for m in messages] + \
Khen Nursimulub4e71472017-01-06 18:05:47 -0500535 [e['name'].split('/')[-1] for e in enums]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500536
537 data = {
538 'name': name.split('/')[-1],
539 'package': package,
540 'description': description,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500541 'messages': messages,
542 'enums': enums,
543 'services': services,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500544 'defined_types': defined_types,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500545 'referenced_messages': list(set(referenced_messages)),
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500546 }
547 return data
548
549
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500550# For now, annotations are added to first level messages only.
551# Therefore, at this time no need to tackle nested messages.
552def move_message_to_parent_level(message, messages, enums):
553 new_message = []
554 new_enum = copy.deepcopy(enums)
555 for msg in messages:
556 if msg['full_name'] == message['full_name']:
557 # Move all sub messages and enums to top level
558 if msg['messages']:
559 new_message = new_message + copy.deepcopy(msg['messages'])
560 if msg['enums']:
561 new_enum = new_enum + copy.deepcopy(msg['enums'])
562
563 # if the message has some fields then enclose them in a container
564 if msg['fields']:
565 new_message.append(
566 {
567 'full_name': msg['full_name'],
568 'name': msg['name'],
569 'fields': msg['fields'],
Khen Nursimulub4e71472017-01-06 18:05:47 -0500570 'oneofs': msg['oneofs'],
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500571 'description': msg['description'],
572 'messages': [],
573 'enums': []
574 }
575 )
576 else:
577 new_message.append(msg)
578
579 return new_message, new_enum
580
581
582def update_messages_per_annotations_rule(options, messages, enums):
583 new_messages = messages
584 new_enums = enums
585 # Used when a message needs to exist both as a type and a container
586 duplicate_messages = []
587 for message in messages:
588 opts = get_message_options(message['full_name'], options)
589 if opts:
590 for opt in opts:
591 if opt['name'] == 'voltha.yang_child_rule':
Khen Nursimulub4e71472017-01-06 18:05:47 -0500592 new_messages, new_enums = move_message_to_parent_level(
593 message,
594 new_messages, new_enums)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500595 elif opt['name'] == 'voltha.yang_message_rule':
596 # create a duplicate message
Khen Nursimulub4e71472017-01-06 18:05:47 -0500597 # TODO: update references to point to the
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500598 duplicate_messages.append(message['name'])
599 clone = copy.deepcopy(message)
Khen Nursimulub4e71472017-01-06 18:05:47 -0500600 clone['full_name'] = ''.join(
601 [clone['full_name'], '_', 'grouping'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500602 clone['name'] = ''.join([clone['name'], '_', 'grouping'])
603 new_messages = new_messages + [clone]
604
605 return new_messages, new_enums, duplicate_messages
606
607
608def inline_field(message, field, option, messages):
609 new_message = copy.deepcopy(message)
610 new_message['fields'] = []
611 for f in message['fields']:
612 if f['full_name'] == field['full_name']:
613 # look for the message this field referred to.
614 # Addresses only top-level messages
615 for m in messages:
616 # 'proto_type' is the name of the message type this field
617 # refers to
618 if m['full_name'] == option['proto_type']:
619 # Copy all content of m into the field
620 new_message['fields'] = new_message['fields'] + \
621 copy.deepcopy(m['fields'])
Khen Nursimulub4e71472017-01-06 18:05:47 -0500622 new_message['oneofs'] = new_message['oneofs'].update(
623 copy.deepcopy(m['oneofs']))
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500624 new_message['enums'] = new_message['enums'] + \
625 copy.deepcopy(m['enums'])
626 new_message['messages'] = new_message['messages'] + \
Khen Nursimulub4e71472017-01-06 18:05:47 -0500627 copy.deepcopy(m['messages'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500628 else:
629 new_message['fields'].append(f)
630
631 return new_message
632
Khen Nursimulub4e71472017-01-06 18:05:47 -0500633
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500634# Address only annotations on top-level messages, i.e. no nested messages
635def update_fields_per_annotations_rule(options, messages):
636 new_messages = []
637 for message in messages:
638 new_message = None
639 for field in message['fields']:
640 opt = get_field_options(field['full_name'], options)
641 if opt:
642 if opt['option'] == 'voltha.yang_inline_node':
Khen Nursimulub4e71472017-01-06 18:05:47 -0500643 new_message = inline_field(message, field, opt, messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500644
645 if new_message:
646 new_messages.append(new_message)
647 else:
648 new_messages.append(message)
649
650 return new_messages
651
652
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500653def set_messages_keys(messages):
654 for message in messages:
655 message['key'] = _get_message_key(message, messages)
656 if message['messages']:
657 set_messages_keys(message['messages'])
658
Khen Nursimulub4e71472017-01-06 18:05:47 -0500659
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500660def _get_message_key(message, messages):
661 # assume key is first yang base type field
662 for field in message['fields']:
663 if not field['type_ref']:
664 return field['name']
665 else:
666 # if the field name is a message then loop for the key in that
667 # message
668 ref_message = _get_message(field['type'], messages)
669 if ref_message:
670 return _get_message_key(ref_message, messages)
671
672 # no key yet - search nested messaged
673 for m in message['messages']:
674 key = _get_message_key(m, messages)
675 if key is not None:
676 return key
677 else:
678 return None
679
Khen Nursimulub4e71472017-01-06 18:05:47 -0500680
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500681def _get_message(name, messages):
682 for m in messages:
683 if m['name'] == name:
684 return m
685 return None
686
Khen Nursimulub4e71472017-01-06 18:05:47 -0500687
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500688def get_message_key(message_name, messages):
689 for message in messages:
690 if message_name == message['name']:
691 return message['key']
692 if message['messages']:
693 return get_message_key(message_name, message['messages'])
694 return None
695
696
697def update_module_imports(module):
698 used_imports = set()
699 for ref_msg in module['referenced_messages']:
700 for type_dict in module['data_types']:
701 if ref_msg == type_dict['type']:
702 if module['name'] != type_dict['module']:
703 used_imports.add(type_dict['module'])
704 break
Khen Nursimulub4e71472017-01-06 18:05:47 -0500705 module['imports'] = [{'name': i} for i in used_imports]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500706
707
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500708def update_referred_messages(all_referred_messages, all_duplicate_messages):
709 new_referred_messages = []
710 for ref in all_referred_messages:
711 if ref in all_duplicate_messages:
712 new_referred_messages.append(''.join([ref, '_grouping']))
713 else:
714 new_referred_messages.append(ref)
715
716 return new_referred_messages
717
Khen Nursimulub4e71472017-01-06 18:05:47 -0500718
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500719def update_message_references_based_on_duplicates(duplicates, messages):
720 # Duplicates has a list of messages that exist both as a grouping and as
721 # a container. All reference to the container name by existing fields
722 # should be changed to the grouping name instead
723 for m in messages:
724 for f in m['fields']:
725 if f['type'] in duplicates:
726 f['type'] = ''.join([f['type'], '_grouping'])
727 if m['messages']:
728 update_message_references_based_on_duplicates(duplicates,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500729 m['messages'])
730
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500731
732def update_servic_references_based_on_duplicates(duplicates, services):
733 # Duplicates has a list of messages that exist both as a grouping and as
734 # a container. All reference to the container name by existing fields
735 # should be changed to the grouping name instead
736 for s in services:
737 for m in s['methods']:
738 if m['input_ref'] and m['input'] in duplicates:
739 m['input'] = ''.join([m['input'], '_grouping'])
740 if m['output_ref'] and m['output'] in duplicates:
741 m['output'] = ''.join([m['output'], '_grouping'])
742
743
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500744def generate_code(request, response):
745 assert isinstance(request, plugin.CodeGeneratorRequest)
746
747 parser = DescriptorParser()
748
749 # First process the proto file with the imports
750 all_defined_types = []
751 all_proto_data = []
752 all_referred_messages = []
753 all_messages = []
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500754 all_duplicate_messages = []
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500755 for proto_file in request.proto_file:
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500756 options = traverse_options(proto_file)
757 # print options
758
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500759 native_data = parser.parse_file_descriptor(proto_file,
760 type_tag_name='_type',
761 fold_comments=True)
762
763 # Consolidate the defined types across imports
764 yang_data = traverse_desc(native_data)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500765
766 duplicates = []
767 if options:
Khen Nursimulub4e71472017-01-06 18:05:47 -0500768 new_messages, new_enums, duplicates = \
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500769 update_messages_per_annotations_rule(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500770 options, yang_data['messages'], yang_data['enums'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500771
772 new_messages = update_fields_per_annotations_rule(options,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500773 new_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500774
775 # TODO: Need to do the change across all schema files. Not
776 # needed as annotations are single file based for now
777 if duplicates:
778 update_message_references_based_on_duplicates(duplicates,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500779 new_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500780 update_servic_references_based_on_duplicates(duplicates,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500781 yang_data[
782 'services'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500783
784 yang_data['messages'] = new_messages
785 yang_data['enums'] = new_enums
786
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500787 for type in yang_data['defined_types']:
788 all_defined_types.append(
789 {
Khen Nursimulub4e71472017-01-06 18:05:47 -0500790 'type': type,
791 'module': yang_data['name']
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500792 }
793 )
794
795 all_proto_data.append(
796 {
797 'file_name': '{}-{}'.format('ietf', proto_file.name.split(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500798 '/')[-1].replace('.proto', '.yang')),
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500799 'module': yang_data
800 }
801 )
802
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500803 # Consolidate all duplicate messages
804 all_duplicate_messages = all_duplicate_messages + duplicates
805
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500806 # Consolidate referred messages across imports
Khen Nursimulub4e71472017-01-06 18:05:47 -0500807 all_referred_messages = all_referred_messages + yang_data[
808 'referenced_messages']
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500809
810 # consolidate all messages
811 all_messages = all_messages + yang_data['messages']
812
Khen Nursimulub4e71472017-01-06 18:05:47 -0500813 # # Update the referred_messages
814 all_referred_messages = update_referred_messages(all_referred_messages,
815 all_duplicate_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500816
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500817 # Set the message keys - required for List definitions (repeated label)
818 set_messages_keys(all_messages)
819 unique_referred_messages_with_keys = []
820 for m in all_messages:
821 unique_referred_messages_with_keys.append(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500822 {
823 'name': m['name'],
824 'key': m['key']
825 }
826 )
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500827
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500828 # print_referred_msg(unique_referred_messages_with_keys)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500829 # Create the files
830 for proto_data in all_proto_data:
831 f = response.file.add()
832 f.name = proto_data['file_name']
833 proto_data['module']['data_types'] = all_defined_types
834 proto_data['module']['referred_messages'] = all_referred_messages
Khen Nursimulub4e71472017-01-06 18:05:47 -0500835 proto_data['module'][
836 'referred_messages_with_keys'] = unique_referred_messages_with_keys
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500837 proto_data['module']['duplicates'] = all_duplicate_messages
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500838 update_module_imports(proto_data['module'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500839 # print_message(proto_data['module']['messages'])
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500840 f.content = template_yang.render(module=proto_data['module'])
841
842
843def get_yang_type(field):
844 type = field['type']
845 if type in YANG_TYPE_MAP.keys():
846 _type, _ = YANG_TYPE_MAP[type]
847 if _type in ['enumeration', 'message', 'group']:
848 return field['type_name'].split('.')[-1]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500849 else:
850 return _type
851 else:
852 return type
853
Khen Nursimulub4e71472017-01-06 18:05:47 -0500854
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500855def is_enumeration(type):
856 if type in YANG_TYPE_MAP.keys():
857 _type, _ = YANG_TYPE_MAP[type]
858 return _type in ['enumeration']
859 return False
860
Khen Nursimulub4e71472017-01-06 18:05:47 -0500861
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500862def is_base_type(type):
863 # check numeric value of the type first
864 if type in YANG_TYPE_MAP.keys():
865 _type, _ = YANG_TYPE_MAP[type]
866 return _type not in ['message', 'group']
867 else:
868 # proto name of the type
869 result = [_format for (_, _format) in YANG_TYPE_MAP.values() if
870 _format == type and _format not in ['message',
871 'group']]
872 return len(result) > 0
873
874
875def remove_unsupported_characters(text):
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500876 unsupported_characters = ["{", "}", "[", "]", "\"", "\\", "*", "/", "<",
877 ">"]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500878 return ''.join([i if i not in unsupported_characters else ' ' for i in
879 text])
880
881
882def remove_first_character_if_match(str, char):
883 if str.startswith(char):
884 return str[1:]
885 return str
886
887
888YANG_TYPE_MAP = {
889 FieldDescriptor.TYPE_BOOL: ('boolean', 'boolean'),
890 FieldDescriptor.TYPE_BYTES: ('binary', 'byte'),
891 FieldDescriptor.TYPE_DOUBLE: ('decimal64', 'double'),
892 FieldDescriptor.TYPE_ENUM: ('enumeration', 'enum'),
893 FieldDescriptor.TYPE_FIXED32: ('int32', 'int64'),
894 FieldDescriptor.TYPE_FIXED64: ('int64', 'uint64'),
895 FieldDescriptor.TYPE_FLOAT: ('decimal64', 'float'),
896 FieldDescriptor.TYPE_INT32: ('int32', 'int32'),
897 FieldDescriptor.TYPE_INT64: ('int64', 'int64'),
898 FieldDescriptor.TYPE_SFIXED32: ('int32', 'int32'),
899 FieldDescriptor.TYPE_SFIXED64: ('int64', 'int64'),
900 FieldDescriptor.TYPE_STRING: ('string', 'string'),
901 FieldDescriptor.TYPE_SINT32: ('int32', 'int32'),
902 FieldDescriptor.TYPE_SINT64: ('int64', 'int64'),
903 FieldDescriptor.TYPE_UINT32: ('uint32', 'int64'),
904 FieldDescriptor.TYPE_UINT64: ('uint64', 'uint64'),
905 FieldDescriptor.TYPE_MESSAGE: ('message', 'message'),
906 FieldDescriptor.TYPE_GROUP: ('group', 'group')
907}
908
909if __name__ == '__main__':
910 # Read request message from stdin
911 data = sys.stdin.read()
912
913 # Parse request
914 request = plugin.CodeGeneratorRequest()
915 request.ParseFromString(data)
916
917 # Create response
918 response = plugin.CodeGeneratorResponse()
919
920 # Generate code
921 generate_code(request, response)
922
923 # Serialise response message
924 output = response.SerializeToString()
925
926 # Write to stdout
927 sys.stdout.write(output)
928 # print is_base_type(9)