cli (event): update commands following changes in events:
- commands have been update to match changes in bridge method. - arguments have been completely reworked for `create` and `modify` commands, making it possible to fine tune events. - `list` has been removed in favor of `get` which can now display several events at once rel 372
This commit is contained in:
parent
093b9d83bc
commit
4bae534e0f
1 changed files with 278 additions and 101 deletions
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
|
||||
# jp: a SàT command line tool
|
||||
# libervia-cli: Libervia CLI frontend
|
||||
# Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
|
|
@ -18,52 +18,28 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from dateutil import parser as du_parser
|
||||
import calendar
|
||||
import time
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
from sqlalchemy import desc
|
||||
|
||||
from sat.core.i18n import _
|
||||
from sat.core.i18n import _
|
||||
from sat.tools.common.ansi import ANSI as A
|
||||
from sat_frontends.jp.constants import Const as C
|
||||
from sat_frontends.jp import common
|
||||
from sat.tools.common import data_format
|
||||
from sat.tools.common import data_format
|
||||
from sat.tools.common import date_utils
|
||||
from sat.tools.common.ansi import ANSI as A
|
||||
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 sat_frontends.jp.constants import Const as C
|
||||
|
||||
from . import base
|
||||
|
||||
__commands__ = ["Event"]
|
||||
|
||||
OUTPUT_OPT_TABLE = "table"
|
||||
|
||||
# TODO: move date parsing to base, it may be useful for other commands
|
||||
|
||||
|
||||
class List(base.CommandBase):
|
||||
def __init__(self, host):
|
||||
base.CommandBase.__init__(
|
||||
self,
|
||||
host,
|
||||
"list",
|
||||
use_output=C.OUTPUT_LIST_DICT,
|
||||
use_pubsub=True,
|
||||
use_verbose=True,
|
||||
help=_("get list of registered events"),
|
||||
)
|
||||
|
||||
def add_parser_options(self):
|
||||
pass
|
||||
|
||||
async def start(self):
|
||||
try:
|
||||
events = await self.host.bridge.eventsList(
|
||||
self.args.service,
|
||||
self.args.node,
|
||||
self.profile,
|
||||
)
|
||||
except Exception as e:
|
||||
self.disp(f"can't get list of events: {e}", error=True)
|
||||
self.host.quit(C.EXIT_BRIDGE_ERRBACK)
|
||||
else:
|
||||
await self.output(events)
|
||||
self.host.quit()
|
||||
|
||||
|
||||
class Get(base.CommandBase):
|
||||
def __init__(self, host):
|
||||
|
|
@ -71,11 +47,14 @@ class Get(base.CommandBase):
|
|||
self,
|
||||
host,
|
||||
"get",
|
||||
use_output=C.OUTPUT_DICT,
|
||||
use_output=C.OUTPUT_LIST_DICT,
|
||||
use_pubsub=True,
|
||||
pubsub_flags={C.SINGLE_ITEM},
|
||||
pubsub_flags={C.MULTI_ITEMS, C.CACHE},
|
||||
use_verbose=True,
|
||||
help=_("get event data"),
|
||||
extra_outputs={
|
||||
"default": self.default_output,
|
||||
},
|
||||
help=_("get event(s) data"),
|
||||
)
|
||||
|
||||
def add_parser_options(self):
|
||||
|
|
@ -83,91 +62,281 @@ class Get(base.CommandBase):
|
|||
|
||||
async def start(self):
|
||||
try:
|
||||
event_tuple = await self.host.bridge.eventGet(
|
||||
events_data_s = await self.host.bridge.eventsGet(
|
||||
self.args.service,
|
||||
self.args.node,
|
||||
self.args.item,
|
||||
self.args.items,
|
||||
self.getPubsubExtra(),
|
||||
self.profile,
|
||||
)
|
||||
except Exception as e:
|
||||
self.disp(f"can't get event data: {e}", error=True)
|
||||
self.disp(f"can't get events data: {e}", error=True)
|
||||
self.host.quit(C.EXIT_BRIDGE_ERRBACK)
|
||||
else:
|
||||
event_date, event_data = event_tuple
|
||||
event_data["date"] = event_date
|
||||
await self.output(event_data)
|
||||
events_data = data_format.deserialise(events_data_s, type_check=list)
|
||||
await self.output(events_data)
|
||||
self.host.quit()
|
||||
|
||||
def default_output(self, events):
|
||||
nb_events = len(events)
|
||||
for idx, event in enumerate(events):
|
||||
names = event["name"]
|
||||
name = names.get("") or next(iter(names.values()))
|
||||
start = event["start"]
|
||||
start_human = date_utils.date_fmt(
|
||||
start, "medium", tz_info=date_utils.TZ_LOCAL
|
||||
)
|
||||
end = event["end"]
|
||||
self.disp(A.color(
|
||||
A.BOLD, start_human, A.RESET, " ",
|
||||
f"({date_utils.delta2human(start, end)}) ",
|
||||
C.A_HEADER, name
|
||||
))
|
||||
if self.verbosity > 0:
|
||||
descriptions = event.get("descriptions", [])
|
||||
if descriptions:
|
||||
self.disp(descriptions[0]["description"])
|
||||
if idx < (nb_events-1):
|
||||
self.disp("")
|
||||
|
||||
class EventBase(object):
|
||||
|
||||
class CategoryAction(argparse.Action):
|
||||
|
||||
def __init__(self, option_strings, dest, nargs=None, metavar=None, **kwargs):
|
||||
if nargs is not None or metavar is not None:
|
||||
raise ValueError("nargs and metavar must not be used")
|
||||
if metavar is not None:
|
||||
metavar="TERM WIKIDATA_ID LANG"
|
||||
if "--help" in sys.argv:
|
||||
# FIXME: dirty workaround to have correct --help message
|
||||
# argparse doesn't normally allow variable number of arguments beside "+"
|
||||
# and "*", this workaround show METAVAR as 3 arguments were expected, while
|
||||
# we can actuall use 1, 2 or 3.
|
||||
nargs = 3
|
||||
metavar = ("TERM", "[WIKIDATA_ID]", "[LANG]")
|
||||
else:
|
||||
nargs = "+"
|
||||
|
||||
super().__init__(option_strings, dest, metavar=metavar, nargs=nargs, **kwargs)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
categories = getattr(namespace, self.dest)
|
||||
if categories is None:
|
||||
categories = []
|
||||
setattr(namespace, self.dest, categories)
|
||||
|
||||
if not values:
|
||||
parser.error("category values must be set")
|
||||
|
||||
category = {
|
||||
"term": values[0]
|
||||
}
|
||||
|
||||
if len(values) == 1:
|
||||
pass
|
||||
elif len(values) == 2:
|
||||
value = values[1]
|
||||
if value.startswith("Q"):
|
||||
category["wikidata_id"] = value
|
||||
else:
|
||||
category["language"] = value
|
||||
elif len(values) == 3:
|
||||
__, wd, lang = values
|
||||
category["wikidata_id"] = wd
|
||||
category["language"] = lang
|
||||
else:
|
||||
parser.error("Category can't have more than 3 arguments")
|
||||
|
||||
categories.append(category)
|
||||
|
||||
|
||||
class EventBase:
|
||||
def add_parser_options(self):
|
||||
self.parser.add_argument(
|
||||
"-S", "--start", type=base.date_decoder, metavar="TIME_PATTERN",
|
||||
help=_("the start time of the event"))
|
||||
end_group = self.parser.add_mutually_exclusive_group()
|
||||
end_group.add_argument(
|
||||
"-E", "--end", type=base.date_decoder, metavar="TIME_PATTERN",
|
||||
help=_("the time of the end of the event"))
|
||||
end_group.add_argument(
|
||||
"-D", "--duration", help=_("duration of the event"))
|
||||
self.parser.add_argument(
|
||||
"-H", "--head-picture", help="URL to a picture to use as head-picture"
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"-d", "--description", help="plain text description the event"
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"-C", "--category", action=CategoryAction, dest="categories",
|
||||
help="Category of the event"
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"-l", "--location", action="append", nargs="+", metavar="[KEY] VALUE",
|
||||
help="Location metadata"
|
||||
)
|
||||
rsvp_group = self.parser.add_mutually_exclusive_group()
|
||||
rsvp_group.add_argument(
|
||||
"--rsvp", action="store_true", help=_("RSVP is requested"))
|
||||
rsvp_group.add_argument(
|
||||
"--rsvp_json", metavar="JSON", help=_("JSON description of RSVP form"))
|
||||
for node_type in ("invitees", "comments", "blog", "schedule"):
|
||||
self.parser.add_argument(
|
||||
f"--{node_type}",
|
||||
nargs=2,
|
||||
metavar=("JID", "NODE"),
|
||||
help=_("link {node_type} pubsub node").format(node_type=node_type)
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"-a", "--attachment", action="append", dest="attachments",
|
||||
help=_("attach a file")
|
||||
)
|
||||
self.parser.add_argument("--website", help=_("website of the event"))
|
||||
self.parser.add_argument(
|
||||
"--status", choices=["confirmed", "tentative", "cancelled"],
|
||||
help=_("status of the event")
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"-T", "--language", metavar="LANG", action="append", dest="languages",
|
||||
help=_("main languages spoken at the event")
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"--wheelchair", choices=["full", "partial", "no"],
|
||||
help=_("is the location accessible by wheelchair")
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"--external",
|
||||
nargs=3,
|
||||
metavar=("JID", "NODE", "ITEM"),
|
||||
help=_("link to an external event")
|
||||
)
|
||||
|
||||
def get_event_data(self):
|
||||
if self.args.duration is not None:
|
||||
if self.args.start is None:
|
||||
self.parser.error("--start must be send if --duration is used")
|
||||
# if duration is used, we simply add it to start time to get end time
|
||||
self.args.end = base.date_decoder(f"{self.args.start} + {self.args.duration}")
|
||||
|
||||
event = {}
|
||||
if self.args.name is not None:
|
||||
event["name"] = {"": self.args.name}
|
||||
|
||||
if self.args.start is not None:
|
||||
event["start"] = self.args.start
|
||||
|
||||
if self.args.end is not None:
|
||||
event["end"] = self.args.end
|
||||
|
||||
if self.args.head_picture:
|
||||
event["head-picture"] = {
|
||||
"sources": [{
|
||||
"url": self.args.head_picture
|
||||
}]
|
||||
}
|
||||
if self.args.description:
|
||||
event["descriptions"] = [
|
||||
{
|
||||
"type": "text",
|
||||
"description": self.args.description
|
||||
}
|
||||
]
|
||||
if self.args.categories:
|
||||
event["categories"] = self.args.categories
|
||||
if self.args.location is not None:
|
||||
location = {}
|
||||
for location_data in self.args.location:
|
||||
if len(location_data) == 1:
|
||||
location["description"] = location_data[0]
|
||||
else:
|
||||
key, *values = location_data
|
||||
location[key] = " ".join(values)
|
||||
event["locations"] = [location]
|
||||
|
||||
if self.args.rsvp:
|
||||
event["rsvp"] = [{}]
|
||||
elif self.args.rsvp_json:
|
||||
if isinstance(self.args.rsvp_elt, dict):
|
||||
event["rsvp"] = [self.args.rsvp_json]
|
||||
else:
|
||||
event["rsvp"] = self.args.rsvp_json
|
||||
|
||||
for node_type in ("invitees", "comments", "blog", "schedule"):
|
||||
value = getattr(self.args, node_type)
|
||||
if value:
|
||||
service, node = value
|
||||
event[node_type] = {"service": service, "node": node}
|
||||
|
||||
if self.args.attachments:
|
||||
attachments = event["attachments"] = []
|
||||
for attachment in self.args.attachments:
|
||||
attachments.append({
|
||||
"sources": [{"url": attachment}]
|
||||
})
|
||||
|
||||
extra = {}
|
||||
|
||||
for arg in ("website", "status", "languages"):
|
||||
value = getattr(self.args, arg)
|
||||
if value is not None:
|
||||
extra[arg] = value
|
||||
if self.args.wheelchair is not None:
|
||||
extra["accessibility"] = {"wheelchair": self.args.wheelchair}
|
||||
|
||||
if extra:
|
||||
event["extra"] = extra
|
||||
|
||||
if self.args.external:
|
||||
ext_jid, ext_node, ext_item = self.args.external
|
||||
event["external"] = {
|
||||
"jid": ext_jid,
|
||||
"node": ext_node,
|
||||
"item": ext_item
|
||||
}
|
||||
return event
|
||||
|
||||
|
||||
class Create(EventBase, base.CommandBase):
|
||||
def __init__(self, host):
|
||||
super().__init__(
|
||||
host,
|
||||
"create",
|
||||
use_pubsub=True,
|
||||
help=_("create or replace event"),
|
||||
)
|
||||
|
||||
def add_parser_options(self):
|
||||
super().add_parser_options()
|
||||
self.parser.add_argument(
|
||||
"-i",
|
||||
"--id",
|
||||
default="",
|
||||
help=_("ID of the PubSub Item"),
|
||||
)
|
||||
self.parser.add_argument("-d", "--date", type=str, help=_("date of the event"))
|
||||
self.parser.add_argument(
|
||||
"-f",
|
||||
"--field",
|
||||
action="append",
|
||||
nargs=2,
|
||||
dest="fields",
|
||||
metavar=("KEY", "VALUE"),
|
||||
help=_("configuration field to set"),
|
||||
)
|
||||
|
||||
def parseFields(self):
|
||||
return dict(self.args.fields) if self.args.fields else {}
|
||||
|
||||
def parseDate(self):
|
||||
if self.args.date:
|
||||
try:
|
||||
date = int(self.args.date)
|
||||
except ValueError:
|
||||
try:
|
||||
date_time = du_parser.parse(
|
||||
self.args.date, dayfirst=not ("-" in self.args.date)
|
||||
)
|
||||
except ValueError as e:
|
||||
self.parser.error(_("Can't parse date: {msg}").format(msg=e))
|
||||
if date_time.tzinfo is None:
|
||||
date = calendar.timegm(date_time.timetuple())
|
||||
else:
|
||||
date = time.mktime(date_time.timetuple())
|
||||
else:
|
||||
date = -1
|
||||
return date
|
||||
|
||||
|
||||
class Create(EventBase, base.CommandBase):
|
||||
def __init__(self, host):
|
||||
super(Create, self).__init__(
|
||||
host,
|
||||
"create",
|
||||
use_pubsub=True,
|
||||
help=_("create or replace event"),
|
||||
)
|
||||
EventBase.__init__(self)
|
||||
# name is mandatory here
|
||||
self.parser.add_argument("name", help=_("name of the event"))
|
||||
|
||||
async def start(self):
|
||||
fields = self.parseFields()
|
||||
date = self.parseDate()
|
||||
if self.args.start is None:
|
||||
self.parser.error("--start must be set")
|
||||
event_data = self.get_event_data()
|
||||
# we check self.args.end after get_event_data because it may be set there id
|
||||
# --duration is used
|
||||
if self.args.end is None:
|
||||
self.parser.error("--end or --duration must be set")
|
||||
try:
|
||||
node = await self.host.bridge.eventCreate(
|
||||
date,
|
||||
fields,
|
||||
self.args.service,
|
||||
self.args.node,
|
||||
await self.host.bridge.eventCreate(
|
||||
data_format.serialise(event_data),
|
||||
self.args.id,
|
||||
self.args.node,
|
||||
self.args.service,
|
||||
self.profile,
|
||||
)
|
||||
except Exception as e:
|
||||
self.disp(f"can't create event: {e}", error=True)
|
||||
self.host.quit(C.EXIT_BRIDGE_ERRBACK)
|
||||
else:
|
||||
self.disp(_("Event created successfuly on node {node}").format(node=node))
|
||||
self.disp(_("Event created successfuly)"))
|
||||
self.host.quit()
|
||||
|
||||
|
||||
|
|
@ -177,21 +346,24 @@ class Modify(EventBase, base.CommandBase):
|
|||
host,
|
||||
"modify",
|
||||
use_pubsub=True,
|
||||
pubsub_flags={C.NODE},
|
||||
pubsub_flags={C.SINGLE_ITEM},
|
||||
help=_("modify an existing event"),
|
||||
)
|
||||
EventBase.__init__(self)
|
||||
|
||||
def add_parser_options(self):
|
||||
super().add_parser_options()
|
||||
# name is optional here
|
||||
self.parser.add_argument("-N", "--name", help=_("name of the event"))
|
||||
|
||||
async def start(self):
|
||||
fields = self.parseFields()
|
||||
date = 0 if not self.args.date else self.parseDate()
|
||||
event_data = self.get_event_data()
|
||||
try:
|
||||
self.host.bridge.eventModify(
|
||||
await self.host.bridge.eventModify(
|
||||
data_format.serialise(event_data),
|
||||
self.args.item,
|
||||
self.args.service,
|
||||
self.args.node,
|
||||
self.args.id,
|
||||
date,
|
||||
fields,
|
||||
self.profile,
|
||||
)
|
||||
except Exception as e:
|
||||
|
|
@ -209,28 +381,32 @@ class InviteeGet(base.CommandBase):
|
|||
"get",
|
||||
use_output=C.OUTPUT_DICT,
|
||||
use_pubsub=True,
|
||||
pubsub_flags={C.NODE},
|
||||
pubsub_flags={C.SINGLE_ITEM},
|
||||
use_verbose=True,
|
||||
help=_("get event attendance"),
|
||||
)
|
||||
|
||||
def add_parser_options(self):
|
||||
self.parser.add_argument(
|
||||
"-j", "--jid", default="", help=_("bare jid of the invitee")
|
||||
"-j", "--jid", action="append", dest="jids", default=[],
|
||||
help=_("only retrieve RSVP from those JIDs")
|
||||
)
|
||||
|
||||
async def start(self):
|
||||
try:
|
||||
event_data = await self.host.bridge.eventInviteeGet(
|
||||
event_data_s = await self.host.bridge.eventInviteeGet(
|
||||
self.args.service,
|
||||
self.args.node,
|
||||
self.args.jid,
|
||||
self.args.item,
|
||||
self.args.jids,
|
||||
"",
|
||||
self.profile,
|
||||
)
|
||||
except Exception as e:
|
||||
self.disp(f"can't get event data: {e}", error=True)
|
||||
self.host.quit(C.EXIT_BRIDGE_ERRBACK)
|
||||
else:
|
||||
event_data = data_format.deserialise(event_data_s)
|
||||
await self.output(event_data)
|
||||
self.host.quit()
|
||||
|
||||
|
|
@ -240,9 +416,8 @@ class InviteeSet(base.CommandBase):
|
|||
super(InviteeSet, self).__init__(
|
||||
host,
|
||||
"set",
|
||||
use_output=C.OUTPUT_DICT,
|
||||
use_pubsub=True,
|
||||
pubsub_flags={C.NODE},
|
||||
pubsub_flags={C.SINGLE_ITEM},
|
||||
help=_("set event attendance"),
|
||||
)
|
||||
|
||||
|
|
@ -258,12 +433,14 @@ class InviteeSet(base.CommandBase):
|
|||
)
|
||||
|
||||
async def start(self):
|
||||
# TODO: handle RSVP with XMLUI in a similar way as for `ad-hoc run`
|
||||
fields = dict(self.args.fields) if self.args.fields else {}
|
||||
try:
|
||||
self.host.bridge.eventInviteeSet(
|
||||
self.args.service,
|
||||
self.args.node,
|
||||
fields,
|
||||
self.args.item,
|
||||
data_format.serialise(fields),
|
||||
self.profile,
|
||||
)
|
||||
except Exception as e:
|
||||
|
|
@ -570,7 +747,7 @@ class Invitee(base.CommandBase):
|
|||
|
||||
|
||||
class Event(base.CommandBase):
|
||||
subcommands = (List, Get, Create, Modify, Invitee)
|
||||
subcommands = (Get, Create, Modify, Invitee)
|
||||
|
||||
def __init__(self, host):
|
||||
super(Event, self).__init__(
|
||||
|
|
|
|||
Loading…
Reference in a new issue