#!/usr/bin/env python3 """terminalspeed: check whether a web page reads well in a terminal browser. Usage: terminalspeed.py FILE.html terminalspeed.py https://example.org/ No dependencies. Rules are derived from how w3m, lynx, links, elinks and EWW actually render HTML: they ignore stylesheets, most inline CSS and all JavaScript. """ import re import sys from html.parser import HTMLParser class Page(HTMLParser): def __init__(self): super().__init__(convert_charrefs=True) self.has_title = self.has_charset = self.has_h1 = False self.title_text = "" self.headings = [] self.links_before_main = 0 self.seen_main = False self.imgs_no_alt = self.imgs_empty_alt = self.inline_color = ( self.inline_display_none ) = 0 self.br_run = self.br_max = 0 self.tables_no_th = self.forms_bad = self.inputs_no_name = self.js_handlers = 0 self._in_table = self._table_th = self._table_pres = self._in_title = False def handle_starttag(self, tag, attrs): a = dict(attrs) if tag != "br": self.br_run = 0 if tag == "title": self.has_title = self._in_title = True if tag == "meta" and ( a.get("charset") or a.get("http-equiv", "").lower() == "content-type" ): self.has_charset = True if tag in ("main", "article"): self.seen_main = True if tag == "a" and not self.seen_main: self.links_before_main += 1 if tag == "h1": self.has_h1 = True if tag in ("h1", "h2", "h3", "h4", "h5", "h6"): self.headings.append(int(tag[1])) if tag == "img": if "alt" not in a: self.imgs_no_alt += 1 elif not a["alt"].strip(): self.imgs_empty_alt += 1 style = a.get("style", "") if re.search(r"(^|;)\s*color\s*:", style): self.inline_color += 1 if re.search(r"display\s*:\s*none", style): self.inline_display_none += 1 if tag == "br": self.br_run += 1 self.br_max = max(self.br_max, self.br_run) if tag == "table": self._in_table, self._table_th = True, False self._table_pres = a.get("role") == "presentation" if tag == "th": self._table_th = True if tag == "form" and ( not a.get("action") or a.get("method", "").lower() not in ("get", "post") ): self.forms_bad += 1 if ( tag in ("input", "textarea", "select") and a.get("type") not in ("submit", "button", "reset", "hidden") and not a.get("name") ): self.inputs_no_name += 1 if any(k in ("onclick", "onsubmit", "onchange") for k in a): self.js_handlers += 1 def handle_endtag(self, tag): if tag == "title": self._in_title = False if tag == "table": self._in_table = False if self._table_pres or not self._table_th: self.tables_no_th += 1 def handle_data(self, data): if self._in_title: self.title_text += data def analyze(html): p = Page() p.feed(html) w = [] def add(sev, msg): w.append((sev, msg)) if not p.has_title: add( "ERROR", "No