diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-18 09:26:32 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-18 09:26:32 +0200 |
| commit | c64bf67e15b075182deb4eebead8315c9081ac46 (patch) | |
| tree | faa8bbce23d9f2358b606727f5c36707e2ac2acb /lib | |
| parent | 6aee635dd0cc5bab4fe3a989e41cea7faa75a9e1 (diff) | |
| download | conky-theme-udt-c64bf67e15b075182deb4eebead8315c9081ac46.tar.gz conky-theme-udt-c64bf67e15b075182deb4eebead8315c9081ac46.zip | |
feat: add a calendar card reading khal
The month grid with the week's appointments below it, colour-coded by
calendar. Events come from a cache file rather than a live call: khal
costs about 200ms of Python startup, which is two orders of magnitude
over the 2-second draw budget, so bin/calendar-sample.sh writes the
cache on a 5-minute execi and the widget only parses.
Calendar colours are read out of khal's own config by the sampler and
mapped onto the board's palette roles, so the card follows a scheme
change instead of pinning three literal hues.
khal will not emit an ISO date: {start-date} uses the user's dateformat,
which carries no year here, and a strftime spec inside the field raises.
The sampler asks for {start-date-long} and data.calendar_date recovers
the numbers by position, resolving day/month by magnitude and reading a
genuinely ambiguous pair day-first. A 2-digit year is refused rather
than guessed at.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/data.lua | 115 |
1 files changed, 115 insertions, 0 deletions
diff --git a/lib/data.lua b/lib/data.lua index 4803f5b..6f1f7ea 100644 --- a/lib/data.lua +++ b/lib/data.lua @@ -403,4 +403,119 @@ function M.iface_addr(iface) return M.iface_addr_parse(out) end +-- The calendar cache written by bin/calendar-sample.sh. +-- +-- Returns { today = { y, m, d }, colors = { <calendar> = <khal colour name> }, +-- events = { { y, m, d, start, finish, calendar, title }, ... } }, or nil when +-- the cache is absent or carries no 'generated' key. nil means "the sampler +-- never ran", which the card names; an empty events list means "nothing in the +-- next week", which is a different and equally valid state. +-- +-- Dates arrive in khal's own `longdateformat`, whatever the user set it to, so +-- the three numbers are pulled out by position and assigned by magnitude: the +-- 4-digit group is the year, and of the remaining two the one that cannot be a +-- month is the day. An unambiguous pair (03.04.2026) is read as day-first, +-- matching khal's default and the European formats its config ships. This is a +-- heuristic, and it is the honest one available: khal will not emit ISO. +function M.calendar_parse(text) + if type(text) ~= 'string' then return nil end + + local out = { colors = {}, events = {} } + local generated = false + + for line in text:gmatch('[^\n]+') do + local kind, rest = line:match('^(%S+)%s+(.*)$') + if kind == 'generated' then + generated = true + elseif kind == 'today' then + local y, m, d = rest:match('^(%d+)-(%d+)-(%d+)$') + if y then out.today = { tonumber(y), tonumber(m), tonumber(d) } end + elseif kind == 'color' then + local name, colour = rest:match('^(%S+)%s+(.*)$') + if name and colour ~= '' then out.colors[name] = colour end + elseif kind == 'event' then + -- Split on the first four pipes only: a title may contain one. + local date, st, en, cal, title = + rest:match('^([^|]*)|([^|]*)|([^|]*)|([^|]*)|(.*)$') + local ev = date and M.calendar_date(date) + if ev then + out.events[#out.events + 1] = { + y = ev[1], m = ev[2], d = ev[3], + start = st ~= '' and st or nil, + finish = en ~= '' and en or nil, + calendar = cal ~= '' and cal or nil, + title = title, + } + end + end + end + + if not generated then return nil end + return out +end + +-- Three numbers out of a formatted date, as { year, month, day }. +-- See calendar_parse for why this is positional rather than a format string. +function M.calendar_date(s) + if type(s) ~= 'string' then return nil end + local nums = {} + for n in s:gmatch('%d+') do nums[#nums + 1] = n end + if #nums < 3 then return nil end + + -- The year is the 4-digit group. A 2-digit year is not handled: khal's + -- shipped formats all use %Y, and guessing a century from two digits would + -- be a second heuristic stacked on the first. + local yi + for i = 1, 3 do + if #nums[i] == 4 then yi = i break end + end + if not yi then return nil end + + local rest = {} + for i = 1, 3 do + if i ~= yi then rest[#rest + 1] = tonumber(nums[i]) end + end + local a, b = rest[1], rest[2] + + local day, month + if a > 12 then day, month = a, b + elseif b > 12 then month, day = a, b + else day, month = a, b end -- ambiguous: day-first, as khal's defaults are + + if month < 1 or month > 12 or day < 1 or day > 31 then return nil end + return { tonumber(nums[yi]), month, day } +end + +-- Which days of a given month carry an event, and whose calendar owns each. +-- Returns { [day] = <calendar name> }. First event of a day wins, so a day +-- with two calendars takes the earlier one rather than blending into a colour +-- that means neither. +function M.calendar_month_days(events, year, month) + local out = {} + if type(events) ~= 'table' then return out end + for _, e in ipairs(events) do + if e.y == year and e.m == month and not out[e.d] then + out[e.d] = e.calendar or true + end + end + return out +end + +-- Days in a month, and the weekday its 1st falls on. +-- +-- os.time/os.date rather than a leap-year rule: the C library already knows, +-- and a hand-rolled rule is one more thing to get wrong in a century year. +-- Normalised to Monday = 1 .. Sunday = 7, because khal's firstweekday = 0 +-- means Monday while os.date's wday means Sunday = 1. +function M.month_shape(year, month) + local first = os.date('*t', os.time({ year = year, month = month, day = 1, + hour = 12 })) + -- Day 0 of the next month is the last day of this one, which os.time + -- normalises for us across the December boundary. + local last = os.date('*t', os.time({ year = year, month = month + 1, day = 0, + hour = 12 })) + return last.day, (first.wday + 5) % 7 + 1 +end + + return M |
