From b23e4a8d6a486917612c29f25e57b7d4d6a3a139 Mon Sep 17 00:00:00 2001 From: dece Date: Tue, 16 Feb 2021 19:10:11 +0100 Subject: [PATCH] screen: handle input request (code 10) --- bebop/navigation.py | 14 +++++++++++--- bebop/screen.py | 22 +++++++++++++++++----- bebop/tests/test_navigation.py | 8 +++++++- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/bebop/navigation.py b/bebop/navigation.py index 03d482d..3d7911c 100644 --- a/bebop/navigation.py +++ b/bebop/navigation.py @@ -1,7 +1,7 @@ import urllib.parse -def parse_url(url, absolute=False): +def parse_url(url: str, absolute: bool =False): """Return URL parts from this URL. This uses urllib.parse.urlparse to not reinvent the wheel, with a few @@ -25,14 +25,22 @@ def parse_url(url, absolute=False): return parts -def sanitize_url(url): +def sanitize_url(url: str): """Parse and unparse an URL to ensure it has been properly formatted.""" return urllib.parse.urlunparse(parse_url(url)) -def join_url(base_url, url): +def join_url(base_url: str, url: str): """Join a base URL with a relative url.""" if base_url.startswith("gemini://"): base_url = base_url[7:] parts = parse_url(urllib.parse.urljoin(base_url, url)) return urllib.parse.urlunparse(parts) + + +def set_parameter(url: str, user_input: str): + """Return a new URL with the user input escaped (RFC 3986) appended.""" + quoted_input = urllib.parse.quote(user_input) + if "?" in url: + url = url.rsplit("?", maxsplit=1)[0] + return url + "?" + quoted_input diff --git a/bebop/screen.py b/bebop/screen.py index d56be53..345d8fd 100644 --- a/bebop/screen.py +++ b/bebop/screen.py @@ -7,7 +7,7 @@ from bebop.colors import ColorPair, init_colors from bebop.command_line import (CommandLine, EscapeCommandInterrupt, TerminateCommandInterrupt) from bebop.mouse import ButtonState -from bebop.navigation import join_url, parse_url, sanitize_url +from bebop.navigation import join_url, parse_url, sanitize_url, set_parameter from bebop.page import Page from bebop.protocol import Request, Response @@ -71,7 +71,7 @@ class Screen: if char == ord("q"): running = False elif char == ord(":"): - command = self.input_common_command() + command = self.take_user_input() self.set_status(f"Command: {command}") elif char == ord("s"): self.set_status(f"h {self.h} w {self.w}") @@ -203,6 +203,8 @@ class Screen: elif response.generic_code in (40, 50): error = f"Server error: {response.meta or Response.code.name}." self.set_status_error(error) + elif response.generic_code == 10: + self.handle_input_request(url, response) def load_page(self, gemtext: bytes): """Load Gemtext data as the current page.""" @@ -215,9 +217,9 @@ class Screen: else: self.refresh_page() - def input_common_command(self): - """Focus command line to type a regular command. Currently useless.""" - return self.command_line.focus(":", self.validate_common_char) + def take_user_input(self, type_char: str =":"): + """Focus command line to let the user type something""" + return self.command_line.focus(type_char, self.validate_common_char) def validate_common_char(self, ch: int): """Generic input validator, handles a few more cases than default. @@ -321,6 +323,16 @@ class Screen: return self.open_url(links[link_id]) + def handle_input_request(self, from_url: str, response: Response): + if response.meta: + self.set_status(f"Input needed: {response.meta}") + else: + self.set_status("Input needed:") + user_input = self.take_user_input("?") + if user_input: + url = set_parameter(from_url, user_input) + self.open_gemini_url(url) + def handle_mouse(self, mouse_id: int, x: int, y: int, z: int, bstate: int): """Handle mouse events. diff --git a/bebop/tests/test_navigation.py b/bebop/tests/test_navigation.py index 9eb24de..e32207a 100644 --- a/bebop/tests/test_navigation.py +++ b/bebop/tests/test_navigation.py @@ -1,6 +1,6 @@ import unittest -from ..navigation import join_url, parse_url +from ..navigation import join_url, parse_url, set_parameter class TestNavigation(unittest.TestCase): @@ -28,3 +28,9 @@ class TestNavigation(unittest.TestCase): self.assertEqual(url, "gemini://dece.space/dir1/other-file.gmi") url = join_url("gemini://dece.space/dir1/file.gmi", "../top-level.gmi") self.assertEqual(url, "gemini://dece.space/top-level.gmi") + + def test_set_parameter(self): + url = set_parameter("gemini://gus.guru/search", "my search") + self.assertEqual(url, "gemini://gus.guru/search?my%20search") + url = set_parameter("gemini://gus.guru/search?old%20search", "new") + self.assertEqual(url, "gemini://gus.guru/search?new")