Removed definitely ansiwrap. This is a huge achievement.

This commit is contained in:
Lionel Dricot 2022-03-21 22:43:33 +01:00
parent 68fb345ccc
commit af86a08e31
3 changed files with 74 additions and 91 deletions

View File

@ -1,7 +1,8 @@
# Offpunk History
## 1.2 - Unreleased
- "set beta true" allows to test the new HTML renderer
- Completely rewritten the HMTL, Gemtext and Gopher renderer. Tests needed!
- Removed dependancy to ansiwrap. We dont use it anymore (which is important)
- Limit width of --sync output
- Fixed a crash when trying to save a folder

View File

@ -67,7 +67,7 @@ Announces about Offpunk will be made on Ploums Gemlog => gemini://rawtext.cl
Offpunk has no "strict dependencies", i.e. it should run and work without anything
else beyond the Python standard library and the "less" pager. However, it will "opportunistically
import" a few other libraries if they are available to offer an improved
experience or some other features. Python libraries requests, bs4 and readability are required for http/html support. Images are displayed if python-ansiwrap and chafa or timg are presents (python-pil is needed for chafa version before 1.10). When displaying only a picture (not inline), rendering will be pixel perfect in compatible terminals (such as Kitty) if chafa is at least version 1.8 or if timg is used.
experience or some other features. Python libraries requests, bs4 and readability are required for http/html support. Images are displayed if chafa or timg are presents (python-pil is needed for chafa version before 1.10). When displaying only a picture (not inline), rendering will be pixel perfect in compatible terminals (such as Kitty) if chafa is at least version 1.8 or if timg is used.
To avoid using unstable or too recent libraries, the rule of thumb is that a library should be packaged in Debian/Ubuntu. Keep in mind that Offpunk is mainly tested will all libraries installed. If you encounter a crash without one optional dependencies, please report it.
@ -77,8 +77,6 @@ Run command `version` in offpunk to see if you are missing some dependencies.
* [Python-requests](http://python-requests.org) is needed to handle http/https requests natively (apt-get install python3-requests). Without it, http links will be opened in an external browser
* [BeautifulSoup4](https://www.crummy.com/software/BeautifulSoup) and [Readability](https://github.com/buriy/python-readability) are both needed to render HTML. Without them, HTML will not be rendered or be sent to an external parser like Lynx. (apt-get install python3-bs4 python3-readability or pip3 install readability-lxml)
* [Python-feedparser](https://github.com/kurtmckee/feedparser) will allow parsing of RSS/Atom feeds and thus subscriptions to them. (apt-get install python3-feedparser)
* The [ansiwrap library](https://pypi.org/project/ansiwrap/) may result in
neater display of text which makes use of ANSI escape codes to control colour. Ansiwrap is also required to display pictures in HTML pages (together with Chafa and python-pil) (not in Debian?).
* The [cryptography library](https://pypi.org/project/cryptography/) will
provide a better and slightly more secure experience when using the default
TOFU certificate validation mode and is highly recommended (apt-get install python3-cryptography).
@ -86,7 +84,7 @@ Run command `version` in offpunk to see if you are missing some dependencies.
* [Python editor](https://github.com/fmoo/python-editor) is used to edit your lists with "list edit". (apt-get install python3-editor)
* [Xsel](http://www.vergenet.net/~conrad/software/xsel/) allows to `go` to the URL copied in the clipboard without having to paste it (both X and traditional clipboards are supported). Also needed to use the `copy` command. (apt-get install xsel)
* [Chafa](https://hpjansson.org/chafa/) allows to display pictures in your console. Install it and browse to an HTML page with picture to see the magic.
* [Timg](https://github.com/hzeller/timg) is a slower alternative to chafa for inline images. But it has better rendering when displaying only the image. Install both to get the best of both world.
* [Timg](https://github.com/hzeller/timg) is a slower alternative to chafa for inline images. But it has better rendering when displaying only the image. Install both to get the best of both world but if you need to choose one, choose Chafa.
* [Python-pil](http://python-pillow.github.io/) is required to only display the first frame of animated gif with chafa if chafa version is lower than 1.10.
* [Python-setproctitle](https://github.com/dvarrazzo/py-setproctitle) will change the process name from "python" to "offpunk". Useful to kill it without killing every python service.

View File

@ -60,14 +60,6 @@ except ModuleNotFoundError:
_HAS_EDITOR = False
import textwrap
try:
import ansiwrap
wrap_method = ansiwrap.wrap
_HAS_ANSIWRAP = True
except ModuleNotFoundError:
print("Try installing python-ansiwrap for better rendering")
wrap_method = textwrap.wrap
_HAS_ANSIWRAP = False
global TERM_WIDTH
TERM_WIDTH = 72
@ -79,28 +71,6 @@ def term_width():
width = cur
return width
# return wrapped text as a list of lines
def wraplines(*args,**kwargs):
if "center" in kwargs:
center = kwargs.pop("center")
else:
center = True
lines = wrap_method(*args,**kwargs)
lines2 = []
textwidth = TERM_WIDTH
termspace = shutil.get_terminal_size()[0]
#Following code instert blanck spaces to center the content
if center and termspace > textwidth:
margin = int((termspace - textwidth)//2)
else:
margin = 0
for l in lines :
lines2.append(margin*" "+l)
return lines2
# return wrapped text as one string
def wrapparagraph(*args,**kwargs):
return "\n".join(wraplines(*args,**kwargs))
try:
from PIL import Image
_HAS_PIL = True
@ -119,15 +89,15 @@ if _HAS_CHAFA:
# with chafa < 1.10, --version was returned to stderr instead of stdout.
if output != '':
_NEW_CHAFA = True
if _NEW_CHAFA and _HAS_ANSIWRAP:
if _NEW_CHAFA :
_RENDER_IMAGE = True
elif _HAS_TIMG and _HAS_ANSIWRAP:
elif _HAS_TIMG :
_RENDER_IMAGE = True
elif _HAS_CHAFA and _HAS_PIL and _HAS_ANSIWRAP:
elif _HAS_CHAFA and _HAS_PIL:
_RENDER_IMAGE = True
else:
_RENDER_IMAGE = False
print("To render images inline, you need either chafa or timg and ansiwrap.")
print("To render images inline, you need either chafa or timg.")
if not _NEW_CHAFA and not _HAS_TIMG:
print("Before Chafa 1.10, you also need python-pil")
@ -142,7 +112,7 @@ def inline_image(img_file,width):
#Chafa is faster than timg inline. Let use that one by default
inline = None
ansi_img = ""
if _HAS_CHAFA and _HAS_ANSIWRAP:
if _HAS_CHAFA:
if _HAS_PIL and not _NEW_CHAFA:
# this code is a hack to remove frames from animated gif
img_obj = Image.open(img_file)
@ -152,7 +122,7 @@ def inline_image(img_file,width):
inline = "chafa --bg white -s %s -f symbols"
elif _NEW_CHAFA:
inline = "chafa --bg white -s %s -f symbols --animate=off"
if not inline and _HAS_TIMG and _HAS_ANSIWRAP:
if not inline and _HAS_TIMG:
inline = "timg --frames=1 -p q -g %sx1000"
if inline:
cmd = inline%width+ " \"%s\""%img_file
@ -402,12 +372,14 @@ class AbstractRenderer():
self.current_indent = ""
self.disabled_indents = None
# each color is an [open,close] pair code
self.colors = { "italic" : ["3","23"],
self.colors = {
"bold" : ["1","22"],
"blue" : ["34","39"],
"underline": ["4","24"],
"faint" : ["2","22"],
"italic" : ["3","23"],
"underline": ["4","24"],
"red" : ["31","39"],
"yellow" : ["33","39"],
"blue" : ["34","39"],
}
def _insert(self,color,open=True):
@ -627,8 +599,20 @@ class AbstractRenderer():
self.links[mode] = result[1]
return self.rendered_text[mode]
def display(self,mode="readable",title=""):
body = title + self.get_body(mode=mode)
def _window_title(self,title,info=None):
title_r = self.representation(term_width())
title_r.open_color("red")
title_r.open_color("bold")
title_r.add_text(title)
title_r.close_color("bold")
if info:
title_r.add_text(" (%s)"%info)
title_r.close_color("red")
return title_r.get_final()
def display(self,mode="readable",window_title="",window_info=None):
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
@ -772,33 +756,43 @@ class GopherRenderer(AbstractRenderer):
def get_mime(self):
return "text/gopher"
def get_title(self):
return "Gopher - No Title"
if not self.title:
if self.body:
firstline = self.body.splitlines()[0]
firstline = firstline.split("\t")[0]
if firstline.startswith("i"):
firstline = firstline[1:]
self.title = firstline
return self.title
#menu_or_text
def render(self,body,width=None,mode=None):
if not width:
width = term_width()
try:
render,links = self._render_goph(width=width,mode=mode)
render,links = self._render_goph(body,width=width,mode=mode)
except Exception as err:
print("Error ",err)
lines = body.split("\n")
render = ""
for line in lines:
render += wrapparagraph(line,width) + "\n"
print("Error rendering Gopher ",err)
r = self.representation(width)
r.add_block(body)
render = r.get_final()
links = []
return render,links
def _render_goph(self,width=None,mode=None):
def _render_goph(self,body,width=None,mode=None):
if not width:
width = term_width()
# This is copied straight from Agena (and thus from VF1)
rendered_text = ""
# This was copied straight from Agena (then later adapted)
links = []
r = self.representation(width)
for line in self.body.split("\n"):
r.newline()
if line.startswith("i"):
towrap = line[1:].split("\t")[0] + "\r\n"
rendered_text += wrapparagraph(towrap,width) + "\n"
towrap = line[1:].split("\t")[0]
if len(towrap.strip()) > 0:
r.add_text(towrap)
else:
r.newparagraph()
elif not line.strip() in [".",""]:
parts = line.split("\t")
parts[-1] = parts[-1].strip()
@ -820,12 +814,11 @@ class GopherRenderer(AbstractRenderer):
url = "gopher://%s%s/%s%s" %(host,port,itemtype,path)
linkline = url + " " + name
links.append(linkline)
towrap = "[%s] "%len(links)+ name + "\n"
rendered_text += wrapparagraph(towrap,width) + "\n"
towrap = "[%s] "%len(links)+ name
r.add_text(towrap)
else:
towrap = line +"\n"
rendered_text += wrapparagraph(towrap,width) + "\n"
return rendered_text,links
r.add_text(line)
return r.get_final(),links
class FolderRenderer(GemtextRenderer):
@ -990,9 +983,9 @@ class ImageRenderer(AbstractRenderer):
for l in lines:
new_img += spaces*" " + l + "\n"
return new_img, []
def display(self,mode=None,title=None):
if title:
print(title)
def display(self,mode=None,window_title=None,window_info=None):
if window_title:
print(self._window_title(window_title,info=window_info))
terminal_image(self.body)
return True
@ -1459,22 +1452,6 @@ class GeminiItem():
else:
return []
# Red title above rendered content
def _make_terminal_title(self):
title = self.get_capsule_title()
#FIXME : how do I know that Im offline_only ?
if self.is_cache_valid(): #and self.offline_only and not self.local:
last_modification = self.cache_last_modified()
str_last = time.ctime(last_modification)
nbr = len(self.get_links(mode="links_only"))
if self.local:
title += " (%s items) \x1b[0;31m(local file)"%nbr
else:
title += " (%s links) \x1b[0;31m(last accessed on %s)"%(nbr,str_last)
rendered_title = "\x1b[31m\x1b[1m"+ title + "\x1b[0m"
wrapped = wrapparagraph(rendered_title,term_width())
return wrapped + "\n"
def _set_renderer(self,mime=None):
if self.local and os.path.isdir(self.get_cache_path()):
self.renderer = FolderRenderer("",self.get_cache_path())
@ -1512,8 +1489,16 @@ class GeminiItem():
self._set_renderer()
if self.renderer and self.renderer.is_valid():
self.last_mode = mode
title = self._make_terminal_title()
return self.renderer.display(mode=mode,title=title)
title = self.get_capsule_title()
if self.is_cache_valid(): #and self.offline_only and not self.local:
nbr = len(self.get_links(mode="links_only"))
if self.local:
title += " (%s items)"%nbr
str_last = "local file)"
else:
str_last = "last accessed on %s" %time.ctime(self.cache_last_modified())
title += " (%s links)"%nbr
return self.renderer.display(mode=mode,window_title=title,window_info=str_last)
else:
return False
@ -3061,7 +3046,6 @@ Marks are temporary until shutdown (not saved to disk)."""
output = "Offpunk " + _VERSION + "\n"
output += "===========\n"
output += " - python-editor : " + has(_HAS_EDITOR)
output += " - python-ansiwrap : " + has(_HAS_ANSIWRAP)
output += " - python-cryptography : " + has(_HAS_CRYPTOGRAPHY)
output += " - python-magic : " + has(_HAS_MAGIC)
output += " - python-requests : " + has(_DO_HTTP)
@ -3079,12 +3063,12 @@ Marks are temporary until shutdown (not saved to disk)."""
output += " - python-pil : " + has(_HAS_PIL)
output += "\nFeatures :\n"
output += " - Render images (ansiwrap, chafa|timg) : " + has(_RENDER_IMAGE)
output += " - Render HTML (bs4, readability) : " + has(_DO_HTML)
output += " - Render Atom/RSS feeds (feedparser) : " + has(_DO_FEED)
output += " - Connect to http/https (requests) : " + has(_DO_HTTP)
output += " - copy to/from clipboard (xsel) : " + has(_HAS_XSEL)
output += " - restore last position (less 572+) : " + has(_LESS_RESTORE_POSITION)
output += " - Render images (chafa|timg) : " + has(_RENDER_IMAGE)
output += " - Render HTML (bs4, readability) : " + has(_DO_HTML)
output += " - Render Atom/RSS feeds (feedparser) : " + has(_DO_FEED)
output += " - Connect to http/https (requests) : " + has(_DO_HTTP)
output += " - copy to/from clipboard (xsel) : " + has(_HAS_XSEL)
output += " - restore last position (less 572+) : " + has(_LESS_RESTORE_POSITION)
output += "\n"
output += "Config directory : " + _CONFIG_DIR + "\n"
output += "User Data directory : " + _DATA_DIR + "\n"