retronews

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

commit 48bab411643cd258f5e413a71e2cd99aaa6a54c5
parent da466cc17a286d05008d119341c1bf35f1d4ae5b
Author: luke8086 <55237178+luke8086@users.noreply.github.com>
Date:   Wed, 20 Jul 2022 17:38:13 +0000

Improve displaying of messages

- Improve how paragraphs are wrapped
- Display message headers, including upstream URLs

Diffstat:
Mretronews.py | 50+++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 41 insertions(+), 9 deletions(-)

diff --git a/retronews.py b/retronews.py @@ -11,6 +11,7 @@ import curses import html.parser import json import logging +import re import sys import urllib.request from dataclasses import dataclass, field @@ -20,6 +21,7 @@ from typing import Any, Generator, List, Optional, TypedDict, TypeVar, Union T = TypeVar("T") +QUOTE_REX = re.compile(r"^(> ?)+") MENU_HEIGHT = 1 @@ -46,6 +48,7 @@ class Colors: class Message: msg_id: str story_id: str + content_location: str date: datetime author: str title: str @@ -101,6 +104,24 @@ class HTMLParser(html.parser.HTMLParser): self.text += "\n\n" +def parse_html(html: str) -> str: + parser = HTMLParser() + parser.feed(html) + parser.close() + return parser.text.strip() + + +def wrap_paragraph(text: str) -> List[str]: + # Preserve empty lines + if len(text) == 0: + return [""] + + # Preserve quotation symbols in subsequent lines + match = QUOTE_REX.match(text) + quote_symbols = match[0] if match else "" + return wrap(text, subsequent_indent=quote_symbols) + + def fetch(url: str) -> str: logging.debug(f"Fetching '{url}'...") @@ -111,14 +132,6 @@ 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 @@ -197,7 +210,7 @@ def app_select_message(app: AppState, message: Optional[Message]) -> None: app.pager_visible = False return - message.lines = wrap(message.body) if app.raw_mode else parse_html(message.body) + message.lines = wrap(message.body) if app.raw_mode else msg_build_lines(message) def app_load_messages(app: AppState, messages: List[Message], selected_message_id: Optional[str] = None) -> None: @@ -341,10 +354,28 @@ def app_init(screen: "curses._CursesWindow") -> AppState: return app +def msg_build_lines(msg: Message) -> List[str]: + lines = [ + f"Content-Location: {msg.content_location}", + f"Date: {msg.date.strftime('%Y-%m-%d %H:%M')}", + f"From: {msg.author}", + f"Subject: {msg.title}", + "", + ] + + text = parse_html(msg.body or "") + + for p in text.split("\n"): + lines += wrap_paragraph(p) + + return lines + + def hn_parse_search_hit(hit: HNSearchHit) -> Message: return Message( msg_id=f"{hit['objectID']}@hn", story_id=f"{hit['objectID']}@hn", + content_location=f"https://news.ycombinator.com/item?id={hit['objectID']}", date=datetime.fromtimestamp(hit["created_at_i"]), author=hit["author"], title=hit["title"], @@ -360,6 +391,7 @@ def hn_parse_entry(entry: HNEntry, story_id: str = "", parent_title: str = "") - return Message( msg_id=f"{entry['id']}@hn", story_id=f"{story_id}@hn", + content_location=f"https://news.ycombinator.com/item?id={entry['id']}", date=datetime.fromtimestamp(entry["created_at_i"]), author=entry["author"] or "unknown", title=entry["title"] or f"Re: {parent_title}",