def _remove_consecutive_delims(expr, ldelim="(", rdelim=")"):
    """Remove consecutive delimiters."""
    tpars = _pair_delims(expr, ldelim=ldelim, rdelim=rdelim)
    # Flag superfluous delimiters
    ddelim = []
    for ctuple, ntuple in zip(tpars, tpars[1:]):
        if ctuple == (ntuple[0] - 1, ntuple[1] + 1):
            ddelim.extend(ntuple)
    ddelim.sort()
    # Actually remove delimiters from expression
    for num, item in enumerate(ddelim):
        expr = expr[: item - num] + expr[item - num + 1 :]
    # Get functions
    return expr

def _remove_consecutive_delims ( expr , ldelim = "(" , rdelim = ")" ) : tpars = _pair_delims ( expr , ldelim = ldelim , rdelim = rdelim ) ddelim = [ ] for ctuple , ntuple in zip ( tpars , tpars [ 1 : ] ) : if ctuple == ( ntuple [ 0 ] - 1 , ntuple [ 1 ] + 1 ) : ddelim . extend ( ntuple ) ddelim . sort ( ) for num , item in enumerate ( ddelim ) : expr = expr [ : item - num ] + expr [ item - num + 1 : ] return expr


def _split_every(text, sep, count, lstrip=False, rstrip=False):
    """
    Return list of the words in the string, using count of a separator as delimiter.

    :param text: String to split
    :type  text: string

    :param sep: Separator
    :type  sep: string

    :param count: Number of separators to use as delimiter
    :type  count: integer

    :param lstrip: Flag that indicates whether whitespace is removed
                   from the beginning of each list item (True) or not
                   (False)
    :type  lstrip: boolean

    :param rstrip: Flag that indicates whether whitespace is removed
                   from the end of each list item (True) or not (False)
    :type  rstrip: boolean

    :rtype: tuple
    """
    ltr = "_rl "[2 * lstrip + rstrip].strip()
    func = lambda x: getattr(x, ltr + "strip")() if ltr != "_" else x
    items = text.split(sep)
    groups = zip_longest(*[iter(items)] * count, fillvalue="")
    joints = (sep.join(group).rstrip(sep) for group in groups)
    return tuple(func(joint) for joint in joints)

def
_split_every
(
text
,
sep
,
count
,
lstrip
=
False
,
rstrip
=
False
)
:
ltr
=
"_rl "
[
2
*
lstrip
+
rstrip
]
.
strip
(
)
func
=
lambda
x
:
getattr
(
x
,
ltr
+
"strip"
)
(
)
if
ltr
!=
"_"
else
x
items
=
text
.
split
(
sep
)
groups
=
zip_longest
(
*
[
iter
(
items
)
]
*
count
,
fillvalue
=
""
)
joints
=
(
sep
.
join
(
group
)
.
rstrip
(
sep
)
for
group
in
groups
)
return
tuple
(
func
(
joint
)
for
joint
in
joints
)

Return list of the words in the string using count of a separator as delimiter .


def _to_eng_tuple(number):
    """
    Return tuple with mantissa and exponent of number formatted in engineering notation.

    :param number: Number
    :type  number: integer or float

    :rtype: tuple
    """
    # pylint: disable=W0141
    # Helper function: split integer and fractional part of mantissa
    #  + ljust ensures that integer part in engineering notation has
    #    at most 3 digits (say if number given is 1E4)
    #  + rstrip ensures that there is no empty fractional part
    split = lambda x, p: (x.ljust(3 + neg, "0")[:p], x[p:].rstrip("0"))
    # Convert number to scientific notation, a "constant" format
    mant, exp = to_scientific_tuple(number)
    mant, neg = mant.replace(".", ""), mant.startswith("-")
    # New values
    new_mant = ".".join(filter(None, split(mant, 1 + (exp % 3) + neg)))
    new_exp = int(3 * math.floor(exp / 3))
    return NumComp(new_mant, new_exp)

def _to_eng_tuple ( number ) : split = lambda x , p : ( x . ljust ( 3 + neg , "0" ) [ : p ] , x [ p : ] . rstrip ( "0" ) ) mant , exp = to_scientific_tuple ( number ) mant , neg = mant . replace ( "." , "" ) , mant . startswith ( "-" ) new_mant = "." . join ( filter ( None , split ( mant , 1 + ( exp % 3 ) + neg ) ) ) new_exp = int ( 3 * math . floor ( exp / 3 ) ) return NumComp ( new_mant , new_exp )

Return tuple with mantissa and exponent of number formatted in engineering notation .


def no_exp(number):
    r"""
    Convert number to string guaranteeing result is not in scientific notation.

    :param number: Number to convert
    :type  number: integer or float

    :rtype: string

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for peng.functions.no_exp

    :raises: RuntimeError (Argument \`number\` is not valid)

    .. [[[end]]]
    """
    mant, exp = to_scientific_tuple(number)
    if not exp:
        return str(number)
    floating_mant = "." in mant
    mant = mant.replace(".", "")
    if exp < 0:
        return "0." + "0" * (-exp - 1) + mant
    if not floating_mant:
        return mant + "0" * exp + (".0" if isinstance(number, float) else "")
    lfpart = len(mant) - 1
    if lfpart < exp:
        return (mant + "0" * (exp - lfpart)).rstrip(".")
    return mant

def no_exp ( number ) : r mant , exp = to_scientific_tuple ( number ) if not exp : return str ( number ) floating_mant = "." in mant mant = mant . replace ( "." , "" ) if exp < 0 : return "0." + "0" * ( - exp - 1 ) + mant if not floating_mant : return mant + "0" * exp + ( ".0" if isinstance ( number , float ) else "" ) lfpart = len ( mant ) - 1 if lfpart < exp : return ( mant + "0" * ( exp - lfpart ) ) . rstrip ( "." ) return mant

r Convert number to string guaranteeing result is not in scientific notation .


def peng(number, frac_length, rjust=True):
    r"""
    Convert a number to engineering notation.

    The absolute value of the number (if it is not exactly zero) is bounded to
    the interval [1E-24, 1E+24)

    :param number: Number to convert
    :type  number: integer or float

    :param frac_length: Number of digits of fractional part
    :type  frac_length: `NonNegativeInteger
                        <https://pexdoc.readthedocs.io/en/stable/
                        ptypes.html#nonnegativeinteger>`_

    :param rjust: Flag that indicates whether the number is
                  right-justified (True) or not (False)
    :type  rjust: boolean

    :rtype: string

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for peng.functions.peng

    :raises:
     * RuntimeError (Argument \`frac_length\` is not valid)

     * RuntimeError (Argument \`number\` is not valid)

     * RuntimeError (Argument \`rjust\` is not valid)

    .. [[[end]]]

    The supported engineering suffixes are:

    +----------+-------+--------+
    | Exponent | Name  | Suffix |
    +==========+=======+========+
    | 1E-24    | yocto | y      |
    +----------+-------+--------+
    | 1E-21    | zepto | z      |
    +----------+-------+--------+
    | 1E-18    | atto  | a      |
    +----------+-------+--------+
    | 1E-15    | femto | f      |
    +----------+-------+--------+
    | 1E-12    | pico  | p      |
    +----------+-------+--------+
    | 1E-9     | nano  | n      |
    +----------+-------+--------+
    | 1E-6     | micro | u      |
    +----------+-------+--------+
    | 1E-3     | milli | m      |
    +----------+-------+--------+
    | 1E+0     |       |        |
    +----------+-------+--------+
    | 1E+3     | kilo  | k      |
    +----------+-------+--------+
    | 1E+6     | mega  | M      |
    +----------+-------+--------+
    | 1E+9     | giga  | G      |
    +----------+-------+--------+
    | 1E+12    | tera  | T      |
    +----------+-------+--------+
    | 1E+15    | peta  | P      |
    +----------+-------+--------+
    | 1E+18    | exa   | E      |
    +----------+-------+--------+
    | 1E+21    | zetta | Z      |
    +----------+-------+--------+
    | 1E+24    | yotta | Y      |
    +----------+-------+--------+

    For example:

        >>> import peng
        >>> peng.peng(1235.6789E3, 3, False)
        '1.236M'
    """
    # The decimal module has a to_eng_string() function, but it does not seem
    # to work well in all cases. For example:
    # >>> decimal.Decimal('34.5712233E8').to_eng_string()
    # '3.45712233E+9'
    # >>> decimal.Decimal('34.57122334E8').to_eng_string()
    # '3457122334'
    # It seems that the conversion function does not work in all cases
    #
    # Return formatted zero if number is zero, easier to not deal with this
    # special case through the rest of the algorithm
    if number == 0:
        number = "0.{zrs}".format(zrs="0" * frac_length) if frac_length else "0"
        # Engineering notation numbers can have a sign, a 3-digit integer part,
        # a period, and a fractional part of length frac_length, so the
        # length of the number to the left of, and including, the period is 5
        return "{0} ".format(number.rjust(5 + frac_length)) if rjust else number
    # Low-bound number
    sign = +1 if number >= 0 else -1
    ssign = "-" if sign == -1 else ""
    anumber = abs(number)
    if anumber < 1e-24:
        anumber = 1e-24
        number = sign * 1e-24
    # Round fractional part if requested frac_length is less than length
    # of fractional part. Rounding method is to add a '5' at the decimal
    # position just after the end of frac_length digits
    exp = 3.0 * math.floor(math.floor(math.log10(anumber)) / 3.0)
    mant = number / 10 ** exp
    # Because exponent is a float, mantissa is a float and its string
    # representation always includes a period
    smant = str(mant)
    ppos = smant.find(".")
    if len(smant) - ppos - 1 > frac_length:
        mant += sign * 5 * 10 ** (-frac_length - 1)
        if abs(mant) >= 1000:
            exp += 3
            mant = mant / 1e3
        smant = str(mant)
        ppos = smant.find(".")
    # Make fractional part have frac_length digits
    bfrac_length = bool(frac_length)
    flength = ppos - (not bfrac_length) + frac_length + 1
    new_mant = smant[:flength].ljust(flength, "0")
    # Upper-bound number
    if exp > 24:
        new_mant, exp = (
            "{sign}999.{frac}".format(sign=ssign, frac="9" * frac_length),
            24,
        )
    # Right-justify number, engineering notation numbers can have a sign,
    # a 3-digit integer part and a period, and a fractional part of length
    # frac_length, so the length of the number to the left of the
    # period is 4
    new_mant = new_mant.rjust(rjust * (4 + bfrac_length + frac_length))
    # Format number
    num = "{mant}{suffix}".format(
        mant=new_mant, suffix=_POWER_TO_SUFFIX_DICT[exp] if exp else " " * bool(rjust)
    )
    return num

