Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
915c5b1ea1 | |||
9d0c77c8af | |||
033862f793 | |||
bf5c0a5752 | |||
6977dfbc2c |
14
README.md
Normal file
14
README.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Personal Time Keeper
|
||||||
|
|
||||||
|
`ptk` is a small utility to log time entries. It uses a simple conceptual model
|
||||||
|
and a simple JSON data format.
|
||||||
|
|
||||||
|
A ptk timeline is made up of a series of ptk entries, or marks. Each mark has a
|
||||||
|
summary, a timestamp, and a universally unique id (generated by `ptk`).
|
||||||
|
Additionally a mark may be tagged with an arbitrary number of tags and may have
|
||||||
|
detailed notes attached to it (anything representable in plain text).
|
||||||
|
|
||||||
|
The duration of a task is calculated by taking the difference between that
|
||||||
|
task's timestamp and the one following it chronologically. The `STOP` value as
|
||||||
|
a summary serves as a sentinal to indicate that an entry has been completed
|
||||||
|
and no new entry is being tracked.
|
315
ptk.nim
315
ptk.nim
@ -3,25 +3,26 @@
|
|||||||
##
|
##
|
||||||
## Simple time keeping CLI
|
## Simple time keeping CLI
|
||||||
|
|
||||||
import algorithm, docopt, json, langutils, logging, os, sequtils, strutils,
|
import algorithm, docopt, json, langutils, logging, os, nre, sequtils,
|
||||||
tempfile, terminal, times, timeutils, uuids
|
strutils, tempfile, terminal, times, timeutils, uuids
|
||||||
|
|
||||||
import ptkutil
|
import ptkutil
|
||||||
|
|
||||||
type
|
type
|
||||||
Mark* = tuple[id: UUID, time: TimeInfo, summary: string, notes: string]
|
Mark* = tuple[id: UUID, time: TimeInfo, summary: string, notes: string, tags: seq[string]]
|
||||||
Timeline* = tuple[name: string, marks: seq[Mark]]
|
Timeline* = tuple[name: string, marks: seq[Mark]]
|
||||||
|
|
||||||
const STOP_MSG = "STOP"
|
const STOP_MSG = "STOP"
|
||||||
|
|
||||||
let NO_MARK: Mark = (
|
let NO_MARK: Mark = (
|
||||||
id: parseUUID("00000000-0000-0000-0000-000000000000"),
|
id: parseUUID("00000000-0000-0000-0000-000000000000"),
|
||||||
time: getLocalTime(getTime()),
|
time: fromSeconds(0).getLocalTime,
|
||||||
summary: "", notes: "")
|
summary: "", notes: "", tags: @[])
|
||||||
|
|
||||||
const ISO_TIME_FORMAT = "yyyy:MM:dd'T'HH:mm:ss"
|
const ISO_TIME_FORMAT = "yyyy:MM:dd'T'HH:mm:ss"
|
||||||
|
|
||||||
const TIME_FORMATS = @[
|
const TIME_FORMATS = @[
|
||||||
"HH:mm", "HH:mm:ss", "HH:mm:ss",
|
"H:mm", "HH:mm", "H:mm:ss", "HH:mm:ss",
|
||||||
"yyyy:MM:dd'T'HH:mm:ss", "yyyy:MM:dd'T'HH:mm"]
|
"yyyy:MM:dd'T'HH:mm:ss", "yyyy:MM:dd'T'HH:mm"]
|
||||||
|
|
||||||
#proc `$`*(mark: Mark): string =
|
#proc `$`*(mark: Mark): string =
|
||||||
@ -38,12 +39,19 @@ proc parseTime(timeStr: string): TimeInfo =
|
|||||||
|
|
||||||
raise newException(Exception, "unable to interpret as a date: " & timeStr)
|
raise newException(Exception, "unable to interpret as a date: " & timeStr)
|
||||||
|
|
||||||
|
proc startOfDay(ti: TimeInfo): TimeInfo =
|
||||||
|
result = ti
|
||||||
|
result.hour = 0
|
||||||
|
result.minute = 0
|
||||||
|
result.second = 0
|
||||||
|
|
||||||
template `%`(mark: Mark): JsonNode =
|
template `%`(mark: Mark): JsonNode =
|
||||||
%* {
|
%* {
|
||||||
"id": $(mark.id),
|
"id": $(mark.id),
|
||||||
"time": mark.time.format(ISO_TIME_FORMAT),
|
"time": mark.time.format(ISO_TIME_FORMAT),
|
||||||
"summary": mark.summary,
|
"summary": mark.summary,
|
||||||
"notes": mark.notes
|
"notes": mark.notes,
|
||||||
|
"tags": mark.tags
|
||||||
}
|
}
|
||||||
|
|
||||||
template `%`(timeline: Timeline): JsonNode =
|
template `%`(timeline: Timeline): JsonNode =
|
||||||
@ -63,7 +71,11 @@ proc loadTimeline(filename: string): Timeline =
|
|||||||
id: parseUUID(markJson["id"].getStr()),
|
id: parseUUID(markJson["id"].getStr()),
|
||||||
time: parse(markJson["time"].getStr(), ISO_TIME_FORMAT),
|
time: parse(markJson["time"].getStr(), ISO_TIME_FORMAT),
|
||||||
summary: markJson["summary"].getStr(),
|
summary: markJson["summary"].getStr(),
|
||||||
notes: markJson["notes"].getStr()))
|
notes: markJson["notes"].getStr(),
|
||||||
|
tags: markJson["tags"].getElems(@[]).map(proc (t: JsonNode): string = t.getStr())))
|
||||||
|
|
||||||
|
timeline.marks = timeline.marks.sorted(
|
||||||
|
proc(a, b: Mark): int = cmp(a.time, b.time))
|
||||||
|
|
||||||
return timeline
|
return timeline
|
||||||
|
|
||||||
@ -84,47 +96,64 @@ proc flexFormat(i: TimeInterval): string =
|
|||||||
|
|
||||||
return i.format(fmt)
|
return i.format(fmt)
|
||||||
|
|
||||||
proc writeMarks(marks: seq[Mark], includeNotes = false): void =
|
type WriteData = tuple[idx: int, mark: Mark, prefixLen: int, interval: TimeInterval]
|
||||||
|
|
||||||
|
proc writeMarks(timeline: Timeline, indices: seq[int], includeNotes = false): void =
|
||||||
|
let marks = timeline.marks
|
||||||
let now = getLocalTime(getTime())
|
let now = getLocalTime(getTime())
|
||||||
|
|
||||||
|
var idxs = indices.sorted(
|
||||||
|
proc(a, b: int): int = cmp(marks[a].time, marks[b].time))
|
||||||
|
|
||||||
|
let largestInterval = now - marks[idxs.first].time
|
||||||
let timeFormat =
|
let timeFormat =
|
||||||
if now - marks.first.time > 1.years: "yyyy-MM-dd HH:mm"
|
if largestInterval > 1.years: "yyyy-MM-dd HH:mm"
|
||||||
elif now - marks.first.time > 7.days: "MMM dd HH:mm"
|
elif largestInterval > 7.days: "MMM dd HH:mm"
|
||||||
elif now - marks.first.time > 1.days: "ddd HH:mm"
|
elif largestInterval > 1.days: "ddd HH:mm"
|
||||||
else: "HH:mm"
|
else: "HH:mm"
|
||||||
|
|
||||||
var intervals: seq[TimeInterval] = @[]
|
var toWrite: seq[WriteData] = @[]
|
||||||
for i in 0..<marks.len - 1: intervals.add(marks[i+1].time - marks[i].time)
|
|
||||||
intervals.add(now - marks.last.time)
|
|
||||||
|
|
||||||
var prefixLens: seq[int] = @[]
|
|
||||||
var longestPrefix = 0
|
var longestPrefix = 0
|
||||||
for i in 0..<marks.len:
|
|
||||||
let
|
|
||||||
mark = marks[i]
|
|
||||||
interval = intervals[i]
|
|
||||||
prefix = ($mark.id)[0..<8] & " " & mark.time.format(timeFormat) & " (" & interval.flexFormat & ")"
|
|
||||||
|
|
||||||
prefixLens.add(prefix.len)
|
for i in idxs:
|
||||||
|
let
|
||||||
|
interval: TimeInterval =
|
||||||
|
if (i == marks.len - 1): now - marks[i].time
|
||||||
|
else: marks[i + 1].time - marks[i].time
|
||||||
|
prefix =
|
||||||
|
($marks[i].id)[0..<8] & " " & marks[i].time.format(timeFormat) &
|
||||||
|
" (" & interval.flexFormat & ")"
|
||||||
|
|
||||||
|
toWrite.add((
|
||||||
|
idx: i,
|
||||||
|
mark: marks[i],
|
||||||
|
prefixLen: prefix.len,
|
||||||
|
interval: interval))
|
||||||
|
|
||||||
if prefix.len > longestPrefix: longestPrefix = prefix.len
|
if prefix.len > longestPrefix: longestPrefix = prefix.len
|
||||||
|
|
||||||
for i in 0..<marks.len:
|
for w in toWrite:
|
||||||
let mark = marks[i]
|
if w.mark.summary == STOP_MSG: continue
|
||||||
|
|
||||||
if mark.summary == STOP_MSG: continue
|
|
||||||
|
|
||||||
let duration = intervals[i].flexFormat
|
|
||||||
setForegroundColor(stdout, fgBlack, true)
|
setForegroundColor(stdout, fgBlack, true)
|
||||||
write(stdout, ($mark.id)[0..<8])
|
write(stdout, ($w.mark.id)[0..<8])
|
||||||
setForegroundColor(stdout, fgYellow)
|
setForegroundColor(stdout, fgYellow)
|
||||||
write(stdout, " " & mark.time.format(timeFormat))
|
write(stdout, " " & w.mark.time.format(timeFormat))
|
||||||
setForegroundColor(stdout, fgCyan)
|
setForegroundColor(stdout, fgCyan)
|
||||||
write(stdout, " (" & duration & ")")
|
write(stdout, " (" & w.interval.flexFormat & ")")
|
||||||
resetAttributes(stdout)
|
resetAttributes(stdout)
|
||||||
writeLine(stdout, spaces(longestPrefix - prefixLens[i]) & " -- " & mark.summary)
|
write(stdout, spaces(longestPrefix - w.prefixLen) & " -- " & w.mark.summary)
|
||||||
|
|
||||||
if includeNotes and len(mark.notes.strip) > 0:
|
if w.mark.tags.len > 0:
|
||||||
writeLine(stdout, spaces(longestPrefix) & mark.notes)
|
setForegroundColor(stdout, fgGreen)
|
||||||
|
write(stdout, " (" & w.mark.tags.join(", ") & ")")
|
||||||
|
resetAttributes(stdout)
|
||||||
|
|
||||||
|
writeLine(stdout, "")
|
||||||
|
|
||||||
|
if includeNotes and len(w.mark.notes.strip) > 0:
|
||||||
|
writeLine(stdout, spaces(longestPrefix) & w.mark.notes)
|
||||||
writeLine(stdout, "")
|
writeLine(stdout, "")
|
||||||
|
|
||||||
proc formatMark(mark: Mark, nextMark = NO_MARK, timeFormat = ISO_TIME_FORMAT, includeNotes = false): string =
|
proc formatMark(mark: Mark, nextMark = NO_MARK, timeFormat = ISO_TIME_FORMAT, includeNotes = false): string =
|
||||||
@ -170,6 +199,8 @@ proc doInit(timelineLocation: string): void =
|
|||||||
timelineFile.write($timeline.pretty)
|
timelineFile.write($timeline.pretty)
|
||||||
finally: close(timelineFile)
|
finally: close(timelineFile)
|
||||||
|
|
||||||
|
type ExpectedMarkPart = enum Time, Summary, Tags, Notes
|
||||||
|
|
||||||
proc edit(mark: var Mark): void =
|
proc edit(mark: var Mark): void =
|
||||||
var
|
var
|
||||||
tempFile: File
|
tempFile: File
|
||||||
@ -178,10 +209,11 @@ proc edit(mark: var Mark): void =
|
|||||||
try:
|
try:
|
||||||
(tempFile, tempFileName) = mkstemp("timestamp-mark-", ".txt", "", fmWrite)
|
(tempFile, tempFileName) = mkstemp("timestamp-mark-", ".txt", "", fmWrite)
|
||||||
tempFile.writeLine(
|
tempFile.writeLine(
|
||||||
"""# Edit the time, mark, and notes below. Any lines starting with '#' will be
|
"""# Edit the time, mark, tags, and notes below. Any lines starting with '#' will
|
||||||
# ignored. When done, save the file and close the editor.""")
|
# be ignored. When done, save the file and close the editor.""")
|
||||||
tempFile.writeLine(mark.time.format(ISO_TIME_FORMAT))
|
tempFile.writeLine(mark.time.format(ISO_TIME_FORMAT))
|
||||||
tempFile.writeLine(mark.summary)
|
tempFile.writeLine(mark.summary)
|
||||||
|
tempFile.writeLine(mark.tags.join(","))
|
||||||
tempFile.writeLine(
|
tempFile.writeLine(
|
||||||
"""# Everything from the line below to the end of the file will be considered
|
"""# Everything from the line below to the end of the file will be considered
|
||||||
# notes for this timeline mark.""")
|
# notes for this timeline mark.""")
|
||||||
@ -190,18 +222,65 @@ proc edit(mark: var Mark): void =
|
|||||||
|
|
||||||
discard os.execShellCmd "$EDITOR " & tempFileName & " </dev/tty >/dev/tty"
|
discard os.execShellCmd "$EDITOR " & tempFileName & " </dev/tty >/dev/tty"
|
||||||
|
|
||||||
var
|
var markPart = Time
|
||||||
readTime = false
|
|
||||||
readSummary = false
|
|
||||||
|
|
||||||
for line in lines tempFileName:
|
for line in lines tempFileName:
|
||||||
if strip(line)[0] == '#': continue
|
if strip(line)[0] == '#': continue
|
||||||
elif not readTime: mark.time = parseTime(line); readTime = true
|
elif markPart == Time: mark.time = parseTime(line); markPart = Summary
|
||||||
elif not readSummary: mark.summary = line; readSummary = true
|
elif markPart == Summary: mark.summary = line; markPart = Tags
|
||||||
else: mark.notes &= line
|
elif markPart == Tags:
|
||||||
|
mark.tags = line.split({',', ';'});
|
||||||
|
markPart = Notes
|
||||||
|
else: mark.notes &= line & "\x0D\x0A"
|
||||||
|
|
||||||
finally: close(tempFile)
|
finally: close(tempFile)
|
||||||
|
|
||||||
|
proc filterMarkIndices(timeline: Timeline, args: Table[string, Value]): seq[int] =
|
||||||
|
let marks = timeline.marks
|
||||||
|
result = sequtils.toSeq(0..<marks.len).filterIt(marks[it].summary != STOP_MSG)
|
||||||
|
|
||||||
|
if args["<firstId>"]:
|
||||||
|
let idx = marks.findById($args["<firstId>"])
|
||||||
|
if idx > 0: result = result.filterIt(it >= idx)
|
||||||
|
|
||||||
|
if args["<lastId>"]:
|
||||||
|
let idx = marks.findById($args["<lastId>"])
|
||||||
|
if (idx > 0): result = result.filterIt(it <= idx)
|
||||||
|
|
||||||
|
if args["--after"]:
|
||||||
|
var startTime: TimeInfo
|
||||||
|
try: startTime = parseTime($args["--after"])
|
||||||
|
except: raise newException(ValueError,
|
||||||
|
"invalid value for --after: " & getCurrentExceptionMsg())
|
||||||
|
result = result.filterIt(marks[it].time > startTime)
|
||||||
|
|
||||||
|
if args["--before"]:
|
||||||
|
var endTime: TimeInfo
|
||||||
|
try: endTime = parseTime($args["--before"])
|
||||||
|
except: raise newException(ValueError,
|
||||||
|
"invalid value for --before: " & getCurrentExceptionMsg())
|
||||||
|
result = result.filterIt(marks[it].time < endTime)
|
||||||
|
|
||||||
|
if args["--today"]:
|
||||||
|
let now = getLocalTime(getTime())
|
||||||
|
let b = now.startOfDay
|
||||||
|
let e = b + 1.days
|
||||||
|
result = result.filterIt(marks[it].time >= b and marks[it].time < e)
|
||||||
|
|
||||||
|
if args["--tags"]:
|
||||||
|
let tags = (args["--tags"] ?: "").split({',', ';'})
|
||||||
|
result = result.filter(proc (i: int): bool =
|
||||||
|
tags.allIt(marks[i].tags.contains(it)))
|
||||||
|
|
||||||
|
if args["--remove-tags"]:
|
||||||
|
let tags = (args["--remove-tags"] ?: "").split({',', ';'})
|
||||||
|
result = result.filter(proc (i: int): bool =
|
||||||
|
not tags.allIt(marks[i].tags.contains(it)))
|
||||||
|
|
||||||
|
if args["--matching"]:
|
||||||
|
let pattern = re(args["--matching"] ?: "")
|
||||||
|
result = result.filterIt(marks[it].summary.find(pattern).isSome)
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
try:
|
try:
|
||||||
let doc = """
|
let doc = """
|
||||||
@ -209,11 +288,11 @@ Usage:
|
|||||||
ptk init [options]
|
ptk init [options]
|
||||||
ptk add [options]
|
ptk add [options]
|
||||||
ptk add [options] <summary>
|
ptk add [options] <summary>
|
||||||
ptk ammend [options] <id> [<summary>]
|
ptk amend [options] <id> [<summary>]
|
||||||
ptk stop [options]
|
ptk stop [options]
|
||||||
ptk continue
|
ptk continue
|
||||||
ptk delete <id>
|
ptk delete <id>
|
||||||
ptk list [options]
|
ptk (list | ls) [options]
|
||||||
ptk sum-time --ids <ids>...
|
ptk sum-time --ids <ids>...
|
||||||
ptk sum-time [options] [<firstId>] [<lastId>]
|
ptk sum-time [options] [<firstId>] [<lastId>]
|
||||||
ptk (-V | --version)
|
ptk (-V | --version)
|
||||||
@ -221,25 +300,30 @@ Usage:
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
-f --file <file> Use the given timeline file.
|
-E --echo-args Echo the program's understanding of it's arguments.
|
||||||
-c --config <cfgFile> Use <cfgFile> as configuration for the CLI.
|
|
||||||
-t --time <time> For add and ammend, use this time instead of the current time.
|
|
||||||
-n --notes <notes> For add and ammend, set the notes for a time mark.
|
|
||||||
-V --version Print the tool's version information.
|
-V --version Print the tool's version information.
|
||||||
-e --edit Open the mark in an editor.
|
|
||||||
-a --after <after> Restrict the selection to marks after <after>.
|
-a --after <after> Restrict the selection to marks after <after>.
|
||||||
-b --before <before> Restrict the selection to marks after <before>.
|
-b --before <before> Restrict the selection to marks after <before>.
|
||||||
|
-c --config <cfgFile> Use <cfgFile> as configuration for the CLI.
|
||||||
|
-e --edit Open the mark in an editor.
|
||||||
|
-f --file <file> Use the given timeline file.
|
||||||
|
-g --tags <tags> Add the given tags (comma-separated) to 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>.
|
||||||
|
-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 --today Restrict the seelction to marks during today.
|
||||||
-v --verbose Include notes in timeline entry output.
|
-v --verbose Include notes in timeline entry output.
|
||||||
-E --echo-args Echo the program's understanding of it's arguments.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: add ptk delete [options]
|
# TODO: add ptk delete [options]
|
||||||
|
|
||||||
logging.addHandler(newConsoleLogger())
|
logging.addHandler(newConsoleLogger())
|
||||||
|
let now = getLocalTime(getTime())
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
let args = docopt(doc, version = "ptk 0.1.0")
|
let args = docopt(doc, version = "ptk 0.4.0")
|
||||||
|
|
||||||
if args["--echo-args"]: echo $args
|
if args["--echo-args"]: echo $args
|
||||||
|
|
||||||
@ -295,17 +379,17 @@ Options:
|
|||||||
|
|
||||||
if args["stop"]:
|
if args["stop"]:
|
||||||
|
|
||||||
let newMark = (
|
let newMark: Mark = (
|
||||||
id: genUUID(),
|
id: genUUID(),
|
||||||
time:
|
time: if args["--time"]: parseTime($args["--time"]) else: now,
|
||||||
if args["--time"]: parseTime($args["--time"])
|
|
||||||
else: getLocalTime(getTime()),
|
|
||||||
summary: STOP_MSG,
|
summary: STOP_MSG,
|
||||||
notes: args["--notes"] ?: "")
|
notes: args["--notes"] ?: "",
|
||||||
|
tags: (args["--tags"] ?: "").split({',', ';'}))
|
||||||
|
|
||||||
timeline.marks.add(newMark)
|
timeline.marks.add(newMark)
|
||||||
writeMarks(
|
|
||||||
marks = timeline.marks[timeline.marks.len - 2..<timeline.marks.len],
|
timeline.writeMarks(
|
||||||
|
indices = @[timeline.marks.len - 2],
|
||||||
includeNotes = args["--verbose"])
|
includeNotes = args["--verbose"])
|
||||||
echo "stopped timer"
|
echo "stopped timer"
|
||||||
|
|
||||||
@ -315,22 +399,23 @@ Options:
|
|||||||
|
|
||||||
if timeline.marks.last.summary != STOP_MSG:
|
if timeline.marks.last.summary != STOP_MSG:
|
||||||
echo "There is already something in progress:"
|
echo "There is already something in progress:"
|
||||||
writeMarks(
|
timeline.writeMarks(
|
||||||
marks = @[timeline.marks.last],
|
indices = @[timeline.marks.len - 1],
|
||||||
includeNotes = args["--verbose"])
|
includeNotes = args["--verbose"])
|
||||||
quit(0)
|
quit(0)
|
||||||
|
|
||||||
let prevMark = timeline.marks[timeline.marks.len - 2]
|
let prevMark = timeline.marks[timeline.marks.len - 2]
|
||||||
var newMark: Mark = (
|
var newMark: Mark = (
|
||||||
id: genUUID(),
|
id: genUUID(),
|
||||||
time:
|
time: if args["--time"]: parseTime($args["--time"]) else: now,
|
||||||
if args["--time"]: parseTime($args["--time"])
|
|
||||||
else: getLocalTime(getTime()),
|
|
||||||
summary: prevMark.summary,
|
summary: prevMark.summary,
|
||||||
notes: prevMark.notes)
|
notes: prevMark.notes,
|
||||||
|
tags: prevMark.tags)
|
||||||
|
|
||||||
timeline.marks.add(newMark)
|
timeline.marks.add(newMark)
|
||||||
writeMarks(marks = @[newMark], includeNotes = args["--verbose"])
|
timeline.writeMarks(
|
||||||
|
indices = @[timeline.marks.len - 1],
|
||||||
|
includeNotes = args["--verbose"])
|
||||||
|
|
||||||
saveTimeline(timeline, timelineLocation)
|
saveTimeline(timeline, timelineLocation)
|
||||||
|
|
||||||
@ -338,20 +423,21 @@ Options:
|
|||||||
|
|
||||||
var newMark: Mark = (
|
var newMark: Mark = (
|
||||||
id: genUUID(),
|
id: genUUID(),
|
||||||
time:
|
time: if args["--time"]: parseTime($args["--time"]) else: now,
|
||||||
if args["--time"]: parseTime($args["--time"])
|
|
||||||
else: getLocalTime(getTime()),
|
|
||||||
summary: args["<summary>"] ?: "",
|
summary: args["<summary>"] ?: "",
|
||||||
notes: args["--notes"] ?: "")
|
notes: args["--notes"] ?: "",
|
||||||
|
tags: (args["--tags"] ?: "").split({',', ';'}))
|
||||||
|
|
||||||
if args["--edit"]: edit(newMark)
|
if args["--edit"]: edit(newMark)
|
||||||
|
|
||||||
timeline.marks.add(newMark)
|
timeline.marks.add(newMark)
|
||||||
writeMarks(marks = @[newMark], includeNotes = args["--verbose"])
|
timeline.writeMarks(
|
||||||
|
indices = @[timeline.marks.len - 1],
|
||||||
|
includeNotes = args["--verbose"])
|
||||||
|
|
||||||
saveTimeline(timeline, timelineLocation)
|
saveTimeline(timeline, timelineLocation)
|
||||||
|
|
||||||
if args["ammend"]:
|
if args["amend"]:
|
||||||
|
|
||||||
# Note, this returns a copy, not a reference to the mark in the seq.
|
# Note, this returns a copy, not a reference to the mark in the seq.
|
||||||
let markIdx = timeline.marks.findById($args["<id>"])
|
let markIdx = timeline.marks.findById($args["<id>"])
|
||||||
@ -359,6 +445,13 @@ Options:
|
|||||||
|
|
||||||
if args["<summary>"]: mark.summary = $args["<summary>"]
|
if args["<summary>"]: mark.summary = $args["<summary>"]
|
||||||
if args["--notes"]: mark.notes = $args["<notes>"]
|
if args["--notes"]: mark.notes = $args["<notes>"]
|
||||||
|
if args["--tags"]:
|
||||||
|
mark.tags &= (args["--tags"] ?: "").split({',', ';'})
|
||||||
|
mark.tags = mark.tags.deduplicate
|
||||||
|
if args["--remove-tags"]:
|
||||||
|
let tagsToRemove = (args["--remove-tags"] ?: "").split({',', ';'})
|
||||||
|
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,
|
||||||
@ -366,13 +459,14 @@ Options:
|
|||||||
|
|
||||||
if args["--edit"]: edit(mark)
|
if args["--edit"]: edit(mark)
|
||||||
|
|
||||||
echo formatMark(
|
|
||||||
mark = mark,
|
|
||||||
timeFormat = "HH:mm",
|
|
||||||
includeNotes = args["--verbose"])
|
|
||||||
|
|
||||||
timeline.marks.delete(markIdx)
|
timeline.marks.delete(markIdx)
|
||||||
timeline.marks.insert(mark, markIdx)
|
timeline.marks.insert(mark, markIdx)
|
||||||
|
|
||||||
|
timeline.writeMarks(
|
||||||
|
indices = @[markIdx],
|
||||||
|
includeNotes = args["--verbose"])
|
||||||
|
|
||||||
|
|
||||||
saveTimeline(timeline, timelineLocation)
|
saveTimeline(timeline, timelineLocation)
|
||||||
|
|
||||||
if args["delete"]:
|
if args["delete"]:
|
||||||
@ -381,27 +475,13 @@ Options:
|
|||||||
timeline.marks.delete(markIdx)
|
timeline.marks.delete(markIdx)
|
||||||
saveTimeline(timeline, timelineLocation)
|
saveTimeline(timeline, timelineLocation)
|
||||||
|
|
||||||
if args["list"]:
|
if args["list"] or args["ls"]:
|
||||||
|
|
||||||
var marks = timeline.marks
|
var selectedIndices = timeline.filterMarkIndices(args)
|
||||||
|
|
||||||
if args["--after"]:
|
timeline.writeMarks(
|
||||||
var startTime: TimeInfo
|
indices = selectedIndices,
|
||||||
try: startTime = parseTime($args["--after"])
|
includeNotes = args["--version"])
|
||||||
except: raise newException(ValueError,
|
|
||||||
"invalid value for --after: " & getCurrentExceptionMsg())
|
|
||||||
marks = marks.filter(proc(m: Mark): bool = m.time > startTime)
|
|
||||||
|
|
||||||
if args["--before"]:
|
|
||||||
var endTime: TimeInfo
|
|
||||||
try: endTime = parseTime($args["--before"])
|
|
||||||
except: raise newException(ValueError,
|
|
||||||
"invalid value for --before: " & getCurrentExceptionMsg())
|
|
||||||
marks = marks.filter(proc(m: Mark): bool = m.time < endTime)
|
|
||||||
|
|
||||||
marks = marks.sorted(proc(a, b: Mark): int = cmp(a.time, b.time))
|
|
||||||
|
|
||||||
writeMarks(marks = marks, includeNotes = args["--version"])
|
|
||||||
|
|
||||||
if args["sum-time"]:
|
if args["sum-time"]:
|
||||||
|
|
||||||
@ -413,56 +493,25 @@ Options:
|
|||||||
if markIdx == -1:
|
if markIdx == -1:
|
||||||
warn "ptk: could not find mark for id " & id
|
warn "ptk: could not find mark for id " & id
|
||||||
elif markIdx == timeline.marks.len - 1:
|
elif markIdx == timeline.marks.len - 1:
|
||||||
intervals.add(getLocalTime(getTime()) - timeline.marks.last.time)
|
intervals.add(now - timeline.marks.last.time)
|
||||||
else:
|
else:
|
||||||
intervals.add(timeline.marks[markIdx + 1].time - timeline.marks[markIdx].time)
|
intervals.add(timeline.marks[markIdx + 1].time - timeline.marks[markIdx].time)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
||||||
var startIdx = 0
|
var indicesToSum = timeline.filterMarkIndices(args)
|
||||||
var endIdx = timeline.marks.len - 1
|
|
||||||
|
|
||||||
if args["<firstId>"]:
|
for idx in indicesToSum:
|
||||||
startIdx = max(timeline.marks.findById($args["<firstId>"]), 0)
|
let mark = timeline.marks[idx]
|
||||||
|
if idx == timeline.marks.len - 1: intervals.add(now - mark.time)
|
||||||
if args["<lastId>"]:
|
else: intervals.add(timeline.marks[idx + 1].time - mark.time)
|
||||||
let idx = timeline.marks.findById($args["<firstId>"])
|
|
||||||
if (idx > 0): endIdx = idx
|
|
||||||
|
|
||||||
if args["--after"]:
|
|
||||||
var startTime: TimeInfo
|
|
||||||
try: startTime = parseTime($args["--after"])
|
|
||||||
except: raise newException(ValueError,
|
|
||||||
"invalid value for --after: " & getCurrentExceptionMsg())
|
|
||||||
let marks = timeline.marks.filter(proc(m: Mark): bool = m.time > startTime)
|
|
||||||
|
|
||||||
let idx = timeline.marks.findById($marks.first.id)
|
|
||||||
if idx > startIdx: startIdx = idx
|
|
||||||
|
|
||||||
if args["--before"]:
|
|
||||||
var endTime: TimeInfo
|
|
||||||
try: endTime = parseTime($args["--before"])
|
|
||||||
except: raise newException(ValueError,
|
|
||||||
"invalid value for --after: " & getCurrentExceptionMsg())
|
|
||||||
let marks = timeline.marks.filter(proc(m: Mark): bool = m.time < endTime)
|
|
||||||
|
|
||||||
let idx = timeline.marks.findById($marks.last.id)
|
|
||||||
if idx < endIdx: endIdx = idx
|
|
||||||
|
|
||||||
for idx in startIdx..<min(endIdx, timeline.marks.len - 1):
|
|
||||||
if timeline.marks[idx].summary == STOP_MSG: continue # don't count stops
|
|
||||||
intervals.add(timeline.marks[idx + 1].time - timeline.marks[idx].time)
|
|
||||||
|
|
||||||
if endIdx == timeline.marks.len - 1 and
|
|
||||||
timeline.marks.last.summary != STOP_MSG:
|
|
||||||
intervals.add(getLocalTime(getTime()) - timeline.marks.last.time)
|
|
||||||
|
|
||||||
if intervals.len == 0:
|
if intervals.len == 0:
|
||||||
echo "ptk: no marks found"
|
echo "ptk: no marks found"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
let total = foldl(intervals, a + b)
|
let total = intervals.foldl(a + b)
|
||||||
echo total.flexFormat
|
echo flexFormat(total)
|
||||||
|
|
||||||
except:
|
except:
|
||||||
fatal "ptk: " & getCurrentExceptionMsg()
|
fatal "ptk: " & getCurrentExceptionMsg()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "0.1.0"
|
version = "0.4.0"
|
||||||
author = "Jonathan Bernard"
|
author = "Jonathan Bernard"
|
||||||
description = "Personal Time Keeper"
|
description = "Personal Time Keeper"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
Reference in New Issue
Block a user