blob: 7539efb44bb0b1fe26beb1b51c71b51946aa2b5e [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
38from jinja2 import Template
39from google.protobuf.compiler import plugin_pb2 as plugin
Khen Nursimulub4e71472017-01-06 18:05:47 -050040from google.protobuf.descriptor_pb2 import DescriptorProto, \
41 FieldDescriptorProto
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050042from descriptor_parser import DescriptorParser
Khen Nursimulu7626ce12016-12-21 11:51:46 -050043import copy
44import yang_options_pb2
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -050045
46from google.protobuf.descriptor import FieldDescriptor
47
48template_yang = Template("""
49module ietf-{{ module.name }} {
50
51 {% macro set_module_prefix(type) %}
52 {% for t in module.data_types %}
53 {% if t.type == type %}
54 {% if t.module != module.name %} {{ t.module }}:{{ type }};
55 {% else %} {{ type }};
56 {% endif %}
57 {% set found=True %}
58 {% endif %}
59 {% if loop.last %}
60 {% if not found %} {{ type }}; {% endif %}
61 {% endif %}
62 {% endfor %}
63 {% endmacro %}
64
Khen Nursimulub4e71472017-01-06 18:05:47 -050065
66 {% macro process_oneofs(oneofs, ref_msgs) %}
67
68 {% for key, value in oneofs.iteritems() %}
69 choice {{ key }} {
70 {% for field in value %}
71 case {{ field.name }} {
72 {% if field.type_ref %}
73 {% for dict_item in ref_msgs %}
74 {% if dict_item.name == field.type %}
75 container {{ field.name }} {
76 uses {{ set_module_prefix(field.type) }}
77 description
78 "{{ field.description }}";
79 {% endif %}
80 {% endfor %}
81 }
82 {% else %}
83 leaf {{ field.name }} {
84 {% if field.type == "decimal64" %}
85 type {{ field.type }} {
86 fraction-digits 5;
87 }
88 {% else %}
89 type {{ set_module_prefix(field.type) }}
90 {% endif %}
91 description
92 "{{ field.description }}";
93 }
94 {% endif %}
95 }
96 {% endfor %}
97 }
98 {% endfor %}
99 {% endmacro %}
100
101
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500102 namespace "urn:opencord:params:xml:ns:voltha:ietf-{{ module.name }}";
103 prefix {{ module.name }};
104
105 {% for imp in module.imports %}
106 import ietf-{{ imp.name }} { prefix {{ imp.name }} ; }
107 {% endfor %}
108
109 organization "CORD";
110 contact
111 " Any name";
112
113 description
114 "{{ module.description }}";
115
116 revision "2016-11-15" {
117 description "Initial revision.";
118 reference "reference";
119 }
120
121 {% for enum in module.enums %}
122 typedef {{ enum.name }} {
123 type enumeration {
124 {% for v in enum.value %}
125 enum {{ v.name }} {
126 description "{{ v.description }}";
127 }
128 {% endfor %}
129 }
130 description
131 "{{ enum.description }}";
132 }
133 {% endfor %}
134
135 {% for message in module.messages recursive %}
136 {% if message.name in module.referred_messages %}
137 grouping {{ message.name }} {
138 {% else %}
139 container {{ message.name }} {
140 {% endif %}
141 description
142 "{{ message.description }}";
143 {% for field in message.fields %}
144 {% if field.type_ref %}
145 {% for dict_item in module.referred_messages_with_keys %}
146 {% if dict_item.name == field.type %}
147 {% if not field.repeated %}
148 container {{ field.name }} {
149 {% else %}
150 list {{ field.name }} {
151 key "{{ dict_item.key }}";
152 {% if not field.repeated %}
153 max-elements 1;
154 {% endif %}
155 {% endif %}
156 uses {{ set_module_prefix(field.type) }}
157 description
158 "{{ field.description }}";
159 }
160 {% endif %}
161 {% endfor %}
162 {% elif field.repeated %}
163 list {{ field.name }} {
164 key "{{ field.name }}";
165 leaf {{ field.name }} {
166 {% if field.type == "decimal64" %}
167 type {{ field.type }} {
168 fraction-digits 5;
169 }
170 {% else %}
171 type {{ set_module_prefix(field.type) }}
172 {% endif %}
173 description
174 "{{ field.description }}";
175 }
176 description
177 "{{ field.description }}";
178 }
179 {% else %}
180 leaf {{ field.name }} {
181 {% if field.type == "decimal64" %}
182 type {{ field.type }} {
183 fraction-digits 5;
184 }
185 {% else %}
186 type {{ set_module_prefix(field.type) }}
187 {% endif %}
188 description
189 "{{ field.description }}";
190 }
191 {% endif %}
192
193 {% endfor %}
Khen Nursimulub4e71472017-01-06 18:05:47 -0500194
195 {% if message.oneofs %}
196 {{ process_oneofs(message.oneofs, module.referred_messages_with_keys) }}
197 {% endif %}
198
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500199 {% for enum_type in message.enums %}
200 typedef {{ enum_type.name }} {
201 type enumeration {
202 {% for v in enum_type.value %}
203 enum {{ v.name }} {
204 description "{{ v.description }}";
205 }
206 {% endfor %}
207 }
208 description
209 "{{ enum_type.description }}";
210 }
211
212 {% endfor %}
213 {% if message.messages %}
214 {{ loop (message.messages)|indent(4, false) }}
215 {% endif %}
216 }
217
218 {% endfor %}
219 {% for service in module.services %}
220 {% if service.description %}
221 /* {{ service.description }}" */
222 {% endif %}
223 {% for method in service.methods %}
224 rpc {{ service.service }}-{{ method.method }} {
225 description
226 "{{ method.description }}";
227 {% if method.input %}
228 input {
229 {% if method.input_ref %}
230 uses {{ set_module_prefix(method.input) }}
231 {% else %}
232 leaf {{ method.input }} {
233 type {{ set_module_prefix(method.input) }}
234 }
235 {% endif %}
236 }
237 {% endif %}
238 {% if method.output %}
239 output {
240 {% if method.output_ref %}
241 uses {{ set_module_prefix(method.output) }}
242 {% else %}
243 leaf {{ method.output }} {
244 type {{ set_module_prefix(method.output) }}
245 }
246 {% endif %}
247 }
248 {% endif %}
249 }
250
251 {% endfor %}
252
253 {% endfor %}
254}
255""", trim_blocks=True, lstrip_blocks=True)
256
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500257
258def traverse_field_options(fields, prefix):
259 field_options = []
260 for field in fields:
261 assert isinstance(field, FieldDescriptorProto)
262 full_name = prefix + '-' + field.name
263 option = None
264 if field.type == FieldDescriptor.TYPE_MESSAGE and field.label != \
265 FieldDescriptor.LABEL_REPEATED:
266 if field.options:
267 for fd, val in field.options.ListFields():
268 if fd.full_name == 'voltha.yang_inline_node':
269 field_options.append(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500270 {'name': full_name,
271 'option': fd.full_name,
272 'proto_name': val.id,
273 'proto_type': val.type
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500274 }
275 )
276 return field_options
277
278
279def traverse_message_options(message_types, prefix):
Khen Nursimulub4e71472017-01-06 18:05:47 -0500280 message_options = []
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500281 for message_type in message_types:
282 assert isinstance(message_type, DescriptorProto)
283 full_name = prefix + '-' + message_type.name
284 option_rules = []
285
286 options = message_type.options
287 if options:
288 for fd, val in options.ListFields():
289 if fd.full_name in ['voltha.yang_child_rule',
290 'voltha.yang_message_rule']:
291 option_rules.append({
Khen Nursimulub4e71472017-01-06 18:05:47 -0500292 'name': fd.full_name,
293 'value': val
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500294 })
295
296 # parse fields for options
297 field_options = traverse_field_options(message_type.field,
298 full_name)
299
300 # parse nested messages
301 nested_messages_options = []
302 nested = message_type.nested_type
303 if nested:
304 nested_messages_options = traverse_message_options(nested,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500305 full_name)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500306
307 if option_rules or nested_messages_options or field_options:
308 message_options.append(
309 {
310 'name': full_name,
311 'options': option_rules,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500312 'field_options': field_options,
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500313 'nested_options': nested_messages_options,
314 }
315 )
316 return message_options
317
318
319def get_message_options(name, options):
320 result = None
321 for opt in options:
322 if opt['name'] == name:
323 return opt['options']
324 if opt['nested_options']:
325 result = get_message_options(name, opt['nested_options'])
326 if result:
327 return result
328
Khen Nursimulub4e71472017-01-06 18:05:47 -0500329
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500330def get_field_options(name, options):
331 result = None
332 for opt in options:
333 if opt['field_options']:
334 for field_opt in opt['field_options']:
335 if field_opt['name'] == name:
336 result = field_opt
337 if opt['nested_options']:
338 result = get_field_options(name, opt['nested_options'])
339 if result:
340 return result
341
342
343def traverse_options(proto_file):
344 package = proto_file.name
345 prefix = package.replace('.proto', '')
346 if proto_file.message_type:
347 message_options = traverse_message_options(proto_file.message_type,
348 prefix)
349 return message_options
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500350
351
352def traverse_messages(message_types, prefix, referenced_messages):
353 messages = []
354 for message_type in message_types:
355 assert message_type['_type'] == 'google.protobuf.DescriptorProto'
356
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500357 full_name = prefix + '-' + message_type['name']
358 name = message_type['name']
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500359
360 # parse the fields
Khen Nursimulub4e71472017-01-06 18:05:47 -0500361 oneofs, fields = traverse_fields(message_type.get('field', []),
362 full_name, referenced_messages)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500363
364 # parse the enums
365 enums = traverse_enums(message_type.get('enum_type', []), full_name)
366
367 # parse nested messages
368 nested = message_type.get('nested_type', [])
369 nested_messages = traverse_messages(nested, full_name,
370 referenced_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500371
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500372 messages.append(
373 {
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500374 'full_name': full_name,
375 'name': name,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500376 'fields': fields,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500377 'oneofs': oneofs,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500378 'enums': enums,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500379 'messages': nested_messages,
380 'description': remove_unsupported_characters(
381 message_type.get('_description', '')),
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500382 }
383 )
384 return messages
385
386
387def traverse_fields(fields_desc, prefix, referenced_messages):
388 fields = []
Khen Nursimulub4e71472017-01-06 18:05:47 -0500389 oneofs = {}
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500390 for field in fields_desc:
Khen Nursimulub4e71472017-01-06 18:05:47 -0500391 # if field.get('oneof_index', None) >= 0:
392 # print '{},{}'.format(field.get('name', ''), field.get('number'))
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500393 assert field['_type'] == 'google.protobuf.FieldDescriptorProto'
394 yang_base_type = is_base_type(field['type'])
395 _type = get_yang_type(field)
396 if not yang_base_type:
397 referenced_messages.append(_type)
398 # add to referred messages also if it is an enumeration type
399 if is_enumeration(field['type']):
400 referenced_messages.append(_type)
401
Khen Nursimulub4e71472017-01-06 18:05:47 -0500402 if field.get('oneof_index', None) >= 0:
403 # Oneof fields
404 key = ''.join(['choice_', str(field['oneof_index'])])
405 if not oneofs.has_key(key):
406 oneofs[key] = []
407 oneofs[key].append(
408 {
409 'full_name': prefix + '-' + field.get('name', ''),
410 'oneof_index': field.get('oneof_index', None),
411 'name': field.get('name', ''),
412 'label': field.get('label', ''),
413 'repeated': field[
414 'label'] == FieldDescriptor.LABEL_REPEATED,
415 'number': field.get('number', ''),
416 'options': field.get('options', ''),
417 'type_name': field.get('type_name', ''),
418 'type': _type,
419 'type_ref': not yang_base_type,
420 'description': remove_unsupported_characters(field.get(
421 '_description', ''))
422 }
423 )
424 else:
425 fields.append(
426 {
427 'full_name': prefix + '-' + field.get('name', ''),
428 'name': field.get('name', ''),
429 'label': field.get('label', ''),
430 'repeated': field[
431 'label'] == FieldDescriptor.LABEL_REPEATED,
432 'number': field.get('number', ''),
433 'options': field.get('options', ''),
434 'type_name': field.get('type_name', ''),
435 'type': _type,
436 'type_ref': not yang_base_type,
437 'description': remove_unsupported_characters(field.get(
438 '_description', ''))
439 }
440 )
441 # print oneofs
442 return oneofs, fields
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500443
444
445def traverse_enums(enums_desc, prefix):
446 enums = []
447 for enum in enums_desc:
448 assert enum['_type'] == 'google.protobuf.EnumDescriptorProto'
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500449 full_name = prefix + '-' + enum.get('name', '')
450 name = enum.get('name', '')
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500451 enums.append(
452 {
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500453 'full_name': full_name,
454 'name': name,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500455 'value': enum.get('value', ''),
456 'description': remove_unsupported_characters(enum.get(
457 '_description', ''))
458 }
459 )
460 return enums
461
462
463def traverse_services(service_desc, referenced_messages):
464 services = []
465 for service in service_desc:
466 methods = []
467 for method in service.get('method', []):
468 assert method['_type'] == 'google.protobuf.MethodDescriptorProto'
469
470 input_name = method.get('input_type')
471 input_ref = False
472 if not is_base_type(input_name):
473 input_name = remove_first_character_if_match(input_name, '.')
474 # input_name = input_name.replace(".", "-")
475 input_name = input_name.split('.')[-1]
476 referenced_messages.append(input_name)
477 input_ref = True
478
479 output_name = method.get('output_type')
480 output_ref = False
481 if not is_base_type(output_name):
482 output_name = remove_first_character_if_match(output_name, '.')
483 # output_name = output_name.replace(".", "-")
484 output_name = output_name.split('.')[-1]
485 referenced_messages.append(output_name)
486 output_ref = True
487
488 methods.append(
489 {
490 'method': method.get('name', ''),
491 'input': input_name,
492 'input_ref': input_ref,
493 'output': output_name,
494 'output_ref': output_ref,
495 'description': remove_unsupported_characters(method.get(
496 '_description', '')),
497 'server_streaming': method.get('server_streaming',
498 False) == True
499 }
500 )
501 services.append(
502 {
503 'service': service.get('name', ''),
504 'methods': methods,
505 'description': remove_unsupported_characters(service.get(
506 '_description', '')),
507 }
508 )
509 return services
510
511
512def rchop(thestring, ending):
513 if thestring.endswith(ending):
514 return thestring[:-len(ending)]
515 return thestring
516
517
518def traverse_desc(descriptor):
519 referenced_messages = []
520 name = rchop(descriptor.get('name', ''), '.proto')
521 package = descriptor.get('package', '')
522 description = descriptor.get('_description', '')
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500523 messages = traverse_messages(descriptor.get('message_type', []),
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500524 name, referenced_messages)
525 enums = traverse_enums(descriptor.get('enum_type', []), name)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500526 services = traverse_services(descriptor.get('service', []),
527 referenced_messages)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500528
529 # Get a list of type definitions (messages, enums) defined in this
530 # descriptor
531 defined_types = [m['name'].split('/')[-1] for m in messages] + \
Khen Nursimulub4e71472017-01-06 18:05:47 -0500532 [e['name'].split('/')[-1] for e in enums]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500533
534 data = {
535 'name': name.split('/')[-1],
536 'package': package,
537 'description': description,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500538 'messages': messages,
539 'enums': enums,
540 'services': services,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500541 'defined_types': defined_types,
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500542 'referenced_messages': list(set(referenced_messages)),
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500543 }
544 return data
545
546
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500547# For now, annotations are added to first level messages only.
548# Therefore, at this time no need to tackle nested messages.
549def move_message_to_parent_level(message, messages, enums):
550 new_message = []
551 new_enum = copy.deepcopy(enums)
552 for msg in messages:
553 if msg['full_name'] == message['full_name']:
554 # Move all sub messages and enums to top level
555 if msg['messages']:
556 new_message = new_message + copy.deepcopy(msg['messages'])
557 if msg['enums']:
558 new_enum = new_enum + copy.deepcopy(msg['enums'])
559
560 # if the message has some fields then enclose them in a container
561 if msg['fields']:
562 new_message.append(
563 {
564 'full_name': msg['full_name'],
565 'name': msg['name'],
566 'fields': msg['fields'],
Khen Nursimulub4e71472017-01-06 18:05:47 -0500567 'oneofs': msg['oneofs'],
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500568 'description': msg['description'],
569 'messages': [],
570 'enums': []
571 }
572 )
573 else:
574 new_message.append(msg)
575
576 return new_message, new_enum
577
578
579def update_messages_per_annotations_rule(options, messages, enums):
580 new_messages = messages
581 new_enums = enums
582 # Used when a message needs to exist both as a type and a container
583 duplicate_messages = []
584 for message in messages:
585 opts = get_message_options(message['full_name'], options)
586 if opts:
587 for opt in opts:
588 if opt['name'] == 'voltha.yang_child_rule':
Khen Nursimulub4e71472017-01-06 18:05:47 -0500589 new_messages, new_enums = move_message_to_parent_level(
590 message,
591 new_messages, new_enums)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500592 elif opt['name'] == 'voltha.yang_message_rule':
593 # create a duplicate message
Khen Nursimulub4e71472017-01-06 18:05:47 -0500594 # TODO: update references to point to the
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500595 duplicate_messages.append(message['name'])
596 clone = copy.deepcopy(message)
Khen Nursimulub4e71472017-01-06 18:05:47 -0500597 clone['full_name'] = ''.join(
598 [clone['full_name'], '_', 'grouping'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500599 clone['name'] = ''.join([clone['name'], '_', 'grouping'])
600 new_messages = new_messages + [clone]
601
602 return new_messages, new_enums, duplicate_messages
603
604
605def inline_field(message, field, option, messages):
606 new_message = copy.deepcopy(message)
607 new_message['fields'] = []
608 for f in message['fields']:
609 if f['full_name'] == field['full_name']:
610 # look for the message this field referred to.
611 # Addresses only top-level messages
612 for m in messages:
613 # 'proto_type' is the name of the message type this field
614 # refers to
615 if m['full_name'] == option['proto_type']:
616 # Copy all content of m into the field
617 new_message['fields'] = new_message['fields'] + \
618 copy.deepcopy(m['fields'])
Khen Nursimulub4e71472017-01-06 18:05:47 -0500619 new_message['oneofs'] = new_message['oneofs'].update(
620 copy.deepcopy(m['oneofs']))
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500621 new_message['enums'] = new_message['enums'] + \
622 copy.deepcopy(m['enums'])
623 new_message['messages'] = new_message['messages'] + \
Khen Nursimulub4e71472017-01-06 18:05:47 -0500624 copy.deepcopy(m['messages'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500625 else:
626 new_message['fields'].append(f)
627
628 return new_message
629
Khen Nursimulub4e71472017-01-06 18:05:47 -0500630
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500631# Address only annotations on top-level messages, i.e. no nested messages
632def update_fields_per_annotations_rule(options, messages):
633 new_messages = []
634 for message in messages:
635 new_message = None
636 for field in message['fields']:
637 opt = get_field_options(field['full_name'], options)
638 if opt:
639 if opt['option'] == 'voltha.yang_inline_node':
Khen Nursimulub4e71472017-01-06 18:05:47 -0500640 new_message = inline_field(message, field, opt, messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500641
642 if new_message:
643 new_messages.append(new_message)
644 else:
645 new_messages.append(message)
646
647 return new_messages
648
649
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500650def set_messages_keys(messages):
651 for message in messages:
652 message['key'] = _get_message_key(message, messages)
653 if message['messages']:
654 set_messages_keys(message['messages'])
655
Khen Nursimulub4e71472017-01-06 18:05:47 -0500656
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500657def _get_message_key(message, messages):
658 # assume key is first yang base type field
659 for field in message['fields']:
660 if not field['type_ref']:
661 return field['name']
662 else:
663 # if the field name is a message then loop for the key in that
664 # message
665 ref_message = _get_message(field['type'], messages)
666 if ref_message:
667 return _get_message_key(ref_message, messages)
668
669 # no key yet - search nested messaged
670 for m in message['messages']:
671 key = _get_message_key(m, messages)
672 if key is not None:
673 return key
674 else:
675 return None
676
Khen Nursimulub4e71472017-01-06 18:05:47 -0500677
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500678def _get_message(name, messages):
679 for m in messages:
680 if m['name'] == name:
681 return m
682 return None
683
Khen Nursimulub4e71472017-01-06 18:05:47 -0500684
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500685def get_message_key(message_name, messages):
686 for message in messages:
687 if message_name == message['name']:
688 return message['key']
689 if message['messages']:
690 return get_message_key(message_name, message['messages'])
691 return None
692
693
694def update_module_imports(module):
695 used_imports = set()
696 for ref_msg in module['referenced_messages']:
697 for type_dict in module['data_types']:
698 if ref_msg == type_dict['type']:
699 if module['name'] != type_dict['module']:
700 used_imports.add(type_dict['module'])
701 break
Khen Nursimulub4e71472017-01-06 18:05:47 -0500702 module['imports'] = [{'name': i} for i in used_imports]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500703
704
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500705def update_referred_messages(all_referred_messages, all_duplicate_messages):
706 new_referred_messages = []
707 for ref in all_referred_messages:
708 if ref in all_duplicate_messages:
709 new_referred_messages.append(''.join([ref, '_grouping']))
710 else:
711 new_referred_messages.append(ref)
712
713 return new_referred_messages
714
Khen Nursimulub4e71472017-01-06 18:05:47 -0500715
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500716def update_message_references_based_on_duplicates(duplicates, messages):
717 # Duplicates has a list of messages that exist both as a grouping and as
718 # a container. All reference to the container name by existing fields
719 # should be changed to the grouping name instead
720 for m in messages:
721 for f in m['fields']:
722 if f['type'] in duplicates:
723 f['type'] = ''.join([f['type'], '_grouping'])
724 if m['messages']:
725 update_message_references_based_on_duplicates(duplicates,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500726 m['messages'])
727
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500728
729def update_servic_references_based_on_duplicates(duplicates, services):
730 # Duplicates has a list of messages that exist both as a grouping and as
731 # a container. All reference to the container name by existing fields
732 # should be changed to the grouping name instead
733 for s in services:
734 for m in s['methods']:
735 if m['input_ref'] and m['input'] in duplicates:
736 m['input'] = ''.join([m['input'], '_grouping'])
737 if m['output_ref'] and m['output'] in duplicates:
738 m['output'] = ''.join([m['output'], '_grouping'])
739
740
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500741def generate_code(request, response):
742 assert isinstance(request, plugin.CodeGeneratorRequest)
743
744 parser = DescriptorParser()
745
746 # First process the proto file with the imports
747 all_defined_types = []
748 all_proto_data = []
749 all_referred_messages = []
750 all_messages = []
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500751 all_duplicate_messages = []
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500752 for proto_file in request.proto_file:
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500753 options = traverse_options(proto_file)
754 # print options
755
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500756 native_data = parser.parse_file_descriptor(proto_file,
757 type_tag_name='_type',
758 fold_comments=True)
759
760 # Consolidate the defined types across imports
761 yang_data = traverse_desc(native_data)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500762
763 duplicates = []
764 if options:
Khen Nursimulub4e71472017-01-06 18:05:47 -0500765 new_messages, new_enums, duplicates = \
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500766 update_messages_per_annotations_rule(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500767 options, yang_data['messages'], yang_data['enums'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500768
769 new_messages = update_fields_per_annotations_rule(options,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500770 new_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500771
772 # TODO: Need to do the change across all schema files. Not
773 # needed as annotations are single file based for now
774 if duplicates:
775 update_message_references_based_on_duplicates(duplicates,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500776 new_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500777 update_servic_references_based_on_duplicates(duplicates,
Khen Nursimulub4e71472017-01-06 18:05:47 -0500778 yang_data[
779 'services'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500780
781 yang_data['messages'] = new_messages
782 yang_data['enums'] = new_enums
783
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500784 for type in yang_data['defined_types']:
785 all_defined_types.append(
786 {
Khen Nursimulub4e71472017-01-06 18:05:47 -0500787 'type': type,
788 'module': yang_data['name']
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500789 }
790 )
791
792 all_proto_data.append(
793 {
794 'file_name': '{}-{}'.format('ietf', proto_file.name.split(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500795 '/')[-1].replace('.proto', '.yang')),
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500796 'module': yang_data
797 }
798 )
799
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500800 # Consolidate all duplicate messages
801 all_duplicate_messages = all_duplicate_messages + duplicates
802
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500803 # Consolidate referred messages across imports
Khen Nursimulub4e71472017-01-06 18:05:47 -0500804 all_referred_messages = all_referred_messages + yang_data[
805 'referenced_messages']
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500806
807 # consolidate all messages
808 all_messages = all_messages + yang_data['messages']
809
Khen Nursimulub4e71472017-01-06 18:05:47 -0500810 # # Update the referred_messages
811 all_referred_messages = update_referred_messages(all_referred_messages,
812 all_duplicate_messages)
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500813
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500814 # Set the message keys - required for List definitions (repeated label)
815 set_messages_keys(all_messages)
816 unique_referred_messages_with_keys = []
817 for m in all_messages:
818 unique_referred_messages_with_keys.append(
Khen Nursimulub4e71472017-01-06 18:05:47 -0500819 {
820 'name': m['name'],
821 'key': m['key']
822 }
823 )
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500824
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500825 # print_referred_msg(unique_referred_messages_with_keys)
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500826 # Create the files
827 for proto_data in all_proto_data:
828 f = response.file.add()
829 f.name = proto_data['file_name']
830 proto_data['module']['data_types'] = all_defined_types
831 proto_data['module']['referred_messages'] = all_referred_messages
Khen Nursimulub4e71472017-01-06 18:05:47 -0500832 proto_data['module'][
833 'referred_messages_with_keys'] = unique_referred_messages_with_keys
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500834 proto_data['module']['duplicates'] = all_duplicate_messages
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500835 update_module_imports(proto_data['module'])
Khen Nursimulu7626ce12016-12-21 11:51:46 -0500836 # print_message(proto_data['module']['messages'])
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500837 f.content = template_yang.render(module=proto_data['module'])
838
839
840def get_yang_type(field):
841 type = field['type']
842 if type in YANG_TYPE_MAP.keys():
843 _type, _ = YANG_TYPE_MAP[type]
844 if _type in ['enumeration', 'message', 'group']:
845 return field['type_name'].split('.')[-1]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500846 else:
847 return _type
848 else:
849 return type
850
Khen Nursimulub4e71472017-01-06 18:05:47 -0500851
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500852def is_enumeration(type):
853 if type in YANG_TYPE_MAP.keys():
854 _type, _ = YANG_TYPE_MAP[type]
855 return _type in ['enumeration']
856 return False
857
Khen Nursimulub4e71472017-01-06 18:05:47 -0500858
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500859def is_base_type(type):
860 # check numeric value of the type first
861 if type in YANG_TYPE_MAP.keys():
862 _type, _ = YANG_TYPE_MAP[type]
863 return _type not in ['message', 'group']
864 else:
865 # proto name of the type
866 result = [_format for (_, _format) in YANG_TYPE_MAP.values() if
867 _format == type and _format not in ['message',
868 'group']]
869 return len(result) > 0
870
871
872def remove_unsupported_characters(text):
Khen Nursimuluc9ef7c12017-01-04 20:40:53 -0500873 unsupported_characters = ["{", "}", "[", "]", "\"", "\\", "*", "/", "<",
874 ">"]
Khen Nursimuluaaac7ee2016-12-11 22:03:52 -0500875 return ''.join([i if i not in unsupported_characters else ' ' for i in
876 text])
877
878
879def remove_first_character_if_match(str, char):
880 if str.startswith(char):
881 return str[1:]
882 return str
883
884
885YANG_TYPE_MAP = {
886 FieldDescriptor.TYPE_BOOL: ('boolean', 'boolean'),
887 FieldDescriptor.TYPE_BYTES: ('binary', 'byte'),
888 FieldDescriptor.TYPE_DOUBLE: ('decimal64', 'double'),
889 FieldDescriptor.TYPE_ENUM: ('enumeration', 'enum'),
890 FieldDescriptor.TYPE_FIXED32: ('int32', 'int64'),
891 FieldDescriptor.TYPE_FIXED64: ('int64', 'uint64'),
892 FieldDescriptor.TYPE_FLOAT: ('decimal64', 'float'),
893 FieldDescriptor.TYPE_INT32: ('int32', 'int32'),
894 FieldDescriptor.TYPE_INT64: ('int64', 'int64'),
895 FieldDescriptor.TYPE_SFIXED32: ('int32', 'int32'),
896 FieldDescriptor.TYPE_SFIXED64: ('int64', 'int64'),
897 FieldDescriptor.TYPE_STRING: ('string', 'string'),
898 FieldDescriptor.TYPE_SINT32: ('int32', 'int32'),
899 FieldDescriptor.TYPE_SINT64: ('int64', 'int64'),
900 FieldDescriptor.TYPE_UINT32: ('uint32', 'int64'),
901 FieldDescriptor.TYPE_UINT64: ('uint64', 'uint64'),
902 FieldDescriptor.TYPE_MESSAGE: ('message', 'message'),
903 FieldDescriptor.TYPE_GROUP: ('group', 'group')
904}
905
906if __name__ == '__main__':
907 # Read request message from stdin
908 data = sys.stdin.read()
909
910 # Parse request
911 request = plugin.CodeGeneratorRequest()
912 request.ParseFromString(data)
913
914 # Create response
915 response = plugin.CodeGeneratorResponse()
916
917 # Generate code
918 generate_code(request, response)
919
920 # Serialise response message
921 output = response.SerializeToString()
922
923 # Write to stdout
924 sys.stdout.write(output)
925 # print is_base_type(9)