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 |
---|