aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md12
-rw-r--r--src/then.nim103
2 files changed, 12 insertions, 103 deletions
diff --git a/README.md b/README.md
index c7bd73e..cc742d9 100644
--- a/README.md
+++ b/README.md
@@ -159,9 +159,9 @@ Julian date modulus is definitely weird, but it's a concise way to express multi
Then was created to adapt existing CLI calendars to my personal workflow. Then is not only for the niche of people who use their terminal for scheduling, but for a sub-niche who prefer to edit a file over using a TUI which is why concise, legible syntax is so important. This is a very small group of people who virtually exclusively use [Remind](https://dianne.skoll.ca/projects/remind/). If you're happy with a web or phone calendar, Then probably won't change your mind.
-## Then has concise syntax compared to similar programs
+Then has a few benefits over similar text file calendars:
-It minimizes special characters and simplifies ranges.
+1. **Then has the most concise syntax**. It minimizes special characters and simplifies ranges. Remind is arguably nicer for complex recurring cases and supports more types of conditions, but I rarely need these:
**Then**: `1, Water bill`\
**When**: `* * 1, Water bill`\
@@ -171,19 +171,15 @@ It minimizes special characters and simplifies ranges.
**When**: `m=Jul & d>=1 & d<=7 & y=2025, Vacation!`\
**Remind**: `REM Jul 1 2025 THROUGH Jul 7 2025 MSG Vacation!`
-Remind is wordier but nicer for recurring cases cases that require Julian dates, although I rarely do anything this complex. Remind is inarguably more powerful when it comes to conditions and recurrence:
-
**Then**: `Jul 10-19 2025 J%3-2`\
**When**: `m=jul & d>=10 & d<=19 & y=2024 & !(j%3-2)`\
**Remind**: `REM Jul 10 2025 *3 THROUGH Jul 19 2025`
-## Then *optionally* can archive calendar events.
+2. Then *optionally* can use an archive file to treat events like todos
Take the event "Change air filter", scheduled every 6 months. I might see that upcoming and archive this instance a week early, which removes it from my calendar. Or I might let it slide until next weekend, in which case it sticks around until it's done.
-I use many calendar events (bills, etc.) as a quasi-todo list, so I need to know when I've actually completed them, not just be reminded on a specific day.
-
-## It *optionally* shows a to-do list and upcoming deadlines.
+3. It *optionally* shows a todo list and upcoming deadlines.
# Alternatives
diff --git a/src/then.nim b/src/then.nim
index a74a863..5395ca4 100644
--- a/src/then.nim
+++ b/src/then.nim
@@ -1,14 +1,13 @@
-import std/[times, os, strutils, strbasics, strscans, sets, re, tables], parseopt
+import std/[times, os, strutils, strbasics, strscans, sets, tables], parseopt
+import then/[conditions,typechecker]
const VERSION = "0.1"
-const MONTHS = DefaultLocale.MMM.toHashSet + DefaultLocale.MMMM.toHashSet
-const WEEKDAYS = DefaultLocale.ddd.toHashSet + DefaultLocale.dddd.toHashSet
const configDir = expandTilde("~/.config/then/")
const defaultConfigFile = configDir & "then.toml"
const defaultCalendarFile = configDir & "calendar.then"
-# Forward declarations
-proc checkCond(cond: string, date: DateTime): bool
+const MONTHS = DefaultLocale.MMM.toHashSet + DefaultLocale.MMMM.toHashSet
+const WEEKDAYS = DefaultLocale.ddd.toHashSet + DefaultLocale.dddd.toHashSet
# Creates a default configuration file if it doesn't exist
proc createDefaultConfigFile() =
@@ -53,104 +52,19 @@ proc printVersion() =
echo "then " & VERSION
quit()
-# Check an individual condition (Jan) and return the condition type (month)
-proc checkType(c: string): string =
- var cond = c
- if find(cond, re"^\d{4}$") > -1: return "year"
- if find(cond, re"^\d{1,2}$") > -1: return "day"
- if find(cond, re"^\dw$") > -1: return "firstweek"
- if find(cond, re"^\dl$") > -1: return "lastweek"
- if find(cond, re"^\([A-Za-z0-9 ]+\)$") > -1: return "dategroup"
- if find(cond, re"^\d{4}-\d{4}$") > -1: return "rangeyear"
- if find(cond, re"^\d{1,2}-\d{1,2}$") > -1: return "rangeday"
- if find(cond, re"^\d{1,2}:\d{2}(-\d{1,2}:\d{2})?$") > -1: return "time"
- if find(cond, re"^\(?\s*[Jj]\s*%\s*\d+\s*([-+]\d+)?\s*\)?$") > -1: return "julian"
- cond = cond.toLower.capitalizeAscii
- if cond in MONTHS: return "month"
- if cond in WEEKDAYS: return "dayofweek"
-
- # Split c on - and check for ranges
- let cs = cond.split("-", maxsplit = 1)
- if cs.len != 2: return "unknown"
- let startRange = cs[0].toLower.capitalizeAscii
- let endRange = cs[1].toLower.capitalizeAscii
- if (startRange in MONTHS) and (endRange in MONTHS): return "rangemonth"
- if (startRange in WEEKDAYS) and (endRange in WEEKDAYS): return "rangedayofweek"
- if find(startRange, re"^\([A-Za-z0-9 ]+\)$") > -1 and find(endRange, re"^\([A-Za-z0-9 ]+\)$") > -1:
- return "rangedategroup"
- return "unknown"
-
-proc getWeekFromEnd(date: DateTime): int =
- let daysInMonth = getDaysInMonth(date.month, date.year)
- return int((daysInMonth - date.monthday) / 7) + 1
-
-proc getWeek(date: DateTime): int =
- return int((date.monthDay - 1) / 7) + 1
-
-# Check each condition in a date group like (Jan 1 2025)
-proc checkDateGroup(s: string, date: DateTime): bool =
- var conditions = s
- let cs = conditions[1..conditions.len-2].splitWhitespace
- for c in cs:
- if checkCond(c, date) == false:
- return false
- return true
-
-proc checkRangeYear(s: string, date: DateTime): bool =
- var years = s.split('-')
- return date.year >= years[0] and date.year <= years[1]
-
-proc checkCond(cond: string, date: DateTime): bool =
- var condition = cond
- var invert = false
- var found = false
- if condition[0] == '!':
- condition.delete(0..0)
- invert = true
- case checkType(condition):
- of "time":
- return true
- of "year":
- if parseInt(condition) == date.year:
- found = true
- of "day":
- if condition == date.format("d") or condition == date.format("dd"):
- found = true
- of "month":
- if condition == date.format("MMM") or condition == date.format("MMMM"):
- found = true
- of "dayofweek":
- if condition == date.format("ddd") or condition == date.format("dddd"):
- found = true
- of "firstweek":
- if parseInt(condition[0..1]) == getWeek(date):
- found = true
- of "lastweek":
- if parseInt(condition[0..1]) == getWeekFromEnd(date):
- found = true
- of "dategroup":
- found = checkDateGroup(condition, date)
- of "rangeyear":
- found = checkRangeYear(condition, date)
- of "unknown":
- echo "ERROR: Unknown condition: ", condition
- if invert == true:
- return not found
- return found
-
# Splits a string on whitespace, but maintains whitespace in date groups
proc splitWhitespaceExceptParens(str:string): seq[string] =
- var result: seq[string] = newSeq[string]()
+ var output: seq[string] = newSeq[string]()
var inParens = false
for token in str.splitWhitespace:
if inParens:
- result.add(result.pop & ' ' & token)
+ output.add(output.pop & ' ' & token)
else:
- result.add(token)
+ output.add(token)
for c in token:
if c == '(': inParens = true
if c == ')': inParens = false
- return result
+ return output
# Returns Table[conditionType, seq of conditions]
proc parseDateToTable(date:string): Table[string,seq[string]] =
@@ -177,7 +91,6 @@ proc getEvents(cal: seq[string], date: DateTime): seq[string] =
for k,cs in conditions:
var keyMatched = false
for c in cs:
- #var cond = c.toLower.capitalizeAscii
if checkCond(c, date) == true:
keyMatched = true
break