generate.py 8.48 KB
#!/usr/bin/env python
from glob import glob
from importlib import import_module
import json
import os
import re
import urllib.request
from shutil import rmtree

import autopep8

from floraconcierge.client.types import module_name_from_class
from floraconcierge.client.utils import CaseInsensitiveDict

mapping_url = 'http://api.floraconcierge.loc/mapping'
data = urllib.request.urlopen(mapping_url).read()
mapping = json.loads(data)

field_types = {
    'id': 'int',
    'slug': 'str',
    'iso': 'str',
    'name': 'str',
    'parent_id': 'int',
    'code': 'str',
    'mixed': 'str'
}


class CodeGenerator:
    def __init__(self, indentation='    '):
        self.indentation = indentation
        self.level = 0
        self.code = ''

    def indent(self):
        self.level += 1

    def dedent(self):
        if self.level > 0:
            self.level -= 1

    def __add__(self, value):
        temp = CodeGenerator(indentation=self.indentation)
        temp.level = self.level
        value = str(value.rstrip("\n\r")) + "\n"
        temp.code = str(self) + ''.join([self.indentation for i in range(0, self.level)]) + value
        return temp

    def __str__(self):
        return str(self.code)


models = {}
stdtypes = list(getattr(__builtins__, x) for x in dir(__builtins__) if
                getattr(__builtins__, x).__class__.__name__ == 'type')
stdtypes = list(x.__name__ for x in stdtypes if not issubclass(x, BaseException))

typealiases = CaseInsensitiveDict({
    'string': 'str',
    'mixed': 'str',
    'array': 'list',
    'model': 'Object'
})

print('Building modules')
for name, config in mapping['models'].iteritems():
    module, clsname = module_name_from_class(config['class'])
    types = field_types.copy()
    if 'types' in config:
        types.update(config['types'])

    if not module in models:
        models[module] = {
            'import': [],
            'body': []
        }

    parent_clsname = 'Object'
    mixin_module, mixin_clsname = module_name_from_class('mixins_' + config['class'])
    try:
        mixin = import_module(mixin_module)
        getattr(mixin, mixin_clsname)
        parent_clsname = mixin_clsname + 'Mixin'
        models[module]['import'].append('from %s import %s as %s' % (mixin_module, mixin_clsname, parent_clsname))
    except (ImportError, AttributeError):
        models[module]['import'].append('from floraconcierge.client.types import Object')

    body = CodeGenerator()
    body += 'class %s(%s):' % (clsname, parent_clsname)
    body.indent()
    body += '"""'
    for field in config['fields']:
        if field in types:
            ftypes = types[field]
            if not isinstance(ftypes, (list, tuple)):
                ftypes = [ftypes]

            ftypes, doctypes = list(ftypes), []
            if field.endswith('_id'):
                ftypes.append('int')

            for i, ftype in enumerate(ftypes):
                fformat = '%s'
                if ftype.endswith("[]"):
                    fformat = 'list of %s'
                    ftype = ftype[0:-2]

                ftype = typealiases.get(ftype, ftype)
                if ftype not in stdtypes:
                    fmodule, fclass = module_name_from_class(ftype)

                    # if fmodule != module:
                    #     models[module]['import'].append('from %s import %s' % (fmodule, fclass))

                    doctypes.append(fformat % fclass)
                else:
                    doctypes.append(fformat % ftype)

            body += ':type %s: %s' % (field, "|".join(set(doctypes)))
    body += '"""'

    body += 'def __init__(self, *args, **kwargs):'
    body.indent()

    for field in config['fields']:
        body += 'self.%s = None' % field

    body += ''
    body += 'super(%s, self).__init__(*args, **kwargs)' % clsname

    models[module]['body'].append(str(body))

basedir = '%s/floraconcierge/mapping/model/' % os.path.dirname(__file__)
if os.path.isdir(basedir):
    print('Removing old model directories tree')
    rmtree(basedir)

print('Generating modules')
pepoptions = autopep8.parse_args(['', '-aa'])
for module, config in models.iteritems():
    if '.mailer' in module: continue
    print('\t%s' % module)
    dirpath = module.replace(".", '/')
    if not os.path.isdir(dirpath):
        os.makedirs(dirpath)

    initpath = '%s/__init__.py' % dirpath
    code = "%s\n\n\n%s" % (
        "\n".join(set(config['import'])),
        "\n\n".join(config['body'])
    )

    code = autopep8.fix_code(code, pepoptions)

    open(initpath, 'wb').write(code)