def
peng
(
number
,
frac_length
,
rjust
=
True
)
:
r
if
number
==
0
:
number
=
"0.{zrs}"
.
format
(
zrs
=
"0"
*
frac_length
)
if
frac_length
else
"0"
return
"{0} "
.
format
(
number
.
rjust
(
5
+
frac_length
)
)
if
rjust
else
number
sign
=
+
1
if
number
>=
0
else
1
ssign
=
"-"
if
sign
==
1
else
""
anumber
=
abs
(
number
)
if
anumber
<
1e-24
:
anumber
=
1e-24
number
=
sign
*
1e-24
exp
=
3.0
*
math
.
floor
(
math
.
floor
(
math
.
log10
(
anumber
)
)
/
3.0
)
mant
=
number
/
10
**
exp
smant
=
str
(
mant
)
ppos
=
smant
.
find
(
"."
)
if
len
(
smant
)
ppos
1
>
frac_length
:
mant
+=
sign
*
5
*
10
**
(
frac_length
1
)
if
abs
(
mant
)
>=
1000
:
exp
+=
3
mant
=
mant
/
1e3
smant
=
str
(
mant
)
ppos
=
smant
.
find
(
"."
)
bfrac_length
=
bool
(
frac_length
)
flength
=
ppos
(
not
bfrac_length
)
+
frac_length
+
1
new_mant
=
smant
[
:
flength
]
.
ljust
(
flength
,
"0"
)
if
exp
>
24
:
new_mant
,
exp
=
(
"{sign}999.{frac}"
.
format
(
sign
=
ssign
,
frac
=
"9"
*
frac_length
)
,
24
,
)
new_mant
=
new_mant
.
rjust
(
rjust
*
(
4
+
bfrac_length
+
frac_length
)
)
num
=
"{mant}{suffix}"
.
format
(
mant
=
new_mant
,
suffix
=
_POWER_TO_SUFFIX_DICT
[
exp
]
if
exp
else
" "
*
bool
(
rjust
)
)
return
num

r Convert a number to engineering notation .


def peng_float(snum):
    r"""
    Return floating point equivalent of a number represented in engineering notation.

    :param snum: Number
    :type  snum: :ref:`EngineeringNotationNumber`

    :rtype: string

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.functions.peng_float

    :raises: RuntimeError (Argument \`snum\` is not valid)

    .. [[[end]]]

    For example:

        >>> import peng
        >>> peng.peng_float(peng.peng(1235.6789E3, 3, False))
        1236000.0
    """
    # This can be coded as peng_mant(snum)*(peng_power(snum)[1]), but the
    # "function unrolling" is about 4x faster
    snum = snum.rstrip()
    power = _SUFFIX_POWER_DICT[" " if snum[-1].isdigit() else snum[-1]]
    return float(snum if snum[-1].isdigit() else snum[:-1]) * power

def
peng_float
(
snum
)
:
r
snum
=
snum
.
rstrip
(
)
power
=
_SUFFIX_POWER_DICT
[
" "
if
snum
[
1
]
.
isdigit
(
)
else
snum
[
1
]
]
return
float
(
snum
if
snum
[
1
]
.
isdigit
(
)
else
snum
[
:
1
]
)
*
power

r Return floating point equivalent of a number represented in engineering notation .


def peng_frac(snum):
    r"""
    Return the fractional part of a number represented in engineering notation.

    :param snum: Number
    :type  snum: :ref:`EngineeringNotationNumber`

    :rtype: integer

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.functions.peng_frac

    :raises: RuntimeError (Argument \`snum\` is not valid)

    .. [[[end]]]

    For example:

        >>> import peng
        >>> peng.peng_frac(peng.peng(1235.6789E3, 3, False))
        236
    """
    snum = snum.rstrip()
    pindex = snum.find(".")
    if pindex == -1:
        return 0
    return int(snum[pindex + 1 :] if snum[-1].isdigit() else snum[pindex + 1 : -1])

def peng_frac ( snum ) : r snum = snum . rstrip ( ) pindex = snum . find ( "." ) if pindex == - 1 : return 0 return int ( snum [ pindex + 1 : ] if snum [ - 1 ] . isdigit ( ) else snum [ pindex + 1 : - 1 ] )

r Return the fractional part of a number represented in engineering notation .


def peng_mant(snum):
    r"""
    Return the mantissa of a number represented in engineering notation.

    :param snum: Number
    :type  snum: :ref:`EngineeringNotationNumber`

    :rtype: float

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.functions.peng_mant

    :raises: RuntimeError (Argument \`snum\` is not valid)

    .. [[[end]]]

    For example:

        >>> import peng
        >>> peng.peng_mant(peng.peng(1235.6789E3, 3, False))
        1.236
    """
    snum = snum.rstrip()
    return float(snum if snum[-1].isdigit() else snum[:-1])

def peng_mant ( snum ) : r snum = snum . rstrip ( ) return float ( snum if snum [ - 1 ] . isdigit ( ) else snum [ : - 1 ] )

r Return the mantissa of a number represented in engineering notation .


def peng_power(snum):
    r"""
    Return engineering suffix and its floating point equivalent of a number.

    :py:func:`peng.peng` lists the correspondence between suffix and floating
    point exponent.

    :param snum: Number
    :type  snum: :ref:`EngineeringNotationNumber`

    :rtype: named tuple in which the first item is the engineering suffix and
            the second item is the floating point equivalent of the suffix
            when the number is represented in engineering notation.

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.functions.peng_power

    :raises: RuntimeError (Argument \`snum\` is not valid)

    .. [[[end]]]

    For example:

        >>> import peng
        >>> peng.peng_power(peng.peng(1235.6789E3, 3, False))
        EngPower(suffix='M', exp=1000000.0)
    """
    suffix = " " if snum[-1].isdigit() else snum[-1]
    return EngPower(suffix, _SUFFIX_POWER_DICT[suffix])

def
peng_power
(
snum
)
:
r
suffix
=
" "
if
snum
[
1
]
.
isdigit
(
)
else
snum
[
1
]
return
EngPower
(
suffix
,
_SUFFIX_POWER_DICT
[
suffix
]
)

r Return engineering suffix and its floating point equivalent of a number .


def peng_suffix_math(suffix, offset):
    r"""
    Return engineering suffix from a starting suffix and an number of suffixes offset.

    :param suffix: Engineering suffix
    :type  suffix: :ref:`EngineeringNotationSuffix`

    :param offset: Engineering suffix offset
    :type  offset: integer

    :rtype: string

    .. [[[cog cog.out(exobj_eng.get_sphinx_autodoc()) ]]]
    .. Auto-generated exceptions documentation for
    .. peng.functions.peng_suffix_math

    :raises:
     * RuntimeError (Argument \`offset\` is not valid)

     * RuntimeError (Argument \`suffix\` is not valid)

     * ValueError (Argument \`offset\` is not valid)

    .. [[[end]]]

    For example:

        >>> import peng
        >>> peng.peng_suffix_math('u', 6)
        'T'
    """
    # pylint: disable=W0212
    eobj = pexdoc.exh.addex(ValueError, "Argument `offset` is not valid")
    try:
        return _POWER_TO_SUFFIX_DICT[_SUFFIX_TO_POWER_DICT[suffix] + 3 * offset]
    except KeyError:
        eobj(True)

def
peng_suffix_math
(
suffix
,
offset
)
:
r
eobj
=
pexdoc
.
exh
.
addex
(
ValueError
,
"Argument `offset` is not valid"
)
try
:
return
_POWER_TO_SUFFIX_DICT
[
_SUFFIX_TO_POWER_DICT
[
suffix
]
+
3
*
offset
]
except
KeyError
:
eobj
(
True
)

r Return engineering suffix from a starting suffix and an number of suffixes offset .


def remove_extra_delims(expr, ldelim="(", rdelim=")"):
    r"""
    Remove unnecessary delimiters in mathematical expressions.

    Delimiters (parenthesis, brackets, etc.) may be removed either because
    there are multiple consecutive delimiters enclosing a single expressions or
    because the delimiters are implied by operator precedence rules. Function
    names must start with a letter and then can contain alphanumeric characters
    and a maximum of one underscore

    :param expr: Mathematical expression
    :type  expr: string

    :param ldelim: Single character left delimiter
    :type  ldelim: string

    :param rdelim: Single character right delimiter
    :type  rdelim: string

    :rtype: string

    :raises:
     * RuntimeError (Argument \`expr\` is not valid)

     * RuntimeError (Argument \`ldelim\` is not valid)

     * RuntimeError (Argument \`rdelim\` is not valid)

     * RuntimeError (Function name `*[function_name]*` is not valid)

     * RuntimeError (Mismatched delimiters)
    """
    op_group = ""
    for item1 in _OP_PREC:
        if isinstance(item1, list):
            for item2 in item1:
                op_group += item2
        else:
            op_group += item1
    iobj = zip([expr, ldelim, rdelim], ["expr", "ldelim", "rdelim"])
    for item, desc in iobj:
        if not isinstance(item, str):
            raise RuntimeError("Argument `{0}` is not valid".format(desc))
    if (len(ldelim) != 1) or ((len(ldelim) == 1) and (ldelim in op_group)):
        raise RuntimeError("Argument `ldelim` is not valid")
    if (len(rdelim) != 1) or ((len(rdelim) == 1) and (rdelim in op_group)):
        raise RuntimeError("Argument `rdelim` is not valid")
    if expr.count(ldelim) != expr.count(rdelim):
        raise RuntimeError("Mismatched delimiters")
    if not expr:
        return expr
    vchars = (
        "abcdefghijklmnopqrstuvwxyz"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        ".0123456789"
        r"_()[]\{\}" + rdelim + ldelim + op_group
    )
    if any([item not in vchars for item in expr]) or ("__" in expr):
        raise RuntimeError("Argument `expr` is not valid")
    expr = _remove_consecutive_delims(expr, ldelim=ldelim, rdelim=rdelim)
    expr = expr.replace(ldelim + rdelim, "")
    return _remove_extra_delims(expr, ldelim=ldelim, rdelim=rdelim)

def
remove_extra_delims
(
expr
,
ldelim
=
"("
,
rdelim
=
")"
)
:
r
op_group
=
""
for
item1
in
_OP_PREC
:
if
isinstance
(
item1
,
list
)
:
for
item2
in
item1
:
op_group
+=
item2
else
:
op_group
+=
item1
iobj
=
zip
(
[
expr
,
ldelim
,
rdelim
]
,
[
"expr"
,
"ldelim"
,
"rdelim"
]
)
for
item
,
desc
in
iobj
:
if
not
isinstance
(
item
,
str
)
:
raise
RuntimeError
(
"Argument `{0}` is not valid"
.
format
(
desc
)
)
if
(
len
(
ldelim
)
!=
1
)
or
(
(
len
(
ldelim
)
==
1
)
and
(
ldelim
in
op_group
)
)
:
raise
RuntimeError
(
"Argument `ldelim` is not valid"
)
if
(
len
(
rdelim
)
!=
1
)
or
(
(
len
(
rdelim
)
==
1
)
and
(
rdelim
in
op_group
)
)
:
raise
RuntimeError
(
"Argument `rdelim` is not valid"
)
if
expr
.
count
(
ldelim
)
!=
expr
.
count
(
rdelim
)
:
raise
RuntimeError
(
"Mismatched delimiters"
)
if
not
expr
:
return
expr
vchars
=
(
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
".0123456789"
r"_()[]\{\}"
+
rdelim
+
ldelim
+
op_group
)
if
any
(
[
item
not
in
vchars
for
item
in
expr
]
)
or
(
"__"
in
expr
)
:
raise
RuntimeError
(
"Argument `expr` is not valid"
)
expr
=
_remove_consecutive_delims
(
expr
,
ldelim
=
ldelim
,
rdelim
=
rdelim
)
expr
=
expr
.
replace
(
ldelim
+
rdelim
,
""
)
return
_remove_extra_delims
(
expr
,
ldelim
=
ldelim
,
rdelim
=
rdelim
)

r Remove unnecessary delimiters in mathematical expressions .


