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

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