OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 """Access the commit queue from the command line. |
| 7 """ |
| 8 |
| 9 __version__ = '0.1' |
| 10 |
| 11 import functools |
| 12 import logging |
| 13 import optparse |
| 14 import os |
| 15 import sys |
| 16 import urllib2 |
| 17 |
| 18 import breakpad # pylint: disable=W0611 |
| 19 |
| 20 import fix_encoding |
| 21 import rietveld |
| 22 |
| 23 |
| 24 def usage(more): |
| 25 def hook(fn): |
| 26 fn.func_usage_more = more |
| 27 return fn |
| 28 return hook |
| 29 |
| 30 |
| 31 def need_issue(fn): |
| 32 """Post-parse args to create a Rietveld object.""" |
| 33 @functools.wraps(fn) |
| 34 def hook(parser, args, *extra_args, **kwargs): |
| 35 old_parse_args = parser.parse_args |
| 36 |
| 37 def new_parse_args(args): |
| 38 options, args = old_parse_args(args) |
| 39 if not options.issue: |
| 40 parser.error('Require --issue') |
| 41 obj = rietveld.Rietveld(options.server, options.user, None) |
| 42 return options, args, obj |
| 43 |
| 44 parser.parse_args = new_parse_args |
| 45 |
| 46 parser.add_option( |
| 47 '-u', '--user', |
| 48 metavar='U', |
| 49 default=os.environ.get('EMAIL_ADDRESS', None), |
| 50 help='Email address, default: %default') |
| 51 parser.add_option( |
| 52 '-i', '--issue', |
| 53 metavar='I', |
| 54 type='int', |
| 55 help='Rietveld issue number') |
| 56 parser.add_option( |
| 57 '-s', |
| 58 '--server', |
| 59 metavar='S', |
| 60 default='http://codereview.chromium.org', |
| 61 help='Rietveld server, default: %default') |
| 62 |
| 63 # Call the original function with the modified parser. |
| 64 return fn(parser, args, *extra_args, **kwargs) |
| 65 |
| 66 hook.func_usage_more = '[options]' |
| 67 return hook |
| 68 |
| 69 |
| 70 def set_commit(obj, issue, flag): |
| 71 """Sets the commit bit flag on an issue.""" |
| 72 try: |
| 73 patchset = obj.get_issue_properties(issue, False)['patchsets'][-1] |
| 74 print obj.set_flag(issue, patchset, 'commit', flag) |
| 75 except urllib2.HTTPError, e: |
| 76 if e.code == 404: |
| 77 print >> sys.stderr, 'Issue %d doesn\'t exist.' % issue |
| 78 elif e.code == 403: |
| 79 print >> sys.stderr, 'Access denied to issue %d.' % issue |
| 80 else: |
| 81 raise |
| 82 return 1 |
| 83 |
| 84 @need_issue |
| 85 def CMDset(parser, args): |
| 86 """Sets the commit bit.""" |
| 87 options, args, obj = parser.parse_args(args) |
| 88 if args: |
| 89 parser.error('Unrecognized args: %s' % ' '.join(args)) |
| 90 return set_commit(obj, options.issue, '1') |
| 91 |
| 92 |
| 93 @need_issue |
| 94 def CMDclear(parser, args): |
| 95 """Clears the commit bit.""" |
| 96 options, args, obj = parser.parse_args(args) |
| 97 if args: |
| 98 parser.error('Unrecognized args: %s' % ' '.join(args)) |
| 99 return set_commit(obj, options.issue, '0') |
| 100 |
| 101 |
| 102 ############################################################################### |
| 103 ## Boilerplate code |
| 104 |
| 105 |
| 106 def gen_parser(): |
| 107 """Returns an OptionParser instance with default options. |
| 108 |
| 109 It should be then processed with gen_usage() before being used. |
| 110 """ |
| 111 parser = optparse.OptionParser(version=__version__) |
| 112 # Remove description formatting |
| 113 parser.format_description = lambda x: parser.description |
| 114 # Add common parsing. |
| 115 old_parser_args = parser.parse_args |
| 116 |
| 117 def Parse(*args, **kwargs): |
| 118 options, args = old_parser_args(*args, **kwargs) |
| 119 logging.basicConfig( |
| 120 level=[logging.WARNING, logging.INFO, logging.DEBUG][ |
| 121 min(2, options.verbose)], |
| 122 format='%(levelname)s %(filename)s(%(lineno)d): %(message)s') |
| 123 return options, args |
| 124 |
| 125 parser.parse_args = Parse |
| 126 |
| 127 parser.add_option( |
| 128 '-v', '--verbose', action='count', default=0, |
| 129 help='Use multiple times to increase logging level') |
| 130 return parser |
| 131 |
| 132 |
| 133 def Command(name): |
| 134 return getattr(sys.modules[__name__], 'CMD' + name, None) |
| 135 |
| 136 |
| 137 @usage('<command>') |
| 138 def CMDhelp(parser, args): |
| 139 """Print list of commands or use 'help <command>'.""" |
| 140 # Strip out the help command description and replace it with the module |
| 141 # docstring. |
| 142 parser.description = sys.modules[__name__].__doc__ |
| 143 parser.description += '\nCommands are:\n' + '\n'.join( |
| 144 ' %-12s %s' % ( |
| 145 fn[3:], Command(fn[3:]).__doc__.split('\n', 1)[0].rstrip('.')) |
| 146 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')) |
| 147 |
| 148 _, args = parser.parse_args(args) |
| 149 if len(args) == 1 and args[0] != 'help': |
| 150 return main(args + ['--help']) |
| 151 parser.print_help() |
| 152 return 0 |
| 153 |
| 154 |
| 155 def gen_usage(parser, command): |
| 156 """Modifies an OptionParser object with the command's documentation. |
| 157 |
| 158 The documentation is taken from the function's docstring. |
| 159 """ |
| 160 obj = Command(command) |
| 161 more = getattr(obj, 'func_usage_more') |
| 162 # OptParser.description prefer nicely non-formatted strings. |
| 163 parser.description = obj.__doc__ + '\n' |
| 164 parser.set_usage('usage: %%prog %s %s' % (command, more)) |
| 165 |
| 166 |
| 167 def main(args=None): |
| 168 # Do it late so all commands are listed. |
| 169 # pylint: disable=E1101 |
| 170 parser = gen_parser() |
| 171 if args is None: |
| 172 args = sys.argv[1:] |
| 173 if args: |
| 174 command = Command(args[0]) |
| 175 if command: |
| 176 # "fix" the usage and the description now that we know the subcommand. |
| 177 gen_usage(parser, args[0]) |
| 178 return command(parser, args[1:]) |
| 179 |
| 180 # Not a known command. Default to help. |
| 181 gen_usage(parser, 'help') |
| 182 return CMDhelp(parser, args) |
| 183 |
| 184 |
| 185 if __name__ == "__main__": |
| 186 fix_encoding.fix_encoding() |
| 187 sys.exit(main()) |
OLD | NEW |