Offpunk has a feature where any updated links from a subscribed list will be added to tour on sync. I expanded on that feature by making it possible to parameterize #subscribe with a list name like #subscribe(some_list_name) to the result of the links getting added to that list, with just the one link to that list getting added to tour.
See also another patch for introducing slash-containing list names (as directory hierarchy underneath): Slash-hierarchical list names — my draft implementation for Offpunk
From a4df30bc796c6c8d197d6cc79f4d8e665e07f4a2 Mon Sep 17 00:00:00 2001
Date: Thu, 29 Jan 2026 19:28:37 +0100
Subject: [PATCH] feature: Subscribe into #subscribe(into-another-list)
If user specifies an additional parameter to the subscribe subcommand,
it will be mentioned as a reference to another list, to which on sync
the links are to be added instead. Then, list URI link is added to the
tour.
---
offpunk.py | 112 +++++++++++++++++++++++++++++++++++++----------------
1 file changed, 79 insertions(+), 33 deletions(-)
diff --git a/offpunk.py b/offpunk.py
index eeadc42..a03edbb 100755
--- a/offpunk.py
+++ b/offpunk.py
@@ -14,6 +14,7 @@ import os.path
import shutil
import sys
import time
+import typing
import urllib.parse
import gettext
@@ -1889,9 +1890,9 @@ Use "view XX" where XX is a number to view information about link XX.
print(_("Removed from %s") % l)
self.list_add_line(args[0])
- def list_lists(self):
+ def list_lists(self) -> list[str]:
listdir = os.path.join(xdg("data"), "lists")
- to_return = []
+ to_return: list[str] = []
if os.path.exists(listdir):
lists = os.listdir(listdir)
if len(lists) > 0:
@@ -1904,13 +1905,26 @@ Use "view XX" where XX is a number to view information about link XX.
def list_has_status(self, list, status):
path = self.list_path(list)
- toreturn = False
+ toreturn: str | bool = False
if path:
with open(path) as f:
line = f.readline().strip()
f.close()
- if line.startswith("#") and status in line:
- toreturn = True
+ toreturn = self._list_line_has_status(line, status)
+ return toreturn
+
+ @staticmethod
+ def _list_line_has_status(line: str, status: str) -> bool:
+ toreturn: str | bool = False
+ pos = line.find(status)
+ if line.startswith("#") and pos != -1:
+ toreturn = True
+ pos += len(status)
+ if line[pos] == "(":
+ pos += 1
+ end = line.find(")", pos)
+ if end != -1:
+ toreturn = line[pos:end]
return toreturn
def list_is_subscribed(self, list):
@@ -1925,21 +1939,31 @@ Use "view XX" where XX is a number to view information about link XX.
# This modify the status of a list to one of :
# normal, frozen, subscribed
# action is either #frozen, #subscribed or None
- def list_modify(self, list, action=None):
- path = self.list_path(list)
+ def list_modify(self, list_name, action=None,
+ argument: str | None = None):
+ path = self.list_path(list_name)
with open(path) as f:
lines = f.readlines()
f.close()
if lines[0].strip().startswith("#"):
first_line = lines.pop(0).strip("\n")
else:
- first_line = "# %s " % list
- first_line = first_line.replace("#subscribed", "").replace("#frozen", "")
+ first_line = "# %s " % list_name
+ for such_status in ("#subscribed", "#frozen"):
+ haz_status = self._list_line_has_status(first_line, such_status)
+ if haz_status:
+ # noinspection PySimplifyBooleanCheck
+ if haz_status != True:
+ first_line = first_line.replace(f"{such_status}({haz_status})", "")
+ else:
+ first_line = first_line.replace(such_status, "")
if action:
+ if argument:
+ action = f"{action}({argument})"
first_line += " " + action
- print(_("List %s has been marked as %s") % (list, action))
+ print(_("List %s has been marked as %s") % (list_name, action))
else:
- print(_("List %s is now a normal list") % list)
+ print(_("List %s is now a normal list") % list_name)
first_line += "\n"
lines.insert(0, first_line)
with open(path, "w") as f:
@@ -2062,7 +2086,8 @@ Use "view XX" where XX is a number to view information about link XX.
action = "#frozen"
else:
action = None
- self.list_modify(args[1], action=action)
+ self.list_modify(args[1], action=action,
+ argument=(args[2] if len(args) > 2 else None))
else:
print(_("A valid list name is required after %s") % args[0])
elif args[0] == "help":
@@ -2125,7 +2150,7 @@ Use "view XX" where XX is a number to view information about link XX.
validity = 0
self.call_sync(refresh_time=validity)
- def call_sync(self, refresh_time=0, depth=1, lists=None):
+ def call_sync(self, refresh_time=0, depth=1, lists: list[str] | None = None):
# fetch_url is the core of the sync algorithm.
# It takes as input :
# - an URL to be fetched
@@ -2134,26 +2159,40 @@ Use "view XX" where XX is a number to view information about link XX.
# being refreshed (0 = never refreshed if it already exists)
# - savetotour : if True, newly cached items are added to tour
def add_to_tour(url):
+ add_to_list(url, "tour")
+ def add_to_list(url, list_name: str):
if url and netcache.is_cache_valid(url):
- toprint = _(" -> adding to tour: %s") % url
+ toprint = _(f" -> adding to %s: %s") % (list_name, url)
width = term_width() - 1
toprint = toprint[:width]
toprint += " " * (width - len(toprint))
print(toprint)
- self.list_add_line("tour", url=url, verbose=False)
- return True
- else:
- return False
+ return self.list_add_line(list_name, url=url, verbose=False)
+ return False
def fetch_url(
- url, depth=0, validity=0, savetotour=False, count=[0, 0], strin="",
+ url, depth=0, validity=0,
+ savetotour: str | bool = False,
+ count=[0, 0], strin="",
force_large_download=False
- ):
- # savetotour = True will save to tour newly cached content
- # else, do not save to tour
- # regardless of valitidy
+ ) -> str | None:
+ """
+
+ :param url:
+ :param depth:
+ :param validity:
+ :param savetotour: savetotour = True will save to tour newly cached content
+ savetotour = <list name> will save to the list and return its name
+ else, do not save to tour
+ regardless of valitidy
+ :param count:
+ :param strin:
+ :param force_large_download:
+ :return: savetotour in case of savetotour being truthy but not true and something saved (implied: list name), else None
+ """
+ #
if not url:
- return
+ return None
if not netcache.is_cache_valid(url, validity=validity):
if strin != "":
endline = "\r"
@@ -2173,7 +2212,11 @@ Use "view XX" where XX is a number to view information about link XX.
if savetotour and isnew and netcache.is_cache_valid(url):
# we add to the next tour only if we managed to cache
# the ressource
- add_to_tour(url)
+ match savetotour:
+ case True: add_to_tour(url)
+ case _:
+ add_to_list(url, savetotour)
+ return savetotour
# Now, recursive call, even if we didn’t refresh the cache
# This recursive call is impacting performances a lot but is needed
# For the case when you add a address to a list to read later
@@ -2206,6 +2249,7 @@ Use "view XX" where XX is a number to view information about link XX.
count=subcount,
strin=substri,
)
+ return None
def fetch_list(
list, validity=0, depth=1, tourandremove=False, tourchildren=False,
@@ -2218,14 +2262,15 @@ Use "view XX" where XX is a number to view information about link XX.
for l in links:
counter += 1
# If cache for a link is newer than the list
- fetch_url(
+ if fetch_url(
l,
depth=depth,
validity=validity,
savetotour=tourchildren,
count=[counter, end],
force_large_download=force_large_download
- )
+ ):
+ add_to_tour(f"list://{tourchildren}")
if tourandremove:
if add_to_tour(l):
self.list_rm_url(l, list)
@@ -2235,23 +2280,24 @@ Use "view XX" where XX is a number to view information about link XX.
lists = self.list_lists()
# We will fetch all the lists except "archives" and "history"
# We keep tour for the last round
- subscriptions = []
+ subscriptions: list[tuple[str, str | bool]] = []
normal_lists = []
fridge = []
for l in lists:
# only try existing lists
if l in self.list_lists():
if not self.list_is_system(l):
- if self.list_is_frozen(l):
+ sub: str | bool = self.list_is_subscribed(l)
+ if sub:
+ subscriptions.append((l, sub))
+ elif self.list_is_frozen(l):
fridge.append(l)
- elif self.list_is_subscribed(l):
- subscriptions.append(l)
else:
normal_lists.append(l)
# We start with the "subscribed" as we need to find new items
starttime = int(time.time())
- for l in subscriptions:
- fetch_list(l, validity=refresh_time, depth=depth, tourchildren=True)
+ for (l, sub) in subscriptions:
+ fetch_list(l, validity=refresh_time, depth=depth, tourchildren=sub)
# Then the to_fetch list (item are removed from the list after fetch)
# We fetch regarless of the refresh_time
if "to_fetch" in lists:
--
2.52.0
Comments on “Subscription into list rather than tour — Offpunk draft feature patch”