aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md107
-rw-r--r--src/then.nim151
-rw-r--r--then.nimble4
3 files changed, 220 insertions, 42 deletions
diff --git a/README.md b/README.md
index e052221..51f27f8 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# then
-Then is a minimalist plain text calendar program inspired by [When](https://www.lightandmatter.com/when/when.html). It aims to have as simple as possible syntax for the average scheduling case rather than cover every possible edge case. Then supports recurring events at least as complex as average calendar apps.
+Then is a minimalist plain text file calendar inspired by [When](https://www.lightandmatter.com/when/when.html). It aims to have simple syntax for the average scheduling case. Then supports recurring events at least as complex as average calendar apps.
# .then file syntax
@@ -14,23 +14,29 @@ DATE, EVENT
- Lines beginning with `-` are additional details attached to the above event.
- Blank lines are ignored.
-Otherwise, a line is an event. Everything before the first comma is interpreted as the date, which is a **list of conditions separated by whitespace**. Parameters in the date can be written in any order.
-
+Otherwise, a line is an event. Everything before the first comma is interpreted as the date, which is a list of conditions separated by whitespace. Parameters can be written in any order.
## Simple examples
-This simple syntax should cover the vast majority of events
+This syntax should cover the vast majority of events
```
2025 Jan 1 00:00-03:00, Work party
2025 Jan 1 04:00, Go to bed
+2025 Feb 13, Project due
Jan 1, New Year's Day (Jan 1 every year)
+Tue, Taco night (Every Tuesday)
1, Mortgage due (1st of every month)
+
+Mon Wed Fri, Submit TPS Report (Every Monday, Wednesday, or Friday)
+20:00 Fri, Friday Night Magic (Every Friday at 8pm)
+09:00, Coffee (Every day at 9am)
+, Journal (Every day)
```
## Adding details to events
-Any line starting with a `-` is attached to the nearest above event. This can be used for details about an event if you don't want to put everyting on one line (address, etc.)
+Any line starting with a `-` or `+` is attached to the nearest above event. This can be used for details about an event if you don't want to put everything on one line.
```
2025 Jan 5 16:00, Doctor's Appointment
@@ -40,43 +46,47 @@ Any line starting with a `-` is attached to the nearest above event. This can be
## Logical Operators (`|`, `!`)
-Parameters can be grouped with parentheses.
-`|` means or.
-`!` means not.
-`and` is **always implied** by whitespace
+Parameters can be grouped with parentheses.\
+`|` means or.\
+`!` means not.\
+`and` is always *implied* by whitespace
```
-1 | 15, 1st OR 15th of month
-Fri !13, Every Friday, NOT the 13th
-Sun !(Dec 25), Every Sunday, NOT December 25
+1 | 15, Every 1st OR 15th
+Fri !13, Every Friday AND NOT the 13th
+Sun !(Dec 25), Every Sunday AND NOT December 25
2025 Aug 14, 2025 AND August AND the 14th
-1 2, 1st AND 2nd (i.e. never)
+1 2, Every 1st AND 2nd (i.e. never)
```
## Comparison Operators (`<`, `>`)
-`<` and `<=` can be used to mean before.
-`>` and `>=` can be used to mean after.
+`<` mean on or before.\
+`>` mean on or after.
-This lets you express start and end dates.
+This lets you express start and end dates. Note that these operators are **inclusive** of the date they operate on. I virtually always mean `<=` when writing `<` with a date, so the equals is omitted to reduce special characters.
```
-Fri <=(Sep 13 2024), Every Friday, ending September 13 2024
->(Jan 1 2025) <(Jan 15 2025), After Jan 1 2025, Before Jan 15 2025
-2025 Jan >1 <15, Same as above expressed a different way
->=(Jul 15), All days of year from July 15 to Dec 31.
+Fri <(Sep 13 2024), Every Friday, ending September 13 2024
+>(Jan 1 2025) <(Jan 15 2025), Every day from Jan 1 2025 through Jan 15 2025
+2025 Jan >1 <15, Same as above expressed a different way
+>(Jul 15), All days from July 15 to Dec 31.
```
-For groups in parentheses if a day, month, and year is given, the date will be converted to a Modified Julian Date and probably work how you expect.
+You can put a comparison operator in front of a group in these two cases:
+1. The group includes a day, month, and year
+2. The group includes a day and month
+
+For groups in parentheses if a day, month, and year is given, the date will be converted to a Modified Julian Date and works how you expect.
```
->=(Dec 24 2025) <=(Jan 4 2026), Christmas vacation - this works!
+>(Dec 24 2025) <(Jan 4 2026), Christmas vacation
```
-If groups do not include a day, month, and year, the comparison operator is applied to each parameter individually. I can't really think of a use case for this.
+If groups have only a month and day, it will match every date from Jan 1 or until Dec 31.
```
->=(Dec 24) >=(Jan 4), This would match months after December and before January (i.e. nothing)
+>(Dec 24) <(Jan 4), Christmas vacation every year
```
## First/last week(s) of Month (`W`, `L`)
@@ -93,16 +103,35 @@ May 1L Mon, Memorial Day (Last Monday of May)
## Modified Julian Date (`J%n+y`)
-Similar to When, Then supports using a Modified Julian Date as `J` (or `j`). `J` is the number of days since midnight November 17, 1858. This can be used along with the modulo operator `%` to create more complex recurring events.
+Similar to When, Then supports using a Modified Julian Date as `J`. `J` is the number of days since midnight November 17, 1858. This can be used along with the modulo operator `%` to create more complex recurring events.
```
!(J%14-5), Every other Saturday
!(J%14+2), Every other Saturday (on the other weeks)
```
-This syntax sucks but is included because it's possible. It's on the outside edge of what Then can support while still meeting the primary goal of every parameter being a condition. It's included as a last resort to allow for "every N days" when necessary.
+Note, you can't put spaces in here like `!(J % 3)` because spaces are always interpreted as AND.
+
+This syntax sucks but is included because it's possible. It's on the outside edge of what Then is intended to support support while still meeting the primary goal of every parameter being a condition. It's included as a last resort to allow for "every N days" when necessary.
+
+You technically can express dates like "Repeat every 3 days between July 10-19 2025" as `>(Jul 10 2025) <(Jul 19 2025) !(J%3-2)`, but Then syntax is optimized for simple cases and gets klugey for complex recurring events. You should consider the much more powerful [Remind](https://dianne.skoll.ca/projects/remind/) instead if you're frequently making events like this.
-You technically can express dates like "Repeat every 3 days between July 10-19 2025" as `>=(Jul 10 2025) <=(Jul 19 2025) !(J%3-2)`, but Then syntax is optimized for simple cases and gets klugey for complex recurring events. You should consider the much more powerful [Remind](https://dianne.skoll.ca/projects/remind/) instead if you're frequently making events like this.
+## Deadline Recipe
+
+There is no distinction between a scheduled event and deadline but by using a less than operator `<` combined with some frequency you might create useful deadline reminders:
+
+```
+<(Aug 15 2025), Due 8/15 - Remind me every day
+<(Aug 15 2025) Mon, Due 8/15 - Remind me every Mon
+<(Aug 15 2025) J%2, Due 8/15 - Remind me every other day
+>(Aug 10 2025) <(Aug 15 2025), Due 8/15 - Start daily reminders on 8/10/2025
+
+You technically could do this as well:
+<(Aug 15 2025) ((>(Aug 1 2025) J%2) | >(Aug 10 2025)), SomeProject
+- Due 8/15
+- Bi-daily reminders starting 8/1
+- Daily reminders starting 8/10
+```
# Why would I use this?
@@ -110,30 +139,30 @@ You technically can express dates like "Repeat every 3 days between July 10-19 2
2. You want to manage your schedule directly in your text editor
3. It's fun
-Then is minimal and created to meet my personal use case. If your first thought was planning how to sync your calendar to another program to get so-and-so functionality, Then is the wrong tool and you're probably overcomplicating your workflow rather than simplifying it.
+Then is minimal and created to meet my personal use case. If your first thought was something about how the world runs on mobile phones or planning how to sync Then to *another* Calendar program to get so-and-so functionality, Then is the wrong tool and you'd probably be overcomplicating your workflow rather than simplifying it.
## Why would I use this over When?
-I *really* like [When](https://www.lightandmatter.com/when/when.html)! 95% of my events are single dates or simple recurrences (e.g. bill due on 5th of month). Then was only created to slightly simplify When's syntax to remove special characters from average cases.
+I love [When](https://www.lightandmatter.com/when/when.html)! 95% of my events are single dates or simple recurrences (e.g. bill due on 5th of month). Then was only created to slightly simplify When's syntax to remove special characters from average cases.
-1. Parameter types are inferred. The only 4-digit string that can appear in a date is the year. `y=1984` in `when` is simplified to `1984` in `then`.
+1. Parameter types in the date are inferred. The only 4-digit string that can appear in a date is the year. `y=1984` in `when` is simplified to `1984` in `then`.
2. The `&` logical operator is implied by whitespace. `m=jan & d=1` in `when` is simplified to `Jan 1` in `then`.
In short, I think it looks nicer:
-when: `* Dec 25, Christmas` or `m=dec & d=25, Christmas`
-then: `Dec 25, Christmas`
+**when**: `* Dec 25, Christmas` or `m=dec & d=25, Christmas`\
+**then**: `Dec 25, Christmas`
-when: `m=sep & w=mon & a=1, Labor Day`
-then: `Sep Mon 1W, Labor Day`
+**when**: `m=sep & w=mon & a=1, Labor Day`\
+**then**: `Sep Mon 1W, Labor Day`
## Why would I use this over Remind?
-I *really* like [Remind](https://dianne.skoll.ca/projects/remind/)! It is incredibly powerful! I am personally not a scheduling nerd and keep a very simple calendar. Then does 1% of what Remind does, *if that*. Particularly if you deal with events with unusual recurrence patterns, Remind is nice because you won't have to convert Julian dates:
+I love [Remind](https://dianne.skoll.ca/projects/remind/)! It is incredibly powerful! I am not a scheduling nerd and keep a very simple calendar. Then does 1% of what Remind does, *if that*. Particularly if you deal with events with unusual recurrence patterns, Remind is nicer than Then:
-Every 3 days between July 10-19 2025:
-Remind: `REM Jul 10 2025 *3 THROUGH Jul 19 2025`
-then: `>=(Jul 10 2025) <=(Jul 19 2025) !(J%3-2)`
+Every 3 days between July 10-19 2025:\
+**Remind**: `REM Jul 10 2025 *3 THROUGH Jul 19 2025`\
+**then**: `>(Jul 10 2025) <(Jul 19 2025) !(J%3-2)`
If you have any case on the edge of what Then supports, or far beyond, you should use Remind instead.
@@ -142,7 +171,7 @@ If you have any case on the edge of what Then supports, or far beyond, you shoul
If you're interested in text file based calendars, the three GOATs are:
- [When](https://www.lightandmatter.com/when/when.html) - IMO, the simplest terminal calendar that's flexible enough for real world use.
-- [Remind](https://dianne.skoll.ca/projects/remind/) - A **VERY** flexible, programmable calendar format. I can't imagine a scheduling case you couldn't cover. [(Better overview here.)](https://blog.thechases.com/posts/remind/)
+- [Remind](https://dianne.skoll.ca/projects/remind/) - A *very* flexible, programmable calendar format. [(Better overview here.)](https://blog.thechases.com/posts/remind/)
- [Org Mode](https://orgmode.org) - The most flexible and expansive productivity playground for text (warning: endless time sink).
## See Also
diff --git a/src/then.nim b/src/then.nim
index b346329..67700a4 100644
--- a/src/then.nim
+++ b/src/then.nim
@@ -1 +1,150 @@
-echo "Hello world. Thanks for checking the first commit."
+import std/[times, os, strutils, strbasics, strscans, sets], parseopt
+
+const VERSION = "0.1"
+const defaultConfigDir = expandTilde("~/.config/then/")
+const defaultConfigFile = defaultConfigDir & "then.toml"
+const defaultCalendarFile = defaultConfigDir & "calendar.then"
+
+# Creates a default configuration file if it doesn't exist
+proc createDefaultConfigFile() =
+ if not fileExists(defaultConfigFile):
+ if not dirExists(defaultConfigDir):
+ createDir(defaultConfigDir)
+ writeFile(defaultConfigFile, """
+ [config]
+ directory = "~/.config/then/"
+ """)
+
+# Creates a default calendar file if it doesn't exist
+proc createDefaultCalendarFile() =
+ if not fileExists(defaultCalendarFile):
+ if not dirExists(defaultConfigDir):
+ createDir(defaultConfigDir)
+ writeFile(defaultCalendarFile, """
+ # Default calendar file
+ # Add your events here
+ """)
+
+proc printHelp(badCmd: string) =
+ if badCmd != "":
+ echo badCmd & " is not a valid option\n"
+ echo """
+Usage:
+ then [options] [commands]
+
+OPTIONS
+ -h, --help show list of CLI options
+ -v, --version show then version
+ --future=DAYS set future
+ --past=DAYS set past
+
+COMMANDS
+ a print upcoming agenda (default command)
+ e open default calendar file in $EDITOR
+ j print the modified julian day
+ """
+ quit()
+
+proc printVersion() =
+ echo "then " & VERSION
+ quit()
+
+proc checkType(cond: string): string =
+ if cond.len == 4 and parseInt(cond) != 0: return "year"
+ if cond.len in [1, 2] and parseInt(cond) != 0: return "day"
+ if cond in ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"].toHashSet: return "month"
+ return "unknown"
+
+proc checkCond(cond: string, day: DateTime): bool =
+ case checkType(cond):
+ of "year":
+ if parseInt(cond) == day.year: return true
+ of "day":
+ if parseInt(cond) == parseInt(day.format("d")): return true
+ if parseInt(cond) == parseInt(day.format("dd")): return true
+ of "month":
+ if cond == day.format("MMM"): return true
+ if cond == day.format("MMMM"): return true
+ of "unknown":
+ echo "UNKNOWN THING ", cond
+ return false
+
+proc getConditions(dateStr: string): seq[string] =
+ var conditions: seq[string]
+ conditions = dateStr.rsplit(' ')
+ return conditions
+
+proc getEvents(cal: seq[string], day: DateTime): seq[string] =
+ var events: seq[string]
+ for line in cal:
+ var (success, dateStr, eventStr) = scanTuple(line, "$+,$*")
+ if success:
+ dateStr.strip
+ eventStr.strip
+ let conditions = getConditions(dateStr)
+ var match = true
+ for c in conditions:
+ if checkCond(c, day) == false:
+ match = false
+ break
+ if match:
+ events.add(eventStr)
+ else:
+ echo "WARNING: Cannot parse line: ", line
+ return events
+
+proc printEvents(cal: seq[string], day: DateTime) =
+ let events = getEvents(cal, day)
+ if events.len > 0:
+ echo day.format("ddd yyyy MMM dd")
+ for e in events:
+ echo " - ", e
+
+proc openCal(file: string): seq[string] =
+ let f = open(file)
+ var cal: seq[string]
+ for line in f.lines:
+ cal.add(line)
+ f.close()
+ return cal
+
+proc printCalendar() =
+ let cal = openCal(defaultCalendarFile)
+ var past = 3
+ var future = 7
+ let now = now()
+
+ while past > 0:
+ let curDay = now - past.days
+ printEvents(cal, curDay)
+ dec past
+
+ printEvents(cal, now)
+
+ for i in 1..future:
+ let curDay = now + i.days
+ printEvents(cal, curDay)
+
+proc main() =
+ var past: int = 1
+ var future: int = 14
+ var noArg: bool = true
+ for kind, key, val in getopt():
+ case kind
+ of cmdLongOption, cmdShortOption:
+ noArg = false
+ case key
+ of "help", "h":
+ printHelp("")
+ of "version", "v":
+ printVersion()
+ else:
+ printHelp(key)
+ else:
+ discard
+
+ if noArg:
+ printCalendar()
+
+when isMainModule:
+ main()
diff --git a/then.nimble b/then.nimble
index 9b8755d..632f70a 100644
--- a/then.nimble
+++ b/then.nimble
@@ -1,7 +1,7 @@
# Package
-version = "0.1.0"
-author = "Historia"
+version = "0.1"
+author = "historia"
description = "Minimalist, single text file calendar inspired by when."
license = "MIT"
srcDir = "src"