From 323c088683036bfe4e8bf7dfe21cd60f9c792713 Mon Sep 17 00:00:00 2001 From: Lionel Dricot Date: Fri, 11 Aug 2023 01:28:58 +0200 Subject: [PATCH] opnk is now basically working --- ansirenderer.py | 80 +------------------- offpunk.py | 85 ++++------------------ opnk.py | 189 +++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 196 insertions(+), 158 deletions(-) diff --git a/ansirenderer.py b/ansirenderer.py index a1e2f2c..86b57fb 100755 --- a/ansirenderer.py +++ b/ansirenderer.py @@ -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("(I’m 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 diff --git a/offpunk.py b/offpunk.py index ddf8a65..8a2ab70 100755 --- a/offpunk.py +++ b/offpunk.py @@ -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 \"Can’t 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): diff --git a/opnk.py b/opnk.py index e201cb5..15206aa 100644 --- a/opnk.py +++ b/opnk.py @@ -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("(I’m 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 \"Can’t 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 don’t 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 don’t try to open in the terminal, #we immediately fallback to xdg-open. #netcache currently provide the path if it’s 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 can’t 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__)