def to_scientific_string(number, frac_length=None, exp_length=None, sign_always=False):
    """
    Convert number or number string to a number string in scientific notation.

    Full precision is maintained if the number is represented as a string

    :param number: Number to convert
    :type  number: number or string

    :param frac_length: Number of digits of fractional part, None indicates
                        that the fractional part of the number should not be
                        limited
    :type  frac_length: integer or None

    :param exp_length: Number of digits of the exponent; the actual length of
                       the exponent takes precedence if it is longer
    :type  exp_length: integer or None

    :param sign_always: Flag that indicates whether the sign always
                        precedes the number for both non-negative and negative
                        numbers (True) or only for negative numbers (False)
    :type  sign_always: boolean

    :rtype: string

    For example:

        >>> import peng
        >>> peng.to_scientific_string(333)
        '3.33E+2'
        >>> peng.to_scientific_string(0.00101)
        '1.01E-3'
        >>> peng.to_scientific_string(99.999, 1, 2, True)
        '+1.0E+02'
    """
    # pylint: disable=W0702
    try:
        number = -1e20 if np.isneginf(number) else number
    except:
        pass
    try:
        number = +1e20 if np.isposinf(number) else number
    except:
        pass
    exp_length = 0 if not exp_length else exp_length
    mant, exp = to_scientific_tuple(number)
    fmant = float(mant)
    if (not frac_length) or (fmant == int(fmant)):
        return "{sign}{mant}{period}{zeros}E{exp_sign}{exp}".format(
            sign="+" if sign_always and (fmant >= 0) else "",
            mant=mant,
            period="." if frac_length else "",
            zeros="0" * frac_length if frac_length else "",
            exp_sign="-" if exp < 0 else "+",
            exp=str(abs(exp)).rjust(exp_length, "0"),
        )
    rounded_mant = round(fmant, frac_length)
    # Avoid infinite recursion when rounded mantissa is _exactly_ 10
    if abs(rounded_mant) == 10:
        rounded_mant = fmant = -1.0 if number < 0 else 1.0
        frac_length = 1
        exp = exp + 1
    zeros = 2 + (1 if (fmant < 0) else 0) + frac_length - len(str(rounded_mant))
    return "{sign}{mant}{zeros}E{exp_sign}{exp}".format(
        sign="+" if sign_always and (fmant >= 0) else "",
        mant=rounded_mant,
        zeros="0" * zeros,
        exp_sign="-" if exp < 0 else "+",
        exp=str(abs(exp)).rjust(exp_length, "0"),
    )

Convert number or number string to a number string in scientific notation .


def to_scientific_tuple(number):
    """
    Return mantissa and exponent of a number in scientific notation.

    Full precision is maintained if the number is represented as a string

    :param number: Number
    :type  number: integer, float or string

    :rtype: named tuple in which the first item is the mantissa (*string*)
            and the second item is the exponent (*integer*) of the number
            when expressed in scientific notation

    For example:

        >>> import peng
        >>> peng.to_scientific_tuple('135.56E-8')
        NumComp(mant='1.3556', exp=-6)
        >>> peng.to_scientific_tuple(0.0000013556)
        NumComp(mant='1.3556', exp=-6)
    """
    # pylint: disable=W0632
    convert = not isinstance(number, str)
    # Detect zero and return, simplifies subsequent algorithm
    if (convert and (number == 0)) or (
        (not convert) and (not number.strip("0").strip("."))
    ):
        return ("0", 0)
    # Break down number into its components, use Decimal type to
    # preserve resolution:
    # sign  : 0 -> +, 1 -> -
    # digits: tuple with digits of number
    # exp   : exponent that gives null fractional part
    sign, digits, exp = Decimal(str(number) if convert else number).as_tuple()
    mant = (
        "{sign}{itg}{frac}".format(
            sign="-" if sign else "",
            itg=digits[0],
            frac=(
                ".{frac}".format(frac="".join([str(num) for num in digits[1:]]))
                if len(digits) > 1
                else ""
            ),
        )
        .rstrip("0")
        .rstrip(".")
    )
    exp += len(digits) - 1
    return NumComp(mant, exp)

def to_scientific_tuple ( number ) : convert = not isinstance ( number , str ) if ( convert and ( number == 0 ) ) or ( ( not convert ) and ( not number . strip ( "0" ) . strip ( "." ) ) ) : return ( "0" , 0 ) sign , digits , exp = Decimal ( str ( number ) if convert else number ) . as_tuple ( ) mant = ( "{sign}{itg}{frac}" . format ( sign = "-" if sign else "" , itg = digits [ 0 ] , frac = ( ".{frac}" . format ( frac = "" . join ( [ str ( num ) for num in digits [ 1 : ] ] ) ) if len ( digits ) > 1 else "" ) , ) . rstrip ( "0" ) . rstrip ( "." ) ) exp += len ( digits ) - 1 return NumComp ( mant , exp )

Return mantissa and exponent of a number in scientific notation .


def find_sourcemap_comment(filepath, block_size=100):
    """
    Seeks and removes the sourcemap comment. If found, the sourcemap line is
    returned.

    Bundled output files can have massive amounts of lines, and the sourceMap
    comment is always at the end. So, to extract it efficiently, we read out the
    lines of the file starting from the end. We look back at most 2 lines.

    :param:filepath: path to output bundle file containing the sourcemap comment
    :param:blocksize: integer saying how many bytes to read at once
    :return:string with the sourcemap comment or None
    """

    MAX_TRACKBACK = 2  # look back at most 2 lines, catching potential blank line at the end

    block_number = -1
    # blocks of size block_size, in reverse order starting from the end of the file
    blocks = []
    sourcemap = None

    try:
        # open file in binary read+write mode, so we can seek with negative offsets
        of = io.open(filepath, 'br+')
        # figure out what's the end byte
        of.seek(0, os.SEEK_END)
        block_end_byte = of.tell()

        # track back for maximum MAX_TRACKBACK lines and while we can track back
        while block_end_byte > 0 and MAX_TRACKBACK > 0:
            if (block_end_byte - block_size > 0):
                # read the last block we haven't yet read
                of.seek(block_number*block_size, os.SEEK_END)
                blocks.append(of.read(block_size))
            else:
                # file too small, start from begining
                of.seek(0, os.SEEK_SET)
                # only read what was not read
                blocks = [of.read(block_end_byte)]

            # update variables that control while loop
            content = b''.join(reversed(blocks))
            lines_found = content.count(b'\n')
            MAX_TRACKBACK -= lines_found
            block_end_byte -= block_size
            block_number -= 1

            # early check and bail out if we found the sourcemap comment
            if SOURCEMAPPING_URL_COMMENT in content:
                offset = 0
                # splitlines eats the last \n if its followed by a blank line
                lines = content.split(b'\n')
                for i, line in enumerate(lines):
                    if line.startswith(SOURCEMAPPING_URL_COMMENT):
                        offset = len(line)
                        sourcemap = line
                        break
                while i+1 < len(lines):
                    offset += 1  # for the newline char
                    offset += len(lines[i+1])
                    i += 1
                # track back until the start of the comment, and truncate the comment
                if sourcemap:
                    offset += 1  # for the newline before the sourcemap comment
                    of.seek(-offset, os.SEEK_END)
                    of.truncate()
                return force_text(sourcemap)
    finally:
        of.close()
    return sourcemap

def find_sourcemap_comment ( filepath , block_size = 100 ) : MAX_TRACKBACK = 2 block_number = - 1 blocks = [ ] sourcemap = None try : of = io . open ( filepath , 'br+' ) of . seek ( 0 , os . SEEK_END ) block_end_byte = of . tell ( ) while block_end_byte > 0 and MAX_TRACKBACK > 0 : if ( block_end_byte - block_size > 0 ) : of . seek ( block_number * block_size , os . SEEK_END ) blocks . append ( of . read ( block_size ) ) else : of . seek ( 0 , os . SEEK_SET ) blocks = [ of . read ( block_end_byte ) ] content = b'' . join ( reversed ( blocks ) ) lines_found = content . count ( b'\n' ) MAX_TRACKBACK -= lines_found block_end_byte -= block_size block_number -= 1 if SOURCEMAPPING_URL_COMMENT in content : offset = 0 lines = content . split ( b'\n' ) for i , line in enumerate ( lines ) : if line . startswith ( SOURCEMAPPING_URL_COMMENT ) : offset = len ( line ) sourcemap = line break while i + 1 < len ( lines ) : offset += 1 offset += len ( lines [ i + 1 ] ) i += 1 if sourcemap : offset += 1 of . seek ( - offset , os . SEEK_END ) of . truncate ( ) return force_text ( sourcemap ) finally : of . close ( ) return sourcemap

Seeks and removes the sourcemap comment . If found the sourcemap line is returned .


def needs_ext(self):
        """
        Check whether `self.app` is missing the '.js' extension and if it needs it.
        """
        if settings.SYSTEMJS_DEFAULT_JS_EXTENSIONS:
            name, ext = posixpath.splitext(self.app)
            if not ext:
                return True
        return False

def needs_ext ( self ) : if settings . SYSTEMJS_DEFAULT_JS_EXTENSIONS : name , ext = posixpath . splitext ( self . app ) if not ext : return True return False

Check whether self . app is missing the . js extension and if it needs it .


def bundle(self):
        """
        Bundle the app and return the static url to the bundle.
        """
        outfile, rel_path = self.get_paths()

        options = self.opts
        if self.system._has_jspm_log():
            self.command += ' --log {log}'
            options.setdefault('log', 'err')

        if options.get('minify'):
            self.command += ' --minify'

        if options.get('skip_source_maps'):
            self.command += ' --skip-source-maps'

        try:
            cmd = self.command.format(app=self.app, outfile=outfile, **options)
            proc = subprocess.Popen(
                cmd, shell=True, cwd=self.system.cwd, stdout=self.stdout,
                stdin=self.stdin, stderr=self.stderr)

            result, err = proc.communicate()  # block until it's done
            if err and self.system._has_jspm_log():
                fmt = 'Could not bundle \'%s\': \n%s'
                logger.warn(fmt, self.app, err)
                raise BundleError(fmt % (self.app, err))
            if result.strip():
                logger.info(result)
        except (IOError, OSError) as e:
            if isinstance(e, BundleError):
                raise
            raise BundleError('Unable to apply %s (%r): %s' % (
                              self.__class__.__name__, cmd, e))
        else:
            if not options.get('sfx'):
                # add the import statement, which is missing for non-sfx bundles
                sourcemap = find_sourcemap_comment(outfile)
                with open(outfile, 'a') as of:
                    of.write("\nSystem.import('{app}{ext}');\n{sourcemap}".format(
                        app=self.app,
                        ext='.js' if self.needs_ext() else '',
                        sourcemap=sourcemap if sourcemap else '',
                    ))
        return rel_path

Bundle the app and return the static url to the bundle .


def trace(self, app):
        """
        Trace the dependencies for app.

        A tracer-instance is shortlived, and re-tracing the same app should
        yield the same results. Since tracing is an expensive process, cache
        the result on the tracer instance.
        """
        if app not in self._trace_cache:
            process = subprocess.Popen(
                "trace-deps.js {}".format(app), shell=True,
                stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                env=self.env, universal_newlines=True, cwd=self._package_json_dir
            )
            out, err = process.communicate()
            if err:
                raise TraceError(err)
            self._trace_cache[app] = json.loads(out)
        return self._trace_cache[app]

def
trace
(
self
,
app
)
:
if
app
not
in
self
.
_trace_cache
:
process
=
subprocess
.
Popen
(
"trace-deps.js {}"
.
format
(
app
)
,
shell
=
True
,
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
PIPE
,
env
=
self
.
env
,
universal_newlines
=
True
,
cwd
=
self
.
_package_json_dir
)
out
,
err
=
process
.
communicate
(
)
if
err
:
raise
TraceError
(
err
)
self
.
_trace_cache
[
app
]
=
json
.
loads
(
out
)
return
self
.
_trace_cache
[
app
]


