From 4be27f862b24acd6cf8fb7c99ed0644b418f3af9 Mon Sep 17 00:00:00 2001 From: Daniel Schwarz Date: Mon, 27 Nov 2023 20:22:37 -0500 Subject: [PATCH] Added --plaintext option to command line tools Now we default to markdown when displaying status messages; the --plaintext option reverts to previous behavior. This switch is used in test_console.py so existing tests will pass; we can add tests in future without --plaintext to test the markdown rendering. --- .github/workflows/test.yml | 2 +- tests/test_console.py | 8 ++++---- toot/commands.py | 9 +++++---- toot/console.py | 14 ++++++++++++-- toot/output.py | 20 ++++++++++---------- toot/richtext/__init__.py | 24 +++++++----------------- toot/richtext/markdown.py | 20 +++++++++++++------- 7 files changed, 52 insertions(+), 45 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5417a2f0..520ee76e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e ".[test,richtext]" + pip install -e ".[test,richtext,markdown]" - name: Run tests run: | pytest diff --git a/tests/test_console.py b/tests/test_console.py index 1d321df0..eb004d92 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -132,7 +132,7 @@ def test_timeline(mock_get, monkeypatch, capsys): 'media_attachments': [], }]) - console.run_command(app, user, 'timeline', ['--once']) + console.run_command(app, user, 'timeline', ['--once', '--plaintext']) mock_get.assert_called_once_with(app, user, '/api/v1/timelines/home', {'limit': 10}) @@ -175,7 +175,7 @@ def test_timeline_with_re(mock_get, monkeypatch, capsys): 'media_attachments': [], }]) - console.run_command(app, user, 'timeline', ['--once']) + console.run_command(app, user, 'timeline', ['--once', '--plaintext']) mock_get.assert_called_once_with(app, user, '/api/v1/timelines/home', {'limit': 10}) @@ -237,7 +237,7 @@ def test_thread(mock_get, monkeypatch, capsys): }), ] - console.run_command(app, user, 'thread', ['111111111111111111']) + console.run_command(app, user, 'thread', ['--plaintext', '111111111111111111']) calls = [ mock.call(app, user, '/api/v1/statuses/111111111111111111'), @@ -553,7 +553,7 @@ def test_notifications(mock_get, capsys): }, }]) - console.run_command(app, user, 'notifications', []) + console.run_command(app, user, 'notifications', ['--plaintext']) mock_get.assert_called_once_with(app, user, '/api/v1/notifications', {'exclude_types[]': [], 'limit': 20}) diff --git a/toot/commands.py b/toot/commands.py index e16d8f15..3e7ee4d3 100644 --- a/toot/commands.py +++ b/toot/commands.py @@ -58,7 +58,8 @@ def timeline(app, user, args, generator=None): items = reversed(items) statuses = [from_dict(Status, item) for item in items] - print_timeline(statuses) + + print_timeline(statuses, render_mode="plaintext" if args.plaintext else "markdown") if args.once or not sys.stdout.isatty(): break @@ -71,7 +72,7 @@ def timeline(app, user, args, generator=None): def status(app, user, args): status = api.single_status(app, user, args.status_id) status = from_dict(Status, status) - print_status(status) + print_status(status, render_mode="plaintext" if args.plaintext else "markdown") def thread(app, user, args): @@ -87,7 +88,7 @@ def thread(app, user, args): thread.append(item) statuses = [from_dict(Status, s) for s in thread] - print_timeline(statuses) + print_timeline(statuses, render_mode="plaintext" if args.plaintext else "markdown") def post(app, user, args): @@ -572,7 +573,7 @@ def notifications(app, user, args): notifications = reversed(notifications) notifications = [from_dict(Notification, n) for n in notifications] - print_notifications(notifications) + print_notifications(notifications, render_mode="plaintext" if args.plaintext else "markdown") def tui(app, user, args): diff --git a/toot/console.py b/toot/console.py index 9515d68a..86e08d88 100644 --- a/toot/console.py +++ b/toot/console.py @@ -235,7 +235,13 @@ def editor(value): json_arg = (["--json"], { "action": "store_true", "default": False, - "help": "print json instead of plaintext", + "help": "print json instead of standard text", +}) + +plaintext_arg = (["-pt", "--plaintext"], { + "action": "store_true", + "help": "Render status messages in plaintext, not markdown", + "default": False, }) # Arguments for selecting a timeline (see `toot.commands.get_timeline_generator`) @@ -284,6 +290,7 @@ def editor(value): "default": False, "help": "Only show the first toots, do not prompt to continue.", }), + plaintext_arg, ] timeline_args = common_timeline_args + timeline_and_bookmark_args @@ -426,7 +433,8 @@ def editor(value): "action": "store_true", "default": False, "help": "Only print mentions", - }) + }), + plaintext_arg, ], require_auth=True, ), @@ -464,6 +472,7 @@ def editor(value): (["status_id"], { "help": "Show thread for toot.", }), + plaintext_arg, ], require_auth=True, ), @@ -474,6 +483,7 @@ def editor(value): (["status_id"], { "help": "ID of the status to show.", }), + plaintext_arg, ], require_auth=True, ), diff --git a/toot/output.py b/toot/output.py index 9bf7d919..8f0c28ed 100644 --- a/toot/output.py +++ b/toot/output.py @@ -277,7 +277,7 @@ def print_search_results(results): print_out("Nothing found") -def print_status(status: Status, width: int = 80): +def print_status(status: Status, width=80, render_mode=""): status_id = status.id in_reply_to_id = status.in_reply_to_id reblogged_by = status.account if status.reblog else None @@ -299,7 +299,7 @@ def print_status(status: Status, width: int = 80): f"{time}", ) - print_html(status.content, width) + print_html(status.content, width, render_mode=render_mode) if status.media_attachments: print_out("\nMedia:") @@ -320,8 +320,8 @@ def print_status(status: Status, width: int = 80): ) -def print_html(text, width=80): - markdown = "\n".join(html_to_text(text, columns=width, highlight_tags=False)) +def print_html(text, width=80, render_mode=""): + markdown = "\n".join(html_to_text(text, columns=width, render_mode=render_mode, highlight_tags=False)) print_out("") print_out(markdown) @@ -352,10 +352,10 @@ def print_poll(poll: Poll): print_out(poll_footer) -def print_timeline(items: List[Status], width=100): +def print_timeline(items: List[Status], width=100, render_mode=""): print_out("─" * width) for item in items: - print_status(item, width) + print_status(item, width, render_mode=render_mode) print_out("─" * width) @@ -367,7 +367,7 @@ def print_timeline(items: List[Status], width=100): } -def print_notification(notification: Notification, width=100): +def print_notification(notification: Notification, width=100, render_mode=""): account = f"{notification.account.display_name} @{notification.account.acct}" msg = notification_msgs.get(notification.type) if msg is None: @@ -376,10 +376,10 @@ def print_notification(notification: Notification, width=100): print_out("─" * width) print_out(msg.format(account=account)) if notification.status: - print_status(notification.status, width) + print_status(notification.status, width, render_mode=render_mode) -def print_notifications(notifications: List[Notification], width=100): +def print_notifications(notifications: List[Notification], render_mode="", width=100): for notification in notifications: - print_notification(notification) + print_notification(notification, render_mode=render_mode) print_out("─" * width) diff --git a/toot/richtext/__init__.py b/toot/richtext/__init__.py index 9888a5de..71bd8b58 100644 --- a/toot/richtext/__init__.py +++ b/toot/richtext/__init__.py @@ -1,6 +1,5 @@ -from toot.tui.utils import highlight_hashtags -from toot.utils import html_to_paragraphs -from toot.wcstring import wc_wrap +from toot.exceptions import ConsoleError +from toot.richtext.plaintext import html_to_plaintext from typing import List try: @@ -9,17 +8,8 @@ except ImportError: # Fallback to render in plaintext - def html_to_text(html: str, columns=80, highlight_tags=False) -> List: - output = [] - first = True - for paragraph in html_to_paragraphs(html): - if not first: - output.append("") - for line in paragraph: - for subline in wc_wrap(line, columns): - if highlight_tags: - output.append(highlight_hashtags(subline)) - else: - output.append(subline) - first = False - return output + def html_to_text(html: str, columns=80, render_mode: str = "", highlight_tags=False) -> List: + if render_mode == "markdown": + raise ConsoleError("Can't render as markdown because the pypandoc library is not available.") + + return html_to_plaintext(html, columns, highlight_tags) diff --git a/toot/richtext/markdown.py b/toot/richtext/markdown.py index a3ea03c1..a3184bb0 100644 --- a/toot/richtext/markdown.py +++ b/toot/richtext/markdown.py @@ -1,11 +1,17 @@ +from toot.exceptions import ConsoleError from pypandoc import convert_text +from toot.richtext.plaintext import html_to_plaintext from typing import List -def html_to_text(html: str, columns=80, highlight_tags=False) -> List: - return [convert_text( - html, - format="html", - to="gfm-raw_html", - extra_args=["--wrap=auto", f"--columns={columns}"], - )] +def html_to_text(html: str, columns=80, render_mode: str = "", highlight_tags=False) -> List: + if render_mode == "plaintext": + return html_to_plaintext(html, columns, highlight_tags) + elif render_mode == "markdown" or render_mode == "": + return [convert_text( + html, + format="html", + to="gfm-raw_html", + extra_args=["--wrap=auto", f"--columns={columns}"], + )] + raise ConsoleError("Unknown render mode; specify 'plaintext' or 'markdown'")