diff --git a/sat_frontends/jp/cmd_blog.py b/sat_frontends/jp/cmd_blog.py
index 6e667aa3..4367ba81 100644
--- a/sat_frontends/jp/cmd_blog.py
+++ b/sat_frontends/jp/cmd_blog.py
@@ -18,24 +18,28 @@
# along with this program. If not, see .
-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_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 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_frontends.jp.constants import Const as C
+
+from . import base, cmd_pubsub
__commands__ = ["Blog"]
@@ -84,6 +88,8 @@ MB_KEYS = (
"extra"
)
OUTPUT_OPT_NO_HEADER = "no-header"
+RE_ATTACHMENT_METADATA = re.compile(r"^(?P[a-z_]+)=(?P.*)")
+ALLOWER_ATTACH_MD_KEY = ("desc", "media_type", "external")
async def guessSyntaxFromPath(host, sat_conf, path):
@@ -104,7 +110,7 @@ async def guessSyntaxFromPath(host, sat_conf, path):
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 @@ class BlogPublishCommon(object):
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 @@ class BlogPublishCommon(object):
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 @@ class BlogPublishCommon(object):
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 @@ class BlogPublishCommon(object):
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 @@ class Set(base.CommandBase, BlogPublishCommon):
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 @@ class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit):
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 @@ class Edit(base.CommandBase, BlogPublishCommon, common.BaseEdit):
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