aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-18 09:26:32 +0200
committerDanilo M. <danix@danix.xyz>2026-09-18 09:26:32 +0200
commitc64bf67e15b075182deb4eebead8315c9081ac46 (patch)
treefaa8bbce23d9f2358b606727f5c36707e2ac2acb /lib
parent6aee635dd0cc5bab4fe3a989e41cea7faa75a9e1 (diff)
downloadconky-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.lua115
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