def hashes_match(self, dep_tree):
        """
        Compares the app deptree file hashes with the hashes stored in the
        cache.
        """
        hashes = self.get_hashes()
        for module, info in dep_tree.items():
            md5 = self.get_hash(info['path'])
            if md5 != hashes[info['path']]:
                return False
        return True

def hashes_match ( self , dep_tree ) : hashes = self . get_hashes ( ) for module , info in dep_tree . items ( ) : md5 = self . get_hash ( info [ 'path' ] ) if md5 != hashes [ info [ 'path' ] ] : return False return True

Compares the app deptree file hashes with the hashes stored in the cache .


def format_hexdump(arg):
    """Convert the bytes object to a hexdump.

    The output format will be:

    <offset, 4-byte>  <16-bytes of output separated by 1 space>  <16 ascii characters>
    """

    line = ''

    for i in range(0, len(arg), 16):
        if i > 0:
            line += '\n'
        chunk = arg[i:i + 16]
        hex_chunk = hexlify(chunk).decode('utf-8')
        hex_line = ' '.join(hex_chunk[j:j + 2] for j in range(0, len(hex_chunk), 2))

        if len(hex_line) < (3 * 16) - 1:
            hex_line += ' ' * (((3 * 16) - 1) - len(hex_line))

        ascii_line = ''.join(_convert_to_ascii(x) for x in chunk)
        offset_line = '%08x' % i

        line += "%s  %s  %s" % (offset_line, hex_line, ascii_line)

    return line

def
format_hexdump
(
arg
)
:
line
=
''
for
i
in
range
(
0
,
len
(
arg
)
,
16
)
:
if
i
>
0
:
line
+=
'\n'
chunk
=
arg
[
i
:
i
+
16
]
hex_chunk
=
hexlify
(
chunk
)
.
decode
(
'utf-8'
)
hex_line
=
' '
.
join
(
hex_chunk
[
j
:
j
+
2
]
for
j
in
range
(
0
,
len
(
hex_chunk
)
,
2
)
)
if
len
(
hex_line
)
<
(
3
*
16
)
1
:
hex_line
+=
' '
*
(
(
(
3
*
16
)
1
)
len
(
hex_line
)
)
ascii_line
=
''
.
join
(
_convert_to_ascii
(
x
)
for
x
in
chunk
)
offset_line
=
'%08x'
%
i
line
+=
"%s  %s  %s"
%
(
offset_line
,
hex_line
,
ascii_line
)
return
line


def parse_docstring(doc):
    """Parse a docstring into ParameterInfo and ReturnInfo objects."""

    doc = inspect.cleandoc(doc)
    lines = doc.split('\n')
    section = None
    section_indent = None

    params = {}
    returns = None

    for line in lines:
        line = line.rstrip()

        if len(line) == 0:
            continue
        elif str(line) == 'Args:':
            section = 'args'
            section_indent = None
            continue
        elif str(line) == 'Returns:':
            section = 'return'
            section_indent = None
            continue

        if section is not None:
            stripped = line.lstrip()
            margin = len(line) - len(stripped)

            if section_indent is None:
                section_indent = margin

            if margin != section_indent:
                continue

            # These are all the param lines in the docstring that are
            # not continuations of the previous line
            if section == 'args':
                param_name, type_info = parse_param(stripped)
                params[param_name] = type_info
            elif section == 'return':
                returns = parse_return(stripped)

    return params, returns

def parse_docstring ( doc ) : doc = inspect . cleandoc ( doc ) lines = doc . split ( '\n' ) section = None section_indent = None params = { } returns = None for line in lines : line = line . rstrip ( ) if len ( line ) == 0 : continue elif str ( line ) == 'Args:' : section = 'args' section_indent = None continue elif str ( line ) == 'Returns:' : section = 'return' section_indent = None continue if section is not None : stripped = line . lstrip ( ) margin = len ( line ) - len ( stripped ) if section_indent is None : section_indent = margin if margin != section_indent : continue if section == 'args' : param_name , type_info = parse_param ( stripped ) params [ param_name ] = type_info elif section == 'return' : returns = parse_return ( stripped ) return params , returns

Parse a docstring into ParameterInfo and ReturnInfo objects .


def valid_identifiers(self):
        """Get a list of all valid identifiers for the current context.

        Returns:
            list(str): A list of all of the valid identifiers for this context
        """

        funcs = list(utils.find_all(self.contexts[-1])) + list(self.builtins)
        return funcs

def valid_identifiers ( self ) : funcs = list ( utils . find_all ( self . contexts [ - 1 ] ) ) + list ( self . builtins ) return funcs

Get a list of all valid identifiers for the current context .


def _deferred_add(cls, add_action):
        """Lazily load a callable.

        Perform a lazy import of a context so that we don't have a huge initial startup time
        loading all of the modules that someone might want even though they probably only
        will use a few of them.
        """

        module, _, obj = add_action.partition(',')

        mod = importlib.import_module(module)
        if obj == "":
            _, con = annotate.context_from_module(mod)
            return con

        if hasattr(mod, obj):
            return getattr(mod, obj)

        raise ArgumentError("Attempted to import nonexistent object from module", module=module, object=obj)


def _split_line(self, line):
        """Split a line into arguments using shlex and a dequoting routine."""

        parts = shlex.split(line, posix=self.posix_lex)
        if not self.posix_lex:
            parts = [self._remove_quotes(x) for x in parts]

        return parts

def _split_line ( self , line ) : parts = shlex . split ( line , posix = self . posix_lex ) if not self . posix_lex : parts = [ self . _remove_quotes ( x ) for x in parts ] return parts

Split a line into arguments using shlex and a dequoting routine .


def _check_initialize_context(self):
        """
        Check if our context matches something that we have initialization commands
        for.  If so, run them to initialize the context before proceeding with other
        commands.
        """

        path = ".".join([annotate.context_name(x) for x in self.contexts])

        #Make sure we don't clutter up the output with return values from
        #initialization functions
        old_interactive = type_system.interactive
        type_system.interactive = False

        for key, cmds in self.init_commands.items():
            if path.endswith(key):
                for cmd in cmds:
                    line = self._split_line(cmd)
                    self.invoke(line)

        type_system.interactive = old_interactive

def _check_initialize_context ( self ) : path = "." . join ( [ annotate . context_name ( x ) for x in self . contexts ] ) old_interactive = type_system . interactive type_system . interactive = False for key , cmds in self . init_commands . items ( ) : if path . endswith ( key ) : for cmd in cmds : line = self . _split_line ( cmd ) self . invoke ( line ) type_system . interactive = old_interactive

Check if our context matches something that we have initialization commands for . If so run them to initialize the context before proceeding with other commands .


def _builtin_help(self, args):
        """Return help information for a context or function."""

        if len(args) == 0:
            return self.list_dir(self.contexts[-1])
        if len(args) == 1:
            func = self.find_function(self.contexts[-1], args[0])
            return annotate.get_help(func)

        help_text = "Too many arguments: " + str(args) + "\n"
        help_text += "Usage: help [function]"
        return help_text

def
_builtin_help
(
self
,
args
)
:
if
len
(
args
)
==
0
:
return
self
.
list_dir
(
self
.
contexts
[
1
]
)
if
len
(
args
)
==
1
:
func
=
self
.
find_function
(
self
.
contexts
[
1
]
,
args
[
0
]
)
return
annotate
.
get_help
(
func
)
help_text
=
"Too many arguments: "
+
str
(
args
)
+
"\n"
help_text
+=
"Usage: help [function]"
return
help_text

Return help information for a context or function .


def find_function(self, context, funname):
        """Find a function in the given context by name.

        This function will first search the list of builtins and if the
        desired function is not a builtin, it will continue to search
        the given context.

        Args:
            context (object): A dict or class that is a typedargs context
            funname (str): The name of the function to find

        Returns:
            callable: The found function.
        """

        if funname in self.builtins:
            return self.builtins[funname]

        func = None
        if isinstance(context, dict):
            if funname in context:
                func = context[funname]

                #Allowed lazy loading of functions
                if isinstance(func, str):
                    func = self._deferred_add(func)
                    context[funname] = func
        elif hasattr(context, funname):
            func = getattr(context, funname)

        if func is None:
            raise NotFoundError("Function not found", function=funname)

        return func

def
find_function
(
self
,
context
,
funname
)
:
if
funname
in
self
.
builtins
:
return
self
.
builtins
[
funname
]
func
=
if
isinstance
(
context
,
dict
)
:
if
funname
in
context
:
func
=
context
[
funname
]
if
isinstance
(
func
,
str
)
:
func
=
self
.
_deferred_add
(
func
)
context
[
funname
]
=
func
elif
hasattr
(
context
,
funname
)
:
func
=
getattr
(
context
,
funname
)
if
func
is
:
raise
NotFoundError
(
"Function not found"
,
function
=
funname
)
return
func

Find a function in the given context by name .


def list_dir(self, context):
        """Return a listing of all of the functions in this context including builtins.

        Args:
            context (object): The context to print a directory for.

        Returns:
            str
        """

        doc = inspect.getdoc(context)

        listing = ""
        listing += "\n"

        listing += annotate.context_name(context) + "\n"

        if doc is not None:
            doc = inspect.cleandoc(doc)
            listing += doc + "\n"

        listing += "\nDefined Functions:\n"
        is_dict = False

        if isinstance(context, dict):
            funs = context.keys()
            is_dict = True
        else:
            funs = utils.find_all(context)

        for fun in sorted(funs):
            override_name = None
            if is_dict:
                override_name = fun

            fun = self.find_function(context, fun)

            if isinstance(fun, dict):
                if is_dict:
                    listing += " - " + override_name + '\n'
                else:
                    listing += " - " + fun.metadata.name + '\n'
            else:
                listing += " - " + fun.metadata.signature(name=override_name) + '\n'

            if annotate.short_description(fun) != "":
                listing += "   " + annotate.short_description(fun) + '\n'

        listing += "\nBuiltin Functions\n"
        for bif in sorted(self.builtins.keys()):
            listing += ' - ' + bif + '\n'

        listing += '\n'
        return listing

def
list_dir
(
self
,
context
)
:
doc
=
inspect
.
getdoc
(
context
)
listing
=
""
listing
+=
"\n"
listing
+=
annotate
.
context_name
(
context
)
+
"\n"
if
doc
is
not
:
doc
=
inspect
.
cleandoc
(
doc
)
listing
+=
doc
+
"\n"
listing
+=
"\nDefined Functions:\n"
is_dict
=
False
if
isinstance
(
context
,
dict
)
:
funs
=
context
.
keys
(
)
is_dict
=
True
else
:
funs
=
utils
.
find_all
(
context
)
for
fun
in
sorted
(
funs
)
:
override_name
=
if
is_dict
:
override_name
=
fun
fun
=
self
.
find_function
(
context
,
fun
)
if
isinstance
(
fun
,
dict
)
:
if
is_dict
:
listing
+=
" - "
+
override_name
+
'\n'
else
:
listing
+=
" - "
+
fun
.
metadata
.
name
+
'\n'
else
:
listing
+=
" - "
+
fun
.
metadata
.
signature
(
name
=
override_name
)
+
'\n'
if
annotate
.
short_description
(
fun
)
!=
""
:
listing
+=
"   "
+
annotate
.
short_description
(
fun
)
+
'\n'
listing
+=
"\nBuiltin Functions\n"
for
bif
in
sorted
(
self
.
builtins
.
keys
(
)
)
:
listing
+=
' - '
+
bif
+
'\n'
listing
+=
'\n'
return
listing

