diff options
author | Tobias Brox <tobias@redpill-linpro.com> | 2022-04-18 10:07:34 +0200 |
---|---|---|
committer | Tobias Brox <tobias@redpill-linpro.com> | 2022-04-18 10:07:34 +0200 |
commit | 36fbbe2654d2f73a7583d9538787fd807b781b0e (patch) | |
tree | a58a786ddc4575c6c948e2f2f1cc343435d18462 | |
parent | 4111bcda1bd37df518153273fc5c8add3b684134 (diff) | |
download | calendar-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-x | cal.py | 171 | ||||
-rwxr-xr-x | tests/test_calendar-cli.sh | 8 | ||||
-rwxr-xr-x | tests/tests.sh | 17 |
3 files changed, 160 insertions, 36 deletions
@@ -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 |