# Copyright (c) 2020 Canonical Ltd.
# Copyright (c) 2020 Dave Jones <dave@waveform.org.uk>
#
# This file is part of pibootctl.
#
# pibootctl is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pibootctl is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pibootctl. If not, see <https://www.gnu.org/licenses/>.
"""
The :mod:`pibootctl.userstr` module provides the :class:`UserStr` class which
represents unparsed user input on the command line.
The module also provides a variety of functions for converting input (either
from JSON, YAML, or other structured formats, or from unparsed
:class:`UserStr`) into common types (:func:`to_bool`, :func:`to_int`,
:func:`to_str`, etc).
.. autoclass:: UserStr
.. autofunction:: to_bool
.. autofunction:: to_int
.. autofunction:: to_float
.. autofunction:: to_str
.. autofunction:: to_list
"""
import gettext
_ = gettext.gettext
[docs]class UserStr(str):
"""
Type used to represent a value expressed as a string on the command line.
In other words, any value bearing this type is a string representation of
some other type (possibly :class:`str`, :class:`int`, :data:`None`, etc.)
Primarily used by various conversion routines (:func:`to_bool`,
:func:`to_str`, etc.) to determine whether a value is a string parsed from
some serialization format (like JSON or YAML) which should be treated as a
string literal.
.. note::
The blank :class:`UserStr` is special in that it *always* represents
:data:`None` in conversions.
"""
[docs]def to_bool(s):
"""
Converts the :class:`UserStr` (or other type) *s* to a :class:`bool`.
Various "typical" string representations of true and false are accepted
including "true", "yes", and "on", along with their counter-parts "false",
"no", and "off". Literal :data:`None` passes through unchanged, and a blank
:class:`UserStr` will convert to :data:`None`.
"""
if s is None:
return None
elif isinstance(s, UserStr):
try:
return {
'': None,
'auto': None,
'true': True,
'yes': True,
'on': True,
'1': True,
'y': True,
'false': False,
'no': False,
'off': False,
'0': False,
'n': False,
}[s.strip().lower()]
except KeyError:
raise ValueError(
_('{value} is not a valid bool').format(value=s))
return bool(s)
[docs]def to_int(s):
"""
Converts the :class:`UserStr` (or other type) *s* to a :class:`int`. As
with all :class:`UserStr` conversions, blank string inputs are converted to
:data:`None`, and literal :data:`None` passes through unchanged. Otherwise,
decimal integers and hexi-decimal integers prefixed with "0x" are accepted.
"""
if s is None:
return None
elif isinstance(s, str):
if isinstance(s, UserStr):
if not s:
return None
s = s.strip().lower()
if s[:2] == '0x':
return int(s, base=16)
return int(s)
[docs]def to_float(s):
"""
Converts the :class:`UserStr` (or other type) *s* to a :class:`float`. As
with all :class:`UserStr` conversions, blank string inputs are converted to
:data:`None`, and literal :data:`None` passes through unchanged. Otherwise,
typical floating point values (optionally prefixed with sign, optionally
suffixed with an exponent) are accepted.
"""
if s is None:
return None
elif isinstance(s, str):
if isinstance(s, UserStr):
if not s:
return None
return float(s)
[docs]def to_str(s):
"""
Converts the :class:`UserStr` (or other type) *s* to a :class:`str`. Blank
:class:`UserStr` are converted to :data:`None`, and literal :data:`None`
passes through unchanged. Everything else is simply passed to the
:class:`str` constructor.
"""
if s is None:
return None
elif isinstance(s, UserStr):
if not s:
return None
else:
return s.strip()
return str(s)
[docs]def to_list(s, sep=','):
"""
Converts the :class:`UserStr` (or other type) *s* to a :class:`list` based
on the separator character *sep* (which defaults to ","). Blank
:class:`UserStr` are converted to :data:`None`, and literal :data:`None`
passes through unchanged. Everything else is passed to the :class:`list`
constructor. This ensures that the result is always a unique reference.
"""
if s is None:
return None
elif isinstance(s, UserStr):
if not s:
return None
else:
s = s.strip()
if isinstance(s, str):
if sep in s:
return [elem.strip() for elem in s.split(sep)]
else:
return [s]
return list(s)