Return a listing of all of the functions in this context including builtins .


def _is_flag(cls, arg):
        """Check if an argument is a flag.

        A flag starts with - or -- and the next character must be a letter
        followed by letters, numbers, - or _.  Currently we only check the
        alpha'ness of the first non-dash character to make sure we're not just
        looking at a negative number.

        Returns:
            bool: Whether the argument is a flag.
        """

        if arg == '--':
            return False

        if not arg.startswith('-'):
            return False

        if arg.startswith('--'):
            first_char = arg[2]
        else:
            first_char = arg[1]

        if not first_char.isalpha():
            return False

        return True

def _is_flag ( cls , arg ) : if arg == '--' : return False if not arg . startswith ( '-' ) : return False if arg . startswith ( '--' ) : first_char = arg [ 2 ] else : first_char = arg [ 1 ] if not first_char . isalpha ( ) : return False return True


def process_arguments(self, func, args):
        """Process arguments from the command line into positional and kw args.

        Arguments are consumed until the argument spec for the function is filled
        or a -- is found or there are no more arguments.  Keyword arguments can be
        specified using --field=value, -f value or --field value.  Positional
        arguments are specified just on the command line itself.

        If a keyword argument (`field`) is a boolean, it can be set to True by just passing
        --field or -f without needing to explicitly pass True unless this would cause
        ambiguity in parsing since the next expected positional argument is also a boolean
        or a string.

        Args:
            func (callable): A function previously annotated with type information
            args (list): A list of all of the potential arguments to this function.

        Returns:
            (args, kw_args, unused args): A tuple with a list of args, a dict of
                keyword args and a list of any unused args that were not processed.
        """

        pos_args = []
        kw_args = {}

        while len(args) > 0:
            if func.metadata.spec_filled(pos_args, kw_args) and not self._is_flag(args[0]):
                break

            arg = args.pop(0)

            if arg == '--':
                break
            elif self._is_flag(arg):
                arg_value = None
                arg_name = None

                if len(arg) == 2:
                    arg_name = func.metadata.match_shortname(arg[1:], filled_args=pos_args)
                else:
                    if not arg.startswith('--'):
                        raise ArgumentError("Invalid method of specifying keyword argument that did not start with --", argument=arg)

                    # Skip the --
                    arg = arg[2:]

                    # Check if the value is embedded in the parameter
                    # Make sure we allow the value after the equals sign to also
                    # contain an equals sign.
                    if '=' in arg:
                        arg, arg_value = arg.split('=', 1)

                    arg_name = func.metadata.match_shortname(arg, filled_args=pos_args)

                arg_type = func.metadata.param_type(arg_name)
                if arg_type is None:
                    raise ArgumentError("Attempting to set a parameter from command line that does not have type information", argument=arg_name)

                # If we don't have a value yet, attempt to get one from the next parameter on the command line
                if arg_value is None:
                    arg_value = self._extract_arg_value(arg_name, arg_type, args)

                kw_args[arg_name] = arg_value
            else:
                pos_args.append(arg)

        # Always check if there is a trailing '--' and chomp so that we always
        # start on a function name.  This can happen if there is a gratuitous
        # -- for a 0 arg function or after an implicit boolean flag like -f --
        if len(args) > 0 and args[0] == '--':
            args.pop(0)

        return pos_args, kw_args, args

Process arguments from the command line into positional and kw args .


def _extract_arg_value(cls, arg_name, arg_type, remaining):
        """Try to find the value for a keyword argument."""

        next_arg = None
        should_consume = False
        if len(remaining) > 0:
            next_arg = remaining[0]
            should_consume = True

            if next_arg == '--':
                next_arg = None

        # Generally we just return the next argument, however if the type
        # is bool we allow not specifying anything to mean true if there
        # is no ambiguity
        if arg_type == "bool":
            if next_arg is None or next_arg.startswith('-'):
                next_arg = True
                should_consume = False
        else:
            if next_arg is None:
                raise ArgumentError("Could not find value for keyword argument", argument=arg_name)

        if should_consume:
            remaining.pop(0)

        return next_arg

Try to find the value for a keyword argument .


def invoke_one(self, line):
        """Invoke a function given a list of arguments with the function listed first.

        The function is searched for using the current context on the context stack
        and its annotated type information is used to convert all of the string parameters
        passed in line to appropriate python types.

        Args:
            line (list): The list of command line arguments.

        Returns:
            (object, list, bool): A tuple containing the return value of the function, if any,
                a boolean specifying if the function created a new context (False if a new context
                was created) and a list with the remainder of the command line if this function
                did not consume all arguments.
        """

        funname = line.pop(0)

        context = self.contexts[-1]
        func = self.find_function(context, funname)

        #If this is a context derived from a module or package, just jump to it
        #since there is no initialization function
        if isinstance(func, dict):
            self.contexts.append(func)
            self._check_initialize_context()
            return None, line, False

        # If the function wants arguments directly, do not parse them, otherwise turn them
        # into positional and kw arguments
        if func.takes_cmdline is True:
            val = func(line)
            line = []
        else:
            posargs, kwargs, line = self.process_arguments(func, line)

            #We need to check for not enough args for classes before calling or the call won't make it all the way to __init__
            if inspect.isclass(func) and not func.metadata.spec_filled(posargs, kwargs):
                raise ValidationError("Not enough parameters specified to call function", function=func.metadata.name, signature=func.metadata.signature())

            val = func(*posargs, **kwargs)

        # Update our current context if this function destroyed it or returned a new one.
        finished = True

        if func.finalizer is True:
            self.contexts.pop()
        elif val is not None:
            if func.metadata.returns_data():
                val = func.metadata.format_returnvalue(val)
            else:
                self.contexts.append(val)
                self._check_initialize_context()
                finished = False
                val = None

        return val, line, finished

Invoke a function given a list of arguments with the function listed first .


def invoke(self, line):
        """Invoke a one or more function given a list of arguments.

        The functions are searched for using the current context on the context stack
        and its annotated type information is used to convert all of the string parameters
        passed in line to appropriate python types.

        Args:
            line (list): The list of command line arguments.

        Returns:
            bool: A boolean specifying if the last function created a new context
                (False if a new context was created) and a list with the remainder of the
                command line if this function did not consume all arguments.)
        """

        finished = True

        while len(line) > 0:
            val, line, finished = self.invoke_one(line)
            if val is not None:
                iprint(val)

        return finished

def invoke ( self , line ) : finished = True while len ( line ) > 0 : val , line , finished = self . invoke_one ( line ) if val is not None : iprint ( val ) return finished

Invoke a one or more function given a list of arguments .


def invoke_string(self, line):
        """Parse and invoke a string line.

        Args:
            line (str): The line that we want to parse and invoke.

        Returns:
            bool: A boolean specifying if the last function created a new context
                (False if a new context was created) and a list with the remainder of the
                command line if this function did not consume all arguments.)
        """

        # Make sure line is a unicode string on all python versions
        line = str(line)

        # Ignore empty lines and comments
        if len(line) == 0:
            return True

        if line[0] == u'#':
            return True

        args = self._split_line(line)
        return self.invoke(args)

def invoke_string ( self , line ) : line = str ( line ) if len ( line ) == 0 : return True if line [ 0 ] == u'#' : return True args = self . _split_line ( line ) return self . invoke ( args )


def parse_param(param, include_desc=False):
    """Parse a single typed parameter statement."""

    param_def, _colon, desc = param.partition(':')
    if not include_desc:
        desc = None
    else:
        desc = desc.lstrip()

    if _colon == "":
        raise ValidationError("Invalid parameter declaration in docstring, missing colon", declaration=param)

    param_name, _space, param_type = param_def.partition(' ')
    if len(param_type) < 2 or param_type[0] != '(' or param_type[-1] != ')':
        raise ValidationError("Invalid parameter type string not enclosed in ( ) characters", param_string=param_def, type_string=param_type)

    param_type = param_type[1:-1]
    return param_name, ParameterInfo(param_type, [], desc)

Parse a single typed parameter statement .


def parse_return(return_line, include_desc=False):
    """Parse a single return statement declaration.

    The valid types of return declarion are a Returns: section heading
    followed a line that looks like:
    type [format-as formatter]: description

    OR

    type [show-as (string | context)]: description sentence
    """

    ret_def, _colon, desc = return_line.partition(':')
    if _colon == "":
        raise ValidationError("Invalid return declaration in docstring, missing colon", declaration=ret_def)

    if not include_desc:
        desc = None

    if 'show-as' in ret_def:
        ret_type, _showas, show_type = ret_def.partition('show-as')
        ret_type = ret_type.strip()
        show_type = show_type.strip()

        if show_type not in ('string', 'context'):
            raise ValidationError("Unkown show-as formatting specifier", found=show_type, expected=['string', 'context'])

        if show_type == 'string':
            return ReturnInfo(None, str, True, desc)

        return ReturnInfo(None, None, False, desc)

    if 'format-as' in ret_def:
        ret_type, _showas, formatter = ret_def.partition('format-as')
        ret_type = ret_type.strip()
        formatter = formatter.strip()

        return ReturnInfo(ret_type, formatter, True, desc)

    return ReturnInfo(ret_def, None, True, desc)

Parse a single return statement declaration .


def _classify_section(cls, section):
        """Attempt to find the canonical name of this section."""

        name = section.lower()

        if name in frozenset(['args', 'arguments', "params", "parameters"]):
            return cls.ARGS_SECTION

        if name in frozenset(['returns', 'return']):
            return cls.RETURN_SECTION

        if name in frozenset(['main']):
            return cls.MAIN_SECTION

        return None

def _classify_section ( cls , section ) : name = section . lower ( ) if name in frozenset ( [ 'args' , 'arguments' , "params" , "parameters" ] ) : return cls . ARGS_SECTION if name in frozenset ( [ 'returns' , 'return' ] ) : return cls . RETURN_SECTION if name in frozenset ( [ 'main' ] ) : return cls . MAIN_SECTION return None

Attempt to find the canonical name of this section .


def _classify_line(cls, line):
        """Classify a line into a type of object."""

        line = line.rstrip()

        if len(line) == 0:
            return BlankLine('')

        if ' ' not in line and line.endswith(':'):
            name = line[:-1]
            return SectionHeader(name)

        if line.startswith('  '):
            return ContinuationLine(line.lstrip())

        if line.startswith(' - '):
            return ListItem('-', line[3:].lstrip())

        if line.startswith('- '):
            return ListItem('-', line[2:].lstrip())

        return Line(line)

def
_classify_line
(
cls
,
line
)
:
line
=
line
.
rstrip
(
)
if
len
(
line
)
==
0
:
return
BlankLine
(
''
)
if
' '
not
in
line
and
line
.
endswith
(
':'
)
:
name
=
line
[
:
1
]
return
SectionHeader
(
name
)
if
line
.
startswith
(
'  '
)
:
return
ContinuationLine
(
line
.
lstrip
(
)
)
if
line
.
startswith
(
' - '
)
:
return
ListItem
(
'-'
,
line
[
3
:
]
.
lstrip
(
)
)
if
line
.
startswith
(
'- '
)
:
return
ListItem
(
'-'
,
line
[
2
:
]
.
lstrip
(
)
)
return
Line
(
line
)


