From 3ffed9076873c11e77dee2e6f11b06902c54d884 Mon Sep 17 00:00:00 2001 From: Andrew Tran Date: Sun, 25 Apr 2021 16:41:18 -0700 Subject: [PATCH 1/8] Update git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 4b8ea91..b4dcdf6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /grit /dist/ /.release-env +tags From 2d928d98de8e411843bc6dc9709d4f4162871345 Mon Sep 17 00:00:00 2001 From: Andrew Tran Date: Sun, 25 Apr 2021 16:42:14 -0700 Subject: [PATCH 2/8] cmd/grit: Add ability to alias nodes on addition This commit allows the option `-a|--alias` for `grit add` that will allow aliasing a node on addition. --- cmd/grit/cmds.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/cmd/grit/cmds.go b/cmd/grit/cmds.go index 377e953..224c406 100644 --- a/cmd/grit/cmds.go +++ b/cmd/grit/cmds.go @@ -16,7 +16,7 @@ import ( ) func cmdAdd(cmd *cli.Cmd) { - cmd.Spec = "[ -p= | -r ] NAME_PARTS..." + cmd.Spec = "[ -a= ] [ -p= | -r ] NAME_PARTS..." today := time.Now().Format("2006-01-02") var ( @@ -26,6 +26,8 @@ func cmdAdd(cmd *cli.Cmd) { "predecessor to attach the node to") makeRoot = cmd.BoolOpt("r root", false, "create a root node") + alias = cmd.StringOpt("a alias", "", + "give the node an alias") ) cmd.Action = func() { @@ -37,14 +39,20 @@ func cmdAdd(cmd *cli.Cmd) { name := strings.Join(*nameParts, " ") + var node *multitree.Node if *makeRoot { - node, err := a.AddRoot(name) + node, err = a.AddRoot(name) if err != nil { dief("Couldn't create node: %v\n", err) } color.Cyan("(%d)", node.ID) + if *alias != "" { + color.Cyan("(%d):%s\n", node.ID, *alias) + } else { + color.Cyan("(%d)", node.ID) + } } else { - node, err := a.AddChild(name, *predecessor) + node, err = a.AddChild(name, *predecessor) if err != nil { dief("Couldn't create node: %v\n", err) } @@ -54,7 +62,16 @@ func cmdAdd(cmd *cli.Cmd) { accent = color.New(color.FgYellow).SprintFunc() } highlighted := accent(fmt.Sprintf("(%d)", node.ID)) - fmt.Printf("(%d) -> %s\n", parents[0].ID, highlighted) + if *alias != "" { + fmt.Printf("(%d) -> %s:%s\n", parents[0].ID, highlighted, *alias) + } else { + fmt.Printf("(%d) -> %s\n", parents[0].ID, highlighted) + } + } + + id := node.ID + if err := a.SetAlias(id, *alias); err != nil { + dief("Couldn't set alias: %v", err) } } } From 614fefd3fc93c30dc3a8c19e70c9f0e9923b951c Mon Sep 17 00:00:00 2001 From: Andrew Tran Date: Sun, 25 Apr 2021 16:43:17 -0700 Subject: [PATCH 3/8] cmd/grit: Allow special "all" keyword for list `grit list` will now recognize "all" as a special selector, listing *all* nodes including children nodes. --- cmd/grit/cmds.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/cmd/grit/cmds.go b/cmd/grit/cmds.go index 224c406..00e63ad 100644 --- a/cmd/grit/cmds.go +++ b/cmd/grit/cmds.go @@ -107,7 +107,9 @@ func cmdTree(cmd *cli.Cmd) { func cmdList(cmd *cli.Cmd) { cmd.Spec = "[NODE]" var ( - selector = cmd.StringArg("NODE", "", "node selector") + selector = cmd.StringArg("NODE", "", + "node selector. \"all\" will list all existing nodes, root nodes are marked" + + " by *") ) cmd.Action = func() { a, err := app.New() @@ -134,6 +136,22 @@ func cmdList(cmd *cli.Cmd) { } nodes = append(nodes, n) } + } else if *selector == "all" { + roots, err := a.GetRoots() + if err != nil { + die(err) + } + for _, r := range roots { + n, err := a.GetGraph(r.ID) + if err != nil { + die(err) + } + if n == nil { + continue + } + nodes = append(nodes, n) + nodes = append(nodes, n.Children()...) + } } else { node, err := a.GetGraph(*selector) if err != nil { @@ -147,7 +165,11 @@ func cmdList(cmd *cli.Cmd) { multitree.SortNodesByName(nodes) for _, n := range nodes { - fmt.Println(n) + if n.IsRoot() { + fmt.Printf("%s*\n", n) + } else { + fmt.Println(n) + } } } } From e158743b0fe0f589075436697937d463be59d6e0 Mon Sep 17 00:00:00 2001 From: Andrew Tran Date: Sun, 25 Apr 2021 16:44:25 -0700 Subject: [PATCH 4/8] cmd/grit: Add ability to move nodes Add new "move" command that allows a node to be moved from one parent to another. It simply aliases the action of unlink a node and re-linking it with a different parent. --- cmd/grit/cmds.go | 24 ++++++++++++++++++++++++ cmd/grit/main.go | 1 + 2 files changed, 25 insertions(+) diff --git a/cmd/grit/cmds.go b/cmd/grit/cmds.go index 00e63ad..b3cfd52 100644 --- a/cmd/grit/cmds.go +++ b/cmd/grit/cmds.go @@ -538,3 +538,27 @@ func cmdStat(cmd *cli.Cmd) { } } + +func cmdMove(cmd *cli.Cmd) { + cmd.Spec = "NODE ORIGIN TARGET" + var ( + node = cmd.StringArg("NODE", "", "node selector") + origin = cmd.StringArg("ORIGIN", "", "origin selector") + target = cmd.StringArg("TARGET", "", "target selector") + ) + + cmd.Action = func() { + a, err := app.New() + if err != nil { + die(err) + } + defer a.Close() + + if err := a.UnlinkNodes(*origin, *node); err != nil { + dief("Couldn't unlink nodes: %v\n", err) + } + if _, err := a.LinkNodes(*target, *node); err != nil { + dief("Couldn't link nodes: %v\n", err) + } + } +} diff --git a/cmd/grit/main.go b/cmd/grit/main.go index 09bc1b6..6617829 100644 --- a/cmd/grit/main.go +++ b/cmd/grit/main.go @@ -20,6 +20,7 @@ func main() { c.Command("uncheck", "Revert node status to inactive", cmdUncheck) c.Command("link", "Create a link from one node to another", cmdLink) c.Command("unlink", "Remove an existing link between two nodes", cmdUnlink) + c.Command("move", "Move a node from one parent to another", cmdMove) c.Command("list ls", "List children of selected node", cmdList) c.Command("list-dates lsd", "List all date nodes", cmdListDates) c.Command("rename", "Rename a node", cmdRename) From 269ab53257d7669a70147af4fd11452b178f9d10 Mon Sep 17 00:00:00 2001 From: Andrew Tran Date: Sun, 25 Apr 2021 16:59:42 -0700 Subject: [PATCH 5/8] cmd/grit: Add alias for move --- cmd/grit/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/grit/main.go b/cmd/grit/main.go index 6617829..a03fb2f 100644 --- a/cmd/grit/main.go +++ b/cmd/grit/main.go @@ -20,7 +20,7 @@ func main() { c.Command("uncheck", "Revert node status to inactive", cmdUncheck) c.Command("link", "Create a link from one node to another", cmdLink) c.Command("unlink", "Remove an existing link between two nodes", cmdUnlink) - c.Command("move", "Move a node from one parent to another", cmdMove) + c.Command("move mv", "Move a node from one parent to another", cmdMove) c.Command("list ls", "List children of selected node", cmdList) c.Command("list-dates lsd", "List all date nodes", cmdListDates) c.Command("rename", "Rename a node", cmdRename) From e5692b73dd34f6137abcb881b5a69db94f6d961a Mon Sep 17 00:00:00 2001 From: Andrew Tran Date: Mon, 10 May 2021 10:05:32 -0700 Subject: [PATCH 6/8] cmd/grit: Add extra unlink options The options allow unlinking a node from all children or all parents, these options are mutually exclusive (including the existing "target" arguemnts) --- cmd/grit/cmds.go | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/cmd/grit/cmds.go b/cmd/grit/cmds.go index b3cfd52..7fb79b7 100644 --- a/cmd/grit/cmds.go +++ b/cmd/grit/cmds.go @@ -234,10 +234,14 @@ func cmdLink(cmd *cli.Cmd) { } func cmdUnlink(cmd *cli.Cmd) { - cmd.Spec = "ORIGIN TARGET" + cmd.Spec = "ORIGIN ( -A | -P | TARGETS... )" var ( origin = cmd.StringArg("ORIGIN", "", "origin selector") - target = cmd.StringArg("TARGET", "", "target selector") + targets = cmd.StringsArg("TARGETS", nil, "target selector") + allChildren = cmd.BoolOpt("A allChildren", false, + "unlink all children of the node") + allParents = cmd.BoolOpt("P allParents", false, + "unlink all parent of the node, essentially making it a root node") ) cmd.Action = func() { a, err := app.New() @@ -246,8 +250,34 @@ func cmdUnlink(cmd *cli.Cmd) { } defer a.Close() - if err := a.UnlinkNodes(*origin, *target); err != nil { - dief("Couldn't unlink nodes: %v\n", err) + originNode, err := a.GetGraph(*origin) + if err != nil { + die(err) + } else if originNode == nil { + die("Node does not exist") + } + + var nodes []*multitree.Node + if *allChildren { + nodes = originNode.Children() + for _, n := range nodes { + if err := a.UnlinkNodes(*origin, n); err != nil { + dief("Couldn't unlink nodes: %v\n", err) + } + } + } else if *allParents { + nodes = originNode.Parents() + for _, n := range nodes { + if err := a.UnlinkNodes(n, *origin); err != nil { + dief("Couldn't unlink nodes: %v\n", err) + } + } + } else { + for _, t := range *targets { + if err := a.UnlinkNodes(*origin, t); err != nil { + dief("Couldn't unlink nodes: %v\n", err) + } + } } } } From e2efb62d334a1e73108a233905671b5120335ab4 Mon Sep 17 00:00:00 2001 From: Andrew Tran Date: Tue, 24 Aug 2021 14:57:10 -0700 Subject: [PATCH 7/8] Allow 'today' as special keyword for link/unlink The app crashes if no node for the current day has been created, either from `add` or something else. This allows the user to specify `today` as a keyword in order to automatically add the date node. --- cmd/grit/cmds.go | 18 ++++++++++++++++-- multitree/validate.go | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cmd/grit/cmds.go b/cmd/grit/cmds.go index 7fb79b7..bbd875d 100644 --- a/cmd/grit/cmds.go +++ b/cmd/grit/cmds.go @@ -215,7 +215,9 @@ func cmdUncheck(cmd *cli.Cmd) { func cmdLink(cmd *cli.Cmd) { cmd.Spec = "ORIGIN TARGETS..." var ( - origin = cmd.StringArg("ORIGIN", "", "origin selector") + origin = cmd.StringArg("ORIGIN", "", + "origin selector. 'today' is a valid option and will" + + " create the node if it doesn't already exist") targets = cmd.StringsArg("TARGETS", nil, "target selector(s)") ) cmd.Action = func() { @@ -225,6 +227,11 @@ func cmdLink(cmd *cli.Cmd) { } defer a.Close() + today := time.Now().Format("2006-01-02") + if *origin == "today" { + *origin = today + } + for _, t := range *targets { if _, err := a.LinkNodes(*origin, t); err != nil { errf("Couldn't create link (%s) -> (%s): %v\n", *origin, t, err) @@ -236,7 +243,9 @@ func cmdLink(cmd *cli.Cmd) { func cmdUnlink(cmd *cli.Cmd) { cmd.Spec = "ORIGIN ( -A | -P | TARGETS... )" var ( - origin = cmd.StringArg("ORIGIN", "", "origin selector") + origin = cmd.StringArg("ORIGIN", "", + "origin selector. 'today' is a valid option and will" + + " create the node if it doesn't already exist") targets = cmd.StringsArg("TARGETS", nil, "target selector") allChildren = cmd.BoolOpt("A allChildren", false, "unlink all children of the node") @@ -250,6 +259,11 @@ func cmdUnlink(cmd *cli.Cmd) { } defer a.Close() + today := time.Now().Format("2006-01-02") + if *origin == "today" { + *origin = today + } + originNode, err := a.GetGraph(*origin) if err != nil { die(err) diff --git a/multitree/validate.go b/multitree/validate.go index 8c2c8e2..af2811e 100644 --- a/multitree/validate.go +++ b/multitree/validate.go @@ -13,7 +13,7 @@ func ValidateNodeName(name string) error { if len(name) == 0 { return errors.New("invalid node name (empty name)") } - if len(name) > 100 { + if len(name) > 200 { return errors.New("invalid node name (name too long)") } return nil From 1ffb132300f318bcb37456a4848ddafb3891ff30 Mon Sep 17 00:00:00 2001 From: Andrew Tran Date: Wed, 1 Sep 2021 15:09:11 -0700 Subject: [PATCH 8/8] cmd/grit: Allow tree command to sort by status `grit tree -u` will sort the nodes by order of Inactive -> InProgress -> Completed. This is helpful in seeing what needs to be done still. --- cmd/grit/cmds.go | 9 +++++++-- multitree/sort.go | 11 +++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/cmd/grit/cmds.go b/cmd/grit/cmds.go index bbd875d..14cd47b 100644 --- a/cmd/grit/cmds.go +++ b/cmd/grit/cmds.go @@ -77,10 +77,11 @@ func cmdAdd(cmd *cli.Cmd) { } func cmdTree(cmd *cli.Cmd) { - cmd.Spec = "[NODE]" + cmd.Spec = "[ -u= ] [NODE]" today := time.Now().Format("2006-01-02") var ( selector = cmd.StringArg("NODE", today, "node selector") + unfinished = cmd.BoolOpt("u unfinished", false, "Show unfinished nodes first") ) cmd.Action = func() { a, err := app.New() @@ -97,8 +98,12 @@ func cmdTree(cmd *cli.Cmd) { die("Node does not exist") } + sortOrder := multitree.SortNodesByName + if *unfinished { + sortOrder = multitree.SortNodesByStatus + } node.TraverseDescendants(func(current *multitree.Node, _ func()) { - multitree.SortNodesByName(current.Children()) + sortOrder(current.Children()) }) fmt.Print(node.StringTree()) } diff --git a/multitree/sort.go b/multitree/sort.go index 1f8ec07..34f9ad2 100644 --- a/multitree/sort.go +++ b/multitree/sort.go @@ -20,3 +20,14 @@ func SortNodesByName(nodes []*Node) { return naturalsort.Compare(nodes[i].Name, nodes[j].Name) }) } + +// SortNodesByStatus sorts a the nodes by their status in the order of +// Inactive -> InProgress -> Completed +func SortNodesByStatus(nodes []*Node) { + sort.SliceStable(nodes, func(i, j int) bool { + if nodes[i].Status() == nodes[j].Status() { + return naturalsort.Compare(nodes[i].Name, nodes[j].Name) + } + return nodes[i].Status() > nodes[j].Status() + }) +}