aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhistoria <gravel.justness270@slmail.me>2024-07-26 15:26:41 -0400
committerhistoria <gravel.justness270@slmail.me>2024-07-26 15:26:41 -0400
commit2438fe0116bb4c8abafb4e236c148ba0f8290e6a (patch)
tree44e463b9d057d0b40c46492ece524bd93890eec3
parentf6efe2d52423a33cd9d7d5c64c26d40a4a7a2416 (diff)
downloadsoon-2438fe0116bb4c8abafb4e236c148ba0f8290e6a.tar.gz
Changed condition data structure to Table
-rw-r--r--README.md78
-rw-r--r--src/then.nim130
2 files changed, 82 insertions, 126 deletions
diff --git a/README.md b/README.md
index 3172ebf..aab7a3e 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Then is a minimalist plain text file calendar inspired by [When](https://www.lightandmatter.com/when/when.html). It has simple syntax optimized for the average scheduling case, but supports recurring cases more complex than most calendars. It optionally supports todo lists with deadlines as well.
-Then is a single binary with no dependencies optimized for people who want to manage events directly in their text editor rather than a TUI/GUI. You can columnize, group, sort, and archive events/todos in your .then file(s) to keep them neat, pretty, and human readable.
+Then is a single binary with no dependencies optimized for text nerds who want to manage events directly in their text editor rather than a TUI/GUI. You can columnize, group, sort, and archive events/todos in your .then file(s) to keep them neat, pretty, and human readable.
In terms of features and complexity, Then lies between [When](https://www.lightandmatter.com/when/when.html) and [Remind](https://dianne.skoll.ca/projects/remind/) but aims to be less verbose than either.
@@ -49,6 +49,10 @@ July 7-13 2024, Shark Week
J%3, Every third day
Fri 1W, First Friday of the month
+
+# Combine multiple features for complex recurrence
+
+Jul 10-19 2025 !(J%3-2), This event repeats July 10, 13, 16, 19
```
## Adding details to events
@@ -85,30 +89,33 @@ Jan 1-7 2025, Las Vegas convention
You can omit one end of a date range to mean before or after a certain date.
```
-Fri -(Sep 13 2025), Every Friday before Sep 13 2025
-Fri (Sep 13 2025)-, Every Friday after Sep 13 2025
+Fri -(Sep 12 2025), Every Friday before (and including) Sep 12 2025
+Fri (Sep 12 2025)-, Every Friday after (and including) Sep 12 2025
```
-## Logical Operators (`|`, `!`)
+## Logic (AND, OR, NOT)
+
+The date string is a list of conditions. Each condition of a different type (day, month, etc.) has to met for days the event takes place.
-`|` means OR.\
-`!` means NOT.\
-AND is *implied* by whitespace between conditions
+If multiple conditions of the same type exist, any of them can be satisfied.
+
+Prepend any condition with `!` to mean NOT.
```
-1 | 15, Every 1st OR 15th
-Fri !13, Every Friday AND NOT the 13th
-Sun !(Dec 25), Every Sunday AND NOT December 25
+Fri Jan, Friday AND January (Every Fri in Jan)
+1 15, Every 1st OR 15th
+Fri !13, Every Friday, NOT the 13th
+Sun !(Dec 25), Every Sunday, NOT December 25
2025 Aug 14, 2025 AND August AND the 14th
-1 2, Every 1st AND 2nd (never)
+Sat Sun Aug, Saturday OR Sundays, in August
```
## First/last week(s) of Month (`W`, `L`)
-*x*W is the *xth* set of 7 days of a month
-*x*L is the *xth*-from-last set of 7 days of a month
+xW is the xth set of 7 days of a month\
+xL is the xth-from-last set of 7 days of a month
-This is mostly useful for certain Holidays.
+This is mostly useful for certain holidays.
```
May 2W Sun, Mother's Day (Second Sunday of May)
@@ -146,30 +153,13 @@ Jul 10-19 2025 !(J%3-2), This event repeats July 10, 13, 16, 19
Julian date modulus is definitely weird, but it's a concise way to express multiple uncommon recurring patterns without increasing program complexity and syntax vocabulary for features I virtually never use. Plus it's an homage to [When](https://www.lightandmatter.com/when/when.html), and I love When.
-## Deadline Recipe (TODO: Redo this entirely)
-
-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, although I wouldn't:
-<(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?
-1. You live in an open source, offline, self-hosted cabin in the woods to escape the bloated, dystopian tech world
-2. You want to manage your schedule directly in your text editor
+1. You live in a librebooted, open source, offline cabin in the woods to escape the bloated, dystopian tech world
+2. You use text files and a terminal for every single possible thing
3. It's fun
-Then is minimal and created to meet my personal use case. If your first thought was something about mobile phone notifications or planning how to sync Then to *another* calendar program, Then is the wrong tool and you'd be overcomplicating your workflow rather than simplifying it.
+Then is minimal and created to meet my personal use case. If your first thought was planning how to sync it to Google Calendar, Then is probably the wrong tool and you'd be overcomplicating your workflow rather than simplifying it.
## Why would I use this over When?
@@ -187,21 +177,19 @@ I love [Remind](https://dianne.skoll.ca/projects/remind/)! It is incredibly powe
**Remind**: `REM Jan 1 MSG New Years!`\
**then**: `Jan 1, New Years!`
-If you deal with events with unusual recurrence patterns or conditions, Remind is arguably nicer and inarguably much more powerful:
+If you deal with events with unusual recurrence patterns or conditions, Remind is arguably nicer and inarguably much more powerful.
Every 3 days between July 10-19 2025:\
**Remind**: `REM Jul 10 2025 *3 THROUGH Jul 19 2025`\
-**then**: `Jul 10-19 !(J%3-2)`
-
-If you have any case on the edge of what Then supports, or far beyond, you should use Remind instead.
+**then**: `Jul 10-19 2025 !(J%3-2)`
# Alternatives
-If you're interested in other plain text file calendars, I highly recommend When or Remind. It's easy to get caught up yak shaving the maximalist Emacs, so Org Mode is a bit of a beast.
+If you're interested in other plain text file calendars, I highly recommend When or Remind. It's easy to get caught up yak shaving the maximalist Emacs, so Org Mode has a very different vibe.
- [When](https://www.lightandmatter.com/when/when.html) - The simplest text file calendar that's flexible enough for real world use.
- [Remind](https://dianne.skoll.ca/projects/remind/) - The opposite: The most powerful, programmable calendar program ever made (CLI or otherwise) but simple cases are easy and concise.
- - [(Better overview here.)](https://blog.thechases.com/posts/remind/)
+ - [Better overview here.](https://blog.thechases.com/posts/remind/)
- [Org Mode](https://orgmode.org) - An expansive productivity playground for text with agenda features, but also a time sink trap for tinkerers.
## See Also
@@ -212,6 +200,16 @@ If you're interested in other plain text file calendars, I highly recommend When
- [Plain Text Personal Organizer](https://danlucraft.com/blog/2008/04/plain-text-organizer/)
- [Calendar.txt](https://terokarvinen.com/2021/calendar-txt/) - A one-day per line text calendar idea inspired by [todo.txt](https://todotxt.org/). Very simple but high maintenence due to lack of repeating events, etc.
+# Todo
+
+- Todos
+- Todos with deadlines
+- TUI
+- Calendar output
+- Color
+- Julian modulus calculator (`then -j Jun 5 2025` > Outputs J and a table of common mod offsets)
+- Android app
+
# License
MIT
diff --git a/src/then.nim b/src/then.nim
index be3e813..c7c1e61 100644
--- a/src/then.nim
+++ b/src/then.nim
@@ -1,4 +1,4 @@
-import std/[times, os, strutils, strbasics, strscans, sets, re, deques], parseopt
+import std/[times, os, strutils, strbasics, strscans, sets, re, tables], parseopt
const VERSION = "0.1"
const configDir = expandTilde("~/.config/then/")
@@ -53,9 +53,12 @@ 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"
+proc checkType(c: string): string =
+ var cond = c
+ if cond[0] == '!':
+ cond.delete(0..0)
+ if find(cond, re"^\d{4}$") > -1: return "year"
+ if find(cond, re"^\d{1,2}$") > -1: return "day"
if cond in DefaultLocale.MMM.toHashSet or cond in DefaultLocale.MMMM.toHashSet: return "month"
if cond in DefaultLocale.ddd.toHashSet or cond in DefaultLocale.dddd.toHashSet: return "dayofweek"
if find(cond, re"\d{1,2}:\d{2}") > -1: return "time"
@@ -87,93 +90,48 @@ proc checkCond(cond: string, day: DateTime): bool =
echo "UNKNOWN THING ", cond
return false
-proc getConditions(dateStr: string): seq[string] =
- var conditions: seq[string]
- conditions = dateStr.rsplit(' ')
- return conditions
-
-# TODO simplify?? Would it be easier to identify conditions and replace them with TRUE/FALSE then just process the string?
-
-# Shunting yard to parse conditions, parens, and logical operators
-# Does NOT parse out individual conditions yet because some individual
-# conditions (like 1W) require context of surrounding conditions.
-# The logic around parentheses is a little weird because conditions like
-# "<(March 15)" contain parentheses that we don't want to throw out
-proc parseDateToEvents(dateStr: string): seq[string] =
- var holdingStack = initDeque[Token]()
- var resultStack = initDeque[Token]()
- var newToken: string = ""
- for c in dateStr:
- echo c
- case c:
- of '(':
- if newToken.len == 1:
- if newToken[0] == '>' or newToken[0] == '<':
- newToken.add(c)
- else
- echo "ERROR: newToken exists but we got an open paren?"
- else:
- holdingStack.addLast(Token(token: "(", precedence: 0))
- of ')':
- if newToken.len > 0: # Closing token like <(March 15). Push the token.
- resultStack.addLast(Token(token: newToken, precedence: 0))
- newToken = ""
- else: # Logical closing paren. Pop stack until (.
- while holdingStack.len > 0 and holdingStack.peekLast.token != "(":
- if holdingStack.len == 0:
- echo "UH OH, ERROR MISMATCHED PARENS"
- resultStack.addLast(holdingStack.popLast)
- discard holdingStack.popLast # Pop the (
- of '!':
- holdingStack.addLast(Token(token: "!", precedence: 3))
- of '|':
- while holdingStack.len > 0 and holdingStack.peekLast.precedence > 2:
- resultStack.addLast(holdingStack.popLast)
- holdingStack.addLast(Token(token: "|", precedence: 2))
- of ' ':
- if newToken.len > 1 and newToken[1] == '(' and newToken[newToken.len-1] != ')': # Middle of token like <(March 15).
- newToken.add(c)
- else: # End of new token, implicit &
- # Add newToken to result
- resultStack.addLast(Token(token: newToken, precedence: 0))
- newToken = ""
- # Add implicit & to holding
- while holdingStack.len > 0 and holdingStack.peekLast.precedence > 1:
- resultStack.addLast(holdingStack.popLast)
- holdingStack.addLast(Token(token:"&", precedence: 1))
- of '>','<',':','-','a'..'z','A'..'Z','0'..'9':
- newToken.add(c)
- else:
- echo "Weird character in dateStr: ", c
- discard
- # Push last token
- if newToken.len > 0:
- resultStack.addLast(Token(token: newToken, precedence: 0))
- # Clear the holding stack
- while holdingStack.len > 0:
- var t = holdingStack.popLast
- if t.token != "(":
- resultStack.addLast(t)
- echo resultStack
+# Splits a string on whitespace, but maintains whitespace in date groups
+proc splitWhitespaceExceptParens(str:string): seq[string] =
+ var result: seq[string] = newSeq[string]()
+ var inParens = false
+ for token in str.splitWhitespace:
+ if token.contains(')'): inParens = false
+ if inParens:
+ result.add(result.pop & ' ' & token)
+ else:
+ result.add(token)
+ if token.contains('('): inParens = true
+ return result
+
+proc parseDateToTable(date:string): Table[string,seq[string]] =
+ var table = initTable[string,seq[string]]()
+ let conditions = splitWhitespaceExceptParens(date)
+ for c in conditions:
+ let condType = checkType(c)
+ if not table.hasKey(condType):
+ table[condType] = @[]
+ table[condType].add(c)
+ return table
proc getEvents(cal: seq[string], day: DateTime): seq[string] =
- var events: seq[string]
+ var events: seq[string] = newSeq[string]()
for line in cal:
- var (success, dateStr, eventStr) = scanTuple(line, "$*,$*")
+ var (success, date, event) = scanTuple(line, "$*,$*")
if success:
- dateStr.strip
- eventStr.strip
- discard parseDateToEvents(dateStr)
- #let conditions = getConditions(dateStr)
- # var match = true
- # for c in conditions:
- # c = c.toLower.capitalizeAscii
- # if checkCond(c, day) == false:
- # match = false
- # break
- # if match:
- # events.add(eventStr)
+ date.strip
+ event.strip
+ let conditions = parseDateToTable(date)
+ echo conditions
+ var match = true
+ #for c in conditions:
+ # var cond = c.toLower.capitalizeAscii
+ # if checkCond(cond, day) == false:
+ # match = false
+ # break
+ #if match:
+ # events.add(event)
+ # break
else:
echo "WARNING: Cannot parse line: ", line
return events