summaryrefslogtreecommitdiff
path: root/logilab/common/optik_ext.py
diff options
context:
space:
mode:
Diffstat (limited to 'logilab/common/optik_ext.py')
-rw-r--r--logilab/common/optik_ext.py133
1 files changed, 84 insertions, 49 deletions
diff --git a/logilab/common/optik_ext.py b/logilab/common/optik_ext.py
index 07365a7..11e2155 100644
--- a/logilab/common/optik_ext.py
+++ b/logilab/common/optik_ext.py
@@ -55,6 +55,11 @@ import sys
import time
from copy import copy
from os.path import exists
+from logilab.common import attrdict
+
+from typing import Any, Union, List, Optional, Tuple, Dict
+from optparse import Values, IndentedHelpFormatter, OptionGroup
+from _io import StringIO
# python >= 2.3
from optparse import OptionParser as BaseParser, Option as BaseOption, \
@@ -83,7 +88,7 @@ def check_regexp(option, opt, value):
raise OptionValueError(
"option %s: invalid regexp value: %r" % (opt, value))
-def check_csv(option, opt, value):
+def check_csv(option: Optional['Option'], opt: str, value: Union[List[str], Tuple[str, ...], str]) -> Union[List[str], Tuple[str, ...]]:
"""check a csv value by trying to split it
return the list of separated values
"""
@@ -95,7 +100,7 @@ def check_csv(option, opt, value):
raise OptionValueError(
"option %s: invalid csv value: %r" % (opt, value))
-def check_yn(option, opt, value):
+def check_yn(option: Optional['Option'], opt: str, value: Union[bool, str]) -> bool:
"""check a yn value
return true for yes and false for no
"""
@@ -108,18 +113,21 @@ def check_yn(option, opt, value):
msg = "option %s: invalid yn value %r, should be in (y, yes, n, no)"
raise OptionValueError(msg % (opt, value))
-def check_named(option, opt, value):
+def check_named(option: Optional[Any], opt: str, value: Union[Dict[str, str], str]) -> Dict[str, str]:
"""check a named value
return a dictionary containing (name, value) associations
"""
if isinstance(value, dict):
return value
- values = []
+ values: List[Tuple[str, str]] = []
for value in check_csv(option, opt, value):
+ # mypy: Argument 1 to "append" of "list" has incompatible type "List[str]";
+ # mypy: expected "Tuple[str, str]"
+ # we know that the split will give a 2 items list
if value.find('=') != -1:
- values.append(value.split('=', 1))
+ values.append(value.split('=', 1)) # type: ignore
elif value.find(':') != -1:
- values.append(value.split(':', 1))
+ values.append(value.split(':', 1)) # type: ignore
if values:
return dict(values)
msg = "option %s: invalid named value %r, should be <NAME>=<VALUE> or \
@@ -173,10 +181,12 @@ def check_time(option, opt, value):
return value
return apply_units(value, TIME_UNITS)
-def check_bytes(option, opt, value):
+def check_bytes(option: Optional['Option'], opt: str, value: Any) -> int:
if hasattr(value, '__int__'):
return value
- return apply_units(value, BYTE_UNITS, final=int)
+ # mypy: Incompatible return value type (got "Union[float, int]", expected "int")
+ # we force "int" using "final=int"
+ return apply_units(value, BYTE_UNITS, final=int) # type: ignore
class Option(BaseOption):
@@ -201,50 +211,62 @@ class Option(BaseOption):
TYPES += ('date',)
TYPE_CHECKER['date'] = check_date
- def __init__(self, *opts, **attrs):
+ def __init__(self, *opts: str, **attrs: Any) -> None:
BaseOption.__init__(self, *opts, **attrs)
- if hasattr(self, "hide") and self.hide:
+ # mypy: "Option" has no attribute "hide"
+ # we test that in the if
+ if hasattr(self, "hide") and self.hide: # type: ignore
self.help = SUPPRESS_HELP
- def _check_choice(self):
+ def _check_choice(self) -> None:
"""FIXME: need to override this due to optik misdesign"""
if self.type in ("choice", "multiple_choice"):
- if self.choices is None:
+ # mypy: "Option" has no attribute "choices"
+ # we know that option of this type has this attribute
+ if self.choices is None: # type: ignore
raise OptionError(
"must supply a list of choices for type 'choice'", self)
- elif not isinstance(self.choices, (tuple, list)):
+ elif not isinstance(self.choices, (tuple, list)): # type: ignore
raise OptionError(
"choices must be a list of strings ('%s' supplied)"
- % str(type(self.choices)).split("'")[1], self)
- elif self.choices is not None:
+ % str(type(self.choices)).split("'")[1], self) # type: ignore
+ elif self.choices is not None: # type: ignore
raise OptionError(
"must not supply choices for type %r" % self.type, self)
- BaseOption.CHECK_METHODS[2] = _check_choice
-
+ # mypy: Unsupported target for indexed assignment
+ # black magic?
+ BaseOption.CHECK_METHODS[2] = _check_choice # type: ignore
- def process(self, opt, value, values, parser):
+ def process(self, opt: str, value: str, values: Values, parser: BaseParser) -> int:
# First, convert the value(s) to the right type. Howl if any
# value(s) are bogus.
value = self.convert_value(opt, value)
if self.type == 'named':
+ assert self.dest is not None
existant = getattr(values, self.dest)
if existant:
existant.update(value)
value = existant
- # And then take whatever action is expected of us.
+ # And then take whatever action is expected of us.
# This is a separate method to make life easier for
# subclasses to add new actions.
+ # mypy: Argument 2 to "take_action" of "Option" has incompatible type "Optional[str]";
+ # mypy: expected "str"
+ # is it ok?
return self.take_action(
- self.action, self.dest, opt, value, values, parser)
+ self.action, self.dest, opt, value, values, parser) # type: ignore
class OptionParser(BaseParser):
"""override optik.OptionParser to use our Option class
"""
- def __init__(self, option_class=Option, *args, **kwargs):
- BaseParser.__init__(self, option_class=Option, *args, **kwargs)
+ def __init__(self, option_class: type = Option, *args: Any, **kwargs: Any) -> None:
+ # mypy: Argument "option_class" to "__init__" of "OptionParser" has incompatible type
+ # mypy: "type"; expected "Option"
+ # mypy is doing really weird things with *args/**kwargs and looks buggy
+ BaseParser.__init__(self, option_class=option_class, *args, **kwargs) # type: ignore
- def format_option_help(self, formatter=None):
+ def format_option_help(self, formatter: Optional[HelpFormatter] = None) -> str:
if formatter is None:
formatter = self.formatter
outputlevel = getattr(formatter, 'output_level', 0)
@@ -256,7 +278,9 @@ class OptionParser(BaseParser):
result.append(OptionContainer.format_option_help(self, formatter))
result.append("\n")
for group in self.option_groups:
- if group.level <= outputlevel and (
+ # mypy: "OptionParser" has no attribute "level"
+ # but it has one no?
+ if group.level <= outputlevel and ( # type: ignore
group.description or level_options(group, outputlevel)):
result.append(group.format_help(formatter))
result.append("\n")
@@ -265,12 +289,16 @@ class OptionParser(BaseParser):
return "".join(result[:-1])
-OptionGroup.level = 0
+# mypy error: error: "Type[OptionGroup]" has no attribute "level"
+# monkeypatching
+OptionGroup.level = 0 # type: ignore
-def level_options(group, outputlevel):
+def level_options(group: BaseParser, outputlevel: int) -> List[BaseOption]:
+ # mypy: "Option" has no attribute "help"
+ # but it does
return [option for option in group.option_list
if (getattr(option, 'level', 0) or 0) <= outputlevel
- and not option.help is SUPPRESS_HELP]
+ and not option.help is SUPPRESS_HELP] # type: ignore
def format_option_help(self, formatter):
result = []
@@ -278,33 +306,42 @@ def format_option_help(self, formatter):
for option in level_options(self, outputlevel):
result.append(formatter.format_option(option))
return "".join(result)
-OptionContainer.format_option_help = format_option_help
+# mypy error: Cannot assign to a method
+# but we still do it because magic
+OptionContainer.format_option_help = format_option_help # type: ignore
class ManHelpFormatter(HelpFormatter):
"""Format help using man pages ROFF format"""
def __init__ (self,
- indent_increment=0,
- max_help_position=24,
- width=79,
- short_first=0):
+ indent_increment: int = 0,
+ max_help_position: int = 24,
+ width: int = 79,
+ short_first: int = 0) -> None:
HelpFormatter.__init__ (
self, indent_increment, max_help_position, width, short_first)
- def format_heading(self, heading):
+ def format_heading(self, heading: str) -> str:
return '.SH %s\n' % heading.upper()
def format_description(self, description):
return description
- def format_option(self, option):
+ def format_option(self, option: BaseParser) -> str:
try:
- optstring = option.option_strings
+ # mypy: "Option" has no attribute "option_strings"
+ # we handle if it doesn't
+ optstring = option.option_strings # type: ignore
except AttributeError:
optstring = self.format_option_strings(option)
- if option.help:
- help_text = self.expand_default(option)
+ # mypy: "OptionParser" has no attribute "help"
+ # it does
+ if option.help: # type: ignore
+ # mypy: Argument 1 to "expand_default" of "HelpFormatter" has incompatible type
+ # mypy: "OptionParser"; expected "Option"
+ # it still works?
+ help_text = self.expand_default(option) # type: ignore
help = ' '.join([l.strip() for l in help_text.splitlines()])
else:
help = ''
@@ -312,13 +349,9 @@ class ManHelpFormatter(HelpFormatter):
%s
''' % (optstring, help)
- def format_head(self, optparser, pkginfo, section=1):
+ def format_head(self, optparser: OptionParser, pkginfo: attrdict, section: int = 1) -> str:
long_desc = ""
- try:
- pgm = optparser._get_prog_name()
- except AttributeError:
- # py >= 2.4.X (dunno which X exactly, at least 2)
- pgm = optparser.get_prog_name()
+ pgm = optparser.get_prog_name()
short_desc = self.format_short_description(pgm, pkginfo.description)
if hasattr(pkginfo, "long_desc"):
long_desc = self.format_long_description(pgm, pkginfo.long_desc)
@@ -326,17 +359,17 @@ class ManHelpFormatter(HelpFormatter):
short_desc, self.format_synopsis(pgm),
long_desc)
- def format_title(self, pgm, section):
+ def format_title(self, pgm: str, section: int) -> str:
date = '-'.join([str(num) for num in time.localtime()[:3]])
return '.TH %s %s "%s" %s' % (pgm, section, date, pgm)
- def format_short_description(self, pgm, short_desc):
+ def format_short_description(self, pgm: str, short_desc: str) -> str:
return '''.SH NAME
.B %s
\- %s
''' % (pgm, short_desc.strip())
- def format_synopsis(self, pgm):
+ def format_synopsis(self, pgm: str) -> str:
return '''.SH SYNOPSIS
.B %s
[
@@ -357,7 +390,7 @@ class ManHelpFormatter(HelpFormatter):
%s
''' % (pgm, long_desc.strip())
- def format_tail(self, pkginfo):
+ def format_tail(self, pkginfo: attrdict) -> str:
tail = '''.SH SEE ALSO
/usr/share/doc/pythonX.Y-%s/
@@ -378,10 +411,12 @@ Please report bugs on the project\'s mailing list:
return tail
-def generate_manpage(optparser, pkginfo, section=1, stream=sys.stdout, level=0):
+def generate_manpage(optparser: OptionParser, pkginfo: attrdict, section: int = 1, stream: StringIO = sys.stdout, level: int = 0) -> None:
"""generate a man page from an optik parser"""
formatter = ManHelpFormatter()
- formatter.output_level = level
+ # mypy: "ManHelpFormatter" has no attribute "output_level"
+ # dynamic attribute?
+ formatter.output_level = level # type: ignore
formatter.parser = optparser
print(formatter.format_head(optparser, pkginfo, section), file=stream)
print(optparser.format_option_help(formatter), file=stream)