root/galaxy-central/eggs/PasteScript-1.7.3-py2.6.egg/paste/script/command.py

リビジョン 3, 27.6 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
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
3import pkg_resources
4import sys
5import optparse
6import bool_optparse
7import os
8import re
9import textwrap
10import pluginlib
11import ConfigParser
12import getpass
13try:
14    import subprocess
15except ImportError:
16    from paste.script.util import subprocess24 as subprocess
17difflib = None
18
19if sys.version_info >= (2, 6):
20    from logging.config import fileConfig
21else:
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
26class 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
33class NoDefault(object):
34    pass
35
36dist = pkg_resources.get_distribution('PasteScript')
37
38python_version = sys.version.splitlines()[0].strip()
39
40parser = 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
45parser.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)")
50parser.add_option(
51    '-h', '--help',
52    action='store_true',
53    dest='do_help',
54    help="Show this help message")
55parser.disable_interspersed_args()
56
57# @@: Add an option to run this in another Python interpreter
58
59system_plugins = []
60
61def 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
86def 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
106def 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
120def 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
130class 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
759class 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
781def 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
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。