Compare commits

..

No commits in common. "main" and "0.2.0" have entirely different histories.
main ... 0.2.0

7 changed files with 36 additions and 63 deletions

View File

@ -1,7 +1,5 @@
import cliutils, docopt, json, logging, os, nre, random, sequtils, import daemonize, docopt, json, os, nre, sequtils, times, timeutils
times, timeutils from posix import SIGTERM, SIGHUP, signal, kill
from posix import SIGTERM, SIGHUP, signal, kill, Pid
import strutils except toUpper import strutils except toUpper
from unicode import toUpper from unicode import toUpper
@ -11,23 +9,32 @@ type
DPConfig = tuple[planDir, dateFmt, pidfile, logfile, errfile: string, DPConfig = tuple[planDir, dateFmt, pidfile, logfile, errfile: string,
notificationSecs: int] notificationSecs: int]
const VERSION = "0.3.3" CombinedConfig = object
const NOTE_TITLE = "Daily Notifier v" & VERSION docopt: Table[string, Value]
json: JsonNode
proc getVal(cfg: CombinedConfig, key, default: string): string =
let argKey = "--" & key
let envKey = key.replace('-', '_').toUpper
let jsonKey = key.replace(re"(-\w)", proc (m: RegexMatch): string = ($m)[1..1].toUpper)
if cfg.docopt[argKey]: return $cfg.docopt[argKey]
elif existsEnv(envKey): return getEnv(envKey)
elif cfg.json.hasKey(jsonKey): return cfg.json[jsonKey].getStr
else: return default
const VERSION = "0.2.0"
const timeFmt = "HH:mm" const timeFmt = "HH:mm"
var args: Table[string, Value] var args: Table[string, Value]
var cfg: DPConfig var cfg: DPConfig
let appName = getAppFilename()
let soundsDir: string = appName[0..(appName.len-15)] & "sounds/navi"
let soundFiles = seqUtils.toSeq(walkFiles(soundsDir & "/*"))
randomize()
proc parseDailyPlan(filename: string): seq[PlanItem] = proc parseDailyPlan(filename: string): seq[PlanItem] =
debug "Parsing daily plan file: " & filename
result = @[] result = @[]
if not existsFile(filename): return
var parseState = BeforeHeading var parseState = BeforeHeading
let planItemRe = re"\s*(\d{4})\s+(.*)" let planItemRe = re"\s*(\d{4})\s+(.*)"
const timeFmt = "HHmm" const timeFmt = "HHmm"
@ -49,27 +56,16 @@ proc parseDailyPlan(filename: string): seq[PlanItem] =
of AfterPlans: break of AfterPlans: break
else: break else: break
debug "Found " & $result.len & " items."
proc doAndLog(cmd: string): void =
debug "Executing '" & cmd & "'"
discard execShellCmd(cmd)
proc notifyDailyPlanItem(item: PlanItem): void = proc notifyDailyPlanItem(item: PlanItem): void =
let desc = item.time.format(timeFmt) & " - " & item.note
debug "Notifying: " & desc
let soundFile = soundFiles[random(soundFiles.len)]
case hostOS case hostOS
of "macosx": of "macosx":
doAndLog "osascript -e 'display notification \"" & discard execShellCmd("osascript -e 'display notification \"" &
desc & "\" with title \"" & NOTE_TITLE & "\"'" item.time.format(timeFmt) & " - " & item.note &
doAndLog "ogg123 \"" & soundFile & "\"" "\" with title \"Daily Notifier v" & VERSION &
"\" sound name \"default\"'")
of "linux": of "linux":
doAndLog "notify-send '" & NOTE_TITLE & "' '" & desc & "'" discard execShellCmd("notify-send 'Daily Notifier v" & VERSION & "' '" & item.note & "'")
doAndLog "paplay \"" & soundFile & "\""
else: quit("Unsupported host OS: '" & hostOS & "'.") else: quit("Unsupported host OS: '" & hostOS & "'.")
@ -77,15 +73,13 @@ proc loadConfig(s: cint): void {. exportc, noconv .} =
# Find and parse the .dailynotificationrc file # Find and parse the .dailynotificationrc file
let rcLocations = @[ let rcLocations = @[
if args["--config"]: $args["--config"] else:"", if args["--config"]: $args["<cfgFile>"] else:"",
".dailynotificationrc", $getEnv("DAILY_NOTIFICATION_RC"), ".dailynotificationrc", $getEnv("DAILY_NOTIFICATION_RC"),
$getEnv("HOME") & "/.dailynotificationrc"] $getEnv("HOME") & "/.dailynotificationrc"]
var rcFilename: string = var rcFilename: string =
foldl(rcLocations, if len(a) > 0: a elif existsFile(b): b else: "") foldl(rcLocations, if len(a) > 0: a elif existsFile(b): b else: "")
debug "Reloading config file from " & rcFilename
var jsonCfg: JsonNode var jsonCfg: JsonNode
try: jsonCfg = parseFile(rcFilename) try: jsonCfg = parseFile(rcFilename)
except: jsonCfg = newJObject() except: jsonCfg = newJObject()
@ -106,35 +100,22 @@ proc loadConfig(s: cint): void {. exportc, noconv .} =
proc mainLoop(args: Table[string, Value]): void = proc mainLoop(args: Table[string, Value]): void =
debug "Started daemon main loop."
loadConfig(0) loadConfig(0)
signal(SIGHUP, loadConfig) signal(SIGHUP, loadConfig)
var curDay: TimeInfo = getLocalTime(fromSeconds(0)) var curDay: TimeInfo = getLocalTime(fromSeconds(0))
var todaysFile = "nofile"
var lastModTime = fromSeconds(0)
var todaysItems: seq[PlanItem] = @[] var todaysItems: seq[PlanItem] = @[]
while true: while true:
# Check the date # Check the date
let now = getLocalTime(getTime()) let now = getLocalTime(getTime())
let today = startOfDay(now) var today = startOfDay(now)
# If we need to change day, look for a new file. # Load today's plan items
if today != curDay: if today != curDay:
curDay = today curDay = today
todaysFile = cfg.planDir & "/" & today.format(cfg.dateFmt) & "-plan.md" let todaysPlanFile =
todaysItems = @[] cfg.planDir & "/" & today.format(cfg.dateFmt) & "-plan.md"
lastModTime = fromSeconds(0) todaysItems = parseDailyPlan(todaysPlanFile)
# Check for our plan file.
if fileExists(todaysFile):
let fileModTime = getLastModificationTime(todaysFile)
# Check if the file has been modified or we have no itmes
if fileModTime > lastModTime or todaysItems.len == 0:
lastModTime = fileModTime
todaysItems = parseDailyPlan(todaysFile)
# Check to see if any items are happening soon. # Check to see if any items are happening soon.
let cutoff = now + seconds(cfg.notificationSecs) let cutoff = now + seconds(cfg.notificationSecs)
@ -174,7 +155,6 @@ Options:
-h --help Print this usage information. -h --help Print this usage information.
-s --notification-seconds <sec> -s --notification-seconds <sec>
-v --verbose Enable verbose output.
Notification period for plan items. Notification period for plan items.
@ -188,25 +168,18 @@ Options:
loadConfig(0) loadConfig(0)
addHandler(newConsoleLogger(
if args["--verbose"]: lvlAll
else: lvlInfo))
if args["start"]: if args["start"]:
# Start our daemon process (if needed) # Start our daemon process (if needed)
info "daily_notifier: Starting daemon." daemonize(cfg.pidfile, "/dev/null", cfg.logfile, cfg.errfile):
let childPid = daemonize(cfg.pidfile, "/dev/null", cfg.logfile, cfg.errfile, proc(): void = mainLoop(args)) mainLoop(args)
if childPid == 0: quit(QuitSuccess) # We are the child... don't need to do anything else.
info "daily_notifier: Started, pid: " & $childPid
elif args["stop"] or args["reconfigure"]: elif args["stop"] or args["reconfigure"]:
if not fileExists(cfg.pidfile): if not fileExists(cfg.pidfile):
info "daily_notifier: not running" echo "daily_notifier is not running"
quit(QuitSuccess) quit(QuitSuccess)
let pid: Pid = cast[Pid] (parseInt(readFile(cfg.pidfile).strip)) let pid = parseInt(readFile(cfg.pidfile).strip)
info "daily_notifier: Killing process " & $pid
if args["stop"]: discard kill(pid, SIGTERM) if args["stop"]: discard kill(pid, SIGTERM)
else: discard kill(pid, SIGHUP) else: discard kill(pid, SIGHUP)

View File

@ -1,6 +1,6 @@
# Package # Package
version = "0.3.3" version = "0.2.0"
author = "Jonathan Bernard" author = "Jonathan Bernard"
description = "Little programs that reads my daily plan and notifies me of upcoming events." description = "Little programs that reads my daily plan and notifies me of upcoming events."
license = "MIT" license = "MIT"
@ -8,5 +8,5 @@ bin = @["daily_notifier", "deploy_plans_via_ftp"]
# Dependencies # Dependencies
requires @["nim >= 0.18.0", "docopt", "timeutils", "tempfile", "cliutils"] requires @["nim >= 0.15.0", "docopt", "timeutils", "tempfile", "daemonize"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.