retronews

a featureful fork of the luke8086/retronews hn+lobste.rs tui
Log | Files | Refs | README | LICENSE

commit 680c38933aa97f649aed971d9af7f1746eb9e97d
parent 16bae1cf0a0d9732c99868b90e1e090487e790b4
Author: luke8086 <55237178+luke8086@users.noreply.github.com>
Date:   Tue, 19 Jul 2022 16:57:24 +0000

Initial support for displaying parsed body, in addition to raw HTML

Diffstat:
Mretronews.py | 45++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 42 insertions(+), 3 deletions(-)

diff --git a/retronews.py b/retronews.py @@ -8,6 +8,7 @@ # import curses +import html.parser import json import logging import sys @@ -48,7 +49,9 @@ class Message: author: str title: str date: datetime + loaded: bool = False lines: List[str] = field(default_factory=list) + raw_lines: List[str] = field(default_factory=list) children: List["Message"] = field(default_factory=list) index_position: int = 0 index_tree: str = "" @@ -61,6 +64,7 @@ class AppState: messages: List[Message] = field(default_factory=list) selected_message: Optional[Message] = None pager_visible: bool = False + raw_mode: bool = False flash: Optional[str] = None @@ -85,6 +89,19 @@ class HNEntry(TypedDict): url: Optional[str] +class HTMLParser(html.parser.HTMLParser): + text: str = "" + + def handle_data(self, data): + self.text += data + + def handle_endtag(self, tag): + if tag == "br": + self.text += "\n" + elif tag == "p": + self.text += "\n\n" + + def fetch(url: str) -> str: logging.debug(f"Fetching '{url}'...") @@ -95,6 +112,14 @@ def fetch(url: str) -> str: return resp +def parse_html(html: str) -> List[str]: + parser = HTMLParser() + parser.feed(html) + parser.close() + + return wrap(parser.text.strip()) + + def list_get(lst: List[T], index: int, default: Optional[T] = None) -> Optional[T]: return lst[index] if 0 <= index < len(lst) else default @@ -144,6 +169,10 @@ def cmd_close(app: AppState) -> None: app_close_story(app) +def cmd_toggle_raw_mode(app: AppState) -> None: + app.raw_mode = not app.raw_mode + + def cmd_unknown(app: AppState) -> None: app.flash = "Unknown key" @@ -153,6 +182,7 @@ KEY_BINDINGS = { ord("\n"): cmd_open, ord(" "): cmd_open, ord("x"): cmd_close, + ord("r"): cmd_toggle_raw_mode, curses.KEY_UP: cmd_up, curses.KEY_DOWN: cmd_down, curses.KEY_PPAGE: cmd_page_up, @@ -220,8 +250,10 @@ def app_render_pager(app: AppState, top: int, height: int) -> None: if message is None: return + lines = message.raw_lines if app.raw_mode else message.lines + for i in range(height): - line = list_get(message.lines, i) or "" + line = list_get(lines, i) or "" app.screen.insstr(i + top, 0, line[: curses.COLS].ljust(curses.COLS)) @@ -308,20 +340,27 @@ def hn_parse_search_hit(hit: HNSearchHit) -> Message: author=hit["author"], title=hit["title"], date=datetime.fromtimestamp(hit["created_at_i"]), - lines=wrap(hit["story_text"] or hit["url"] or "", 80), + loaded=False, + lines=[], + raw_lines=[], ) def hn_parse_entry(entry: HNEntry, story_id: str = "", parent_title: str = "") -> Message: story_id = story_id or str(entry["id"]) + content = f"<p>{entry['url']}</p>" if entry["url"] else "" + content = f"{content}{entry['text']}" if entry["text"] else content + return Message( msg_id=f"{entry['id']}@hn", story_id=f"{story_id}@hn", author=entry["author"] or "unknown", title=entry["title"] or f"Re: {parent_title}", date=datetime.fromtimestamp(entry["created_at_i"]), - lines=wrap(entry["text"] or entry["url"] or "", 80), + loaded=True, + lines=parse_html(content), + raw_lines=wrap(content), children=[hn_parse_entry(child, story_id, entry["title"] or parent_title) for child in entry["children"]], )