2026-09-18 14:46:25 -04:00
|
|
|
-- typikon, through LuaJIT's FFI.
|
|
|
|
|
--
|
|
|
|
|
-- Drop this beside your config and `local typikon = require("typikon")`. It
|
|
|
|
|
-- needs libtypikon.so on the loader's path, or TYPIKON_LIB set to it:
|
|
|
|
|
--
|
|
|
|
|
-- cargo build --release --features capi
|
|
|
|
|
-- TYPIKON_LIB=target/release/libtypikon.so luajit examples/lua/statusline.lua
|
|
|
|
|
--
|
|
|
|
|
-- Written for AwesomeWM, WezTerm and Neovim, which all embed LuaJIT and none
|
|
|
|
|
-- of which should be spawning a process on every redraw.
|
|
|
|
|
|
|
|
|
|
local ffi = require("ffi")
|
|
|
|
|
|
|
|
|
|
ffi.cdef [[
|
|
|
|
|
const char *typikon_version(void);
|
|
|
|
|
uint32_t typikon_abi_version(void);
|
|
|
|
|
const char *typikon_last_error(void);
|
|
|
|
|
|
|
|
|
|
int32_t typikon_jdn_from_ymd(int32_t year, uint32_t month, uint32_t day);
|
|
|
|
|
int typikon_ymd_from_jdn(int32_t jdn, int32_t *year, uint32_t *month, uint32_t *day);
|
|
|
|
|
int32_t typikon_today(void);
|
|
|
|
|
|
|
|
|
|
typedef struct typikon_day typikon_day;
|
|
|
|
|
typikon_day *typikon_describe(int32_t jdn, const char *jurisdiction);
|
|
|
|
|
typikon_day *typikon_describe_full(int32_t jdn, const char *jurisdiction,
|
|
|
|
|
const char *tradition, int discipline);
|
|
|
|
|
void typikon_day_free(typikon_day *day);
|
|
|
|
|
|
|
|
|
|
const char *typikon_day_json(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_iso(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_weekday(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_jurisdiction(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_calendar(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_tradition(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_church_date(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_title(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_fast(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_fast_label(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_emoji(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_reason(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_season(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_cycle(const typikon_day *day);
|
2026-09-21 09:25:56 -04:00
|
|
|
const char *typikon_day_week(const typikon_day *day);
|
2026-09-18 14:46:25 -04:00
|
|
|
const char *typikon_day_feast(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_rank(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_sign(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_obligation(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_binds(const typikon_day *day);
|
|
|
|
|
uint32_t typikon_day_commemoration_count(const typikon_day *day);
|
|
|
|
|
const char *typikon_day_commemoration(const typikon_day *day, uint32_t i);
|
|
|
|
|
int typikon_day_is_obligatory(const typikon_day *day);
|
|
|
|
|
int32_t typikon_day_days_to_pascha(const typikon_day *day);
|
|
|
|
|
int32_t typikon_day_pascha(const typikon_day *day);
|
|
|
|
|
int32_t typikon_day_julian_offset(const typikon_day *day);
|
|
|
|
|
|
|
|
|
|
int32_t typikon_pascha(int32_t year, const char *jurisdiction);
|
|
|
|
|
uint32_t typikon_jurisdiction_count(void);
|
|
|
|
|
const char *typikon_jurisdiction_key(uint32_t i);
|
|
|
|
|
const char *typikon_jurisdiction_name(uint32_t i);
|
|
|
|
|
const char *typikon_jurisdiction_note(uint32_t i);
|
|
|
|
|
]]
|
|
|
|
|
|
|
|
|
|
local C = ffi.load(os.getenv("TYPIKON_LIB") or "typikon")
|
|
|
|
|
|
|
|
|
|
local M = { ABI = 1 }
|
|
|
|
|
|
|
|
|
|
-- A string field, or nil where the library returned NULL. Every accessor may,
|
|
|
|
|
-- and NULL means "the day has no such thing" rather than an error.
|
|
|
|
|
local function str(p)
|
|
|
|
|
if p == nil then return nil end
|
|
|
|
|
return ffi.string(p)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function err()
|
|
|
|
|
return str(C.typikon_last_error()) or "unknown error"
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- The fields worth reading without a JSON parser. Order matters only for the
|
|
|
|
|
-- table's own readability.
|
|
|
|
|
local FIELDS = {
|
|
|
|
|
"iso", "weekday", "jurisdiction", "calendar", "tradition", "church_date",
|
|
|
|
|
"title", "fast", "fast_label", "emoji", "reason",
|
2026-09-21 09:25:56 -04:00
|
|
|
"season", "cycle", "week", "feast", "rank", "sign", "obligation", "binds",
|
2026-09-18 14:46:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
-- Copy the day into a plain Lua table and free it. The C day owns its strings
|
|
|
|
|
-- and they die with it, so nothing here may hold on to a pointer.
|
|
|
|
|
local function harvest(d)
|
|
|
|
|
local t = {}
|
|
|
|
|
for _, f in ipairs(FIELDS) do
|
|
|
|
|
t[f] = str(C["typikon_day_" .. f](d))
|
|
|
|
|
end
|
|
|
|
|
t.is_obligatory = C.typikon_day_is_obligatory(d) ~= 0
|
|
|
|
|
t.days_to_pascha = C.typikon_day_days_to_pascha(d)
|
|
|
|
|
t.pascha_jdn = C.typikon_day_pascha(d)
|
|
|
|
|
t.julian_offset = C.typikon_day_julian_offset(d)
|
|
|
|
|
t.commemorations = {}
|
|
|
|
|
for i = 0, C.typikon_day_commemoration_count(d) - 1 do
|
|
|
|
|
t.commemorations[i + 1] = str(C.typikon_day_commemoration(d, i))
|
|
|
|
|
end
|
|
|
|
|
t.json = str(C.typikon_day_json(d))
|
|
|
|
|
C.typikon_day_free(d)
|
|
|
|
|
return t
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- One day, as a table. `opts` may carry `jurisdiction`, `tradition` and
|
|
|
|
|
--- `typikon` (true to show the day at the Typikon's full discipline, false for
|
|
|
|
|
--- only what particular law requires, nil for whatever was remembered).
|
|
|
|
|
--- Returns nil and a message on error.
|
|
|
|
|
function M.describe(jdn, opts)
|
|
|
|
|
opts = opts or {}
|
|
|
|
|
local discipline = -1
|
|
|
|
|
if opts.typikon == true then discipline = 1
|
|
|
|
|
elseif opts.typikon == false then discipline = 0 end
|
|
|
|
|
local d = C.typikon_describe_full(jdn, opts.jurisdiction, opts.tradition, discipline)
|
|
|
|
|
if d == nil then return nil, err() end
|
|
|
|
|
return harvest(d)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- Today, by local time.
|
|
|
|
|
function M.today(opts)
|
|
|
|
|
return M.describe(C.typikon_today(), opts)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- The Julian Day Number for a civil date, or nil and a message.
|
|
|
|
|
function M.jdn(year, month, day)
|
|
|
|
|
local n = C.typikon_jdn_from_ymd(year, month, day)
|
|
|
|
|
if n == 0 then return nil, err() end
|
|
|
|
|
return n
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- The civil year, month and day for a Julian Day Number.
|
|
|
|
|
function M.ymd(jdn)
|
|
|
|
|
local y = ffi.new("int32_t[1]")
|
|
|
|
|
local m = ffi.new("uint32_t[1]")
|
|
|
|
|
local d = ffi.new("uint32_t[1]")
|
|
|
|
|
if C.typikon_ymd_from_jdn(jdn, y, m, d) ~= 0 then return nil, err() end
|
|
|
|
|
return y[0], tonumber(m[0]), tonumber(d[0])
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- Pascha in a civil year, as a Julian Day Number.
|
|
|
|
|
function M.pascha(year, jurisdiction)
|
|
|
|
|
local n = C.typikon_pascha(year, jurisdiction)
|
|
|
|
|
if n == 0 then return nil, err() end
|
|
|
|
|
return n
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- Every jurisdiction the library knows: { key, name, note }.
|
|
|
|
|
function M.jurisdictions()
|
|
|
|
|
local out = {}
|
|
|
|
|
for i = 0, C.typikon_jurisdiction_count() - 1 do
|
|
|
|
|
out[i + 1] = {
|
|
|
|
|
key = str(C.typikon_jurisdiction_key(i)),
|
|
|
|
|
name = str(C.typikon_jurisdiction_name(i)),
|
|
|
|
|
note = str(C.typikon_jurisdiction_note(i)),
|
|
|
|
|
}
|
|
|
|
|
end
|
|
|
|
|
return out
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
M.version = str(C.typikon_version())
|
|
|
|
|
|
|
|
|
|
-- A library newer in a way that changed meaning, not one merely newer.
|
|
|
|
|
if C.typikon_abi_version() ~= M.ABI then
|
|
|
|
|
error(("typikon ABI %d, this binding wants %d")
|
|
|
|
|
:format(tonumber(C.typikon_abi_version()), M.ABI))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return M
|