1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""The migrate command-line tool."""
5
6import sys
7import inspect
8import logging
9from optparse import OptionParser, BadOptionError
10
11from migrate import exceptions
12from migrate.versioning import api
13from migrate.versioning.config import *
14from migrate.versioning.util import asbool
15import six
16
17
18alias = dict(
19    s=api.script,
20    vc=api.version_control,
21    dbv=api.db_version,
22    v=api.version,
23)
24
25def alias_setup():
26    global alias
27    for key, val in six.iteritems(alias):
28        setattr(api, key, val)
29alias_setup()
30
31
32class PassiveOptionParser(OptionParser):
33
34    def _process_args(self, largs, rargs, values):
35        """little hack to support all --some_option=value parameters"""
36
37        while rargs:
38            arg = rargs[0]
39            if arg == "--":
40                del rargs[0]
41                return
42            elif arg[0:2] == "--":
43                # if parser does not know about the option
44                # pass it along (make it anonymous)
45                try:
46                    opt = arg.split('=', 1)[0]
47                    self._match_long_opt(opt)
48                except BadOptionError:
49                    largs.append(arg)
50                    del rargs[0]
51                else:
52                    self._process_long_opt(rargs, values)
53            elif arg[:1] == "-" and len(arg) > 1:
54                self._process_short_opts(rargs, values)
55            elif self.allow_interspersed_args:
56                largs.append(arg)
57                del rargs[0]
58
59def main(argv=None, **kwargs):
60    """Shell interface to :mod:`migrate.versioning.api`.
61
62    kwargs are default options that can be overriden with passing
63    --some_option as command line option
64
65    :param disable_logging: Let migrate configure logging
66    :type disable_logging: bool
67    """
68    if argv is not None:
69        argv = argv
70    else:
71        argv = list(sys.argv[1:])
72    commands = list(api.__all__)
73    commands.sort()
74
75    usage = """%%prog COMMAND ...
76
77    Available commands:
78        %s
79
80    Enter "%%prog help COMMAND" for information on a particular command.
81    """ % '\n\t'.join(["%s - %s" % (command.ljust(28), api.command_desc.get(command)) for command in commands])
82
83    parser = PassiveOptionParser(usage=usage)
84    parser.add_option("-d", "--debug",
85                     action="store_true",
86                     dest="debug",
87                     default=False,
88                     help="Shortcut to turn on DEBUG mode for logging")
89    parser.add_option("-q", "--disable_logging",
90                      action="store_true",
91                      dest="disable_logging",
92                      default=False,
93                      help="Use this option to disable logging configuration")
94    help_commands = ['help', '-h', '--help']
95    HELP = False
96
97    try:
98        command = argv.pop(0)
99        if command in help_commands:
100            HELP = True
101            command = argv.pop(0)
102    except IndexError:
103        parser.print_help()
104        return
105
106    command_func = getattr(api, command, None)
107    if command_func is None or command.startswith('_'):
108        parser.error("Invalid command %s" % command)
109
110    parser.set_usage(inspect.getdoc(command_func))
111    f_args, f_varargs, f_kwargs, f_defaults = inspect.getargspec(command_func)
112    for arg in f_args:
113        parser.add_option(
114            "--%s" % arg,
115            dest=arg,
116            action='store',
117            type="string")
118
119    # display help of the current command
120    if HELP:
121        parser.print_help()
122        return
123
124    options, args = parser.parse_args(argv)
125
126    # override kwargs with anonymous parameters
127    override_kwargs = dict()
128    for arg in list(args):
129        if arg.startswith('--'):
130            args.remove(arg)
131            if '=' in arg:
132                opt, value = arg[2:].split('=', 1)
133            else:
134                opt = arg[2:]
135                value = True
136            override_kwargs[opt] = value
137
138    # override kwargs with options if user is overwriting
139    for key, value in six.iteritems(options.__dict__):
140        if value is not None:
141            override_kwargs[key] = value
142
143    # arguments that function accepts without passed kwargs
144    f_required = list(f_args)
145    candidates = dict(kwargs)
146    candidates.update(override_kwargs)
147    for key, value in six.iteritems(candidates):
148        if key in f_args:
149            f_required.remove(key)
150
151    # map function arguments to parsed arguments
152    for arg in args:
153        try:
154            kw = f_required.pop(0)
155        except IndexError:
156            parser.error("Too many arguments for command %s: %s" % (command,
157                                                                    arg))
158        kwargs[kw] = arg
159
160    # apply overrides
161    kwargs.update(override_kwargs)
162
163    # configure options
164    for key, value in six.iteritems(options.__dict__):
165        kwargs.setdefault(key, value)
166
167    # configure logging
168    if not asbool(kwargs.pop('disable_logging', False)):
169        # filter to log =< INFO into stdout and rest to stderr
170        class SingleLevelFilter(logging.Filter):
171            def __init__(self, min=None, max=None):
172                self.min = min or 0
173                self.max = max or 100
174
175            def filter(self, record):
176                return self.min <= record.levelno <= self.max
177
178        logger = logging.getLogger()
179        h1 = logging.StreamHandler(sys.stdout)
180        f1 = SingleLevelFilter(max=logging.INFO)
181        h1.addFilter(f1)
182        h2 = logging.StreamHandler(sys.stderr)
183        f2 = SingleLevelFilter(min=logging.WARN)
184        h2.addFilter(f2)
185        logger.addHandler(h1)
186        logger.addHandler(h2)
187
188        if options.debug:
189            logger.setLevel(logging.DEBUG)
190        else:
191            logger.setLevel(logging.INFO)
192
193    log = logging.getLogger(__name__)
194
195    # check if all args are given
196    try:
197        num_defaults = len(f_defaults)
198    except TypeError:
199        num_defaults = 0
200    f_args_default = f_args[len(f_args) - num_defaults:]
201    required = list(set(f_required) - set(f_args_default))
202    required.sort()
203    if required:
204        parser.error("Not enough arguments for command %s: %s not specified" \
205            % (command, ', '.join(required)))
206
207    # handle command
208    try:
209        ret = command_func(**kwargs)
210        if ret is not None:
211            log.info(ret)
212    except (exceptions.UsageError, exceptions.KnownError) as e:
213        parser.error(e.args[0])
214
215if __name__ == "__main__":
216    main()
217