aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md14
-rw-r--r--src/soon.nim12
-rw-r--r--src/soon/agenda.nim52
-rw-r--r--src/soon/parsetime.nim59
-rw-r--r--src/soon/typechecker.nim6
-rw-r--r--tests/tparsetime.nim57
6 files changed, 168 insertions, 32 deletions
diff --git a/README.md b/README.md
index 1610fa6..f671f7b 100644
--- a/README.md
+++ b/README.md
@@ -14,11 +14,11 @@ In terms of features and complexity, Soon lies between [When](https://www.lighta
# Quick Start
-1. Run `soon -e`. This will generate a config and calendar file under $XDG_CONFIG_HOME/soon and open the calendar file in your editor.
+1. Run `soon -e` to edit your default calendar. On first run, soon will generate a config and calendar file under $XDG_CONFIG_HOME/soon.
-2. Check the example calendar and add some events/todos. You can just add `J%2, Hello World` for an event that repeats every other day if you want to quickly see how it looks.
+2. Check the example calendar and add some events/todos.
-3. Run `soon` to see your agenda. With no options it will show you the next 14 days of events as well as your todos and deadlines.
+3. Run `soon` to see your agenda for the next 14 days. Run `soon -cm` to print a reference calendar and your agenda for the next month.
4. Run `soon -i` to start interactive mode and archive events or todos. Select events/todos with HJKL and archive them with Space. Esc to exit.
@@ -39,7 +39,7 @@ Fri !13, Every Friday except the 13th
# Ranges
(Jul 7 2024)-(Jul 13 2024), Shark Week
July 7-13 2024, Shark Week
-Mon-Fri 9am-5pm, What a way to make a living
+Mon-Fri 9am-5pm, Work all day
# Uncommon recurrence patterns
J%3, Every third day (See: Advanced Recurrence)
@@ -130,7 +130,7 @@ May 1L Mon, Memorial Day (Last Monday of May)
You can express times in 24h format or AM/PM format like this. Your agenda will be sorted by the start time of events.
```
-Fri 06:00, With no am/pm, 24h time is assumed
+Fri 16:00, With no am/pm, 24h time is assumed
Fri 12:00am, Midnight
Fri 12pm, Noon
@@ -166,9 +166,7 @@ Julian date modulus is weird, but it's a concise way to express multiple uncommo
#### Footnote for astronomers and time nerds
-The actual MJD is based on UTC, so Soon technically uses a *rounded*, modified Julian Day because it counts days since 1858-11-17 in your local time zone. This doesn't matter in practice. It's just a number that goes up by 1 every day.
-
-Soon's MJD is off by 1 from When. When starts counting from JD 1 while Soon more correctly starts counting from JD 0.
+The actual MJD is based on UTC, so Soon technically uses a *rounded*, modified Julian Day because it counts days since 1858-11-17 in your local time zone. This doesn't matter in practice. It's just a number that goes up by 1 every day. Soon's MJD is off by 1 from When. When starts counting from JD 1 while Soon more correctly starts counting from JD 0.
## Todo List
diff --git a/src/soon.nim b/src/soon.nim
index cede38c..2c5f109 100644
--- a/src/soon.nim
+++ b/src/soon.nim
@@ -35,7 +35,7 @@ proc createDefaultCalendarFile(file: string) =
if not fileExists(file):
writeFile(file, "# This is your first calendar file. Put events and todos in here.")
-proc getCalendarFileLines(file: string): seq[string] =
+proc getFileLines(file: string): seq[string] =
let f = open(file)
for line in f.lines:
if line == "": continue
@@ -43,11 +43,13 @@ proc getCalendarFileLines(file: string): seq[string] =
result.add(line)
f.close()
-proc processCalendars(calendarPath: string, past: int, future: int) =
- var calendar = newSeq[string]()
- for file in os.walkDirRec(calendarPath):
+proc readAllSoonFiles(path: string): seq[string] =
+ for file in os.walkDirRec(path):
if file.toLower().endsWith(".soon"):
- calendar = calendar.concat(getCalendarFileLines(file))
+ result = result.concat(getFileLines(file))
+
+proc processCalendars(path: string, past: int, future: int) =
+ var calendar = readAllSoonFiles(path)
agenda.printAgenda(calendar, past, future)
todos.printTodos(calendar)
diff --git a/src/soon/agenda.nim b/src/soon/agenda.nim
index 4288f0a..59bbe12 100644
--- a/src/soon/agenda.nim
+++ b/src/soon/agenda.nim
@@ -1,5 +1,10 @@
-import std/[times, strutils, strbasics, strscans, tables]
-import conditionchecker, typechecker
+import std/[times, strutils, strbasics, strscans, tables, algorithm, re]
+import conditionchecker, typechecker, parsetime
+
+type Event = tuple
+ name: string
+ time: DateTime
+ attachments: seq[string]
# Splits a string on whitespace, but maintains whitespace in date groups
proc splitWhitespaceExceptParens(str:string): seq[string] =
@@ -26,8 +31,7 @@ proc parseDateToTable(date:string): Table[string, seq[string]] =
table[condType].add(c)
return table
-proc allConditionsMet(dateConditions: string, date: DateTime): bool =
- let conditions = parseDateToTable(dateConditions.strip)
+proc allConditionsMet(conditions: Table[string,seq[string]], date: DateTime): bool =
if conditions.hasKey("unknown"): return false
for k,cs in conditions:
var keyMatched = false
@@ -39,30 +43,48 @@ proc allConditionsMet(dateConditions: string, date: DateTime): bool =
return false
return true
+#proc formatTime(time: string): DateTime =
+# let (start, end) = time.split("-", maxsplit = 1)
+# var matches: array[1, string]
+# discard find(condition, re"0?(\d{1,2})(:)?(\d{2})?([AaPp][Mm])?(\-(\d{1,2})(:)?(\d{2})?([AaPp][Mm])?)?", matches)
+
+
# Returns Table[event, attachments]
-proc getEventsForDate(cal: seq[string], date: DateTime): Table[string, seq[string]] =
+proc getEventsForDate(cal: seq[string], date: DateTime): seq[Event] =
var lastInsertion = ""
for line in cal:
var (success, dateConditions, event) = scanTuple(line, "$*,$*")
if success:
- if allConditionsMet(dateConditions, date):
+ dateConditions.strip
+ let conditions = parseDateToTable(dateConditions)
+ if allConditionsMet(conditions, date):
event.strip
- result[event] = @[]
+ if conditions.hasKey("time"):
+ echo conditions["time"]
+ #echo formatTime(conditions["time"])
+ result.add((event, now(), @[]))
lastInsertion = event
elif line.strip[0] == '+':
- if result.hasKey(lastInsertion):
- result[lastInsertion].add(line)
+ if result[result.high].name == lastInsertion:
+ result[result.high].attachments.add(line)
elif line[0..1] == "- ":
lastInsertion = ""
else:
echo "WARNING: Cannot parse line: ", line
-proc printEvents(cal: seq[string], date: DateTime, format: string) =
- let events = getEventsForDate(cal, date)
+#proc sortTable(t: Table[string, seq[string]]) =
+# for
+
+# Check nim-lang algorithm basic usage
+proc compare(a, b: string): int =
+ cmp(a.len, b.len)
+
+proc printDaysEvents(cal: seq[string], date: DateTime, format: string) =
+ var events = getEventsForDate(cal, date)
if events.len > 0:
- for e in events.keys:
- echo date.format(format), " ", e
- for attachment in events[e]:
+ for event in events:
+ echo date.format(format), " ", event.name
+ for attachment in event.attachments:
let space = date.format(format).len + 2
echo attachment.indent(space)
@@ -71,4 +93,4 @@ proc printAgenda*(calendar: seq[string], past: int, future: int) =
var today = dateTime(now.year, now.month, now.monthday, 00, 00, 00, 00, local())
for i in past..future:
let curDay = today + i.days
- printEvents(calendar, curDay, "ddd yyyy MMM dd")
+ printDaysEvents(calendar, curDay, "ddd yyyy MMM dd")
diff --git a/src/soon/parsetime.nim b/src/soon/parsetime.nim
new file mode 100644
index 0000000..5cfa245
--- /dev/null
+++ b/src/soon/parsetime.nim
@@ -0,0 +1,59 @@
+import strutils, tables, re, times
+
+proc flipAmPm(s : string): string =
+ case s
+ of "am": return "pm"
+ of "pm": return "am"
+ else: return "am"
+
+proc fixTwelveAm(hour: int, meridiem: string): int =
+ if hour == 12 and meridiem == "am":
+ return 0
+ if hour == 24:
+ return 0
+ return hour
+
+# Returns (hour, minute, meridium)
+proc parseString(t: string): (int, int, string) =
+ var matches: array[3, string]
+ discard find(t, re"^(\d{1,2}):?(\d{2})?([PpAa][Mm])?", matches)
+ var
+ hour = parseInt(matches[0])
+ minute = 0
+ meridiem = matches[2]
+ if matches[1] != "": minute = parseInt(matches[1])
+ hour = fixTwelveAm(hour, meridiem)
+ if hour > 12:
+ hour = hour - 12
+ meridiem = "pm"
+ return (hour, minute, meridiem)
+
+proc getMeridiem(startHour: int, startMin: int, endTime: string): string =
+# Determine meridium of the left hand side of a time range when it isn't specified
+ var (endHour, endMinute, endMeridiem) = parseString(endTime)
+ if (startHour < endHour and endHour == 12):
+ return "am"
+ if (startHour < endHour) or (startHour == endHour and startMin < endMinute):
+ return endMeridiem
+ elif startHour > endHour:
+ return flipAmPm(endMeridiem)
+
+proc timeToStr(time: int): string =
+ result = intToStr(time)
+ if result.len == 1:
+ result = "0" & result
+
+proc toDateTime*(time: string): DateTime =
+# Returns a DateTime for any time string like 9am. 09:00, or 9-10pm
+ let splitTime = time.split("-", maxsplit = 1)
+ let startTime = splitTime[0]
+ var (hour, minute, meridiem) = parseString(startTime)
+ if meridiem == "" and splitTime.len > 1:
+ meridiem = getMeridiem(hour, minute, splitTime[1])
+ var sHour = timeToStr(hour)
+ var sMin = timeToStr(minute)
+ result = parse(sHour & sMin, "HHmm", utc())
+ if meridiem == "pm" and hour <= 12:
+ result = result + 12.hours
+ if result.monthday == 2:
+ result = result - 1.days
diff --git a/src/soon/typechecker.nim b/src/soon/typechecker.nim
index 34e5723..0163c9f 100644
--- a/src/soon/typechecker.nim
+++ b/src/soon/typechecker.nim
@@ -32,11 +32,9 @@ proc checkType*(c: string): string =
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"^\d{1,2}(:\d{2})?[AaPp]\.?[Mm](-\d{1,2}(:\d{2})?[AaPp]\.?[Mm])?") > -1: return "time"
- if find(cond, re"^\(?\s*[Jj]\s*%\s*\d+\s*([-+]\s*\d+)?\s*\)?$") > -1: return "julian"
+ if find(cond, re"^\(?\s*[Jj]\s*%\s*\d+\s*([-+]\s*\d+)?\s*\)?$") > -1: return "julian"
+ if find(cond, re"^\d{1,2}(:\d{2})?([AaPp][Mm])?(-\d{1,2}(:\d{2})?)?([AaPp][Mm])?$") > -1: return "time"
cond = cond.toLower.capitalizeAscii
if cond in MONTHS: return "month"
if cond in WEEKDAYS: return "weekday"
-
return checkForRanges(cond)
diff --git a/tests/tparsetime.nim b/tests/tparsetime.nim
new file mode 100644
index 0000000..e49868c
--- /dev/null
+++ b/tests/tparsetime.nim
@@ -0,0 +1,57 @@
+import soon/parsetime
+import std/[unittest]
+import std/[times]
+
+test "Parse single times":
+ check toDateTime("9") == parse("0900", "HHmm", utc())
+ check toDateTime("9am") == parse("0900", "HHmm", utc())
+ check toDateTime("9:00") == parse("0900", "HHmm", utc())
+ check toDateTime("9:30") == parse("0930", "HHmm", utc())
+ check toDateTime("09:00") == parse("0900", "HHmm", utc())
+ check toDateTime("09:30") == parse("0930", "HHmm", utc())
+ check toDateTime("9:00am") == parse("0900", "HHmm", utc())
+ check toDateTime("9:30pm") == parse("2130", "HHmm", utc())
+ check toDateTime("09:00am") == parse("0900", "HHmm", utc())
+ check toDateTime("09:00pm") == parse("2100", "HHmm", utc())
+ check toDateTime("09:30am") == parse("0930", "HHmm", utc())
+ check toDateTime("09:30pm") == parse("2130", "HHmm", utc())
+ check toDateTime("17") == parse("1700", "HHmm", utc())
+ check toDateTime("24") == parse("0000", "HHmm", utc())
+ check toDateTime("17:00") == parse("1700", "HHmm", utc())
+ check toDateTime("12:00") == parse("1200", "HHmm", utc())
+ check toDateTime("24:00") == parse("0000", "HHmm", utc())
+ check toDateTime("00:00") == parse("0000", "HHmm", utc())
+ check toDateTime("01:00") == parse("0100", "HHmm", utc())
+
+
+
+test "Parse time ranges":
+ check toDateTime("9-1am") == parse("2100", "HHmm", utc())
+ check toDateTime("9am-1am") == parse("0900", "HHmm", utc())
+ check toDateTime("9:00-1:00am") == parse("2100", "HHmm", utc())
+ check toDateTime("9:30-1:30am") == parse("2130", "HHmm", utc())
+ check toDateTime("09:00-01:00am") == parse("2100", "HHmm", utc())
+ check toDateTime("09:30-01:30am") == parse("2130", "HHmm", utc())
+ check toDateTime("9:00am-1:00am") == parse("0900", "HHmm", utc())
+ check toDateTime("9:30pm-01:00am") == parse("2130", "HHmm", utc())
+ check toDateTime("09:00am-01:00am") == parse("0900", "HHmm", utc())
+ check toDateTime("09:00pm-01:00am") == parse("2100", "HHmm", utc())
+ check toDateTime("09:30am-01:30am") == parse("0930", "HHmm", utc())
+ check toDateTime("09:30pm-01:30am") == parse("2130", "HHmm", utc())
+ check toDateTime("17-1am") == parse("1700", "HHmm", utc())
+ check toDateTime("17:00-1:00am") == parse("1700", "HHmm", utc())
+ check toDateTime("12:00-1:00am") == parse("0000", "HHmm", utc())
+ check toDateTime("12-1:00am") == parse("0000", "HHmm", utc())
+ check toDateTime("00:00-01:00am") == parse("0000", "HHmm", utc())
+ check toDateTime("11-12pm") == parse("1100", "HHmm", utc())
+ check toDateTime("11-12am") == parse("2300", "HHmm", utc())
+ check toDateTime("11-12") == parse("1100", "HHmm", utc())
+ check toDateTime("9pm-12am") == parse("2100", "HHmm", utc())
+ check toDateTime("11:00-13:00") == parse("1100", "HHmm", utc())
+ check toDateTime("12:00-13:00") == parse("1200", "HHmm", utc())
+ check toDateTime("11:00-24:00") == parse("1100", "HHmm", utc())
+ check toDateTime("23:00-24:00") == parse("2300", "HHmm", utc())
+ check toDateTime("24:00-23:00") == parse("0000", "HHmm", utc())
+
+
+