root / logilab.pylintinstaller / logilab / common / optik_ext.py

Revision 202:d67e86292521, 11.0 kB (checked in by tziade@…, 9 months ago)

added logilab.pylintinstaller

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