1#!/usr/bin/env python2
2
3"""Generate.py
4
5> python Generate.py -h
6"""
7
8import os
9import sys
10from getopt import getopt
11import FixPath
12import MiddleKit
13
14if sys.platform == 'win32':
15    # without this, I can't see output from uncaught exceptions!
16    # perhaps this is caused by the recent incorporation of win32all (via DataTable)?
17    sys.stderr = sys.stdout
18
19
20class Generate(object):
21
22    def databases(self):
23        """Return a list with the names of the supported database engines."""
24        return ['MSSQL', 'MySQL', 'PostgreSQL', 'SQLite']
25
26    def main(self, args=sys.argv):
27        """Main method."""
28        opt = self.options(args)
29
30        # Make or check the output directory
31        outdir = opt['outdir']
32        if not os.path.exists(outdir):
33            os.mkdir(outdir)
34        elif not os.path.isdir(outdir):
35            print 'Error: Output target, %s, is not a directory.' % outdir
36
37        # Generate
38        if 'sql' in opt:
39            print 'Generating SQL...'
40            self.generate(
41                pyClass = opt['db'] + 'SQLGenerator',
42                model = opt['model'],
43                configFilename = opt.get('config'),
44                outdir = os.path.join(outdir, 'GeneratedSQL'))
45        if 'py' in opt:
46            print 'Generating Python...'
47            self.generate(
48                pyClass = opt['db'] + 'PythonGenerator',
49                model = opt['model'],
50                configFilename = opt.get('config'),
51                outdir=outdir)
52        model = MiddleKit.Core.Model.Model(opt['model'],
53            configFilename=opt.get('config'), havePythonClasses=0)
54        model.printWarnings()
55
56    def usage(self, errorMsg=None):
57        """Print usage information."""
58        progName = os.path.basename(sys.argv[0])
59        if errorMsg:
60            print '%s: error: %s' % (progName, errorMsg)
61            print
62        print '''\
63Usage: %s --db DBNAME --model FILENAME \\
64           [--sql] [--py] [--config FILENAME] [--outdir DIRNAME]
65       %s -h | --help
66
67    * Known databases include: %s.
68    * If neither --sql nor --py are specified, both are generated.
69    * If --outdir is not specified,
70      then the base filename (sans extension) is used.
71    * --config lets you specify a different config filename inside the model.
72      This is mostly useful for the regression test suite.
73'''  % (progName, progName, ', '.join(self.databases()))
74        sys.exit(1)
75
76    def options(self, args):
77        """Get command line options."""
78        # Command line dissection
79        if isinstance(args, basestring):
80            args = args.split()
81        optPairs, files = getopt(args[1:], 'h',
82            ['help', 'db=', 'model=', 'sql', 'py', 'config=', 'outdir='])
83        if len(optPairs) < 1:
84            self.usage('Missing options.')
85        if len(files) > 0:
86            self.usage('Extra files or options passed.')
87
88        # Turn the cmd line optPairs into a dictionary
89        opt = {}
90        for key, value in optPairs:
91            if key.startswith('--'):
92                key = key[2:]
93            elif key.startswith('-'):
94                key = key[1:]
95            opt[key] = value
96
97        # Check for required opt, set defaults, etc.
98        if 'h' in opt or 'help' in opt:
99            self.usage()
100        if 'db' not in opt:
101            self.usage('No database specified.')
102        if 'model' not in opt:
103            self.usage('No model specified.')
104        if 'sql' not in opt and 'py' not in opt:
105            opt['sql'] = ''
106            opt['py'] = ''
107        if 'outdir' not in opt:
108            opt['outdir'] = os.curdir
109
110        return opt
111
112    def generate(self, pyClass, model, configFilename, outdir):
113        """Generate code using the given class, model and output directory.
114
115        The pyClass may be a string, in which case a module of the same name is
116        imported and the class extracted from that. The model may be a string,
117        in which case it is considered a filename of a model.
118        """
119        if isinstance(pyClass, basestring):
120            module = __import__(pyClass, globals())
121            pyClass = getattr(module, pyClass)
122        generator = pyClass()
123        if isinstance(model, basestring):
124            generator.readModelFileNamed(model,
125                configFilename=configFilename, havePythonClasses=False)
126        else:
127            generator.setModel(model)
128        generator.generate(outdir)
129
130
131if __name__ == '__main__':
132    Generate().main(sys.argv)
133