From 1320375ebc0e87956807c0c543abe9f9a9f1dd6e Mon Sep 17 00:00:00 2001 From: historia Date: Tue, 6 Aug 2024 20:08:43 -0400 Subject: Config added. Name change to Soon. --- .gitignore | 2 +- README.md | 64 ++++++++++++++++++++-------- soon.nimble | 14 ++++++ src/soon.nim | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/then.nim | 137 ----------------------------------------------------------- then.nimble | 14 ------ 6 files changed, 197 insertions(+), 170 deletions(-) create mode 100644 soon.nimble create mode 100644 src/soon.nim delete mode 100644 src/then.nim delete mode 100644 then.nimble diff --git a/.gitignore b/.gitignore index 83f77d3..792058f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -then +soon /tests/* !/tests/*.nim !/tests/*.nims diff --git a/README.md b/README.md index 08ac0b5..7f77316 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Then +# Soon -Then is a minimalist, text file based, CLI calendar inspired by [When](https://www.lightandmatter.com/when/when.html). It has simple, concise syntax but supports recurring cases more complex than most calendars. +Soon is a minimalist, text file based, CLI calendar inspired by [When](https://www.lightandmatter.com/when/when.html). It has simple, concise syntax but supports recurring cases more complex than most calendars. - Schedule events in a text file - Concise syntax for recurring cases and date ranges @@ -11,9 +11,37 @@ Then is a minimalist, text file based, CLI calendar inspired by [When](https://w - Todo list with upcoming deadlines - Fun -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. +In terms of features and complexity, Soon 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. -# .then file syntax +# Arguments + +``` +Usage: soon [options] [commands] [file] + +Options: +-p, --past DAYS days in past to print agenda (default: 1) +-f, --future DAYS days in future to print agenda (default: 14) +-h, --help show this help +-v, --version print version + +Commands: +a print agenda +c print calendar +t print todos +e open default calendar file in $EDITOR +i open TUI in interactive mode to schedule/archive events +w, m, y print agenda for upcoming week, month, or year +j print the modified julian day +d print today's date + +Combine commands to get output in a certain order. +`soon ca` prints a calendar followed by an agenda. +`soon at` prints an agenda followed by todos. + +Check the docs at https://codeberg.org/historia/soon +``` + +# .soon file syntax ``` # COMMENT @@ -146,7 +174,7 @@ Fri 12pm-1pm, Lunch ## Modified Julian Day (`J%x+y`) - Repeat every N days -Then supports using a Modified Julian Day as `J`. `J` is the number of full days since midnight November 17, 1858. This can be used along with the modulo operator `%` to create complex recurring events. +Soon supports using a Modified Julian Day as `J`. `J` is the number of full days since midnight November 17, 1858. This can be used along with the modulo operator `%` to create complex recurring events. This condition returns **true** when the result is 0. This is the opposite of how When works! You are almost always hunting zeros with this function so it makes no sense to invert it every time. @@ -164,13 +192,13 @@ Julian date modulus is definitely weird, but it's a concise way to express multi ### Footnote for astronomers and time nerds -The actual MJD is based on UTC, so Then 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. +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. -Then's MJD is off by 1 from When. When starts counting from JD 1 while Then more correctly starts counting from JD 0. +Soon's MJD is off by 1 from When. When starts counting from JD 1 while Soon more correctly starts counting from JD 0. # Archive File -You can archive events in interactive mode with `then i`. This lets you archive an event like "Pay rent" early so you know it's done. +You can archive events in interactive mode with `soon i`. This lets you archive an event like "Pay rent" early so you know it's done. - For recurring events with no end date, only the individual instance is archived. - For events with one date, the event is removed from your calendar file. @@ -180,27 +208,27 @@ You can archive events in interactive mode with `then i`. This lets you archive ## Keep events on calendar until manually archived -In then.config, set `alwaysShowUnarchivedEvents=true` and old events will stick on your calendar until archived (regardless of the `past` setting). This means you will have to manually acknowledge every event. +In soon.config, set `alwaysShowUnarchivedEvents=true` and old events will stick on your calendar until archived (regardless of the `past` setting). This means you will have to manually acknowledge every event. -Then checks for events starting from `startDate` so you don't get recurring events showing up from the 1970s. +Soon checks for events starting from `startDate` so you don't get recurring events showing up from the 1970s. Basically, everything before this date is considered archived. # Why would I use this? -Then was created as alternative to [When](https://www.lightandmatter.com/when/when.html) with nicer syntax and todo features. It's for a small niche of people who mainly edit their schedule directly in a text file rather than a TUI. This group almost exclusively uses [Remind](https://dianne.skoll.ca/projects/remind/) or [Org Mode](https://orgmode.org). +Soon was created as alternative to [When](https://www.lightandmatter.com/when/when.html) with nicer syntax and todo features. It's for a small niche of people who mainly edit their schedule directly in a text file rather than a TUI. This group almost exclusively uses [Remind](https://dianne.skoll.ca/projects/remind/) or [Org Mode](https://orgmode.org). -Then has a few benefits over similar text file calendars: +Soon has a few benefits over similar text file calendars: -1. Then has the most concise syntax. It minimizes special characters and simplifies ranges. +1. Soon has the most concise syntax. It minimizes special characters and simplifies ranges. - **Then**: `Jul 1-7 2025, Vacation!`\ + **Soon**: `Jul 1-7 2025, Vacation!`\ **When**: `m=Jul & d>=1 & d<=7 & y=2025, Vacation!`\ **Remind**: `REM Jul 1 2025 THROUGH Jul 7 2025 MSG Vacation!` -2. Then can use an archive file to manually "complete" events like todos +2. Soon can use an archive file to manually "complete" events like todos Take the event "Change air filter", scheduled every 6 months. I might see that upcoming and archive that instance a week early to remove it from my calendar. Or I might let it slide until next weekend, in which case it sticks around until it's manually archived. -3. Then can shows a todo list and upcoming deadlines. +3. Soon can shows a todo list and upcoming deadlines. # Alternatives @@ -229,8 +257,8 @@ If you're interested in other text file calendars, I highly recommend When or Re - TUI - Calendar output - Color -- Julian modulus calculator (`then -j Jun 5 2025` > Outputs J and a table of common mod offsets) -- CalDAV/.ics export? - Numerous CLI programs store calendars with .ics files directly, so it doesn't make much sense to use Then if you need to sync to a "main" calendar. +- Julian modulus calculator (`soon -j Jun 5 2025` > Outputs J and a table of common mod offsets) +- CalDAV/.ics export? - Numerous CLI programs store calendars with .ics files directly, so it doesn't make much sense to use Soon if you need to sync to a "main" calendar. - Import? # License diff --git a/soon.nimble b/soon.nimble new file mode 100644 index 0000000..ec2e0c9 --- /dev/null +++ b/soon.nimble @@ -0,0 +1,14 @@ +# Package + +version = "0.1" +author = "historia" +description = "Minimalist, single text file calendar inspired by when." +license = "MIT" +srcDir = "src" +bin = @["soon"] + +# Dependencies + +requires "nim >= 2.0.4" + + diff --git a/src/soon.nim b/src/soon.nim new file mode 100644 index 0000000..3040f84 --- /dev/null +++ b/src/soon.nim @@ -0,0 +1,136 @@ +import std/[times, os, strutils, strbasics, strscans, sets, strtabs, tables], parseopt +import soon/[conditions, config, typechecker] + +const VERSION = "0.1" +const configDir = expandTilde("~/.config/soon/") +const defaultConfigFile = configDir & "soon.toml" +const defaultCalendarFile = configDir & "calendar.soon" + +const MONTHS = DefaultLocale.MMM.toHashSet + DefaultLocale.MMMM.toHashSet +const WEEKDAYS = DefaultLocale.ddd.toHashSet + DefaultLocale.dddd.toHashSet + +proc printHelp(badCmd: string) = + if badCmd != "": + echo badCmd & " is not a valid option\n" + echo """ +Usage: + soon [options] [commands] + +OPTIONS + -h, --help show list of CLI options + -v, --version show soon 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 "soon " & VERSION + quit() + +# Splits a string on whitespace, but maintains whitespace in date groups +proc splitWhitespaceExceptParens(str:string): seq[string] = + var output: seq[string] = newSeq[string]() + var inParens = false + for token in str.splitWhitespace: + if inParens: + output.add(output.pop & ' ' & token) + else: + output.add(token) + for c in token: + if c == '(': inParens = true + if c == ')': inParens = false + return output + +# Returns Table[conditionType, seq of conditions] +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], date: DateTime): seq[string] = + var events: seq[string] = newSeq[string]() + for line in cal: + var (success, dateConditions, event) = scanTuple(line, "$*,$*") + if success: + dateConditions.strip + event.strip + let conditions = parseDateToTable(dateConditions) + var match = true + # key = Condition Type (e.g. month), cs = Seq of conditions + for k,cs in conditions: + var keyMatched = false + for c in cs: + if checkCond(c, date) == true: + keyMatched = true + break + if not keyMatched: + match = false + break + if match: + events.add(event) + else: + echo "WARNING: Cannot parse line: ", line + return events + +proc printEvents(cal: seq[string], date: DateTime, format: string) = + let events = getEvents(cal, date) + if events.len > 0: + for e in events: + echo date.format(format), " ", e + +proc openCal(file: string): seq[string] = + let f = open(file) + var cal: seq[string] + for line in f.lines: + if line == "": continue + if line.strip[0] == '#': continue + cal.add(line) + f.close() + return cal + +proc printCalendar() = + let cal = openCal(defaultCalendarFile) + var past = -3 + var future = 7 + let now = now() + + for i in past..future: + let curDay = now + i.days + printEvents(cal, curDay, "ddd yyyy MMM dd") + +proc parseArgs(config: StringTableRef): StringTableRef = + for kind, key, val in getopt(): + case kind + of cmdLongOption, cmdShortOption: + case key + of "help", "h": + printHelp("") + of "version", "v": + printVersion() + else: + printHelp(key) + else: + discard + +proc main() = + var config = loadConfig() + config = parseArgs(config) + var noArg: bool = true + + if noArg: + printCalendar() + +when isMainModule: + main() diff --git a/src/then.nim b/src/then.nim deleted file mode 100644 index 4f4199d..0000000 --- a/src/then.nim +++ /dev/null @@ -1,137 +0,0 @@ -import std/[times, os, strutils, strbasics, strscans, sets, strtabs, tables], parseopt -import then/[conditions, config, typechecker] - -const VERSION = "0.1" -const configDir = expandTilde("~/.config/then/") -const defaultConfigFile = configDir & "then.toml" -const defaultCalendarFile = configDir & "calendar.then" - -const MONTHS = DefaultLocale.MMM.toHashSet + DefaultLocale.MMMM.toHashSet -const WEEKDAYS = DefaultLocale.ddd.toHashSet + DefaultLocale.dddd.toHashSet - -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() - -# Splits a string on whitespace, but maintains whitespace in date groups -proc splitWhitespaceExceptParens(str:string): seq[string] = - var output: seq[string] = newSeq[string]() - var inParens = false - for token in str.splitWhitespace: - if inParens: - output.add(output.pop & ' ' & token) - else: - output.add(token) - for c in token: - if c == '(': inParens = true - if c == ')': inParens = false - return output - -# Returns Table[conditionType, seq of conditions] -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], date: DateTime): seq[string] = - var events: seq[string] = newSeq[string]() - for line in cal: - var (success, dateConditions, event) = scanTuple(line, "$*,$*") - if success: - dateConditions.strip - event.strip - let conditions = parseDateToTable(dateConditions) - var match = true - # key = Condition Type (e.g. month), cs = Seq of conditions - for k,cs in conditions: - var keyMatched = false - for c in cs: - if checkCond(c, date) == true: - keyMatched = true - break - if not keyMatched: - match = false - break - if match: - events.add(event) - else: - echo "WARNING: Cannot parse line: ", line - return events - -proc printEvents(cal: seq[string], date: DateTime, format: string) = - let events = getEvents(cal, date) - if events.len > 0: - for e in events: - echo date.format(format), " ", e - -proc openCal(file: string): seq[string] = - let f = open(file) - var cal: seq[string] - for line in f.lines: - if line == "": continue - if line.strip[0] == '#': continue - cal.add(line) - f.close() - return cal - -proc printCalendar() = - let cal = openCal(defaultCalendarFile) - var past = -3 - var future = 7 - let now = now() - - for i in past..future: - let curDay = now + i.days - printEvents(cal, curDay, "ddd yyyy MMM dd") - -proc main() = - var config = loadConfig() - echo config - 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 deleted file mode 100644 index 1b7cf9a..0000000 --- a/then.nimble +++ /dev/null @@ -1,14 +0,0 @@ -# Package - -version = "0.1" -author = "historia" -description = "Minimalist, single text file calendar inspired by when." -license = "MIT" -srcDir = "src" -bin = @["then"] - -# Dependencies - -requires "nim >= 2.0.4" - - -- cgit v1.2.3