cli (blog): add --comments-from to target an item comments node:

- add `--comments-from <item_id>` and `--comments-from-last` to automatically retrieve the
  comments node of a blog post.
- ⚠️`--draft-path` short option moved from `-F` to `-d` (so `-F` can be used for
  `--comments-from`).
This commit is contained in:
Goffi 2026-10-27 21:19:59 +01:00
commit 25f1aa9178
2 changed files with 111 additions and 10 deletions

View file

@ -439,7 +439,7 @@ class LiberviaCli:
"-D", "--current", action="store_true", help=_("load current draft") "-D", "--current", action="store_true", help=_("load current draft")
) )
draft_group.add_argument( draft_group.add_argument(
"-F", "--draft-path", type=Path, help=_("path to a draft file to retrieve") "-d", "--draft-path", type=Path, help=_("path to a draft file to retrieve")
) )
def make_pubsub_group(self, flags, defaults): def make_pubsub_group(self, flags, defaults):

View file

@ -143,11 +143,31 @@ class AltLinkAction(argparse.Action):
alt_links.append(alt_link) alt_links.append(alt_link)
class BlogPublishCommon: class CommentsFromCommon:
"""handle common option for publising commands (Set and Edit)""" """handle the --comments-from option"""
async def get_existing_item(self, service, node, item): def add_comments_from_options(self):
"""Fetch the data of an existing item from the pubsub service. """Add the --comments-from options to the parser"""
group = self.parser.add_mutually_exclusive_group()
group.add_argument(
"-F",
"--comments-from",
nargs="?",
const="",
default=None,
metavar="PARENT_ITEM_ID",
help=_(
"target the comments node of the given item (or URL item if provided)"
),
)
group.add_argument(
"--comments-from-last",
action="store_true",
help=_("target the comments node of the last item of the node"),
)
async def get_item(self, service, node, item):
"""Fetch an existing item from a pubsub service
@param service: JID of the pubsub service (None for PEP) @param service: JID of the pubsub service (None for PEP)
@param node: identifier of the pubsub node (None for the default one) @param node: identifier of the pubsub node (None for the default one)
@ -163,6 +183,76 @@ class BlogPublishCommon:
) )
return mb_data["items"][0] return mb_data["items"][0]
def get_url_item(self):
"""Return the item ID from the -u URL, if any
@return: item ID from the URL, None if there is no URL or no item in it
"""
url = self.args.pubsub_url
if not url:
return None
if url.startswith("http"):
url = self.host.get_xmpp_uri_from_http(url)
return uri.parse_xmpp_uri(url).get("item")
def resolve_comments_args(self):
"""Adjust the parsed args for the --comments-from option
The item from the -u URL is the parent item, not an item to process: the base
class merged it in the item options, we retrieve it here.
"""
self.args.url_item = self.get_url_item()
if self.args.comments_from == "" and not self.args.url_item:
self.parser.error(
_(
"--comments-from without item ID can only be used "
"with a -u URL containing an item"
)
)
if self.args.url_item:
if hasattr(self.args, "item"):
if self.args.item == self.args.url_item:
self.args.item = ""
elif self.args.items == [self.args.url_item]:
self.args.items = []
async def resolve_comments_from(self):
"""Resolve the comments node and update the service and node args
The service/node options then locate the parent item, not the comments node.
"""
if self.args.comments_from is None and not self.args.comments_from_last:
return
self.resolve_comments_args()
self.args.service, self.args.node = await self.resolve_comments_target()
async def resolve_comments_target(self):
"""Return the (service, node) of the parent item's comments node."""
if self.args.comments_from_last:
parent_item = None
else:
parent_item = self.args.comments_from or self.args.url_item
try:
parent = await self.get_item(self.args.service, self.args.node, parent_item)
except BridgeException as e:
if e.classname == "NotFound":
self.disp(f"parent item {parent_item!r} not found", error=True)
self.host.quit(C.EXIT_NOT_FOUND)
self.disp(f"can't get parent item: {e}", error=True)
self.host.quit(C.EXIT_BRIDGE_ERRBACK)
comments = parent.get("comments")
if not comments:
self.disp(
"parent item has no comments node (comments disabled?)",
error=True,
)
self.host.quit(C.EXIT_DATA_ERROR)
return comments[0]["service"], comments[0]["node"]
class BlogPublishCommon(CommentsFromCommon):
"""handle common option for publising commands (Set and Edit)"""
async def get_current_syntax(self): async def get_current_syntax(self):
"""Retrieve current_syntax """Retrieve current_syntax
@ -191,6 +281,7 @@ class BlogPublishCommon:
return self.args.syntax return self.args.syntax
def add_parser_options(self): def add_parser_options(self):
self.add_comments_from_options()
self.parser.add_argument("-T", "--title", help=_("title of the item")) self.parser.add_argument("-T", "--title", help=_("title of the item"))
self.parser.add_argument( self.parser.add_argument(
"-t", "-t",
@ -382,6 +473,7 @@ class Set(base.CommandBase, BlogPublishCommon):
async def start(self): async def start(self):
self.current_syntax = await self.get_current_syntax() self.current_syntax = await self.get_current_syntax()
await self.resolve_comments_from()
self.pubsub_item = self.args.item self.pubsub_item = self.args.item
if self.args.replace and not self.pubsub_item: if self.args.replace and not self.pubsub_item:
self.parser.error("--replace can only be used with -i") self.parser.error("--replace can only be used with -i")
@ -390,11 +482,11 @@ class Set(base.CommandBase, BlogPublishCommon):
# start from the existing item data, so fields not given on the command line # start from the existing item data, so fields not given on the command line
# (tags, attachments, publication date, etc.) are kept # (tags, attachments, publication date, etc.) are kept
try: try:
existing = await self.get_existing_item( existing = await self.get_item(
self.args.service, self.args.node, self.pubsub_item self.args.service, self.args.node, self.pubsub_item
) )
except BridgeException as e: except BridgeException as e:
if e.condition == "item-not-found": if e.classname == "NotFound":
self.disp(f"item {self.pubsub_item!r} not found, creating a new one") self.disp(f"item {self.pubsub_item!r} not found, creating a new one")
else: else:
self.disp(f"can't get item: {e}", error=True) self.disp(f"can't get item: {e}", error=True)
@ -425,7 +517,7 @@ class Set(base.CommandBase, BlogPublishCommon):
self.host.quit(C.EXIT_OK) self.host.quit(C.EXIT_OK)
class Get(base.CommandBase): class Get(base.CommandBase, CommentsFromCommon):
TEMPLATE = "blog/articles.html" TEMPLATE = "blog/articles.html"
def __init__(self, host): def __init__(self, host):
@ -443,7 +535,8 @@ class Get(base.CommandBase):
) )
def add_parser_options(self): def add_parser_options(self):
#  TODO: a key(s) argument to select keys to display self.add_comments_from_options()
# TODO: a key(s) argument to select keys to display
self.parser.add_argument( self.parser.add_argument(
"-k", "-k",
"--key", "--key",
@ -607,6 +700,7 @@ class Get(base.CommandBase):
print(("\n" + sep + "\n")) print(("\n" + sep + "\n"))
async def start(self): async def start(self):
await self.resolve_comments_from()
try: try:
mb_data = data_format.deserialise( mb_data = data_format.deserialise(
await self.host.bridge.mb_get( await self.host.bridge.mb_get(
@ -777,7 +871,7 @@ class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit):
return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT[""]) return SYNTAX_EXT.get(self.current_syntax, SYNTAX_EXT[""])
async def get_item_data(self, service, node, item): async def get_item_data(self, service, node, item):
item = await self.get_existing_item(service, node, item) item = await self.get_item(service, node, item)
try: try:
content = item["content_xhtml"] content = item["content_xhtml"]
@ -812,6 +906,13 @@ class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit):
# if there are user defined extension, we use them # if there are user defined extension, we use them
SYNTAX_EXT.update(self.host.cli_settings.syntax_ext) SYNTAX_EXT.update(self.host.cli_settings.syntax_ext)
self.current_syntax = await self.get_current_syntax() self.current_syntax = await self.get_current_syntax()
if self.args.comments_from is not None or self.args.comments_from_last:
self.resolve_comments_args()
if not self.args.item and not self.args.last_item:
self.parser.error(
_("edit with --comments-from requires -i (comment ID) or -L")
)
self.args.service, self.args.node = await self.resolve_comments_target()
( (
self.pubsub_service, self.pubsub_service,