summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--common.lua2
-rw-r--r--rem.lua344
2 files changed, 346 insertions, 0 deletions
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
+ }
+