• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pyta-uoft / pyta / 21504033550

30 Jan 2026 04:07AM UTC coverage: 94.18% (+0.006%) from 94.174%
21504033550

Pull #1286

github

web-flow
Merge a371a7f2a into 1fbc5d121
Pull Request #1286: Fix markdown formatting issue

8 of 8 new or added lines in 2 files covered. (100.0%)

2 existing lines in 1 file now uncovered.

3560 of 3780 relevant lines covered (94.18%)

17.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.92
/packages/python-ta/src/python_ta/reporters/html_reporter.py
1
import base64
20✔
2
import os
20✔
3
import socket
20✔
4
import sys
20✔
5

6
from jinja2 import Environment, FileSystemLoader
20✔
7
from markdown_it import MarkdownIt
20✔
8
from pygments import highlight
20✔
9
from pygments.formatters import HtmlFormatter
20✔
10
from pygments.lexers import PythonLexer
20✔
11
from pylint.lint import PyLinter
20✔
12
from pylint.reporters.ureports.nodes import BaseLayout
20✔
13

14
from ..util.servers.one_shot_server import open_html_in_browser
20✔
15
from ..util.servers.persistent_server import PersistentHTMLServer
20✔
16
from .core import PythonTaReporter
20✔
17

18
TEMPLATES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "templates")
20✔
19

20

21
class HTMLReporter(PythonTaReporter):
20✔
22
    """Reporter that displays results in HTML form.
23

24
    By default, automatically opens the report in a web browser.
25
    """
26

27
    name = "pyta-html"
20✔
28

29
    _COLOURING = {
20✔
30
        "black": '<span class="black">',
31
        "black-line": '<span class="black line-num">',
32
        "bold": "<span>",
33
        "code-heading": "<span>",
34
        "style-heading": "<span>",
35
        "code-name": "<span>",
36
        "style-name": "<span>",
37
        "highlight": '<span class="highlight-pyta">',
38
        "grey": '<span class="grey">',
39
        "grey-line": '<span class="grey line-num">',
40
        "gbold": '<span class="gbold">',
41
        "gbold-line": '<span class="gbold line-num">',
42
        "reset": "</span>",
43
    }
44
    _PRE_LINE_NUM_SPACES = 0
20✔
45

46
    no_err_message = "No problems detected, good job!"
20✔
47
    no_snippet = "No code to display for this message."
20✔
48
    code_err_title = "Code Errors or Forbidden Usage (fix: high priority)"
20✔
49
    style_err_title = "Style or Convention Errors (fix: before submission)"
20✔
50
    OUTPUT_FILENAME = "pyta_report.html"
20✔
51
    port = None
20✔
52
    persistent_server = None
20✔
53

54
    def print_messages(self, level="all"):
20✔
55
        """Do nothing to print messages, since all are displayed in a single HTML file."""
56

57
    def display_messages(self, layout: BaseLayout) -> None:
20✔
58
        """Hook for displaying the messages of the reporter
59

60
        This will be called whenever the underlying messages
61
        needs to be displayed. For some reporters, it probably
62
        doesn't make sense to display messages as soon as they
63
        are available, so some mechanism of storing them could be used.
64
        This method can be implemented to display them after they've
65
        been aggregated.
66
        """
67
        md = MarkdownIt()
20✔
68
        grouped_messages = {
20✔
69
            path: self.group_messages(msgs) for path, msgs in self.gather_messages().items()
70
        }
71

72
        template_f = self.linter.config.pyta_template_file
20✔
73
        template_f = (
20✔
74
            template_f if template_f != "" else os.path.join(TEMPLATES_DIR, "template.html.jinja")
75
        )
76
        path = os.path.abspath(template_f)
20✔
77
        filename, file_parent_directory = os.path.basename(path), os.path.dirname(path)
20✔
78

79
        template = Environment(loader=FileSystemLoader(file_parent_directory)).get_template(
20✔
80
            filename
81
        )
82
        if not self.port:
20✔
83
            self.port = (
20✔
84
                _find_free_port()
85
                if self.linter.config.server_port == 0
86
                else self.linter.config.server_port
87
            )
88
        if not self.persistent_server:
20✔
89
            self.persistent_server = PersistentHTMLServer(self.port)
20✔
90

91
        # Embed resources so the output html can go anywhere, independent of assets.
92
        with open(os.path.join(TEMPLATES_DIR, "pyta_logo_markdown.png"), "rb+") as image_file:
20✔
93
            # Encode img binary to base64 (+33% size), decode to remove the "b'"
94
            pyta_logo_base64_encoded = base64.b64encode(image_file.read()).decode()
20✔
95
        pyta_logo_data_url = f"data:image/png;base64,{pyta_logo_base64_encoded}"
20✔
96

97
        # Use the same pyta_logo_url for the favicon
98
        favicon_data_url = pyta_logo_data_url
20✔
99

100
        # Render the jinja template
101
        rendered_template = template.render(
20✔
102
            date_time=self._generate_report_date_time(),
103
            port=self.port,
104
            reporter=self,
105
            grouped_messages=grouped_messages,
106
            os=os,
107
            enumerate=enumerate,
108
            md=md,
109
            pyta_logo_data_url=pyta_logo_data_url,
110
            favicon_data_url=favicon_data_url,
111
        )
112

113
        # If a filepath was specified, write to the file
114
        if self.out is not sys.stdout:
20✔
115
            self.writeln(rendered_template)
20✔
116
            self.out.flush()
20✔
117
        else:
118
            rendered_template = rendered_template.encode("utf8")
20✔
119
            if self.linter.config.watch:
20✔
UNCOV
120
                self.persistent_server.start_server_once(rendered_template)
×
121
            else:
122
                open_html_in_browser(rendered_template, self.port)
20✔
123
                print(
20✔
124
                    "[INFO] Your PythonTA report is being opened in your web browser.\n"
125
                    "       If it doesn't open, please add an output argument to python_ta.check_all\n"
126
                    "       as follows:\n\n"
127
                    "         check_all(..., output='pyta_report.html')\n\n"
128
                    "       This will cause PythonTA to save the report to a file, pyta_report.html,\n"
129
                    "       that you can open manually in a web browser.",
130
                    file=sys.stderr,
131
                )
132

133
    @classmethod
20✔
134
    def _colourify(cls, colour_class: str, text: str) -> str:
20✔
135
        """Return a colourized version of text, using colour_class."""
136
        colour = cls._COLOURING[colour_class]
20✔
137
        new_text = text.replace(" ", cls._SPACE)
20✔
138
        if "-line" not in colour_class:
20✔
139
            new_text = highlight(
20✔
140
                new_text,
141
                PythonLexer(),
142
                HtmlFormatter(nowrap=True, lineseparator="", classprefix="pygments-"),
143
            )
144

145
        return colour + new_text + cls._COLOURING["reset"]
20✔
146

147

148
def _find_free_port() -> int:
20✔
149
    """Find and return an available TCP port on localhost."""
150
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
20✔
151
        s.bind(("127.0.0.1", 0))
20✔
152
        return s.getsockname()[1]
20✔
153

154

155
def register(linter: PyLinter):
20✔
UNCOV
156
    linter.register_reporter(HTMLReporter)
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc