diff --git a/libervia/cli/base.py b/libervia/cli/base.py index 06d7b04a..02c59089 100644 --- a/libervia/cli/base.py +++ b/libervia/cli/base.py @@ -439,7 +439,7 @@ class LiberviaCli: "-D", "--current", action="store_true", help=_("load current draft") ) 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): diff --git a/libervia/cli/cmd_blog.py b/libervia/cli/cmd_blog.py index 8cbc17fc..b816caf0 100644 --- a/libervia/cli/cmd_blog.py +++ b/libervia/cli/cmd_blog.py @@ -143,11 +143,31 @@ class AltLinkAction(argparse.Action): alt_links.append(alt_link) -class BlogPublishCommon: - """handle common option for publising commands (Set and Edit)""" +class CommentsFromCommon: + """handle the --comments-from option""" - async def get_existing_item(self, service, node, item): - """Fetch the data of an existing item from the pubsub service. + def add_comments_from_options(self): + """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 node: identifier of the pubsub node (None for the default one) @@ -163,6 +183,76 @@ class BlogPublishCommon: ) 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): """Retrieve current_syntax @@ -191,6 +281,7 @@ class BlogPublishCommon: return self.args.syntax 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", @@ -382,6 +473,7 @@ class Set(base.CommandBase, BlogPublishCommon): async def start(self): self.current_syntax = await self.get_current_syntax() + await self.resolve_comments_from() self.pubsub_item = self.args.item if self.args.replace and not self.pubsub_item: 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 # (tags, attachments, publication date, etc.) are kept try: - existing = await self.get_existing_item( + existing = await self.get_item( self.args.service, self.args.node, self.pubsub_item ) 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") else: 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) -class Get(base.CommandBase): +class Get(base.CommandBase, CommentsFromCommon): TEMPLATE = "blog/articles.html" def __init__(self, host): @@ -443,7 +535,8 @@ class Get(base.CommandBase): ) 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( "-k", "--key", @@ -607,6 +700,7 @@ class Get(base.CommandBase): print(("\n" + sep + "\n")) async def start(self): + await self.resolve_comments_from() try: mb_data = data_format.deserialise( 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[""]) 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: content = item["content_xhtml"] @@ -812,6 +906,13 @@ class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit): # if there are user defined extension, we use them SYNTAX_EXT.update(self.host.cli_settings.syntax_ext) 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,