Package logilab :: Package common :: Module optik_ext
[frames] | no frames]

Source Code for Module logilab.common.optik_ext

  1  """Add an abstraction level to transparently import optik classes from optparse 
  2  (python >= 2.3) or the optik package. 
  3   
  4  It also defines three new types for optik/optparse command line parser : 
  5   
  6    * regexp 
  7      argument of this type will be converted using re.compile 
  8    * csv 
  9      argument of this type will be converted using split(',') 
 10    * yn 
 11      argument of this type will be true if 'y' or 'yes', false if 'n' or 'no' 
 12    * named 
 13      argument of this type are in the form <NAME>=<VALUE> or <NAME>:<VALUE> 
 14    * password 
 15      argument of this type wont be converted but this is used by other tools 
 16      such as interactive prompt for configuration to double check value and 
 17      use an invisible field 
 18    * multiple_choice 
 19      same as default "choice" type but multiple choices allowed 
 20    * file 
 21      argument of this type wont be converted but checked that the given file exists 
 22    * color 
 23      argument of this type wont be converted but checked its either a 
 24      named color or a color specified using hexadecimal notation (preceded by a #) 
 25    * time 
 26      argument of this type will be converted to a float value in seconds 
 27      according to time units (ms, s, min, h, d) 
 28    * bytes 
 29      argument of this type will be converted to a float value in bytes 
 30      according to byte units (b, kb, mb, gb, tb) 
 31   
 32  :copyright: 2003-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 
 33  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr 
 34  :license: General Public License version 2 - http://www.gnu.org/licenses 
 35  """ 
 36  __docformat__ = "restructuredtext en" 
 37   
 38  import re 
 39  import sys 
 40  import time 
 41  from copy import copy 
 42  from os.path import exists 
 43   
 44  try: 
 45      # python >= 2.3 
 46      from optparse import OptionParser as BaseParser, Option as BaseOption, \ 
 47           OptionGroup, OptionValueError, OptionError, Values, HelpFormatter, \ 
 48           NO_DEFAULT, SUPPRESS_HELP 
 49  except ImportError: 
 50      # python < 2.3 
 51      from optik import OptionParser as BaseParser, Option as BaseOption, \ 
 52           OptionGroup, OptionValueError, OptionError, Values, HelpFormatter 
 53      try: 
 54          from optik import NO_DEFAULT 
 55      except: 
 56          NO_DEFAULT = [] 
 57      # XXX check SUPPRESS_HELP availability 
 58      try: 
 59          from optik import SUPPRESS_HELP 
 60      except: 
 61          SUPPRESS_HELP = None 
 62   
 63  try: 
 64      from mx import DateTime 
 65      HAS_MX_DATETIME = True 
 66  except ImportError: 
 67      HAS_MX_DATETIME = False 
 68   
 69   
 70  OPTPARSE_FORMAT_DEFAULT = sys.version_info >= (2, 4) 
 71   
 72  from logilab.common.textutils import splitstrip 
 73   
