From 9910a0442adde579343fb1619fa3d9df095564d1 Mon Sep 17 00:00:00 2001 From: David Barnett Date: Sat, 14 Sep 2024 15:32:40 -0600 Subject: [PATCH] config: Add output.week-start="sunday"|"monday" setting --- ChangeLog | 2 +- data/config-schema.json | 22 +++++++++++++++++++++ gcalcli/argparsers.py | 15 ++++++++++----- gcalcli/cli.py | 10 ++++++++-- gcalcli/config.py | 25 +++++++++++++++++++++++- gcalcli/gcal.py | 42 ++++++++++++++++++++--------------------- 6 files changed, 86 insertions(+), 30 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9cc34cb..69f787d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,7 +6,7 @@ v4.5.0 * Migrate data files like ~/.gcalcli_oauth into standard data file paths (with fallback to migrate detected files into the new paths) * Add support for $GCALCLI_CONFIG env var and deprecate --config-folder - * Add support for config.toml file with section "calendars" + * Add support for config.toml file with sections "calendars" and "output" v4.4.0 * Fix lots of bugs by switching from deprecated oauth2client to diff --git a/data/config-schema.json b/data/config-schema.json index c952688..543a1cc 100644 --- a/data/config-schema.json +++ b/data/config-schema.json @@ -23,11 +23,33 @@ } } } + }, + "OutputSection": { + "title": "Settings about gcalcli output (formatting, colors, etc)", + "type": "object", + "properties": { + "week-start": { + "title": "Weekday to treat as start of week", + "$ref": "#/$defs/WeekStart", + "default": "sunday" + } + } + }, + "WeekStart": { + "title": "WeekStart", + "type": "string", + "enum": [ + "sunday", + "monday" + ] } }, "properties": { "calendars": { "$ref": "#/$defs/CalendarsSection" + }, + "output": { + "$ref": "#/$defs/OutputSection" } } } diff --git a/gcalcli/argparsers.py b/gcalcli/argparsers.py index 8e41664..b1cfe05 100644 --- a/gcalcli/argparsers.py +++ b/gcalcli/argparsers.py @@ -12,7 +12,7 @@ import gcalcli -from . import env, utils +from . import config, env, utils from .deprecations import DeprecatedStoreTrue, parser_allow_deprecated from .details import DETAILS from .printer import valid_color_name @@ -235,11 +235,16 @@ def get_cal_query_parser(): cal_query_parser = argparse.ArgumentParser(add_help=False) cal_query_parser.add_argument('start', type=str, nargs='?') cal_query_parser.add_argument( - '--monday', action='store_true', dest='cal_monday', default=False, - help='Start the week on Monday') + '--monday', + action='store_const', + const=config.WeekStart.MONDAY, + 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') 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 diff --git a/gcalcli/cli.py b/gcalcli/cli.py index d725714..dcb06ae 100755 --- a/gcalcli/cli.py +++ b/gcalcli/cli.py @@ -127,9 +127,15 @@ def main(): 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=opts_from_config.to_argparse_namespace() - ) + 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() diff --git a/gcalcli/config.py b/gcalcli/config.py index 4bf0f42..86aa96d 100644 --- a/gcalcli/config.py +++ b/gcalcli/config.py @@ -2,6 +2,7 @@ import argparse from collections import OrderedDict +from enum import Enum import sys from typing import Any, List, Mapping @@ -34,6 +35,22 @@ class CalendarsSection(BaseModel): ) +class WeekStart(str, Enum): + SUNDAY = "sunday" + MONDAY = "monday" + + +class OutputSection(BaseModel): + model_config = ConfigDict( + title='Settings about gcalcli output (formatting, colors, etc)' + ) + + week_start: WeekStart = Field( + alias='week-start', + title='Weekday to treat as start of week', + default=WeekStart.SUNDAY) + + class Config(BaseModel): """User configuration for gcalcli command-line tool. @@ -46,6 +63,7 @@ class Config(BaseModel): ) calendars: CalendarsSection = Field(default_factory=CalendarsSection) + output: OutputSection = Field(default_factory=OutputSection) @classmethod def from_toml(cls, config_file): @@ -53,7 +71,12 @@ def from_toml(cls, config_file): return cls(**config) def to_argparse_namespace(self) -> argparse.Namespace: - return argparse.Namespace(**vars(self.calendars or {})) + kwargs = {} + if self.calendars: + kwargs.update(vars(self.calendars)) + if self.output: + kwargs.update(vars(self.output)) + return argparse.Namespace(**kwargs) @classmethod def json_schema(cls) -> Mapping[str, Any]: diff --git a/gcalcli/gcal.py b/gcalcli/gcal.py index 62c983e..2e89ba4 100644 --- a/gcalcli/gcal.py +++ b/gcalcli/gcal.py @@ -23,7 +23,7 @@ from googleapiclient.discovery import build from googleapiclient.errors import HttpError -from . import actions, auth, env, ics, utils +from . import actions, auth, config, env, ics, utils from ._types import Cache, CalendarListEntry, Event from .actions import ACTIONS from .conflicts import ShowConflicts @@ -326,14 +326,16 @@ def _calendar_color(self, event, override_color=False): else: return 'default' - def _cal_monday(self, day_num): - """Shift the day number if we're doing cal monday, or cal_weekend is - false, since that also means we're starting on day 1 - """ - if self.options['cal_monday'] or not self.options['cal_weekend']: - day_num -= 1 - if day_num < 0: - day_num = 6 + def _cal_weekday_num(self, day: date | datetime) -> int: + """Number of day of week for the given date. + + Evaluates a date against week_start and cal_weekend options and returns + a day number starting from sunday=0 or monday=0.""" + day_num = int(day.strftime('%w')) + if (self.options['week_start'] == config.WeekStart.MONDAY + or not self.options['cal_weekend']): + # Shift to count starting from monday (subtract 1, mod 7) + day_num = (day_num - 1 + 7) % 7 return day_num def _event_time_in_range(self, e_time, r_start, r_end): @@ -371,7 +373,7 @@ def _get_week_events(self, start_dt, end_dt, event_list): now_in_week = False for event in event_list: - event_daynum = self._cal_monday(int(event['s'].strftime('%w'))) + event_daynum = self._cal_weekday_num(event['s']) event_allday = is_all_day(event) event_end_date = event['e'] @@ -436,10 +438,7 @@ def _get_week_events(self, start_dt, end_dt, event_list): if event_end_date > end_dt: end_daynum = 6 else: - end_daynum = \ - self._cal_monday( - int(event_end_date.strftime('%w')) - ) + end_daynum = self._cal_weekday_num(event_end_date) if event_daynum > end_daynum: event_daynum = 0 for day in range(event_daynum, end_daynum + 1): @@ -520,7 +519,8 @@ def _GraphEvents(self, cmd, start_datetime, count, event_list): days = 7 if self.options['cal_weekend'] else 5 # Get the localized day names... January 1, 2001 was a Monday day_names = [date(2001, 1, i + 1).strftime('%A') for i in range(days)] - if not self.options['cal_monday'] or not self.options['cal_weekend']: + if (self.options['week_start'] != config.WeekStart.MONDAY + or not self.options['cal_weekend']): day_names = day_names[6:] + day_names[:6] def build_divider(left, center, right): @@ -569,7 +569,7 @@ def build_divider(left, center, right): # get date range objects for the first week if cmd == 'calm': - day_num = self._cal_monday(int(start_datetime.strftime('%w'))) + day_num = self._cal_weekday_num(start_datetime) start_datetime = (start_datetime - timedelta(days=day_num)) start_week_datetime = start_datetime end_week_datetime = (start_week_datetime + timedelta(days=7)) @@ -1281,7 +1281,7 @@ def CalQuery(self, cmd, start_text='', count=1): # convert start date to the beginning of the week or month if cmd == 'calw': - day_num = self._cal_monday(int(start.strftime('%w'))) + day_num = self._cal_weekday_num(start) start = (start - timedelta(days=day_num)) end = (start + timedelta(days=(count * 7))) else: # cmd == 'calm': @@ -1293,11 +1293,11 @@ def CalQuery(self, cmd, start_text='', count=1): end_year += 1 end = start.replace(month=end_month, year=end_year) days_in_month = (end - start).days + # TODO: Is this correct for --noweekend? Still uses % 7 below? offset_days = int(start.strftime('%w')) - if self.options['cal_monday']: - offset_days -= 1 - if offset_days < 0: - offset_days = 6 + if self.options['week_start'] == config.WeekStart.MONDAY: + # Shift to count starting from monday (subtract 1, mod 7) + offset_days = (offset_days - 1 + 7) % 7 total_days = (days_in_month + offset_days) count = int(total_days / 7) if total_days % 7: