summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Brox <tobias@redpill-linpro.com>2022-04-18 10:07:34 +0200
committerTobias Brox <tobias@redpill-linpro.com>2022-04-18 10:07:34 +0200
commit36fbbe2654d2f73a7583d9538787fd807b781b0e (patch)
treea58a786ddc4575c6c948e2f2f1cc343435d18462
parent4111bcda1bd37df518153273fc5c8add3b684134 (diff)
downloadcalendar-cli-36fbbe2654d2f73a7583d9538787fd807b781b0e.zip
Work in progress on new cli
(I was working on this several months ago - then I got interrupted, forgot to commit, never got time to look back)
-rwxr-xr-xcal.py171
-rwxr-xr-xtests/test_calendar-cli.sh8
-rwxr-xr-xtests/tests.sh17
3 files changed, 160 insertions, 36 deletions
diff --git a/cal.py b/cal.py
index f1b9b10..222a2f7 100755
--- a/cal.py
+++ b/cal.py
@@ -38,8 +38,18 @@ import caldav
## See https://click.palletsprojects.com/en/8.0.x/api/#click.ParamType and
## /usr/lib/*/site-packages/click/types.py on how to do this.
-## TODO ...
+## TODO: maybe find those attributes through the icalendar library?
+attr_txt_one = ['location', 'description', 'geo', 'organizer', 'summary']
+attr_txt_many = ['categories', 'comment', 'contact', 'resources']
+
+## TODO ... (and should be moved somewhere else)
def _parse_timespec(timespec):
+ """
+ will parse a timespec and return two timestamps. timespec can be
+ in ISO8601 interval format, as format 1, 2 or 3 as described at
+ https://en.wikipedia.org/wiki/ISO_8601#Time_intervals - or it can
+ be on calendar-cli format (i.e. 2021-01-08 15:00:00+1h)
+ """
raise NotImplementedError()
@click.group()
@@ -58,7 +68,6 @@ def cli(ctx, **kwargs):
This command will eventually replace calendar-cli.
"""
-
## The cli function will prepare a context object, a dict containing the
## caldav_client, principal and calendar
@@ -90,31 +99,123 @@ def test(ctx):
"""
click.echo("Seems like everything is OK")
-attr_txt_one = ['location', 'description', 'geo', 'organizer', 'summary']
-attr_txt_many = ['categories', 'comment', 'contact', 'resources']
-
-def set_attr_options(func):
+def _set_attr_options(func):
+ """
+ decorator that will add options --set-categories, --set-description etc
+ """
for foo in attr_txt_one:
- func = click.option()
+ func = click.option(f"--set-{foo}", help=f"Set ical attribute {foo}")(func)
+ for foo in attr_txt_many:
+ func = click.option(f"--set-{foo}", help=f"Set ical attribute {foo}", multiple=True)(func)
+ return func
@cli.group()
+@click.option('--all/--none', default=None, help='Select all (or none) of the objects. Overrides all other selection options.')
+@click.option('--uid', multiple=True, help='select an object with a given uid (or select more object with given uids)')
+@click.option('--abort-on-missing-uid/--ignore-missing-uid', default=False, help='Abort if (one or more) uids are not found (default: silently ignore missing uids)')
+@click.pass_context
+def select(ctx, all, uid, abort_on_missing_uid, **kwargs):
+ """
+ select/search/filter tasks/events, for listing/editing/deleting, etc
+ """
+ objs = []
+ ctx.obj['objs'] = objs
+
+ ## TODO: move all search/filter/select logic to caldav library?
+
+ ## handle all/none options
+ if all is False: ## means --none.
+ return
+ if all:
+ for c in ctx.obj['calendars']:
+ objs.append(c.objects)
+
+ ## uid(s)
+ missing_uids = []
+ for uid_ in uid:
+ cnt = 0
+ for c in ctx.obj['calendars']:
+ try:
+ objs.append(c.object_by_uid(uid_))
+ cnt += 1
+ except caldav.error.NotFoundError:
+ pass
+ if not cnt:
+ missing_uids.append(uid_)
+ if abort_on_missing_uid and missing_uids:
+ raise click.Abort(f"Did not find the following uids in any calendars: {missing_uids}")
+
+@select.command()
+@click.pass_context
+def list(ctx, **kwargs):
+ """
+ print out a list of tasks/events/journals
+ """
+ raise NotImplementedError()
+
+@select.command()
+@click.option('--multi-delete/--no-multi-delete', default=None, help="Delete multiple things without confirmation prompt")
+@click.pass_context
+def delete(ctx, multi_delete, **kwargs):
+ """
+ delete the selected item(s)
+ """
+ objs = ctx.obj['objs']
+ if multi_delete is None and len(objs)>1:
+ multi_delete = click.confirm(f"OK to delete {len(objs)} items?")
+ if len(objs)>1 and not multi_delete:
+ raise click.Abort(f"Not going to delete {len(objs)} items")
+ for obj in objs:
+ obj.delete()
+
+@select.command()
+@click.pass_context
+def complete(ctx, **kwargs):
+ raise NotImplementedError()
+
+@select.command()
+@click.pass_context
+def calculate_panic_time(ctx, **kwargs):
+ raise NotImplementedError()
+
+@select.command()
+@click.pass_context
+def sum_hours(ctx, **kwargs):
+ raise NotImplementedError()
+
+## TODO: all combinations of --first-calendar, --no-first-calendar, --multi-add, --no-multi-add should be tested
+@cli.group()
@click.option('-l', '--add-ical-line', multiple=True, help="extra ical data to be injected")
-@click.option('--multi-add/--no-multi-add', default=False, help="Add things to multiple calendars")
-@click.option('--first-calendar/--no-first-calendar', default=False, help="Add things to the first given calendar")
+@click.option('--multi-add/--no-multi-add', default=None, help="Add things to multiple calendars")
+@click.option('--first-calendar/--no-first-calendar', default=None, help="Add things only to the first calendar found")
+@_set_attr_options
@click.pass_context
def add(ctx, **kwargs):
- if (len(ctx.obj['calendars'])>1 and
- not kwargs['multi_add'] and
- not click.confirm(f"Multiple calendars given. Do you want to duplicate to {len(ctx.obj['calendars'])} calendars? (tip: use option --multi-add to avoid this prompt in the future)")):
+ """
+ Save new objects on calendar(s)
+ """
+ if len(ctx.obj['calendars'])>1 and kwargs['multi_add'] is False:
+ raise click.Abort("Giving up: Multiple calendars given, but --no-multi-add is given")
+ ## TODO: crazy-long if-conditions can be refactored - see delete on how it's done there
+ if (kwargs['first_calendar'] or
+ (len(ctx.obj['calendars'])>1 and
+ not kwargs['multi_add'] and
+ not click.confirm(f"Multiple calendars given. Do you want to duplicate to {len(ctx.obj['calendars'])} calendars? (tip: use option --multi-add to avoid this prompt in the future)"))):
calendar = ctx.obj['calendars'][0]
## TODO: we need to make sure f"{calendar.name}" will always work or something
- if (kwargs['first_calendar'] or
- click.confirm(f"First calendar on the list has url {calendar.url} - should we add there? (tip: use --calendar-url={calendar.url} or --first_calendar to avoid this prompt in the future)")):
+ if (kwargs['first_calendar'] is not None and
+ (kwargs['first_calendar'] or
+ click.confirm(f"First calendar on the list has url {calendar.url} - should we add there? (tip: use --calendar-url={calendar.url} or --first_calendar to avoid this prompt in the future)"))):
ctx.obj['calendars'] = [ calendar ]
else:
- raise click.Abort()
+ raise click.Abort("Giving up: Multiple calendars found/given, please specify which calendar you want to use")
+
ctx.obj['ical_fragment'] = "\n".join(kwargs['add_ical_line'])
-
+ ctx.obj['add_args'] = {}
+ for x in kwargs:
+ if x.startswith('set_'):
+ ctx.obj['add_args'][x[4:]] = kwargs[x]
+
@add.command()
@click.pass_context
@click.option('-d', '--ical-data', '--ical', help="ical object to be added")
@@ -129,30 +230,46 @@ def ical(ctx, ical_data, ical_file):
c.save_event(ical)
@add.command()
-def todo():
- click.echo("soon you should be able to add tasks to your calendar")
- raise NotImplementedError("foo")
+@click.argument('summary', nargs=-1)
+@click.pass_context
+def todo(ctx, summary):
+ """
+ Creates a new task with given SUMMARY
+
+ Examples:
+
+ kal add todo "fix all known bugs in calendar-cli"
+ kal add todo --set-due=2050-12-10 "release calendar-cli version 42.0.0"
+ """
+ summary = " ".join(summary)
+ ctx.obj['add_args']['summary'] = (ctx.obj['add_args']['summary'] or '') + summary
+ if not ctx.obj['add_args']['summary']:
+ raise click.Abort("denying to add a TODO with no summary given")
+ return
+ for cal in ctx.obj['calendars']:
+ todo = cal.save_todo(ical=ctx.obj['ical_fragment'], **ctx.obj['add_args'], no_overwrite=True)
+ click.echo(f"uid={todo.id}")
@add.command()
## TODO
-@click.argument('description')
+@click.argument('summary')
@click.argument('timespec')
@click.pass_context
-def event(ctx, description, timespec):
+def event(ctx, summary, timespec):
"""
- Creates a new event with given DESCRIPTION at the time specifed through TIMESPEC.
-
- TIMESPEC is an ISO-formatted date or timestamp, optionally with a postfixed interval.
+ Creates a new event with given SUMMARY at the time specifed through TIMESPEC.
- Examples:
+ TIMESPEC is an ISO-formatted date or timestamp, optionally with a postfixed interval
+.
+ Examples:
kal add event "final bughunting session" 2004-11-25+5d
kal add event "release party" 2004-11-30T19:00+2h
"""
- for cal in ctx['calendars']:
+ for cal in ctx.obj['calendars']:
(dtstart, dtend) = _parse_timespec(timespec)
#event = cal.add_event(
- click.echo(uid)
+ click.echo(f"uid={uid}")
def journal():
click.echo("soon you should be able to add journal entries to your calendar")
diff --git a/tests/test_calendar-cli.sh b/tests/test_calendar-cli.sh
index d1400e2..b3b3920 100755
--- a/tests/test_calendar-cli.sh
+++ b/tests/test_calendar-cli.sh
@@ -11,8 +11,8 @@ radicale_pid=$(jobs -l | perl -ne '/^\[\d+\]\+\s+(\d+)\s+Running/ && print $1')
if [ -n "$radicale_pid" ]
then
echo "## Radicale now running on pid $radicale_pid"
- calendar_cli="../calendar-cli.py --caldav-url=http://localhost:5232/ --caldav-user=testuser --calendar-url=/testuser/calendar-cli-test-calendar"
- kal="../cal.py --caldav-url=http://localhost:5232/ --caldav-user=testuser --calendar-url=/testuser/calendar-cli-test-calendar"
+ calendar_cli="../calendar-cli.py --caldav-url=http://localhost:5232/ --caldav-pass=password1 --caldav-user=testuser --calendar-url=/testuser/calendar-cli-test-calendar"
+ kal="../cal.py --caldav-password=password1 --caldav-url=http://localhost:5232/ --caldav-user=testuser --calendar-url=/testuser/calendar-cli-test-calendar"
echo "## Creating a calendar"
$calendar_cli calendar create calendar-cli-test-calendar
@@ -21,7 +21,11 @@ then
## just get 404 when running tests.
export calendar_cli
export kal
+ echo "press enter to run tests"
+ read foo
./tests.sh
+ echo "press enter to take down test server"
+ read foo
kill $radicale_pid
sleep 0.3
else
diff --git a/tests/tests.sh b/tests/tests.sh
index b93d037..1370578 100755
--- a/tests/tests.sh
+++ b/tests/tests.sh
@@ -46,19 +46,19 @@ echo "## OK: Added the event, uid is $uid"
echo "## Taking out the agenda for 2010-10-09 + four days"
calendar_cli calendar agenda --from-time=2010-10-09 --agenda-days=4 --event-template='{description} {location}'
-echo $output | { grep -q 'this is a test calendar event Москва' && echo "## OK: found the event" ; } || echo "## FAIL: didn't find the event"
+echo $output | { grep -q 'this is a test calendar event Москва' && echo "## OK: found the event" ; } || error "didn't find the event"
echo "## Taking out the agenda for 2010-10-10, with uid"
calendar_cli calendar agenda --from-time=2010-10-10 --agenda-days=1 --event-template='{dtstart} {uid}'
-echo $output | { grep -q $uid2 && echo "## OK: found the UID" ; } || echo "## FAIL: didn't find the UID"
+echo $output | { grep -q $uid2 && echo "## OK: found the UID" ; } || error "didn't find the UID"
echo "## Deleting events with uid $uid $uid1 $uid2"
calendar_cli calendar delete --event-uid=$uid
calendar_cli calendar delete --event-uid=$uid2
-calendar_cli calendar delete --event-uid=$uid3
+kal select --uid=$uid3 delete
echo "## Searching again for the deleted event"
-calendar_cli calendar agenda --from-time=2010-10-10 --agenda-days=1
-echo $output | { grep -q 'testing testing' && echo "## FAIL: still found the event" ; } || echo "## OK: didn't find the event"
+calendar_cli calendar agenda --from-time=2010-10-10 --agenda-days=3
+echo $output | { grep -q 'testing testing' && error "still found the event" ; } || echo "## OK: didn't find the event"
echo "## Adding a full day event"
calendar_cli calendar add --whole-day '2010-10-10+4d' 'whole day testing'
@@ -82,6 +82,7 @@ calendar_cli calendar delete --event-uid=$uid
echo "## Same, using kal add ics"
kal add ical --ical-file=$tmpfile
+
rm $tmpfile
calendar_cli --icalendar calendar agenda --from-time=2010-10-13 --agenda-days=1
echo "$output" | grep -q "whole day" || error "could not find the event"
@@ -160,15 +161,17 @@ echo "## TODOS / TASK LISTS"
echo "## Attempting to add a task with category 'scripttest'"
calendar_cli todo add --set-categories scripttest "edit this task"
-echo test
uidtodo1=$(echo $output | perl -ne '/uid=(.*)$/ && print $1')
+kal add --set-categories scripttest todo "edit this task2"
+uidtodo2=$(echo $output | perl -ne '/uid=(.*)$/ && print $1')
echo "## Listing out all tasks with category set to 'scripttest'"
calendar_cli todo --categories scripttest list
-[ $(echo "$output" | wc -l) == 1 ] && echo "## OK: found the todo item we just added and nothing more"
+[ $(echo "$output" | wc -l) == 2 ] || error "We found more or less or none of the two todo items we just added"
echo "## Editing the task"
calendar_cli todo --categories scripttest edit --set-summary "editing" --add-categories "scripttest2"
+#kal select --todo --categories scripttest edit --add-categories "scripttest3"
echo "## Verifying that the edits got through"
calendar_cli todo --categories scripttest list