diff sat_frontends/jp/cmd_blog.py @ 4024:4941cd102f93

jp (blog): new `--attachment` argument to attach files or external data to a blog post
author Goffi <goffi@goffi.org>
date Thu, 23 Mar 2023 15:43:48 +0100
parents 570254d5a798
children 524856bd7b19
line wrap: on
line diff
--- a/sat_frontends/jp/cmd_blog.py	Thu Mar 23 15:42:21 2023 +0100
+++ b/sat_frontends/jp/cmd_blog.py	Thu Mar 23 15:43:48 2023 +0100
@@ -18,24 +18,28 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
-import json
-import sys
-import os.path
-import os
-import tempfile
-import subprocess
 import asyncio
 from asyncio.subprocess import DEVNULL
+from configparser import NoOptionError, NoSectionError
+import json
+import os
+import os.path
 from pathlib import Path
-from . import base, cmd_pubsub
+import re
+import subprocess
+import sys
+import tempfile
+from urllib.parse import urlparse
+
 from sat.core.i18n import _
-from sat_frontends.jp.constants import Const as C
+from sat.tools import config
+from sat.tools.common import uri
+from sat.tools.common import data_format
+from sat.tools.common.ansi import ANSI as A
 from sat_frontends.jp import common
-from sat.tools.common.ansi import ANSI as A
-from sat.tools.common import uri
-from sat.tools import config
-from configparser import NoSectionError, NoOptionError
-from sat.tools.common import data_format
+from sat_frontends.jp.constants import Const as C
+
+from . import base, cmd_pubsub
 
 __commands__ = ["Blog"]
 
@@ -84,6 +88,8 @@
     "extra"
 )
 OUTPUT_OPT_NO_HEADER = "no-header"
+RE_ATTACHMENT_METADATA = re.compile(r"^(?P<key>[a-z_]+)=(?P<value>.*)")
+ALLOWER_ATTACH_MD_KEY = ("desc", "media_type", "external")
 
 
 async def guessSyntaxFromPath(host, sat_conf, path):
@@ -104,7 +110,7 @@
     return await host.bridge.getParamA("Syntax", "Composition", "value", host.profile)
 
 
-class BlogPublishCommon(object):
+class BlogPublishCommon:
     """handle common option for publising commands (Set and Edit)"""
 
     async def get_current_syntax(self):
@@ -148,6 +154,16 @@
             help=_("language of the item (ISO 639 code)"),
         )
 
+        self.parser.add_argument(
+            "-a",
+            "--attachment",
+            dest="attachments",
+            nargs="+",
+            help=_(
+                "attachment in the form URL [metadata_name=value]"
+            )
+        )
+
         comments_group = self.parser.add_mutually_exclusive_group()
         comments_group.add_argument(
             "-C",
@@ -192,7 +208,7 @@
             help=_("cryptographically sign the blog post")
         )
 
-    async def setMbDataContent(self, content, mb_data):
+    async def set_mb_data_content(self, content, mb_data):
         if self.default_syntax_used:
             # default syntax has been used
             mb_data["content_rich"] = content
@@ -203,7 +219,51 @@
                 content, self.current_syntax, SYNTAX_XHTML, False, self.profile
             )
 
-    def setMbDataFromArgs(self, mb_data):
+    def handle_attachments(self, mb_data: dict) -> None:
+        """Check, validate and add attachments to mb_data"""
+        if self.args.attachments:
+            attachments = []
+            attachment = {}
+            for arg in self.args.attachments:
+                m = RE_ATTACHMENT_METADATA.match(arg)
+                if m is None:
+                    # we should have an URL
+                    url_parsed = urlparse(arg)
+                    if url_parsed.scheme not in ("http", "https"):
+                        self.parser.error(
+                            "invalid URL in --attachment (only http(s) scheme is "
+                            f" accepted): {arg}"
+                        )
+                    if attachment:
+                        # if we hae a new URL, we have a new attachment
+                        attachments.append(attachment)
+                        attachment = {}
+                    attachment["url"] = arg
+                else:
+                    # we should have a metadata
+                    if "url" not in attachment:
+                        self.parser.error(
+                            "you must to specify an URL before any metadata in "
+                            "--attachment"
+                        )
+                    key = m.group("key")
+                    if key not in ALLOWER_ATTACH_MD_KEY:
+                        self.parser.error(
+                            f"invalid metadata key in --attachment: {key!r}"
+                        )
+                    value = m.group("value").strip()
+                    if key == "external":
+                        if not value:
+                            value=True
+                        else:
+                            value = C.bool(value)
+                    attachment[key] = value
+            if attachment:
+                attachments.append(attachment)
+            if attachments:
+                mb_data.setdefault("extra", {})["attachments"] = attachments
+
+    def set_mb_data_from_args(self, mb_data):
         """set microblog metadata according to command line options
 
         if metadata already exist, it will be overwritten
@@ -222,6 +282,7 @@
             mb_data["signed"] = True
         if self.args.encrypt_for:
             mb_data["encrypted_for"] = {"targets": self.args.encrypt_for}
+        self.handle_attachments(mb_data)
 
 
 class Set(base.CommandBase, BlogPublishCommon):
@@ -243,11 +304,11 @@
         self.current_syntax = await self.get_current_syntax()
         self.pubsub_item = self.args.item
         mb_data = {}
-        self.setMbDataFromArgs(mb_data)
+        self.set_mb_data_from_args(mb_data)
         if self.pubsub_item:
             mb_data["id"] = self.pubsub_item
         content = sys.stdin.read()
-        await self.setMbDataContent(content, mb_data)
+        await self.set_mb_data_content(content, mb_data)
 
         try:
             item_id = await self.host.bridge.mbSend(
@@ -533,7 +594,7 @@
             except KeyError:
                 pass
                 # and override metadata with command-line arguments
-        self.setMbDataFromArgs(mb_data)
+        self.set_mb_data_from_args(mb_data)
 
         if self.args.no_publish:
             mb_data["publish"] = False
@@ -597,7 +658,7 @@
         await asyncio.gather(*coroutines)
 
     async def publish(self, content, mb_data):
-        await self.setMbDataContent(content, mb_data)
+        await self.set_mb_data_content(content, mb_data)
 
         if self.pubsub_item:
             mb_data["id"] = self.pubsub_item