0001
0002
0003import pkg_resources
0004import sys
0005import optparse
0006import bool_optparse
0007import os
0008import re
0009import textwrap
0010import pluginlib
0011import ConfigParser
0012import getpass
0013try:
0014 import subprocess
0015except ImportError:
0016 from paste.script.util import subprocess24 as subprocess
0017difflib = None
0018
0019from paste.script.util.logging_config import fileConfig
0020
0021class BadCommand(Exception):
0022
0023 def __init__(self, message, exit_code=2):
0024 self.message = message
0025 self.exit_code = exit_code
0026 Exception.__init__(self, message)
0027
0028class NoDefault(object):
0029 pass
0030
0031dist = pkg_resources.get_distribution('PasteScript')
0032
0033python_version = sys.version.splitlines()[0].strip()
0034
0035parser = optparse.OptionParser(add_help_option=False,
0036 version='%s from %s (python %s)'
0037 % (dist, dist.location, python_version),
0038 usage='%prog [paster_options] COMMAND [command_options]')
0039
0040parser.add_option(
0041 '--plugin',
0042 action='append',
0043 dest='plugins',
0044 help="Add a plugin to the list of commands (plugins are Egg specs; will also require() the Egg)")
0045parser.add_option(
0046 '-h', '--help',
0047 action='store_true',
0048 dest='do_help',
0049 help="Show this help message")
0050parser.disable_interspersed_args()
0051
0052
0053
0054system_plugins = []
0055
0056def run(args=None):
0057 if (not args and
0058 len(sys.argv) >= 2
0059 and os.environ.get('_') and sys.argv[0] != os.environ['_']
0060 and os.environ['_'] == sys.argv[1]):
0061
0062 args = ['exe', os.environ['_']] + sys.argv[2:]
0063 if args is None:
0064 args = sys.argv[1:]
0065 options, args = parser.parse_args(args)
0066 options.base_parser = parser
0067 system_plugins.extend(options.plugins or [])
0068 commands = get_commands()
0069 if options.do_help:
0070 args = ['help'] + args
0071 if not args:
0072 print 'Usage: %s COMMAND' % sys.argv[0]
0073 args = ['help']
0074 command_name = args[0]
0075 if command_name not in commands:
0076 command = NotFoundCommand
0077 else:
0078 command = commands[command_name].load()
0079 invoke(command, command_name, options, args[1:])
0080
0081def parse_exe_file(config):
0082 import shlex
0083 p = ConfigParser.RawConfigParser()
0084 p.read([config])
0085 command_name = 'exe'
0086 options = []
0087 if p.has_option('exe', 'command'):
0088 command_name = p.get('exe', 'command')
0089 if p.has_option('exe', 'options'):
0090 options = shlex.split(p.get('exe', 'options'))
0091 if p.has_option('exe', 'sys.path'):
0092 paths = shlex.split(p.get('exe', 'sys.path'))
0093 paths = [os.path.abspath(os.path.join(os.path.dirname(config), p))
0094 for p in paths]
0095 for path in paths:
0096 pkg_resources.working_set.add_entry(path)
0097 sys.path.insert(0, path)
0098 args = [command_name, config] + options
0099 return args
0100
0101def get_commands():
0102 plugins = system_plugins[:]
0103 egg_info_dir = pluginlib.find_egg_info_dir(os.getcwd())
0104 if egg_info_dir:
0105 plugins.append(os.path.splitext(os.path.basename(egg_info_dir))[0])
0106 base_dir = os.path.dirname(egg_info_dir)
0107 if base_dir not in sys.path:
0108 sys.path.insert(0, base_dir)
0109 pkg_resources.working_set.add_entry(base_dir)
0110 plugins = pluginlib.resolve_plugins(plugins)
0111 commands = pluginlib.load_commands_from_plugins(plugins)
0112 commands.update(pluginlib.load_global_commands())
0113 return commands
0114
0115def invoke(command, command_name, options, args):
0116 try:
0117 runner = command(command_name)
0118 exit_code = runner.run(args)
0119 except BadCommand, e:
0120 print e.message
0121 exit_code = e.exit_code
0122 sys.exit(exit_code)
0123
0124
0125class Command(object):
0126
0127 def __init__(self, name):
0128 self.command_name = name
0129
0130 max_args = None
0131 max_args_error = 'You must provide no more than %(max_args)s arguments'
0132 min_args = None
0133 min_args_error = 'You must provide at least %(min_args)s arguments'
0134 required_args = None
0135
0136
0137
0138 takes_config_file = None
0139
0140
0141 group_name = ''
0142
0143 required_args = ()
0144 description = None
0145 usage = ''
0146 hidden = False
0147
0148
0149 default_verbosity = 0
0150
0151 default_interactive = 0
0152 return_code = 0
0153
0154 BadCommand = BadCommand
0155
0156
0157
0158
0159
0160
0161 def run(self, args):
0162 self.parse_args(args)
0163
0164
0165 for name, default in [('verbose', 0),
0166 ('quiet', 0),
0167 ('interactive', False),
0168 ('overwrite', False)]:
0169 if not hasattr(self.options, name):
0170 setattr(self.options, name, default)
0171 if getattr(self.options, 'simulate', False):
0172 self.options.verbose = max(self.options.verbose, 1)
0173 self.interactive = self.default_interactive
0174 if getattr(self.options, 'interactive', False):
0175 self.interactive += self.options.interactive
0176 if getattr(self.options, 'no_interactive', False):
0177 self.interactive = False
0178 self.verbose = self.default_verbosity
0179 self.verbose += self.options.verbose
0180 self.verbose -= self.options.quiet
0181 self.simulate = getattr(self.options, 'simulate', False)
0182
0183
0184 if (os.environ.get('PASTE_CONFIG_FILE')
0185 and self.takes_config_file is not None):
0186 take = self.takes_config_file
0187 filename = os.environ.get('PASTE_CONFIG_FILE')
0188 if take == 1:
0189 self.args.insert(0, filename)
0190 elif take == -1:
0191 self.args.append(filename)
0192 else:
0193 assert 0, (
0194 "Value takes_config_file must be None, 1, or -1 (not %r)"
0195 % take)
0196
0197 if (os.environ.get('PASTE_DEFAULT_QUIET')):
0198 self.verbose = 0
0199
0200
0201 if self.min_args is not None and len(self.args) < self.min_args:
0202 raise BadCommand(
0203 self.min_args_error % {'min_args': self.min_args,
0204 'actual_args': len(self.args)})
0205 if self.max_args is not None and len(self.args) > self.max_args:
0206 raise BadCommand(
0207 self.max_args_error % {'max_args': self.max_args,
0208 'actual_args': len(self.args)})
0209 for var_name, option_name in self.required_args:
0210 if not getattr(self.options, var_name, None):
0211 raise BadCommand(
0212 'You must provide the option %s' % option_name)
0213 result = self.command()
0214 if result is None:
0215 return self.return_code
0216 else:
0217 return result
0218
0219 def parse_args(self, args):
0220 if self.usage:
0221 usage = ' '+self.usage
0222 else:
0223 usage = ''
0224 self.parser.usage = "%%prog [options]%s\n%s" % (
0225 usage, self.summary)
0226 self.parser.prog = '%s %s' % (sys.argv[0], self.command_name)
0227 if self.description:
0228 desc = self.description
0229 desc = textwrap.dedent(desc)
0230 self.parser.description = desc
0231 self.options, self.args = self.parser.parse_args(args)
0232
0233
0234
0235
0236
0237 def here(cls):
0238 mod = sys.modules[cls.__module__]
0239 return os.path.dirname(mod.__file__)
0240
0241 here = classmethod(here)
0242
0243 def ask(self, prompt, safe=False, default=True):
0244 """
0245 Prompt the user. Default can be true, false, ``'careful'`` or
0246 ``'none'``. If ``'none'`` then the user must enter y/n. If
0247 ``'careful'`` then the user must enter yes/no (long form).
0248
0249 If the interactive option is over two (``-ii``) then ``safe``
0250 will be used as a default. This option should be the
0251 do-nothing option.
0252 """
0253
0254
0255 if self.options.interactive >= 2:
0256 default = safe
0257 if default == 'careful':
0258 prompt += ' [yes/no]?'
0259 elif default == 'none':
0260 prompt += ' [y/n]?'
0261 elif default:
0262 prompt += ' [Y/n]? '
0263 else:
0264 prompt += ' [y/N]? '
0265 while 1:
0266 response = raw_input(prompt).strip().lower()
0267 if not response:
0268 if default in ('careful', 'none'):
0269 print 'Please enter yes or no'
0270 continue
0271 return default
0272 if default == 'careful':
0273 if response in ('yes', 'no'):
0274 return response == 'yes'
0275 print 'Please enter "yes" or "no"'
0276 continue
0277 if response[0].lower() in ('y', 'n'):
0278 return response[0].lower() == 'y'
0279 print 'Y or N please'
0280
0281 def challenge(self, prompt, default=NoDefault, should_echo=True):
0282 """
0283 Prompt the user for a variable.
0284 """
0285 if default is not NoDefault:
0286 prompt += ' [%r]' % default
0287 prompt += ': '
0288 while 1:
0289 if should_echo:
0290 prompt_method = raw_input
0291 else:
0292 prompt_method = getpass.getpass
0293 response = prompt_method(prompt).strip()
0294 if not response:
0295 if default is not NoDefault:
0296 return default
0297 else:
0298 continue
0299 else:
0300 return response
0301
0302 def pad(self, s, length, dir='left'):
0303 if len(s) >= length:
0304 return s
0305 if dir == 'left':
0306 return s + ' '*(length-len(s))
0307 else:
0308 return ' '*(length-len(s)) + s
0309
0310 def standard_parser(cls, verbose=True,
0311 interactive=False,
0312 no_interactive=False,
0313 simulate=False,
0314 quiet=False,
0315 overwrite=False):
0316 """
0317 Create a standard ``OptionParser`` instance.
0318
0319 Typically used like::
0320
0321 class MyCommand(Command):
0322 parser = Command.standard_parser()
0323
0324 Subclasses may redefine ``standard_parser``, so use the
0325 nearest superclass's class method.
0326 """
0327 parser = bool_optparse.BoolOptionParser()
0328 if verbose:
0329 parser.add_option('-v', '--verbose',
0330 action='count',
0331 dest='verbose',
0332 default=0)
0333 if quiet:
0334 parser.add_option('-q', '--quiet',
0335 action='count',
0336 dest='quiet',
0337 default=0)
0338 if no_interactive:
0339 parser.add_option('--no-interactive',
0340 action="count",
0341 dest="no_interactive",
0342 default=0)
0343 if interactive:
0344 parser.add_option('-i', '--interactive',
0345 action='count',
0346 dest='interactive',
0347 default=0)
0348 if simulate:
0349 parser.add_option('-n', '--simulate',
0350 action='store_true',
0351 dest='simulate',
0352 default=False)
0353 if overwrite:
0354 parser.add_option('-f', '--overwrite',
0355 dest="overwrite",
0356 action="store_true",
0357 help="Overwrite files (warnings will be emitted for non-matching files otherwise)")
0358 return parser
0359
0360 standard_parser = classmethod(standard_parser)
0361
0362 def shorten(self, fn, *paths):
0363 """
0364 Return a shorted form of the filename (relative to the current
0365 directory), typically for displaying in messages. If
0366 ``*paths`` are present, then use os.path.join to create the
0367 full filename before shortening.
0368 """
0369 if paths:
0370 fn = os.path.join(fn, *paths)
0371 if fn.startswith(os.getcwd()):
0372 return fn[len(os.getcwd()):].lstrip(os.path.sep)
0373 else:
0374 return fn
0375
0376 def ensure_dir(self, dir, svn_add=True):
0377 """
0378 Ensure that the directory exists, creating it if necessary.
0379 Respects verbosity and simulation.
0380
0381 Adds directory to subversion if ``.svn/`` directory exists in
0382 parent, and directory was created.
0383 """
0384 dir = dir.rstrip(os.sep)
0385 if not dir:
0386
0387
0388
0389
0390
0391 return
0392 if not os.path.exists(dir):
0393 self.ensure_dir(os.path.dirname(dir))
0394 if self.verbose:
0395 print 'Creating %s' % self.shorten(dir)
0396 if not self.simulate:
0397 os.mkdir(dir)
0398 if (svn_add and
0399 os.path.exists(os.path.join(os.path.dirname(dir), '.svn'))):
0400 self.svn_command('add', dir)
0401 else:
0402 if self.verbose > 1:
0403 print "Directory already exists: %s" % self.shorten(dir)
0404
0405 def ensure_file(self, filename, content, svn_add=True):
0406 """
0407 Ensure a file named ``filename`` exists with the given
0408 content. If ``--interactive`` has been enabled, this will ask
0409 the user what to do if a file exists with different content.
0410 """
0411 global difflib
0412 assert content is not None, (
0413 "You cannot pass a content of None")
0414 self.ensure_dir(os.path.dirname(filename), svn_add=svn_add)
0415 if not os.path.exists(filename):
0416 if self.verbose:
0417 print 'Creating %s' % filename
0418 if not self.simulate:
0419 f = open(filename, 'wb')
0420 f.write(content)
0421 f.close()
0422 if svn_add and os.path.exists(os.path.join(os.path.dirname(filename), '.svn')):
0423 self.svn_command('add', filename,
0424 warn_returncode=True)
0425 return
0426 f = open(filename, 'rb')
0427 old_content = f.read()
0428 f.close()
0429 if content == old_content:
0430 if self.verbose > 1:
0431 print 'File %s matches expected content' % filename
0432 return
0433 if not self.options.overwrite:
0434 print 'Warning: file %s does not match expected content' % filename
0435 if difflib is None:
0436 import difflib
0437 diff = difflib.context_diff(
0438 content.splitlines(),
0439 old_content.splitlines(),
0440 'expected ' + filename,
0441 filename)
0442 print '\n'.join(diff)
0443 if self.interactive:
0444 while 1:
0445 s = raw_input(
0446 'Overwrite file with new content? [y/N] ').strip().lower()
0447