offpunk 2.1__tar.gz → 2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {offpunk-2.1 → offpunk-2.2}/CHANGELOG +18 -0
  2. {offpunk-2.1 → offpunk-2.2}/PKG-INFO +1 -1
  3. {offpunk-2.1 → offpunk-2.2}/ansicat.py +95 -25
  4. {offpunk-2.1 → offpunk-2.2}/man/ansicat.1 +2 -0
  5. {offpunk-2.1 → offpunk-2.2}/man/netcache.1 +11 -0
  6. {offpunk-2.1 → offpunk-2.2}/man/opnk.1 +4 -0
  7. {offpunk-2.1 → offpunk-2.2}/netcache.py +8 -7
  8. {offpunk-2.1 → offpunk-2.2}/offpunk.py +56 -44
  9. {offpunk-2.1 → offpunk-2.2}/offutils.py +7 -1
  10. {offpunk-2.1 → offpunk-2.2}/opnk.py +16 -10
  11. {offpunk-2.1 → offpunk-2.2}/.gitignore +0 -0
  12. {offpunk-2.1 → offpunk-2.2}/CONTRIBUTORS +0 -0
  13. {offpunk-2.1 → offpunk-2.2}/LICENSE +0 -0
  14. {offpunk-2.1 → offpunk-2.2}/README.md +0 -0
  15. {offpunk-2.1 → offpunk-2.2}/debug.sh +0 -0
  16. {offpunk-2.1 → offpunk-2.2}/doc/config.gmi +0 -0
  17. {offpunk-2.1 → offpunk-2.2}/doc/dev.gmi +0 -0
  18. {offpunk-2.1 → offpunk-2.2}/doc/index.gmi +0 -0
  19. {offpunk-2.1 → offpunk-2.2}/doc/install.gmi +0 -0
  20. {offpunk-2.1 → offpunk-2.2}/doc/lists.gmi +0 -0
  21. {offpunk-2.1 → offpunk-2.2}/doc/offline.gmi +0 -0
  22. {offpunk-2.1 → offpunk-2.2}/doc/shell.gmi +0 -0
  23. {offpunk-2.1 → offpunk-2.2}/doc/tutorial.gmi +0 -0
  24. {offpunk-2.1 → offpunk-2.2}/man/offpunk.1 +0 -0
  25. {offpunk-2.1 → offpunk-2.2}/netcache_migration.py +0 -0
  26. {offpunk-2.1 → offpunk-2.2}/offblocklist.py +0 -0
  27. {offpunk-2.1 → offpunk-2.2}/offthemes.py +0 -0
  28. {offpunk-2.1 → offpunk-2.2}/pyproject.toml +0 -0
  29. {offpunk-2.1 → offpunk-2.2}/requirements.txt +0 -0
  30. {offpunk-2.1 → offpunk-2.2}/screenshot_offpunk1.png +0 -0
  31. {offpunk-2.1 → offpunk-2.2}/screenshot_offpunk2.png +0 -0
  32. {offpunk-2.1 → offpunk-2.2}/ubuntu_dependencies.txt +0 -0
@@ -1,5 +1,23 @@
1
1
  # Offpunk History
2
2
 