def _join_paragraphs(cls, lines, use_indent=False, leading_blanks=False, trailing_blanks=False):
        """Join adjacent lines together into paragraphs using either a blank line or indent as separator."""

        curr_para = []
        paragraphs = []

        for line in lines:
            if use_indent:
                if line.startswith(' '):
                    curr_para.append(line.lstrip())
                    continue
                elif line == '':
                    continue
                else:
                    if len(curr_para) > 0:
                        paragraphs.append(cls._join_paragraph(curr_para, leading_blanks, trailing_blanks))

                    curr_para = [line.lstrip()]
            else:
                if len(line) != 0:
                    curr_para.append(line)
                else:
                    paragraphs.append(cls._join_paragraph(curr_para, leading_blanks, trailing_blanks))
                    curr_para = []

        # Finish the last paragraph if there is one
        if len(curr_para) > 0:
            paragraphs.append(cls._join_paragraph(curr_para, leading_blanks, trailing_blanks))

        return paragraphs

def
_join_paragraphs
(
cls
,
lines
,
use_indent
=
False
,
leading_blanks
=
False
,
trailing_blanks
=
False
)
:
curr_para
=
[
]
paragraphs
=
[
]
for
line
in
lines
:
if
use_indent
:
if
line
.
startswith
(
' '
)
:
curr_para
.
append
(
line
.
lstrip
(
)
)
continue
elif
line
==
''
:
continue
else
:
if
len
(
curr_para
)
>
0
:
paragraphs
.
append
(
cls
.
_join_paragraph
(
curr_para
,
leading_blanks
,
trailing_blanks
)
)
curr_para
=
[
line
.
lstrip
(
)
]
else
:
if
len
(
line
)
!=
0
:
curr_para
.
append
(
line
)
else
:
paragraphs
.
append
(
cls
.
_join_paragraph
(
curr_para
,
leading_blanks
,
trailing_blanks
)
)
curr_para
=
[
]
if
len
(
curr_para
)
>
0
:
paragraphs
.
append
(
cls
.
_join_paragraph
(
curr_para
,
leading_blanks
,
trailing_blanks
)
)
return
paragraphs

Join adjacent lines together into paragraphs using either a blank line or indent as separator .


def wrap_and_format(self, width=None, include_params=False, include_return=False, excluded_params=None):
        """Wrap, format and print this docstring for a specific width.

        Args:
            width (int): The number of characters per line.  If set to None
                this will be inferred from the terminal width and default
                to 80 if not passed or if passed as None and the terminal
                width cannot be determined.
            include_return (bool): Include the return information section
                in the output.
            include_params (bool): Include a parameter information section
                in the output.
            excluded_params (list): An optional list of parameter names to exclude.
                Options for excluding things are, for example, 'self' or 'cls'.
        """

        if excluded_params is None:
            excluded_params = []

        out = StringIO()
        if width is None:
            width, _height = get_terminal_size()

        for line in self.maindoc:
            if isinstance(line, Line):
                out.write(fill(line.contents, width=width))
                out.write('\n')
            elif isinstance(line, BlankLine):
                out.write('\n')
            elif isinstance(line, ListItem):
                out.write(fill(line.contents, initial_indent=" %s " % line.marker[0], subsequent_indent="   ", width=width))
                out.write('\n')

        if include_params:
            included_params = set(self.param_info) - set(excluded_params)
            if len(included_params) > 0:
                out.write("\nParameters:\n")

                for param in included_params:
                    info = self.param_info[param]
                    out.write(" - %s (%s):\n" % (param, info.type_name))
                    out.write(fill(info.desc, initial_indent="   ", subsequent_indent="   ", width=width))
                    out.write('\n')

        if include_return:
            print("Returns:")
            print("    " + self.return_info.type_name)
            #pylint:disable=fixme; Issue tracked in #32
            # TODO: Also include description information here

        return out.getvalue()

def
wrap_and_format
(
self
,
width
=
,
include_params
=
False
,
include_return
=
False
,
excluded_params
=
)
:
if
excluded_params
is
:
excluded_params
=
[
]
out
=
StringIO
(
)
if
width
is
:
width
,
_height
=
get_terminal_size
(
)
for
line
in
self
.
maindoc
:
if
isinstance
(
line
,
Line
)
:
out
.
write
(
fill
(
line
.
contents
,
width
=
width
)
)
out
.
write
(
'\n'
)
elif
isinstance
(
line
,
BlankLine
)
:
out
.
write
(
'\n'
)
elif
isinstance
(
line
,
ListItem
)
:
out
.
write
(
fill
(
line
.
contents
,
initial_indent
=
" %s "
%
line
.
marker
[
0
]
,
subsequent_indent
=
"   "
,
width
=
width
)
)
out
.
write
(
'\n'
)
if
include_params
:
included_params
=
set
(
self
.
param_info
)
set
(
excluded_params
)
if
len
(
included_params
)
>
0
:
out
.
write
(
"\nParameters:\n"
)
for
param
in
included_params
:
info
=
self
.
param_info
[
param
]
out
.
write
(
" - %s (%s):\n"
%
(
param
,
info
.
type_name
)
)
out
.
write
(
fill
(
info
.
desc
,
initial_indent
=
"   "
,
subsequent_indent
=
"   "
,
width
=
width
)
)
out
.
write
(
'\n'
)
if
include_return
:
print
(
"Returns:"
)
print
(
"    "
+
self
.
return_info
.
type_name
)
return
out
.
getvalue
(
)

Wrap format and print this docstring for a specific width .


def convert_to_type(self, value, typename, **kwargs):
        """
        Convert value to type 'typename'

        If the conversion routine takes various kwargs to
        modify the conversion process, \\**kwargs is passed
        through to the underlying conversion function
        """
        try:
            if isinstance(value, bytearray):
                return self.convert_from_binary(value, typename, **kwargs)

            typeobj = self.get_type(typename)

            conv = typeobj.convert(value, **kwargs)
            return conv
        except (ValueError, TypeError) as exc:
            raise ValidationError("Could not convert value", type=typename, value=value, error_message=str(exc))

def
convert_to_type
(
self
,
value
,
typename
,
**
kwargs
)
:
try
:
if
isinstance
(
value
,
bytearray
)
:
return
self
.
convert_from_binary
(
value
,
typename
,
**
kwargs
)
typeobj
=
self
.
get_type
(
typename
)
conv
=
typeobj
.
convert
(
value
,
**
kwargs
)
return
conv
except
(
ValueError
,
TypeError
)
as
exc
:
raise
ValidationError
(
"Could not convert value"
,
type
=
typename
,
value
=
value
,
error_message
=
str
(
exc
)
)


def convert_from_binary(self, binvalue, type, **kwargs):
        """
        Convert binary data to type 'type'.

        'type' must have a convert_binary function.  If 'type'
        supports size checking, the size function is called to ensure
        that binvalue is the correct size for deserialization
        """

        size = self.get_type_size(type)
        if size > 0 and len(binvalue) != size:
            raise ArgumentError("Could not convert type from binary since the data was not the correct size", required_size=size, actual_size=len(binvalue), type=type)

        typeobj = self.get_type(type)

        if not hasattr(typeobj, 'convert_binary'):
            raise ArgumentError("Type does not support conversion from binary", type=type)

        return typeobj.convert_binary(binvalue, **kwargs)


def get_type_size(self, type):
        """
        Get the size of this type for converting a hex string to the
        type. Return 0 if the size is not known.
        """

        typeobj = self.get_type(type)

        if hasattr(typeobj, 'size'):
            return typeobj.size()

        return 0

def get_type_size ( self , type ) : typeobj = self . get_type ( type ) if hasattr ( typeobj , 'size' ) : return typeobj . size ( ) return 0

Get the size of this type for converting a hex string to the type . Return 0 if the size is not known .


def format_value(self, value, type, format=None, **kwargs):
        """
        Convert value to type and format it as a string

        type must be a known type in the type system and format,
        if given, must specify a valid formatting option for the
        specified type.
        """

        typed_val = self.convert_to_type(value, type, **kwargs)
        typeobj = self.get_type(type)

        #Allow types to specify default formatting functions as 'default_formatter'
        #otherwise if not format is specified, just convert the value to a string
        if format is None:
            if hasattr(typeobj, 'default_formatter'):
                format_func = getattr(typeobj, 'default_formatter')
                return format_func(typed_val, **kwargs)

            return str(typed_val)

        formatter = "format_%s" % str(format)
        if not hasattr(typeobj, formatter):
            raise ArgumentError("Unknown format for type", type=type, format=format, formatter_function=formatter)

        format_func = getattr(typeobj, formatter)
        return format_func(typed_val, **kwargs)

def
format_value
(
self
,
value
,
type
,
format
=
,
**
kwargs
)
:
typed_val
=
self
.
convert_to_type
(
value
,
type
,
**
kwargs
)
typeobj
=
self
.
get_type
(
type
)
if
format
is
:
if
hasattr
(
typeobj
,
'default_formatter'
)
:
format_func
=
getattr
(
typeobj
,
'default_formatter'
)
return
format_func
(
typed_val
,
**
kwargs
)
return
str
(
typed_val
)
formatter
=
"format_%s"
%
str
(
format
)
if
not
hasattr
(
typeobj
,
formatter
)
:
raise
ArgumentError
(
"Unknown format for type"
,
type
=
type
,
format
=
format
,
formatter_function
=
formatter
)
format_func
=
getattr
(
typeobj
,
formatter
)
return
format_func
(
typed_val
,
**
kwargs
)


def _validate_type(cls, typeobj):
        """
        Validate that all required type methods are implemented.

        At minimum a type must have:
        - a convert() or convert_binary() function
        - a default_formatter() function

        Raises an ArgumentError if the type is not valid
        """

        if not (hasattr(typeobj, "convert") or hasattr(typeobj, "convert_binary")):
            raise ArgumentError("type is invalid, does not have convert or convert_binary function", type=typeobj, methods=dir(typeobj))

        if not hasattr(typeobj, "default_formatter"):
            raise ArgumentError("type is invalid, does not have default_formatter function", type=typeobj, methods=dir(typeobj))

Validate that all required type methods are implemented .


def is_known_type(self, type_name):
        """Check if type is known to the type system.

        Returns:
            bool: True if the type is a known instantiated simple type, False otherwise
        """

        type_name = str(type_name)
        if type_name in self.known_types:
            return True

        return False

def is_known_type ( self , type_name ) : type_name = str ( type_name ) if type_name in self . known_types : return True return False

Check if type is known to the type system .


def split_type(self, typename):
        """
        Given a potentially complex type, split it into its base type and specializers
        """

        name = self._canonicalize_type(typename)
        if '(' not in name:
            return name, False, []

        base, sub = name.split('(')
        if len(sub) == 0 or sub[-1] != ')':
            raise ArgumentError("syntax error in complex type, no matching ) found", passed_type=typename, basetype=base, subtype_string=sub)

        sub = sub[:-1]

        subs = sub.split(',')
        return base, True, subs

Given a potentially complex type split it into its base type and specializers


def instantiate_type(self, typename, base, subtypes):
        """Instantiate a complex type."""

        if base not in self.type_factories:
            raise ArgumentError("unknown complex base type specified", passed_type=typename, base_type=base)

        base_type = self.type_factories[base]

        #Make sure all of the subtypes are valid
        for sub_type in subtypes:
            try:
                self.get_type(sub_type)
            except KeyValueException as exc:
                raise ArgumentError("could not instantiate subtype for complex type", passed_type=typename, sub_type=sub_type, error=exc)

        typeobj = base_type.Build(*subtypes, type_system=self)
        self.inject_type(typename, typeobj)


