Working on the SemaConnect EV charging station firmware over the past 8-9 years, I’ve written a lot of C code. While I like C, I do not like writing boilerplate code. It is time-consuming and error-prone. So I automate.
One strategy that I’ve adopted is using the Protocol Buffer language to specify interfaces, and transforming these into code using the python protobuf extension and the mako template language.
#!/usr/bin/env python
import sys
import os
import types
from mako.template import Template
from mako.lookup import TemplateLookup
from google.protobuf.descriptor import ServiceDescriptor, EnumDescriptor, FieldDescriptor
def flatten( l ):
return [item for sublist in l for item in sublist]
sys.path.append( os.path.dirname(sys.argv[1]))
pbmod = __import__( os.path.basename(sys.argv[1]).split('.')[0])
# extract services, enums
services=[]
enums=[]
for name, desc in pbmod.__dict__.items():
if type(desc)==ServiceDescriptor:
services.append( desc )
elif str(type(desc)).find('EnumDescriptor')>=0:
enums.append( desc )
elif type(desc)==types.ModuleType and name.endswith('pb2'):
pass
# extract messages
messages = pbmod.DESCRIPTOR.message_types_by_name.values()
# sort message types so that if a depends on b,
# b appears before a in the list
def messages_sorted_by_dependency( msgs ):
sorted_msgs = [msg for msg in msgs]
for i in xrange(len(sorted_msgs)):
for j in xrange(len(sorted_msgs)):
for k in xrange(j, len(sorted_msgs)):
for field in sorted_msgs[j].fields:
if (field.type == FieldDescriptor.TYPE_MESSAGE and
field.message_type.name == sorted_msgs[k].name ):
# swap if a field in j depends on k
tmp = sorted_msgs[j]
sorted_msgs[j] = sorted_msgs[k]
sorted_msgs[k] = tmp
break
return sorted_msgs
messages = messages_sorted_by_dependency( pbmod.DESCRIPTOR.message_types_by_name.values() )
imports=set()
for message in messages:
for field in message.fields:
if field.type==FieldDescriptor.TYPE_MESSAGE:
fpkg = field.message_type.file
if fpkg != pbmod.DESCRIPTOR:
imports.add( fpkg )
outfname = sys.argv[3]
tmpl_fname = sys.argv[2]
mylookup = TemplateLookup(directories=['/'])
tmpl = Template(filename=tmpl_fname, lookup=mylookup)
output = tmpl.render( pbmod=pbmod, descriptor=pbmod.DESCRIPTOR, messages=messages, services=services, enums=enums, imports=imports)
open(outfname,"w").write( output )
print("Wrote " + outfname)
<% # some definitions package = descriptor.package PACKAGE = package.upper() # def fullname(T): return T.full_name.replace('.','_') %>
#ifndef ${PACKAGE}_H
#define ${PACKAGE}_H
%for enum in enums:
enum ${fullname(enum)}
{
%for enum_value in enum.values:
${package}_${enum_value.name} = ${enum_value.number},
%endfor
};
#define ${fullname(enum)}_MIN ${min(map(lambda x: x.number, enum.values))}
#define ${fullname(enum)}_MAX ${max(map(lambda x: x.number, enum.values))}
%endfor
// END enum types
#endif // ${PACKAGE}_H
