Compare commits

..

1 Commits

Author SHA1 Message Date
ed3058a9c8 Initial ideas around a related-to special property. 2023-12-15 21:34:53 -06:00
4 changed files with 60 additions and 47 deletions

View File

@ -1,6 +1,6 @@
# Package # Package
version = "4.25.1" version = "4.24.0"
author = "Jonathan Bernard" author = "Jonathan Bernard"
description = "Personal issue tracker." description = "Personal issue tracker."
license = "MIT" license = "MIT"

View File

@ -70,24 +70,6 @@ proc formatIssue*(issue: Issue): string =
result &= termReset result &= termReset
proc formatPlainIssueSummary*(issue: Issue): string =
result = "$#: $# $#" % [
$issue.state,
($issue.id)[0..<6],
issue.summary ]
if issue.hasProp("delegated-to") or issue.hasProp("pending"):
var parts = newSeq[string]()
if issue.hasProp("delegated-to"):
parts.add("delegated to " & issue["delegated-to"])
if issue.hasProp("pending"):
parts.add("pendin: " & issue["pending"])
result &= "($#)" % [ parts.join("; ") ]
proc formatSectionIssue*( proc formatSectionIssue*(
issue: Issue, issue: Issue,
width: int = 80, width: int = 80,
@ -269,15 +251,7 @@ proc list(
it.hasProp("completed") and it.hasProp("completed") and
sameDay(getTime().local, it.getDateTime("completed"))) sameDay(getTime().local, it.getDateTime("completed")))
if isatty(stdout):
stdout.write ctx.formatSection(ctx.issues[state], state, "", verbose) stdout.write ctx.formatSection(ctx.issues[state], state, "", verbose)
else:
stdout.writeLine ctx.issues[state]
.mapIt(formatPlainIssueSummary(it))
.join("\n")
trace "listing complete" trace "listing complete"
return return
@ -305,14 +279,8 @@ proc list(
not (it.hasProp("hide-until") and not (it.hasProp("hide-until") and
it.getDateTime("hide-until") > getTime().local)) it.getDateTime("hide-until") > getTime().local))
if isatty(stdout):
stdout.write ctx.formatSection(visibleIssues, s, indent, verbose) stdout.write ctx.formatSection(visibleIssues, s, indent, verbose)
else:
stdout.writeLine visibleIssues
.mapIt(formatPlainIssueSummary(it))
.join("\n")
# Future items # Future items
if future: if future:
if today: ctx.writeHeader("Future") if today: ctx.writeHeader("Future")
@ -328,14 +296,8 @@ proc list(
not (it.hasProp("hide-until") and not (it.hasProp("hide-until") and
it.getDateTime("hide-until") > getTime().local)) it.getDateTime("hide-until") > getTime().local))
if isatty(stdout):
stdout.write ctx.formatSection(visibleIssues, s, indent, verbose) stdout.write ctx.formatSection(visibleIssues, s, indent, verbose)
else:
stdout.writeLine visibleIssues
.mapIt(formatPlainIssueSummary(it))
.join("\n")
trace "listing complete" trace "listing complete"
when isMainModule: when isMainModule:
@ -501,7 +463,7 @@ when isMainModule:
) )
cmd &= " -g \"" & tags.join(",") & "\"" cmd &= " -g \"" & tags.join(",") & "\""
cmd &= " -n \"pit-id: " & $issue.id & "\"" cmd &= " -n \"pit-id: " & $issue.id & "\""
cmd &= " \"[" & ($issue.id)[0..<6] & "] " & issue.summary & "\"" cmd &= " \"" & issue.summary & "\""
discard execShellCmd(cmd) discard execShellCmd(cmd)
elif targetState == Done or targetState == Pending: elif targetState == Done or targetState == Pending:
discard execShellCmd("ptk stop") discard execShellCmd("ptk stop")

View File

@ -1,4 +1,4 @@
const PIT_VERSION* = "4.25.1" const PIT_VERSION* = "4.24.0"
const USAGE* = """Usage: const USAGE* = """Usage:
pit ( new | add) <summary> [<state>] [options] pit ( new | add) <summary> [<state>] [options]
@ -181,6 +181,30 @@ Issue Properties:
after 12 hours after 12 hours
every 2 weeks, 10a544 every 2 weeks, 10a544
relations
Used to store information about relationships between issues. PIT treats
all relations as bi-directional and will update related tickets as
necessary to maintain the integrity of the links.
Relations are captured as <relation-type> <related-issue-id>. Multiple
relations can be separated by ",". The <related-issue-id> must be a unique
issue ID or ID prefix (in the case of multiple matching issues, the first
found will be used with no guarantee on ordering). Valid value pairs for
<relation-type> are:
- child-of / parent-of
- related-to (default)
- blocks / blocked-by
- follows / precedes
- caused / caused-by
Examples:
relation: child-of fb3e63, blocked-by 2b71c1
relation: 2b71c1, 8f2b4c, follows 184dc6
relation: relates-to 2b71c1
tags tags
If present, expected to be a comma-delimited list of text tags. The -g If present, expected to be a comma-delimited list of text tags. The -g

