Added deploy helper to push plans out to FastMail hosting.
This commit is contained in:
parent
5b5ee924f9
commit
29b9b4f167
70
daily_notifier.nim
Normal file
70
daily_notifier.nim
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import docopt, os, nre, sequtils, strutils, times, timeutils
|
||||||
|
|
||||||
|
type ParseState = enum BeforeHeading, ReadingPlans, AfterPlans
|
||||||
|
|
||||||
|
type PlanItem* = tuple[time: TimeInfo, note: string]
|
||||||
|
|
||||||
|
proc parseDailyPlan(filename: string): seq[PlanItem] =
|
||||||
|
|
||||||
|
var planItems: seq[PlanItem] = @[]
|
||||||
|
var parseState = BeforeHeading
|
||||||
|
let planItemRe = re"\s*(\d{4})\s+(.*)"
|
||||||
|
let timeFmt = "HHmm"
|
||||||
|
|
||||||
|
for line in lines filename:
|
||||||
|
case parseState
|
||||||
|
|
||||||
|
of BeforeHeading:
|
||||||
|
if line.strip.startsWith("# Timeline"): parseState = ReadingPlans
|
||||||
|
|
||||||
|
of ReadingPlans:
|
||||||
|
let match = line.find(planItemRe)
|
||||||
|
if match.isSome(): planItems.add((
|
||||||
|
time: parse(match.get().captures[0], timeFmt),
|
||||||
|
note: match.get().captures[1]))
|
||||||
|
else: parseState = AfterPlans
|
||||||
|
|
||||||
|
of AfterPlans: break
|
||||||
|
else: break
|
||||||
|
|
||||||
|
return planItems
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
|
||||||
|
let doc = """
|
||||||
|
Usage:
|
||||||
|
daily_notifier [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-d --plan-directory <dir> Directory to search for plan files.
|
||||||
|
|
||||||
|
-f --plan-date-format <fmt> Date pattern for identifying plan files. This
|
||||||
|
is used in conjunction with plan directory to
|
||||||
|
idenfity files that should be parsed as plan
|
||||||
|
files.
|
||||||
|
|
||||||
|
-c --config <cfgFile> Use <cfgFile> as the source of configuration
|
||||||
|
for daily-notification.
|
||||||
|
|
||||||
|
-h --help Print this usage information.
|
||||||
|
"""
|
||||||
|
let args = docopt(doc, version = "daily_notifier 0.1.0")
|
||||||
|
|
||||||
|
if args["--help"]:
|
||||||
|
echo doc
|
||||||
|
quit()
|
||||||
|
|
||||||
|
# Find and parse the .dailynotificationrc file
|
||||||
|
let rcLocations = @[
|
||||||
|
if args["--config"]: $args["<cfgFile>"] else:"",
|
||||||
|
".dailynotificationrc", $getEnv("DAILY_NOTIFICATION_RC"),
|
||||||
|
$getEnv("HOME") & "/.dailynotificationrc"]
|
||||||
|
|
||||||
|
var ptkrcFilename: string =
|
||||||
|
foldl(rcLocations, if len(a) > 0: a elif existsFile(b): b else: "")
|
||||||
|
|
||||||
|
# Determine our plan directory and file template
|
||||||
|
# Start our daemon process (if needed)
|
||||||
|
# exit
|
||||||
|
|
@ -4,8 +4,9 @@ version = "0.1.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"
|
||||||
|
bin = @["daily_notifier", "deploy_plans_to_fastmail"]
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
requires "nim >= 0.15.0"
|
requires @["nim >= 0.15.0", "docopt", "timeutils", "tempfile"]
|
||||||
|
|
||||||
|
103
deploy_plans_to_fastmail.nim
Normal file
103
deploy_plans_to_fastmail.nim
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import json, nre, os, osproc, sequtils, sets, strutils, tempfile
|
||||||
|
|
||||||
|
proc doStep(step, cmd: string): tuple[output: TaintedString, exitCode: int] =
|
||||||
|
echo "> " & cmd
|
||||||
|
result = execCmdEx(cmd, {poUsePath})
|
||||||
|
if result.exitCode != 0:
|
||||||
|
writeLine(stderr, "Failed step [" & step &
|
||||||
|
"] Received error code: " & $result.exitCode)
|
||||||
|
quit(1)
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
let cfgFilePath = $getEnv("HOME") & "/.personal-planning.config.json"
|
||||||
|
|
||||||
|
if not existsFile(cfgFilePath):
|
||||||
|
quit("Cannot find config file: " & cfgFilePath, 2)
|
||||||
|
|
||||||
|
var cfg: JsonNode
|
||||||
|
try: cfg = parseFile(cfgFilePath)
|
||||||
|
except: quit("Could not parse as json: " & cfgFilePath, 3)
|
||||||
|
|
||||||
|
let repoUrl =
|
||||||
|
if cfg.hasKey("repo"): cfg["repo"].str
|
||||||
|
else: "_git@git.jdb-labs.com:jdb/personal-planning.git"
|
||||||
|
|
||||||
|
let ftpRoot =
|
||||||
|
if cfg.hasKey("ftp") and cfg["ftp"].hasKey("root"): cfg["ftp"]["root"].str
|
||||||
|
else: "ftp://ftp.fastmail.com/jonathan.jdbernard.com/files/personal-planning"
|
||||||
|
|
||||||
|
let ftpUsername =
|
||||||
|
if cfg.hasKey("ftp") and cfg["ftp"].hasKey("username"):
|
||||||
|
cfg["ftp"]["username"].str
|
||||||
|
else: "jonathan@jdbernard.com"
|
||||||
|
|
||||||
|
if not (cfg.hasKey("ftp") and cfg["ftp"].hasKey("password")):
|
||||||
|
quit("Could not find ftp.password in config file: " & cfgFilePath, 4)
|
||||||
|
|
||||||
|
let ftpPassword = cfg["ftp"]["password"].str
|
||||||
|
|
||||||
|
let htmlFileRe =
|
||||||
|
if cfg.hasKey("outputPattern"): re(cfg["outputPattern"].str)
|
||||||
|
else: re".* (\S+)\.html?$"
|
||||||
|
|
||||||
|
let mdFileRe =
|
||||||
|
if cfg.hasKey("inputPattern"): re(cfg["inputPattern"].str)
|
||||||
|
else: re".*/(\S+-plan)\.md?$"
|
||||||
|
|
||||||
|
const subDirs = ["daily", "weekly", "monthly", "yearly"]
|
||||||
|
|
||||||
|
var output: string
|
||||||
|
var exitCode: int
|
||||||
|
|
||||||
|
# Make a temprary directory
|
||||||
|
let tempdir = mkdtemp("personal-planning-")
|
||||||
|
|
||||||
|
# Checkout personal development repository (local clone)
|
||||||
|
discard doStep("clone repo", "git clone " & repoUrl & " " & tempdir)
|
||||||
|
|
||||||
|
for curDir in subDirs:
|
||||||
|
let fullDirPath = tempdir & "/" & curDir
|
||||||
|
let remoteOptions = " --user '" &
|
||||||
|
ftpUsername & ":" & ftpPassword & "' '" &
|
||||||
|
ftpRoot & "/" & curDir & "/' "
|
||||||
|
|
||||||
|
# List the files on the server.
|
||||||
|
(output, exitCode) = doStep("read remote files (" & curDir & ")",
|
||||||
|
"curl " & remoteOptions)
|
||||||
|
|
||||||
|
let remoteFiles = output.splitLines
|
||||||
|
.filterIt(it.find(htmlFileRe).isSome)
|
||||||
|
.mapIt(it.find(htmlFileRe).get().captures[0])
|
||||||
|
.toSet
|
||||||
|
|
||||||
|
# List the daily files locally.
|
||||||
|
let localFiles =
|
||||||
|
sequtils.toSeq(walkDir(fullDirPath))
|
||||||
|
.filterIt(it.kind == pcFile and
|
||||||
|
it.path.find(mdFileRe).isSome)
|
||||||
|
.mapIt(it.path.find(mdFileRe).get().captures[0])
|
||||||
|
.toSet
|
||||||
|
|
||||||
|
# ID the files that are new (present locally but not remotely).
|
||||||
|
let newFiles = localFiles - remoteFiles
|
||||||
|
|
||||||
|
for fileName in newFiles:
|
||||||
|
let tempPath = fullDirPath & "/temp.html"
|
||||||
|
let filePath = fullDirPath & "/" & fileName
|
||||||
|
|
||||||
|
# Compile the markdown into HTML
|
||||||
|
discard doStep("compile plan file (" & fileName & ")",
|
||||||
|
"markdown " & filePath & ".md" & " > " & tempPath)
|
||||||
|
|
||||||
|
# Concatenate the HTML template to create the final HTML
|
||||||
|
discard doStep("concatenate HTML template (" & fileName & ")",
|
||||||
|
"cat " & tempdir & "/code/start.html " & tempPath & " " &
|
||||||
|
tempdir & "/code/end.html > " & filePath & ".html")
|
||||||
|
# Upload the new file to FastMail
|
||||||
|
discard doStep("upload file to FastMail (" & fileName & ")",
|
||||||
|
"curl -T '" & filePath & ".html' " & remoteOptions)
|
||||||
|
|
||||||
|
# Delete local temp repo
|
||||||
|
echo "Deleting " & tempdir
|
||||||
|
removeDir(tempdir)
|
Loading…
x
Reference in New Issue
Block a user