This thread contains a patchset. You're looking at the original emails,
but you may wish to use the patch review UI.
Review patch
5
2
[PATCH 1/3] qm: improve user informing
---
qm | 3 ++ -
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/qm b/qm
index a3b2d6f..5e50a0c 100755
--- a/qm
+++ b/qm
@@ -16,10 +16,11 @@ main() {
export QM_EDITOR="nvim"
if [[ ! -d "$QM_PATH" ]]; then
- echo "\"$QM_PATH\" created."
+ echo "No QM database yet. Iniatlizing..."
mkdir -p "$QM_PATH"
mkdir -p "$QM_PATH"/$QM_COLLECTION/pool
mkdir -p "$QM_PATH"/$QM_COLLECTION/projects
+ echo "\"$QM_PATH\" initialized."
fi
if which "qm-$cmd" >& /dev/null; then
--
2.30.1
[PATCH 2/3] lib: implement search methods and project creation
Implements:
- Projects are now created on collections.
- Layer-Search-Methods for tree-printing of projects
- UUIDs for projects
---
Those are major changes. We will have to find a way to use Container for
all of this soonish.
lib/collection.go | 85 +++++++++++++++++++++++++++++++++++++++++++ ----
lib/container.go | 20 +++++ ------
lib/project.go | 5 ++ -
3 files changed, 91 insertions(+), 19 deletions(-)
diff --git a/lib/collection.go b/lib/collection.go
index 37784ff..6a28d35 100644
--- a/lib/collection.go
+++ b/lib/collection.go
@@ -23,6 +23,11 @@ const (
SearchAttrTag = "tag"
)
+ type ProjectSubTreeS struct {
+ Proj *Project
+ Subprojs []*ProjectSubTreeS
+ }
+
type Collection struct {
Path string
PoolPath string
@@ -99,6 +104,7 @@ func getProjMeta(path string) (*Project, error) {
return proj, nil
}
+ // TODO: Works only properly with minimum number = 1. Maybe 0 is better?
func buildLayerNGlob(depth uint) string {
var i uint
s := "/"
@@ -112,34 +118,99 @@ func buildLayerNGlob(depth uint) string {
}
// get subprojects N layers deep
- func (p *Project) GetSubprojectsN(depth uint) ([]*Project, error) {
+ func (p *Project) GetSubprojectsN(depth int) ([]*Project, error) {
var subprojs []*Project
- glob := p.Path + buildLayerNGlob(depth)
- fmt.Println("glob: ", glob)
+ glob := p.Path + buildLayerNGlob(uint(depth))
matches, err := filepath.Glob(glob)
if err != nil {
return nil, err
}
- fmt.Println("nr of matches: ", len(matches))
for _, m := range matches {
- p, err := getProjMeta(m)
+ foundP, err := getProjMeta(m)
if err != nil {
return nil, err
}
- subprojs = append(subprojs, p)
+
+ if foundP.UUID != p.UUID {
+ subprojs = append(subprojs, foundP)
+ }
}
return subprojs, nil
}
+ // calls itself recursively until all subprojects have been found.
+ func (p *Project) GetSubprojectTree() ([]*ProjectSubTreeS, error) {
+ subprojs, err := p.GetSubprojectsN(1)
+ if len(subprojs) == 0 || err != nil {
+ return nil, err
+ }
+
+ subtree := make([]*ProjectSubTreeS, 0, 8)
+
+ for _, subpr := range subprojs {
+ deeperSubs, err := subpr.GetSubprojectTree()
+ if err != nil {
+ break
+ }
+ filler := ProjectSubTreeS{subpr, deeperSubs}
+ subtree = append(subtree, &filler)
+ }
+
+ return subtree, err
+ }
+
+ // TODO implement this
+ func (c *Collection) GetProjectTree() ([]ProjectSubTreeS, error) {
+ var tree []ProjectSubTreeS
+
+ _, err := c.AllProjects()
+ if err != nil {
+ return nil, err
+ }
+
+ return tree, nil
+ }
+
+ // Gets all Projects layer N deep. Can be used to get all root projects.
+ func (c *Collection) AllProjectsN(depth int) ([]*Project, error) {
+ var (
+ projs []*Project
+ err error
+ )
+ if depth == 0 {
+ return nil, fmt.Errorf("Invalid depth")
+ } else if depth == -1 {
+ return c.AllProjects()
+ }
+
+ glob := c.ProjectsPath + buildLayerNGlob(uint(depth))
+ matches, err := filepath.Glob(glob)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, m := range matches {
+ foundP, err := getProjMeta(m)
+ if err != nil {
+ return nil, err
+ }
+
+ projs = append(projs, foundP)
+ }
+
+ return projs, nil
+ }
+
func (c *Collection) AllProjects() ([]*Project, error) {
var (
projs []*Project
err error
)
+ // FIXME: Chdir sucks
if err = os.Chdir(c.ProjectsPath); err != nil {
return nil, err
}
@@ -208,6 +279,8 @@ func (p *Collection) FindTags(t *Item) ([]*Tag, error) {
return out, nil
}
+ // TODO: warn user when there is more than 1 proj with identical name
+ // if there is, than make him choose by UUID.
func (c *Collection) ProjectByTitle(title string) (*Project, error) {
// TODO: implement this more efficently
projs, err := c.AllProjects()
diff --git a/lib/container.go b/lib/container.go
index 5859a42..7a32365 100644
--- a/lib/container.go
+++ b/lib/container.go
@@ -7,6 +7,7 @@ package qm
import (
"encoding/json"
+ "github.com/google/uuid"
"os"
"path/filepath"
"time"
@@ -18,26 +19,24 @@ type Container struct {
Description string `json:"description" yaml:"description"`
CreatedAt time.Time `json:"created_at" yaml:"created_at"`
Path string `json:"path" yaml:"-"`
+ UUID uuid.UUID `json:"uuid" yaml:"uuid"`
parent string
}
- func checkParentExists(c *Collection, title string) error {
- _, err := c.ProjectByTitle(title)
- return err
- }
-
+ // FIXME: this should not only work for Projects
func (c *Collection) CreateContainer(title,
- descr, type_, parent string) (*Container, error) {
+ descr, type_, parentTitle string) (*Container, error) {
var path string
slug := Slug(title)
- if parent == "" {
+ if parentTitle == "" {
path = filepath.Join(c.Path, type_, slug)
} else {
- if err := checkParentExists(c, parent); err != nil {
+ parent, err := c.ProjectByTitle(parentTitle)
+ if err != nil {
return nil, err
}
- path = filepath.Join(c.Path, type_, parent, slug)
+ path = filepath.Join(parent.Path, slug)
}
cont := &Container{
@@ -46,7 +45,8 @@ func (c *Collection) CreateContainer(title,
Description: descr,
CreatedAt: time.Now(),
Path: path,
- parent: parent,
+ UUID: uuid.New(),
+ parent: parentTitle,
}
return cont, nil
diff --git a/lib/project.go b/lib/project.go
index 946b3d0..51b33f1 100644
--- a/lib/project.go
+++ b/lib/project.go
@@ -19,10 +19,9 @@ type Project struct {
//Progress uint `json:"progress" yaml:"progress"`
}
- func (c *Collection) CreateProject(title, description,
- parent string) (*Project, error) {
+ func (c *Collection) CreateProject(title, descr, parent string) (*Project, error) {
p := &Project{}
- cont, err := c.CreateContainer(title, description, "projects", parent)
+ cont, err := c.CreateContainer(title, descr, "projects", parent)
if err != nil {
return nil, err
}
--
2.30.1
[PATCH 3/3] bin: list: First version of project tree printing
Working, but incomplete first draft of proj-tree printing.
---
Thanks for your last patch.
I have fixed the global variable and use defer instead.
As for the recursion: Lots of work, we'll keep it, but feel free to
offer a patch adjusting everything in a way you viel as appropriate, and
we can have a discussion about this.
But this code will probably change very quickly anyways.
bin/qm-list/main.go | 116 ++++++++++++++++++++++++++++++++++++++ ------
1 file changed, 100 insertions(+), 16 deletions(-)
diff --git a/bin/qm-list/main.go b/bin/qm-list/main.go
index 512baca..6f99180 100644
--- a/bin/qm-list/main.go
@@ -2,51 +2,135 @@ package main
import (
"fmt"
+ "os"
qm "git.sr.ht/~imperator/quartiermeister/lib"
"github.com/integrii/flaggy"
)
- func listItems(c *qm.Collection) {
+ type options struct {
+ recurse uint
+ noLines bool
+ }
+
+ // options should be global, otherwise get passed around 1000 times.
+ var opts options
+
+ func listItems(c *qm.Collection) error {
items, err := c.AllItems()
if err != nil {
- fmt.Println("error: ", err)
- return
+ return err
}
for _, i := range items {
fmt.Println(i.Title)
}
+
+ return nil
+ }
+
+ // creates string prefix for printing a tree
+ func prefixByDepth(depth int) string {
+ if depth == 0 {
+ return ""
+ }
+ prefix := "├─"
+ lengthener := "────"
+ if opts.noLines {
+ prefix = " "
+ lengthener = " "
+ }
+
+ var indentation string
+ for i := 0; i < depth; i++ {
+ indentation += lengthener
+ }
+ prefix += indentation
+
+ return prefix
+ }
+
+ func decrDepth(depth *int) {
+ *depth -= 1
+ }
+
+ // prints passed subproject and all its subprojects recursively
+ func printProjAndSubs(subproj *qm.ProjectSubTreeS, depth int) error {
+ defer decrDepth(&depth)
+
+ // when this function recurses, the recursors get higher and higher
+ // numbers, causing the right indentation.
+ prefix := prefixByDepth(depth)
+ depth += 1
+
+ // TODO: find out why there is a whitespace too much
+ fmt.Println(prefix, subproj.Proj.Title)
+
+ for _, proj := range subproj.Subprojs {
+ prefix = prefixByDepth(depth)
+ fmt.Println(prefix, proj.Proj.Title)
+ subprojs, err := proj.Proj.GetSubprojectTree()
+ if err != nil {
+ return err
+ }
+
+ for _, sub := range subprojs {
+ depth += 1 // ugly... recursion.
+ printProjAndSubs(sub, depth)
+ depth -= 1
+ }
+ }
+
+ return nil
}
- func listProjs(c *qm.Collection) {
- projs, err := c.AllProjects()
+ // loads all projects and passes them to the print function
+ func printProjFamily(c *qm.Collection) error {
+ projs, err := c.AllProjectsN(1)
if err != nil {
- fmt.Println("error: ", err)
- return
+ return err
}
- for _, p := range projs {
- fmt.Println(p.Title)
+
+ for _, proj := range projs {
+ tmpSubs, err := proj.GetSubprojectTree()
+ if err != nil {
+ return err
+ }
+
+ tmp := qm.ProjectSubTreeS{proj, tmpSubs}
+ if err = printProjAndSubs(&tmp, 0); err != nil {
+ return err
+ }
}
+
+ return nil
}
func main() {
- var (
- itemCmd = flaggy.NewSubcommand("items")
- projCmd = flaggy.NewSubcommand("projs")
- )
+ var err error
+ itemCmd := flaggy.NewSubcommand("items")
+ projCmd := flaggy.NewSubcommand("projs")
+ projCmd.UInt(&opts.recurse, "r", "recurse",
+ "<N> show subprojects N layers deep")
+ flaggy.Bool(&opts.noLines, "n", "no-lines", "Show no lines")
+
flaggy.AttachSubcommand(itemCmd, 1)
flaggy.AttachSubcommand(projCmd, 1)
flaggy.Parse()
c, err := qm.LoadCollection()
if err != nil {
- fmt.Println("error: ", err)
+ fmt.Fprintln(os.Stderr, "error: ", err)
return
}
if itemCmd.Used {
- listItems(c)
+ err = listItems(c)
} else if projCmd.Used {
- listProjs(c)
+ err = printProjFamily(c)
+ }
+
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
}
}
--
2.30.1
IMO this makes kind of sense. In the long run I would prefer a `qm init`
command for this. Then the user has full control and `qm` does not do
any magic without being asked.
For now it is fine for me, just quickfix the typo below. :)
PS: Sent by mutt.
On Thu, Mar 11, 2021 at 10:25:59PM +0100, Philipp Stanner wrote:
> ---
> qm | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/qm b/qm
> index a3b2d6f..5e50a0c 100755
> --- a/qm
> +++ b/qm
> @@ -16,10 +16,11 @@ main() {
> export QM_EDITOR="nvim"
>
> if [[ ! -d "$QM_PATH" ]]; then
> - echo "\"$QM_PATH\" created."
> + echo "No QM database yet. Iniatlizing..."
Typo.
> mkdir -p "$QM_PATH"
> mkdir -p "$QM_PATH"/$QM_COLLECTION/pool
> mkdir -p "$QM_PATH"/$QM_COLLECTION/projects
> + echo "\"$QM_PATH\" initialized."
> fi
>
> if which "qm-$cmd" >& /dev/null; then
> --
> 2.30.1
>
Re: [PATCH 2/3] lib: implement search methods and project creation
A few nits. Rest seems ok for my first review like this in mutt.
On Thu, Mar 11, 2021 at 10:26:01PM +0100, Philipp Stanner wrote:
> Implements:
> - Projects are now created on collections.
> - Layer-Search-Methods for tree-printing of projects
> - UUIDs for projects
> ---
> Those are major changes. We will have to find a way to use Container for
> all of this soonish.
>
> lib/collection.go | 85 +++++++++++++++++++++++++++++++++++++++++++----
> lib/container.go | 20 +++++------
> lib/project.go | 5 ++-
> 3 files changed, 91 insertions(+), 19 deletions(-)
>
> diff --git a/lib/collection.go b/lib/collection.go
> index 37784ff..6a28d35 100644
> --- a/lib/collection.go
> +++ b/lib/collection.go
> @@ -23,6 +23,11 @@ const (
> SearchAttrTag = "tag"
> )
>
> +type ProjectSubTreeS struct {
> + Proj *Project
> + Subprojs []*ProjectSubTreeS
> +}
> +
> type Collection struct {
> Path string
> PoolPath string
> @@ -99,6 +104,7 @@ func getProjMeta(path string) (*Project, error) {
> return proj, nil
> }
>
> +// TODO: Works only properly with minimum number = 1. Maybe 0 is better?
> func buildLayerNGlob(depth uint) string {
> var i uint
> s := "/"
var (
i uint
s = "/"
)
> @@ -112,34 +118,99 @@ func buildLayerNGlob(depth uint) string {
> }
>
> // get subprojects N layers deep
> -func (p *Project) GetSubprojectsN(depth uint) ([]*Project, error) {
> +func (p *Project) GetSubprojectsN(depth int) ([]*Project, error) {
These guys can be grouped; reduces the noise a bit:
var (
subprojs []*Project
glob = p.Path + buildLayerNGlob(depth)
)
fmt.Println("glob: ", glob)
> var subprojs []*Project
>
> - glob := p.Path + buildLayerNGlob(depth)
> - fmt.Println("glob: ", glob)
> + glob := p.Path + buildLayerNGlob(uint(depth))
> matches, err := filepath.Glob(glob)
> if err != nil {
> return nil, err
> }
> - fmt.Println("nr of matches: ", len(matches))
>
> for _, m := range matches {
> - p, err := getProjMeta(m)
> + foundP, err := getProjMeta(m)
> if err != nil {
> return nil, err
> }
> - subprojs = append(subprojs, p)
> +
> + if foundP.UUID != p.UUID {
> + subprojs = append(subprojs, foundP)
> + }
> }
>
> return subprojs, nil
> }
>
> +// calls itself recursively until all subprojects have been found.
> +func (p *Project) GetSubprojectTree() ([]*ProjectSubTreeS, error) {
> + subprojs, err := p.GetSubprojectsN(1)
> + if len(subprojs) == 0 || err != nil {
> + return nil, err
> + }
> +
> + subtree := make([]*ProjectSubTreeS, 0, 8)
Append is used below. No make is required; let's offload this to go.
It has reasonable defaults.
> +
> + for _, subpr := range subprojs {
> + deeperSubs, err := subpr.GetSubprojectTree()
> + if err != nil {
> + break
> + }
> + filler := ProjectSubTreeS{subpr, deeperSubs}
I would use the field names to avoid bugs in the future:
filler := ProjectSubTreeS{foo: subpr, bar: deeperSubs}
> + subtree = append(subtree, &filler)
> + }
> +
> + return subtree, err
> +}
> +
> +// TODO implement this
> +func (c *Collection) GetProjectTree() ([]ProjectSubTreeS, error) {
> + var tree []ProjectSubTreeS
> +
> + _, err := c.AllProjects()
> + if err != nil {
> + return nil, err
> + }
> +
> + return tree, nil
> +}
> +
> +// Gets all Projects layer N deep. Can be used to get all root projects.
> +func (c *Collection) AllProjectsN(depth int) ([]*Project, error) {
> + var (
> + projs []*Project
> + err error
> + )
> + if depth == 0 {
> + return nil, fmt.Errorf("Invalid depth")
> + } else if depth == -1 {
> + return c.AllProjects()
> + }
> +
> + glob := c.ProjectsPath + buildLayerNGlob(uint(depth))
> + matches, err := filepath.Glob(glob)
> + if err != nil {
> + return nil, err
> + }
> +
> + for _, m := range matches {
> + foundP, err := getProjMeta(m)
> + if err != nil {
> + return nil, err
> + }
> +
> + projs = append(projs, foundP)
> + }
> +
> + return projs, nil
> +}
> +
> func (c *Collection) AllProjects() ([]*Project, error) {
> var (
> projs []*Project
> err error
> )
>
> + // FIXME: Chdir sucks
> if err = os.Chdir(c.ProjectsPath); err != nil {
> return nil, err
> }
> @@ -208,6 +279,8 @@ func (p *Collection) FindTags(t *Item) ([]*Tag, error) {
> return out, nil
> }
>
> +// TODO: warn user when there is more than 1 proj with identical name
> +// if there is, than make him choose by UUID.
> func (c *Collection) ProjectByTitle(title string) (*Project, error) {
> // TODO: implement this more efficently
> projs, err := c.AllProjects()
> diff --git a/lib/container.go b/lib/container.go
> index 5859a42..7a32365 100644
> --- a/lib/container.go
> +++ b/lib/container.go
> @@ -7,6 +7,7 @@ package qm
>
> import (
> "encoding/json"
> + "github.com/google/uuid"
Please run `goimports` on this file. The imports usually are grouped
differently. Ale in nvim does this automatically for you. :)
> "os"
> "path/filepath"
> "time"
> @@ -18,26 +19,24 @@ type Container struct {
> Description string `json:"description" yaml:"description"`
> CreatedAt time.Time `json:"created_at" yaml:"created_at"`
> Path string `json:"path" yaml:"-"`
> + UUID uuid.UUID `json:"uuid" yaml:"uuid"`
> parent string
> }
>
> -func checkParentExists(c *Collection, title string) error {
> - _, err := c.ProjectByTitle(title)
> - return err
> -}
> -
> +// FIXME: this should not only work for Projects
> func (c *Collection) CreateContainer(title,
> - descr, type_, parent string) (*Container, error) {
> + descr, type_, parentTitle string) (*Container, error) {
> var path string
> slug := Slug(title)
>
> - if parent == "" {
> + if parentTitle == "" {
> path = filepath.Join(c.Path, type_, slug)
> } else {
> - if err := checkParentExists(c, parent); err != nil {
> + parent, err := c.ProjectByTitle(parentTitle)
> + if err != nil {
> return nil, err
> }
> - path = filepath.Join(c.Path, type_, parent, slug)
> + path = filepath.Join(parent.Path, slug)
> }
>
> cont := &Container{
> @@ -46,7 +45,8 @@ func (c *Collection) CreateContainer(title,
> Description: descr,
> CreatedAt: time.Now(),
> Path: path,
> - parent: parent,
> + UUID: uuid.New(),
> + parent: parentTitle,
> }
>
> return cont, nil
> diff --git a/lib/project.go b/lib/project.go
> index 946b3d0..51b33f1 100644
> --- a/lib/project.go
> +++ b/lib/project.go
> @@ -19,10 +19,9 @@ type Project struct {
> //Progress uint `json:"progress" yaml:"progress"`
> }
>
> -func (c *Collection) CreateProject(title, description,
> - parent string) (*Project, error) {
> +func (c *Collection) CreateProject(title, descr, parent string) (*Project, error) {
> p := &Project{}
> - cont, err := c.CreateContainer(title, description, "projects", parent)
> + cont, err := c.CreateContainer(title, descr, "projects", parent)
> if err != nil {
> return nil, err
> }
> --
> 2.30.1
>
Re: [PATCH 3/3] bin: list: First version of project tree printing
On Thu, Mar 11, 2021 at 10:26:03PM +0100, Philipp Stanner wrote:
> Working, but incomplete first draft of proj-tree printing.
Yeah, I would say, merge it soon and let's play around with it.
> ---
> Thanks for your last patch.
> I have fixed the global variable and use defer instead.
> As for the recursion: Lots of work, we'll keep it, but feel free to
> offer a patch adjusting everything in a way you viel as appropriate, and
> we can have a discussion about this.
> But this code will probably change very quickly anyways.
> bin/qm-list/main.go | 116 ++++++++++++++++++++++++++++++++++++++------
> 1 file changed, 100 insertions(+), 16 deletions(-)
>
> diff --git a/bin/qm-list/main.go b/bin/qm-list/main.go
> index 512baca..6f99180 100644
> --- a/bin/qm-list/main.go
> +++ b/bin/qm-list/main.go
> @@ -2,51 +2,135 @@ package main
>
> import (
> "fmt"
> + "os"
>
> qm "git.sr.ht/~imperator/quartiermeister/lib"
> "github.com/integrii/flaggy"
> )
>
> -func listItems(c *qm.Collection) {
> +type options struct {
> + recurse uint
> + noLines bool
> +}
> +
> +// options should be global, otherwise get passed around 1000 times.
> +var opts options
> +
> +func listItems(c *qm.Collection) error {
> items, err := c.AllItems()
> if err != nil {
> - fmt.Println("error: ", err)
> - return
> + return err
> }
> for _, i := range items {
> fmt.Println(i.Title)
> }
> +
> + return nil
> +}
> +
> +// creates string prefix for printing a tree
> +func prefixByDepth(depth int) string {
> + if depth == 0 {
> + return ""
> + }
> + prefix := "├─"
> + lengthener := "────"
I prefer groups because it is always a lot clearer to me. No idea why, less
visual noise maybe.
var (
indentation string
prefix = "├─"
lengthener = "────"
)
> + if opts.noLines {
> + prefix = " "
> + lengthener = " "
> + }
> +
> + var indentation string
> + for i := 0; i < depth; i++ {
> + indentation += lengthener
> + }
> + prefix += indentation
> +
> + return prefix
> +}
> +
> +func decrDepth(depth *int) {
> + *depth -= 1
Pointer magic required?
Ah I see, it's below. Ok.
> +}
> +
> +// prints passed subproject and all its subprojects recursively
> +func printProjAndSubs(subproj *qm.ProjectSubTreeS, depth int) error {
> + defer decrDepth(&depth)
> +
> + // when this function recurses, the recursors get higher and higher
> + // numbers, causing the right indentation.
> + prefix := prefixByDepth(depth)
> + depth += 1
> +
> + // TODO: find out why there is a whitespace too much
More a FIXME than a TODO, isn't it? Never mind.
> + fmt.Println(prefix, subproj.Proj.Title)
> +
> + for _, proj := range subproj.Subprojs {
> + prefix = prefixByDepth(depth)
> + fmt.Println(prefix, proj.Proj.Title)
> + subprojs, err := proj.Proj.GetSubprojectTree()
> + if err != nil {
> + return err
> + }
> +
> + for _, sub := range subprojs {
> + depth += 1 // ugly... recursion.
> + printProjAndSubs(sub, depth)
> + depth -= 1
> + }
> + }
> +
> + return nil
> }
>
> -func listProjs(c *qm.Collection) {
> - projs, err := c.AllProjects()
> +// loads all projects and passes them to the print function
> +func printProjFamily(c *qm.Collection) error {
> + projs, err := c.AllProjectsN(1)
> if err != nil {
> - fmt.Println("error: ", err)
> - return
> + return err
> }
> - for _, p := range projs {
> - fmt.Println(p.Title)
> +
> + for _, proj := range projs {
> + tmpSubs, err := proj.GetSubprojectTree()
> + if err != nil {
> + return err
> + }
> +
> + tmp := qm.ProjectSubTreeS{proj, tmpSubs}
> + if err = printProjAndSubs(&tmp, 0); err != nil {
> + return err
> + }
> }
> +
> + return nil
> }
>
> func main() {
> - var (
> - itemCmd = flaggy.NewSubcommand("items")
> - projCmd = flaggy.NewSubcommand("projs")
> - )
> + var err error
> + itemCmd := flaggy.NewSubcommand("items")
> + projCmd := flaggy.NewSubcommand("projs")
> + projCmd.UInt(&opts.recurse, "r", "recurse",
> + "<N> show subprojects N layers deep")
> + flaggy.Bool(&opts.noLines, "n", "no-lines", "Show no lines")
> +
> flaggy.AttachSubcommand(itemCmd, 1)
> flaggy.AttachSubcommand(projCmd, 1)
> flaggy.Parse()
>
> c, err := qm.LoadCollection()
> if err != nil {
> - fmt.Println("error: ", err)
> + fmt.Fprintln(os.Stderr, "error: ", err)
Should we create a helper for this? For example `PrintError()`?
> return
> }
>
> if itemCmd.Used {
> - listItems(c)
> + err = listItems(c)
> } else if projCmd.Used {
> - listProjs(c)
> + err = printProjFamily(c)
> + }
> +
> + if err != nil {
> + fmt.Fprintln(os.Stderr, err)
> + os.Exit(1)
> }
> }
> --
> 2.30.1
>