diff options
author | Christian Geier <geier@lostpackets.de> | 2023-10-28 16:42:47 +0200 |
---|---|---|
committer | Christian Geier <geier@lostpackets.de> | 2023-10-28 16:42:47 +0200 |
commit | 4e555cbf0bde64fe06720594f60c7d547854f2d0 (patch) | |
tree | 43cbdf43a04f81301f22dc973df09350ffa4b140 | |
parent | 0e48b29be4a353a44c7768b3a60f9a06b3009017 (diff) |
mv cli, interactive_cli out of function call
-rw-r--r-- | khal/cli.py | 902 |
1 files changed, 450 insertions, 452 deletions
diff --git a/khal/cli.py b/khal/cli.py index 99615f4e..7a266fa1 100644 --- a/khal/cli.py +++ b/khal/cli.py @@ -240,269 +240,288 @@ def stringify_conf(conf): return '\n'.join(out) -def _get_cli(): - @click.group() - @click_log.simple_verbosity_option('khal') - @global_options - @click.pass_context - def cli(ctx, config): - # setting the process title so it looks nicer in ps - # shows up as 'khal' under linux and as 'python: khal (python2.7)' - # under FreeBSD, which is still nicer than the default - setproctitle('khal') - if ctx.logfilepath: - logger = logging.getLogger('khal') - logger.handlers = [logging.FileHandler(ctx.logfilepath)] - prepare_context(ctx, config) - find_load_plugins() - - @cli.command() - @multi_calendar_option - @click.option('--format', '-f', - help=('The format of the events.')) - @click.option('--day-format', '-df', - help=('The format of the day line.')) - @click.option( - '--once', '-o', - help=('Print each event only once (even if it is repeated or spans multiple days).'), - is_flag=True) - @click.option('--notstarted', help=('Print only events that have not started.'), - is_flag=True) - @click.argument('DATERANGE', nargs=-1, required=False) - @click.pass_context - def calendar(ctx, include_calendar, exclude_calendar, daterange, once, - notstarted, format, day_format): - '''Print calendar with agenda.''' - try: - rows = controllers.calendar( - build_collection( - ctx.obj['conf'], - multi_calendar_select(ctx, include_calendar, exclude_calendar) - ), - agenda_format=format, - day_format=day_format, - once=once, - notstarted=notstarted, - daterange=daterange, - conf=ctx.obj['conf'], - firstweekday=ctx.obj['conf']['locale']['firstweekday'], - locale=ctx.obj['conf']['locale'], - weeknumber=ctx.obj['conf']['locale']['weeknumbers'], - monthdisplay=ctx.obj['conf']['view']['monthdisplay'], - hmethod=ctx.obj['conf']['highlight_days']['method'], - default_color=ctx.obj['conf']['highlight_days']['default_color'], - multiple=ctx.obj['conf']['highlight_days']['multiple'], - multiple_on_overflow=ctx.obj['conf']['highlight_days']['multiple_on_overflow'], - color=ctx.obj['conf']['highlight_days']['color'], - highlight_event_days=ctx.obj['conf']['default']['highlight_event_days'], - bold_for_light_color=ctx.obj['conf']['view']['bold_for_light_color'], - env={"calendars": ctx.obj['conf']['calendars']} - ) - click.echo('\n'.join(rows)) - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) - - @cli.command("list") - @multi_calendar_option - @click.option('--format', '-f', - help=('The format of the events.')) - @click.option('--day-format', '-df', - help=('The format of the day line.')) - @click.option('--once', '-o', is_flag=True, - help=('Print each event only once ' - '(even if it is repeated or spans multiple days).') - ) - @click.option('--notstarted', help=('Print only events that have not started.'), - is_flag=True) - @click.option('--json', help=("Fields to output in json"), multiple=True) - @click.argument('DATERANGE', nargs=-1, required=False, - metavar='[DATETIME [DATETIME | RANGE]]') - @click.pass_context - def klist(ctx, include_calendar, exclude_calendar, - daterange, once, notstarted, json, format, day_format): - """List all events between a start (default: today) and (optional) - end datetime.""" - try: - event_column = controllers.khal_list( - build_collection( - ctx.obj['conf'], - multi_calendar_select(ctx, include_calendar, exclude_calendar) - ), - agenda_format=format, - day_format=day_format, - daterange=daterange, - once=once, - notstarted=notstarted, - conf=ctx.obj['conf'], - env={"calendars": ctx.obj['conf']['calendars']}, - json=json - ) - if event_column: - click.echo('\n'.join(event_column)) - else: - logger.debug('No events found') +@click.group() +@click_log.simple_verbosity_option('khal') +@global_options +@click.pass_context +def cli(ctx, config): + # setting the process title so it looks nicer in ps + # shows up as 'khal' under linux and as 'python: khal (python2.7)' + # under FreeBSD, which is still nicer than the default + setproctitle('khal') + if ctx.logfilepath: + logger = logging.getLogger('khal') + logger.handlers = [logging.FileHandler(ctx.logfilepath)] + prepare_context(ctx, config) + find_load_plugins() + +@cli.command() +@multi_calendar_option +@click.option('--format', '-f', + help=('The format of the events.')) +@click.option('--day-format', '-df', + help=('The format of the day line.')) +@click.option( + '--once', '-o', + help=('Print each event only once (even if it is repeated or spans multiple days).'), + is_flag=True) +@click.option('--notstarted', help=('Print only events that have not started.'), + is_flag=True) +@click.argument('DATERANGE', nargs=-1, required=False) +@click.pass_context +def calendar(ctx, include_calendar, exclude_calendar, daterange, once, + notstarted, format, day_format): + '''Print calendar with agenda.''' + try: + rows = controllers.calendar( + build_collection( + ctx.obj['conf'], + multi_calendar_select(ctx, include_calendar, exclude_calendar) + ), + agenda_format=format, + day_format=day_format, + once=once, + notstarted=notstarted, + daterange=daterange, + conf=ctx.obj['conf'], + firstweekday=ctx.obj['conf']['locale']['firstweekday'], + locale=ctx.obj['conf']['locale'], + weeknumber=ctx.obj['conf']['locale']['weeknumbers'], + monthdisplay=ctx.obj['conf']['view']['monthdisplay'], + hmethod=ctx.obj['conf']['highlight_days']['method'], + default_color=ctx.obj['conf']['highlight_days']['default_color'], + multiple=ctx.obj['conf']['highlight_days']['multiple'], + multiple_on_overflow=ctx.obj['conf']['highlight_days']['multiple_on_overflow'], + color=ctx.obj['conf']['highlight_days']['color'], + highlight_event_days=ctx.obj['conf']['default']['highlight_event_days'], + bold_for_light_color=ctx.obj['conf']['view']['bold_for_light_color'], + env={"calendars": ctx.obj['conf']['calendars']} + ) + click.echo('\n'.join(rows)) + except FatalError as error: + logger.debug(error, exc_info=True) + logger.fatal(error) + sys.exit(1) - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) - - @cli.command() - @calendar_option - @click.option('--interactive', '-i', help=('Add event interactively'), - is_flag=True) - @click.option('--location', '-l', - help=('The location of the new event.')) - @click.option('--categories', '-g', - help=('The categories of the new event, comma separated.')) - @click.option('--repeat', '-r', - help=('Repeat event: daily, weekly, monthly or yearly.')) - @click.option('--until', '-u', - help=('Stop an event repeating on this date.')) - @click.option('--format', '-f', - help=('The format to print the event.')) - @click.option('--json', help=("Fields to output in json"), multiple=True) - @click.option('--alarms', '-m', - help=('Alarm times for the new event as DELTAs comma separated')) - @click.option('--url', help=("URI for the event.")) - @click.argument('info', metavar='[START [END | DELTA] [TIMEZONE] [SUMMARY] [:: DESCRIPTION]]', - nargs=-1) - @click.pass_context - def new(ctx, calendar, info, location, categories, repeat, until, alarms, url, format, - json, interactive): - '''Create a new event from arguments. - - START and END can be either dates, times or datetimes, please have a - look at the man page for details. - Everything that cannot be interpreted as a (date)time or a timezone is - assumed to be the event's summary, if two colons (::) are present, - everything behind them is taken as the event's description. - ''' - if not info and not interactive: +@cli.command("list") +@multi_calendar_option +@click.option('--format', '-f', + help=('The format of the events.')) +@click.option('--day-format', '-df', + help=('The format of the day line.')) +@click.option('--once', '-o', is_flag=True, + help=('Print each event only once ' + '(even if it is repeated or spans multiple days).') + ) +@click.option('--notstarted', help=('Print only events that have not started.'), + is_flag=True) +@click.option('--json', help=("Fields to output in json"), multiple=True) +@click.argument('DATERANGE', nargs=-1, required=False, + metavar='[DATETIME [DATETIME | RANGE]]') +@click.pass_context +def klist(ctx, include_calendar, exclude_calendar, + daterange, once, notstarted, json, format, day_format): + """List all events between a start (default: today) and (optional) + end datetime.""" + try: + event_column = controllers.khal_list( + build_collection( + ctx.obj['conf'], + multi_calendar_select(ctx, include_calendar, exclude_calendar) + ), + agenda_format=format, + day_format=day_format, + daterange=daterange, + once=once, + notstarted=notstarted, + conf=ctx.obj['conf'], + env={"calendars": ctx.obj['conf']['calendars']}, + json=json + ) + if event_column: + click.echo('\n'.join(event_column)) + else: + logger.debug('No events found') + + except FatalError as error: + logger.debug(error, exc_info=True) + logger.fatal(error) + sys.exit(1) + +@cli.command() +@calendar_option +@click.option('--interactive', '-i', help=('Add event interactively'), + is_flag=True) +@click.option('--location', '-l', + help=('The location of the new event.')) +@click.option('--categories', '-g', + help=('The categories of the new event, comma separated.')) +@click.option('--repeat', '-r', + help=('Repeat event: daily, weekly, monthly or yearly.')) +@click.option('--until', '-u', + help=('Stop an event repeating on this date.')) +@click.option('--format', '-f', + help=('The format to print the event.')) +@click.option('--json', help=("Fields to output in json"), multiple=True) +@click.option('--alarms', '-m', + help=('Alarm times for the new event as DELTAs comma separated')) +@click.option('--url', help=("URI for the event.")) +@click.argument('info', metavar='[START [END | DELTA] [TIMEZONE] [SUMMARY] [:: DESCRIPTION]]', + nargs=-1) +@click.pass_context +def new(ctx, calendar, info, location, categories, repeat, until, alarms, url, format, + json, interactive): + '''Create a new event from arguments. + + START and END can be either dates, times or datetimes, please have a + look at the man page for details. + Everything that cannot be interpreted as a (date)time or a timezone is + assumed to be the event's summary, if two colons (::) are present, + everything behind them is taken as the event's description. + ''' + if not info and not interactive: + raise click.BadParameter( + 'no details provided, did you mean to use --interactive/-i?' + ) + + calendar = calendar or ctx.obj['conf']['default']['default_calendar'] + if calendar is None: + if interactive: + while calendar is None: + calendar = click.prompt('calendar') + if calendar == '?': + for calendar in ctx.obj['conf']['calendars']: + click.echo(calendar) + calendar = None + elif calendar not in ctx.obj['conf']['calendars']: + click.echo('unknown calendar enter ? for list') + calendar = None + else: raise click.BadParameter( - 'no details provided, did you mean to use --interactive/-i?' + 'No default calendar is configured, ' + 'please provide one explicitly.' ) + try: + new_func = controllers.new_from_string + if interactive: + new_func = controllers.new_interactive + new_func( + build_collection(ctx.obj['conf'], ctx.obj.get('calendar_selection', None)), + calendar, + ctx.obj['conf'], + info=' '.join(info), + location=location, + categories=categories, + repeat=repeat, + env={"calendars": ctx.obj['conf']['calendars']}, + until=until, + alarms=alarms, + url=url, + format=format, + json=json + ) + except FatalError as error: + logger.debug(error, exc_info=True) + logger.fatal(error) + sys.exit(1) - calendar = calendar or ctx.obj['conf']['default']['default_calendar'] - if calendar is None: - if interactive: - while calendar is None: - calendar = click.prompt('calendar') - if calendar == '?': - for calendar in ctx.obj['conf']['calendars']: - click.echo(calendar) - calendar = None - elif calendar not in ctx.obj['conf']['calendars']: - click.echo('unknown calendar enter ? for list') - calendar = None +@cli.command('import') +@click.option('--include-calendar', '-a', help=('The calendar to use.'), + callback=_select_one_calendar_callback, multiple=True) +@click.option('--batch', help=('do not ask for any confirmation.'), + is_flag=True) +@click.option('--random_uid', '-r', help=('Select a random uid.'), + is_flag=True) +@click.argument('ics', type=click.File('rb'), nargs=-1) +@click.option('--format', '-f', help=('The format to print the event.')) +@click.pass_context +def import_ics(ctx, ics, include_calendar, batch, random_uid, format): + '''Import events from an .ics file (or stdin). + + If an event with the same UID is already present in the (implicitly) + selected calendar import will ask before updating (i.e. overwriting) + that old event with the imported one, unless --batch is given, than it + will always update. If this behaviour is not desired, use the + `--random-uid` flag to generate a new, random UID. + If no calendar is specified (and not `--batch`), you will be asked + to choose a calendar. You can either enter the number printed behind + each calendar's name or any unique prefix of a calendar's name. + + ''' + if include_calendar: + ctx.obj['calendar_selection'] = {include_calendar, } + collection = build_collection(ctx.obj['conf'], ctx.obj.get('calendar_selection', None)) + if batch and len(collection.names) > 1 and \ + ctx.obj['conf']['default']['default_calendar'] is None: + raise click.UsageError( + 'When using batch import, please specify a calendar to import ' + 'into or set the `default_calendar` in the config file.') + rvalue = 0 + # Default to stdin: + if not ics: + ics_strs = ((sys.stdin.read(), 'stdin'),) + if not batch: + + def isatty(_file): + try: + return _file.isatty() + except Exception: + return False + + if isatty(sys.stdin) and os.stat('/dev/tty').st_mode & stat.S_IFCHR > 0: + sys.stdin = open('/dev/tty') else: - raise click.BadParameter( - 'No default calendar is configured, ' - 'please provide one explicitly.' - ) + logger.warning('/dev/tty does not exist, importing might not work') + else: + ics_strs = ((ics_file.read(), ics_file.name) for ics_file in ics) + + for ics_str, filename in ics_strs: try: - new_func = controllers.new_from_string - if interactive: - new_func = controllers.new_interactive - new_func( - build_collection(ctx.obj['conf'], ctx.obj.get('calendar_selection', None)), - calendar, + controllers.import_ics( + collection, ctx.obj['conf'], - info=' '.join(info), - location=location, - categories=categories, - repeat=repeat, + ics=ics_str, + batch=batch, + random_uid=random_uid, env={"calendars": ctx.obj['conf']['calendars']}, - until=until, - alarms=alarms, - url=url, - format=format, - json=json ) except FatalError as error: logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) - - @cli.command('import') - @click.option('--include-calendar', '-a', help=('The calendar to use.'), - callback=_select_one_calendar_callback, multiple=True) - @click.option('--batch', help=('do not ask for any confirmation.'), - is_flag=True) - @click.option('--random_uid', '-r', help=('Select a random uid.'), - is_flag=True) - @click.argument('ics', type=click.File('rb'), nargs=-1) - @click.option('--format', '-f', help=('The format to print the event.')) - @click.pass_context - def import_ics(ctx, ics, include_calendar, batch, random_uid, format): - '''Import events from an .ics file (or stdin). - - If an event with the same UID is already present in the (implicitly) - selected calendar import will ask before updating (i.e. overwriting) - that old event with the imported one, unless --batch is given, than it - will always update. If this behaviour is not desired, use the - `--random-uid` flag to generate a new, random UID. - If no calendar is specified (and not `--batch`), you will be asked - to choose a calendar. You can either enter the number printed behind - each calendar's name or any unique prefix of a calendar's name. - - ''' - if include_calendar: - ctx.obj['calendar_selection'] = {include_calendar, } - collection = build_collection(ctx.obj['conf'], ctx.obj.get('calendar_selection', None)) - if batch and len(collection.names) > 1 and \ - ctx.obj['conf']['default']['default_calendar'] is None: - raise click.UsageError( - 'When using batch import, please specify a calendar to import ' - 'into or set the `default_calendar` in the config file.') - rvalue = 0 - # Default to stdin: - if not ics: - ics_strs = ((sys.stdin.read(), 'stdin'),) + logger.fatal(f"An error occurred when trying to import the file from {filename}") + logger.fatal("Events from it will not be available in khal") if not batch: + sys.exit(1) + rvalue = 1 + sys.exit(rvalue) + +@cli.command() +@multi_calendar_option +@mouse_option +@click.pass_context +def interactive(ctx, include_calendar, exclude_calendar, mouse): + '''Interactive UI. Also launchable via `ikhal`.''' + if mouse is not None: + ctx.obj['conf']['default']['enable_mouse'] = mouse + controllers.interactive( + build_collection( + ctx.obj['conf'], + multi_calendar_select(ctx, include_calendar, exclude_calendar) + ), + ctx.obj['conf'] + ) - def isatty(_file): - try: - return _file.isatty() - except Exception: - return False - - if isatty(sys.stdin) and os.stat('/dev/tty').st_mode & stat.S_IFCHR > 0: - sys.stdin = open('/dev/tty') - else: - logger.warning('/dev/tty does not exist, importing might not work') - else: - ics_strs = ((ics_file.read(), ics_file.name) for ics_file in ics) - - for ics_str, filename in ics_strs: - try: - controllers.import_ics( - collection, - ctx.obj['conf'], - ics=ics_str, - batch=batch, - random_uid=random_uid, - env={"calendars": ctx.obj['conf']['calendars']}, - ) - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(f"An error occurred when trying to import the file from {filename}") - logger.fatal("Events from it will not be available in khal") - if not batch: - sys.exit(1) - rvalue = 1 - sys.exit(rvalue) - - @cli.command() - @multi_calendar_option - @mouse_option - @click.pass_context - def interactive(ctx, include_calendar, exclude_calendar, mouse): - '''Interactive UI. Also launchable via `ikhal`.''' - if mouse is not None: - ctx.obj['conf']['default']['enable_mouse'] = mouse +@click.command() +@global_options +@multi_calendar_option +@mouse_option +@click.pass_context +def interactive_cli(ctx, config, include_calendar, exclude_calendar, mouse): + '''Interactive UI. Also launchable via `khal interactive`.''' + prepare_context(ctx, config) + find_load_plugins() + if mouse is not None: + ctx.obj['conf']['default']['enable_mouse'] = mouse + try: controllers.interactive( build_collection( ctx.obj['conf'], @@ -510,214 +529,193 @@ def _get_cli(): ), ctx.obj['conf'] ) + except FatalError as error: + logger.debug(error, exc_info=True) + logger.fatal(error) + sys.exit(1) - @click.command() - @global_options - @multi_calendar_option - @mouse_option - @click.pass_context - def interactive_cli(ctx, config, include_calendar, exclude_calendar, mouse): - '''Interactive UI. Also launchable via `khal interactive`.''' - prepare_context(ctx, config) - find_load_plugins() - if mouse is not None: - ctx.obj['conf']['default']['enable_mouse'] = mouse - try: - controllers.interactive( - build_collection( - ctx.obj['conf'], - multi_calendar_select(ctx, include_calendar, exclude_calendar) - ), - ctx.obj['conf'] +@cli.command() +@multi_calendar_option +@click.pass_context +def printcalendars(ctx, include_calendar, exclude_calendar): + '''List all calendars.''' + try: + click.echo('\n'.join(build_collection( + ctx.obj['conf'], + multi_calendar_select(ctx, include_calendar, exclude_calendar) + ).names)) + except FatalError as error: + logger.debug(error, exc_info=True) + logger.fatal(error) + sys.exit(1) + +@cli.command() +@click.pass_context +def printformats(ctx): + '''Print a date in all formats. + + Print the date 2013-12-21 21:45 in all configured date(time) + formats to check if these locale settings are configured to ones + liking.''' + time = dt.datetime(2013, 12, 21, 21, 45) + try: + for strftime_format in [ + 'longdatetimeformat', 'datetimeformat', 'longdateformat', + 'dateformat', 'timeformat']: + dt_str = time.strftime(ctx.obj['conf']['locale'][strftime_format]) + click.echo(f'{strftime_format}: {dt_str}') + except FatalError as error: + logger.debug(error, exc_info=True) + logger.fatal(error) + sys.exit(1) + +@cli.command() +@click.argument('ics', type=click.File('rb'), required=False) +@click.option('--format', '-f', + help=('The format to print the event.')) +@click.pass_context +def printics(ctx, ics, format): + '''Print an ics file (or read from stdin) without importing it. + + Just print the ics file, do nothing else.''' + try: + if ics: + ics_str = ics.read() + name = ics.name + else: + ics_str = sys.stdin.read() + name = 'stdin input' + controllers.print_ics(ctx.obj['conf'], name, ics_str, format) + except FatalError as error: + logger.debug(error, exc_info=True) + logger.fatal(error) + sys.exit(1) + +@cli.command() +@multi_calendar_option +@click.option('--format', '-f', + help=('The format of the events.')) +@click.option('--json', help=("Fields to output in json"), multiple=True) +@click.argument('search_string') +@click.pass_context +def search(ctx, format, json, search_string, include_calendar, exclude_calendar): + '''Search for events matching SEARCH_STRING. + + For recurring events, only the master event and different overwritten + events are shown. + ''' + # TODO support for time ranges, location, description etc + if format is None: + format = ctx.obj['conf']['view']['event_format'] + try: + collection = build_collection( + ctx.obj['conf'], + multi_calendar_select(ctx, include_calendar, exclude_calendar) + ) + events = sorted(collection.search(search_string)) + event_column = [] + term_width, _ = get_terminal_size() + now = dt.datetime.now() + env = {"calendars": ctx.obj['conf']['calendars']} + if len(json) == 0: + formatter = human_formatter(format) + else: + formatter = json_formatter(json) + for event in events: + desc = textwrap.wrap(formatter( + event.attributes(relative_to=now, env=env)), term_width) + event_column.extend( + [colored(d, event.color, + bold_for_light_color=ctx.obj['conf']['view']['bold_for_light_color']) + for d in desc] ) - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) - - @cli.command() - @multi_calendar_option - @click.pass_context - def printcalendars(ctx, include_calendar, exclude_calendar): - '''List all calendars.''' - try: - click.echo('\n'.join(build_collection( + if event_column: + click.echo('\n'.join(event_column)) + else: + logger.debug('No events found') + except FatalError as error: + logger.debug(error, exc_info=True) + logger.fatal(error) + sys.exit(1) + +@cli.command() +@multi_calendar_option +@click.option('--format', '-f', + help=('The format of the events.')) +@click.option('--show-past', help=('Show events that have already occurred as options'), + is_flag=True) +@click.argument('search_string', nargs=-1) +@click.pass_context +def edit(ctx, format, search_string, show_past, include_calendar, exclude_calendar): + '''Interactively edit (or delete) events matching the search string.''' + try: + controllers.edit( + build_collection( ctx.obj['conf'], multi_calendar_select(ctx, include_calendar, exclude_calendar) - ).names)) - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) - - @cli.command() - @click.pass_context - def printformats(ctx): - '''Print a date in all formats. - - Print the date 2013-12-21 21:45 in all configured date(time) - formats to check if these locale settings are configured to ones - liking.''' - time = dt.datetime(2013, 12, 21, 21, 45) - try: - for strftime_format in [ - 'longdatetimeformat', 'datetimeformat', 'longdateformat', - 'dateformat', 'timeformat']: - dt_str = time.strftime(ctx.obj['conf']['locale'][strftime_format]) - click.echo(f'{strftime_format}: {dt_str}') - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) - - @cli.command() - @click.argument('ics', type=click.File('rb'), required=False) - @click.option('--format', '-f', - help=('The format to print the event.')) - @click.pass_context - def printics(ctx, ics, format): - '''Print an ics file (or read from stdin) without importing it. - - Just print the ics file, do nothing else.''' - try: - if ics: - ics_str = ics.read() - name = ics.name - else: - ics_str = sys.stdin.read() - name = 'stdin input' - controllers.print_ics(ctx.obj['conf'], name, ics_str, format) - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) - - @cli.command() - @multi_calendar_option - @click.option('--format', '-f', - help=('The format of the events.')) - @click.option('--json', help=("Fields to output in json"), multiple=True) - @click.argument('search_string') - @click.pass_context - def search(ctx, format, json, search_string, include_calendar, exclude_calendar): - '''Search for events matching SEARCH_STRING. - - For recurring events, only the master event and different overwritten - events are shown. - ''' - # TODO support for time ranges, location, description etc - if format is None: - format = ctx.obj['conf']['view']['event_format'] - try: - collection = build_collection( + ), + ' '.join(search_string), + format=format, + allow_past=show_past, + locale=ctx.obj['conf']['locale'], + conf=ctx.obj['conf'] + ) + except FatalError as error: + logger.debug(error, exc_info=True) + logger.fatal(error) + sys.exit(1) + +@cli.command() +@multi_calendar_option +@click.option('--format', '-f', + help=('The format of the events.')) +@click.option('--day-format', '-df', + help=('The format of the day line.')) +@click.option('--notstarted', help=('Print only events that have not started'), + is_flag=True) +@click.option('--json', help=("Fields to output in json"), multiple=True) +@click.argument('DATETIME', nargs=-1, required=False, metavar='[[START DATE] TIME | now]') +@click.pass_context +def at(ctx, datetime, notstarted, format, day_format, json, include_calendar, exclude_calendar): + '''Print all events at a specific datetime (defaults to now).''' + if not datetime: + datetime = ("now",) + if format is None: + format = ctx.obj['conf']['view']['event_format'] + try: + rows = controllers.khal_list( + build_collection( ctx.obj['conf'], multi_calendar_select(ctx, include_calendar, exclude_calendar) - ) - events = sorted(collection.search(search_string)) - event_column = [] - term_width, _ = get_terminal_size() - now = dt.datetime.now() - env = {"calendars": ctx.obj['conf']['calendars']} - if len(json) == 0: - formatter = human_formatter(format) - else: - formatter = json_formatter(json) - for event in events: - desc = textwrap.wrap(formatter( - event.attributes(relative_to=now, env=env)), term_width) - event_column.extend( - [colored(d, event.color, - bold_for_light_color=ctx.obj['conf']['view']['bold_for_light_color']) - for d in desc] - ) - if event_column: - click.echo('\n'.join(event_column)) - else: - logger.debug('No events found') - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) - - @cli.command() - @multi_calendar_option - @click.option('--format', '-f', - help=('The format of the events.')) - @click.option('--show-past', help=('Show events that have already occurred as options'), - is_flag=True) - @click.argument('search_string', nargs=-1) - @click.pass_context - def edit(ctx, format, search_string, show_past, include_calendar, exclude_calendar): - '''Interactively edit (or delete) events matching the search string.''' - try: - controllers.edit( - build_collection( - ctx.obj['conf'], - multi_calendar_select(ctx, include_calendar, exclude_calendar) - ), - ' '.join(search_string), - format=format, - allow_past=show_past, - locale=ctx.obj['conf']['locale'], - conf=ctx.obj['conf'] - ) - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) - - @cli.command() - @multi_calendar_option - @click.option('--format', '-f', - help=('The format of the events.')) - @click.option('--day-format', '-df', - help=('The format of the day line.')) - @click.option('--notstarted', help=('Print only events that have not started'), - is_flag=True) - @click.option('--json', help=("Fields to output in json"), multiple=True) - @click.argument('DATETIME', nargs=-1, required=False, metavar='[[START DATE] TIME | now]') - @click.pass_context - def at(ctx, datetime, notstarted, format, day_format, json, include_calendar, exclude_calendar): - '''Print all events at a specific datetime (defaults to now).''' - if not datetime: - datetime = ("now",) - if format is None: - format = ctx.obj['conf']['view']['event_format'] - try: - rows = controllers.khal_list( - build_collection( - ctx.obj['conf'], - multi_calendar_select(ctx, include_calendar, exclude_calendar) - ), - agenda_format=format, - day_format=day_format, - datepoint=list(datetime), - once=True, - notstarted=notstarted, - conf=ctx.obj['conf'], - env={"calendars": ctx.obj['conf']['calendars']}, - json=json - ) - if rows: - click.echo('\n'.join(rows)) - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) - - @cli.command() - @click.pass_context - def configure(ctx): - """Helper for initial configuration of khal.""" - from . import configwizard - try: - configwizard.configwizard() - except FatalError as error: - logger.debug(error, exc_info=True) - logger.fatal(error) - sys.exit(1) + ), + agenda_format=format, + day_format=day_format, + datepoint=list(datetime), + once=True, + notstarted=notstarted, + conf=ctx.obj['conf'], + env={"calendars": ctx.obj['conf']['calendars']}, + json=json + ) + if rows: + click.echo('\n'.join(rows)) + except Fata |