Package logilab-common-0 :: Package 39 :: Package 0 :: Module optik_ext
[frames] | no frames]

Source Code for Module logilab-common-0.39.0.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 get_csv 
 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 get_csv(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 dictionnary 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 -def check_date(option, opt, value):
145 """check a file value 146 return the filepath 147 """ 148 try: 149 return DateTime.strptime(value, "%Y/%m/%d") 150 except DateTime.Error : 151 raise OptionValueError( 152 "expected format of %s is yyyy/mm/dd" % opt)
153
154 -def check_color(option, opt, value):
155 """check a color value and returns it 156 /!\ does *not* check color labels (like 'red', 'green'), only 157 checks hexadecimal forms 158 """ 159 # Case (1) : color label, we trust the end-user 160 if re.match('[a-z0-9 ]+$', value, re.I): 161 return value 162 # Case (2) : only accepts hexadecimal forms 163 if re.match('#[a-f0-9]{6}', value, re.I): 164 return value 165 # Else : not a color label neither a valid hexadecimal form => error 166 msg = "option %s: invalid color : %r, should be either hexadecimal \ 167 value or predefinied color" 168 raise OptionValueError(msg % (opt, value))
169
170 -def check_time(option, opt, value):
171 from logilab.common.textutils import TIME_UNITS, apply_units 172 apply_units(value, TIME_UNITS)
173
174 -def check_bytes(option, opt, value):
175 from logilab.common.textutils import BYTE_UNITS, apply_units 176 apply_units(value, BYTE_UNITS)
177 178 import types 179
180 -class Option(BaseOption):
181 """override optik.Option to add some new option types 182 """ 183 TYPES = BaseOption.TYPES + ('regexp', 'csv', 'yn', 'named', 'password', 184 'multiple_choice', 'file', 'color', 185 'time', 'bytes') 186 ATTRS = BaseOption.ATTRS + ['hide'] 187 TYPE_CHECKER = copy(BaseOption.TYPE_CHECKER) 188 TYPE_CHECKER['regexp'] = check_regexp 189 TYPE_CHECKER['csv'] = check_csv 190 TYPE_CHECKER['yn'] = check_yn 191 TYPE_CHECKER['named'] = check_named 192 TYPE_CHECKER['multiple_choice'] = check_csv 193 TYPE_CHECKER['file'] = check_file 194 TYPE_CHECKER['color'] = check_color 195 TYPE_CHECKER['password'] = check_password 196 TYPE_CHECKER['time'] = check_time 197 TYPE_CHECKER['bytes'] = check_bytes 198 if HAS_MX_DATETIME: 199 TYPES += ('date',) 200 TYPE_CHECKER['date'] = check_date 201
202 - def __init__(self, *opts, **attrs):
203 BaseOption.__init__(self, *opts, **attrs) 204 if hasattr(self, "hide") and self.hide: 205 self.help = SUPPRESS_HELP
206
207 - def _check_choice(self):
208 """FIXME: need to override this due to optik misdesign""" 209 if self.type in ("choice", "multiple_choice"): 210 if self.choices is None: 211 raise OptionError( 212 "must supply a list of choices for type 'choice'", self) 213 elif type(self.choices) not in (types.TupleType, types.ListType): 214 raise OptionError( 215 "choices must be a list of strings ('%s' supplied)" 216 % str(type(self.choices)).split("'")[1], self) 217 elif self.choices is not None: 218 raise OptionError( 219 "must not supply choices for type %r" % self.type, self)
220 BaseOption.CHECK_METHODS[2] = _check_choice 221 222
223 - def process(self, opt, value, values, parser):
224 # First, convert the value(s) to the right type. Howl if any 225 # value(s) are bogus. 226 try: 227 value = self.convert_value(opt, value) 228 except AttributeError: # py < 2.4 229 value = self.check_value(opt, value) 230 if self.type == 'named': 231 existant = getattr(values, self.dest) 232 if existant: 233 existant.update(value) 234 value = existant 235 # And then take whatever action is expected of us. 236 # This is a separate method to make life easier for 237 # subclasses to add new actions. 238 return self.take_action( 239 self.action, self.dest, opt, value, values, parser)
240
241 -class OptionParser(BaseParser):
242 """override optik.OptionParser to use our Option class 243 """
244 - def __init__(self, option_class=Option, *args, **kwargs):
245 BaseParser.__init__(self, option_class=Option, *args, **kwargs)
246 247
248 -class ManHelpFormatter(HelpFormatter):
249 """Format help using man pages ROFF format""" 250
251 - def __init__ (self, 252 indent_increment=0, 253 max_help_position=24, 254 width=79, 255 short_first=0):
256 HelpFormatter.__init__ ( 257 self, indent_increment, max_help_position, width, short_first)
258
259 - def format_heading(self, heading):
260 return '.SH %s\n' % heading.upper()
261
262 - def format_description(self, description):
263 return description
264
265 - def format_option(self, option):
266 try: 267 optstring = option.option_strings 268 except AttributeError: 269 optstring = self.format_option_strings(option) 270 if option.help: 271 help_text = self.expand_default(option) 272 help = ' '.join([l.strip() for l in help_text.splitlines()]) 273 else: 274 help = '' 275 return '''.IP "%s" 276 %s 277 ''' % (optstring, help)
278
279 - def format_head(self, optparser, pkginfo, section=1):
280 try: 281 pgm = optparser._get_prog_name() 282 except AttributeError: 283 # py >= 2.4.X (dunno which X exactly, at least 2) 284 pgm = optparser.get_prog_name() 285 short_desc = self.format_short_description(pgm, pkginfo.short_desc) 286 long_desc = self.format_long_description(pgm, pkginfo.long_desc) 287 return '%s\n%s\n%s\n%s' % (self.format_title(pgm, section), short_desc, 288 self.format_synopsis(pgm), long_desc)
289
290 - def format_title(self, pgm, section):
291 date = '-'.join([str(num) for num in time.localtime()[:3]]) 292 return '.TH %s %s "%s" %s' % (pgm, section, date, pgm)
293
294 - def format_short_description(self, pgm, short_desc):
295 return '''.SH NAME 296 .B %s 297 \- %s 298 ''' % (pgm, short_desc.strip())
299
300 - def format_synopsis(self, pgm):
301 return '''.SH SYNOPSIS 302 .B %s 303 [ 304 .I OPTIONS 305 ] [ 306 .I <arguments> 307 ] 308 ''' % pgm
309
310 - def format_long_description(self, pgm, long_desc):
311 long_desc = '\n'.join([line.lstrip() 312 for line in long_desc.splitlines()]) 313 long_desc = long_desc.replace('\n.\n', '\n\n') 314 if long_desc.lower().startswith(pgm): 315 long_desc = long_desc[len(pgm):] 316 return '''.SH DESCRIPTION 317 .B %s 318 %s 319 ''' % (pgm, long_desc.strip())
320
321 - def format_tail(self, pkginfo):
322 return '''.SH SEE ALSO 323 /usr/share/doc/pythonX.Y-%s/ 324 325 .SH COPYRIGHT 326 %s 327 328 This program is free software; you can redistribute it and/or modify 329 it under the terms of the GNU General Public License as published 330 by the Free Software Foundation; either version 2 of the License, 331 or (at your option) any later version. 332 333 This program is distributed in the hope that it will be useful, 334 but WITHOUT ANY WARRANTY; without even the implied warranty of 335 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 336 GNU General Public License for more details. 337 338 You should have received a copy of the GNU General Public License 339 along with this program; if not, write to the Free Software 340 Foundation, Inc., 59 Temple Place, Suite 330, Boston, 341 MA 02111-1307 USA. 342 .SH BUGS 343 Please report bugs on the project\'s mailing list: 344 %s 345 346 .SH AUTHOR 347 %s <%s> 348 ''' % (getattr(pkginfo, 'debian_name', pkginfo.modname), pkginfo.copyright, 349 pkginfo.mailinglist, pkginfo.author, pkginfo.author_email)
350 351
352 -def generate_manpage(optparser, pkginfo, section=1, stream=sys.stdout):
353 """generate a man page from an optik parser""" 354 formatter = ManHelpFormatter() 355 formatter.parser = optparser 356 print >> stream, formatter.format_head(optparser, pkginfo, section) 357 print >> stream, optparser.format_option_help(formatter) 358 print >> stream, formatter.format_tail(pkginfo)
359 360 361 __all__ = ('OptionParser', 'Option', 'OptionGroup', 'OptionValueError', 362 'Values') 363