View File

@ -11,6 +11,7 @@ type
filepath*: string filepath*: string
summary*, details*: string summary*, details*: string
properties*: TableRef[string, string] properties*: TableRef[string, string]
relations*: seq[Relation]
tags*: seq[string] tags*: seq[string]
state*: IssueState state*: IssueState
@ -40,6 +41,19 @@ type
interval*: TimeInterval interval*: TimeInterval
isFromCompletion*: bool isFromCompletion*: bool
Relation* = tuple[rel: RelationType, id: UUID]
RelationType* = enum
ParentOf = "parent-of",
ChildOf = "child-of",
RelatedTo = "related-to",
Blocks = "blocks",
BlockedBy = "blocked-by",
Follow = "follows",
FollowedBy = "followed-by",
Caused = "caused",
CausedBy = "caused-by"
const DONE_FOLDER_FORMAT* = "yyyy-MM" const DONE_FOLDER_FORMAT* = "yyyy-MM"
const ISO8601_MS = "yyyy-MM-dd'T'HH:mm:ss'.'fffzzz" const ISO8601_MS = "yyyy-MM-dd'T'HH:mm:ss'.'fffzzz"
@ -173,6 +187,11 @@ proc parseDate*(d: string): DateTime =
continue continue
raise newException(ValueError, "Unable to parse input as a date: " & d & errMsg) raise newException(ValueError, "Unable to parse input as a date: " & d & errMsg)
proc parseRelation*(relStr: string): Relation =
let parts = relStr.split({' '})
if parts.len == 1: result = (RelatedTo, parseUUID(parts[0]))
else: result = (parseEnum[RelationType](parts[0]), parseUUID(parts[1]))
## Parse and format issues ## Parse and format issues
proc fromStorageFormat*(id: string, issueTxt: string): Issue = proc fromStorageFormat*(id: string, issueTxt: string): Issue =
type ParseState = enum ReadingSummary, ReadingProps, ReadingDetails type ParseState = enum ReadingSummary, ReadingProps, ReadingDetails
@ -180,6 +199,7 @@ proc fromStorageFormat*(id: string, issueTxt: string): Issue =
result = Issue( result = Issue(
id: parseUUID(id), id: parseUUID(id),
properties: newTable[string,string](), properties: newTable[string,string](),
relations: @[],
tags: @[]) tags: @[])
var parseState = ReadingSummary var parseState = ReadingSummary
@ -203,14 +223,16 @@ proc fromStorageFormat*(id: string, issueTxt: string): Issue =
parseState = ReadingDetails parseState = ReadingDetails
continue continue
let parts = line.split({':'}, 1) --> map(it.strip()) let parts = line.split({':'}, 1) --> map(it.strip())
if parts.len != 2: if parts.len != 2:
raise newException(ValueError, "unable to parse property line: " & line) raise newException(ValueError, "unable to parse property line: " & line)
# Take care of special properties: `tags` # Take care of special properties: `tags` and `relations`
if parts[0] == "tags": if parts[0] == "tags":
result.tags = parts[1].split({','}) --> map(it.strip()) result.tags = parts[1].split({','}) --> map(it.strip())
elif parts[0] == "relations":
result.relations = parts[1].split({','}) -->
map(parseRelation(it.strip))
else: result[parts[0]] = parts[1] else: result[parts[0]] = parts[1]
of ReadingDetails: of ReadingDetails:
@ -231,6 +253,11 @@ proc toStorageFormat*(issue: Issue, withComments = false): string =
if issue.tags.len > 0: lines.add("tags: " & issue.tags.join(",")) if issue.tags.len > 0: lines.add("tags: " & issue.tags.join(","))
if issue.relations.len > 0:
lines.add("relations: " &
(issue.relations --> map(it.rel & " " & it.id)).
join(', '))
if not isEmptyOrWhitespace(issue.details) or withComments: if not isEmptyOrWhitespace(issue.details) or withComments:
if withComments: lines.add("# Details go below the \"--------\"") if withComments: lines.add("# Details go below the \"--------\"")
lines.add("--------") lines.add("--------")