| 1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) |
|---|
| 2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php |
|---|
| 3 | import pkg_resources |
|---|
| 4 | import sys |
|---|
| 5 | import optparse |
|---|
| 6 | import bool_optparse |
|---|
| 7 | import os |
|---|
| 8 | import re |
|---|
| 9 | import textwrap |
|---|
| 10 | import pluginlib |
|---|
| 11 | import ConfigParser |
|---|
| 12 | import getpass |
|---|
| 13 | try: |
|---|
| 14 | import subprocess |
|---|
| 15 | except ImportError: |
|---|
| 16 | from paste.script.util import subprocess24 as subprocess |
|---|
| 17 | difflib = None |
|---|
| 18 | |
|---|
| 19 | if sys.version_info >= (2, 6): |
|---|
| 20 | from logging.config import fileConfig |
|---|
| 21 | else: |
|---|
| 22 | # Use our custom fileConfig -- 2.5.1's with a custom Formatter class |
|---|
| 23 | # and less strict whitespace (which were incorporated into 2.6's) |
|---|
| 24 | from paste.script.util.logging_config import fileConfig |
|---|
| 25 | |
|---|
| 26 | class BadCommand(Exception): |
|---|
| 27 | |
|---|
| 28 | def __init__(self, message, exit_code=2): |
|---|
| 29 | self.message = message |
|---|
| 30 | self.exit_code = exit_code |
|---|
| 31 | Exception.__init__(self, message) |
|---|
| 32 | |
|---|
| 33 | class NoDefault(object): |
|---|
| 34 | pass |
|---|
| 35 | |
|---|
| 36 | dist = pkg_resources.get_distribution('PasteScript') |
|---|
| 37 | |
|---|
| 38 | python_version = sys.version.splitlines()[0].strip() |
|---|
| 39 | |
|---|
| 40 | parser = optparse.OptionParser(add_help_option=False, |
|---|
| 41 | version='%s from %s (python %s)' |
|---|
| 42 | % (dist, dist.location, python_version), |
|---|
| 43 | usage='%prog [paster_options] COMMAND [command_options]') |
|---|
| 44 | |
|---|
| 45 | parser.add_option( |
|---|
| 46 | '--plugin', |
|---|
| 47 | action='append', |
|---|
| 48 | dest='plugins', |
|---|
| 49 | help="Add a plugin to the list of commands (plugins are Egg specs; will also require() the Egg)") |
|---|
| 50 | parser.add_option( |
|---|
| 51 | '-h', '--help', |
|---|
| 52 | action='store_true', |
|---|
| 53 | dest='do_help', |
|---|
| 54 | help="Show this help message") |
|---|
| 55 | parser.disable_interspersed_args() |
|---|
| 56 | |
|---|
| 57 | # @@: Add an option to run this in another Python interpreter |
|---|
| 58 | |
|---|
| 59 | system_plugins = [] |
|---|
| 60 | |
|---|
| 61 | def run(args=None): |
|---|
| 62 | if (not args and |
|---|
| 63 | len(sys.argv) >= 2 |
|---|
| 64 | and os.environ.get('_') and sys.argv[0] != os.environ['_'] |
|---|
| 65 | and os.environ['_'] == sys.argv[1]): |
|---|
| 66 | # probably it's an exe execution |
|---|
| 67 | args = ['exe', os.environ['_']] + sys.argv[2:] |
|---|
| 68 | if args is None: |
|---|
| 69 | args = sys.argv[1:] |
|---|
| 70 | options, args = parser.parse_args(args) |
|---|
| 71 | options.base_parser = parser |
|---|
| 72 | system_plugins.extend(options.plugins or []) |
|---|
| 73 | commands = get_commands() |
|---|
| 74 | if options.do_help: |
|---|
| 75 | args = ['help'] + args |
|---|
| 76 | if not args: |
|---|
| 77 | print 'Usage: %s COMMAND' % sys.argv[0] |
|---|
| 78 | args = ['help'] |
|---|
| 79 | command_name = args[0] |
|---|
| 80 | if command_name not in commands: |
|---|
| 81 | command = NotFoundCommand |
|---|
| 82 | else: |
|---|
| 83 | command = commands[command_name].load() |
|---|
| 84 | invoke(command, command_name, options, args[1:]) |
|---|
| 85 | |
|---|
| 86 | def parse_exe_file(config): |
|---|
| 87 | import shlex |
|---|
| 88 | p = ConfigParser.RawConfigParser() |
|---|
| 89 | p.read([config]) |
|---|
| 90 | command_name = 'exe' |
|---|
| 91 | options = [] |
|---|
| 92 | if p.has_option('exe', 'command'): |
|---|
| 93 | command_name = p.get('exe', 'command') |
|---|
| 94 | if p.has_option('exe', 'options'): |
|---|
| 95 | options = shlex.split(p.get('exe', 'options')) |
|---|
| 96 | if p.has_option('exe', 'sys.path'): |
|---|
| 97 | paths = shlex.split(p.get('exe', 'sys.path')) |
|---|
| 98 | paths = [os.path.abspath(os.path.join(os.path.dirname(config), p)) |
|---|
| 99 | for p in paths] |
|---|
| 100 | for path in paths: |
|---|
| 101 | pkg_resources.working_set.add_entry(path) |
|---|
| 102 | sys.path.insert(0, path) |
|---|
| 103 | args = [command_name, config] + options |
|---|
| 104 | return args |
|---|
| 105 | |
|---|
| 106 | def get_commands(): |
|---|
| 107 | plugins = system_plugins[:] |
|---|
| 108 | egg_info_dir = pluginlib.find_egg_info_dir(os.getcwd()) |
|---|
| 109 | if egg_info_dir: |
|---|
| 110 | plugins.append(os.path.splitext(os.path.basename(egg_info_dir))[0]) |
|---|
| 111 | base_dir = os.path.dirname(egg_info_dir) |
|---|
| 112 | if base_dir not in sys.path: |
|---|
| 113 | sys.path.insert(0, base_dir) |
|---|
| 114 | pkg_resources.working_set.add_entry(base_dir) |
|---|
| 115 | plugins = pluginlib.resolve_plugins(plugins) |
|---|
| 116 | commands = pluginlib.load_commands_from_plugins(plugins) |
|---|
| 117 | commands.update(pluginlib.load_global_commands()) |
|---|
| 118 | return commands |
|---|
| 119 | |
|---|
| 120 | def invoke(command, command_name, options, args): |
|---|
| 121 | try: |
|---|
| 122 | runner = command(command_name) |
|---|
| 123 | exit_code = runner.run(args) |
|---|
| 124 | except BadCommand, e: |
|---|
| 125 | print e.message |
|---|
| 126 | exit_code = e.exit_code |
|---|
| 127 | sys.exit(exit_code) |
|---|
| 128 | |
|---|
| 129 | |
|---|
| 130 | class Command(object): |
|---|
| 131 | |
|---|
| 132 | def __init__(self, name): |
|---|
| 133 | self.command_name = name |
|---|
| 134 | |
|---|
| 135 | max_args = None |
|---|
| 136 | max_args_error = 'You must provide no more than %(max_args)s arguments' |
|---|
| 137 | min_args = None |
|---|
| 138 | min_args_error = 'You must provide at least %(min_args)s arguments' |
|---|
| 139 | required_args = None |
|---|
| 140 | # If this command takes a configuration file, set this to 1 or -1 |
|---|
| 141 | # Then if invoked through #! the config file will be put into the positional |
|---|
| 142 | # arguments -- at the beginning with 1, at the end with -1 |
|---|
| 143 | takes_config_file = None |
|---|
| 144 | |
|---|
| 145 | # Grouped in help messages by this: |
|---|
| 146 | group_name = '' |
|---|
| 147 | |
|---|
| 148 | required_args = () |
|---|
| 149 | description = None |
|---|
| 150 | usage = '' |
|---|
| 151 | hidden = False |
|---|
| 152 | # This is the default verbosity level; --quiet subtracts, |
|---|
| 153 | # --verbose adds: |
|---|
| 154 | default_verbosity = 0 |
|---|
| 155 | # This is the default interactive state: |
|---|
| 156 | default_interactive = 0 |
|---|
| 157 | return_code = 0 |
|---|
| 158 | |
|---|
| 159 | BadCommand = BadCommand |
|---|
| 160 | |
|---|
| 161 | # Must define: |
|---|
| 162 | # parser |
|---|
| 163 | # summary |
|---|
| 164 | # command() |
|---|
| 165 | |
|---|
| 166 | def run(self, args): |
|---|
| 167 | self.parse_args(args) |
|---|
| 168 | |
|---|
| 169 | # Setup defaults: |
|---|
| 170 | for name, default in [('verbose', 0), |
|---|
| 171 | ('quiet', 0), |
|---|
| 172 | ('interactive', False), |
|---|
| 173 | ('overwrite', False)]: |
|---|
| 174 | if not hasattr(self.options, name): |
|---|
| 175 | setattr(self.options, name, default) |
|---|
| 176 | if getattr(self.options, 'simulate', False): |
|---|
| 177 | self.options.verbose = max(self.options.verbose, 1) |
|---|
| 178 | self.interactive = self.default_interactive |
|---|
| 179 | if getattr(self.options, 'interactive', False): |
|---|
| 180 | self.interactive += self.options.interactive |
|---|
| 181 | if getattr(self.options, 'no_interactive', False): |
|---|
| 182 | self.interactive = False |
|---|
| 183 | self.verbose = self.default_verbosity |
|---|
| 184 | self.verbose += self.options.verbose |
|---|
| 185 | self.verbose -= self.options.quiet |
|---|
| 186 | self.simulate = getattr(self.options, 'simulate', False) |
|---|
| 187 | |
|---|
| 188 | # For #! situations: |
|---|
| 189 | if (os.environ.get('PASTE_CONFIG_FILE') |
|---|
| 190 | and self.takes_config_file is not None): |
|---|
| 191 | take = self.takes_config_file |
|---|
| 192 | filename = os.environ.get('PASTE_CONFIG_FILE') |
|---|
| 193 | if take == 1: |
|---|
| 194 | self.args.insert(0, filename) |
|---|
| 195 | elif take == -1: |
|---|
| 196 | self.args.append(filename) |
|---|
| 197 | else: |
|---|
| 198 | assert 0, ( |
|---|
| 199 | "Value takes_config_file must be None, 1, or -1 (not %r)" |
|---|
| 200 | % take) |
|---|
| 201 | |
|---|
| 202 | if (os.environ.get('PASTE_DEFAULT_QUIET')): |
|---|
| 203 | self.verbose = 0 |
|---|
| 204 | |
|---|
| 205 | # Validate: |
|---|
| 206 | if self.min_args is not None and len(self.args) < self.min_args: |
|---|
| 207 | raise BadCommand( |
|---|
| 208 | self.min_args_error % {'min_args': self.min_args, |
|---|
| 209 | 'actual_args': len(self.args)}) |
|---|
| 210 | if self.max_args is not None and len(self.args) > self.max_args: |
|---|
| 211 | raise BadCommand( |
|---|
| 212 | self.max_args_error % {'max_args': self.max_args, |
|---|
| 213 | 'actual_args': len(self.args)}) |
|---|
| 214 | for var_name, option_name in self.required_args: |
|---|
| 215 | if not getattr(self.options, var_name, None): |
|---|
| 216 | raise BadCommand( |
|---|
| 217 | 'You must provide the option %s' % option_name) |
|---|
| 218 | result = self.command() |
|---|
| 219 | if result is None: |
|---|
| 220 | return self.return_code |
|---|
| 221 | else: |
|---|
| 222 | return result |
|---|
| 223 | |
|---|
| 224 | def parse_args(self, args): |
|---|
| 225 | if self.usage: |
|---|
| 226 | usage = ' '+self.usage |
|---|
| 227 | else: |
|---|
| 228 | usage = '' |
|---|
| 229 | self.parser.usage = "%%prog [options]%s\n%s" % ( |
|---|
| 230 | usage, self.summary) |
|---|
| 231 | self.parser.prog = '%s %s' % (sys.argv[0], self.command_name) |
|---|
| 232 | if self.description: |
|---|
| 233 | desc = self.description |
|---|
| 234 | desc = textwrap.dedent(desc) |
|---|
| 235 | self.parser.description = desc |
|---|
| 236 | self.options, self.args = self.parser.parse_args(args) |
|---|
| 237 | |
|---|
| 238 | ######################################## |
|---|
| 239 | ## Utility methods |
|---|
| 240 | ######################################## |
|---|
| 241 | |
|---|
| 242 | def here(cls): |
|---|
| 243 | mod = sys.modules[cls.__module__] |
|---|
| 244 | return os.path.dirname(mod.__file__) |
|---|
| 245 | |
|---|
| 246 | here = classmethod(here) |
|---|
| 247 | |
|---|
| 248 | def ask(self, prompt, safe=False, default=True): |
|---|
| 249 | """ |
|---|
| 250 | Prompt the user. Default can be true, false, ``'careful'`` or |
|---|
| 251 | ``'none'``. If ``'none'`` then the user must enter y/n. If |
|---|
| 252 | ``'careful'`` then the user must enter yes/no (long form). |
|---|
| 253 | |
|---|
| 254 | If the interactive option is over two (``-ii``) then ``safe`` |
|---|
| 255 | will be used as a default. This option should be the |
|---|
| 256 | do-nothing option. |
|---|
| 257 | """ |
|---|
| 258 | # @@: Should careful be a separate argument? |
|---|
| 259 | |
|---|
| 260 | if self.options.interactive >= 2: |
|---|
| 261 | default = safe |
|---|
| 262 | if default == 'careful': |
|---|
| 263 | prompt += ' [yes/no]?' |
|---|
| 264 | elif default == 'none': |
|---|
| 265 | prompt += ' [y/n]?' |
|---|
| 266 | elif default: |
|---|
| 267 | prompt += ' [Y/n]? ' |
|---|
| 268 | else: |
|---|
| 269 | prompt += ' [y/N]? ' |
|---|
| 270 | while 1: |
|---|
| 271 | response = raw_input(prompt).strip().lower() |
|---|
| 272 | if not response: |
|---|
| 273 | if default in ('careful', 'none'): |
|---|
| 274 | print 'Please enter yes or no' |
|---|
| 275 | continue |
|---|
| 276 | return default |
|---|
| 277 | if default == 'careful': |
|---|
| 278 | if response in ('yes', 'no'): |
|---|
| 279 | return response == 'yes' |
|---|
| 280 | print 'Please enter "yes" or "no"' |
|---|
| 281 | continue |
|---|
| 282 | if response[0].lower() in ('y', 'n'): |
|---|
| 283 | return response[0].lower() == 'y' |
|---|
| 284 | print 'Y or N please' |
|---|
| 285 | |
|---|
| 286 | def challenge(self, prompt, default=NoDefault, should_echo=True): |
|---|
| 287 | """ |
|---|
| 288 | Prompt the user for a variable. |
|---|
| 289 | """ |
|---|
| 290 | if default is not NoDefault: |
|---|
| 291 | prompt += ' [%r]' % default |
|---|
| 292 | prompt += ': ' |
|---|
| 293 | while 1: |
|---|
| 294 | if should_echo: |
|---|
| 295 | prompt_method = raw_input |
|---|
| 296 | else: |
|---|
| 297 | prompt_method = getpass.getpass |
|---|
| 298 | response = prompt_method(prompt).strip() |
|---|
| 299 | if not response: |
|---|
| 300 | if default is not NoDefault: |
|---|
| 301 | return default |
|---|
| 302 | else: |
|---|
| 303 | continue |
|---|
| 304 | else: |
|---|
| 305 | return response |
|---|
| 306 | |
|---|
| 307 | def pad(self, s, length, dir='left'): |
|---|
| 308 | if len(s) >= length: |
|---|
| 309 | return s |
|---|
| 310 | if dir == 'left': |
|---|
| 311 | return s + ' '*(length-len(s)) |
|---|
| 312 | else: |
|---|
| 313 | return ' '*(length-len(s)) + s |
|---|
| 314 | |
|---|
| 315 | def standard_parser(cls, verbose=True, |
|---|
| 316 | interactive=False, |
|---|
| 317 | no_interactive=False, |
|---|
| 318 | simulate=False, |
|---|
| 319 | quiet=False, |
|---|
| 320 | overwrite=False): |
|---|
| 321 | """ |
|---|
| 322 | Create a standard ``OptionParser`` instance. |
|---|
| 323 | |
|---|
| 324 | Typically used like:: |
|---|
| 325 | |
|---|
| 326 | class MyCommand(Command): |
|---|
| 327 | parser = Command.standard_parser() |
|---|
| 328 | |
|---|
| 329 | Subclasses may redefine ``standard_parser``, so use the |
|---|
| 330 | nearest superclass's class method. |
|---|
| 331 | """ |
|---|
| 332 | parser = bool_optparse.BoolOptionParser() |
|---|
| 333 | if verbose: |
|---|
| 334 | parser.add_option('-v', '--verbose', |
|---|
| 335 | action='count', |
|---|
| 336 | dest='verbose', |
|---|
| 337 | default=0) |
|---|
| 338 | if quiet: |
|---|
| 339 | parser.add_option('-q', '--quiet', |
|---|
| 340 | action='count', |
|---|
| 341 | dest='quiet', |
|---|
| 342 | default=0) |
|---|
| 343 | if no_interactive: |
|---|
| 344 | parser.add_option('--no-interactive', |
|---|
| 345 | action="count", |
|---|
| 346 | dest="no_interactive", |
|---|
| 347 | default=0) |
|---|
| 348 | if interactive: |
|---|
| 349 | parser.add_option('-i', '--interactive', |
|---|
| 350 | action='count', |
|---|
| 351 | dest='interactive', |
|---|
| 352 | default=0) |
|---|
| 353 | if simulate: |
|---|
| 354 | parser.add_option('-n', '--simulate', |
|---|
| 355 | action='store_true', |
|---|
| 356 | dest='simulate', |
|---|
| 357 | default=False) |
|---|
| 358 | if overwrite: |
|---|
| 359 | parser.add_option('-f', '--overwrite', |
|---|
| 360 | dest="overwrite", |
|---|
| 361 | action="store_true", |
|---|
| 362 | help="Overwrite files (warnings will be emitted for non-matching files otherwise)") |
|---|
| 363 | return parser |
|---|
| 364 | |
|---|
| 365 | standard_parser = classmethod(standard_parser) |
|---|
| 366 | |
|---|
| 367 | def shorten(self, fn, *paths): |
|---|
| 368 | """ |
|---|
| 369 | Return a shorted form of the filename (relative to the current |
|---|
| 370 | directory), typically for displaying in messages. If |
|---|
| 371 | ``*paths`` are present, then use os.path.join to create the |
|---|
| 372 | full filename before shortening. |
|---|
| 373 | """ |
|---|
| 374 | if paths: |
|---|
| 375 | fn = os.path.join(fn, *paths) |
|---|
| 376 | if fn.startswith(os.getcwd()): |
|---|
| 377 | return fn[len(os.getcwd()):].lstrip(os.path.sep) |
|---|
| 378 | else: |
|---|
| 379 | return fn |
|---|
| 380 | |
|---|
| 381 | def ensure_dir(self, dir, svn_add=True): |
|---|
| 382 | """ |
|---|
| 383 | Ensure that the directory exists, creating it if necessary. |
|---|
| 384 | Respects verbosity and simulation. |
|---|
| 385 | |
|---|
| 386 | Adds directory to subversion if ``.svn/`` directory exists in |
|---|
| 387 | parent, and directory was created. |
|---|
| 388 | """ |
|---|
| 389 | dir = dir.rstrip(os.sep) |
|---|
| 390 | if not dir: |
|---|
| 391 | # we either reached the parent-most directory, or we got |
|---|
| 392 | # a relative directory |
|---|
| 393 | # @@: Should we make sure we resolve relative directories |
|---|
| 394 | # first? Though presumably the current directory always |
|---|
| 395 | # exists. |
|---|
| 396 | return |
|---|
| 397 | if not os.path.exists(dir): |
|---|
| 398 | self.ensure_dir(os.path.dirname(dir)) |
|---|
| 399 | if self.verbose: |
|---|
| 400 | print 'Creating %s' % self.shorten(dir) |
|---|
| 401 | if not self.simulate: |
|---|
| 402 | os.mkdir(dir) |
|---|
| 403 | if (svn_add and |
|---|
| 404 | os.path.exists(os.path.join(os.path.dirname(dir), '.svn'))): |
|---|
| 405 | self.svn_command('add', dir) |
|---|
| 406 | else: |
|---|
| 407 | if self.verbose > 1: |
|---|
| 408 | print "Directory already exists: %s" % self.shorten(dir) |
|---|
| 409 | |
|---|
| 410 | def ensure_file(self, filename, content, svn_add=True): |
|---|
| 411 | """ |
|---|
| 412 | Ensure a file named ``filename`` exists with the given |
|---|
| 413 | content. If ``--interactive`` has been enabled, this will ask |
|---|
| 414 | the user what to do if a file exists with different content. |
|---|
| 415 | """ |
|---|
| 416 | global difflib |
|---|
| 417 | assert content is not None, ( |
|---|
| 418 | "You cannot pass a content of None") |
|---|
| 419 | self.ensure_dir(os.path.dirname(filename), svn_add=svn_add) |
|---|
| 420 | if not os.path.exists(filename): |
|---|
| 421 | if self.verbose: |
|---|
| 422 | print 'Creating %s' % filename |
|---|
| 423 | if not self.simulate: |
|---|
| 424 | f = open(filename, 'wb') |
|---|
| 425 | f.write(content) |
|---|
| 426 | f.close() |
|---|
| 427 | if svn_add and os.path.exists(os.path.join(os.path.dirname(filename), '.svn')): |
|---|
| 428 | self.svn_command('add', filename, |
|---|
| 429 | warn_returncode=True) |
|---|
| 430 | return |
|---|
| 431 | f = open(filename, 'rb') |
|---|
| 432 | old_content = f.read() |
|---|
| 433 | f.close() |
|---|
| 434 | if content == old_content: |
|---|
| 435 | if self.verbose > 1: |
|---|
| 436 | print 'File %s matches expected content' % filename |
|---|
| 437 | return |
|---|
| 438 | if not self.options.overwrite: |
|---|
| 439 | print 'Warning: file %s does not match expected content' % filename |
|---|
| 440 | if difflib is None: |
|---|
| 441 | import difflib |
|---|
| 442 | diff = difflib.context_diff( |
|---|
| 443 | content.splitlines(), |
|---|
| 444 | old_content.splitlines(), |
|---|
| 445 | 'expected ' + filename, |
|---|
| 446 | filename) |
|---|
| 447 | print '\n'.join(diff) |
|---|
| 448 | if self.interactive: |
|---|
| 449 | while 1: |
|---|
| 450 | s = raw_input( |
|---|
| 451 | 'Overwrite file with new content? [y/N] ').strip().lower() |
|---|
| 452 | if not s: |
|---|
| 453 | s = 'n' |
|---|
| 454 | if s.startswith('y'): |
|---|
| 455 | break |
|---|
| 456 | if s.startswith('n'): |
|---|
| 457 | return |
|---|
| 458 | print 'Unknown response; Y or N please' |
|---|
| 459 | else: |
|---|
| 460 | return |
|---|
| 461 | |
|---|
| 462 | if self.verbose: |
|---|
| 463 | print 'Overwriting %s with new content' % filename |
|---|
| 464 | if not self.simulate: |
|---|
| 465 | f = open(filename, 'wb') |
|---|
| 466 | f.write(content) |
|---|
| 467 | f.close() |
|---|
| 468 | |
|---|
| 469 | def insert_into_file(self, filename, marker_name, text, |
|---|
| 470 | indent=False): |
|---|
| 471 | """ |
|---|
| 472 | Inserts ``text`` into the file, right after the given marker. |
|---|
| 473 | Markers look like: ``-*- <marker_name>[:]? -*-``, and the text |
|---|
| 474 | will go on the immediately following line. |
|---|
| 475 | |
|---|
| 476 | Raises ``ValueError`` if the marker is not found. |
|---|
| 477 | |
|---|
| 478 | If ``indent`` is true, then the text will be indented at the |
|---|
| 479 | same level as the marker. |
|---|
| 480 | """ |
|---|
| 481 | if not text.endswith('\n'): |
|---|
| 482 | raise ValueError( |
|---|
| 483 | "The text must end with a newline: %r" % text) |
|---|
| 484 | if not os.path.exists(filename) and self.simulate: |
|---|
| 485 | # If we are doing a simulation, it's expected that some |
|---|
| 486 | # files won't exist... |
|---|
| 487 | if self.verbose: |
|---|
| 488 | print 'Would (if not simulating) insert text into %s' % ( |
|---|
| 489 | self.shorten(filename)) |
|---|
| 490 | return |
|---|
| 491 | |
|---|
| 492 | f = open(filename) |
|---|
| 493 | lines = f.readlines() |
|---|
| 494 | f.close() |
|---|
| 495 | regex = re.compile(r'-\*-\s+%s:?\s+-\*-' % re.escape(marker_name), |
|---|
| 496 | re.I) |
|---|
| 497 | for i in range(len(lines)): |
|---|
| 498 | if regex.search(lines[i]): |
|---|
| 499 | # Found it! |
|---|
| 500 | if (lines[i:] and len(lines[i:]) > 1 and |
|---|
| 501 | ''.join(lines[i+1:]).strip().startswith(text.strip())): |
|---|
| 502 | # Already have it! |
|---|
| 503 | print 'Warning: line already found in %s (not inserting' % filename |
|---|
| 504 | print ' %s' % lines[i] |
|---|
| 505 | return |
|---|
| 506 | |
|---|
| 507 | if indent: |
|---|
| 508 | text = text.lstrip() |
|---|
| 509 | match = re.search(r'^[ \t]*', lines[i]) |
|---|
| 510 | text = match.group(0) + text |
|---|
| 511 | lines[i+1:i+1] = [text] |
|---|
| 512 | break |
|---|
| 513 | else: |
|---|
| 514 | errstr = ( |
|---|
| 515 | "Marker '-*- %s -*-' not found in %s" |
|---|
| 516 | % (marker_name, filename)) |
|---|
| 517 | if 1 or self.simulate: # @@: being permissive right now |
|---|
| 518 | print 'Warning: %s' % errstr |
|---|
| 519 | else: |
|---|
| 520 | raise ValueError(errstr) |
|---|
| 521 | if self.verbose: |
|---|
| 522 | print 'Updating %s' % self.shorten(filename) |
|---|
| 523 | if not self.simulate: |
|---|
| 524 | f = open(filename, 'w') |
|---|
| 525 | f.write(''.join(lines)) |
|---|
| 526 | f.close() |
|---|
| 527 | |
|---|
| 528 | def run_command(self, cmd, *args, **kw): |
|---|
| 529 | """ |
|---|
| 530 | Runs the command, respecting verbosity and simulation. |
|---|
| 531 | Returns stdout, or None if simulating. |
|---|
| 532 | |
|---|
| 533 | Keyword arguments: |
|---|
| 534 | |
|---|
| 535 | cwd: |
|---|
| 536 | the current working directory to run the command in |
|---|
| 537 | capture_stderr: |
|---|
| 538 | if true, then both stdout and stderr will be returned |
|---|
| 539 | expect_returncode: |
|---|
| 540 | if true, then don't fail if the return code is not 0 |
|---|
| 541 | force_no_simulate: |
|---|
| 542 | if true, run the command even if --simulate |
|---|
| 543 | """ |
|---|
| 544 | cmd = self.quote_first_command_arg(cmd) |
|---|
| 545 | cwd = popdefault(kw, 'cwd', os.getcwd()) |
|---|
| 546 | capture_stderr = popdefault(kw, 'capture_stderr', False) |
|---|
| 547 | expect_returncode = popdefault(kw, 'expect_returncode', False) |
|---|
| 548 | force = popdefault(kw, 'force_no_simulate', False) |
|---|
| 549 | warn_returncode = popdefault(kw, 'warn_returncode', False) |
|---|
| 550 | if warn_returncode: |
|---|
| 551 | expect_returncode = True |
|---|
| 552 | simulate = self.simulate |
|---|
| 553 | if force: |
|---|
| 554 | simulate = False |
|---|
| 555 | assert not kw, ("Arguments not expected: %s" % kw) |
|---|
| 556 | if capture_stderr: |
|---|
| 557 | stderr_pipe = subprocess.STDOUT |
|---|
| 558 | else: |
|---|
| 559 | stderr_pipe = subprocess.PIPE |
|---|
| 560 | try: |
|---|
| 561 | proc = subprocess.Popen([cmd] + list(args), |
|---|
| 562 | cwd=cwd, |
|---|
| 563 | stderr=stderr_pipe, |
|---|
| 564 | stdout=subprocess.PIPE) |
|---|
| 565 | except OSError, e: |
|---|
| 566 | if e.errno != 2: |
|---|
| 567 | # File not found |
|---|
| 568 | raise |
|---|
| 569 | raise OSError( |
|---|
| 570 | "The expected executable %s was not found (%s)" |
|---|
| 571 | % (cmd, e)) |
|---|
| 572 | if self.verbose: |
|---|
| 573 | print 'Running %s %s' % (cmd, ' '.join(args)) |
|---|
| 574 | if simulate: |
|---|
| 575 | return None |
|---|
| 576 | stdout, stderr = proc.communicate() |
|---|
| 577 | if proc.returncode and not expect_returncode: |
|---|
| 578 | if not self.verbose: |
|---|
| 579 | print 'Running %s %s' % (cmd, ' '.join(args)) |
|---|
| 580 | print 'Error (exit code: %s)' % proc.returncode |
|---|
| 581 | if stderr: |
|---|
| 582 | print stderr |
|---|
| 583 | raise OSError("Error executing command %s" % cmd) |
|---|
| 584 | if self.verbose > 2: |
|---|
| 585 | if stderr: |
|---|
| 586 | print 'Command error output:' |
|---|
| 587 | print stderr |
|---|
| 588 | if stdout: |
|---|
| 589 | print 'Command output:' |
|---|
| 590 | print stdout |
|---|
| 591 | elif proc.returncode and warn_returncode: |
|---|
| 592 | print 'Warning: command failed (%s %s)' % (cmd, ' '.join(args)) |
|---|
| 593 | print 'Exited with code %s' % proc.returncode |
|---|
| 594 | return stdout |
|---|
| 595 | |
|---|
| 596 | def quote_first_command_arg(self, arg): |
|---|
| 597 | """ |
|---|
| 598 | There's a bug in Windows when running an executable that's |
|---|
| 599 | located inside a path with a space in it. This method handles |
|---|
| 600 | that case, or on non-Windows systems or an executable with no |
|---|
| 601 | spaces, it just leaves well enough alone. |
|---|
| 602 | """ |
|---|
| 603 | if (sys.platform != 'win32' |
|---|
| 604 | or ' ' not in arg): |
|---|
| 605 | # Problem does not apply: |
|---|
| 606 | return arg |
|---|
| 607 | try: |
|---|
| 608 | import win32api |
|---|
| 609 | except ImportError: |
|---|
| 610 | raise ValueError( |
|---|
| 611 | "The executable %r contains a space, and in order to " |
|---|
| 612 | "handle this issue you must have the win32api module " |
|---|
| 613 | "installed" % arg) |
|---|
| 614 | arg = win32api.GetShortPathName(arg) |
|---|
| 615 | return arg |
|---|
| 616 | |
|---|
| 617 | _svn_failed = False |
|---|
| 618 | |
|---|
| 619 | def svn_command(self, *args, **kw): |
|---|
| 620 | """ |
|---|
| 621 | Run an svn command, but don't raise an exception if it fails. |
|---|
| 622 | """ |
|---|
| 623 | try: |
|---|
| 624 | return self.run_command('svn', *args, **kw) |
|---|
| 625 | except OSError, e: |
|---|
| 626 | if not self._svn_failed: |
|---|
| 627 | print 'Unable to run svn command (%s); proceeding anyway' % e |
|---|
| 628 | self._svn_failed = True |
|---|
| 629 | |
|---|
| 630 | def write_file(self, filename, content, source=None, |
|---|
| 631 | binary=True, svn_add=True): |
|---|
| 632 | """ |
|---|
| 633 | Like ``ensure_file``, but without the interactivity. Mostly |
|---|
| 634 | deprecated. (I think I forgot it existed) |
|---|
| 635 | """ |
|---|
| 636 | import warnings |
|---|
| 637 | warnings.warn( |
|---|
| 638 | "command.write_file has been replaced with " |
|---|
| 639 | "command.ensure_file", |
|---|
| 640 | DeprecationWarning, 2) |
|---|
| 641 | if os.path.exists(filename): |
|---|
| 642 | if binary: |
|---|
| 643 | f = open(filename, 'rb') |
|---|
| 644 | else: |
|---|
| 645 | f = open(filename, 'r') |
|---|
| 646 | old_content = f.read() |
|---|
| 647 | f.close() |
|---|
| 648 | if content == old_content: |
|---|
| 649 | if self.verbose: |
|---|
| 650 | print 'File %s exists with same content' % ( |
|---|
| 651 | self.shorten(filename)) |
|---|
| 652 | return |
|---|
| 653 | if (not self.simulate and self.options.interactive): |
|---|
| 654 | if not self.ask('Overwrite file %s?' % filename): |
|---|
| 655 | return |
|---|
| 656 | if self.verbose > 1 and source: |
|---|
| 657 | print 'Writing %s from %s' % (self.shorten(filename), |
|---|
| 658 | self.shorten(source)) |
|---|
| 659 | elif self.verbose: |
|---|
| 660 | print 'Writing %s' % self.shorten(filename) |
|---|
| 661 | if not self.simulate: |
|---|
| 662 | already_existed = os.path.exists(filename) |
|---|
| 663 | if binary: |
|---|
| 664 | f = open(filename, 'wb') |
|---|
| 665 | else: |
|---|
| 666 | f = open(filename, 'w') |
|---|
| 667 | f.write(content) |
|---|
| 668 | f.close() |
|---|
| 669 | if (not already_existed |
|---|
| 670 | and svn_add |
|---|
| 671 | and os.path.exists(os.path.join(os.path.dirname(filename), '.svn'))): |
|---|
| 672 | self.svn_command('add', filename) |
|---|
| 673 | |
|---|
| 674 | def parse_vars(self, args): |
|---|
| 675 | """ |
|---|
| 676 | Given variables like ``['a=b', 'c=d']`` turns it into ``{'a': |
|---|
| 677 | 'b', 'c': 'd'}`` |
|---|
| 678 | """ |
|---|
| 679 | result = {} |
|---|
| 680 | for arg in args: |
|---|
| 681 | if '=' not in arg: |
|---|
| 682 | raise BadCommand( |
|---|
| 683 | 'Variable assignment %r invalid (no "=")' |
|---|
| 684 | % arg) |
|---|
| 685 | name, value = arg.split('=', 1) |
|---|
| 686 | result[name] = value |
|---|
| 687 | return result |
|---|
| 688 | |
|---|
| 689 | def read_vars(self, config, section='pastescript'): |
|---|
| 690 | """ |
|---|
| 691 | Given a configuration filename, this will return a map of values. |
|---|
| 692 | """ |
|---|
| 693 | result = {} |
|---|
| 694 | p = ConfigParser.RawConfigParser() |
|---|
| 695 | p.read([config]) |
|---|
| 696 | if p.has_section(section): |
|---|
| 697 | for key, value in p.items(section): |
|---|
| 698 | if key.endswith('__eval__'): |
|---|
| 699 | result[key[:-len('__eval__')]] = eval(value) |
|---|
| 700 | else: |
|---|
| 701 | result[key] = value |
|---|
| 702 | return result |
|---|
| 703 | |
|---|
| 704 | def write_vars(self, config, vars, section='pastescript'): |
|---|
| 705 | """ |
|---|
| 706 | Given a configuration filename, this will add items in the |
|---|
| 707 | vars mapping to the configuration file. Will create the |
|---|
| 708 | configuration file if it doesn't exist. |
|---|
| 709 | """ |
|---|
| 710 | modified = False |
|---|
| 711 | |
|---|
| 712 | p = ConfigParser.RawConfigParser() |
|---|
| 713 | if not os.path.exists(config): |
|---|
| 714 | f = open(config, 'w') |
|---|
| 715 | f.write('') |
|---|
| 716 | f.close() |
|---|
| 717 | modified = True |
|---|
| 718 | p.read([config]) |
|---|
| 719 | if not p.has_section(section): |
|---|
| 720 | p.add_section(section) |
|---|
| 721 | modified = True |
|---|
| 722 | |
|---|
| 723 | existing_options = p.options(section) |
|---|
| 724 | for key, value in vars.items(): |
|---|
| 725 | if (key not in existing_options and |
|---|
| 726 | '%s__eval__' % key not in existing_options): |
|---|
| 727 | if not isinstance(value, str): |
|---|
| 728 | p.set(section, '%s__eval__' % key, repr(value)) |
|---|
| 729 | else: |
|---|
| 730 | p.set(section, key, value) |
|---|
| 731 | modified = True |
|---|
| 732 | |
|---|
| 733 | if modified: |
|---|
| 734 | p.write(open(config, 'w')) |
|---|
| 735 | |
|---|
| 736 | def indent_block(self, text, indent=2, initial=None): |
|---|
| 737 | """ |
|---|
| 738 | Indent the block of text (each line is indented). If you give |
|---|
| 739 | ``initial``, then that is used in lieue of ``indent`` for the |
|---|
| 740 | first line. |
|---|
| 741 | """ |
|---|
| 742 | if initial is None: |
|---|
| 743 | initial = indent |
|---|
| 744 | lines = text.splitlines() |
|---|
| 745 | first = (' '*initial) + lines[0] |
|---|
| 746 | rest = [(' '*indent)+l for l in lines[1:]] |
|---|
| 747 | return '\n'.join([first]+rest) |
|---|
| 748 | |
|---|
| 749 | def logging_file_config(self, config_file): |
|---|
| 750 | """ |
|---|
| 751 | Setup logging via the logging module's fileConfig function with the |
|---|
| 752 | specified ``config_file``, if applicable. |
|---|
| 753 | """ |
|---|
| 754 | parser = ConfigParser.ConfigParser() |
|---|
| 755 | parser.read([config_file]) |
|---|
| 756 | if parser.has_section('loggers'): |
|---|
| 757 | fileConfig(config_file) |
|---|
| 758 | |
|---|
| 759 | class NotFoundCommand(Command): |
|---|
| 760 | |
|---|
| 761 | def run(self, args): |
|---|
| 762 | #for name, value in os.environ.items(): |
|---|
| 763 | # print '%s: %s' % (name, value) |
|---|
| 764 | #print sys.argv |
|---|
| 765 | print ('Command %r not known (you may need to run setup.py egg_info)' |
|---|
| 766 | % self.command_name) |
|---|
| 767 | commands = get_commands().items() |
|---|
| 768 | commands.sort() |
|---|
| 769 | if not commands: |
|---|
| 770 | print 'No commands registered.' |
|---|
| 771 | print 'Have you installed Paste Script?' |
|---|
| 772 | print '(try running python setup.py develop)' |
|---|
| 773 | return 2 |
|---|
| 774 | print 'Known commands:' |
|---|
| 775 | longest = max([len(n) for n, c in commands]) |
|---|
| 776 | for name, command in commands: |
|---|
| 777 | print ' %s %s' % (self.pad(name, length=longest), |
|---|
| 778 | command.load().summary) |
|---|
| 779 | return 2 |
|---|
| 780 | |
|---|
| 781 | def popdefault(dict, name, default=None): |
|---|
| 782 | if name not in dict: |
|---|
| 783 | return default |
|---|
| 784 | else: |
|---|
| 785 | v = dict[name] |
|---|
| 786 | del dict[name] |
|---|
| 787 | return v |
|---|