<<>> <> <> <> <> <> <> <> DIRECTORY Basics USING [CompareInt], BasicTime, Commander USING [CommandProc, Register], CommanderOps, Convert USING [RopeFromTime, IntFromRope], IO, List USING [CompareProc, LORA, Sort], Remind, Rope, TimeParse, UserProfile USING [CallWhenProfileChanges, ProfileChangedProc, Number]; <> RemindUI: CEDAR PROGRAM IMPORTS Basics, BasicTime, Commander, CommanderOps, Convert, IO, List, Remind, Rope, TimeParse, UserProfile = BEGIN OPEN Remind; ROPE: TYPE = Rope.ROPE; alNumRope: Rope.ROPE = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; Compare: List.CompareProc = { <> p1, p2: REF TimeParse.PieceType; p1 ¬ NARROW[ref1]; p2 ¬ NARROW[ref2]; RETURN[Basics.CompareInt[p1.start, p2.start]]; }; WhiteSpace: IO.BreakProc = { RETURN[SELECT char FROM ' , '\n, '\t, '\l => sepr, ENDCASE => other]; }; StripTrailingNewline: PROC[inStr: Rope.ROPE] RETURNS [Rope.ROPE] = { ch: CHAR; ch ¬ Rope.Fetch[inStr, Rope.Length[inStr]-1]; IF ch = '\n OR ch = '\r THEN inStr ¬ Rope.Substr[inStr, 0, Rope.Length[inStr] - 1]; RETURN[inStr]; }; OldRemind: Commander.CommandProc = { <> time: BasicTime.GMT; inStr: Rope.ROPE; i: INT; IF (i ¬ Rope.Find[cmd.commandLine, "/"]) # -1 THEN inStr ¬ Rope.Substr[cmd.commandLine, 0, i] ELSE inStr ¬ cmd.commandLine; inStr ¬ StripTrailingNewline[inStr]; time ¬ TimeParse.Parse[inStr, BasicTime.Now[] ! TimeParse.ParseError => {cmd.out.PutRope["Couldn't parse the time\n"]; GOTO done;}; ].time; CreateAndAddMeeting[time, inStr, cmd.out, IO.RIS[Rope.Substr[cmd.commandLine, i+1]], i # -1]; EXITS done => NULL; }; Remind: Commander.CommandProc = { <> time: BasicTime.GMT; inStr, newStr: Rope.ROPE; i: INT; ok: BOOLEAN; IF (i ¬ Rope.Find[cmd.commandLine, "/"]) # -1 THEN inStr ¬ Rope.Substr[cmd.commandLine, 0, i] ELSE inStr ¬ cmd.commandLine; inStr ¬ StripTrailingNewline[inStr]; [time, newStr, ok] ¬ RopeToTime[inStr, cmd.out]; IF ok THEN CreateAndAddMeeting[time, inStr, cmd.out, IO.RIS[Rope.Substr[cmd.commandLine, i+1]], i # -1]; }; CreateAndAddMeeting: PROC[time: BasicTime.GMT, str: Rope.ROPE, outStrm, optStrm: IO.STREAM, options: BOOLEAN] = { meeting: Meeting; rmd: Reminder; token1, token2: Rope.ROPE; remindLeadTime, remindDuration: INT; meeting ¬ NEW[MeetingRec]; meeting.duration ¬ defaultDuration; remindDuration ¬ defaultRemindDuration; remindLeadTime ¬ defaultRemindLeadTime; outStrm.PutF1["Time parsed as %g\n", IO.time[time]]; IF options THEN { WHILE TRUE DO { ENABLE BadData => {outStrm.PutF1["Couldn't parse %g\n", IO.rope[token1]]; LOOP}; token1 ¬ IO.GetTokenRope[optStrm, WhiteSpace ! IO.EndOfStream => EXIT].token; token2 ¬ IO.GetTokenRope[optStrm, WhiteSpace ! IO.EndOfStream => EXIT].token; SELECT TRUE FROM Rope.Equal[token1, "repeat", FALSE] => meeting.repeat ¬ RepetitionFromRope[token2]; Rope.Equal[token1, "duration", FALSE] => meeting.duration ¬ Convert.IntFromRope[token2]; Rope.Equal[token1, "RemindLeadTime", FALSE] => remindLeadTime ¬ Convert.IntFromRope[token2]; Rope.Equal[token1, "RemindDuration", FALSE] => remindDuration ¬ Convert.IntFromRope[token2]; Rope.Equal[token1, "more", FALSE] => meeting.more ¬ token2; Rope.Equal[token1, "iconLabel", FALSE] => meeting.iconLabel ¬ token2; Rope.Equal[token1, "iconFlavor", FALSE] => meeting.iconFlavor ¬ token2; ENDCASE => outStrm.PutF1["Couldn't parse %g\n", IO.rope[token1]]; }; ENDLOOP; }; <> meeting.explanation ¬ str; meeting.start ¬ time; rmd ¬ NEW [ReminderRecord ¬ [NIL, alert[ start: BasicTime.Update[time, -60*remindLeadTime], stop: BasicTime.Update[time, 60*(-remindLeadTime + remindDuration)] ]]]; meeting.reminders ¬ LIST[rmd]; [] ¬ AddMeeting[meeting]; }; RopeToTime: PROC[str: Rope.ROPE, strm: IO.STREAM] RETURNS [time: BasicTime.GMT, newStr: Rope.ROPE, ok: BOOLEAN ¬ FALSE] = { cnt, len: INT; pieces: TimeParse.PiecesType; piecesLora: List.LORA; piece: REF TimeParse.PieceType; ListToLora: PROC[ls: TimeParse.PiecesType] RETURNS [lr: List.LORA ¬ NIL] = { WHILE ls # NIL DO lr ¬ CONS[ls.first, lr]; ls ¬ ls.rest; ENDLOOP; }; BEGIN ENABLE TimeParse.ParseError => { SELECT errorType FROM noTime => IO.PutRope[strm, "No time specified\n"]; yearOrMonthButNoDay => IO.PutRope[strm, "Doesn't make sense to have year/month but no day\n"]; yearButNoMonth => IO.PutRope[strm, "Doesn't make sense to have year but no month\n"]; dayWeekdayMismatch => IO.PutRope[strm, "Mismatch between weekday and date\n"]; badYearInSlash => IO.PutRope[strm, "mm/dd/yy has invalid value for year\n"]; twoYears => IO.PutRope[strm, "The year is specified in two different ways\n"]; ENDCASE => NULL; GOTO done; }; [time, pieces] ¬ TimeParse.Parse[str, BasicTime.Now[]]; cnt ¬ 0; piecesLora ¬ List.Sort[ListToLora[pieces], Compare]; WHILE piecesLora # NIL DO piece ¬ NARROW[piecesLora.first]; len ¬ piece.len; <> IF piecesLora.rest # NIL AND Rope.SkipTo[str, piece.start - cnt + len, alNumRope] >= NARROW[pieces.rest.first, REF TimeParse.PieceType].start - cnt THEN len ¬ NARROW[pieces.rest.first, REF TimeParse.PieceType].start - piece.start; str ¬ Rope.Replace[str, piece.start - cnt, len]; cnt ¬ cnt + len; piecesLora ¬ piecesLora.rest; ENDLOOP; ok ¬ TRUE; EXITS done => NULL; END; newStr ¬ str; }; DeleteByTime: Commander.CommandProc = { <> time: BasicTime.GMT; ok: BOOLEAN; cnt: INT ¬ 0; ls: LIST OF Meeting; [time: time, ok: ok] ¬ RopeToTime[cmd.commandLine, cmd.out]; IF ok THEN { ls ¬ ListMeetings[time, time]; FOR aList: LIST OF Meeting ¬ ls, aList.rest UNTIL aList = NIL DO IF aList.first.start = time THEN {IF DeleteMeeting[aList.first.start, aList.first.uniqID] THEN cnt ¬ cnt + 1;}; ENDLOOP; }; cmd.out.PutF["Deleted %g meeting%g\n", IO.int[cnt], IO.char[IF cnt = 1 THEN ' ELSE 's]]; }; DeleteAll: Commander.CommandProc = { <> cnt: INT ¬ 0; ls: LIST OF Meeting; str: Rope.ROPE; IO.PutRope[cmd.out, "Really Delete everything? "]; str ¬ IO.GetLineRope[cmd.in]; IF Rope.Fetch[str, 0] = 'y OR Rope.Fetch[str, 0] = 'Y THEN { cnt ¬ DeleteAllMeetings[]; cmd.out.PutF["Deleted %g meeting%g\n", IO.int[cnt], IO.char[IF cnt = 1 THEN ' ELSE 's]]; }; }; Prune: Commander.CommandProc = { <> cnt: INT ¬ 0; ls: LIST OF Meeting; str: Rope.ROPE; time: BasicTime.GMT; IF CommanderOps.NumArgs[cmd] > 1 THEN { time ¬ TimeParse.Parse[cmd.commandLine, BasicTime.Now[] ! TimeParse.ParseError => {cmd.out.PutRope["Couldn't parse the time\n"]; GOTO done;}; ].time } ELSE { time ¬ BasicTime.Now[]; time ¬ TimeParse.Adjust[baseTime: time, precisionOfResult: days].time; }; IO.PutF1[cmd.out, "This will delete all one-time meetings Previous to %g, OK? ", IO.time[time]]; str ¬ IO.GetLineRope[cmd.in]; IF Rope.Fetch[str, 0] = 'y OR Rope.Fetch[str, 0] = 'Y THEN { ls ¬ ListMeetings[from: time.Update[-86400*365*2], to: time, all: FALSE]; FOR aList: LIST OF Meeting ¬ ls, aList.rest UNTIL aList = NIL DO IF aList.first.repeat = once AND DeleteMeeting[aList.first.start, aList.first.uniqID, FALSE] THEN { cnt ¬ cnt + 1; cmd.out.PutRope["*"]; } <> ENDLOOP; cmd.out.PutF["\nDeleted %g meeting%g\n", IO.int[cnt], IO.char[IF cnt = 1 THEN ' ELSE 's]]; cmd.out.PutRope["Compacting Log.... "]; CompactDb[]; cmd.out.PutRope["\n"]; CloseDb[]; }; EXITS done => NULL; }; Compact: Commander.CommandProc = { <> cmd.out.PutRope["Compacting Log.... "]; CompactDb[]; cmd.out.PutRope["\n"]; CloseDb[]; }; CompareUsingStart: List.CompareProc = { <> m1, m2: Meeting; m1 ¬ NARROW[ref1]; m2 ¬ NARROW[ref2]; RETURN[Basics.CompareInt[0, BasicTime.Period[m1.start, m2.start]]]; }; Read: Commander.CommandProc = { <> argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd]; ls: LIST OF Meeting; lsLora: List.LORA; ListToLora: PROC[ls: LIST OF Meeting] RETURNS [lr: List.LORA ¬ NIL] = { WHILE ls # NIL DO lr ¬ CONS[ls.first, lr]; ls ¬ ls.rest; ENDLOOP; }; SELECT argv.argc FROM 1, 2 => { date: BasicTime.GMT ~ IF argv.argc>1 THEN TimeParse.Parse[argv[1], BasicTime.Now[], heuristic, FALSE, FALSE].time ELSE BasicTime.Now[]; upkd: BasicTime.Unpacked ¬ date.Unpack[]; start: BasicTime.GMT; upkd.hour ¬ upkd.minute ¬ upkd.second ¬ 0; start ¬ upkd.Pack[]; ls ¬ ListMeetings[start, start.Update[86399]]}; 3 => { from: BasicTime.GMT ~ TimeParse.Parse[argv[1], BasicTime.Now[], heuristic, FALSE, FALSE].time; to: BasicTime.GMT ~ TimeParse.Parse[argv[2], BasicTime.Now[], heuristic, FALSE, FALSE].time; ls ¬ ListMeetings[from, to]}; ENDCASE => CommanderOps.Failed[Rope.Cat["Usage: ", argv[0], " [date | from-time to-time]"]]; lsLora ¬ List.Sort[ListToLora[ls], CompareUsingStart]; FOR aList: List.LORA ¬ lsLora, aList.rest UNTIL aList = NIL DO PrintMeeting[cmd.out, NARROW[aList.first]]; cmd.out.PutRope["\n"]; ENDLOOP; RETURN}; PrintMeeting: PROC[strm: IO.STREAM, meeting: Meeting] = { <> strm.PutF1["start: %g\n", IO.rope[Convert.RopeFromTime[meeting.start]]]; strm.PutF1["duration: %g\n", IO.int[meeting.duration]]; strm.PutF1["explanation: %g\n", IO.rope[meeting.explanation]]; IF meeting.repeat # once THEN strm.PutF1["repeated %g\n", IO.rope[RopeFromRepetition[meeting.repeat]]]; FOR rl: RemindList ¬ meeting.reminders, rl.rest WHILE rl#NIL DO WITH rl.first SELECT FROM x: REF ReminderRecord[alert] => strm.PutF[ "Alert Lead Time: %g\nAlert Duration: %g\n", [integer[(BasicTime.Period[x.start, meeting.start] + 30)/60]], [integer[(BasicTime.Period[x.start, x.stop] + 30)/60]] ]; x: REF ReminderRecord[mail] => strm.PutF[ "Mail Lead Time: %g, Recipient: %g\n", [integer[(BasicTime.Period[x.when, meeting.start] + 30)/60]], [rope[x.to]] ]; ENDCASE => ERROR; ENDLOOP; IF meeting.more # NIL AND NOT Rope.IsEmpty[meeting.more] THEN strm.PutF1["More: %g\n", IO.rope[meeting.more]]; IF meeting.iconLabel # NIL AND NOT Rope.IsEmpty[meeting.iconLabel] THEN strm.PutF1["Icon Label: %g\n", IO.rope[meeting.iconLabel]]; IF meeting.iconFlavor # NIL AND NOT Rope.IsEmpty[meeting.iconFlavor] THEN strm.PutF1["Icon Flavor: %g\n", IO.rope[meeting.iconFlavor]]; }; UpdateDefaults: UserProfile.ProfileChangedProc = { <> defaultRemindLeadTime ¬ UserProfile.Number["Remind.RemindLeadTime", 20]; defaultRemindDuration ¬ UserProfile.Number["Remind.RemindDuration", 20]; defaultDuration ¬ UserProfile.Number["Remind.Duration", 60]; }; GetParameters: PROC[meeting: Meeting, str: Rope.ROPE, outStrm: IO.STREAM] RETURNS [timeSpecified: BOOLEAN ¬ FALSE, remindDuration, remindLeadTime: INT]= { token1, token2: Rope.ROPE; optStrm: IO.STREAM ¬ IO.RIS[str]; remindDuration ¬ defaultRemindDuration; remindLeadTime ¬ defaultRemindLeadTime; meeting.duration ¬ defaultDuration; WHILE TRUE DO { ENABLE BadData => {outStrm.PutF1["Couldn't parse %g\n", IO.rope[token1]]; LOOP}; token1 ¬ IO.GetTokenRope[optStrm, WhiteSpace ! IO.EndOfStream => EXIT].token; token2 ¬ IO.GetTokenRope[optStrm, WhiteSpace ! IO.EndOfStream => EXIT].token; SELECT TRUE FROM Rope.Equal[token1, "start", FALSE] => { IF switchToNew THEN meeting.start ¬ TimeParse.Parse[token2, BasicTime.Now[]].time ELSE meeting.start ¬ TimeParse.Parse[token2, BasicTime.Now[]].time; timeSpecified ¬ TRUE; }; Rope.Equal[token1, "repeat", FALSE] => meeting.repeat ¬ RepetitionFromRope[token2]; Rope.Equal[token1, "duration", FALSE] => meeting.duration ¬ Convert.IntFromRope[token2]; Rope.Equal[token1, "RemindLeadTime", FALSE] => remindLeadTime ¬ Convert.IntFromRope[token2]; Rope.Equal[token1, "RemindDuration", FALSE] => remindDuration ¬ Convert.IntFromRope[token2]; Rope.Equal[token1, "more", FALSE] => meeting.more ¬ token2; Rope.Equal[token1, "iconLabel", FALSE] => meeting.iconLabel ¬ token2; Rope.Equal[token1, "iconFlavor", FALSE] => meeting.iconFlavor ¬ token2; ENDCASE => outStrm.PutF1["Couldn't parse %g\n", IO.rope[token1]]; }; ENDLOOP; }; ParseErrorTypeName: ARRAY TimeParse.ParseErrorType OF ROPE ~ [noTime: "noTime", yearOrMonthButNoDay: "yearOrMonthButNoDay", yearButNoMonth: "yearButNoMonth", dayWeekdayMismatch: "dayWeekdayMismatch", twoYears: "twoYears", badYearInSlash: "badYearInSlash"]; TestParse: Commander.CommandProc ~ { argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd]; direction: TimeParse.DirectionType ¬ heuristic; insistTime, insistDay: BOOL ¬ TRUE; FOR i: INT IN (0..argv.argc) DO arg: ROPE ~ argv[i]; SELECT TRUE FROM arg.Equal["heuristic"] => direction ¬ heuristic; arg.Equal["forward"] => direction ¬ forward; arg.Equal["backward"] => direction ¬ backward; arg.Equal["-insistTime"] => insistTime ¬ FALSE; arg.Equal["+insistTime"] => insistTime ¬ TRUE; arg.Equal["-insistDay"] => insistDay ¬ FALSE; arg.Equal["+insistDay"] => insistDay ¬ TRUE; ENDCASE => { now: BasicTime.GMT ~ BasicTime.Now[]; time: BasicTime.GMT; pieces: TimeParse.PiecesType; [time, pieces] ¬ TimeParse.Parse[arg, now, direction, insistTime, insistDay ! TimeParse.ParseError => { cmd.err.PutF1["ParseError[%g]\n", [rope[ParseErrorTypeName[errorType]]] ]; GOTO Not}]; cmd.out.PutRope[Convert.RopeFromTime[from: time, end: seconds]]; FOR pieces ¬ pieces, pieces.rest WHILE pieces#NIL DO cmd.out.PutF[" [%g, %g]", [integer[pieces.first.start]], [integer[pieces.first.len]] ]; ENDLOOP; cmd.out.PutRope["\n"]; EXITS Not => NULL}; ENDLOOP; RETURN}; PrecisionNames: ARRAY TimeParse.Precision OF ROPE ~ [years: "years", months: "months", days: "days", hours: "hours", minutes: "minutes", seconds: "seconds", unspecified: "unspecified"]; TestTempusParse: Commander.CommandProc ~ { argv: CommanderOps.ArgumentVector ~ CommanderOps.Parse[cmd]; search: BOOL ¬ TRUE; FOR i: INT IN (0..argv.argc) DO arg: ROPE ~ argv[i]; SELECT TRUE FROM arg.Equal["-search"] => search ¬ FALSE; arg.Equal["+search"] => search ¬ TRUE; ENDCASE => { now: BasicTime.GMT ~ BasicTime.Now[]; time: BasicTime.GMT; precision: TimeParse.Precision; start, length: NAT; [time, precision, start, length] ¬ TimeParse.TempusParse[arg, now, search ! TimeParse.ParseError => { cmd.err.PutF1["ParseError[%g]\n", [rope[ParseErrorTypeName[errorType]]] ]; GOTO Not}]; cmd.out.PutFL["time=%g, precision=%g, start=%g, length=%g\n", LIST[[rope[Convert.RopeFromTime[from: time, end: seconds]]], [rope[PrecisionNames[precision]]], [integer[start]], [integer[length]]] ]; EXITS Not => NULL}; ENDLOOP; RETURN}; defaultRemindLeadTime, defaultRemindDuration, defaultDuration: INT ¬ 0; walnutUser: BOOLEAN ¬ FALSE; <> switchToNew: BOOLEAN ¬ FALSE; UserProfile.CallWhenProfileChanges[UpdateDefaults]; Commander.Register[ key: "NewRemind", proc: Remind, doc: "Add item to database in style of \"remember\" using new parser" ]; Commander.Register[ key: "remind", proc: OldRemind, doc: "Add item to database in style of \"remember\" using tempus parser" ]; Commander.Register[ key: "DeleteByTimeFromCal", proc: DeleteByTime, doc: "Delete all meetings at given time from database" ]; Commander.Register[ key: "DeleteAllFromCalendar", proc: DeleteAll, doc: "Delete all meetings" ]; Commander.Register[ key: "PruneCalendar", proc: Prune, doc: " CalPrune [Date]\n\tDelete old meetings and then Compact the loganberry files" ]; Commander.Register[ key: "PackCalendar", proc: Compact, doc: " Compact the loganberry files" ]; Commander.Register[ key: "ShowCalendar", proc: Read, doc: "[date | from to] --- print contents of database" ]; Commander.Register[ key: "TimeParse.Parse", proc: TestParse, doc: "(heuristic|forward|backward|(+|-)(insistTime|insistDay)|)* --- test TimeParse.Parse" ]; Commander.Register[ key: "TimeParse.TempusParse", proc: TestTempusParse, doc: "((+|-)search|)* --- test TimeParse.TempusParse" ]; END.