Make IssueState available as a field on Issue.
* Add `state` on `Issue` to be able to query the state of an issue even if you only have a reference to this issue and don't have a reference to the context or issues table. This does not change the persisted format of the issue. On disk the state of an issue is still represented by it's location in the file hierarchy. * Refactored libpit to use zero_functional instead of sequtils.
This commit is contained in:
parent
d01d6e37f4
commit
171adbb59d
@ -1,6 +1,6 @@
|
|||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "4.22.2"
|
version = "4.23.0"
|
||||||
author = "Jonathan Bernard"
|
author = "Jonathan Bernard"
|
||||||
description = "Personal issue tracker."
|
description = "Personal issue tracker."
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@ -14,7 +14,8 @@ requires @[
|
|||||||
"nim >= 1.4.0",
|
"nim >= 1.4.0",
|
||||||
"docopt >= 0.6.8",
|
"docopt >= 0.6.8",
|
||||||
"jester >= 0.5.0",
|
"jester >= 0.5.0",
|
||||||
"uuids >= 0.1.10"
|
"uuids >= 0.1.10",
|
||||||
|
"zero_functional"
|
||||||
]
|
]
|
||||||
|
|
||||||
# Dependencies from git.jdb-software.com/nim-jdb/packages
|
# Dependencies from git.jdb-software.com/nim-jdb/packages
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
## Personal Issue Tracker CLI interface
|
## Personal Issue Tracker CLI interface
|
||||||
## ====================================
|
## ====================================
|
||||||
|
|
||||||
import std/algorithm, std/logging, std/options, std/os, std/sequtils,
|
import std/[algorithm, logging, options, os, sequtils, wordwrap, tables,
|
||||||
std/wordwrap, std/tables, std/terminal, std/times, std/unicode
|
terminal, times, unicode]
|
||||||
import cliutils, data_uri, docopt, json, timeutils, uuids
|
import cliutils, data_uri, docopt, json, timeutils, uuids
|
||||||
|
|
||||||
from nre import re
|
from nre import re
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const PIT_VERSION* = "4.22.2"
|
const PIT_VERSION* = "4.23.0"
|
||||||
|
|
||||||
const USAGE* = """Usage:
|
const USAGE* = """Usage:
|
||||||
pit ( new | add) <summary> [<state>] [options]
|
pit ( new | add) <summary> [<state>] [options]
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import std/json, std/logging, std/options, std/os, std/sequtils, std/strformat,
|
import std/[json, logging, options, os, strformat, strutils, tables, times]
|
||||||
std/strutils, std/tables, std/times
|
import cliutils, docopt, langutils, uuids, zero_functional
|
||||||
import cliutils, docopt, langutils, timeutils, uuids
|
|
||||||
|
|
||||||
import nre except toSeq
|
import nre except toSeq
|
||||||
|
import timeutils except `>`
|
||||||
|
from sequtils import deduplicate, toSeq
|
||||||
|
|
||||||
type
|
type
|
||||||
Issue* = ref object
|
Issue* = ref object
|
||||||
@ -11,6 +12,7 @@ type
|
|||||||
summary*, details*: string
|
summary*, details*: string
|
||||||
properties*: TableRef[string, string]
|
properties*: TableRef[string, string]
|
||||||
tags*: seq[string]
|
tags*: seq[string]
|
||||||
|
state*: IssueState
|
||||||
|
|
||||||
IssueState* = enum
|
IssueState* = enum
|
||||||
Current = "current",
|
Current = "current",
|
||||||
@ -201,12 +203,13 @@ proc fromStorageFormat*(id: string, issueTxt: string): Issue =
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
||||||
let parts = line.split({':'}, 1).mapIt(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`
|
||||||
if parts[0] == "tags": result.tags = parts[1].split({','}).mapIt(it.strip())
|
if parts[0] == "tags":
|
||||||
|
result.tags = parts[1].split({','}) --> map(it.strip())
|
||||||
else: result[parts[0]] = parts[1]
|
else: result[parts[0]] = parts[1]
|
||||||
|
|
||||||
of ReadingDetails:
|
of ReadingDetails:
|
||||||
@ -234,6 +237,11 @@ proc loadIssue*(filePath: string): Issue =
|
|||||||
result = fromStorageFormat(splitFile(filePath).name, readFile(filePath))
|
result = fromStorageFormat(splitFile(filePath).name, readFile(filePath))
|
||||||
result.filepath = filePath
|
result.filepath = filePath
|
||||||
|
|
||||||
|
let parentDirName = filePath.splitFile().dir.splitFile().name
|
||||||
|
let issueState = IssueState.items.toSeq --> find($it == parentDirName)
|
||||||
|
if issueState.isSome: result.state = issueState.get
|
||||||
|
else: result.state = IssueState.Done
|
||||||
|
|
||||||
proc loadIssueById*(tasksDir, id: string): Issue =
|
proc loadIssueById*(tasksDir, id: string): Issue =
|
||||||
for path in walkDirRec(tasksDir):
|
for path in walkDirRec(tasksDir):
|
||||||
if path.splitFile.name.startsWith(id):
|
if path.splitFile.name.startsWith(id):
|
||||||
@ -273,10 +281,10 @@ proc loadIssues*(path: string): seq[Issue] =
|
|||||||
|
|
||||||
let orderedIds =
|
let orderedIds =
|
||||||
if fileExists(orderFile):
|
if fileExists(orderFile):
|
||||||
toSeq(orderFile.lines)
|
(orderFile.lines.toSeq -->
|
||||||
.mapIt(it.split(' ')[0])
|
map(it.split(' ')[0]).
|
||||||
.deduplicate
|
filter(not it.startsWith("> ") and not it.isEmptyOrWhitespace)).
|
||||||
.filterIt(not it.startsWith("> ") and not it.isEmptyOrWhitespace)
|
deduplicate()
|
||||||
else: newSeq[string]()
|
else: newSeq[string]()
|
||||||
|
|
||||||
type TaggedIssue = tuple[issue: Issue, ordered: bool]
|
type TaggedIssue = tuple[issue: Issue, ordered: bool]
|
||||||
@ -332,6 +340,7 @@ proc nextRecurrence*(tasksDir: string, rec: Recurrence, defaultIssue: Issue): Is
|
|||||||
|
|
||||||
result = Issue(
|
result = Issue(
|
||||||
id: genUUID(),
|
id: genUUID(),
|
||||||
|
state: baseIssue.state,
|
||||||
summary: baseIssue.summary,
|
summary: baseIssue.summary,
|
||||||
properties: newProps,
|
properties: newProps,
|
||||||
tags: baseIssue.tags)
|
tags: baseIssue.tags)
|
||||||
@ -354,36 +363,38 @@ proc nextRecurrence*(tasksDir: string, rec: Recurrence, defaultIssue: Issue): Is
|
|||||||
|
|
||||||
## Utilities for working with issue collections.
|
## Utilities for working with issue collections.
|
||||||
proc filter*(issues: seq[Issue], filter: IssueFilter): seq[Issue] =
|
proc filter*(issues: seq[Issue], filter: IssueFilter): seq[Issue] =
|
||||||
result = issues
|
var f: seq[Issue] = issues
|
||||||
|
|
||||||
for k,v in filter.properties:
|
for k,v in filter.properties:
|
||||||
result = result.filterIt(it.hasProp(k) and it[k] == v)
|
f = f --> filter(it.hasProp(k) and it[k] == v)
|
||||||
|
|
||||||
for k,v in filter.exclProperties:
|
for k,v in filter.exclProperties:
|
||||||
result = result.filter(proc (iss: Issue): bool =
|
f = f --> filter(not (it.hasProp(k) and v.contains(it[k])))
|
||||||
not iss.hasProp(k) or
|
|
||||||
not v.anyIt(it == iss[k])
|
|
||||||
)
|
|
||||||
|
|
||||||
if filter.completedRange.isSome:
|
if filter.completedRange.isSome:
|
||||||
let range = filter.completedRange.get
|
let range = filter.completedRange.get
|
||||||
result = result.filterIt(
|
f = f --> filter(
|
||||||
not it.hasProp("completed") or
|
not it.hasProp("completed") or
|
||||||
it.getDateTime("completed").between(range.b, range.e))
|
it.getDateTime("completed").between(range.b, range.e))
|
||||||
|
|
||||||
if filter.summaryMatch.isSome:
|
if filter.summaryMatch.isSome:
|
||||||
let p = filter.summaryMatch.get
|
let p = filter.summaryMatch.get
|
||||||
result = result.filterIt(it.summary.find(p).isSome)
|
f = f --> filter(it.summary.find(p).isSome)
|
||||||
|
|
||||||
if filter.fullMatch.isSome:
|
if filter.fullMatch.isSome:
|
||||||
let p = filter.fullMatch.get
|
let p = filter.fullMatch.get
|
||||||
result = result.filterIt( it.summary.find(p).isSome or it.details.find(p).isSome)
|
f = f -->
|
||||||
|
filter(it.summary.find(p).isSome or it.details.find(p).isSome)
|
||||||
|
|
||||||
for tag in filter.hasTags:
|
for tagLent in filter.hasTags:
|
||||||
result = result.filterIt(it.tags.find(tag) >= 0)
|
let tag = tagLent
|
||||||
|
f = f --> filter(it.tags.find(tag) >= 0)
|
||||||
|
|
||||||
for exclTag in filter.exclTags:
|
for exclTagLent in filter.exclTags:
|
||||||
result = result.filterIt(it.tags.find(exclTag) < 0)
|
let exclTag = exclTagLent
|
||||||
|
f = f --> filter(it.tags.find(exclTag) < 0)
|
||||||
|
|
||||||
|
return f # not using result because zero_functional doesn't play nice with it
|
||||||
|
|
||||||
proc find*(
|
proc find*(
|
||||||
issues: TableRef[IssueState, seq[Issue]],
|
issues: TableRef[IssueState, seq[Issue]],
|
||||||
@ -400,7 +411,7 @@ proc loadConfig*(args: Table[string, Value] = initTable[string, Value]()): PitCo
|
|||||||
".pitrc", $getEnv("PITRC"), $getEnv("HOME") & "/.pitrc"]
|
".pitrc", $getEnv("PITRC"), $getEnv("HOME") & "/.pitrc"]
|
||||||
|
|
||||||
var pitrcFilename: string =
|
var pitrcFilename: string =
|
||||||
foldl(pitrcLocations, if len(a) > 0: a elif fileExists(b): b else: "")
|
pitrcLocations --> fold("", if fileExists(it): it else: a)
|
||||||
|
|
||||||
if not fileExists(pitrcFilename):
|
if not fileExists(pitrcFilename):
|
||||||
warn "could not find .pitrc file: " & pitrcFilename
|
warn "could not find .pitrc file: " & pitrcFilename
|
||||||
|
Loading…
x
Reference in New Issue
Block a user