Removed definitely ansiwrap. This is a huge achievement.
This commit is contained in:
parent
68fb345ccc
commit
af86a08e31
|
@ -1,7 +1,8 @@
|
||||||
# Offpunk History
|
# Offpunk History
|
||||||
|
|
||||||
## 1.2 - Unreleased
|
## 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 don’t use it anymore (which is important)
|
||||||
- Limit width of --sync output
|
- Limit width of --sync output
|
||||||
- Fixed a crash when trying to save a folder
|
- Fixed a crash when trying to save a folder
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ Announces about Offpunk will be made on Ploum’s Gemlog => gemini://rawtext.cl
|
||||||
Offpunk has no "strict dependencies", i.e. it should run and work without anything
|
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
|
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
|
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.
|
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
|
* [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)
|
* [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)
|
* [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
|
* The [cryptography library](https://pypi.org/project/cryptography/) will
|
||||||
provide a better and slightly more secure experience when using the default
|
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).
|
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)
|
* [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)
|
* [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.
|
* [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-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.
|
* [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.
|
||||||
|
|
||||||
|
|
156
offpunk.py
156
offpunk.py
|
@ -60,14 +60,6 @@ except ModuleNotFoundError:
|
||||||
_HAS_EDITOR = False
|
_HAS_EDITOR = False
|
||||||
|
|
||||||
import textwrap
|
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
|
global TERM_WIDTH
|
||||||
TERM_WIDTH = 72
|
TERM_WIDTH = 72
|
||||||
|
@ -79,28 +71,6 @@ def term_width():
|
||||||
width = cur
|
width = cur
|
||||||
return width
|
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:
|
try:
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
_HAS_PIL = True
|
_HAS_PIL = True
|
||||||
|
@ -119,15 +89,15 @@ if _HAS_CHAFA:
|
||||||
# with chafa < 1.10, --version was returned to stderr instead of stdout.
|
# with chafa < 1.10, --version was returned to stderr instead of stdout.
|
||||||
if output != '':
|
if output != '':
|
||||||
_NEW_CHAFA = True
|
_NEW_CHAFA = True
|
||||||
if _NEW_CHAFA and _HAS_ANSIWRAP:
|
if _NEW_CHAFA :
|
||||||
_RENDER_IMAGE = True
|
_RENDER_IMAGE = True
|
||||||
elif _HAS_TIMG and _HAS_ANSIWRAP:
|
elif _HAS_TIMG :
|
||||||
_RENDER_IMAGE = True
|
_RENDER_IMAGE = True
|
||||||
elif _HAS_CHAFA and _HAS_PIL and _HAS_ANSIWRAP:
|
elif _HAS_CHAFA and _HAS_PIL:
|
||||||
_RENDER_IMAGE = True
|
_RENDER_IMAGE = True
|
||||||
else:
|
else:
|
||||||
_RENDER_IMAGE = False
|
_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:
|
if not _NEW_CHAFA and not _HAS_TIMG:
|
||||||
print("Before Chafa 1.10, you also need python-pil")
|
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
|
#Chafa is faster than timg inline. Let use that one by default
|
||||||
inline = None
|
inline = None
|
||||||
ansi_img = ""
|
ansi_img = ""
|
||||||
if _HAS_CHAFA and _HAS_ANSIWRAP:
|
if _HAS_CHAFA:
|
||||||
if _HAS_PIL and not _NEW_CHAFA:
|
if _HAS_PIL and not _NEW_CHAFA:
|
||||||
# this code is a hack to remove frames from animated gif
|
# this code is a hack to remove frames from animated gif
|
||||||
img_obj = Image.open(img_file)
|
img_obj = Image.open(img_file)
|
||||||
|
@ -152,7 +122,7 @@ def inline_image(img_file,width):
|
||||||
inline = "chafa --bg white -s %s -f symbols"
|
inline = "chafa --bg white -s %s -f symbols"
|
||||||
elif _NEW_CHAFA:
|
elif _NEW_CHAFA:
|
||||||
inline = "chafa --bg white -s %s -f symbols --animate=off"
|
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"
|
inline = "timg --frames=1 -p q -g %sx1000"
|
||||||
if inline:
|
if inline:
|
||||||
cmd = inline%width+ " \"%s\""%img_file
|
cmd = inline%width+ " \"%s\""%img_file
|
||||||
|
@ -402,12 +372,14 @@ class AbstractRenderer():
|
||||||
self.current_indent = ""
|
self.current_indent = ""
|
||||||
self.disabled_indents = None
|
self.disabled_indents = None
|
||||||
# each color is an [open,close] pair code
|
# each color is an [open,close] pair code
|
||||||
self.colors = { "italic" : ["3","23"],
|
self.colors = {
|
||||||
"bold" : ["1","22"],
|
"bold" : ["1","22"],
|
||||||
"blue" : ["34","39"],
|
|
||||||
"underline": ["4","24"],
|
|
||||||
"faint" : ["2","22"],
|
"faint" : ["2","22"],
|
||||||
|
"italic" : ["3","23"],
|
||||||
|
"underline": ["4","24"],
|
||||||
|
"red" : ["31","39"],
|
||||||
"yellow" : ["33","39"],
|
"yellow" : ["33","39"],
|
||||||
|
"blue" : ["34","39"],
|
||||||
}
|
}
|
||||||
|
|
||||||
def _insert(self,color,open=True):
|
def _insert(self,color,open=True):
|
||||||
|
@ -627,8 +599,20 @@ class AbstractRenderer():
|
||||||
self.links[mode] = result[1]
|
self.links[mode] = result[1]
|
||||||
return self.rendered_text[mode]
|
return self.rendered_text[mode]
|
||||||
|
|
||||||
def display(self,mode="readable",title=""):
|
def _window_title(self,title,info=None):
|
||||||
body = title + self.get_body(mode=mode)
|
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:
|
if not body:
|
||||||
return False
|
return False
|
||||||
# We actually put the body in a tmpfile before giving it to less
|
# We actually put the body in a tmpfile before giving it to less
|
||||||
|
@ -772,33 +756,43 @@ class GopherRenderer(AbstractRenderer):
|
||||||
def get_mime(self):
|
def get_mime(self):
|
||||||
return "text/gopher"
|
return "text/gopher"
|
||||||
def get_title(self):
|
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
|
#menu_or_text
|
||||||
def render(self,body,width=None,mode=None):
|
def render(self,body,width=None,mode=None):
|
||||||
if not width:
|
if not width:
|
||||||
width = term_width()
|
width = term_width()
|
||||||
try:
|
try:
|
||||||
render,links = self._render_goph(width=width,mode=mode)
|
render,links = self._render_goph(body,width=width,mode=mode)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print("Error ",err)
|
print("Error rendering Gopher ",err)
|
||||||
lines = body.split("\n")
|
r = self.representation(width)
|
||||||
render = ""
|
r.add_block(body)
|
||||||
for line in lines:
|
render = r.get_final()
|
||||||
render += wrapparagraph(line,width) + "\n"
|
|
||||||
links = []
|
links = []
|
||||||
return render,links
|
return render,links
|
||||||
|
|
||||||
def _render_goph(self,width=None,mode=None):
|
def _render_goph(self,body,width=None,mode=None):
|
||||||
if not width:
|
if not width:
|
||||||
width = term_width()
|
width = term_width()
|
||||||
# This is copied straight from Agena (and thus from VF1)
|
# This was copied straight from Agena (then later adapted)
|
||||||
rendered_text = ""
|
|
||||||
links = []
|
links = []
|
||||||
|
r = self.representation(width)
|
||||||
for line in self.body.split("\n"):
|
for line in self.body.split("\n"):
|
||||||
|
r.newline()
|
||||||
if line.startswith("i"):
|
if line.startswith("i"):
|
||||||
towrap = line[1:].split("\t")[0] + "\r\n"
|
towrap = line[1:].split("\t")[0]
|
||||||
rendered_text += wrapparagraph(towrap,width) + "\n"
|
if len(towrap.strip()) > 0:
|
||||||
|
r.add_text(towrap)
|
||||||
|
else:
|
||||||
|
r.newparagraph()
|
||||||
elif not line.strip() in [".",""]:
|
elif not line.strip() in [".",""]:
|
||||||
parts = line.split("\t")
|
parts = line.split("\t")
|
||||||
parts[-1] = parts[-1].strip()
|
parts[-1] = parts[-1].strip()
|
||||||
|
@ -820,12 +814,11 @@ class GopherRenderer(AbstractRenderer):
|
||||||
url = "gopher://%s%s/%s%s" %(host,port,itemtype,path)
|
url = "gopher://%s%s/%s%s" %(host,port,itemtype,path)
|
||||||
linkline = url + " " + name
|
linkline = url + " " + name
|
||||||
links.append(linkline)
|
links.append(linkline)
|
||||||
towrap = "[%s] "%len(links)+ name + "\n"
|
towrap = "[%s] "%len(links)+ name
|
||||||
rendered_text += wrapparagraph(towrap,width) + "\n"
|
r.add_text(towrap)
|
||||||
else:
|
else:
|
||||||
towrap = line +"\n"
|
r.add_text(line)
|
||||||
rendered_text += wrapparagraph(towrap,width) + "\n"
|
return r.get_final(),links
|
||||||
return rendered_text,links
|
|
||||||
|
|
||||||
|
|
||||||
class FolderRenderer(GemtextRenderer):
|
class FolderRenderer(GemtextRenderer):
|
||||||
|
@ -990,9 +983,9 @@ class ImageRenderer(AbstractRenderer):
|
||||||
for l in lines:
|
for l in lines:
|
||||||
new_img += spaces*" " + l + "\n"
|
new_img += spaces*" " + l + "\n"
|
||||||
return new_img, []
|
return new_img, []
|
||||||
def display(self,mode=None,title=None):
|
def display(self,mode=None,window_title=None,window_info=None):
|
||||||
if title:
|
if window_title:
|
||||||
print(title)
|
print(self._window_title(window_title,info=window_info))
|
||||||
terminal_image(self.body)
|
terminal_image(self.body)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -1459,22 +1452,6 @@ class GeminiItem():
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Red title above rendered content
|
|
||||||
def _make_terminal_title(self):
|
|
||||||
title = self.get_capsule_title()
|
|
||||||
#FIXME : how do I know that I’m 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):
|
def _set_renderer(self,mime=None):
|
||||||
if self.local and os.path.isdir(self.get_cache_path()):
|
if self.local and os.path.isdir(self.get_cache_path()):
|
||||||
self.renderer = FolderRenderer("",self.get_cache_path())
|
self.renderer = FolderRenderer("",self.get_cache_path())
|
||||||
|
@ -1512,8 +1489,16 @@ class GeminiItem():
|
||||||
self._set_renderer()
|
self._set_renderer()
|
||||||
if self.renderer and self.renderer.is_valid():
|
if self.renderer and self.renderer.is_valid():
|
||||||
self.last_mode = mode
|
self.last_mode = mode
|
||||||
title = self._make_terminal_title()
|
title = self.get_capsule_title()
|
||||||
return self.renderer.display(mode=mode,title=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:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -3061,7 +3046,6 @@ Marks are temporary until shutdown (not saved to disk)."""
|
||||||
output = "Offpunk " + _VERSION + "\n"
|
output = "Offpunk " + _VERSION + "\n"
|
||||||
output += "===========\n"
|
output += "===========\n"
|
||||||
output += " - python-editor : " + has(_HAS_EDITOR)
|
output += " - python-editor : " + has(_HAS_EDITOR)
|
||||||
output += " - python-ansiwrap : " + has(_HAS_ANSIWRAP)
|
|
||||||
output += " - python-cryptography : " + has(_HAS_CRYPTOGRAPHY)
|
output += " - python-cryptography : " + has(_HAS_CRYPTOGRAPHY)
|
||||||
output += " - python-magic : " + has(_HAS_MAGIC)
|
output += " - python-magic : " + has(_HAS_MAGIC)
|
||||||
output += " - python-requests : " + has(_DO_HTTP)
|
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 += " - python-pil : " + has(_HAS_PIL)
|
||||||
|
|
||||||
output += "\nFeatures :\n"
|
output += "\nFeatures :\n"
|
||||||
output += " - Render images (ansiwrap, chafa|timg) : " + has(_RENDER_IMAGE)
|
output += " - Render images (chafa|timg) : " + has(_RENDER_IMAGE)
|
||||||
output += " - Render HTML (bs4, readability) : " + has(_DO_HTML)
|
output += " - Render HTML (bs4, readability) : " + has(_DO_HTML)
|
||||||
output += " - Render Atom/RSS feeds (feedparser) : " + has(_DO_FEED)
|
output += " - Render Atom/RSS feeds (feedparser) : " + has(_DO_FEED)
|
||||||
output += " - Connect to http/https (requests) : " + has(_DO_HTTP)
|
output += " - Connect to http/https (requests) : " + has(_DO_HTTP)
|
||||||
output += " - copy to/from clipboard (xsel) : " + has(_HAS_XSEL)
|
output += " - copy to/from clipboard (xsel) : " + has(_HAS_XSEL)
|
||||||
output += " - restore last position (less 572+) : " + has(_LESS_RESTORE_POSITION)
|
output += " - restore last position (less 572+) : " + has(_LESS_RESTORE_POSITION)
|
||||||
output += "\n"
|
output += "\n"
|
||||||
output += "Config directory : " + _CONFIG_DIR + "\n"
|
output += "Config directory : " + _CONFIG_DIR + "\n"
|
||||||
output += "User Data directory : " + _DATA_DIR + "\n"
|
output += "User Data directory : " + _DATA_DIR + "\n"
|
||||||
|
|
Loading…
Reference in New Issue