def choice(evt): if as_date(evt.start) == nowdate: return isinstance(evt.end, datetime) and evt.start >= now else: return evt.recurring
def evt2short(evt): if as_date(evt.start) > as_date(now): dark = evt.recurring else: dark = as_datetime(evt.end) <= now return _evt2short(evt, dark=dark)
def _evt2short(evt): if as_date(evt.start) > as_date(self.now): dark = dark_recurring and evt.recurring else: dark = as_datetime(evt.end) <= self.now return evt2short(evt, dark=dark)
def fourweek( todate, calendars, termsize=None, objs=None, zero_offset=False, table_height=4, no_recurring=False, ): table_width = 7 inner_width = (termsize.columns - (table_width + 1)) // table_width inner_height = (termsize.lines - (table_height + 1)) // table_height table = [] offset = todate.isoweekday() % 7 rev_offset = 0 if zero_offset: rev_offset = (7 - offset) % 7 offset = 0 def do_row(fill, left, mid=None, right=None, thick=None): if mid is None: mid = left if right is None: right = left if thick is None: thick = mid line = left for _ in range(table_width): line += fill * inner_width + mid if rev_offset: index = rev_offset * (inner_width + 1) line = line[:index] + thick + line[index + 1:] line = line[:-1] + right return LGRAY + line + RESET obj, _evt2short = objs now = datetime.now(tzlocal()) nowdate = now.date() def evt2short(evt): if as_date(evt.start) > as_date(now): dark = evt.recurring else: dark = as_datetime(evt.end) <= now return _evt2short(evt, dark=dark) def choice(evt): if as_date(evt.start) == nowdate: return isinstance(evt.end, datetime) and evt.start >= now else: return evt.recurring callist = filter_calendars(obj, calendars) OPEN = object() CLOSED = object() weekcells = [[ddict(lambda: OPEN) for _ in range(table_width)] for _ in range(table_height)] cells = [(list(), list()) for _ in range(table_width * table_height)] calstart = todate - t(days=offset) events = get_events( obj, todate - t(days=offset), table_width * table_height, callist, local_recurring=True, ) running_width = 0 ftime_len = len(ftime()) + 1 for evt in events: # XXX do DRY with following loop if no_recurring and evt.recurring: continue cellnum = (as_date(evt.start) - calstart).days cellend = (as_date(evt.end) - calstart).days if (0 <= cellnum < len(cells)) or (0 < cellend <= len(cells)): length = len(evt.summary) if isinstance(evt.start, datetime): length += ftime_len running_width = max(length, running_width) if termsize.columns_auto: inner_width = min(inner_width, running_width) for evt in events: if no_recurring and evt.recurring: continue cellnum = (as_date(evt.start) - calstart).days cellend = (as_date(evt.end) - calstart).days if (0 <= cellnum < len(cells)) or (0 < cellend <= len(cells)): cellnum = max(0, cellnum) if isinstance(evt.start, datetime): text = ftime(evt.start) + ' ' + evt.summary text = shorten(text, inner_width) text = fg(evt2short(evt)) + text + RESET cells[cellnum][choice(evt)].append(text) else: # full-day event # XXX code copied from weekview, need to DRY start_week = cellnum // table_width end_week = (cellnum + (evt.end - evt.start).days - 1) // table_width end_week = min(end_week, table_height - 1) for week in range(start_week, end_week + 1): text = ' ' + evt.summary week_start = calstart + t(days=(week * table_width)) week_end = week_start + t(days=table_width) incellnum = (max(evt.start, week_start) - week_start).days excellnum = (min(evt.end, week_end) - week_start).days ndays = excellnum - incellnum daycells = weekcells[week] subcells = daycells[incellnum:excellnum] topslot = max(map(len, subcells)) for j in range(topslot): if all(j >= len(daycell) or daycell[j] is OPEN for daycell in subcells): # found an empty slot range break else: j = topslot if evt.start < week_start: text = '┄' + text if (evt.end - evt.start).days > 1: outlen = ndays * (inner_width + 1) - 1 text += (' ' + DASH * (outlen - len(text) - 2) + ('┄' if evt.end > week_end else '>')) text = shorten(text, outlen) else: text = shorten(text, inner_width) daycell = subcells[0] assert daycell[j] is OPEN daycell[j] = fg(evt2short(evt)) + text + RESET for daycell in subcells[1:]: assert daycell[j] is OPEN daycell[j] = CLOSED filled_cells = [] for i in range(table_height): daycells = weekcells[i] for j in range(table_width): daycell = daycells[j] cell, cell_recurring = cells[i * table_width + j] filled_cell = [] for k in range(max(daycell.keys(), default=-1) + 1): text = daycell[k] if text is OPEN: text = ' ' * inner_width elif text is CLOSED: text = None filled_cell.append(text) filled_cell.extend(cell) if filled_cell: filled_cell.append(' ' * inner_width) filled_cell.extend(cell_recurring) filled_cells.append(filled_cell) max_inner_height = max(len(cell) for cell in filled_cells) + 2 if termsize.lines_auto: inner_height = min(inner_height, max_inner_height) # set up table borders table.append(do_row(DASH, *CORNERS[0])) for _ in range(table_height): for _ in range(inner_height): table.append([]) table.append(do_row(DASH, *CORNERS[1])) table.pop() table.append(do_row(DASH, *CORNERS[2])) # overwrite table with content of cells for i in range(table_height): for j in range(table_width): cell = filled_cells[i * table_width + j] for k in range(inner_height): lineIndex = i * (inner_height + 1) + k + 1 text = ' ' * inner_width if k == 0: celldate = todate + t(days=(i * table_width + j - offset)) datetext = dtime(celldate) dcolor = LGRAY if celldate == now.date(): datetext = f'> {datetext} <' dcolor = WBOLD text = (dcolor + shorten(f'{datetext:^{inner_width}}', inner_width) + RESET) elif k == 1: text = LGRAY + DASH * inner_width + RESET else: k -= 2 if k + 2 == inner_height - 1 and len(cell) > k + 1: text = shorten( format('... more ...', f'^{inner_width}'), inner_width) text = fg(4) + text + RESET elif k < len(cell): text = cell[k] table[lineIndex].append(text) newtable = [] for line in table: if isinstance(line, list): text = '' for i, segment in enumerate(line): if segment is not None: if rev_offset and rev_offset == i: text += LGRAY + THICK + RESET else: text += LGRAY + PIPE + RESET text += segment text += LGRAY + PIPE + RESET newtable.append(text) else: newtable.append(line) if outofdate := string_outofdate(obj, now): newtable[0] = place(outofdate, 2, newtable[0])
class Agenda: def __init__(self, calendars, objs=None, dark_recurring=False, interval=None): self.now = datetime.now(tzlocal()) self.obj, evt2short = objs def _evt2short(evt): if as_date(evt.start) > as_date(self.now): dark = dark_recurring and evt.recurring else: dark = as_datetime(evt.end) <= self.now return evt2short(evt, dark=dark) self.evt2short = _evt2short self.callist = filter_calendars(self.obj, calendars) self.interval = t(minutes=(interval or 15)) self.seen_events = set() # assumes times with granularity at minutes def quantize(self, thetime, endtime=False): if endtime: return self.quantize(thetime - t(minutes=1)) minutes = self.interval.seconds // 60 theminutes = (thetime.hour * 60 + thetime.minute) // minutes * minutes return datetime( thetime.year, thetime.month, thetime.day, theminutes // 60, theminutes % 60, tzinfo=thetime.tzinfo, ) def agenda_table(self, todate, ndays=None, starttime=None, print_warning=True): self.todate = todate self.has_later = False if starttime is None: starttime = time(tzinfo=tzlocal()) # table[0] is the time column # table[n] is the event column for day n # each column is a map keyed by tick # special key None is for full-day events # also table[0][None] is reserved for status messages (like the outofdate string) # # table[0][None]: list[str] (status messages that should print before the table) # table[0][tick]: bool (whether there is an event starting at this time anywhere in the table # table[n][None]: list[evt] (list of full-day events) # table[n][tick]: list[evt] (list of events starting at that tick) actual_ndays = ndays or 1 table = [ddict(lambda: None)] table[0][None] = [] for _ in range(actual_ndays): table.append(ddict(list)) if print_warning: if outofdate := string_outofdate(self.obj, self.now): table[0][None].append(outofdate) events = get_events(self.obj, todate, actual_ndays, self.callist, local_recurring=True) self.longest_summary = max((len(evt.summary) for evt in events), default=0) for evt in events: # get column (1-indexed) # accounts for events that start before the first day startindex = (as_date(evt.start) - todate).days index = max(startindex, 0) + 1 if not isinstance(evt.start, datetime): # full-day event if evt.id not in self.seen_events: table[index][None].append(evt) self.seen_events.add(evt.id) continue # timeblock event if startindex >= 0: tickt = self.quantize(evt.start).time() else: tickt = starttime table[0][tickt] = tickt table[index][tickt].append(evt) return table