def get_type(self, type_name):
        """Return the type object corresponding to a type name.

        If type_name is not found, this triggers the loading of
        external types until a matching type is found or until there
        are no more external type sources.
        """

        type_name = self._canonicalize_type(type_name)

        # Add basic transformations on common abbreviations
        if str(type_name) == 'int':
            type_name = 'integer'
        elif str(type_name) == 'str':
            type_name = 'string'
        elif str(type_name) == 'dict':
            type_name = 'basic_dict'

        if self.is_known_type(type_name):
            return self.known_types[type_name]

        base_type, is_complex, subtypes = self.split_type(type_name)
        if is_complex and base_type in self.type_factories:
            self.instantiate_type(type_name, base_type, subtypes)
            return self.known_types[type_name]

        # If we're here, this is a type that we don't know anything about, so go find it.
        i = 0
        for i, (source, name) in enumerate(self._lazy_type_sources):
            if isinstance(source, str):
                import pkg_resources

                for entry in pkg_resources.iter_entry_points(source):
                    try:
                        mod = entry.load()
                        type_system.load_type_module(mod)
                    except:  #pylint:disable=W0702; We want to catch everything here since we don't want external plugins breaking us
                        fail_info = ("Entry point group: %s, name: %s" % (source, entry.name), sys.exc_info)
                        logging.exception("Error loading external type source from entry point, group: %s, name: %s", source, entry.name)
                        self.failed_sources.append(fail_info)
            else:
                try:
                    source(self)
                except:  #pylint:disable=W0702; We want to catch everything here since we don't want external plugins breaking us
                    fail_info = ("source: %s" % name, sys.exc_info)
                    logging.exception("Error loading external type source, source: %s", source)
                    self.failed_sources.append(fail_info)

            # Only load as many external sources as we need to resolve this type_name
            if self.is_known_type(type_name) or (is_complex and base_type in self.type_factories):
                break

        self._lazy_type_sources = self._lazy_type_sources[i:]

        # If we've loaded everything and we still can't find it then there's a configuration error somewhere
        if not (self.is_known_type(type_name) or (is_complex and base_type in self.type_factories)):
            raise ArgumentError("get_type called on unknown type", type=type_name, failed_external_sources=[x[0] for x in self.failed_sources])

        return self.get_type(type_name)

Return the type object corresponding to a type name .


def is_known_format(self, type, format):
        """
        Check if format is known for given type.

        Returns boolean indicating if format is valid for the specified type.
        """

        typeobj = self.get_type(type)

        formatter = "format_%s" % str(format)
        if not hasattr(typeobj, formatter):
            return False

        return True

def is_known_format ( self , type , format ) : typeobj = self . get_type ( type ) formatter = "format_%s" % str ( format ) if not hasattr ( typeobj , formatter ) : return False return True

Check if format is known for given type .


def inject_type(self, name, typeobj):
        """
        Given a module-like object that defines a type, add it to our type system so that
        it can be used with the iotile tool and with other annotated API functions.
        """

        name = self._canonicalize_type(name)
        _, is_complex, _ = self.split_type(name)

        if self.is_known_type(name):
            raise ArgumentError("attempting to inject a type that is already defined", type=name)

        if (not is_complex) and self._is_factory(typeobj):
            if name in self.type_factories:
                raise ArgumentError("attempted to inject a complex type factory that is already defined", type=name)
            self.type_factories[name] = typeobj
        else:
            self._validate_type(typeobj)
            self.known_types[name] = typeobj

        if not hasattr(typeobj, "default_formatter"):
            raise ArgumentError("type is invalid, does not have default_formatter function", type=typeobj, methods=dir(typeobj))

Given a module - like object that defines a type add it to our type system so that it can be used with the iotile tool and with other annotated API functions .


def load_type_module(self, module):
        """
        Given a module that contains a list of some types find all symbols in the module that
        do not start with _ and attempt to import them as types.
        """

        for name in (x for x in dir(module) if not x.startswith('_')):
            typeobj = getattr(module, name)

            try:
                self.inject_type(name, typeobj)
            except ArgumentError:
                pass

def load_type_module ( self , module ) : for name in ( x for x in dir ( module ) if not x . startswith ( '_' ) ) : typeobj = getattr ( module , name ) try : self . inject_type ( name , typeobj ) except ArgumentError : pass

Given a module that contains a list of some types find all symbols in the module that do not start with _ and attempt to import them as types .


def spec_filled(self, pos_args, kw_args):
        """Check if we have enough arguments to call this function.

        Args:
            pos_args (list): A list of all the positional values we have.
            kw_args (dict): A dict of all of the keyword args we have.

        Returns:
            bool: True if we have a filled spec, False otherwise.
        """

        req_names = self.arg_names
        if len(self.arg_defaults) > 0:
            req_names = req_names[:-len(self.arg_defaults)]

        req = [x for x in req_names if x not in kw_args]
        return len(req) <= len(pos_args)

def spec_filled ( self , pos_args , kw_args ) : req_names = self . arg_names if len ( self . arg_defaults ) > 0 : req_names = req_names [ : - len ( self . arg_defaults ) ] req = [ x for x in req_names if x not in kw_args ] return len ( req ) <= len ( pos_args )

Check if we have enough arguments to call this function .


def add_param(self, name, type_name, validators, desc=None):
        """Add type information for a parameter by name.

        Args:
            name (str): The name of the parameter we wish to annotate
            type_name (str): The name of the parameter's type
            validators (list): A list of either strings or n tuples that each
                specify a validator defined for type_name.  If a string is passed,
                the validator is invoked with no extra arguments.  If a tuple is
                passed, the validator will be invoked with the extra arguments.
            desc (str): Optional parameter description.
        """

        if name in self.annotated_params:
            raise TypeSystemError("Annotation specified multiple times for the same parameter", param=name)

        if name not in self.arg_names and name != self.varargs and name != self.kwargs:
            raise TypeSystemError("Annotation specified for unknown parameter", param=name)

        info = ParameterInfo(type_name, validators, desc)
        self.annotated_params[name] = info

Add type information for a parameter by name .


def typed_returnvalue(self, type_name, formatter=None):
        """Add type information to the return value of this function.

        Args:
            type_name (str): The name of the type of the return value.
            formatter (str): An optional name of a formatting function specified
                for the type given in type_name.
        """
        self.return_info = ReturnInfo(type_name, formatter, True, None)

def typed_returnvalue ( self , type_name , formatter = None ) : self . return_info = ReturnInfo ( type_name , formatter , True , None )

Add type information to the return value of this function .


def custom_returnvalue(self, printer, desc=None):
        """Use a custom function to print the return value.

        Args:
            printer (callable): A function that should take in the return
                value and convert it to a string.
            desc (str): An optional description of the return value.
        """
        self.return_info = ReturnInfo(None, printer, True, desc)

def custom_returnvalue ( self , printer , desc = None ) : self . return_info = ReturnInfo ( None , printer , True , desc )

Use a custom function to print the return value .


def match_shortname(self, name, filled_args=None):
        """Try to convert a prefix into a parameter name.

        If the result could be ambiguous or there is no matching
        parameter, throw an ArgumentError

        Args:
            name (str): A prefix for a parameter name
            filled_args (list): A list of filled positional arguments that will be
                removed from consideration.

        Returns:
            str: The full matching parameter name
        """

        filled_count = 0
        if filled_args is not None:
            filled_count = len(filled_args)

        possible = [x for x in self.arg_names[filled_count:] if x.startswith(name)]
        if len(possible) == 0:
            raise ArgumentError("Could not convert short-name full parameter name, none could be found", short_name=name, parameters=self.arg_names)
        elif len(possible) > 1:
            raise ArgumentError("Short-name is ambiguous, could match multiple keyword parameters", short_name=name, possible_matches=possible)

        return possible[0]

Try to convert a prefix into a parameter name .


def param_type(self, name):
        """Get the parameter type information by name.

        Args:
            name (str): The full name of a parameter.

        Returns:
            str: The type name or None if no type information is given.
        """

        self._ensure_loaded()

        if name not in self.annotated_params:
            return None

        return self.annotated_params[name].type_name

def param_type ( self , name ) : self . _ensure_loaded ( ) if name not in self . annotated_params : return None return self . annotated_params [ name ] . type_name

Get the parameter type information by name .


def signature(self, name=None):
        """Return our function signature as a string.

        By default this function uses the annotated name of the function
        however if you need to override that with a custom name you can
        pass name=<custom name>

        Args:
            name (str): Optional name to override the default name given
                in the function signature.

        Returns:
            str: The formatted function signature
        """

        self._ensure_loaded()

        if name is None:
            name = self.name

        num_args = len(self.arg_names)

        num_def = 0
        if self.arg_defaults is not None:
            num_def = len(self.arg_defaults)

        num_no_def = num_args - num_def

        args = []
        for i in range(0, len(self.arg_names)):
            typestr = ""

            if self.arg_names[i] in self.annotated_params:
                typestr = "{} ".format(self.annotated_params[self.arg_names[i]].type_name)

            if i >= num_no_def:
                default = str(self.arg_defaults[i-num_no_def])
                if len(default) == 0:
                    default = "''"

                args.append("{}{}={}".format(typestr, str(self.arg_names[i]), default))
            else:
                args.append(typestr + str(self.arg_names[i]))

        return "{}({})".format(name, ", ".join(args))

def
signature
(
self
,
name
=
)
:
self
.
_ensure_loaded
(
)
if
name
is
:
name
=
self
.
name
num_args
=
len
(
self
.
arg_names
)
num_def
=
0
if
self
.
arg_defaults
is
not
:
num_def
=
len
(
self
.
arg_defaults
)
num_no_def
=
num_args
num_def
args
=
[
]
for
i
in
range
(
0
,
len
(
self
.
arg_names
)
)
:
typestr
=
""
if
self
.
arg_names
[
i
]
in
self
.
annotated_params
:
typestr
=
"{} "
.
format
(
self
.
annotated_params
[
self
.
arg_names
[
i
]
]
.
type_name
)
if
i
>=
num_no_def
:
default
=
str
(
self
.
arg_defaults
[
i
num_no_def
]
)
if
len
(
default
)
==
0
:
default
=
"''"
args
.
append
(
"{}{}={}"
.
format
(
typestr
,
str
(
self
.
arg_names
[
i
]
)
,
default
)
)
else
:
args
.
append
(
typestr
+
str
(
self
.
arg_names
[
i
]
)
)
return
"{}({})"
.
format
(
name
,
", "
.
join
(
args
)
)

Return our function signature as a string .


def format_returnvalue(self, value):
        """Format the return value of this function as a string.

        Args:
            value (object): The return value that we are supposed to format.

        Returns:
            str: The formatted return value, or None if this function indicates
                that it does not return data
        """

        self._ensure_loaded()

        if not self.return_info.is_data:
            return None

        # If the return value is typed, use the type_system to format it
        if self.return_info.type_name is not None:
            return typeinfo.type_system.format_value(value, self.return_info.type_name, self.return_info.formatter)

        # Otherwise we expect a callable function to convert this value to a string
        return self.return_info.formatter(value)

def format_returnvalue ( self , value ) : self . _ensure_loaded ( ) if not self . return_info . is_data : return None if self . return_info . type_name is not None : return typeinfo . type_system . format_value ( value , self . return_info . type_name , self . return_info . formatter ) return self . return_info . formatter ( value )

Format the return value of this function as a string .


def convert_positional_argument(self, index, arg_value):
        """Convert and validate a positional argument.

        Args:
            index (int): The positional index of the argument
            arg_value (object): The value to convert and validate

        Returns:
            object: The converted value.
        """

        # For bound methods, skip self
        if self._has_self:
            if index == 0:
                return arg_value

            index -= 1

        arg_name = self.arg_names[index]
        return self.convert_argument(arg_name, arg_value)

