| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- #!/usr/bin/env python
- import os
- import re
- import MySQLdb as mysql
- from itertools import combinations
- from argparse import ArgumentParser
- from singplur import singularize
- PRIMARY_KEY = 'id'
- TAB = '\t'
- def php_value(data, indent=0):
- """
- Convert Python data types to PHP code. Tuples or lists are used for
- single-line arrays, sets for multiline arrays, dictionaries for associative
- arrays, and strings are encapsulated in single quotes.
- """
- if isinstance(data, str):
- return "'%s'" % data.replace("'", "\\'")
- tabs = (indent + 1) * TAB
- if type(data) in (list, tuple):
- return 'array(%s)' % ', '.join(map(php_value, data))
- elif isinstance(data, dict):
- if not data:
- return 'array()'
- lines = ["'%s' => %s" % (k, php_value(v, indent + 1))
- for k, v in data.iteritems()]
- elif isinstance(data, set):
- if not data:
- return 'array()'
- lines = [php_value(item, indent + 1) for item in data]
- else:
- return str(data)
- return 'array(\n%s\n%s)' % (',\n'.join([tabs + line for line in lines]),
- indent * TAB)
- def php_assoc((arg, kwargs)):
- assocs = ["'%s' => %s" % (k, php_value(v)) for k, v in kwargs.iteritems()]
- return 'array(%s)' % ', '.join([php_value(arg)] + assocs)
- def php_static_var(name, value):
- return 'static $%s = %s;' % (name, value)
- def php_static_array(name, values):
- array = 'array(%s)' if len(values) < 2 \
- else 'array(\n{0}{0}%s\n{0})'.format(TAB)
- lines = map(php_assoc, values)
- array_values = (',\n' + 2 * TAB).join(lines)
- return php_static_var(name, array % array_values)
- def php_block(php_code):
- return '<?php\n\n%s\n\n?>' % php_code
- class Model(object):
- def __init__(self, table, options):
- self.table = table
- self.options = options
- self.attributes = []
- self.primary_key = []
- self.foreign_keys = []
- self.has_many = []
- self.has_one = []
- self.belongs_to = []
- def __str__(self):
- return '<Model "%s" table=%s>' % (self.name(), self.table)
- def singular_name(self):
- return singularize(self.table)
- def classname(self):
- return singularize(re.sub('(?:^|_)([a-z])',
- lambda m: m.group(1).upper(), self.table))
- def read_attributes(self, conn):
- fields = read_fields(conn, self.table)
- is_protected = lambda field: field['Key'] in ('PRI', 'MUL')
- self.protected_fields, accessible = partition(is_protected, fields)
- self.accessible_attr = [field['Field'] for field in accessible]
- def process_keys(self, models):
- for field in self.protected_fields:
- name = field['Field']
- if field['Key'] == 'PRI':
- self.primary_key.append(name)
- elif field['Key'] == 'MUL':
- self.add_foreign_key(name, models)
- # Has-many through
- if len(self.belongs_to) > 1:
- relations = [find_model(name, models)
- for name, options in self.belongs_to]
- for a, b in combinations(relations, 2):
- a.add_has_relation(b, through=self.table)
- b.add_has_relation(a, through=self.table)
- def add_has_relation(self, model, **kwargs):
- options = self.relopts(model)
- options.update(kwargs)
- name = model.singular_name()
- if [self.singular_name(), name] in self.options.get('has_one', []):
- self.has_one.append((name, options))
- else:
- self.has_many.append((model.table, options))
- def relopts(self, model, **kwargs):
- relopts = kwargs
- if self.options.get('create_select', True):
- relopts['select'] = PRIMARY_KEY + ', ' \
- + ', '.join(model.accessible_attr)
- return relopts
- def add_foreign_key(self, name, models):
- self.foreign_keys.append(name)
- singular = re.sub('_' + PRIMARY_KEY + '$', '', name)
- try:
- other_model = find_model(singular, models)
- except ValueError:
- return
- self.belongs_to.append((singular, self.relopts(other_model)))
- other_model.add_has_relation(self)
- def strip_array(self, attr):
- array = getattr(self, attr)
- if len(array) == 1:
- relation, opts = array[0]
- if not len(opts):
- return php_static_var(attr, php_value(relation))
- def php_has_many(self):
- stripped = self.strip_array('has_many')
- if stripped:
- return stripped
- if self.has_many:
- return php_static_array('has_many', self.has_many)
- def php_has_one(self):
- stripped = self.strip_array('has_one')
- if stripped:
- return stripped
- if self.has_one:
- return php_static_array('has_one', self.has_one)
- def php_belongs_to(self):
- stripped = self.strip_array('belongs_to')
- if stripped:
- return stripped
- if self.belongs_to:
- return php_static_array('belongs_to', self.belongs_to)
- def php_has_many_and_belongs_to(self):
- if not self.has_many or not self.belongs_to:
- return
- def php_attr_accessible(self):
- # All attributes except primary/foreign keys are accessible
- if self.accessible_attr:
- return php_static_var('attr_accessible',
- php_value(self.accessible_attr))
- def generate_php(self, add_php_block=False):
- # Class definition
- classname = self.classname()
- if self.options.get('namespace'):
- namespace = 'namespace %s;\n\n' % self.options['namespace']
- #classname = options['namespace'] + '\\' + classname
- else:
- namespace = ''
- classdef = 'class %s extends ActiveRecord\\Model {\n%%s\n}' % classname
- # Indented lines within class definition
- lines = []
- if self.options.get('create_relations', True):
- lines += [
- self.php_has_many(),
- self.php_has_one(),
- self.php_belongs_to(),
- self.php_has_many_and_belongs_to(),
- ]
- if self.options.get('create_accessible', True):
- lines.append(self.php_attr_accessible())
- inner_lines = [TAB + line for line in lines if line is not None]
- php = namespace + classdef % '\n'.join(inner_lines)
- return php_block(php) if add_php_block else php
- def filename(self):
- return self.classname().replace('_', '') + '.php'
- def create_path_from_dir(self, dirname):
- return os.path.dirname(dirname + '//') + '/' + self.filename()
- def save_in_file(self, path):
- code = self.generate_php(True)
- f = open(path, 'w')
- f.write(code)
- f.close()
- def find_model(singular_name, models):
- for model in models:
- if model.singular_name() == singular_name:
- return model
- raise ValueError('No model found for "%s".' % singular_name)
- def flatten(iterable):
- return reduce(lambda a, b: a + b, map(list, iterable), [])
- def create_models(conn, options):
- models = [Model(table, options) for table in read_tables(conn)]
- for model in models:
- model.read_attributes(conn)
- for model in models:
- model.process_keys(models)
- return models
- def refine_model(conn, model, models):
- for field in read_fields(conn, model.table):
- model.add_attribute(field, models)
- def read_tables(conn):
- cur = conn.cursor()
- cur.execute('show tables')
- tables = flatten(cur.fetchall())
- cur.close()
- return tables
- def read_fields(conn, table):
- cur = conn.cursor(mysql.cursors.DictCursor)
- cur.execute('show columns from `%s`' % table)
- fields = cur.fetchall()
- cur.close()
- return fields
- def partition(callback, iterable):
- """
- Partition an iterable into two parts using a callback that returns a
- boolean.
- Example:
- >>> partition(lambda x: x & 1, range(6))
- ([1, 3, 5], [0, 2, 4])
- """
- a, b = [], []
- for item in iterable:
- (a if callback(item) else b).append(item)
- return a, b
- def parse_options():
- parser = ArgumentParser(description='Generate PHPActiveRecord models.')
- parser.add_argument('dbname', help='database name')
- parser.add_argument('-H', '--host', metavar='ADDRESS', default='localhost',
- help='MySQL server address')
- parser.add_argument('-u', '--user', metavar='USERNAME', default='root',
- help='MySQL username')
- parser.add_argument('-p', '--password', default='', help='MySQL password')
- parser.add_argument('--has-one', nargs=2, dest='opt_has_one',
- help='one-to-one relationships of the form '
- '\'payment receipt\', where one payment has one '
- 'receipt', action='append', default=[])
- parser.add_argument('--namespace', default='', dest='opt_namespace',
- help='PHP namespace to create models classes in')
- parser.add_argument('--create-select', action='store_true',
- help='whether to create the \'select\' option in '
- 'relations', dest='opt_create_select')
- parser.add_argument('--create-accessible', dest='opt_create_accessible',
- help='whether to create the \'$attr_accessible\' '
- 'variable, containing all ' 'attributes except '
- 'primary or foreing keys', action='store_true')
- parser.add_argument('-a', '--create-all', action='store_true',
- help='create all available options and variables')
- parser.add_argument('-d', '--dir', help='directory to save model files in')
- parser.add_argument('-s', '--spaces', nargs='?', const=4, type=int,
- help='use spaces instead of tabs (4 by default)')
- args = parser.parse_args()
- options = {}
- for arg, value in args._get_kwargs():
- if arg[:4] == 'opt_':
- options[arg[4:]] = value
- if args.create_all:
- options['create_select'] = options['create_accessible'] = True
- if args.spaces:
- TAB = args.spaces * ' '
- return args, options
- if __name__ == '__main__': # pragma: nocover
- args, options = parse_options()
- conn = mysql.connect(args.host, args.user, args.password, args.dbname)
- models = create_models(conn, options)
- conn.close()
- if args.dir:
- if not os.path.exists(args.dir):
- os.makedirs(args.dir)
- for model in models:
- path = model.create_path_from_dir(args.dir)
- model.save_in_file(path)
- print 'Saved model %s in %s' % (model.classname(), path)
- else:
- for model in models:
- print model.generate_php()
|