richreports module

Library that supports the construction of human-readable, interactive static analysis reports that consist of decorated concrete syntax representations of programs.

class location(iterable=(), /)[source]

Bases: Tuple[int, int]

Data structure for representing a location within a report as a tuple of two integers: the line number and the column on that line.

Because this class is derived from the tuple type, relational operators can be used to determine whether one location appears before or after another.

>>> location((12, 24)) < location((13, 24))
True
>>> location((13, 23)) < location((13, 24))
True
>>> location((13, 24)) < location((13, 24))
False
>>> location((14, 0)) < location((13, 0))
False
>>> location((14, 23)) < location((13, 41))
False
>>> location((12, 24)) <= location((13, 24))
True
>>> location((13, 23)) <= location((13, 24))
True
>>> location((13, 24)) <= location((13, 24))
True
>>> location((14, 0)) <= location((13, 0))
False
>>> location((14, 23)) <= location((13, 41))
False
class report(string: str, line: int = 1, column: int = 0)[source]

Bases: object

Data structure that represents the raw concrete syntax string as a two-dimensional array of two-sided stacks. Each stack holds delimiters (left and right) that may appear before or after that character in the rendered version of the report.

>>> r = report(
...    'def f(x, y):\n' +
...    '    return x + y'
... )

The individual lines in the supplied string can be retrieved via the lines attribute.

>>> list(r.lines)
['def f(x, y):', '    return x + y']

Delimiters can be added around a range within the report by specifying the locations corresponding to the endpoints (inclusive) of the range.

>>> r.enrich((2, 11), (2, 15), '(', ')')
>>> for line in r.render().split('\n'):
...     print(line)
def f(x, y):
    return (x + y)

The optional enrich_intermediate_lines parameter can be used to delimit all complete lines that appear between the supplied endpoints.

>>> r.enrich((1, 0), (2, 15), '<b>', '</b>', enrich_intermediate_lines=True)
>>> for line in r.render().split('\n'):
...     print(line)
<b>def f(x, y):</b>
<b>    return (x + y)</b>

By default, the enrich_intermediate_lines parameter is set to False.

>>> r.enrich((1, 0), (2, 15), '<div>\n', '\n</div>')
>>> for line in r.render().split('\n'):
...     print(line)
<div>
<b>def f(x, y):</b>
<b>    return (x + y)</b>
</div>

The optional skip_whitespace parameter (which is set to False by default) can be used to ensure that left-hand delimiters skip over whitespace (moving to the right and down) and, likewise, that right-hand delimiters skip over whitespace (moving to the left and up).

>>> r = report(
...    '   \n' +
...    '\n' +
...    '   \n' +
...    '    def f(x, y):\n' +
...    '        return x + y        \n' +
...    '                            \n' +
...    '                            \n' +
...    '                            '
... )
>>> r.enrich((2, 0), (5, 20), '<b>', '</b>', skip_whitespace=True)
>>> for line in r.render().split('\n'):
...     print(line)



    <b>def f(x, y):
        return x + y</b>


If the delimited text consists of whitespace and skip_whitespace is True, no delimiters are added.

>>> r.enrich((6, 0), (6, 20), '<i>', '</i>', skip_whitespace=True)
>>> r.enrich((1, 0), (1, 3), '<i>', '</i>', skip_whitespace=True)
>>> r.enrich((2, 0), (3, 3), '<i>', '</i>', skip_whitespace=True)
>>> for line in r.render().split('\n'):
...     print(line)



    <b>def f(x, y):
        return x + y</b>


If enrich_intermediate_lines and skip_whitespace are both True, then individual lines between the first occurrence of a left-hand delimiter and the last occurrence of a right-hand delimiter are delimited as if each line was being enriched individually with skip_whitespace set to True.

>>> r = report(
...    '    \n' +
...    '\n' +
...    '    def f(x, y):\n' +
...    '                            \n' +
...    '\n' +
...    '                            \n' +
...    '        return x + y        \n' +
...    '\n' +
...    '                            \n' +
...    '                            '
... )
>>> r.enrich(
...     (1, 3), (10, 20),
...     '<b>', '</b>',
...     enrich_intermediate_lines=True, skip_whitespace=True
... )
>>> for line in r.render().split('\n'):
...     print(line)


    <b>def f(x, y):</b>



        <b>return x + y</b>


It is possible to specify at what value the line and column numbering schemes begin by supplying the optional line and column arguments to the instance constructor.

>>> r = report('    def f(x, y):\n        return x + y', line=1, column=0)
>>> r.enrich((1, 0), (2, 20), '<b>', '</b>', skip_whitespace=True)
>>> list(r.render().split('\n'))
['    <b>def f(x, y):', '        return x + y</b>']
>>> r = report('    def f(x, y):\n        return x + y', line=0, column=0)
>>> r.enrich((0, 0), (1, 20), '<b>', '</b>', skip_whitespace=True)
>>> list(r.render().split('\n'))
['    <b>def f(x, y):', '        return x + y</b>']
enrich(start: Union[Tuple[int, int], location], end: Union[Tuple[int, int], location], left: str, right: str, enrich_intermediate_lines=False, skip_whitespace=False)[source]

Add a pair of left and right delimiters around a given range within this report instance.

>>> r = report(
...    'def f(x, y):\n' +
...    '    return x + y'
... )
>>> r.enrich((1, 0), (2, 15), '<b>', '</b>', True)
>>> for line in r.render().split('\n'):
...     print(line)
<b>def f(x, y):</b>
<b>    return x + y</b>
render() str[source]

Return the report (incorporating all delimiters) as a string.

>>> r = report(
...    'def f(x, y):\n' +
...    '    return x + y'
... )
>>> r.enrich((1, 0), (2, 16), '<b>', '</b>')
>>> for line in r.render().split('\n'):
...     print(line)
<b>def f(x, y):
    return x + y</b>