3
+
4
+ ## 2.2 - February 13th 2023
5
+ - cache folder is now configurable through $OFFPUNK_CACHE_PATH environment variable (by prx)
6
+ - offpunk: adding an URL to a list now update the view mode if url already present
7
+ - netcache: solve an infinite gemini loop with code 6X (see also bug #31)
8
+ - ansicat: added support for <video> HTML-element
9
+ - ansicat: if chafa fails to load an image, fallback to timg if available
10
+ - offpunk: add list autocompletion to "tour"
11
+ - offpunk: removed "blackbox", which has not been used nor maintained
12
+ - offpunk: "gus" was broken, it is functionnal again
13
+ - opnk/offpunk: more informative prompt in less
14
+ - ansicat: added support for HTML description elements <dt> and <dd> (by Bert Livens)
15
+ - opnk: added "--mode" command-line argument (bug #39)
16
+ - offpunk: support for "preformatted" theming (bug #38)
17
+ - opnk/netcache: added "--cache-validity" command-line argument (bug #37)
18
+ - ansicat: consider files as XML, not SVG, if they don’t have .svg extension
19
+ - offpunk: fix "view link" crashing with link to empty files
20
+
3
21
  ## 2.1 - December 15th 2023
4
22
  - freshly updated gemtext/rss links are highlighted ("new_link" theme option)
5
23
  - offpunk : new "copy title" and "copy link" function
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: offpunk
3
- Version: 2.1
3
+ Version: 2.2
4
4
  Project-URL: Homepage, https://sr.ht/~lioploum/offpunk/
5
5
  Project-URL: Source, https://git.sr.ht/~lioploum/offpunk
6
6
  Project-URL: Bug Tracker, https://todo.sr.ht/~lioploum/offpunk
@@ -106,7 +106,8 @@ def inline_image(img_file,width):
106
106
  if not os.path.exists(img_file):
107
107
  return ""
108
108
  #Chafa is faster than timg inline. Let use that one by default
109
- inline = None
109
+ #But we keep a list of "inlines" in case chafa fails
110
+ inlines = []
110
111
  ansi_img = ""
111
112
  #We avoid errors by not trying to render non-image files
112
113
  if shutil.which("file"):
@@ -120,32 +121,39 @@ def inline_image(img_file,width):
120
121
  if hasattr(img_obj,"n_frames") and img_obj.n_frames > 1:
121
122
  # we remove all frames but the first one
122
123
  img_obj.save(img_file,format="gif",save_all=False)
123
- inline = "chafa --bg white -s %s -f symbols"
124
+ inlines.append("chafa --bg white -s %s -f symbols")
124
125
  elif _NEW_CHAFA:
125
- inline = "chafa --bg white -t 1 -s %s -f symbols --animate=off"
126
- if not inline and _NEW_TIMG:
127
- inline = "timg --frames=1 -p q -g %sx1000"
128
- if inline:
129
- cmd = inline%width + " %s"
126
+ inlines.append("chafa --bg white -t 1 -s %s -f symbols --animate=off")
127
+ if _NEW_TIMG:
128
+ inlines.append("timg --frames=1 -p q -g %sx1000")
129
+ image_success = False
130
+ while not image_success and len(inlines)>0:
131
+ cmd = inlines.pop(0)%width + " %s"
130
132
  try:
131
133
  ansi_img = run(cmd, parameter=img_file)
134
+ image_success = True
132
135
  except Exception as err:
133
- ansi_img = "***image failed : %s***\n" %err
136
+ ansi_img = "***IMAGE ERROR***\n%s…\n…%s" %(str(err)[:50],str(err)[-50:])
134
137
  return ansi_img
135
138
 
136
139
  def terminal_image(img_file):
137
140
  #Render by timg is better than old chafa.
138
141
  # it is also centered
139
- cmd = None
142
+ cmds = []
140
143
  if _NEW_CHAFA:
141
- cmd = "chafa -C on -d 0 --bg white -t 1 -w 1"
142
- elif _NEW_TIMG:
143
- cmd = "timg --loops=1 -C"
144
+ cmds.append("chafa -C on -d 0 --bg white -t 1 -w 1")
144
145
  elif _HAS_CHAFA:
145
- cmd = "chafa -d 0 --bg white -t 1 -w 1"
146
- if cmd:
147
- cmd = cmd + " %s"
148
- run(cmd, parameter=img_file, direct_output=True)
146
+ cmds.append("chafa -d 0 --bg white -t 1 -w 1")
147
+ if _NEW_TIMG:
148
+ cmds.append("timg --loops=1 -C")
149
+ image_success = False
150
+ while not image_success and len(cmds) > 0:
151
+ cmd = cmds.pop(0) + " %s"
152
+ try:
153
+ run(cmd, parameter=img_file, direct_output=True)
154
+ image_success = True
155
+ except Exception as err:
156
+ print(err)
149
157
 
150
158
 
151
159
  # First, we define the different content->text renderers, outside of the rest
@@ -357,15 +365,28 @@ class AbstractRenderer():
357
365
 
358
366
  # Beware, blocks are not wrapped nor indented and left untouched!
359
367
  # They are mostly useful for pictures and preformatted text.
360
- def add_block(self,intext):
368
+ def add_block(self,intext,theme=None):
361
369
  # If necessary, we add the title before a block
362
370
  self._title_first()
363
371
  # we don’t want to indent blocks
364
372
  self._endline()
365
373
  self._disable_indents()
366
- self.final_text += self.current_indent + intext
367
- self.new_paragraph = False
368
- self._endline()
374
+ #we have to apply the theme for every line in the intext
375
+ #applying theme to preformatted is controversial as it could change it
376
+ if theme:
377
+ block = ""
378
+ lines = intext.split("\n")
379
+ for l in lines:
380
+ self.open_theme(theme)
381
+ self.last_line += self.current_indent + l
382
+ self.close_theme(theme)
383
+ self._endline()
384
+ self.last_line += "\n"
385
+ #one thing is sure : we need to keep unthemed blocks for images!
386
+ else:
387
+ self.final_text += self.current_indent + intext
388
+ self.new_paragraph = False
389
+ self._endline()
369
390
  self._enable_indents()
370
391
 
371
392
  def add_text(self,intext):
@@ -633,7 +654,7 @@ class GemtextRenderer(AbstractRenderer):
633
654
  r.close_theme("preformatted")
634
655
  elif preformatted:
635
656
  # infinite line to not wrap preformated
636
- r.add_block(line+"\n")
657
+ r.add_block(line+"\n",theme="preformatted")
637
658
  elif len(line.strip()) == 0:
638
659
  r.newparagraph(force=True)
639
660
  elif line.startswith("=>"):
@@ -1077,7 +1098,7 @@ class HtmlRenderer(AbstractRenderer):
1077
1098
  toreturn = " " + toreturn
1078
1099
  return toreturn
1079
1100
  def recursive_render(element,indent="",preformatted=False):
1080
- if element.name == "blockquote":
1101
+ if element.name in ["blockquote", "dd"]:
1081
1102
  r.newparagraph()
1082
1103
  r.startindent(" ",reverse=" ")
1083
1104
  for child in element.children:
@@ -1085,7 +1106,7 @@ class HtmlRenderer(AbstractRenderer):
1085
1106
  recursive_render(child,indent="\t")
1086
1107
  r.close_theme("blockquote")
1087
1108
  r.endindent()
1088
- elif element.name in ["div","p"]:
1109
+ elif element.name in ["div","p","dt"]:
1089
1110
  r.newparagraph()
1090
1111
  for child in element.children:
1091
1112
  recursive_render(child,indent=indent)
@@ -1114,8 +1135,8 @@ class HtmlRenderer(AbstractRenderer):
1114
1135
  recursive_render(child,indent=indent,preformatted=True)
1115
1136
  elif element.name in ["pre"]:
1116
1137
  r.newparagraph()
1117
- r.add_block(element.text)
1118
- r.newparagraph()
1138
+ r.add_block(element.text,theme="preformatted")
1139
+ r.newparagraph(force=True)
1119
1140
  elif element.name in ["li"]:
1120
1141
  r.startindent(" • ",sub=" ")
1121
1142
  for child in element.children:
@@ -1196,6 +1217,52 @@ class HtmlRenderer(AbstractRenderer):
1196
1217
  r.add_text(text + link_id)
1197
1218
  r.close_theme("image_link")
1198
1219
  r.newline()
1220
+
1221
+ elif element.name == "video":
1222
+ poster = element.get("poster")
1223
+ src = element.get("src")
1224
+ for child in element.children:
1225
+ if not src:
1226
+ if child.name == "source":
1227
+ src = child.get("src")
1228
+ text = ""
1229
+ if poster:
1230
+ ansi_img = render_image(poster,width=width,mode=mode)
1231
+ alt = element.get("alt")
1232
+ if alt:
1233
+ alt = sanitize_string(alt)
1234
+ text += "[VIDEO] %s"%alt
1235
+ else:
1236
+ text += "[VIDEO]"
1237
+
1238
+ if poster:
1239
+ if not mode in self.images:
1240
+ self.images[mode] = []
1241
+ poster_url,d = looks_like_base64(poster,self.url)
1242
+ if poster_url:
1243
+ vid_url,d2 = looks_like_base64(src,self.url)
1244
+ self.images[mode].append(poster_url)
1245
+ r.add_block(ansi_img)
1246
+ r.open_theme("image_link")
1247
+ r.center_line()
1248
+ if vid_url and src:
1249
+ links.append(vid_url+" "+text)
1250
+ link_id = " [%s]"%(len(links)+startlinks)
1251
+ r.add_text(text + link_id)
1252
+ else:
1253
+ r.add_text(text)
1254
+ r.close_theme("image_link")
1255
+ r.newline()
1256
+ elif src:
1257
+ vid_url,d = looks_like_base64(src,self.url)
1258
+ links.append(vid_url+" "+text)
1259
+ link_id = " [%s]"%(len(links)+startlinks)
1260
+ r.open_theme("image_link")
1261
+ r.center_line()
1262
+ r.add_text(text + link_id)
1263
+ r.close_theme("image_link")
1264
+ r.newline()
1265
+
1199
1266
  elif element.name == "br":
1200
1267
  r.newline()
1201
1268
  elif element.name not in ["script","style","template"] and type(element) != Comment:
@@ -1288,6 +1355,9 @@ def get_mime(path,url=None):
1288
1355
  # If it’s a xml file, consider it as such, regardless of what file thinks
1289
1356
  elif path.endswith(".xml"):
1290
1357
  mime = "text/xml"
1358
+ # If it doesn’t end with .svg, it is probably an xml, not a SVG file
1359
+ elif "svg" in mime and not path.endswith(".svg"):
1360
+ mime = "text/xml"
1291
1361
  #Some xml/html document are considered as octet-stream
1292
1362
  if mime == "application/octet-stream":
1293
1363
  mime = "text/xml"
@@ -54,6 +54,8 @@ either thanks to the MIME type,
54
54
  or from the file being rendered itself.
55
55
  .It Fl \-mime Ar MIME
56
56
  MIME type of the content to parse.
57
+ .It Fl \-mode Ar MODE
58
+ MODE to use to render to choose between normal (default), full or source
57
59
  .It Fl \-url Ar URL ...
58
60
  original URL of the content.
59
61
  .El
@@ -27,6 +27,15 @@ otherwise it would always refresh it from the version available online.
27
27
  It is also useful for mapping a given URL to its location in the cache,
28
28
  independently of whether it has been downloaded first.
29
29
  .Pp
30
+ Default cache path is
31
+ .Pa ~/.cache/offpunk .
32
+ Set
33
+ .Ev OFFPUNK_CACHE_PATH
34
+ environment variable to use another location.
35
+ .Bd -literal
36
+ OFFPUNK_CACHE_PATH=/home/ploum/custom-cache netcache.py gemini://some.url
37
+ .Ed
38
+ .Pp
30
39
  .Xr Offpunk 1
31
40
  is a command-line browser and feed reader dedicated to browsing the Web,
32
41
  Gemini, Gopher and Spartan.
@@ -47,6 +56,8 @@ The value is expressed in megabytes.
47
56
  .It Fl \-timeout Ar TIMEOUT
48
57
  time to wait before cancelling connection.
49
58
  The value is expressed in seconds.
59
+ .It Fl \-cache-validity CACHE_VALIDITY
60
+ Maximum age (in second) of the cached version before redownloading a new version.
50
61
  .El
51
62
  .
52
63
  .Sh EXIT STATUS
@@ -37,6 +37,10 @@ path to the file or URL to open.
37
37
  .Bl -tag -width Ds -offset indent
38
38
  .It Fl h , \-help
39
39
  Show a help message and exit
40
+ .It Fl \-mode Ar MODE
41
+ MODE to use to render to choose between normal (default), full or source
42
+ .It Fl \-cache-validity CACHE_VALIDITY
43
+ Maximum age (in second) of the cached version before redownloading a new version.
40
44
  .El
41
45
  .
42
46
  .Sh EXIT STATUS
@@ -731,9 +731,9 @@ def _fetch_gemini(url,timeout=DEFAULT_TIMEOUT,interactive=True,accept_bad_ssl_ce
731
731
  raise RuntimeError(meta)
732
732
  # Client cert
733
733
  elif status.startswith("6"):
734
- print("Handling certificates for status 6X are not supported by offpunk\n")
735
- print("See bug #31 for discussion about the problem")
736
- _fetch_gemini(url)
734
+ error = "Handling certificates for status 6X are not supported by offpunk\n"
735
+ error += "See bug #31 for discussion about the problem"
736
+ raise RuntimeError(error)
737
737
  # Invalid status
738
738
  elif not status.startswith("2"):
739
739
  raise RuntimeError("Server returned undefined status code %s!" % status)
@@ -878,11 +878,12 @@ def main():
878
878
  help="Cancel download of items above that size (value in Mb).")
879
879
  parser.add_argument("--timeout", type=int,
880
880
  help="Time to wait before cancelling connection (in second).")
881
+ parser.add_argument("--cache-validity",type=int, default=0,
882
+ help="maximum age, in second, of the cached version before \
883
+ redownloading a new version")
881
884
  # No argument: write help
882
885
  parser.add_argument('url', metavar='URL', nargs='*',
883
886
  help='download URL and returns the content or the path to a cached version')
884
- # arg = URL: download and returns cached URI
885
- # --cache-validity : do not download if cache is valid
886
887
  # --validity : returns the date of the cached version, Null if no version
887
888
  # --force-download : download and replace cache, even if valid
888
889
  args = parser.parse_args()
@@ -893,8 +894,8 @@ def main():
893
894
  if args.offline:
894
895
  path = get_cache_path(u)
895
896
  else:
896
- print("Download URL: %s" %u)
897
- path,url = fetch(u,max_size=args.max_size,timeout=args.timeout)
897
+ path,url = fetch(u,max_size=args.max_size,timeout=args.timeout,\
898
+ validity=args.cache_validity)
898
899
  if args.path:
899
900
  print(path)
900
901
  else:
@@ -4,7 +4,7 @@
4
4
  Offline-First Gemini/Web/Gopher/RSS reader and browser
5
5
  """
6
6
 
7
- __version__ = "2.1"
7
+ __version__ = "2.2"
8
8
 
9
9
  ## Initial imports and conditional imports {{{
10
10
  import argparse
@@ -206,6 +206,8 @@ class GeminiClient(cmd.Cmd):
206
206
  return [i+" " for i in allowed if i.startswith(text)]
207
207
  def complete_move(self,text,line,begidx,endidx):
208
208
  return self.complete_add(text,line,begidx,endidx)
209
+ def complete_tour(self,text,line,begidx,endidx):
210
+ return self.complete_add(text,line,begidx,endidx)
209
211
 
210
212
  def complete_theme(self,text,line,begidx,endidx):
211
213
  elements = offthemes.default
@@ -299,7 +301,7 @@ class GeminiClient(cmd.Cmd):
299
301
  else:
300
302
  self.page_index = 0
301
303
  # Update state (external files are not added to history)
302
- self.current_url = url
304
+ self.current_url = modedurl
303
305
  if update_hist and not self.sync_only:
304
306
  self._update_history(modedurl)
305
307
  else:
@@ -920,7 +922,8 @@ Use 'ls -l' to see URLs."""
920
922
  if not line:
921
923
  print("What?")
922
924
  return
923
- self._go_to_url(urllib.parse.urlunparse("gemini","geminispace.info","/search","",line,""))
925
+ search = line.replace(" ","%20")
926
+ self._go_to_url("gemini://geminispace.info/search?%s"%search)
924
927
 
925
928
  def do_history(self, *args):
926
929
  """Display history."""
@@ -988,8 +991,11 @@ Use "view XX" where XX is a number to view information about link XX.
988
991
  if netcache.is_cache_valid(link_url):
989
992
  last_modified = netcache.cache_last_modified(link_url)
990
993
  link_renderer = self.get_renderer(link_url)
991
- link_title = link_renderer.get_page_title()
992
- print(link_title)
994
+ if link_renderer:
995
+ link_title = link_renderer.get_page_title()
996
+ print(link_title)
997
+ else:
998
+ print("Empty cached version")
993
999
  print("Last cached on %s"%time.ctime(last_modified))
994
1000
  else:
995
1001
  print("No cached version for this link")
@@ -1215,8 +1221,6 @@ archives, which is a special historical list limited in size. It is similar to `
1215
1221
  url = self.current_url
1216
1222
  r = self.get_renderer(url)
1217
1223
  if r:
1218
- mode = r.get_mode()
1219
- url = mode_url(url,mode)
1220
1224
  title = r.get_page_title()
1221
1225
  else:
1222
1226
  title = ""
@@ -1233,23 +1237,26 @@ archives, which is a special historical list limited in size. It is similar to `
1233
1237
  return False
1234
1238
  else:
1235
1239
  if not url:
1236
- url,mode = unmode_url(self.current_url)
1240
+ url = self.current_url
1241
+ unmoded_url,mode = unmode_url(url)
1237
1242
  # first we check if url already exists in the file
1238
- with open(list_path,"r") as l_file:
1239
- lines = l_file.readlines()
1240
- l_file.close()
1241
- for l in lines:
1242
- sp = l.split()
1243
- if url in sp:
1244
- if verbose:
1245
- print("%s already in %s."%(url,list))
1246
- return False
1247
- with open(list_path,"a") as l_file:
1248
- l_file.write(self.to_map_line(url))
1249
- l_file.close()
1250
- if verbose:
1251
- print("%s added to %s" %(url,list))
1252
- return True
1243
+ if self.list_has_url(url,list,exact_mode=True):
1244
+ if verbose:
1245
+ print("%s already in %s."%(url,list))
1246
+ return False
1247
+ # If the URL already exists but without a mode, we update the mode
1248
+ # FIXME: this doesn’t take into account the case where you want to remove the mode
1249
+ elif url != unmoded_url and self.list_has_url(unmoded_url,list):
1250
+ self.list_update_url_mode(unmoded_url,list,mode)
1251
+ if verbose:
1252
+ print("%s has updated mode in %s to %s"%(url,list,mode))
1253
+ else:
1254
+ with open(list_path,"a") as l_file:
1255
+ l_file.write(self.to_map_line(url))
1256
+ l_file.close()
1257
+ if verbose:
1258
+ print("%s added to %s" %(url,list))
1259
+ return True
1253
1260
 
1254
1261
  @needs_gi
1255
1262
  def list_add_top(self,list,limit=0,truncate_lines=0):
@@ -1288,8 +1295,14 @@ archives, which is a special historical list limited in size. It is similar to `
1288
1295
  def list_rm_url(self,url,list):
1289
1296
  return self.list_has_url(url,list,deletion=True)
1290
1297
 
1298
+ def list_update_url_mode(self,url,list,mode):
1299
+ return self.list_has_url(url,list,update_mode = mode)
1300
+
1291
1301
  # deletion and has_url are so similar, I made them the same method
1292
- def list_has_url(self,url,list,deletion=False):
1302
+ # deletion : true or false if you want to delete the URL
1303
+ # exact_mode : True if you want to check only for the exact url, not the canonical one
1304
+ # update_mode : a new mode to update the URL
1305
+ def list_has_url(self,url,list,deletion=False, exact_mode=False, update_mode = None):
1293
1306
  list_path = self.list_path(list)
1294
1307
  if list_path:
1295
1308
  to_return = False
@@ -1298,7 +1311,8 @@ archives, which is a special historical list limited in size. It is similar to `
1298
1311
  lf.close()
1299
1312
  to_write = []
1300
1313
  # let’s remove the mode
1301
- url=unmode_url(url)[0]
1314
+ if not exact_mode:
1315
+ url=unmode_url(url)[0]
1302
1316
  for l in lines:
1303
1317
  # we separate components of the line
1304
1318
  # to ensure we identify a complete URL, not a part of it
@@ -1306,15 +1320,27 @@ archives, which is a special historical list limited in size. It is similar to `
1306
1320
  if url not in splitted and len(splitted) > 1:
1307
1321
  current = unmode_url(splitted[1])[0]
1308
1322
  #sometimes, we must remove the ending "/"
1309
- if url == current:
1310
- to_return = True
1311
- elif url.endswith("/") and url[:-1] == current:
1323
+ if url == current or (url.endswith("/") and url[:-1] == current):
1312
1324
  to_return = True
1325
+ if update_mode:
1326
+ new_line = l.replace(current,mode_url(url,update_mode))
1327
+ to_write.append(new_line)
1328
+ elif not deletion:
1329
+ to_write.append(l)
1313
1330
  else:
1314
1331
  to_write.append(l)
1315
- else:
1332
+ elif url in splitted:
1316
1333
  to_return = True
1317
- if deletion :
1334
+ # We update the mode if asked by replacing the old url
1335
+ # by a moded one in the same line
1336
+ if update_mode:
1337
+ new_line = l.replace(url,mode_url(url,update_mode))
1338
+ to_write.append(new_line)
1339
+ elif not deletion:
1340
+ to_write.append(l)
1341
+ else:
1342
+ to_write.append(l)
1343
+ if deletion or update_mode:
1318
1344
  with open(list_path,"w") as lf:
1319
1345
  for l in to_write:
1320
1346
  lf.write(l)
@@ -1588,19 +1614,6 @@ The following lists cannot be removed or frozen but can be edited with "list edi
1588
1614
  else:
1589
1615
  cmd.Cmd.do_help(self, arg)
1590
1616
 
1591
- ### Flight recorder
1592
- def do_blackbox(self, *args):
1593
- """Display contents of flight recorder, showing statistics for the
1594
- current gemini browsing session."""
1595
- lines = []
1596
- # Compute flight time
1597
- now = time.time()
1598
- delta = now - self.log["start_time"]
1599
- hours, remainder = divmod(delta, 3600)
1600
- minutes, seconds = divmod(remainder, 60)
1601
- # Assemble lines
1602
- lines.append(("Patrol duration", "%02d:%02d:%02d" % (hours, minutes, seconds)))
1603
-
1604
1617
  def do_sync(self, line):
1605
1618
  """Synchronize all bookmarks lists and URLs from the to_fetch list.
1606
1619
  - New elements in pages in subscribed lists will be added to tour
@@ -1879,7 +1892,6 @@ def main():
1879
1892
  gc.onecmd(line)
1880
1893
  lists = None
1881
1894
  gc.call_sync(refresh_time=refresh_time,depth=depth,lists=args.url)
1882
- gc.onecmd("blackbox")
1883
1895
  else:
1884
1896
  # We are in the normal mode. First process config file
1885
1897
  torun_queue = read_config(torun_queue,rcfile=args.config_file,interactive=True)
@@ -62,9 +62,15 @@ def xdg(folder="cache"):
62
62
  #if no XDG .local/share and not XDG .config, we use the old config
63
63
  if not os.path.exists(data_home) and os.path.exists(_old_config):
64
64
  _DATA_DIR = _CONFIG_DIR
65
+ ## get _CACHE_PATH from OFFPUNK_CACHE_PATH environment variable
66
+ # if OFFPUNK_CACHE_PATH empty, set default to ~/.cache/offpunk
65
67
  cache_home = os.environ.get('XDG_CACHE_HOME') or\
66
68
  os.path.join(_home,'.cache')
67
- _CACHE_PATH = os.path.join(os.path.expanduser(cache_home),"offpunk/")
69
+ _CACHE_PATH = os.environ.get('OFFPUNK_CACHE_PATH', \
70
+ os.path.join(os.path.expanduser(cache_home),"offpunk/"))
71
+ #Check that the cache path ends with "/"
72
+ if not _CACHE_PATH.endswith("/"):
73
+ _CACHE_PATH += "/"
68
74
  os.makedirs(_CACHE_PATH,exist_ok=True)
69
75
  if folder == "cache" and not UPGRADED:
70
76
  upgrade_cache(_CACHE_PATH)
@@ -48,16 +48,16 @@ else:
48
48
  # -S : do not wrap long lines. Wrapping is done by offpunk, longlines
49
49
  # are there on purpose (surch in asciiart)
50
50
  #--incsearch : incremental search starting rev581
51
- if less_version >= 581:
52
- less_base = "less --incsearch --save-marks -~ -XRfMWiS"
53
- elif less_version >= 572:
54
- less_base = "less --save-marks -XRfMWiS"
55
- else:
56
- less_base = "less -XRfMWiS"
57
- _DEFAULT_LESS = less_base + " \"+''\" %s"
58
- _DEFAULT_CAT = less_base + " -EF %s"
59
-
60
51
  def less_cmd(file, histfile=None,cat=False,grep=None):
52
+ less_prompt = "page %%d/%%D- lines %%lb/%%L - %%Pb\%%"
53
+ if less_version >= 581:
54
+ less_base = "less --incsearch --save-marks -~ -XRfWiS -P \"%s\""%less_prompt
55
+ elif less_version >= 572:
56
+ less_base = "less --save-marks -XRfMWiS"
57
+ else:
58
+ less_base = "less -XRfMWiS"
59
+ _DEFAULT_LESS = less_base + " \"+''\" %s"
60
+ _DEFAULT_CAT = less_base + " -EF %s"
61
61
  if histfile:
62
62
  env = {"LESSHISTFILE": histfile}
63
63
  else:
@@ -280,12 +280,18 @@ def main():
280
280
  opnk will fallback to opening the file with xdg-open. If given an URL as input \
281
281
  instead of a path, opnk will rely on netcache to get the networked content."
282
282
  parser = argparse.ArgumentParser(prog="opnk",description=descri)
283
+ parser.add_argument("--mode", metavar="MODE",
284
+ help="Which mode should be used to render: normal (default), full or source.\
285
+ With HTML, the normal mode try to extract the article.")
283
286
  parser.add_argument("content",metavar="INPUT", nargs="*",
284
287
  default=sys.stdin, help="Path to the file or URL to open")
288
+ parser.add_argument("--cache-validity",type=int, default=0,
289
+ help="maximum age, in second, of the cached version before \
290
+ redownloading a new version")
285
291
  args = parser.parse_args()
286
292
  cache = opencache()
287
293
  for f in args.content:
288
- cache.opnk(f)
294
+ cache.opnk(f,mode=args.mode,validity=args.cache_validity)
289
295
 
290
296
  if __name__ == "__main__":
291
297
  main()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes