Package svnmailer :: Module cli
[hide private]

Source Code for Module svnmailer.cli

  1  # -*- coding: utf-8 -*- 
  2  # pylint: disable-msg = C0103, W0221 
  3  # 
  4  # Copyright 2004-2006 André Malo or his licensors, as applicable 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #     http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 17  """ 
 18  Command line interface 
 19  ====================== 
 20   
 21  The svnmailer provides two different command line interfaces. On the one hand 
 22  there's the compatibility command line to the mailer.py script, which has some 
 23  limitations and problems because of its unflexibility. On the other hand 
 24  you'll find the new-style command line, which contains no subcommands and 
 25  fixed parameters at all. 
 26   
 27  The CLI simply transforms old-style command lines to the new format internally 
 28  and processes these further using the optparse module:: 
 29   
 30      svn-mailer commit <rep> <rev> [<config>] 
 31   -> svn-mailer --commit --repository <rep> --revision <rev> 
 32                [--config <config>] 
 33   
 34      svn-mailer propchange <rep> <rev> <author> <prop> [<conf>] 
 35   -> svn-mailer --propchange --repository <rep> --revision <rev> 
 36                 --author <author> --propname <prop> 
 37                [--config <conf>] 
 38   
 39      # (useful with svn 1.2 and later) 
 40      svn-mailer propchange2 <rep> <rev> <author> <prop> <action> [<conf>] 
 41   -> svn-mailer --propchange --repository <rep> --revision <rev> 
 42                 --author <author> --propname <prop> --action <action> 
 43                [--config <conf>] 
 44   
 45      svn-mailer lock <rep> <author> [<conf>] 
 46   -> svn-mailer --lock --repository <rep> --author <author> 
 47                [--config <conf>] 
 48   
 49      svn-mailer unlock <rep> <author> [<conf>] 
 50   -> svn-mailer --unlock --repository <rep> --author <author> 
 51                [--config <conf>] 
 52  """ 
 53  __author__    = "André Malo" 
 54  __docformat__ = "epytext en" 
 55  __all__       = ['getOptions', 'CommandlineError'] 
 56   
 57  # global imports 
 58  import optparse 
 59   
 60  # Exceptions 
61 -class Error(Exception):
62 """ Base exception for this module """ 63 pass
64
65 -class CommandlineError(Error):
66 """ Error in commandline """ 67 pass
68 69
70 -def getOptions(argv = None):
71 """ Parse commandline options 72 73 @param argv: Command line list. If argv is None, 74 sys.argv[1:] is evaluated instead 75 @type argv: C{list} 76 77 @return: option object 78 @rtype: C{optparse.OptionParser} 79 80 @exception CommandlineError: Error in command line options 81 """ 82 from svnmailer import util 83 from svnmailer.settings import modes 84 85 usage = """%prog <options>""" 86 parser = SvnmailerOptionParser(usage = usage, version = True) 87 options, args = parser.parse_args(argv) 88 89 # Check parameter consistency 90 if args: 91 raise CommandlineError("Too much arguments") 92 93 if not options.repository: 94 raise CommandlineError("Missing repository path") 95 96 if not options.revision: 97 if options.mode in (modes.commit, modes.propchange): 98 raise CommandlineError("Missing revision number") 99 100 if not options.author: 101 if options.mode in (modes.propchange, modes.lock, modes.unlock): 102 raise CommandlineError("Missing author parameter") 103 104 if not options.propname: 105 if options.mode == modes.propchange: 106 raise CommandlineError("Missing property name parameter") 107 108 # de-localize the paths 109 try: 110 options.repository = util.filename.fromLocale( 111 options.repository, options.path_encoding 112 ) 113 except UnicodeError, exc: 114 raise CommandlineError("--repository recode problem: %s" % str(exc)) 115 116 if options.config: 117 try: 118 options.config = util.filename.fromLocale( 119 options.config, options.path_encoding 120 ) 121 except UnicodeError, exc: 122 raise CommandlineError("--config recode problem: %s" % str(exc)) 123 124 return options
125 126
127 -class SvnmailerOptionParser(optparse.OptionParser):
128 """ Fully initialized option parser 129 130 @ivar _svn: The svn version 131 @type _svn: C{tuple} 132 """ 133
134 - def __init__(self, *args, **kwargs):
135 """ Initialization """ 136 from svnmailer import subversion 137 138 self._svn = subversion.version 139 optparse.OptionParser.__init__(self, *args, **kwargs) 140 self._initSvnmailer()
141 142
143 - def parse_args(self, args = None, *other_args, **kwargs):
144 """ Accepts also the old command line """ 145 args = self._transformSvnmailerOldStyle(args) 146 if not args: 147 raise CommandlineError( 148 "Type '%s --help' for usage" % self.get_prog_name() 149 ) 150 151 options, fixed = optparse.OptionParser.parse_args( 152 self, args, *other_args, **kwargs 153 ) 154 155 # fixup action attribute (expected later) 156 if not self._svn.min_1_2: 157 options.action = None 158 159 return (options, fixed)
160 161
162 - def error(self, msg):
163 """ We raise an exception instead of exiting 164 165 @param msg: The error message 166 @type msg: C{str} 167 168 @exception CommandlineError: command line error 169 """ 170 raise CommandlineError(str(msg))
171 172
173 - def get_version(self):
174 """ Returns the version string """ 175 from svnmailer import version 176 177 return "svnmailer-%s\nwith svn %d.%d.%d%s" % ( 178 version, self._svn.major, self._svn.minor, self._svn.patch, 179 self._svn.tag 180 )
181 182
183 - def get_prog_name(self):
184 """ Returns the program name """ 185 try: 186 # >= python 2.4 187 return optparse.OptionParser.get_prog_name(self) 188 except AttributeError: 189 try: 190 # >= python 2.3.4 191 return optparse.OptionParser._get_prog_name(self) 192 except AttributeError: 193 # <= python 2.3.3 194 if self.prog: 195 return self.prog 196 else: 197 import os, sys 198 return os.path.basename(sys.argv[0])
199 200
201 - def format_help(self, formatter = None):
202 """ Adds a description of the old style options """ 203 import textwrap 204 205 width = (self._getTerminalWidth() or 80) - 1 206 optionhelp = None 207 while optionhelp is None: 208 try: 209 formatter = optparse.IndentedHelpFormatter(width = width) 210 optionhelp = optparse.OptionParser.format_help(self, formatter) 211 except ValueError: 212 # terminal too small *sigh* 213 if width < 79: 214 width = 79 215 else: 216 width += 10 217 218 oldstyle = textwrap.fill( 219 "Alternatively you can use the old style compatibility " 220 "command lines (options described above don't apply then):", 221 width = width, 222 ) 223 224 prog = self.get_prog_name() 225 indent = " " * (len(prog) + 1) 226 clines = [ 227 "", 228 "%(prog)s commit <repos> <revision> [<config>]", 229 "%(prog)s propchange <repos> <revision> <author> <propname> " 230 "[<config>]", 231 ] 232 if self._svn.min_1_2: 233 clines.extend([ 234 "", 235 "svn 1.2 and later:", 236 "%(prog)s propchange2 <repos> <revision> <author> <propname> " 237 "<action> [<config>]", 238 "%(prog)s lock <repos> <author> [<config>]", 239 "%(prog)s unlock <repos> <author> [<config>]", 240 ]) 241 clines = ["%s\n" % textwrap.fill( 242 line % {'prog': prog}, width = width, subsequent_indent = indent 243 ) for line in clines] 244 245 return "%s\n%s\n%s" % (optionhelp, oldstyle, ''.join(clines))
246 247
248 - def _getTerminalWidth(self):
249 """ Returns terminal width if determined, None otherwise 250 251 @return: The width 252 @rtype: C{int} 253 """ 254 try: 255 import errno, fcntl, struct, sys, termios 256 257 def getwidth(fd): 258 """ Returns the width for descriptor fd """ 259 # struct winsize { /* on linux in asm/termios.h */ 260 # unsigned short ws_row; 261 # unsigned short ws_col; 262 # unsigned short ws_xpixel; 263 # unsigned short ws_ypixel; 264 # } 265 return struct.unpack("4H", fcntl.ioctl( 266 fd, termios.TIOCGWINSZ, struct.pack("4H", 0, 0, 0, 0) 267 ))[1]
268 269 try: 270 return getwidth(sys.stdout.fileno()) 271 except IOError, exc: 272 if exc[0] == errno.EINVAL: 273 return getwidth(sys.stdin.fileno()) 274 raise # otherwise 275 276 except (SystemExit, KeyboardInterrupt): 277 raise 278 279 except: 280 # don't even ignore 281 pass 282 283 return None
284 285
286 - def _initSvnmailer(self):
287 """ Builds the options from option groups """ 288 self._addSvnmailerCommonOptions() 289 self._addSvnmailerBehaviourOptions() 290 self._addSvnmailerSupplementalOptions()
291 292
293 - def _addSvnmailerCommonOptions(self):
294 """ Adds the common options group """ 295 common_options = optparse.OptionGroup( 296 self, 'COMMON PARAMETERS' 297 ) 298 common_options.add_option('--debug', 299 action = 'store_true', 300 default = False, 301 help = "Run in debug mode (means basically that all messages " 302 "are sent to STDOUT)", 303 ) 304 common_options.add_option('-d', '--repository', 305 help = 'The repository directory', 306 ) 307 common_options.add_option('-f', '--config', 308 help = 'The configuration file', 309 ) 310 common_options.add_option('-e', '--path-encoding', 311 help = 'Specifies the character encoding to be used for ' 312 'filenames. By default the encoding is tried to be ' 313 'determined automatically depending on the locale.' 314 ) 315 self.add_option_group(common_options)
316 317
318 - def _addSvnmailerBehaviourOptions(self):
319 """ Adds the behaviour options group """ 320 from svnmailer.settings import modes 321 322 behaviour_options = optparse.OptionGroup( 323 self, 'BEHAVIOUR OPTIONS', 324 description = "The behaviour options are mutually exclusive, " 325 "i.e. the last one wins." 326 ) 327 behaviour_options.add_option('-c', '--commit', 328 dest = 'mode', 329 action = 'store_const', 330 const = modes.commit, 331 default = modes.commit, 332 help = 'This is a regular commit of versioned data ' 333 '(post-commit hook). This is default.', 334 ) 335 behaviour_options.add_option('-p', '--propchange', 336 dest = 'mode', 337 action = 'store_const', 338 const = modes.propchange, 339 help = 'This is a modification of unversioned properties ' 340 '(post-revprop-change hook)', 341 ) 342 343 if self._svn.min_1_2: 344 behaviour_options.add_option('-l', '--lock', 345 dest = 'mode', 346 action = 'store_const', 347 const = modes.lock, 348 help = '(svn 1.2 and later) This is a locking call ' 349 '(post-lock hook)', 350 ) 351 behaviour_options.add_option('-u', '--unlock', 352 dest = 'mode', 353 action = 'store_const', 354 const = modes.unlock, 355 help = '(svn 1.2 and later) This is a unlocking call ' 356 '(post-unlock hook)', 357 ) 358 359 self.add_option_group(behaviour_options)
360 361
362 - def _addSvnmailerSupplementalOptions(self):
363 """ Adds the supplemental options """ 364 supp_options = optparse.OptionGroup( 365 self, 'SUPPLEMENTAL PARAMETERS' 366 ) 367 368 supp_options.add_option('-r', '--revision', 369 action = 'store', 370 type = 'int', 371 help = 'The modified/committed revision number', 372 ) 373 supp_options.add_option('-a', '--author', 374 help = 'The author of the modification', 375 ) 376 supp_options.add_option('-n', '--propname', 377 help = 'The name of the modified property', 378 ) 379 380 if self._svn.min_1_2: 381 supp_options.add_option('-o', '--action', 382 help = '(svn 1.2 and later) The property change action', 383 ) 384 385 self.add_option_group(supp_options)
386 387
388 - def _transformSvnmailerOldStyle(self, argv):
389 """ Parses the command line according to old style rules 390 391 @param argv: Command line list. If argv is None, 392 sys.argv[1:] is evaluated instead 393 @type argv: C{list} 394 395 @return: The commandline, possibly transformed to new style 396 @rtype: C{list} 397 """ 398 if argv is None: 399 import sys 400 argv = sys.argv[1:] 401 402 if argv: 403 length = len(argv) 404 405 # svn-mailer commit <rep> <rev> [<conf>] 406 if argv[0] == "commit" and 3 <= length <= 4: 407 config = argv[3:] 408 argv = ["--commit", 409 "--repository", argv[1], "--revision", argv[2], 410 ] 411 if config: 412 argv.extend(["--config", config[0]]) 413 414 # svn-mailer propchange <rep> <rev> <author> <prop> [<conf>] 415 elif argv[0] == "propchange" and 5 <= length <= 6: 416 config = argv[5:] 417 argv = ["--propchange", 418 "--repository", argv[1], "--revision", argv[2], 419 "--author", argv[3], "--propname", argv[4], 420 ] 421 if config: 422 argv.extend(["--config", config[0]]) 423 424 else: 425 if self._svn.min_1_2: 426 # svn-mailer propchange2 <rep> <rev> <author> <prop> <act> 427 # [<conf>] 428 if argv[0] == "propchange2" and 6 <= length <= 7: 429 config = argv[6:] 430 argv = ["--propchange", 431 "--repository", argv[1], "--revision", argv[2], 432 "--author", argv[3], "--propname", argv[4], 433 "--action", argv[5], 434 ] 435 if config: 436 argv.extend(["--config", config[0]]) 437 438 # svn-mailer lock <rep> <author> [<conf>] 439 # svn-mailer unlock <rep> <author> [<conf>] 440 elif argv[0] in ("lock", "unlock") and 3 <= length <= 4: 441 config = argv[3:] 442 argv = ["--%s" % argv[0], 443 "--repository", argv[1], "--author", argv[2] 444 ] 445 if config: 446 argv.extend(["--config", config[0]]) 447 448 return argv
449