def normalize_func_name(name):
    stack = []
    for c in name:
        if c.isupper():
            stack.append('_')
            stack.append(c.lower())
        else:
            stack.append(c)

    return ''.join(stack)


print('Generating api functions list')
code = CodeGenerator()
code += 'from floraconcierge.client import Service'

manager = CodeGenerator()
manager += 'class Manager(object):'
manager.indent()
manager += 'def __init__(self, apiclient):'
manager.indent()

imports = []

print('Clearing shortcuts')
for f in glob('floraconcierge/shortcuts/*.py'):
    if os.path.basename(f) == ('__init__.py'):
        continue

    print('\t%s' % f)
    os.unlink(f)

for service, functions in mapping['functions'].iteritems():
    print('\tService %s' % service)

    shortcut = CodeGenerator()
    shortcut += 'from floraconcierge.shortcuts import get_apiclient'

    code += '# noinspection PyShadowingBuiltins'
    code += 'class %s(Service):' % (service.lower().capitalize())
    code.indent()

    for f in functions:

        name = normalize_func_name(f['name'])
        print('\t  %s -> %s' % (f['name'], name))
        if re.search('_[a-z]($|_)', name):
            print('Please fix Bad PHP function name')

        params, params_get, have_post = [], [], False
        shortcut_params = []
        if len(f['params']):
            for p in f['params'].get('required', tuple()):
                params.append('%s' % p)
                params_get.append('"%s": %s' % (p, p))
                shortcut_params.append(p)
            for p in f['params'].get('post', tuple()):
                params.append('postObject')
                shortcut_params.append('postObject')
                have_post = True
            for p in f['params'].get('optional', tuple()):
                params.append('%s=None' % p)
                params_get.append('"%s": %s' % (p, p))
                shortcut_params.append("%s=%s" % (p, p))

        params = (params and ',%s' % ",".join(params)) or ''

        code += 'def %s(self%s):' % (name, params)
        code.indent()
        if f['return']:
            ftypes = f['return'].split("|")
            if not isinstance(ftypes, (list, tuple)):
                ftypes = [ftypes]

            ftypes, doctypes = list(ftypes), []
            for i, ftype in enumerate(ftypes):
                fformat = '%s'
                if ftype.endswith("[]"):
                    fformat = 'list of %s'
                    ftype = ftype[0:-2]

                ftype = typealiases.get(ftype, ftype)
                if ftype not in stdtypes:
                    fmodule, fclass = module_name_from_class(ftype)
                    imports.append('from %s import %s' % (fmodule, fclass))
                    doctypes.append(fformat % fclass)
                else:
                    doctypes.append(fformat % ftype)

            code += '"""'
            code += ':rtype : %s' % "|".join(set(doctypes))
            code += '"""'

        callparams = []
        if params_get:
            callparams.append('get={%s}' % ",".join(params_get))
        if have_post:
            callparams.append('post=postObject')

        callparams = (callparams and ",%s" % ",".join(callparams)) or ''
        code += 'return self._callapi("%s"%s)' % (f['uri'], callparams)

        shortcut += 'def %s(%s):' % (name, params.lstrip(','))
        shortcut.indent()
        shortcut_params = (shortcut_params and ','.join(shortcut_params)) or ''
        shortcut += 'return get_apiclient().services.%s.%s(%s)' % (service, name, shortcut_params)
        shortcut.dedent()

        code.dedent()

    code.dedent()
    manager += 'self.%s = %s(apiclient)' % (service, service.lower().capitalize())

    shortcut = autopep8.fix_code(str(shortcut), pepoptions)
    open('floraconcierge/shortcuts/%s.py' % service, 'wb').write(shortcut)

print('Aplying pep8 formatting to services code')
code = str(code) + "\n" + str(manager)
code = autopep8.fix_code(code, pepoptions)
open('floraconcierge/services.py', 'wb').write(code)

print('Checking generated code ...')
os.system('./check.sh')
os.system('./checkmappings.py')