def convert_positional_argument ( self , index , arg_value ) : if self . _has_self : if index == 0 : return arg_value index -= 1 arg_name = self . arg_names [ index ] return self . convert_argument ( arg_name , arg_value )

Convert and validate a positional argument .


def check_spec(self, pos_args, kwargs=None):
        """Check if there are any missing or duplicate arguments.

        Args:
            pos_args (list): A list of arguments that will be passed as positional
                arguments.
            kwargs (dict): A dictionary of the keyword arguments that will be passed.

        Returns:
            dict: A dictionary of argument name to argument value, pulled from either
                the value passed or the default value if no argument is passed.

        Raises:
            ArgumentError: If a positional or keyword argument does not fit in the spec.
            ValidationError: If an argument is passed twice.
        """

        if kwargs is None:
            kwargs = {}

        if self.varargs is not None or self.kwargs is not None:
            raise InternalError("check_spec cannot be called on a function that takes *args or **kwargs")

        missing = object()

        arg_vals = [missing]*len(self.arg_names)
        kw_indices = {name: i for i, name in enumerate(self.arg_names)}

        for i, arg in enumerate(pos_args):
            if i >= len(arg_vals):
                raise ArgumentError("Too many positional arguments, first excessive argument=%s" % str(arg))

            arg_vals[i] = arg

        for arg, val in kwargs.items():
            index = kw_indices.get(arg)
            if index is None:
                raise ArgumentError("Cannot find argument by name: %s" % arg)

            if arg_vals[index] is not missing:
                raise ValidationError("Argument %s passed twice" % arg)

            arg_vals[index] = val

        # Fill in any default variables if their args are missing
        if len(self.arg_defaults) > 0:
            for i in range(0, len(self.arg_defaults)):
                neg_index = -len(self.arg_defaults) + i
                if arg_vals[neg_index] is missing:
                    arg_vals[neg_index] = self.arg_defaults[i]

        # Now make sure there isn't a missing gap
        if missing in arg_vals:
            index = arg_vals.index(missing)
            raise ArgumentError("Missing a required argument (position: %d, name: %s)" % (index, self.arg_names[index]))

        return {name: val for name, val in zip(self.arg_names, arg_vals)}

Check if there are any missing or duplicate arguments .


def convert_argument(self, arg_name, arg_value):
        """Given a parameter with type information, convert and validate it.

        Args:
            arg_name (str): The name of the argument to convert and validate
            arg_value (object): The value to convert and validate

        Returns:
            object: The converted value.
        """

        self._ensure_loaded()

        type_name = self.param_type(arg_name)
        if type_name is None:
            return arg_value

        val = typeinfo.type_system.convert_to_type(arg_value, type_name)

        validators = self.annotated_params[arg_name].validators
        if len(validators) == 0:
            return val

        type_obj = typeinfo.type_system.get_type(type_name)

        # Run all of the validators that were defined for this argument.
        # If the validation fails, they will raise an exception that we convert to
        # an instance of ValidationError
        try:
            for validator_name, extra_args in validators:
                if not hasattr(type_obj, validator_name):
                    raise ValidationError("Could not find validator specified for argument", argument=arg_name, validator_name=validator_name, type=str(type_obj), method=dir(type_obj))

                validator = getattr(type_obj, validator_name)
                validator(val, *extra_args)
        except (ValueError, TypeError) as exc:
            raise ValidationError(exc.args[0], argument=arg_name, arg_value=val)

        return val

Given a parameter with type information convert and validate it .


def format(self, exclude_class=False):
        """Format this exception as a string including class name.

        Args:
            exclude_class (bool): Whether to exclude the exception class
                name when formatting this exception

        Returns:
            string: a multiline string with the message, class name and
                key value parameters passed to create the exception.
        """

        if exclude_class:
            msg = self.msg
        else:
            msg = "%s: %s" % (self.__class__.__name__, self.msg)

        if len(self.params) != 0:
            paramstring = "\n".join([str(key) + ": " + str(val) for key, val in self.params.items()])
            msg += "\nAdditional Information:\n" + paramstring

        return msg

def
format
(
self
,
exclude_class
=
False
)
:
if
exclude_class
:
msg
=
self
.
msg
else
:
msg
=
"%s: %s"
%
(
self
.
__class__
.
__name__
,
self
.
msg
)
if
len
(
self
.
params
)
!=
0
:
paramstring
=
"\n"
.
join
(
[
str
(
key
)
+
": "
+
str
(
val
)
for
key
,
val
in
self
.
params
.
items
(
)
]
)
msg
+=
"\nAdditional Information:\n"
+
paramstring
return
msg

Format this exception as a string including class name .


def to_dict(self):
        """Convert this exception to a dictionary.

        Returns:
            dist: A dictionary of information about this exception,
                Has a 'reason' key, a 'type' key  and a dictionary of params
        """

        out = {}
        out['reason'] = self.msg
        out['type'] = self.__class__.__name__
        out['params'] = self.params

        return out

def to_dict ( self ) : out = { } out [ 'reason' ] = self . msg out [ 'type' ] = self . __class__ . __name__ out [ 'params' ] = self . params return out

Convert this exception to a dictionary .


def _check_and_execute(func, *args, **kwargs):
    """
    Check the type of all parameters with type information, converting
    as appropriate and then execute the function.
    """

    convargs = []

    #Convert and validate all arguments
    for i, arg in enumerate(args):
        val = func.metadata.convert_positional_argument(i, arg)
        convargs.append(val)

    convkw = {}
    for key, val in kwargs:
        convkw[key] = func.metadata.convert_argument(key, val)

    if not func.metadata.spec_filled(convargs, convkw):
        raise ValidationError("Not enough parameters specified to call function", function=func.metadata.name, signature=func.metadata.signature())

    retval = func(*convargs, **convkw)
    return retval

Check the type of all parameters with type information converting as appropriate and then execute the function .


def _parse_validators(valids):
    """Parse a list of validator names or n-tuples, checking for errors.

    Returns:
        list((func_name, [args...])): A list of validator function names and a
            potentially empty list of optional parameters for each function.
    """

    outvals = []

    for val in valids:
        if isinstance(val, str):
            args = []
        elif len(val) > 1:
            args = val[1:]
            val = val[0]
        else:
            raise ValidationError("You must pass either an n-tuple or a string to define a validator", validator=val)

        name = "validate_%s" % str(val)
        outvals.append((name, args))

    return outvals

Parse a list of validator names or n - tuples checking for errors .


def find_all(container):
    """Find all annotated function inside of a container.

    Annotated functions are identified as those that:
    - do not start with a _ character
    - are either annotated with metadata
    - or strings that point to lazily loaded modules

    Args:
        container (object): The container to search for annotated functions.

    Returns:
        dict: A dict with all of the found functions in it.
    """

    if isinstance(container, dict):
        names = container.keys()
    else:
        names = dir(container)

    built_context = BasicContext()

    for name in names:
        # Ignore _ and __ names
        if name.startswith('_'):
            continue

        if isinstance(container, dict):
            obj = container[name]
        else:
            obj = getattr(container, name)

        # Check if this is an annotated object that should be included.  Check the type of
        # annotated to avoid issues with module imports where someone did from annotate import *
        # into the module causing an annotated symbol to be defined as a decorator

        # If we are in a dict context then strings point to lazily loaded modules so include them too.
        if isinstance(container, dict) and isinstance(obj, str):
            built_context[name] = obj
        elif hasattr(obj, 'metadata') and isinstance(getattr(obj, 'metadata'), AnnotatedMetadata):
            built_context[name] = obj

    return built_context

def find_all ( container ) : if isinstance ( container , dict ) : names = container . keys ( ) else : names = dir ( container ) built_context = BasicContext ( ) for name in names : if name . startswith ( '_' ) : continue if isinstance ( container , dict ) : obj = container [ name ] else : obj = getattr ( container , name ) if isinstance ( container , dict ) and isinstance ( obj , str ) : built_context [ name ] = obj elif hasattr ( obj , 'metadata' ) and isinstance ( getattr ( obj , 'metadata' ) , AnnotatedMetadata ) : built_context [ name ] = obj return built_context

Find all annotated function inside of a container .


def context_from_module(module):
    """
    Given a module, create a context from all of the top level annotated
    symbols in that module.
    """

    con = find_all(module)

    if hasattr(module, "__doc__"):
        setattr(con, "__doc__", module.__doc__)

    name = module.__name__
    if hasattr(module, "_name_"):
        name = module._name_  # pylint: disable=W0212

    con = annotated(con, name)
    setattr(con, 'context', True)

    return name, con

def context_from_module ( module ) : con = find_all ( module ) if hasattr ( module , "__doc__" ) : setattr ( con , "__doc__" , module . __doc__ ) name = module . __name__ if hasattr ( module , "_name_" ) : name = module . _name_ con = annotated ( con , name ) setattr ( con , 'context' , True ) return name , con

Given a module create a context from all of the top level annotated symbols in that module .


def get_help(func):
    """Return usage information about a context or function.

    For contexts, just return the context name and its docstring
    For functions, return the function signature as well as its
    argument types.

    Args:
        func (callable): An annotated callable function

    Returns:
        str: The formatted help text
    """

    help_text = ""
    if isinstance(func, dict):
        name = context_name(func)

        help_text = "\n" + name + "\n\n"
        doc = inspect.getdoc(func)
        if doc is not None:
            doc = inspect.cleandoc(doc)
            help_text += doc + '\n'

        return help_text

    sig = func.metadata.signature()
    doc = inspect.getdoc(func)
    if doc is not None:
        doc = inspect.cleandoc(doc)

    help_text += "\n" + sig + "\n\n"
    if doc is not None:
        help_text += doc + '\n'

    if inspect.isclass(func):
        func = func.__init__

    # If we derived the parameter annotations from a docstring,
    # don't insert a custom arguments section since it already
    # exists.
    if func.metadata.load_from_doc:
        return help_text

    help_text += "\nArguments:\n"
    for key, info in func.metadata.annotated_params.items():
        type_name = info.type_name
        desc = ""
        if info.desc is not None:
            desc = info.desc

        help_text += "  - %s (%s): %s\n" % (key, type_name, desc)

    return help_text

def
get_help
(
func
)
:
help_text
=
""
if
isinstance
(
func
,
dict
)
:
name
=
context_name
(
func
)
help_text
=
"\n"
+
name
+
"\n\n"
doc
=
inspect
.
getdoc
(
func
)
if
doc
is
not
:
doc
=
inspect
.
cleandoc
(
doc
)
help_text
+=
doc
+
'\n'
return
help_text
sig
=
func
.
metadata
.
signature
(
)
doc
=
inspect
.
getdoc
(
func
)
if
doc
is
not
:
doc
=
inspect
.
cleandoc
(
doc
)
help_text
+=
"\n"
+
sig
+
"\n\n"
if
doc
is
not
:
help_text
+=
doc
+
'\n'
if
inspect
.
isclass
(
func
)
:
func
=
func
.
__init__
if
func
.
metadata
.
load_from_doc
:
return
help_text
help_text
+=
"\nArguments:\n"
for
key
,
info
in
func
.
metadata
.
annotated_params
.
items
(
)
:
type_name
=
info
.
type_name
desc
=
""
if
info
.
desc
is
not
:
desc
=
info
.
desc
help_text
+=
"  - %s (%s): %s\n"
%
(
key
,
type_name
,
desc
)
return
help_text

Return usage information about a context or function .