Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
dece | 471c07122d |
|
@ -29,13 +29,11 @@ handle big files (e.g. gemini://tilde.team/~tomasino/irc/log.txt)
|
||||||
allow encoding overrides (useful for gopher i guess)
|
allow encoding overrides (useful for gopher i guess)
|
||||||
config for web browser, default to webbrowser module
|
config for web browser, default to webbrowser module
|
||||||
use pubkeys instead of the whole DER hash for TOFU
|
use pubkeys instead of the whole DER hash for TOFU
|
||||||
reload after forgetting old cert
|
|
||||||
specify external commands interface (exec, run, pipes)
|
specify external commands interface (exec, run, pipes)
|
||||||
table of contents
|
table of contents
|
||||||
better default editor than vim
|
better default editor than vim
|
||||||
search engine
|
search engine
|
||||||
auto-open some media types
|
auto-open some media types
|
||||||
use about scheme instead of bebop
|
|
||||||
don't store explicit port 1965 in URL prefixes (e.g. for identities)
|
don't store explicit port 1965 in URL prefixes (e.g. for identities)
|
||||||
fix shifted line detection when text goes belong the horizontal limit
|
fix shifted line detection when text goes belong the horizontal limit
|
||||||
auto-rewrite missing "/" in empty URL paths
|
auto-rewrite missing "/" in empty URL paths
|
||||||
|
|
|
@ -19,7 +19,7 @@ from bebop.bookmarks import (
|
||||||
)
|
)
|
||||||
from bebop.colors import A_ITALIC, ColorPair, init_colors
|
from bebop.colors import A_ITALIC, ColorPair, init_colors
|
||||||
from bebop.command_line import CommandLine
|
from bebop.command_line import CommandLine
|
||||||
from bebop.external import open_external_program, substitute_external_command
|
from bebop.external import open_external_program
|
||||||
from bebop.fs import get_capsule_prefs_path, get_identities_list_path
|
from bebop.fs import get_capsule_prefs_path, get_identities_list_path
|
||||||
from bebop.help import get_help
|
from bebop.help import get_help
|
||||||
from bebop.history import History
|
from bebop.history import History
|
||||||
|
@ -269,8 +269,6 @@ class Browser:
|
||||||
self.move_to_search_result(Browser.SEARCH_PREVIOUS)
|
self.move_to_search_result(Browser.SEARCH_PREVIOUS)
|
||||||
elif char == ord("i"):
|
elif char == ord("i"):
|
||||||
self.show_page_info()
|
self.show_page_info()
|
||||||
elif char == ord("!"):
|
|
||||||
self.quick_command("exec")
|
|
||||||
elif curses.ascii.isdigit(char):
|
elif curses.ascii.isdigit(char):
|
||||||
self.handle_digit_input(char)
|
self.handle_digit_input(char)
|
||||||
elif char == curses.KEY_MOUSE:
|
elif char == curses.KEY_MOUSE:
|
||||||
|
@ -387,8 +385,6 @@ class Browser:
|
||||||
else:
|
else:
|
||||||
if command in ("o", "open"):
|
if command in ("o", "open"):
|
||||||
self.open_url(words[1])
|
self.open_url(words[1])
|
||||||
elif command == "exec":
|
|
||||||
self.exec_external_command(" ".join(words[1:]))
|
|
||||||
elif command == "forget-certificate":
|
elif command == "forget-certificate":
|
||||||
from bebop.browser.gemini import forget_certificate
|
from bebop.browser.gemini import forget_certificate
|
||||||
forget_certificate(self, words[1])
|
forget_certificate(self, words[1])
|
||||||
|
@ -793,7 +789,7 @@ class Browser:
|
||||||
encoding = page.encoding or "unk. encoding"
|
encoding = page.encoding or "unk. encoding"
|
||||||
size = f"{len(page.source)} chars"
|
size = f"{len(page.source)} chars"
|
||||||
lines = f"{len(page.metalines)} lines"
|
lines = f"{len(page.metalines)} lines"
|
||||||
info = f"{mime}; {encoding}; {size}; {lines}"
|
info = f"{mime} {encoding} {size} {lines}"
|
||||||
self.set_status(info)
|
self.set_status(info)
|
||||||
|
|
||||||
def set_render_mode(self, mode):
|
def set_render_mode(self, mode):
|
||||||
|
@ -894,16 +890,3 @@ class Browser:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
logging.info(f"Loaded plugin {plugin_name}.")
|
logging.info(f"Loaded plugin {plugin_name}.")
|
||||||
|
|
||||||
def exec_external_command(self, command):
|
|
||||||
if not self.current_page:
|
|
||||||
return
|
|
||||||
command = substitute_external_command(
|
|
||||||
command, self.current_url, self.current_page)
|
|
||||||
self.set_status(f"Launching `{command}`…")
|
|
||||||
success = open_external_program(command)
|
|
||||||
if success:
|
|
||||||
self.reset_status()
|
|
||||||
else:
|
|
||||||
self.set_status_error("Command failed.")
|
|
||||||
self.refresh_windows()
|
|
||||||
|
|
|
@ -172,7 +172,7 @@ def _handle_response(
|
||||||
redirects=redirects + 1
|
redirects=redirects + 1
|
||||||
)
|
)
|
||||||
elif response.generic_code in (40, 50):
|
elif response.generic_code in (40, 50):
|
||||||
error = f"Server error: {response.meta or response.code.name}"
|
error = f"Server error: {response.meta or Response.code.name}"
|
||||||
browser.set_status_error(error)
|
browser.set_status_error(error)
|
||||||
elif response.generic_code == 10:
|
elif response.generic_code == 10:
|
||||||
return _handle_input_request(browser, url, response.meta)
|
return _handle_input_request(browser, url, response.meta)
|
||||||
|
|
|
@ -2,24 +2,7 @@
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
import logging
|
import logging
|
||||||
import re
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
|
||||||
|
|
||||||
from bebop.page import Page
|
|
||||||
|
|
||||||
|
|
||||||
def _pre_exec():
|
|
||||||
curses.nocbreak()
|
|
||||||
curses.echo()
|
|
||||||
curses.curs_set(1)
|
|
||||||
|
|
||||||
|
|
||||||
def _post_exec():
|
|
||||||
curses.mousemask(curses.ALL_MOUSE_EVENTS)
|
|
||||||
curses.curs_set(0)
|
|
||||||
curses.noecho()
|
|
||||||
curses.cbreak()
|
|
||||||
|
|
||||||
|
|
||||||
def open_external_program(command):
|
def open_external_program(command):
|
||||||
|
@ -31,48 +14,17 @@ def open_external_program(command):
|
||||||
Returns:
|
Returns:
|
||||||
True if no exception occured.
|
True if no exception occured.
|
||||||
"""
|
"""
|
||||||
_pre_exec()
|
curses.nocbreak()
|
||||||
|
curses.echo()
|
||||||
|
curses.curs_set(1)
|
||||||
result = True
|
result = True
|
||||||
try:
|
try:
|
||||||
subprocess.run(command)
|
subprocess.run(command)
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
logging.error(f"Failed to run '{command}': {exc}")
|
logging.error(f"Failed to run '{command}': {exc}")
|
||||||
result = False
|
result = False
|
||||||
_post_exec()
|
curses.mousemask(curses.ALL_MOUSE_EVENTS)
|
||||||
|
curses.curs_set(0)
|
||||||
|
curses.noecho()
|
||||||
|
curses.cbreak()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
SUB_URL_RE = re.compile(r"(?<!\$)\$u")
|
|
||||||
SUB_SRC_RE = re.compile(r"(?<!\$)\$s")
|
|
||||||
SUB_LINK_RE = re.compile(r"(?<!\$)\$(\d+)")
|
|
||||||
SUB_LITERAL_RE = re.compile(r"\$\$")
|
|
||||||
|
|
||||||
|
|
||||||
def substitute_external_command(command: str, url: str, page: Page):
|
|
||||||
"""Substitute "$" parts of the command with corresponding values.
|
|
||||||
|
|
||||||
Valid substitutions are:
|
|
||||||
- $u = current url
|
|
||||||
- $n (with n any positive number) = link url
|
|
||||||
- $s = current page source temp file
|
|
||||||
- $$ = $
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The command with all the template parts replaced with the corresponding
|
|
||||||
strings.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError if a substitution is wrong, e.g. a link ID which does not exist.
|
|
||||||
"""
|
|
||||||
# URL substitution.
|
|
||||||
command = SUB_URL_RE.sub(url, command)
|
|
||||||
# Source file substitution.
|
|
||||||
if SUB_SRC_RE.search(command):
|
|
||||||
with tempfile.NamedTemporaryFile("wt", delete=False) as source_file:
|
|
||||||
source_file.write(page.source)
|
|
||||||
command = SUB_SRC_RE.sub(source_file.name, command)
|
|
||||||
# Link ID substitution.
|
|
||||||
command = SUB_LINK_RE.sub(lambda m: page.links[int(m.group(1))], command)
|
|
||||||
# Literal dollar sign.
|
|
||||||
command = SUB_LITERAL_RE.sub("$", command)
|
|
||||||
return command
|
|
||||||
|
|
|
@ -102,7 +102,6 @@ def parse_gemtext(text: str, dumb=False) -> ParsedGemtext:
|
||||||
match = Blockquote.RE.match(line)
|
match = Blockquote.RE.match(line)
|
||||||
if match:
|
if match:
|
||||||
text = match.groups()[0]
|
text = match.groups()[0]
|
||||||
if text or dumb:
|
|
||||||
elements.append(Blockquote(text))
|
elements.append(Blockquote(text))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -153,6 +153,11 @@ class Request:
|
||||||
self.state = Request.STATE_CONNECTION_FAILED
|
self.state = Request.STATE_CONNECTION_FAILED
|
||||||
self.error = exc.strerror
|
self.error = exc.strerror
|
||||||
return False
|
return False
|
||||||
|
except ValueError as exc:
|
||||||
|
self.state = Request.STATE_INVALID_URL
|
||||||
|
self.error = "Some connection parameter is wrong, check again."
|
||||||
|
logging.error(f"ValueError during connection creation: {exc}")
|
||||||
|
return False
|
||||||
|
|
||||||
# Setup TLS.
|
# Setup TLS.
|
||||||
context = Request.get_ssl_context()
|
context = Request.get_ssl_context()
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
from bebop.metalines import RenderOptions
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from ..external import substitute_external_command
|
|
||||||
from ..page import Page
|
|
||||||
|
|
||||||
|
|
||||||
URL = "gemini://example.com"
|
|
||||||
GEMTEXT = """\
|
|
||||||
# Test page
|
|
||||||
|
|
||||||
Blablabla
|
|
||||||
|
|
||||||
=> gemini://example.com/index.gmi Link to index
|
|
||||||
=> gemini://example.com/sub/gemlog.gmi Link to gemlog
|
|
||||||
"""
|
|
||||||
PAGE = Page.from_gemtext(GEMTEXT, RenderOptions(80, "fancy", "- "))
|
|
||||||
|
|
||||||
|
|
||||||
class TestExternal(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_substitute_external_command(self):
|
|
||||||
# Replace URLs occurences.
|
|
||||||
command = "gmni $u | grep $u" # Grep for a page's own URL.
|
|
||||||
result = substitute_external_command(command, URL, PAGE)
|
|
||||||
self.assertEqual(result, "gmni {u} | grep {u}".format(u=URL))
|
|
||||||
|
|
||||||
# Replace link ID's with the target URL.
|
|
||||||
command = "gmni $1 && gmni $2" # Get both links
|
|
||||||
result = substitute_external_command(command, URL, PAGE)
|
|
||||||
expected = (
|
|
||||||
"gmni gemini://example.com/index.gmi"
|
|
||||||
" && gmni gemini://example.com/sub/gemlog.gmi"
|
|
||||||
)
|
|
||||||
self.assertEqual(result, expected)
|
|
||||||
|
|
||||||
# Invalid link ID raise a ValueError.
|
|
||||||
command = "gmni $8"
|
|
||||||
with self.assertRaises(Exception):
|
|
||||||
substitute_external_command(command, URL, PAGE)
|
|
||||||
|
|
||||||
# Replace escaped $$ with literal $.
|
|
||||||
command = "grep ^iamaregex$$ | echo dollar $" # Do nothing with last.
|
|
||||||
result = substitute_external_command(command, URL, PAGE)
|
|
||||||
self.assertEqual(result, "grep ^iamaregex$ | echo dollar $")
|
|
Reference in a new issue