From 8434323ac9aec46ccc356f05f9b604e34a0739c4 Mon Sep 17 00:00:00 2001 From: David Barnett Date: Mon, 16 Sep 2024 11:00:47 -0600 Subject: [PATCH 1/3] Expand --noincluderc to skip ALL gcalclirc files --- ChangeLog | 4 ++ gcalcli/argparsers.py | 2 +- gcalcli/cli.py | 68 +++++++++++-------- .../test-02-test_prints_correct_help.snap | 4 +- 4 files changed, 45 insertions(+), 33 deletions(-) diff --git a/ChangeLog b/ChangeLog index ab97bb5..a3b43f2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -3,6 +3,10 @@ v4.5.0 * Add `init` command to explicitly request auth setup/refresh * Add support for config.toml file and `gcalcli config edit` command * Add support for `gcalcli util config-schema|reset-cache` commands + * Behavior change: `--noincluderc` now skips gcalclirc files unconditionally, + w/ or w/o --config-folder + - POSSIBLE ACTION REQUIRED: Use `@path/to/gcalclirc` explicitly if it stops + reading an rc file you needed * Respect locally-installed certificates (ajkessel) * Re-add a `--noauth_local_server` to provide instructions for authenticating from a remote system using port forwarding diff --git a/gcalcli/argparsers.py b/gcalcli/argparsers.py index 3aa72d1..497b445 100644 --- a/gcalcli/argparsers.py +++ b/gcalcli/argparsers.py @@ -42,7 +42,7 @@ '--noincluderc': { 'action': 'store_false', 'dest': 'includeRc', - 'help': 'Whether to include ~/.gcalclirc when using configFolder', + 'help': 'Whether to include ~/.gcalclirc.', }, '--calendar': { 'default': [], diff --git a/gcalcli/cli.py b/gcalcli/cli.py index 6414ab0..97a6653 100755 --- a/gcalcli/cli.py +++ b/gcalcli/cli.py @@ -22,6 +22,7 @@ # ######################################################################### # import json import os +import pathlib import signal import sys from collections import namedtuple @@ -97,23 +98,37 @@ def main(): truststore.inject_into_ssl() parser = get_argument_parser() - try: - argv = sys.argv[1:] - gcalclirc = os.path.expanduser('~/.gcalclirc') - if os.path.exists(gcalclirc): - # We want .gcalclirc to be sourced before any other --flagfile - # params since we may be told to use a specific config folder, we - # need to store generated argv in temp variable - tmp_argv = [f'@{gcalclirc}'] + argv - else: - tmp_argv = argv + argv = sys.argv[1:] + + rc_paths = [ + pathlib.Path('~/.gcalclirc').expanduser(), + env.config_dir().joinpath('gcalclirc'), + ] + # Note: Order is significant here, so precedence is + # ~/.gcalclirc < CONFIGDIR/gcalclirc < explicit args + fromfile_args = [f'@{rc}' for rc in rc_paths if rc.exists()] - (parsed_args, unparsed) = parser.parse_known_args(tmp_argv) + try: + (parsed_args, unparsed) = parser.parse_known_args(fromfile_args + argv) except Exception as e: sys.stderr.write(str(e)) parser.print_usage() sys.exit(1) + if parsed_args.config_folder: + parsed_args.config_folder = parsed_args.config_folder.expanduser() + # Re-evaluate rc_paths in case --config-folder or something was updated. + # Note this could resolve strangely if you e.g. have a gcalclirc file that + # contains --noincluderc or overrides --config-folder from inside config + # folder. If that causes problems... don't do that. + rc_paths = [ + pathlib.Path('~/.gcalclirc').expanduser(), + parsed_args.config_folder.joinpath('gcalclirc') + if parsed_args.config_folder + else None, + ] + fromfile_args = [f'@{rc}' for rc in rc_paths if rc and rc.exists()] + config_filepath = env.config_file() if config_filepath.exists(): with config_filepath.open('rb') as config_file: @@ -121,27 +136,20 @@ def main(): else: opts_from_config = config.Config() + namespace_from_config = opts_from_config.to_argparse_namespace() + # Pull week_start aside and set it manually after parse_known_args. + # TODO: Figure out why week_start from opts_from_config getting through. + week_start = namespace_from_config.week_start + namespace_from_config.week_start = None + if parsed_args.includeRc: + argv = fromfile_args + argv + (parsed_args, unparsed) = parser.parse_known_args( + argv, namespace=namespace_from_config + ) + if parsed_args.week_start is None: + parsed_args.week_start = week_start if parsed_args.config_folder: parsed_args.config_folder = parsed_args.config_folder.expanduser() - gcalclirc_path = parsed_args.config_folder.joinpath('gcalclirc') - if gcalclirc_path.exists(): - # TODO: Should this precedence be flipped to: - # ['@~/.gcalclirc', '@CONFIG_FOLDER/gcalclirc', ...]? - tmp_argv = [f'@{gcalclirc_path}'] + ( - tmp_argv if parsed_args.includeRc else argv - ) - - namespace_from_config = opts_from_config.to_argparse_namespace() - # Pull week_start aside and set it manually after parse_known_args. - # TODO: Figure out why week_start from opts_from_config getting through. - week_start = namespace_from_config.week_start - namespace_from_config.week_start = None - (parsed_args, unparsed) = parser.parse_known_args( - tmp_argv, namespace=namespace_from_config) - if parsed_args.week_start is None: - parsed_args.week_start = week_start - if parsed_args.config_folder: - parsed_args.config_folder = parsed_args.config_folder.expanduser() printer = Printer( conky=parsed_args.conky, use_color=parsed_args.color, diff --git a/tests/cli/__snapshot__/test-02-test_prints_correct_help.snap b/tests/cli/__snapshot__/test-02-test_prints_correct_help.snap index f21fcfa..1cf2ad7 100644 --- a/tests/cli/__snapshot__/test-02-test_prints_correct_help.snap +++ b/tests/cli/__snapshot__/test-02-test_prints_correct_help.snap @@ -78,8 +78,8 @@ options: Optional directory used to load config files. Deprecated: prefer $GCALCLI_CONFIG. (default: /some/gcalcli/config) - --noincluderc Whether to include ~/.gcalclirc when using - configFolder (default: True) + --noincluderc Whether to include ~/.gcalclirc. (default: + True) --calendar CALENDAR Which calendars to use, in the format "CalendarName" or "CalendarName#color", where the #color suffix is the name of a valid ANSI From df1dd9798c03217e0bc51845c17fca74e77dc0a0 Mon Sep 17 00:00:00 2001 From: David Barnett Date: Sun, 15 Sep 2024 20:56:56 -0600 Subject: [PATCH 2/3] move --calendar to explicit parser (less brittle, more self-documenting) --- gcalcli/argparsers.py | 217 +++++++++++++----- gcalcli/cli.py | 62 ++++- .../test-02-test_prints_correct_help.snap | 15 +- tests/test_argparsers.py | 2 +- 4 files changed, 218 insertions(+), 78 deletions(-) diff --git a/gcalcli/argparsers.py b/gcalcli/argparsers.py index 497b445..a4b52a4 100644 --- a/gcalcli/argparsers.py +++ b/gcalcli/argparsers.py @@ -48,10 +48,11 @@ 'default': [], 'type': str, 'action': 'append', + 'dest': 'global_calendars', 'help': 'Which calendars to use, in the format "CalendarName" or ' - '"CalendarName#color", where the #color suffix is the name of a valid ' - 'ANSI color (such as "brightblue"). This option may be called multiple ' - 'times to display additional calendars.', + '"CalendarName#color".\nSupported here globally for compatibility ' + 'purposes, but prefer passing to individual commands after the command ' + 'name since this global version is brittle.', }, '--default-calendar': { 'default': [], @@ -148,6 +149,36 @@ def get_auto_width(): return day_width if day_width > 9 else 10 +def get_calendars_parser(nargs_multiple: bool) -> argparse.ArgumentParser: + calendar_parser = argparse.ArgumentParser(add_help=False) + plural = 's' if nargs_multiple else '' + calendar_help = ( + f'Which calendar{plural} to use, in the format "CalendarName" ' + 'or "CalendarName#color", where the #color suffix is the name of a ' + 'valid ANSI color (such as "brightblue").' + ) + if nargs_multiple: + calendar_parser.add_argument( + '--calendar', + action='append', + dest='calendars', + type=str, + default=[], + help=f'{calendar_help} This option may be called multiple times to ' + 'display additional calendars.', + ) + else: + calendar_parser.add_argument( + '--calendar', + action='store', + dest='calendar', + type=str, + default=None, + help=calendar_help, + ) + return calendar_parser + + def get_output_parser(parents=[]): output_parser = argparse.ArgumentParser(add_help=False, parents=parents) output_parser.add_argument( @@ -330,6 +361,10 @@ def get_argument_parser(): if legacy_rc_path.exists(): rc_paths.append(utils.shorten_path(legacy_rc_path)) + calendars_parser = get_calendars_parser(nargs_multiple=True) + # Variant for commands that only accept a single --calendar. + calendar_parser = get_calendars_parser(nargs_multiple=False) + parser = argparse.ArgumentParser( description=DESCRIPTION.format( config_dir=config_path, @@ -378,38 +413,64 @@ def get_argument_parser(): ) sub.add_parser( - 'list', parents=[color_parser], help='list available calendars', - description='List available calendars.') + 'list', + parents=[calendars_parser, color_parser], + help='list available calendars', + description='List available calendars.', + ) sub.add_parser( - 'search', parents=[details_parser, output_parser, search_parser], - help='search for events within an optional time period', - description='Provides case insensitive search for calendar ' - 'events.') + 'search', + parents=[ + calendars_parser, + details_parser, + output_parser, + search_parser, + ], + help='search for events within an optional time period', + description='Provides case insensitive search for calendar ' 'events.', + ) sub.add_parser( - 'edit', parents=[details_parser, output_parser, search_parser], - help='edit calendar events', - description='Case insensitive search for items to find and edit ' - 'interactively.') + 'edit', + parents=[ + calendars_parser, + details_parser, + output_parser, + search_parser, + ], + help='edit calendar events', + description='Case insensitive search for items to find and edit ' + 'interactively.', + ) delete = sub.add_parser( - 'delete', parents=[output_parser, search_parser], - help='delete events from the calendar', - description='Case insensitive search for items to delete ' - 'interactively.') + 'delete', + parents=[calendars_parser, output_parser, search_parser], + help='delete events from the calendar', + description='Case insensitive search for items to delete ' + 'interactively.', + ) delete.add_argument( '--iamaexpert', action='store_true', help='Probably not') sub.add_parser( - 'agenda', - parents=[details_parser, output_parser, start_end_parser], - help='get an agenda for a time period', - description='Get an agenda for a time period.') + 'agenda', + parents=[ + calendars_parser, + details_parser, + output_parser, + start_end_parser, + ], + help='get an agenda for a time period', + description='Get an agenda for a time period.', + ) agendaupdate = sub.add_parser( - 'agendaupdate', - help='update calendar from agenda TSV file', - description='Update calendar from agenda TSV file.') + 'agendaupdate', + parents=[calendar_parser], + help='update calendar from agenda TSV file', + description='Update calendar from agenda TSV file.', + ) agendaupdate.add_argument( 'file', type=argparse.FileType('r', errors='replace'), @@ -417,45 +478,75 @@ def get_argument_parser(): default=sys.stdin) sub.add_parser( - 'updates', - parents=[details_parser, output_parser, updates_parser], - help='get updates since a datetime for a time period ' - '(defaults to through end of current month)', - description='Get updates since a datetime for a time period ' - '(default to through end of current month).') + 'updates', + parents=[ + calendars_parser, + details_parser, + output_parser, + updates_parser, + ], + help='get updates since a datetime for a time period ' + '(defaults to through end of current month)', + description='Get updates since a datetime for a time period ' + '(default to through end of current month).', + ) sub.add_parser( - 'conflicts', - parents=[details_parser, output_parser, conflicts_parser], - help='find event conflicts', - description='Find conflicts between events matching search term ' - '(default from now through 30 days into futures)') + 'conflicts', + parents=[ + calendars_parser, + details_parser, + output_parser, + conflicts_parser, + ], + help='find event conflicts', + description='Find conflicts between events matching search term ' + '(default from now through 30 days into futures)', + ) calw = sub.add_parser( - 'calw', parents=[details_parser, output_parser, cal_query_parser], - help='get a week-based agenda in calendar format', - description='Get a week-based agenda in calendar format.') + 'calw', + parents=[ + calendars_parser, + details_parser, + output_parser, + cal_query_parser, + ], + help='get a week-based agenda in calendar format', + description='Get a week-based agenda in calendar format.', + ) calw.add_argument('weeks', type=int, default=1, nargs='?') sub.add_parser( - 'calm', parents=[details_parser, output_parser, cal_query_parser], - help='get a month agenda in calendar format', - description='Get a month agenda in calendar format.') + 'calm', + parents=[ + calendars_parser, + details_parser, + output_parser, + cal_query_parser, + ], + help='get a month agenda in calendar format', + description='Get a month agenda in calendar format.', + ) quick = sub.add_parser( - 'quick', parents=[details_parser, remind_parser], - help='quick-add an event to a calendar', - description='`quick-add\' an event to a calendar. A single ' - '--calendar must be specified.') + 'quick', + parents=[calendar_parser, details_parser, remind_parser], + help='quick-add an event to a calendar', + description='`quick-add\' an event to a calendar. A single ' + '--calendar must be specified.', + ) quick.add_argument('text') add = sub.add_parser( - 'add', parents=[details_parser, remind_parser], - help='add a detailed event to the calendar', - description='Add an event to the calendar. Some or all metadata ' - 'can be passed as options (see optional arguments). If ' - 'incomplete, will drop to an interactive prompt requesting ' - 'remaining data.') + 'add', + parents=[calendar_parser, details_parser, remind_parser], + help='add a detailed event to the calendar', + description='Add an event to the calendar. Some or all metadata ' + 'can be passed as options (see optional arguments). If ' + 'incomplete, will drop to an interactive prompt requesting ' + 'remaining data.', + ) add.add_argument( '--color', dest='event_color', @@ -491,11 +582,13 @@ def get_argument_parser(): help='Don\'t prompt for missing data when adding events') _import = sub.add_parser( - 'import', parents=[remind_parser], - help='import an ics/vcal file to a calendar', - description='Import from an ics/vcal file; a single --calendar ' - 'must be specified. Reads from stdin when no file argument is ' - 'provided.') + 'import', + parents=[calendar_parser, remind_parser], + help='import an ics/vcal file to a calendar', + description='Import from an ics/vcal file; a single --calendar ' + 'must be specified. Reads from stdin when no file argument is ' + 'provided.', + ) _import.add_argument( 'file', type=argparse.FileType('r', errors='replace'), @@ -516,11 +609,13 @@ def get_argument_parser(): default_cmd = 'notify-send -u critical -i appointment-soon -a gcalcli %s' remind = sub.add_parser( - 'remind', - help='execute command if event occurs within time', - description='Execute if event occurs within ; the %s ' - 'in is replaced with event start time and title text.' - 'default command: "' + default_cmd + '"') + 'remind', + parents=[calendars_parser], + help='execute command if event occurs within time', + description='Execute if event occurs within ; the %s ' + 'in is replaced with event start time and title text.' + 'default command: "' + default_cmd + '"', + ) remind.add_argument('minutes', nargs='?', type=int, default=10) remind.add_argument('cmd', nargs='?', type=str, default=default_cmd) diff --git a/gcalcli/cli.py b/gcalcli/cli.py index 97a6653..0b8f955 100755 --- a/gcalcli/cli.py +++ b/gcalcli/cli.py @@ -170,16 +170,8 @@ def main(): except ValueError as exc: printer.err_msg(str(exc)) - if len(parsed_args.calendar) == 0: - parsed_args.calendar = parsed_args.default_calendars + cal_names = set_resolved_calendars(parsed_args, printer=printer) - cal_names = parse_cal_names(parsed_args.calendar) - # Only ignore calendars if they're not explicitly in --calendar list. - parsed_args.ignore_calendars[:] = [ - c - for c in parsed_args.ignore_calendars - if c not in [c2.name for c2 in cal_names] - ] userless_mode = bool(os.environ.get('GCALCLI_USERLESS_MODE')) if parsed_args.command in ('config', 'util'): gcal = None @@ -327,6 +319,58 @@ def main(): sys.exit(1) +def set_resolved_calendars(parsed_args, printer: Printer) -> list[str]: + multiple_allowed = not hasattr(parsed_args, 'calendar') + + # Reflect .calendar into .calendars (as list). + if hasattr(parsed_args, 'calendar') and not hasattr( + parsed_args, 'calendars' + ): + parsed_args.calendars = ( + [parsed_args.calendar] if parsed_args.calendar else [] + ) + # If command didn't request calendar or calendars, bail out with empty list. + # Note: this means if you forget parents=[calendar_parser] on a subparser, + # you'll hit this case and any global/default cals will be ignored. + if not hasattr(parsed_args, 'calendars'): + return [] + + if not parsed_args.calendars: + for cals_type, cals in [ + ('global calendars', parsed_args.global_calendars), + ('default-calendars', parsed_args.default_calendars), + ]: + if len(cals) > 1 and not multiple_allowed: + printer.debug_msg( + f"Can't use multiple {cals_type} for command " + f"`{parsed_args.command}`. Must select --calendar " + "explicitly.\n" + ) + continue + if cals: + parsed_args.calendars = cals + break + elif len(parsed_args.calendars) > 1 and not multiple_allowed: + printer.err_msg( + 'Multiple target calendars specified! Please only pass a ' + 'single --calendar if you want it to be used.\n' + ) + printer.msg( + 'Note: consider using --noincluderc if additional ' + 'calendars may be coming from gcalclirc.\n' + ) + + cal_names = parse_cal_names(parsed_args.calendars) + # Only ignore calendars if they're not explicitly in --calendar list. + parsed_args.ignore_calendars[:] = [ + c + for c in parsed_args.ignore_calendars + if c not in [c2.name for c2 in cal_names] + ] + + return cal_names + + def SIGINT_handler(signum, frame): sys.stderr.write('Signal caught, bye!\n') sys.exit(1) diff --git a/tests/cli/__snapshot__/test-02-test_prints_correct_help.snap b/tests/cli/__snapshot__/test-02-test_prints_correct_help.snap index 1cf2ad7..a34cd77 100644 --- a/tests/cli/__snapshot__/test-02-test_prints_correct_help.snap +++ b/tests/cli/__snapshot__/test-02-test_prints_correct_help.snap @@ -2,7 +2,7 @@ usage: gcalcli [-h] [--version] [--client-id CLIENT_ID] [--client-secret CLIENT_SECRET] [--noauth_local_server] [--config-folder CONFIG_FOLDER] [--noincluderc] - [--calendar CALENDAR] + [--calendar GLOBAL_CALENDARS] [--default-calendar DEFAULT_CALENDARS] [--locale LOCALE] [--refresh] [--nocache] [--conky] [--nocolor] [--lineart {fancy,unicode,ascii}] @@ -80,12 +80,13 @@ options: /some/gcalcli/config) --noincluderc Whether to include ~/.gcalclirc. (default: True) - --calendar CALENDAR Which calendars to use, in the format - "CalendarName" or "CalendarName#color", where - the #color suffix is the name of a valid ANSI - color (such as "brightblue"). This option may - be called multiple times to display additional - calendars. (default: []) + --calendar GLOBAL_CALENDARS + Which calendars to use, in the format + "CalendarName" or "CalendarName#color". + Supported here globally for compatibility + purposes, but prefer passing to individual + commands after the command name since this + global version is brittle. (default: []) --default-calendar DEFAULT_CALENDARS Optional default calendar to use if no --calendar options are given (default: []) diff --git a/tests/test_argparsers.py b/tests/test_argparsers.py index 5c4c680..b4b9078 100644 --- a/tests/test_argparsers.py +++ b/tests/test_argparsers.py @@ -100,4 +100,4 @@ def test_handle_unparsed(): argv = shlex.split('delete --calendar=test "search text"') parsed, unparsed = parser.parse_known_args(argv) parsed = argparsers.handle_unparsed(unparsed, parsed) - assert parsed.calendar == ['test'] + assert parsed.calendars == ['test'] From 1986816196df0aff0e2a72f6344efa68fbf12127 Mon Sep 17 00:00:00 2001 From: David Barnett Date: Mon, 16 Sep 2024 09:48:10 -0600 Subject: [PATCH 3/3] cleanup: formatting fixup in argparsers.py --- gcalcli/argparsers.py | 224 ++++++++++++++++++++++++++++-------------- 1 file changed, 149 insertions(+), 75 deletions(-) diff --git a/gcalcli/argparsers.py b/gcalcli/argparsers.py index a4b52a4..5ee1608 100644 --- a/gcalcli/argparsers.py +++ b/gcalcli/argparsers.py @@ -17,7 +17,6 @@ from .details import DETAILS from .printer import valid_color_name - PROGRAM_OPTIONS = { '--client-id': {'default': None, 'type': str, 'help': 'API client_id'}, '--client-secret': { @@ -97,7 +96,6 @@ class DetailsAction(argparse._AppendAction): - def __call__(self, parser, namespace, value, option_string=None): details = _copy.copy(getattr(namespace, self.dest, {})) @@ -119,7 +117,8 @@ def validwidth(value): def validreminder(value): if not utils.parse_reminder(value): raise argparse.ArgumentTypeError( - 'Not a valid reminder string: %s' % value) + 'Not a valid reminder string: %s' % value + ) else: return value @@ -127,9 +126,12 @@ def validreminder(value): def get_details_parser(): details_parser = argparse.ArgumentParser(add_help=False) details_parser.add_argument( - '--details', default={}, action=DetailsAction, - choices=DETAILS + ['all'], - help='Which parts to display, can be: ' + ', '.join(DETAILS)) + '--details', + default={}, + action=DetailsAction, + choices=DETAILS + ['all'], + help='Which parts to display, can be: ' + ', '.join(DETAILS), + ) return details_parser @@ -182,28 +184,55 @@ def get_calendars_parser(nargs_multiple: bool) -> argparse.ArgumentParser: def get_output_parser(parents=[]): output_parser = argparse.ArgumentParser(add_help=False, parents=parents) output_parser.add_argument( - '--tsv', action='store_true', dest='tsv', default=False, - help='Use Tab Separated Value output') + '--tsv', + action='store_true', + dest='tsv', + default=False, + help='Use Tab Separated Value output', + ) output_parser.add_argument( - '--nostarted', action='store_true', dest='ignore_started', - default=False, help='Hide events that have started') + '--nostarted', + action='store_true', + dest='ignore_started', + default=False, + help='Hide events that have started', + ) output_parser.add_argument( - '--nodeclined', action='store_true', dest='ignore_declined', - default=False, help='Hide events that have been declined') + '--nodeclined', + action='store_true', + dest='ignore_declined', + default=False, + help='Hide events that have been declined', + ) auto_width = get_auto_width() output_parser.add_argument( - '--width', '-w', default=auto_width, dest='cal_width', - type=validwidth, help='Set output width') + '--width', + '-w', + default=auto_width, + dest='cal_width', + type=validwidth, + help='Set output width', + ) has_24_hours = locale_has_24_hours() output_parser.add_argument( - '--military', action='store_true', default=has_24_hours, - help='Use 24 hour display') + '--military', + action='store_true', + default=has_24_hours, + help='Use 24 hour display', + ) output_parser.add_argument( - '--no-military', action='store_false', default=has_24_hours, - help='Use 12 hour display', dest='military') + '--no-military', + action='store_false', + default=has_24_hours, + help='Use 12 hour display', + dest='military', + ) output_parser.add_argument( - '--override-color', action='store_true', default=False, - help='Use overridden color for event') + '--override-color', + action='store_true', + default=False, + help='Use overridden color for event', + ) return output_parser @@ -235,18 +264,25 @@ def get_color_parser(): def get_remind_parser(): remind_parser = argparse.ArgumentParser(add_help=False) remind_parser.add_argument( - '--reminder', default=[], type=validreminder, dest='reminders', - action='append', - help='Reminders in the form "TIME METH" or "TIME". TIME ' - 'is a number which may be followed by an optional ' - '"w", "d", "h", or "m" (meaning weeks, days, hours, ' - 'minutes) and default to minutes. METH is a string ' - '"popup", "email", or "sms" and defaults to popup.') + '--reminder', + default=[], + type=validreminder, + dest='reminders', + action='append', + help='Reminders in the form "TIME METH" or "TIME". TIME ' + 'is a number which may be followed by an optional ' + '"w", "d", "h", or "m" (meaning weeks, days, hours, ' + 'minutes) and default to minutes. METH is a string ' + '"popup", "email", or "sms" and defaults to popup.', + ) remind_parser.add_argument( - '--default-reminders', action='store_true', - dest='default_reminders', default=False, - help='If no --reminder is given, use the defaults. If this is ' - 'false, do not create any reminders.') + '--default-reminders', + action='store_true', + dest='default_reminders', + default=False, + help='If no --reminder is given, use the defaults. If this is ' + 'false, do not create any reminders.', + ) return remind_parser @@ -260,10 +296,15 @@ def get_cal_query_parser(): dest='week_start', # Note defaults to SUNDAY via config.OutputSection (not explicitly set # here because that would override value from config). - help='Start the week on Monday') + help='Start the week on Monday', + ) cal_query_parser.add_argument( - '--noweekend', action='store_false', dest='cal_weekend', - default=True, help='Hide Saturday and Sunday') + '--noweekend', + action='store_false', + dest='cal_weekend', + default=True, + help='Hide Saturday and Sunday', + ) return cal_query_parser @@ -271,8 +312,8 @@ def get_updates_parser(): updates_parser = argparse.ArgumentParser(add_help=False) updates_parser.add_argument('since', type=utils.get_time_from_str) updates_parser.add_argument( - 'start', - type=utils.get_time_from_str, nargs='?') + 'start', type=utils.get_time_from_str, nargs='?' + ) updates_parser.add_argument('end', type=utils.get_time_from_str, nargs='?') return updates_parser @@ -282,9 +323,11 @@ def get_conflicts_parser(): conflicts_parser = argparse.ArgumentParser(add_help=False) conflicts_parser.add_argument('text', nargs='?', type=str) conflicts_parser.add_argument( - 'start', type=utils.get_time_from_str, nargs='?') + 'start', type=utils.get_time_from_str, nargs='?' + ) conflicts_parser.add_argument( - 'end', type=utils.get_time_from_str, nargs='?') + 'end', type=utils.get_time_from_str, nargs='?' + ) return conflicts_parser @@ -299,8 +342,7 @@ def get_search_parser(): # requires search text, optional start and end filters search_parser = argparse.ArgumentParser(add_help=False) search_parser.add_argument('text', nargs=1) - search_parser.add_argument( - 'start', type=utils.get_time_from_str, nargs='?') + search_parser.add_argument('start', type=utils.get_time_from_str, nargs='?') search_parser.add_argument('end', type=utils.get_time_from_str, nargs='?') return search_parser @@ -376,8 +418,10 @@ def get_argument_parser(): ) parser.add_argument( - '--version', action='version', version='%%(prog)s %s (%s)' % - (gcalcli.__version__, gcalcli.__author__)) + '--version', + action='version', + version='%%(prog)s %s (%s)' % (gcalcli.__version__, gcalcli.__author__), + ) # Program level options for option, definition in PROGRAM_OPTIONS.items(): @@ -402,9 +446,10 @@ def get_argument_parser(): search_parser = get_search_parser() sub = parser.add_subparsers( - help='Invoking a subcommand with --help prints subcommand usage.', - dest='command', - required=True) + help='Invoking a subcommand with --help prints subcommand usage.', + dest='command', + required=True, + ) sub.add_parser( 'init', @@ -451,7 +496,8 @@ def get_argument_parser(): 'interactively.', ) delete.add_argument( - '--iamaexpert', action='store_true', help='Probably not') + '--iamaexpert', action='store_true', help='Probably not' + ) sub.add_parser( 'agenda', @@ -475,7 +521,8 @@ def get_argument_parser(): 'file', type=argparse.FileType('r', errors='replace'), nargs='?', - default=sys.stdin) + default=sys.stdin, + ) sub.add_parser( 'updates', @@ -548,38 +595,58 @@ def get_argument_parser(): 'remaining data.', ) add.add_argument( - '--color', - dest='event_color', - default=None, type=str, - help='Color of event in browser (overrides default). Choose ' - 'from lavender, sage, grape, flamingo, banana, tangerine, ' - 'peacock, graphite, blueberry, basil, tomato.' + '--color', + dest='event_color', + default=None, + type=str, + help='Color of event in browser (overrides default). Choose ' + 'from lavender, sage, grape, flamingo, banana, tangerine, ' + 'peacock, graphite, blueberry, basil, tomato.', ) add.add_argument('--title', default=None, type=str, help='Event title') add.add_argument( - '--who', default=[], type=str, action='append', - help='Event participant (may be provided multiple times)') + '--who', + default=[], + type=str, + action='append', + help='Event participant (may be provided multiple times)', + ) add.add_argument('--where', default=None, type=str, help='Event location') add.add_argument('--when', default=None, type=str, help='Event time') # Allow either --duration or --end, but not both. end_group = add.add_mutually_exclusive_group() end_group.add_argument( - '--duration', default=None, type=int, + '--duration', + default=None, + type=int, help='Event duration in minutes (or days if --allday is given). ' - 'Alternative to --end.') + 'Alternative to --end.', + ) end_group.add_argument( - '--end', default=None, type=str, - help='Event ending time. Alternative to --duration.') + '--end', + default=None, + type=str, + help='Event ending time. Alternative to --duration.', + ) add.add_argument( - '--description', default=None, type=str, help='Event description') + '--description', default=None, type=str, help='Event description' + ) add.add_argument( - '--allday', action='store_true', dest='allday', default=False, + '--allday', + action='store_true', + dest='allday', + default=False, help='If --allday is given, the event will be an all-day event ' '(possibly multi-day if --duration is greater than 1). The time part ' - 'of the --when will be ignored.') + 'of the --when will be ignored.', + ) add.add_argument( - '--noprompt', action='store_false', dest='prompt', default=True, - help='Don\'t prompt for missing data when adding events') + '--noprompt', + action='store_false', + dest='prompt', + default=True, + help='Don\'t prompt for missing data when adding events', + ) _import = sub.add_parser( 'import', @@ -590,15 +657,20 @@ def get_argument_parser(): 'provided.', ) _import.add_argument( - 'file', - type=argparse.FileType('r', errors='replace'), - nargs='?', - default=None) + 'file', + type=argparse.FileType('r', errors='replace'), + nargs='?', + default=None, + ) _import.add_argument( - '--verbose', '-v', action='count', help='Be verbose on imports') + '--verbose', '-v', action='count', help='Be verbose on imports' + ) _import.add_argument( - '--dump', '-d', action='store_true', - help='Print events and don\'t import') + '--dump', + '-d', + action='store_true', + help='Print events and don\'t import', + ) _import.add_argument( '--use-legacy-import', action='store_true', @@ -620,12 +692,14 @@ def get_argument_parser(): remind.add_argument('cmd', nargs='?', type=str, default=default_cmd) remind.add_argument( - '--use-reminders', action='store_true', - help='Honor the remind time when running remind command') + '--use-reminders', + action='store_true', + help='Honor the remind time when running remind command', + ) remind.add_argument( - '--use_reminders', action=DeprecatedStoreTrue, - help=argparse.SUPPRESS) + '--use_reminders', action=DeprecatedStoreTrue, help=argparse.SUPPRESS + ) config = sub.add_parser( 'config',