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