preferences: basic per-capsule preferences
For now only a per-path render mode is available.
This commit is contained in:
parent
f827ce3ee1
commit
2d493af64b
|
@ -17,9 +17,10 @@ from bebop.bookmarks import (
|
|||
save_bookmark,
|
||||
)
|
||||
from bebop.colors import ColorPair, init_colors
|
||||
from bebop.config import RENDER_MODES
|
||||
from bebop.command_line import CommandLine
|
||||
from bebop.external import open_external_program
|
||||
from bebop.fs import get_identities_list_path
|
||||
from bebop.fs import get_capsule_prefs_path, get_identities_list_path
|
||||
from bebop.help import get_help
|
||||
from bebop.history import History
|
||||
from bebop.identity import load_identities
|
||||
|
@ -36,6 +37,7 @@ from bebop.navigation import (
|
|||
)
|
||||
from bebop.page import Page
|
||||
from bebop.page_pad import PagePad
|
||||
from bebop.preferences import load_capsule_prefs, save_capsule_prefs
|
||||
from bebop.welcome import WELCOME_PAGE
|
||||
|
||||
|
||||
|
@ -153,6 +155,11 @@ class Browser:
|
|||
failed_to_load.append("identities")
|
||||
else:
|
||||
self.identities = identities
|
||||
capsule_prefs = load_capsule_prefs(get_capsule_prefs_path())
|
||||
if capsule_prefs is None:
|
||||
failed_to_load.append("capsule preferences")
|
||||
else:
|
||||
self.capsule_prefs = capsule_prefs
|
||||
|
||||
if failed_to_load:
|
||||
error_msg = (
|
||||
|
@ -324,6 +331,8 @@ class Browser:
|
|||
elif command == "forget-certificate":
|
||||
from bebop.browser.gemini import forget_certificate
|
||||
forget_certificate(self, words[1])
|
||||
elif command == "render":
|
||||
self.set_render_mode(words[1])
|
||||
|
||||
def get_user_text_input(self, status_text, char, prefix="", strip=False):
|
||||
"""Get user input from the command-line."""
|
||||
|
@ -423,7 +432,7 @@ class Browser:
|
|||
self.set_status_error(f"Protocol '{scheme}' not supported.")
|
||||
|
||||
def load_page(self, page: Page):
|
||||
"""Load Gemtext data as the current page."""
|
||||
"""Set this page as the current page and refresh appropriate windows."""
|
||||
old_pad_height = self.page_pad.dim[0]
|
||||
self.page_pad.show_page(page)
|
||||
if self.page_pad.dim[0] < old_pad_height:
|
||||
|
@ -676,3 +685,23 @@ class Browser:
|
|||
size = f"{len(page.source)} chars"
|
||||
info = f"{mime} {encoding} {size}"
|
||||
self.set_status(info)
|
||||
|
||||
def set_render_mode(self, mode):
|
||||
"""Set the render mode for the current path or capsule."""
|
||||
if mode not in RENDER_MODES:
|
||||
valid_modes = ", ".join(RENDER_MODES)
|
||||
self.set_status_error("Valid render modes are: " + valid_modes)
|
||||
return
|
||||
url = self.get_user_text_input(
|
||||
f"Set '{mode}' render mode for which URL (includes children)?",
|
||||
CommandLine.CHAR_TEXT,
|
||||
prefix=self.current_url,
|
||||
strip=True
|
||||
)
|
||||
if not url:
|
||||
return
|
||||
prefs = self.capsule_prefs.get(url, {})
|
||||
prefs["render_mode"] = mode
|
||||
self.capsule_prefs[url] = prefs
|
||||
save_capsule_prefs(self.capsule_prefs, get_capsule_prefs_path())
|
||||
self.reload_page()
|
||||
|
|
|
@ -13,6 +13,7 @@ from bebop.identity import (
|
|||
)
|
||||
from bebop.navigation import set_parameter
|
||||
from bebop.page import Page
|
||||
from bebop.preferences import get_url_render_mode_pref
|
||||
from bebop.protocol import Request, Response
|
||||
from bebop.tofu import trust_fingerprint, untrust_fingerprint, WRONG_FP_ALERT
|
||||
|
||||
|
@ -213,7 +214,13 @@ def _handle_successful_response(browser: Browser, response: Response, url: str):
|
|||
except LookupError:
|
||||
error = f"Unknown encoding {encoding}."
|
||||
else:
|
||||
page = Page.from_gemtext(text, browser.config["text_width"])
|
||||
text_width = browser.config["text_width"]
|
||||
render_mode = get_url_render_mode_pref(
|
||||
browser.capsule_prefs,
|
||||
url,
|
||||
browser.config["render_mode"]
|
||||
)
|
||||
page = Page.from_gemtext(text, text_width, render=render_mode)
|
||||
else:
|
||||
encoding = "utf-8"
|
||||
text = response.content.decode(encoding, errors="replace")
|
||||
|
|
|
@ -15,8 +15,11 @@ DEFAULT_CONFIG = {
|
|||
"external_commands": {},
|
||||
"external_command_default": ["xdg-open"],
|
||||
"home": "bebop:welcome",
|
||||
"render_mode": "fancy",
|
||||
}
|
||||
|
||||
RENDER_MODES = ("fancy", "dumb")
|
||||
|
||||
|
||||
def load_config(config_path):
|
||||
if not os.path.isfile(config_path):
|
||||
|
|
11
bebop/fs.py
11
bebop/fs.py
|
@ -59,6 +59,12 @@ def get_identities_path():
|
|||
return get_user_data_path() / "identities"
|
||||
|
||||
|
||||
@lru_cache(None)
|
||||
def get_capsule_prefs_path():
|
||||
"""Return the directory where identities are stored."""
|
||||
return get_user_data_path() / "capsule_prefs.json"
|
||||
|
||||
|
||||
def ensure_bebop_files_exist() -> Optional[str]:
|
||||
"""Ensure various Bebop's files or directories are present.
|
||||
|
||||
|
@ -78,5 +84,10 @@ def ensure_bebop_files_exist() -> Optional[str]:
|
|||
identities_path = get_identities_path()
|
||||
if not identities_path.exists():
|
||||
identities_path.mkdir(parents=True)
|
||||
# Ensure the capsule preferences file exists.
|
||||
capsule_prefs_path = get_capsule_prefs_path()
|
||||
if not capsule_prefs_path.exists():
|
||||
with open(capsule_prefs_path, "wt") as prefs_file:
|
||||
prefs_file.write("{}")
|
||||
except OSError as exc:
|
||||
return str(exc)
|
||||
|
|
|
@ -65,6 +65,7 @@ Here are the available options:
|
|||
* external_commands (see note 2): commands to open various files.
|
||||
* external_command_default (see note 1): default command to open files.
|
||||
* home (string): home page.
|
||||
* render_mode (string): default render mode to use ("fancy" or "dumb").
|
||||
|
||||
Notes:
|
||||
|
||||
|
|
|
@ -28,10 +28,11 @@ class Page:
|
|||
encoding: str = ""
|
||||
|
||||
@staticmethod
|
||||
def from_gemtext(gemtext: str, wrap_at: int, dumb: bool =False):
|
||||
def from_gemtext(gemtext: str, wrap_at: int, render: str ="fancy"):
|
||||
"""Produce a Page from a Gemtext file or string."""
|
||||
elements, links, title = parse_gemtext(gemtext, dumb=dumb)
|
||||
metalines = generate_metalines(elements, wrap_at, dumb=dumb)
|
||||
dumb_mode = render == "dumb"
|
||||
elements, links, title = parse_gemtext(gemtext, dumb=dumb_mode)
|
||||
metalines = generate_metalines(elements, wrap_at, dumb=dumb_mode)
|
||||
return Page(gemtext, metalines, links, title)
|
||||
|
||||
@staticmethod
|
||||
|
|
59
bebop/preferences.py
Normal file
59
bebop/preferences.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
"""Per-capsule preferences.
|
||||
|
||||
Currently contains only overrides for render modes, per URL path. In the future
|
||||
it may be interesting to move a few things from the config to here, such as the
|
||||
text width.
|
||||
|
||||
This is a map from an URL to dicts. The only used key is render_mode.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def load_capsule_prefs(prefs_path: Path) -> Optional[dict]:
|
||||
"""Return saved capsule preferences or None on error."""
|
||||
prefs = {}
|
||||
try:
|
||||
with open(prefs_path, "rt") as prefs_file:
|
||||
prefs = json.load(prefs_file)
|
||||
except (OSError, ValueError) as exc:
|
||||
logging.error(f"Failed to load capsule prefs '{prefs_path}': {exc}")
|
||||
return None
|
||||
return prefs
|
||||
|
||||
|
||||
def save_capsule_prefs(prefs: dict, prefs_path: Path):
|
||||
"""Save the capsule preferences. Return True on success."""
|
||||
try:
|
||||
with open(prefs_path, "wt") as prefs_file:
|
||||
json.dump(prefs, prefs_file, indent=2)
|
||||
except (OSError, ValueError) as exc:
|
||||
logging.error(f"Failed to save capsule prefs '{prefs_path}': {exc}")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_url_render_mode_pref(prefs: dict, url: str, default: str):
|
||||
"""Return the desired render mode for this URL.
|
||||
|
||||
If the preferences contain the URL or a parent URL, the corresponding render
|
||||
mode is used. If several URLs are prefixes of the `url` argument, the
|
||||
longest one is used to get the matching preference.
|
||||
|
||||
Arguments:
|
||||
- prefs: current capsule preferences.
|
||||
- url: URL about to be rendered.
|
||||
- default: default render mode if no user preferences match.
|
||||
"""
|
||||
prefix_urls = []
|
||||
for key in prefs:
|
||||
if url.startswith(key):
|
||||
prefix_urls.append(key)
|
||||
if not prefix_urls:
|
||||
return default
|
||||
key = max(prefix_urls, key=len)
|
||||
preference = prefs[key]
|
||||
return preference.get("render_mode", default)
|
54
bebop/tests/test_preferences.py
Normal file
54
bebop/tests/test_preferences.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
import unittest
|
||||
|
||||
from ..preferences import get_url_render_mode_pref
|
||||
|
||||
class TestPreferences(unittest.TestCase):
|
||||
|
||||
def test_get_url_render_mode_pref(self):
|
||||
prefs = {}
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com",
|
||||
"default"
|
||||
), "default")
|
||||
|
||||
prefs["gemini://example.com"] = {}
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com",
|
||||
"default"
|
||||
), "default")
|
||||
|
||||
prefs["gemini://example.com"] = {"render_mode": "test"}
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com",
|
||||
"default"
|
||||
), "test")
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com/path",
|
||||
"default"
|
||||
), "test")
|
||||
|
||||
prefs["gemini://example.com/specific/subdir"] = {"render_mode": "test2"}
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com/path",
|
||||
"default"
|
||||
), "test")
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com/specific",
|
||||
"default"
|
||||
), "test")
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com/specific/subdir",
|
||||
"default"
|
||||
), "test2")
|
||||
self.assertEqual(get_url_render_mode_pref(
|
||||
prefs,
|
||||
"gemini://example.com/specific/subdir/subsubdir",
|
||||
"default"
|
||||
), "test2")
|
Reference in a new issue