Source code for socktools.tools.gen_proto

"""
Python Sock tools: gen_proto.py - Generate a python module for parsing a custom protocol
Copyright (C) 2016 GarethNelson

This file is part of python-sock-tools

python-sock-tools is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.

python-sock-tools is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with python-sock-tools.  If not, see <http://www.gnu.org/licenses/>.


This module is intended to be run as a command-line tool primarily, though a python API is also available.
"""

import eventlet
import argparse
import json
import time
import os

[docs]def get_parser(): """Get a command-line parser for this tool This function simply returns a parser object from argparse, it is here only to make generating documentation simpler. Returns: argparse.ArgumentParser: The populated argument parser """ parser = argparse.ArgumentParser(description='Autogenerate a python module that implements a custom protocol') parser.add_argument('-s','--specfile',type=str,help='The specification file to use',required=True) parser.add_argument('-t','--template',type=str,help='The location of the templates directory to use') parser.add_argument('-o','--output', type=str,help='Where to write output',default='generated.py') return parser
[docs]def load_json_file(path): """Load and parse a JSON file Loads a JSON file from disk and attempts to parse it at the same time - a simple convenience wrapper around json.load(). No exception handling is done, it is up to the caller to wrap in a try block if required. Args: path (str): Path to the JSON file needing to be loaded, this should usually be an absolute path Returns: object.object: The toplevel object parsed from the file, usually this is a dict or a list """ json_fd = open(path,'r') retval = json.load(json_fd) json_fd.close() return retval
[docs]def load_template(path,template_name): """Load a single template Loads a single template file into memory, like load_json_file() this is primarily a simple convenience wrapper that handles opening and reading the full contents of a file. No exception handling is done, it is up to the caller to wrap in a try block if required. Args: path (str): The path to the directory containing the template file template_name (str): The name of the template to load Returns: str: The contents of the template file, in future this might be some sort of template object instead """ template_filename = os.path.join(path,template_name) fd = open(template_filename,'r') retval = fd.read() fd.close() return retval
[docs]def get_template_vars(specdata,filename): """Get the template variables and their default values Evaluates the data from a specification file and uses it to build a dict of variables and their substitution values. Args: specdata (dict): The toplevel object from the specification file as loaded from load_json_file() filename (str): The absolute path to the specification file Returns: dict: A dict mapping %%VARIABLE%% tokens to the strings they should be replaced with """ retval = {'%%PROTONAME%%':specdata['protocol_name'], '%%PROTOSOCK%%':specdata['protocol_sock'], '%%SPECFILE%%': filename, '%%DATETIME%%':time.ctime().upper(), '%%MIXINS%%': ','.join(specdata['mixins']), '%%IMPORTS%%':'', '%%HANDLERS%%':''} for modname in specdata['imports']: retval['%%IMPORTS%%'] += ('import %s\n' % modname) msg_handlers_dict = {} for msg in specdata['messages']: msg_handlers_dict[msg['msg_type_int']] = '[self._handle_%s]' % msg['msg_type_str'] param_str = '' if specdata['named_fields']: param_str = ','.join(map(lambda x: x+'=None',msg['fields'])) retval['%%HANDLERS%%'] += '\n def _handle_%s(self,from_addr,msg_type,msg_data):\n self.handle_%s(from_addr,**msg_data)' % (msg['msg_type_str'],msg['msg_type_str']) else: param_str = ','.join(msg['fields']) retval['%%HANDLERS%%'] += '\n def _handle_%s(self,from_addr,msg_type,msg_data):\n self.handle_%s(from_addr,*msg_data)' % (msg['msg_type_str'],msg['msg_type_str']) retval['%%HANDLERS%%'] += '\n def handle_%s(self,from_addr,%s):\n pass' % (msg['msg_type_str'],param_str) msghandlers_str = repr(msg_handlers_dict) msghandlers_str = msghandlers_str.replace('u\'','') msghandlers_str = msghandlers_str.replace('\'','') retval['%%HANDLERS_DICT%%'] = msghandlers_str return retval
[docs]def load_templates(path): """Loads default templates A simple wrapper around load_template that loads all the default templates needed Args: path (str): The absolute path to the templates directory Returns: dict: A dict mapping the template name to the contents """ retval = {'module':load_template(path,'module'), 'protoclass':load_template(path,'protoclass')} return retval
[docs]def get_default_template_path(): """Calculates the default templates path This function is provided as a convenience for users of the Python API, such as build_example_chat.py Returns: str: The default templates path """ return os.path.join(os.path.dirname(os.path.abspath(__file__)),'templates')
[docs]def render_module(specfile_path,template_path,output_path): """Renders the generated python module This is where the real work happens - this function loads the specification file and templates then renders the module and writes it to disk. Args: specfile_path (str): Absolute path to the specification file template_path (str): Absolute path to the templates directory output_path (str): Absolute path to the output file to create """ specdata = load_json_file(specfile_path) templates = load_templates(template_path) template_vars = get_template_vars(specdata,specfile_path) for x in xrange(2): for k,v in templates.items(): for var_k,var_v in template_vars.items(): if var_k in v: templates[k] = templates[k].replace(var_k,var_v) for t_k,t_v in templates.items(): if ('%%%%%s%%%%' % t_k.upper()) in v: templates[k] = templates[k].replace('%%%%%s%%%%' % t_k.upper(),templates[t_k]) output_fd = open(output_path,'w') output_fd.write(templates['module']) output_fd.close()
if __name__=='__main__': args = get_parser().parse_args() if args.template is None: template_path = get_default_template_path() else: template_path = args.template specfile_path = os.path.abspath(args.specfile) output_path = os.path.abspath(args.output) print 'Rendering specification file %s to %s using templates in %s' % (specfile_path,output_path,template_path) render_module(specfile_path,template_path,output_path)