74 -def check_regexp(option, opt, value):
75 """check a regexp value by trying to compile it 76 return the compiled regexp 77 """ 78 if hasattr(value, 'pattern'): 79 return value 80 try: 81 return re.compile(value) 82 except ValueError: 83 raise OptionValueError( 84 "option %s: invalid regexp value: %r" % (opt, value))
85
86 -def check_csv(option, opt, value):
87 """check a csv value by trying to split it 88 return the list of separated values 89 """ 90 if isinstance(value, (list, tuple)): 91 return value 92 try: 93 return splitstrip(value) 94 except ValueError: 95 raise OptionValueError( 96 "option %s: invalid csv value: %r" % (opt, value))
97
98 -def check_yn(option, opt, value):
99 """check a yn value 100 return true for yes and false for no 101 """ 102 if isinstance(value, int): 103 return bool(value) 104 if value in ('y', 'yes'): 105 return True 106 if value in ('n', 'no'): 107 return False 108 msg = "option %s: invalid yn value %r, should be in (y, yes, n, no)" 109 raise OptionValueError(msg % (opt, value))
110
111 -def check_named(option, opt, value):
112 """check a named value 113 return a dictionary containing (name, value) associations 114 """ 115 if isinstance(value, dict): 116 return value 117 values = [] 118 for value in check_csv(option, opt, value): 119 if value.find('=') != -1: 120 values.append(value.split('=', 1)) 121 elif value.find(':') != -1: 122 values.append(value.split(':', 1)) 123 if values: 124 return dict(values) 125 msg = "option %s: invalid named value %r, should be <NAME>=<VALUE> or \ 126 <NAME>:<VALUE>" 127 raise OptionValueError(msg % (opt, value))
128
129 -def check_password(option, opt, value):
130 """check a password value (can't be empty) 131 """ 132 # no actual checking, monkey patch if you want more 133 return value
134
135 -def check_file(option, opt, value):
136 """check a file value 137 return the filepath 138 """ 139 if exists(value): 140 return value 141 msg = "option %s: file %r does not exist" 142 raise OptionValueError(msg % (opt, value))
143 144 # XXX use python datetime
145 -def check_date(option, opt, value):
146 """check a file value 147 return the filepath 148 """ 149 try: 150 return DateTime.strptime(value, "%Y/%m/%d") 151 except DateTime.Error : 152 raise OptionValueError( 153 "expected format of %s is yyyy/mm/dd" % opt)
154
155 -def check_color(option, opt, value):
156 """check a color value and returns it 157 /!\ does *not* check color labels (like 'red', 'green'), only 158 checks hexadecimal forms 159 """ 160 # Case (1) : color label, we trust the end-user 161 if re.match('[a-z0-9 ]+$', value, re.I): 162 return value 163 # Case (2) : only accepts hexadecimal forms 164 if re.match('#[a-f0-9]{6}', value, re.I): 165 return value 166 # Else : not a color label neither a valid hexadecimal form => error 167 msg = "option %s: invalid color : %r, should be either hexadecimal \ 168 value or predefined color" 169 raise OptionValueError(msg % (opt, value))
170
171 -def check_time(option, opt, value):
172 from logilab.common.textutils import TIME_UNITS, apply_units 173 if isinstance(value, (int, long, float)): 174 return value 175 return apply_units(value, TIME_UNITS)
176
177 -def check_bytes(option, opt, value):
178 from logilab.common.textutils import BYTE_UNITS, apply_units 179 if instance(value, (int, long)): 180 return value 181 return apply_units(value, BYTE_UNITS)
182 183 import types 184
185 -class Option(BaseOption):
186 """override optik.Option to add some new option types 187 """ 188 TYPES = BaseOption.TYPES + ('regexp', 'csv', 'yn', 'named', 'password', 189 'multiple_choice', 'file', 'color', 190 'time', 'bytes') 191 ATTRS = BaseOption.ATTRS + ['hide'] 192 TYPE_CHECKER = copy(BaseOption.TYPE_CHECKER) 193 TYPE_CHECKER['regexp'] = check_regexp 194 TYPE_CHECKER['csv'] = check_csv 195 TYPE_CHECKER['yn'] = check_yn 196 TYPE_CHECKER['named'] = check_named 197 TYPE_CHECKER['multiple_choice'] = check_csv 198 TYPE_CHECKER['file'] = check_file 199 TYPE_CHECKER['color'] = check_color 200 TYPE_CHECKER['password'] = check_password 201 TYPE_CHECKER['time'] = check_time 202 TYPE_CHECKER['bytes'] = check_bytes 203 if HAS_MX_DATETIME: 204 TYPES += ('date',) 205 TYPE_CHECKER['date'] = check_date 206
207 - def __init__(self, *opts, **attrs):
208 BaseOption.__init__(self, *opts, **attrs) 209 if hasattr(self, "hide") and self.hide: 210 self.help = SUPPRESS_HELP
211
212 - def _check_choice(self):
213 """FIXME: need to override this due to optik misdesign""" 214 if self.type in ("choice", "multiple_choice"): 215 if self.choices is None: 216 raise OptionError( 217 "must supply a list of choices for type 'choice'", self) 218 elif type(self.choices) not in (types.TupleType, types.ListType): 219 raise OptionError( 220 "choices must be a list of strings ('%s' supplied)" 221 % str(type(self.choices)).split("'")[1], self) 222 elif self.choices is not None: 223 raise OptionError( 224 "must not supply choices for type %r" % self.type, self)
225 BaseOption.CHECK_METHODS[2] = _check_choice 226 227
228 - def process(self, opt, value, values, parser):
229 # First, convert the value(s) to the right type. Howl if any 230 # value(s) are bogus. 231 try: 232 value = self.convert_value(opt, value) 233 except AttributeError: # py < 2.4 234 value = self.check_value(opt, value) 235 if self.type == 'named': 236 existant = getattr(values, self.dest) 237 if existant: 238 existant.update(value) 239 value = existant 240 # And then take whatever action is expected of us. 241 # This is a separate method to make life easier for 242 # subclasses to add new actions. 243 return self.take_action( 244 self.action, self.dest, opt, value, values, parser)
245
246 -class OptionParser(BaseParser):
247 """override optik.OptionParser to use our Option class 248 """
249 - def __init__(self, option_class=Option, *args, **kwargs):
250 BaseParser.__init__(self, option_class=Option, *args, **kwargs)
251 252
253 -class ManHelpFormatter(HelpFormatter):
254 """Format help using man pages ROFF format""" 255
256 - def __init__ (self, 257 indent_increment=0, 258 max_help_position=24, 259 width=79, 260 short_first=0):
261 HelpFormatter.__init__ ( 262 self, indent_increment, max_help_position, width, short_first)
263
264 - def format_heading(self, heading):
265 return '.SH %s\n' % heading.upper()
266
267 - def format_description(self, description):
268 return description
269
270 - def format_option(self, option):
271 try: 272 optstring = option.option_strings 273 except AttributeError: 274 optstring = self.format_option_strings(option) 275 if option.help: 276 help_text = self.expand_default(option) 277 help = ' '.join([l.strip() for l in help_text.splitlines()]) 278 else: 279 help = '' 280 return '''.IP "%s" 281 %s 282 ''' % (optstring, help)
283
284 - def format_head(self, optparser, pkginfo, section=1):
285 try: 286 pgm = optparser._get_prog_name() 287 except AttributeError: 288 # py >= 2.4.X (dunno which X exactly, at least 2) 289 pgm = optparser.get_prog_name() 290 short_desc = self.format_short_description(pgm, pkginfo.short_desc) 291 long_desc = self.format_long_description(pgm, pkginfo.long_desc) 292 return '%s\n%s\n%s\n%s' % (self.format_title(pgm, section), short_desc, 293 self.format_synopsis(pgm), long_desc)
294
295 - def format_title(self, pgm, section):
296 date = '-'.join([str(num) for num in time.localtime()[:3]]) 297 return '.TH %s %s "%s" %s' % (pgm, section, date, pgm)
298
299 - def format_short_description(self, pgm, short_desc):
300 return '''.SH NAME 301 .B %s 302 \- %s 303 ''' % (pgm, short_desc.strip())
304
305 - def format_synopsis(self, pgm):
306 return '''.SH SYNOPSIS 307 .B %s 308 [ 309 .I OPTIONS 310 ] [ 311 .I <arguments> 312 ] 313 ''' % pgm
314
315 - def format_long_description(self, pgm, long_desc):
316 long_desc = '\n'.join([line.lstrip() 317 for line in long_desc.splitlines()]) 318 long_desc = long_desc.replace('\n.\n', '\n\n') 319 if long_desc.lower().startswith(pgm): 320 long_desc = long_desc[len(pgm):] 321 return '''.SH DESCRIPTION 322 .B %s 323 %s 324 ''' % (pgm, long_desc.strip())
325
326 - def format_tail(self, pkginfo):
327 return '''.SH SEE ALSO 328 /usr/share/doc/pythonX.Y-%s/ 329 330 .SH COPYRIGHT 331 %s 332 333 This program is free software; you can redistribute it and/or modify 334 it under the terms of the GNU General Public License as published 335 by the Free Software Foundation; either version 2 of the License, 336 or (at your option) any later version. 337 338 This program is distributed in the hope that it will be useful, 339 but WITHOUT ANY WARRANTY; without even the implied warranty of 340 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 341 GNU General Public License for more details. 342 343 You should have received a copy of the GNU General Public License 344 along with this program; if not, write to the Free Software 345 Foundation, Inc., 59 Temple Place, Suite 330, Boston, 346 MA 02111-1307 USA. 347 .SH BUGS 348 Please report bugs on the project\'s mailing list: 349 %s 350 351 .SH AUTHOR 352 %s <%s> 353 ''' % (getattr(pkginfo, 'debian_name', pkginfo.modname), pkginfo.copyright, 354 pkginfo.mailinglist, pkginfo.author, pkginfo.author_email)
355 356
357 -def generate_manpage(optparser, pkginfo, section=1, stream=sys.stdout):
358 """generate a man page from an optik parser""" 359 formatter = ManHelpFormatter() 360 formatter.parser = optparser 361 print >> stream, formatter.format_head(optparser, pkginfo, section) 362 print >> stream, optparser.format_option_help(formatter) 363 print >> stream, formatter.format_tail(pkginfo)
364 365 366 __all__ = ('OptionParser', 'Option', 'OptionGroup', 'OptionValueError', 367 'Values') 368