Compare commits
No commits in common. "main" and "1.0.6" have entirely different histories.
@ -1 +0,0 @@
|
|||||||
nim 1.6.20
|
|
@ -1,8 +1,8 @@
|
|||||||
## Personal Time Keeping API Interface
|
## Personal Time Keeping API Interface
|
||||||
## ===================================
|
## ===================================
|
||||||
|
|
||||||
import asyncdispatch, base64, bcrypt, cliutils, docopt, httpcore, jester, json, logging,
|
import asyncdispatch, base64, bcrypt, cliutils, docopt, jester, json, logging,
|
||||||
sequtils, strutils, os, tables, times, uuids
|
ospaths, sequtils, strutils, os, tables, times, uuids
|
||||||
|
|
||||||
import nre except toSeq
|
import nre except toSeq
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ proc loadApiConfig*(json: JsonNode): PtkApiCfg =
|
|||||||
|
|
||||||
template halt(code: HttpCode,
|
template halt(code: HttpCode,
|
||||||
headers: RawHeaders,
|
headers: RawHeaders,
|
||||||
content: string) =
|
content: string): typed =
|
||||||
## Immediately replies with the specified request. This means any further
|
## Immediately replies with the specified request. This means any further
|
||||||
## code will not be executed after calling this template in the current
|
## code will not be executed after calling this template in the current
|
||||||
## route.
|
## route.
|
||||||
@ -55,16 +55,16 @@ template halt(code: HttpCode,
|
|||||||
template checkAuth(cfg: PtkApiCfg) =
|
template checkAuth(cfg: PtkApiCfg) =
|
||||||
## Check this request for authentication and authorization information.
|
## Check this request for authentication and authorization information.
|
||||||
## If the request is not authorized, this template immediately returns a
|
## If the request is not authorized, this template immediately returns a
|
||||||
## 401 Unauthotized response
|
## 401 Unauthotized response
|
||||||
|
|
||||||
var authed {.inject.} = false
|
var authed {.inject.} = false
|
||||||
var user {.inject.}: PtkUser = PtkUser()
|
var user {.inject.}: PtkUser = PtkUser()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not headers(request).hasKey("Authorization"):
|
if not request.headers.hasKey("Authorization"):
|
||||||
raiseEx "No auth token."
|
raiseEx "No auth token."
|
||||||
|
|
||||||
let headerVal = headers(request)["Authorization"]
|
let headerVal = request.headers["Authorization"]
|
||||||
if not headerVal.startsWith("Basic "):
|
if not headerVal.startsWith("Basic "):
|
||||||
raiseEx "Invalid Authorization type (only 'Basic' is supported)."
|
raiseEx "Invalid Authorization type (only 'Basic' is supported)."
|
||||||
|
|
||||||
|
@ -9,8 +9,6 @@ type
|
|||||||
Timeline* = tuple[name: string, marks: seq[Mark]]
|
Timeline* = tuple[name: string, marks: seq[Mark]]
|
||||||
## Representation of a timeline: a name and sequence of Marks.
|
## Representation of a timeline: a name and sequence of Marks.
|
||||||
|
|
||||||
OffsetFrom = enum Year, Month, Day, None
|
|
||||||
|
|
||||||
const STOP_MSG* = "STOP"
|
const STOP_MSG* = "STOP"
|
||||||
|
|
||||||
let NO_MARK*: Mark = (
|
let NO_MARK*: Mark = (
|
||||||
@ -22,26 +20,11 @@ const ISO_TIME_FORMAT* = "yyyy-MM-dd'T'HH:mm:ss"
|
|||||||
## The canonical time format used by PTK.
|
## The canonical time format used by PTK.
|
||||||
|
|
||||||
const TIME_FORMATS* = @[
|
const TIME_FORMATS* = @[
|
||||||
(fmtStr: "yyyy-MM-dd'T'HH:mm:sszzz", offsetFrom: OffsetFrom.None),
|
"yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss",
|
||||||
(fmtStr: "yyyy-MM-dd HH:mm:sszzz", offsetFrom: OffsetFrom.None),
|
"yyyy-MM-dd'T'HH:mm", "yyyy-MM-dd HH:mm",
|
||||||
(fmtStr: "yyyy-MM-dd'T'HH:mm:sszz", offsetFrom: OffsetFrom.None),
|
"MM-dd'T'HH:mm:ss", "MM-dd HH:mm:ss",
|
||||||
(fmtStr: "yyyy-MM-dd HH:mm:sszz", offsetFrom: OffsetFrom.None),
|
"MM-dd'T'HH:mm", "MM-dd HH:mm",
|
||||||
(fmtStr: "yyyy-MM-dd'T'HH:mm:ssz", offsetFrom: OffsetFrom.None),
|
"HH:mm:ss", "H:mm:ss", "H:mm", "HH:mm" ]
|
||||||
(fmtStr: "yyyy-MM-dd HH:mm:ssz", offsetFrom: OffsetFrom.None),
|
|
||||||
(fmtStr: "yyyy-MM-dd'T'HH:mm:ss", offsetFrom: OffsetFrom.None),
|
|
||||||
(fmtStr: "yyyy-MM-dd HH:mm:ss", offsetFrom: OffsetFrom.None),
|
|
||||||
(fmtStr: "yyyy-MM-dd'T'HH:mm", offsetFrom: OffsetFrom.None),
|
|
||||||
(fmtStr: "yyyy-MM-dd HH:mm", offsetFrom: OffsetFrom.None),
|
|
||||||
(fmtStr: "yyyy-MM-dd", offsetFrom: OffsetFrom.None),
|
|
||||||
(fmtStr: "yyyy-MM", offsetFrom: OffsetFrom.None),
|
|
||||||
(fmtStr: "MM-dd'T'HH:mm:ss", offsetFrom: OffsetFrom.Year),
|
|
||||||
(fmtStr: "MM-dd HH:mm:ss", offsetFrom: OffsetFrom.Year),
|
|
||||||
(fmtStr: "MM-dd'T'HH:mm", offsetFrom: OffsetFrom.Year),
|
|
||||||
(fmtStr: "MM-dd HH:mm", offsetFrom: OffsetFrom.Year),
|
|
||||||
(fmtStr: "HH:mm:ss", offsetFrom: OffsetFrom.Day),
|
|
||||||
(fmtStr: "H:mm:ss", offsetFrom: OffsetFrom.Day),
|
|
||||||
(fmtStr: "H:mm", offsetFrom: OffsetFrom.Day),
|
|
||||||
(fmtStr: "HH:mm", offsetFrom: OffsetFrom.Day) ]
|
|
||||||
## Other time formats that PTK will accept as input.
|
## Other time formats that PTK will accept as input.
|
||||||
|
|
||||||
proc getOrFail*(n: JsonNode, key: string, objName: string = ""): JsonNode =
|
proc getOrFail*(n: JsonNode, key: string, objName: string = ""): JsonNode =
|
||||||
@ -56,25 +39,8 @@ proc getIfExists*(n: JsonNode, key: string): JsonNode =
|
|||||||
|
|
||||||
proc parseTime*(timeStr: string): DateTime =
|
proc parseTime*(timeStr: string): DateTime =
|
||||||
## Helper to parse time strings trying multiple known formats.
|
## Helper to parse time strings trying multiple known formats.
|
||||||
let now = now()
|
|
||||||
|
|
||||||
for fmt in TIME_FORMATS:
|
for fmt in TIME_FORMATS:
|
||||||
try:
|
try: return parse(timeStr, fmt)
|
||||||
let parsed = parse(timeStr, fmt.fmtStr)
|
|
||||||
case fmt.offsetFrom:
|
|
||||||
of OffsetFrom.None:
|
|
||||||
return parsed
|
|
||||||
of OffsetFrom.Year:
|
|
||||||
return dateTime(now.year, parsed.month, parsed.monthday,
|
|
||||||
parsed.hour, parsed.minute, parsed.second, parsed.nanosecond,
|
|
||||||
now.timezone)
|
|
||||||
of OffsetFrom.Month:
|
|
||||||
return initDateTime(parsed.monthday, now.month, now.year,
|
|
||||||
parsed.hour, parsed.minute, parsed.second, parsed.nanosecond,
|
|
||||||
now.timezone)
|
|
||||||
of OffsetFrom.Day:
|
|
||||||
return initDateTime(now.monthday, now.month, now.year, parsed.hour,
|
|
||||||
parsed.minute, parsed.second, parsed.nanosecond, now.timezone)
|
|
||||||
except: discard nil
|
except: discard nil
|
||||||
|
|
||||||
raise newException(Exception, "unable to interpret as a date: " & timeStr)
|
raise newException(Exception, "unable to interpret as a date: " & timeStr)
|
||||||
@ -146,3 +112,5 @@ proc getLastIndex*(marks: seq[Mark]): int =
|
|||||||
while idx >= 0 and marks[idx].summary == STOP_MSG: idx -= 1
|
while idx >= 0 and marks[idx].summary == STOP_MSG: idx -= 1
|
||||||
if idx < 0: result = -1
|
if idx < 0: result = -1
|
||||||
else: result = idx
|
else: result = idx
|
||||||
|
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
const PTK_VERSION* = "1.0.14"
|
const PTK_VERSION* = "1.0.6"
|
54
ptk.nim
54
ptk.nim
@ -4,9 +4,9 @@
|
|||||||
## Simple time keeping CLI
|
## Simple time keeping CLI
|
||||||
|
|
||||||
import algorithm, docopt, json, langutils, logging, os, nre, std/wordwrap,
|
import algorithm, docopt, json, langutils, logging, os, nre, std/wordwrap,
|
||||||
sequtils, sets, strutils, sugar, tempfile, terminal, times, uuids
|
sequtils, sets, strutils, tempfile, terminal, times, uuids
|
||||||
|
|
||||||
import timeutils except `-`
|
import timeutils except `-`;
|
||||||
|
|
||||||
import private/util
|
import private/util
|
||||||
import private/api
|
import private/api
|
||||||
@ -43,7 +43,8 @@ proc writeMarks(timeline: Timeline, indices: seq[int], includeNotes = false): vo
|
|||||||
writeLine(stdout, "No marks match the given criteria.")
|
writeLine(stdout, "No marks match the given criteria.")
|
||||||
return
|
return
|
||||||
|
|
||||||
var idxs = indices.sorted((a, b) => cmp(marks[a].time, marks[b].time))
|
var idxs = indices.sorted(
|
||||||
|
proc(a, b: int): int = cmp(marks[a].time, marks[b].time))
|
||||||
|
|
||||||
let largestInterval = now - marks[idxs.first].time
|
let largestInterval = now - marks[idxs.first].time
|
||||||
let timeFormat =
|
let timeFormat =
|
||||||
@ -148,8 +149,7 @@ proc edit(mark: Mark): Mark =
|
|||||||
close(tempFile)
|
close(tempFile)
|
||||||
tempFile = nil
|
tempFile = nil
|
||||||
|
|
||||||
let editor = getEnv("EDITOR", "vim")
|
discard os.execShellCmd "$EDITOR " & tempFileName & " </dev/tty >/dev/tty"
|
||||||
discard os.execShellCmd editor & " " & tempFileName & " </dev/tty >/dev/tty"
|
|
||||||
|
|
||||||
var markPart = Time
|
var markPart = Time
|
||||||
var notes: seq[string] = @[]
|
var notes: seq[string] = @[]
|
||||||
@ -172,15 +172,15 @@ proc filterMarkIndices(timeline: Timeline, args: Table[string, Value]): seq[int]
|
|||||||
|
|
||||||
let marks = timeline.marks
|
let marks = timeline.marks
|
||||||
let now = getTime().local
|
let now = getTime().local
|
||||||
let allIndices = sequtils.toSeq(0..<marks.len).filterIt(marks[it].summary != STOP_MSG).toHashSet
|
let allIndices = sequtils.toSeq(0..<marks.len).filterIt(marks[it].summary != STOP_MSG).toSet
|
||||||
let union = args["--or"]
|
let union = args["--or"]
|
||||||
|
|
||||||
var selected =
|
var selected =
|
||||||
if union: initHashSet[int]()
|
if union: initSet[int]()
|
||||||
else: allIndices
|
else: allIndices
|
||||||
|
|
||||||
template filterMarks(curSet: HashSet[int], pred: untyped): untyped =
|
template filterMarks(curSet: HashSet[int], pred: untyped): untyped =
|
||||||
var res: HashSet[int] = initHashSet[int]()
|
var res: HashSet[int] = initSet[int]()
|
||||||
if union:
|
if union:
|
||||||
for mIdx {.inject.} in allIndices:
|
for mIdx {.inject.} in allIndices:
|
||||||
if pred: res.incl(mIdx)
|
if pred: res.incl(mIdx)
|
||||||
@ -278,9 +278,8 @@ Options:
|
|||||||
-e --edit Open the mark in an editor.
|
-e --edit Open the mark in an editor.
|
||||||
-f --file <file> Use the given timeline file.
|
-f --file <file> Use the given timeline file.
|
||||||
-g --tags <tags> Add the given tags (comma-separated) to the selected marks.
|
-g --tags <tags> Add the given tags (comma-separated) to the selected marks.
|
||||||
-G --remove-tags <tags> Remove the given tag from the selected marks.
|
-G --remove-tags <tagx> Remove the given tag from the selected marks.
|
||||||
-h --help Print this usage information.
|
-h --help Print this usage information.
|
||||||
|
|
||||||
-m --matching <pattern> Restric the selection to marks matching <pattern>.
|
-m --matching <pattern> Restric the selection to marks matching <pattern>.
|
||||||
-n --notes <notes> For add and amend, set the notes for a time mark.
|
-n --notes <notes> For add and amend, set the notes for a time mark.
|
||||||
-t --time <time> For add and amend, use this time instead of the current time.
|
-t --time <time> For add and amend, use this time instead of the current time.
|
||||||
@ -307,21 +306,27 @@ Options:
|
|||||||
quit()
|
quit()
|
||||||
|
|
||||||
# Find and parse the .ptkrc file
|
# Find and parse the .ptkrc file
|
||||||
let ptkrcLocations =
|
let ptkrcLocations = @[
|
||||||
if args["--config"]: @[$args["--config"]]
|
if args["--config"]: $args["--config"] else:"",
|
||||||
else: @[".ptkrc", $getEnv("PTKRC"), $getEnv("HOME") & "/.ptkrc"]
|
".ptkrc", $getEnv("PTKRC"), $getEnv("HOME") & "/.ptkrc"]
|
||||||
|
|
||||||
let foundPtkrcLocations =
|
var ptkrcFilename: string =
|
||||||
ptkrcLocations.filterIt(it.len > 0 and fileExists(it))
|
foldl(ptkrcLocations, if len(a) > 0: a elif existsFile(b): b else: "")
|
||||||
|
|
||||||
var cfg: JsonNode
|
var cfg: JsonNode
|
||||||
if foundPtkrcLocations.len < 1:
|
var cfgFile: File
|
||||||
|
if not existsFile(ptkrcFilename):
|
||||||
warn "ptk: could not find .ptkrc file."
|
warn "ptk: could not find .ptkrc file."
|
||||||
debug "ptk: considered the following locations:\n\t" & ptkrcLocations.join("\n\t")
|
ptkrcFilename = $getEnv("HOME") & "/.ptkrc"
|
||||||
|
try:
|
||||||
|
cfgFile = open(ptkrcFilename, fmWrite)
|
||||||
|
cfgFile.write("{\"timelineLogFile\": \"timeline.log.json\"}")
|
||||||
|
except: warn "ptk: could not write default .ptkrc to " & ptkrcFilename
|
||||||
|
finally: close(cfgFile)
|
||||||
|
|
||||||
try: cfg = parseFile(foundPtkrcLocations[0])
|
try: cfg = parseFile(ptkrcFilename)
|
||||||
except: raise newException(IOError,
|
except: raise newException(IOError,
|
||||||
"unable to read config file: " & foundPtkrcLocations[0] &
|
"unable to read config file: " & ptkrcFilename &
|
||||||
"\x0D\x0A" & getCurrentExceptionMsg())
|
"\x0D\x0A" & getCurrentExceptionMsg())
|
||||||
|
|
||||||
# Find the time log file
|
# Find the time log file
|
||||||
@ -332,7 +337,7 @@ Options:
|
|||||||
"ptk.log.json"]
|
"ptk.log.json"]
|
||||||
|
|
||||||
var timelineLocation =
|
var timelineLocation =
|
||||||
foldl(timelineLocations, if len(a) > 0: a elif fileExists(b): b else: "")
|
foldl(timelineLocations, if len(a) > 0: a elif existsFile(b): b else: "")
|
||||||
|
|
||||||
# Execute commands
|
# Execute commands
|
||||||
if args["init"]:
|
if args["init"]:
|
||||||
@ -343,7 +348,7 @@ Options:
|
|||||||
let filesToMerge = args["<timeline>"]
|
let filesToMerge = args["<timeline>"]
|
||||||
let timelines = filesToMerge.mapIt(loadTimeline(it))
|
let timelines = filesToMerge.mapIt(loadTimeline(it))
|
||||||
|
|
||||||
let names = timelines.mapIt(it.name).toHashSet
|
let names = timelines.mapIt(it.name).toSet
|
||||||
let mergedName = sequtils.toSeq(names.items).foldl(a & " + " & b)
|
let mergedName = sequtils.toSeq(names.items).foldl(a & " + " & b)
|
||||||
var merged: Timeline = (
|
var merged: Timeline = (
|
||||||
name: mergedName,
|
name: mergedName,
|
||||||
@ -381,7 +386,7 @@ Options:
|
|||||||
time: if args["--time"]: parseTime($args["--time"]) else: now,
|
time: if args["--time"]: parseTime($args["--time"]) else: now,
|
||||||
summary: STOP_MSG,
|
summary: STOP_MSG,
|
||||||
notes: args["--notes"] ?: "",
|
notes: args["--notes"] ?: "",
|
||||||
tags: (args["--tags"] ?: "").split({',', ';'}).filterIt(not it.isEmptyOrWhitespace))
|
tags: (args["--tags"] ?: "").split({',', ';'}).filterIt(not it.isNilOrWhitespace))
|
||||||
|
|
||||||
timeline.marks.add(newMark)
|
timeline.marks.add(newMark)
|
||||||
|
|
||||||
@ -423,7 +428,7 @@ Options:
|
|||||||
time: if args["--time"]: parseTime($args["--time"]) else: now,
|
time: if args["--time"]: parseTime($args["--time"]) else: now,
|
||||||
summary: args["<summary>"] ?: "",
|
summary: args["<summary>"] ?: "",
|
||||||
notes: args["--notes"] ?: "",
|
notes: args["--notes"] ?: "",
|
||||||
tags: (args["--tags"] ?: "").split({',', ';'}).filterIt(not it.isEmptyOrWhitespace))
|
tags: (args["--tags"] ?: "").split({',', ';'}).filterIt(not it.isNilOrWhitespace))
|
||||||
|
|
||||||
if args["--edit"]: newMark = edit(newMark)
|
if args["--edit"]: newMark = edit(newMark)
|
||||||
|
|
||||||
@ -485,7 +490,8 @@ Options:
|
|||||||
mark.tags = mark.tags.deduplicate
|
mark.tags = mark.tags.deduplicate
|
||||||
if args["--remove-tags"]:
|
if args["--remove-tags"]:
|
||||||
let tagsToRemove = (args["--remove-tags"] ?: "").split({',', ';'})
|
let tagsToRemove = (args["--remove-tags"] ?: "").split({',', ';'})
|
||||||
mark.tags = mark.tags.filter((t) => not anyIt(tagsToRemove, it == t))
|
mark.tags = mark.tags.filter(proc (t: string): bool =
|
||||||
|
anyIt(tagsToRemove, it == t))
|
||||||
if args["--time"]:
|
if args["--time"]:
|
||||||
try: mark.time = parseTime($args["--time"])
|
try: mark.time = parseTime($args["--time"])
|
||||||
except: raise newException(ValueError,
|
except: raise newException(ValueError,
|
||||||
|
12
ptk.nimble
12
ptk.nimble
@ -1,6 +1,6 @@
|
|||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "1.0.14"
|
version = "1.0.6"
|
||||||
author = "Jonathan Bernard"
|
author = "Jonathan Bernard"
|
||||||
description = "Personal Time Keeper"
|
description = "Personal Time Keeper"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@ -15,11 +15,11 @@ requires @[
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
"isaac >= 0.1.3",
|
"isaac >= 0.1.3",
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
"jester 0.5.0",
|
"jester 0.4.1",
|
||||||
"https://git.jdb-software.com/jdb/nim-lang-utils.git",
|
"https://git.jdb-labs.com/jdb/nim-lang-utils.git",
|
||||||
"https://git.jdb-software.com/jdb/nim-cli-utils.git >= 0.6.5",
|
"https://git.jdb-labs.com/jdb/nim-cli-utils.git",
|
||||||
"https://git.jdb-software.com/jdb/nim-time-utils.git >= 0.5.2",
|
"https://git.jdb-labs.com/jdb/nim-time-utils.git >= 0.5.2",
|
||||||
"https://git.jdb-software.com/jdb/update-nim-package-version"
|
"https://git.jdb-labs.com/jdb/update-nim-package-version"
|
||||||
]
|
]
|
||||||
|
|
||||||
task updateVersion, "Update the version of this package.":
|
task updateVersion, "Update the version of this package.":
|
||||||
|
Loading…
x
Reference in New Issue
Block a user