From e5c80a22c35a5745ae957b8e84da42abd0c41d4d Mon Sep 17 00:00:00 2001 From: historia Date: Sun, 11 Aug 2024 06:09:42 -0400 Subject: Feature: One-sided conditions like -(Aug 15 2025) --- README.md | 3 +- src/soon/agenda.nim | 10 ++-- src/soon/conditionchecker.nim | 106 ++++++++++++++++++++++++++++++------------ src/soon/todos.nim | 2 +- src/soon/typechecker.nim | 6 +++ tests/tconditionchecker.nim | 16 +++++++ tests/tconditions.nim | 16 ------- tests/tests.nim | 2 +- tests/ttypechecker.nim | 18 +++++++ 9 files changed, 124 insertions(+), 55 deletions(-) create mode 100644 tests/tconditionchecker.nim delete mode 100644 tests/tconditions.nim diff --git a/README.md b/README.md index b942099..1610fa6 100644 --- a/README.md +++ b/README.md @@ -230,8 +230,7 @@ You might also be interested in these which are text based but have somewhat dif # Todo -- Todos -- Todos with deadlines +- Create tests for date ranges that start/end on DST changes. The condition checker treats every event as midnight, might be an edge case. - Calendar output - Open editor - Julian modulus calculator (`soon -j Jun 5 2025` > Outputs J and a table of common mod offsets) diff --git a/src/soon/agenda.nim b/src/soon/agenda.nim index 014be1e..4288f0a 100644 --- a/src/soon/agenda.nim +++ b/src/soon/agenda.nim @@ -28,10 +28,11 @@ proc parseDateToTable(date:string): Table[string, seq[string]] = proc allConditionsMet(dateConditions: string, date: DateTime): bool = let conditions = parseDateToTable(dateConditions.strip) + if conditions.hasKey("unknown"): return false for k,cs in conditions: var keyMatched = false for c in cs: - if conditionchecker.checkCond(c, date) == true: + if conditionchecker.checkCond(c, date): keyMatched = true break if not keyMatched: @@ -44,14 +45,14 @@ proc getEventsForDate(cal: seq[string], date: DateTime): Table[string, seq[strin for line in cal: var (success, dateConditions, event) = scanTuple(line, "$*,$*") if success: - if allConditionsMet(dateConditions, date) == true: + if allConditionsMet(dateConditions, date): event.strip result[event] = @[] lastInsertion = event elif line.strip[0] == '+': if result.hasKey(lastInsertion): result[lastInsertion].add(line) - elif line.strip[0] == '-': + elif line[0..1] == "- ": lastInsertion = "" else: echo "WARNING: Cannot parse line: ", line @@ -67,6 +68,7 @@ proc printEvents(cal: seq[string], date: DateTime, format: string) = proc printAgenda*(calendar: seq[string], past: int, future: int) = let now = now() + var today = dateTime(now.year, now.month, now.monthday, 00, 00, 00, 00, local()) for i in past..future: - let curDay = now + i.days + let curDay = today + i.days printEvents(calendar, curDay, "ddd yyyy MMM dd") diff --git a/src/soon/conditionchecker.nim b/src/soon/conditionchecker.nim index 1880d42..d4bd626 100644 --- a/src/soon/conditionchecker.nim +++ b/src/soon/conditionchecker.nim @@ -29,10 +29,7 @@ proc monthToInt(m: string): int = proc weekdayToInt(w: string): int = const weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] let weekday = w.expandWeekday - for i in 0..6: - if weekday == weekdays[i]: - return i+1 - return -1 + return find(weekdays, weekday) # Returns MJD as if date was UTC proc modifiedJulianDay*(date: DateTime): int = @@ -121,15 +118,22 @@ proc dateGroupStringToTable(s: string): Table[string, int] = let conditions = s[1..s.len-2].splitWhitespace var t = initTable[string, int]() for c in conditions: - t[checkType(c)] = conditionToInt(c) + t[typechecker.checkType(c)] = conditionToInt(c) return t -proc isValidDateGroup(left: Table, right: Table): bool = +# comparisonDate is used for strings with no year like (Aug 15) +proc dateGroupStringToDate(s: string, comparisonDate: DateTime): DateTime = + var t = dateGroupStringToTable(s) + if not t.hasKey("year"): + t["year"] = comparisonDate.year + return dateTime(t["year"], Month(t["month"]), t["day"]) + +proc isValidDateGroupRange(left: Table, right: Table): bool = for c in ["year", "month", "day"]: if left.hasKey(c) != right.hasKey(c): echo "Error: Left group conditions don't match right group: ", string return false - if c != "year" and not left.hasKey(c) or not right.hasKey(c): + if c != "year" and (not left.hasKey(c) or not right.hasKey(c)): echo "Error: Missing ", c, " in groups: ", string return false return true @@ -137,15 +141,9 @@ proc isValidDateGroup(left: Table, right: Table): bool = # Check that date falls in a range like (Jan 30 2025)-(Feb 2 2025) proc checkRangeDateGroup(s: string, date: DateTime): bool = let dates = s.split('-') - var left = dateGroupStringToTable(dates[0]) - var right = dateGroupStringToTable(dates[1]) - if not isValidDateGroup(left, right): - return false - if left["year"] > date.year or right["year"] < date.year: - return false - if left["day"] > date.monthday or right["day"] < date.monthday: - return false - return isInLoopingRange(left["month"], right["month"], ord(date.month)) + var left = dateGroupStringToDate(dates[0], date) + var right = dateGroupStringToDate(dates[1], date) + return left <= date and date <= right # Check each condition in a date group like (Jan 1 2025) proc checkDateGroup(s: string, date: DateTime): bool = @@ -156,6 +154,47 @@ proc checkDateGroup(s: string, date: DateTime): bool = return false return true +proc splitRange(s: string): (string,string) = + let range = s.split("-", maxsplit = 1) + return (range[0],range[1]) + +proc checkOneSidedDay(s: string, date: DateTime): bool = + let (left, right) = s.splitRange + if left == "": + return date.monthday <= parseInt(right) + else: + return date.monthday >= parseInt(left) + +proc checkOneSidedWeekday(s: string, date: DateTime): bool = + let (left, right) = s.splitRange + let datewd = ord(date.weekday) + if left == "": + return datewd <= right.weekDayToInt + else: + return datewd >= left.weekDayToInt + +proc checkOneSidedMonth(s: string, date: DateTime): bool = + let (left, right) = s.splitRange + let dateMonth = ord(date.month) + if left == "": + return dateMonth <= right.monthToInt + else: + return dateMonth >= left.monthToInt + +proc checkOneSidedYear(s: string, date: DateTime): bool = + let (left, right) = s.splitRange + if left == "": + return date.year <= parseInt(right) + else: + return date.year >= parseInt(left) + +proc checkOneSidedDateGroup(s: string, date: DateTime): bool = + let (left, right) = s.splitRange + if left == "": + return date <= dateGroupStringToDate(s[1..s.len-1], date) + else: + return date >= dateGroupStringToDate(s[0..s.len-2], date) + proc checkCond*(cond: string, date: DateTime): bool = var condition = cond var invert = false @@ -164,23 +203,28 @@ proc checkCond*(cond: string, date: DateTime): bool = condition.delete(0..0) invert = true case checkType(condition): - of "time": found = checkTime(condition, date) - of "year": found = checkYear(condition, date) - of "month": found = checkMonth(condition, date) - of "day": found = checkDay(condition, date) - of "weekday": found = checkWeekday(condition, date) - of "firstweek": found = checkFirstWeek(condition, date) - of "lastweek": found = checkLastWeek(condition, date) - of "julian": found = checkJulian(condition, date) - of "dategroup": found = checkDateGroup(condition, date) - of "rangeyear": found = checkRangeYear(condition, date) - of "rangemonth": found = checkRangeMonth(condition, date) - of "rangeday": found = checkRangeDay(condition, date) - of "rangeweekday": found = checkRangeWeekday(condition, date) - of "rangedategroup": found = checkRangeDateGroup(condition, date) + of "time": found = checkTime(condition, date) + of "year": found = checkYear(condition, date) + of "month": found = checkMonth(condition, date) + of "day": found = checkDay(condition, date) + of "weekday": found = checkWeekday(condition, date) + of "firstweek": found = checkFirstWeek(condition, date) + of "lastweek": found = checkLastWeek(condition, date) + of "julian": found = checkJulian(condition, date) + of "dategroup": found = checkDateGroup(condition, date) + of "rangeyear": found = checkRangeYear(condition, date) + of "rangemonth": found = checkRangeMonth(condition, date) + of "rangeday": found = checkRangeDay(condition, date) + of "rangeweekday": found = checkRangeWeekday(condition, date) + of "rangedategroup": found = checkRangeDateGroup(condition, date) + of "onesidedday": found = checkOneSidedDay(condition, date) + of "onesidedweekday": found = checkOneSidedWeekday(condition, date) + of "onesidedmonth": found = checkOneSidedMonth(condition, date) + of "onesidedyear": found = checkOneSidedYear(condition, date) + of "onesideddategroup": found = checkOneSidedDateGroup(condition, date) else: echo "ERROR: Unknown condition: ", condition - if invert == true: + if invert: return not found return found diff --git a/src/soon/todos.nim b/src/soon/todos.nim index 2129ec1..8d5ccc3 100644 --- a/src/soon/todos.nim +++ b/src/soon/todos.nim @@ -5,7 +5,7 @@ proc getTodosFromCalendar(calendar: seq[string]): Table[string, seq[string]] = var lastInsertion = "" for l in calendar: let line: string = l.strip - if line[0] == '-': + if line[0..1] == "- ": result[line] = @[] lastInsertion = line elif line[0] == '+': diff --git a/src/soon/typechecker.nim b/src/soon/typechecker.nim index a408d10..34e5723 100644 --- a/src/soon/typechecker.nim +++ b/src/soon/typechecker.nim @@ -2,6 +2,9 @@ import std/[times, re, strutils, sets] const MONTHS = DefaultLocale.MMM.toHashSet + DefaultLocale.MMMM.toHashSet const WEEKDAYS = DefaultLocale.ddd.toHashSet + DefaultLocale.dddd.toHashSet +# Forward declarations +proc checkType*(c: string): string + proc checkForRanges(c: string): string = let cs = c.split("-", maxsplit = 1) if cs.len != 2: @@ -14,6 +17,9 @@ proc checkForRanges(c: string): string = return "rangeweekday" if find(startRange, re"^\([A-Za-z0-9 ]+\)$") > -1 and find(endRange, re"^\([A-Za-z0-9 ]+\)$") > -1: return "rangedategroup" + if startRange == "" or endRange == "": + let single = startRange & endRange + return "onesided" & checkType(single) return "unknown" # Check an individual condition (Jan) and return the condition type (month) diff --git a/tests/tconditionchecker.nim b/tests/tconditionchecker.nim new file mode 100644 index 0000000..f7909b2 --- /dev/null +++ b/tests/tconditionchecker.nim @@ -0,0 +1,16 @@ +import soon/conditionchecker +import std/[unittest] +import std/[times] + +let date1 = dateTime(2025, mJan, 01, 00, 00, 00, 00, utc()) +let date2 = dateTime(2025, mJan, 01, 00, 00, 00, 00, local()) +let date3 = dateTime(2025, mDec, 31, 00, 00, 00, 00, utc()) +let date4 = dateTime(2024, mFeb, 29, 00, 00, 00, 00, local()) +let date5 = dateTime(0001, mJan, 01, 00, 00, 00, 00, local()) +let date6 = dateTime(9999, mDec, 31, 00, 00, 00, 00, local()) + + +test "Time (always true)": + check checkCond("00:00", date1) == true + check checkCond("23:59", date2) == true + check checkCond("25:00", date3) == true diff --git a/tests/tconditions.nim b/tests/tconditions.nim deleted file mode 100644 index a0298e4..0000000 --- a/tests/tconditions.nim +++ /dev/null @@ -1,16 +0,0 @@ -import soon/conditions -import std/[unittest] -import std/[times] - -let date1 = dateTime(2025, mJan, 01, 00, 00, 00, 00, utc()) -let date2 = dateTime(2025, mJan, 01, 00, 00, 00, 00, local()) -let date3 = dateTime(2025, mDec, 31, 00, 00, 00, 00, utc()) -let date4 = dateTime(2024, mFeb, 29, 00, 00, 00, 00, local()) -let date5 = dateTime(0001, mJan, 01, 00, 00, 00, 00, local()) -let date6 = dateTime(9999, mDec, 31, 00, 00, 00, 00, local()) - - -test "Time (always true)": - check checkCond("00:00", date1) == true - check checkCond("23:59", date2) == true - check checkCond("25:00", date3) == true diff --git a/tests/tests.nim b/tests/tests.nim index 5b7f865..45134c0 100644 --- a/tests/tests.nim +++ b/tests/tests.nim @@ -1 +1 @@ -include "tconditions.nim" +include tconditionchecker, ttypechecker diff --git a/tests/ttypechecker.nim b/tests/ttypechecker.nim index 5e76c2f..c0fc057 100644 --- a/tests/ttypechecker.nim +++ b/tests/ttypechecker.nim @@ -75,3 +75,21 @@ test "Testing typechecker": for t in ["!@#$","foo",""]: check checkType(t) == "unknown" + + for t in ["-1", "15-", "-30"]: + check checkType(t) == "onesidedday" + + for t in ["-mon", "WED-"]: + check checkType(t) == "onesidedweekday" + + for t in ["jan-", "-JUN"]: + check checkType(t) == "onesidedmonth" + + for t in ["1984-", "-2025"]: + check checkType(t) == "onesidedyear" + + for t in [ + "(Jan 15)-", + "-(January 15 2025)" + ]: + check checkType(t) == "onesideddategroup" -- cgit v1.2.3