opnk is now basically working

This commit is contained in:
Lionel Dricot 2023-08-11 01:28:58 +02:00
parent 654f03c53c
commit 323c088683
3 changed files with 196 additions and 158 deletions

View File

@ -2,7 +2,6 @@
import os
import sys
import shutil
import tempfile
import subprocess
import textwrap
import time
@ -38,60 +37,6 @@ except ModuleNotFoundError:
_DO_FEED = False
less_version = 0
if not shutil.which("less"):
print("Please install the pager \"less\" to run Offpunk.")
print("If you wish to use another pager, send me an email!")
print("(Im really curious to hear about people not having \"less\" on their system.)")
sys.exit()
output = run("less --version")
# We get less Version (which is the only integer on the first line)
words = output.split("\n")[0].split()
less_version = 0
for w in words:
if w.isdigit():
less_version = int(w)
# restoring position only works for version of less > 572
if less_version >= 572:
_LESS_RESTORE_POSITION = True
else:
_LESS_RESTORE_POSITION = False
#_DEFAULT_LESS = "less -EXFRfM -PMurl\ lines\ \%lt-\%lb/\%L\ \%Pb\%$ %s"
# -E : quit when reaching end of file (to behave like "cat")
# -F : quit if content fits the screen (behave like "cat")
# -X : does not clear the screen
# -R : interpret ANSI colors correctly
# -f : suppress warning for some contents
# -M : long prompt (to have info about where you are in the file)
# -W : hilite the new first line after a page skip (space)
# -i : ignore case in search
# -S : do not wrap long lines. Wrapping is done by offpunk, longlines
# are there on purpose (surch in asciiart)
#--incsearch : incremental search starting rev581
if less_version >= 581:
less_base = "less --incsearch --save-marks -~ -XRfMWiS"
elif less_version >= 572:
less_base = "less --save-marks -XRfMWiS"
else:
less_base = "less -XRfMWiS"
_DEFAULT_LESS = less_base + " \"+''\" %s"
_DEFAULT_CAT = less_base + " -EF %s"
def less_cmd(file, histfile=None,cat=False,grep=None):
if histfile:
env = {"LESSHISTFILE": histfile}
else:
env = {}
if cat:
cmd_str = _DEFAULT_CAT
elif grep:
grep_cmd = _GREP
#case insensitive for lowercase search
if grep.islower():
grep_cmd += " -i"
cmd_str = _DEFAULT_CAT + "|" + grep_cmd + " %s"%grep
else:
cmd_str = _DEFAULT_LESS
run(cmd_str, parameter=file, direct_output=True, env=env)
try:
from PIL import Image
@ -211,7 +156,6 @@ class AbstractRenderer():
self.title = None
self.validity = True
self.temp_files = {}
self.less_histfile = {}
self.center = center
self.last_mode = "readable"
@ -592,31 +536,9 @@ class AbstractRenderer():
def display(self,mode=None,window_title="",window_info=None,grep=None):
if mode: self.last_mode = mode
else: mode = self.last_mode
wtitle = self._window_title(window_title,info=window_info)
body = wtitle + "\n" + self.get_body(mode=mode)
if not body:
return False
# We actually put the body in a tmpfile before giving it to less
if mode not in self.temp_files:
tmpf = tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False)
self.temp_files[mode] = tmpf.name
tmpf.write(body)
tmpf.close()
if mode not in self.less_histfile:
firsttime = True
tmpf = tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False)
self.less_histfile[mode] = tmpf.name
else:
firsttime = False
less_cmd(self.temp_files[mode], histfile=self.less_histfile[mode],cat=firsttime,grep=grep)
#TODO to remove
return True
def get_temp_file(self,mode=None):
if not mode: mode = self.last_mode
if mode in self.temp_files:
return self.temp_files[mode]
else:
return None
# An instance of AbstractRenderer should have a self.render(body,width,mode) method.
# 3 modes are used :readable (by default), full and links_only (the fastest, when

View File

