From 8a3fcd0cb865b7c72545fe7a48b3cc06aebdcbdb Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Thu, 5 Jul 2018 23:04:48 +0200 Subject: =?UTF-8?q?rem:=20implement=20basic=20event=20=E2=86=92=20reminder?= =?UTF-8?q?=20formatter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common.lua | 2 + rem.lua | 344 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+) create mode 100644 rem.lua diff --git a/common.lua b/common.lua index fe29d8c..c009207 100644 --- a/common.lua +++ b/common.lua @@ -1,3 +1,5 @@ +local string = require "string" +local stringformat = string.format local verboselvl = 0 diff --git a/rem.lua b/rem.lua new file mode 100644 index 0000000..e1fdc1f --- /dev/null +++ b/rem.lua @@ -0,0 +1,344 @@ +--[[-- + + remind(1) compatible formatting. + +--]]-- + +local inspect = require "ext.inspect" + +local pcall = pcall + +local io = require "io" +local iostdout = io.stdout + +local math = require "math" +local mathfloor = math.floor + +local os = os +local osdate = os.date +local ostime = os.time + +local string = require "string" +local stringformat = string.format +local stringsub = string.sub + +local table = require "table" +local tableconcat = table.concat + +local common = require "common" +local println = common.println +local errorln = common.errorln +local noiseln = common.noiseln +local debugln = common.debugln + +local seconds_of_time = function (t) + return t % 60 +end + +local minutes_of_time = function (t) + return mathfloor (t / 60) % 60 +end + +local hours_of_time = function (t) + return mathfloor (t / (60 * 60)) % 60 +end + +local days_of_time = function (t) + return mathfloor (t / (24 * 60 * 60)) +end + +local get_param = function (params, name) + local np = #params + + for i = 1, np do + local param = params [i] + if param.name == name then return param end + end + + return nil +end + +local get_param_value = function (params, name) + local param = get_param (params, name) + + if param ~= nil then return param.value end + + return nil +end + +local get_param_value_1 = function (params, name) + local val = get_param_value (params, name) + + if val ~= nil then + return val [1] + end + + return nil +end + +local rem_of_vcalendars do + + local f_sec = 1 + local f_min = 60 * f_sec + local f_hour = 60 * f_min + local f_day = 24 * f_hour + local l_month = { [00] = 0 + , [01] = 31 + , [02] = 30 + , [03] = 31 + , [04] = 30 + , [05] = 31 + , [06] = 30 + , [07] = 31 + , [08] = 31 + , [09] = 30 + , [10] = 31 + , [11] = 30 + , [12] = 31 + } + + local time_of_month = function (m, y) + local r = 0 + + for i = 0, m - 1 do + local f = l_month [i] + + if i == 2 and y % 4 == 0 and y % 100 ~= 0 then + f = f + 1 + end + + r = r + f * f_day + end + + return r + end + + local time_of_year = function (y) + if y % 4 == 0 and y % 100 ~= 0 then + end + end + + --[[-- + + Date format “%Y%M%D” + + --]]-- + + local parse_date_value_DATE = function (st, tzid) + local Y = tonumber (stringsub (st, 1, 4)) + local M = tonumber (stringsub (st, 5, 6)) + local D = tonumber (stringsub (st, 7, 8)) + + return ostime { year = Y, month = M, day = D } + end + + --[[-- + + Date format 1 date with local time: “%Y%M%DT%h%m%s” + Date format 2 date with local time: “%Y%M%DT%h%m%sZ%z” + Date format 3 date with local time: “%Y%M%DT%h%m%s” with TZID parm + + --]]-- + local parse_date_value_DATE_TIME = function (st, tzid) + if #st < 15 then + error (stringformat ("date-time specification [%s] is rubbish", st)) + end + + local Y = tonumber (stringsub (st, 1, 4)) + local M = tonumber (stringsub (st, 5, 6)) + local D = tonumber (stringsub (st, 7, 8)) + assert (stringsub (st, 9, 9) == "T") + local h = tonumber (stringsub (st, 10, 11)) + local m = tonumber (stringsub (st, 12, 13)) + local s = tonumber (stringsub (st, 14, 15)) + + local t = { year = Y + , month = M + , day = D + , hour = h + , min = m + , sec = s + , isdst = false + } + + if #st == 15 then + --- format 3 (with time zone reference) + if tzid then + error (stringformat ("timezone specification (TZID) for dates \z + not implemented, sorry")) + end + + --- format 1 (local) + return ostime (t), true + end + + --- format 2 (UTC) + assert (stringsub (st, 16, 16) == "Z", + stringformat ("rubbish date-time specification %s", st)) + + return ostime (t), false + end + + --[[-- + + if params lack a VALUE entry, DATE-TIME is assumed + --]]-- + + local parse_date_value = function (val) + local params = val.params + + local ptzid = get_param_value_1 (params, "TZID") + local pdate = get_param_value_1 (params, "VALUE") + + if pdate == nil or pdate == "DATE-TIME" then + return parse_date_value_DATE_TIME (val.value, ptzid) + end + + if pdate == "DATE" then + return parse_date_value_DATE (val.value, ptzid), false + end + + return nil + end + + local rem_date = function (d, loctime) + if loctime == true then + return osdate ("%b %d %Y AT %H:%M", d) + end + + return osdate ("!%b %d %Y AT %H:%M", d) + end + + local rem_duration = function (dt) + --[[-- remind(1): Note that duration is specified in hours and minutes. --]]-- + + local s + local ret = "" + + s = dt % 60 + ret = ret .. stringformat ("%0.2d", s) + + dt = mathfloor (dt / 60) + if dt == 0 then return ret end + + return stringformat ("%0.2d:%s", dt, ret) + end + + local rem_date_duration = function (t0, te, loctime) + local dt = te - t0 + local s_t0 = rem_date (t0, loctime) + local s_dt = rem_duration (dt) + + return stringformat ("%s DURATION %s", s_t0, s_dt) + end + + local date_of_interval = function (dtstart, dtend) + local t0, loctime = parse_date_value (dtstart) + local te, _ignore = parse_date_value (dtend) + + if t0 ~= nil then + if te ~= nil then return rem_date_duration (t0, te, loctime) end + return rem_date (t0, loctime) + end + + return nil + end + + local msg_of_description = function (val) + return val.value + end + + local rem_of_vevent = function (ev) + local dtstart, dtend + local date, msg + local vals = ev.value + local nvals = #vals + + for i = 1, nvals do + local val = vals [i] + + if val.kind == "other" then + local valname = val.name + + if valname == "description" then msg = msg_of_description (val) + elseif valname == "dtstart" then dtstart = val + elseif valname == "dtend" then dtend = val + else + noiseln ("rem_of_vevent: ignoring value [%s]", valname) + end + else + end + end + + date = date_of_interval (dtstart, dtend) + + if date == nil then + error ("could not derive date from event values") + end + + if msg == nil then + error ("could not derive message text from event values") + end + + return stringformat ("REM %s MSG %s", date, msg) + end + + local rem_of_vcalendar = function (vcal) + local acc = { } + local vals = vcal.value + local nvals = #vals + + for i = 1, nvals do + local val = vals [i] + + if val.kind ~= "scope" or val.name ~= "VEVENT" then + noiseln ("rem_of_vcalendar: ignoring value %d of type [%s/%s]", + i, val.kind, val.name) + else + local ok, res = pcall (rem_of_vevent, val) + if ok then + acc [#acc + 1] = res + else + errorln ("rem_of_vcalendar: vevent %d contains invalid entry: %s", + i, tostring (res)) + end + end + end + + return tableconcat (acc, "\n") + end + + rem_of_vcalendars = function (vcals) + local value = vcals.value + local nvcal = #value + local acc = { } + + for i = 1, nvcal do + local vcal = value [i] + if vcal.kind ~= "scope" or vcal.name ~= "VCALENDAR" then + noiseln ("rem_of_vcalendars: ignoring object %d of type [%s/%s]", + i, vcal.kind, vcal.name) + else + acc [#acc + 1] = rem_of_vcalendar (vcal) + end + end + + return tableconcat (acc, "\n") + end +end --- [rem_of_vcalendars] + +------------------------------------------------------------------------------- +--- API +------------------------------------------------------------------------------- + +local output_calendar = function (cal, fh) + fh = fh or iostdout + local ok, str = pcall (rem_of_vcalendars, cal) + if ok then + fh:write (str) + end +end + +return { output_calendar = output_calendar + } + -- cgit v1.2.3