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.py245
1 files changed, 155 insertions, 90 deletions
diff --git a/logilab/common/optik_ext.py b/logilab/common/optik_ext.py
index 11e2155..3f321b5 100644
--- a/logilab/common/optik_ext.py
+++ b/logilab/common/optik_ext.py
@@ -62,33 +62,44 @@ from optparse import Values, IndentedHelpFormatter, OptionGroup
from _io import StringIO
# python >= 2.3
-from optparse import OptionParser as BaseParser, Option as BaseOption, \
- OptionGroup, OptionContainer, OptionValueError, OptionError, \
- Values, HelpFormatter, NO_DEFAULT, SUPPRESS_HELP
+from optparse import (
+ OptionParser as BaseParser,
+ Option as BaseOption,
+ OptionGroup,
+ OptionContainer,
+ OptionValueError,
+ OptionError,
+ Values,
+ HelpFormatter,
+ NO_DEFAULT,
+ SUPPRESS_HELP,
+)
try:
from mx import DateTime
+
HAS_MX_DATETIME = True
except ImportError:
HAS_MX_DATETIME = False
-from logilab.common.textutils import splitstrip, TIME_UNITS, BYTE_UNITS, \
- apply_units
+from logilab.common.textutils import splitstrip, TIME_UNITS, BYTE_UNITS, apply_units
def check_regexp(option, opt, value):
"""check a regexp value by trying to compile it
return the compiled regexp
"""
- if hasattr(value, 'pattern'):
+ if hasattr(value, "pattern"):
return value
try:
return re.compile(value)
except ValueError:
- raise OptionValueError(
- "option %s: invalid regexp value: %r" % (opt, value))
+ raise OptionValueError("option %s: invalid regexp value: %r" % (opt, value))
+
-def check_csv(option: Optional['Option'], opt: str, value: Union[List[str], Tuple[str, ...], str]) -> Union[List[str], Tuple[str, ...]]:
+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
"""
@@ -97,23 +108,26 @@ def check_csv(option: Optional['Option'], opt: str, value: Union[List[str], Tupl
try:
return splitstrip(value)
except ValueError:
- raise OptionValueError(
- "option %s: invalid csv value: %r" % (opt, value))
+ raise OptionValueError("option %s: invalid csv value: %r" % (opt, value))
+
-def check_yn(option: Optional['Option'], opt: str, value: Union[bool, str]) -> bool:
+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
"""
if isinstance(value, int):
return bool(value)
- if value in ('y', 'yes'):
+ if value in ("y", "yes"):
return True
- if value in ('n', 'no'):
+ if value in ("n", "no"):
return False
msg = "option %s: invalid yn value %r, should be in (y, yes, n, no)"
raise OptionValueError(msg % (opt, value))
-def check_named(option: Optional[Any], opt: str, value: Union[Dict[str, str], str]) -> Dict[str, str]:
+
+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
"""
@@ -124,22 +138,24 @@ def check_named(option: Optional[Any], opt: str, value: Union[Dict[str, str], st
# 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)) # type: ignore
- elif value.find(':') != -1:
- values.append(value.split(':', 1)) # type: ignore
+ if value.find("=") != -1:
+ values.append(value.split("=", 1)) # type: ignore
+ elif value.find(":") != -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 \
<NAME>:<VALUE>"
raise OptionValueError(msg % (opt, value))
+
def check_password(option, opt, value):
"""check a password value (can't be empty)
"""
# no actual checking, monkey patch if you want more
return value
+
def check_file(option, opt, value):
"""check a file value
return the filepath
@@ -149,6 +165,7 @@ def check_file(option, opt, value):
msg = "option %s: file %r does not exist"
raise OptionValueError(msg % (opt, value))
+
# XXX use python datetime
def check_date(option, opt, value):
"""check a file value
@@ -156,9 +173,9 @@ def check_date(option, opt, value):
"""
try:
return DateTime.strptime(value, "%Y/%m/%d")
- except DateTime.Error :
- raise OptionValueError(
- "expected format of %s is yyyy/mm/dd" % opt)
+ except DateTime.Error:
+ raise OptionValueError("expected format of %s is yyyy/mm/dd" % opt)
+
def check_color(option, opt, value):
"""check a color value and returns it
@@ -166,23 +183,25 @@ def check_color(option, opt, value):
checks hexadecimal forms
"""
# Case (1) : color label, we trust the end-user
- if re.match('[a-z0-9 ]+$', value, re.I):
+ if re.match("[a-z0-9 ]+$", value, re.I):
return value
# Case (2) : only accepts hexadecimal forms
- if re.match('#[a-f0-9]{6}', value, re.I):
+ if re.match("#[a-f0-9]{6}", value, re.I):
return value
# Else : not a color label neither a valid hexadecimal form => error
msg = "option %s: invalid color : %r, should be either hexadecimal \
value or predefined color"
raise OptionValueError(msg % (opt, value))
+
def check_time(option, opt, value):
if isinstance(value, (int, float)):
return value
return apply_units(value, TIME_UNITS)
-def check_bytes(option: Optional['Option'], opt: str, value: Any) -> int:
- if hasattr(value, '__int__'):
+
+def check_bytes(option: Optional["Option"], opt: str, value: Any) -> int:
+ if hasattr(value, "__int__"):
return value
# mypy: Incompatible return value type (got "Union[float, int]", expected "int")
# we force "int" using "final=int"
@@ -192,24 +211,34 @@ def check_bytes(option: Optional['Option'], opt: str, value: Any) -> int:
class Option(BaseOption):
"""override optik.Option to add some new option types
"""
- TYPES = BaseOption.TYPES + ('regexp', 'csv', 'yn', 'named', 'password',
- 'multiple_choice', 'file', 'color',
- 'time', 'bytes')
- ATTRS = BaseOption.ATTRS + ['hide', 'level']
+
+ TYPES = BaseOption.TYPES + (
+ "regexp",
+ "csv",
+ "yn",
+ "named",
+ "password",
+ "multiple_choice",
+ "file",
+ "color",
+ "time",
+ "bytes",
+ )
+ ATTRS = BaseOption.ATTRS + ["hide", "level"]
TYPE_CHECKER = copy(BaseOption.TYPE_CHECKER)
- TYPE_CHECKER['regexp'] = check_regexp
- TYPE_CHECKER['csv'] = check_csv
- TYPE_CHECKER['yn'] = check_yn
- TYPE_CHECKER['named'] = check_named
- TYPE_CHECKER['multiple_choice'] = check_csv
- TYPE_CHECKER['file'] = check_file
- TYPE_CHECKER['color'] = check_color
- TYPE_CHECKER['password'] = check_password
- TYPE_CHECKER['time'] = check_time
- TYPE_CHECKER['bytes'] = check_bytes
+ TYPE_CHECKER["regexp"] = check_regexp
+ TYPE_CHECKER["csv"] = check_csv
+ TYPE_CHECKER["yn"] = check_yn
+ TYPE_CHECKER["named"] = check_named
+ TYPE_CHECKER["multiple_choice"] = check_csv
+ TYPE_CHECKER["file"] = check_file
+ TYPE_CHECKER["color"] = check_color
+ TYPE_CHECKER["password"] = check_password
+ TYPE_CHECKER["time"] = check_time
+ TYPE_CHECKER["bytes"] = check_bytes
if HAS_MX_DATETIME:
- TYPES += ('date',)
- TYPE_CHECKER['date'] = check_date
+ TYPES += ("date",)
+ TYPE_CHECKER["date"] = check_date
def __init__(self, *opts: str, **attrs: Any) -> None:
BaseOption.__init__(self, *opts, **attrs)
@@ -224,15 +253,16 @@ class Option(BaseOption):
# 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)
+ raise OptionError("must supply a list of choices for type 'choice'", self)
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) # type: ignore
+ % 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)
+ raise OptionError("must not supply choices for type %r" % self.type, self)
+
# mypy: Unsupported target for indexed assignment
# black magic?
BaseOption.CHECK_METHODS[2] = _check_choice # type: ignore
@@ -241,7 +271,7 @@ class Option(BaseOption):
# 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':
+ if self.type == "named":
assert self.dest is not None
existant = getattr(values, self.dest)
if existant:
@@ -253,13 +283,13 @@ class Option(BaseOption):
# 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) # type: ignore
+ return self.take_action(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: type = Option, *args: Any, **kwargs: Any) -> None:
# mypy: Argument "option_class" to "__init__" of "OptionParser" has incompatible type
# mypy: "type"; expected "Option"
@@ -269,7 +299,7 @@ class OptionParser(BaseParser):
def format_option_help(self, formatter: Optional[HelpFormatter] = None) -> str:
if formatter is None:
formatter = self.formatter
- outputlevel = getattr(formatter, 'output_level', 0)
+ outputlevel = getattr(formatter, "output_level", 0)
formatter.store_option_strings(self)
result = []
result.append(formatter.format_heading("Options"))
@@ -281,7 +311,8 @@ class OptionParser(BaseParser):
# 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)):
+ group.description or level_options(group, outputlevel)
+ ):
result.append(group.format_help(formatter))
result.append("\n")
formatter.dedent()
@@ -293,19 +324,25 @@ class OptionParser(BaseParser):
# monkeypatching
OptionGroup.level = 0 # type: ignore
+
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] # type: ignore
+ return [
+ option
+ for option in group.option_list
+ if (getattr(option, "level", 0) or 0) <= outputlevel and not option.help is SUPPRESS_HELP
+ ] # type: ignore
+
def format_option_help(self, formatter):
result = []
- outputlevel = getattr(formatter, 'output_level', 0) or 0
+ outputlevel = getattr(formatter, "output_level", 0) or 0
for option in level_options(self, outputlevel):
result.append(formatter.format_option(option))
return "".join(result)
+
+
# mypy error: Cannot assign to a method
# but we still do it because magic
OptionContainer.format_option_help = format_option_help # type: ignore
@@ -314,16 +351,17 @@ OptionContainer.format_option_help = format_option_help # type: ignore
class ManHelpFormatter(HelpFormatter):
"""Format help using man pages ROFF format"""
- def __init__ (self,
- 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 __init__(
+ self,
+ 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: str) -> str:
- return '.SH %s\n' % heading.upper()
+ return ".SH %s\n" % heading.upper()
def format_description(self, description):
return description
@@ -342,12 +380,15 @@ class ManHelpFormatter(HelpFormatter):
# 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()])
+ help = " ".join([l.strip() for l in help_text.splitlines()])
else:
- help = ''
- return '''.IP "%s"
+ help = ""
+ return """.IP "%s"
%s
-''' % (optstring, help)
+""" % (
+ optstring,
+ help,
+ )
def format_head(self, optparser: OptionParser, pkginfo: attrdict, section: int = 1) -> str:
long_desc = ""
@@ -355,43 +396,54 @@ class ManHelpFormatter(HelpFormatter):
short_desc = self.format_short_description(pgm, pkginfo.description)
if hasattr(pkginfo, "long_desc"):
long_desc = self.format_long_description(pgm, pkginfo.long_desc)
- return '%s\n%s\n%s\n%s' % (self.format_title(pgm, section),
- short_desc, self.format_synopsis(pgm),
- long_desc)
+ return "%s\n%s\n%s\n%s" % (
+ self.format_title(pgm, section),
+ short_desc,
+ self.format_synopsis(pgm),
+ long_desc,
+ )
def format_title(self, pgm: str, section: int) -> str:
- date = '-'.join([str(num) for num in time.localtime()[:3]])
+ 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: str, short_desc: str) -> str:
- return '''.SH NAME
+ return """.SH NAME
.B %s
\- %s
-''' % (pgm, short_desc.strip())
+""" % (
+ pgm,
+ short_desc.strip(),
+ )
def format_synopsis(self, pgm: str) -> str:
- return '''.SH SYNOPSIS
+ return (
+ """.SH SYNOPSIS
.B %s
[
.I OPTIONS
] [
.I <arguments>
]
-''' % pgm
+"""
+ % pgm
+ )
def format_long_description(self, pgm, long_desc):
- long_desc = '\n'.join([line.lstrip()
- for line in long_desc.splitlines()])
- long_desc = long_desc.replace('\n.\n', '\n\n')
+ long_desc = "\n".join([line.lstrip() for line in long_desc.splitlines()])
+ long_desc = long_desc.replace("\n.\n", "\n\n")
if long_desc.lower().startswith(pgm):
- long_desc = long_desc[len(pgm):]
- return '''.SH DESCRIPTION
+ long_desc = long_desc[len(pgm) :]
+ return """.SH DESCRIPTION
.B %s
%s
-''' % (pgm, long_desc.strip())
+""" % (
+ pgm,
+ long_desc.strip(),
+ )
def format_tail(self, pkginfo: attrdict) -> str:
- tail = '''.SH SEE ALSO
+ tail = """.SH SEE ALSO
/usr/share/doc/pythonX.Y-%s/
.SH BUGS
@@ -400,18 +452,32 @@ Please report bugs on the project\'s mailing list:
.SH AUTHOR
%s <%s>
-''' % (getattr(pkginfo, 'debian_name', pkginfo.modname),
- pkginfo.mailinglist, pkginfo.author, pkginfo.author_email)
+""" % (
+ getattr(pkginfo, "debian_name", pkginfo.modname),
+ pkginfo.mailinglist,
+ pkginfo.author,
+ pkginfo.author_email,
+ )
if hasattr(pkginfo, "copyright"):
- tail += '''
+ tail += (
+ """
.SH COPYRIGHT
%s
-''' % pkginfo.copyright
+"""
+ % pkginfo.copyright
+ )
return tail
-def generate_manpage(optparser: OptionParser, pkginfo: attrdict, section: int = 1, stream: StringIO = sys.stdout, level: int = 0) -> None:
+
+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()
# mypy: "ManHelpFormatter" has no attribute "output_level"
@@ -423,5 +489,4 @@ def generate_manpage(optparser: OptionParser, pkginfo: attrdict, section: int =
print(formatter.format_tail(pkginfo), file=stream)
-__all__ = ('OptionParser', 'Option', 'OptionGroup', 'OptionValueError',
- 'Values')
+__all__ = ("OptionParser", "Option", "OptionGroup", "OptionValueError", "Values")