@ -38,8 +38,8 @@ import uuid
import webbrowser
import base64
import subprocess
import ansirenderer
import netcache
import opnk
from offutils import run,term_width
try:
import setproctitle
@ -241,10 +241,7 @@ class GeminiClient(cmd.Cmd):
# type sensitivie information.
os.umask(0o077)
# This dictionary contains an url -> ansirenderer mapping. This allows
# to reuse a renderer when visiting several times the same URL during
# the same session
self.rendererdic = {}
self.opencache = opnk.opencache()
self.no_cert_prompt = "\001\x1b[38;5;76m\002" + "ON" + "\001\x1b[38;5;255m\002" + "> " + "\001\x1b[0m\002"
self.cert_prompt = "\001\x1b[38;5;202m\002" + "ON" + "\001\x1b[38;5;255m\002"
self.offline_prompt = "\001\x1b[38;5;76m\002" + "OFF" + "\001\x1b[38;5;255m\002" + "> " + "\001\x1b[0m\001"
@ -357,27 +354,8 @@ class GeminiClient(cmd.Cmd):
def get_renderer(self,url=None):
# If launched without argument, we return the renderer for the current URL
mode = None
if not url: url = self.current_url
findmode = url.split("##offpunk_mode=")
if len(findmode) > 1:
url = findmode[0]
if findmode[1] in ["full"] or findmode[1].isnumeric():
mode = findmode[1]
# reuse existing renderer if any
if url in self.rendererdic.keys():
renderer = self.rendererdic[url]
else:
cache_path = netcache.get_cache_path(url)
if cache_path:
renderer = ansirenderer.renderer_from_file(cache_path,url)
self.rendererdic[url] = renderer
else:
print("WARNING: no cache for requested renderer for %s" %url)
return None
if mode:
renderer.set_mode(mode)
return renderer
return self.opencache.get_renderer(url)
def _go_to_url(self, url, update_hist=True, check_cache=True, handle=True,\
name=None, mode=None,limit_size=False):
@ -465,6 +443,7 @@ class GeminiClient(cmd.Cmd):
handle=False,limit_size=True)
is_rendered = False
if display and netcache.is_cache_valid(url):
#TODO : move title in ansirenderer
title = renderer.get_url_title()
nbr = len(renderer.get_links(mode=mode))
if renderer.is_local():
@ -474,8 +453,9 @@ class GeminiClient(cmd.Cmd):
str_last = "last accessed on %s"\
%time.ctime(netcache.cache_last_modified(url))
title += " (%s links)"%nbr
is_rendered = renderer.display(mode=mode,\
window_title=title,window_info=str_last)
#is_rendered = renderer.display(mode=mode,\
# window_title=title,window_info=str_last)
is_rendered = self.opencache.opnk(url,mode=mode)
if display and is_rendered:
self.page_index = 0
# Update state (external files are not added to history)
@ -484,38 +464,6 @@ class GeminiClient(cmd.Cmd):
self.current_url = url
if update_hist and not self.sync_only:
self._update_history(url)
elif display and not is_rendered :
cmd_str = self._get_handler_cmd(ansirenderer.get_mime(url))
try:
run(cmd_str, netcache.get_cache_path(url), direct_output=True)
except FileNotFoundError:
print("Handler program %s not found!" % shlex.split(cmd_str)[0])
print("You can use the ! command to specify another handler program or pipeline.")
def _get_handler_cmd(self, mimetype):
# Now look for a handler for this mimetype
# Consider exact matches before wildcard matches
exact_matches = []
wildcard_matches = []
for handled_mime, cmd_str in _MIME_HANDLERS.items():
if "*" in handled_mime:
wildcard_matches.append((handled_mime, cmd_str))
else:
exact_matches.append((handled_mime, cmd_str))
for handled_mime, cmd_str in exact_matches + wildcard_matches:
if fnmatch.fnmatch(mimetype, handled_mime):
break
else:
# Use "xdg-open" as a last resort.
if _HAS_XDGOPEN:
cmd_str = "xdg-open %s"
else:
cmd_str = "echo \"Cant find how to open \"%s"
print("Please install xdg-open (usually from xdg-util package)")
return cmd_str
@needs_gi
def _show_lookup(self, offset=0, end=None, show_url=False):
@ -716,19 +664,19 @@ class GeminiClient(cmd.Cmd):
"""View or set handler commands for different MIME types."""
if not line.strip():
# Show all current handlers
for mime in sorted(_MIME_HANDLERS.keys()):
print("%s %s" % (mime, _MIME_HANDLERS[mime]))
h = self.opencache.get_handlers()
for mime in sorted(h.keys()):
print("%s %s" % (mime, h[mime]))
elif len(line.split()) == 1:
mime = line.strip()
if mime in _MIME_HANDLERS:
print("%s %s" % (mime, _MIME_HANDLERS[mime]))
h = self.opencache.get_handlers(mime=mime)
if h:
print("%s %s" % (mime, h))
else:
print("No handler set for MIME type %s" % mime)
else:
mime, handler = line.split(" ", 1)
_MIME_HANDLERS[mime] = handler
if "%s" not in handler:
print("Are you sure you don't want to pass the filename to the handler?")
self.opencache.set_handler(mime,handler)
def do_abbrevs(self, *args):
"""Print all Offpunk command abbreviations."""
@ -986,8 +934,6 @@ Marks are temporary until shutdown (not saved to disk)."""
@needs_gi
def do_info(self,line):
"""Display information about current page."""
print("current url: %s" %self.current_url)
print("current renderer: %s" %self.rendererdic)
renderer = self.get_renderer()
url = self.current_url
out = renderer.get_page_title() + "\n\n"
@ -1176,8 +1122,7 @@ see "handler" command to set your handler."""
if args[0] == "url":
run("xdg-open %s", parameter=self.current_url, direct_output=True)
else:
cmd_str = self._get_handler_cmd(ansirenderer.get_mime(self.current_url))
run(cmd_str, parameter=netcache.get_cache_path(self.current_url), direct_output=True)
self.opencache.opnk(self.current_url,terminal=False)
@needs_gi
def do_shell(self, line):

189
opnk.py
View File

@ -3,28 +3,199 @@
#It will open any file or URL and display it nicely in less.
#If not possible, it will fallback to xdg-open
#URL are retrieved through netcache
import os
import sys
import tempfile
import argparse
import netcache
import ansirenderer
import offutils
import shutil
from offutils import run,term_width
less_version = 0
if not shutil.which("less"):
print("Please install the pager \"less\" to run Offpunk.")
print("If you wish to use another pager, send me an email!")
print("(Im really curious to hear about people not having \"less\" on their system.)")
sys.exit()
output = run("less --version")
# We get less Version (which is the only integer on the first line)
words = output.split("\n")[0].split()
less_version = 0
for w in words:
if w.isdigit():
less_version = int(w)
# restoring position only works for version of less > 572
if less_version >= 572:
_LESS_RESTORE_POSITION = True
else:
_LESS_RESTORE_POSITION = False
#_DEFAULT_LESS = "less -EXFRfM -PMurl\ lines\ \%lt-\%lb/\%L\ \%Pb\%$ %s"
# -E : quit when reaching end of file (to behave like "cat")
# -F : quit if content fits the screen (behave like "cat")
# -X : does not clear the screen
# -R : interpret ANSI colors correctly
# -f : suppress warning for some contents
# -M : long prompt (to have info about where you are in the file)
# -W : hilite the new first line after a page skip (space)
# -i : ignore case in search
# -S : do not wrap long lines. Wrapping is done by offpunk, longlines
# are there on purpose (surch in asciiart)
#--incsearch : incremental search starting rev581
if less_version >= 581:
less_base = "less --incsearch --save-marks -~ -XRfMWiS"
elif less_version >= 572:
less_base = "less --save-marks -XRfMWiS"
else:
less_base = "less -XRfMWiS"
_DEFAULT_LESS = less_base + " \"+''\" %s"
_DEFAULT_CAT = less_base + " -EF %s"
def less_cmd(file, histfile=None,cat=False,grep=None):
if histfile:
env = {"LESSHISTFILE": histfile}
else:
env = {}
if cat:
cmd_str = _DEFAULT_CAT
elif grep:
grep_cmd = _GREP
#case insensitive for lowercase search
if grep.islower():
grep_cmd += " -i"
cmd_str = _DEFAULT_CAT + "|" + grep_cmd + " %s"%grep
else:
cmd_str = _DEFAULT_LESS
run(cmd_str, parameter=file, direct_output=True, env=env)
class opencache():
def __init__(self):
self.temp_files = {}
# This dictionary contains an url -> ansirenderer mapping. This allows
# to reuse a renderer when visiting several times the same URL during
# the same session
self.rendererdic = {}
self.less_histfile = {}
self.mime_handlers = {}
def opnk(inpath,terminal=True):
def _get_handler_cmd(self, mimetype):
# Now look for a handler for this mimetype
# Consider exact matches before wildcard matches
exact_matches = []
wildcard_matches = []
for handled_mime, cmd_str in self.mime_handlers.items():
if "*" in handled_mime:
wildcard_matches.append((handled_mime, cmd_str))
else:
exact_matches.append((handled_mime, cmd_str))
for handled_mime, cmd_str in exact_matches + wildcard_matches:
if fnmatch.fnmatch(mimetype, handled_mime):
break
else:
# Use "xdg-open" as a last resort.
if _HAS_XDGOPEN:
cmd_str = "xdg-open %s"
else:
cmd_str = "echo \"Cant find how to open \"%s"
print("Please install xdg-open (usually from xdg-util package)")
return cmd_str
# Return the handler for a specific mimetype.
# Return the whole dic if no specific mime provided
def get_handlers(self,mime=None):
if mime and mime in self.mime_handlers.keys():
return self.mime_handlers[mime]
elif mime:
return None
else:
return self.mime_handlers
def set_handler(self,mime,handler):
previous = None
if mime in self.mime_handlers.keys():
previous = self.mime_handlers[mime]
self.mime_handlers[mime] = handler
if "%s" not in handler:
print("WARNING: this handler has no %%s, no filename will be provided to the command")
if previous:
print("Previous handler was %s"%previous)
def get_renderer(self,inpath,mode=None):
renderer = None
# We remove the ##offpunk_mode= from the URL
# If mode is already set, we dont use the part from the URL
print("inpath : %s" %inpath)
findmode = inpath.split("##offpunk_mode=")
if len(findmode) > 1:
inpath = findmode[0]
if not mode:
if findmode[1] in ["full"] or findmode[1].isnumeric():
mode = findmode[1]
path = netcache.get_cache_path(inpath)
#TODO: check if inpath is URL?
if path:
if inpath not in self.rendererdic.keys():
renderer = ansirenderer.renderer_from_file(path,inpath)
if renderer:
self.rendererdic[inpath] = renderer
else:
renderer = self.rendererdic[inpath]
if renderer and mode:
renderer.set_mode(mode)
return renderer
def grep(self,inpath,searchterm):
print("TODO: implement grep")
#TODO
def opnk(self,inpath,mode=None,terminal=True):
#Return True if inpath opened in Terminal
# False otherwise
#if terminal = False, we dont try to open in the terminal,
#we immediately fallback to xdg-open.
#netcache currently provide the path if its a file.
#may this should be migrated here.
path = netcache.get_cache_path(inpath)
#TODO: migrate here ansirenderer display
1. À partir du path, tenter le ansirenderer
2. Sauver le rendu dans self.temp_files[mode] (donc le mode doit être passé à opnk)
3. Sauver le renderer dans self.rendererdic
3. Donner à less
4. sinon, donner à xdg-open
print("inpath opnk: %s" %inpath)
renderer = self.get_renderer(inpath,mode=mode)
# TODO: put the window title into ansirenderer itself
#wtitle = self._window_title(window_title,info=window_info)
if terminal and renderer:
wtitle = "Test title\n"
body = wtitle + "\n" + renderer.get_body(mode=mode)
# We actually put the body in a tmpfile before giving it to less
if mode not in self.temp_files:
tmpf = tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False)
self.temp_files[mode] = tmpf.name
tmpf.write(body)
tmpf.close()
if mode not in self.less_histfile:
firsttime = True
tmpf = tempfile.NamedTemporaryFile("w", encoding="UTF-8", delete=False)
self.less_histfile[mode] = tmpf.name
else:
firsttime = False
grep=None
less_cmd(self.temp_files[mode], histfile=self.less_histfile[mode],cat=firsttime,grep=grep)
return True
#maybe, we have no renderer. Or we want to skip it.
else:
print("We cant renderer in Ansi, fallback to xdg-open")
cmd_str = self._get_handler_cmd(ansirenderer.get_mime(inpath))
try:
run(cmd_str, netcache.get_cache_path(inpath), direct_output=True)
except FileNotFoundError:
print("Handler program %s not found!" % shlex.split(cmd_str)[0])
print("You can use the ! command to specify another handler program or pipeline.")
return False
def get_temp_file(self,mode=None):
if not mode: mode = self.last_mode
if mode in self.temp_files:
return self.temp_files[mode]
else:
return None
def main():
parser = argparse.ArgumentParser(description=__doc__)