From dc11a11a41194d415d11a8f3d255a959b51b1704 Mon Sep 17 00:00:00 2001 From: Jon Hadfield Date: Wed, 20 Dec 2023 09:10:25 +0000 Subject: [PATCH] enable displaying list of advanced checklists. --- checklists.go | 157 +++++++++++++++++++++++++++++++++++++++++ cmd/sncli/checklist.go | 81 +++++++++++++++++++++ cmd/sncli/get.go | 4 ++ cmd/sncli/main.go | 1 + cmd/sncli/note.go | 11 +++ go.mod | 6 +- go.sum | 12 ++-- main.go | 12 ++++ 8 files changed, 275 insertions(+), 9 deletions(-) create mode 100644 checklists.go create mode 100644 cmd/sncli/checklist.go diff --git a/checklists.go b/checklists.go new file mode 100644 index 0000000..ceb8825 --- /dev/null +++ b/checklists.go @@ -0,0 +1,157 @@ +package sncli + +import ( + "fmt" + "github.com/alexeyco/simpletable" + "github.com/gookit/color" + "github.com/jonhadfield/gosn-v2/cache" + "github.com/jonhadfield/gosn-v2/items" + "slices" + "time" +) + +func conflictedWarning([]items.Checklist) string { + if len(items.Checklists{}) > 0 { + return color.Yellow.Sprintf("%d conflicted versions", len(items.Checklists{})) + } + + return "-" +} + +func (ci *ListChecklistsInput) Run() (err error) { + checklists, err := getChecklists(ci.Session) + if err != nil { + return err + } + + table := simpletable.New() + + table.Header = &simpletable.Header{ + Cells: []*simpletable.Cell{ + {Align: simpletable.AlignCenter, Text: "Title"}, + {Align: simpletable.AlignCenter, Text: "Last Updated"}, + {Align: simpletable.AlignCenter, Text: "UUID"}, + {Align: simpletable.AlignCenter, Text: "Issues"}, + }, + } + + for _, row := range checklists { + r := []*simpletable.Cell{ + {Align: simpletable.AlignLeft, Text: fmt.Sprintf("%s", row.Title)}, + {Align: simpletable.AlignLeft, Text: fmt.Sprintf("%s", row.UpdatedAt.String())}, + {Align: simpletable.AlignLeft, Text: fmt.Sprintf("%s", row.UUID)}, + {Align: simpletable.AlignLeft, Text: fmt.Sprintf("%s", conflictedWarning(row.Duplicates))}, + } + + table.Body.Cells = append(table.Body.Cells, r) + } + + table.SetStyle(simpletable.StyleCompactLite) + fmt.Println(table.String()) + + return nil +} + +// construct a map of duplicates +func getChecklistsDuplicatesMap(checklistNotes items.Notes) (map[string][]items.Checklist, error) { + duplicates := make(map[string][]items.Checklist) + + for x := range checklistNotes { + if checklistNotes[x].DuplicateOf != "" { + // checklist is a duplicate + // get the checklist content + cl, err := checklistNotes[x].Content.ToCheckList() + if err != nil { + return map[string][]items.Checklist{}, err + } + + // skip trashed content + if cl.Trashed { + continue + } + + cl.UUID = checklistNotes[x].UUID + cl.UpdatedAt, err = time.Parse(timeLayout, checklistNotes[x].UpdatedAt) + if err != nil { + return map[string][]items.Checklist{}, err + } + + duplicates[checklistNotes[x].DuplicateOf] = append(duplicates[checklistNotes[x].DuplicateOf], cl) + } + } + + return duplicates, nil +} + +func getChecklists(sess *cache.Session) (items.Checklists, error) { + var so cache.SyncOutput + + so, err := Sync(cache.SyncInput{ + Session: sess, + }, true) + if err != nil { + return items.Checklists{}, err + } + + var allPersistedItems cache.Items + + if err = so.DB.All(&allPersistedItems); err != nil { + return items.Checklists{}, err + } + + allItemUUIDs := allPersistedItems.UUIDs() + + var gitems items.Items + gitems, err = allPersistedItems.ToItems(sess) + if err != nil { + return items.Checklists{}, err + } + + gitems.Filter(items.ItemFilters{ + Filters: []items.Filter{ + { + Type: "Note", + Key: "editor", + Comparison: "==", + Value: "com.sncommunity.advanced-checklist", + }, + }, + }) + + var checklists items.Checklists + checklistNotes := gitems.Notes() + + duplicatesMap, err := getChecklistsDuplicatesMap(checklistNotes) + // strip any duplicated items that no longer exist + for k := range duplicatesMap { + if !slices.Contains(allItemUUIDs, k) { + delete(duplicatesMap, k) + } + } + + // second pass to get all non-deleted and non-trashed checklists + for x := range checklistNotes { + // strip deleted and trashed + if checklistNotes[x].Deleted || checklistNotes[x].Content.Trashed != nil && *checklistNotes[x].Content.Trashed { + continue + } + + var cl items.Checklist + cl, err = checklistNotes[x].Content.ToCheckList() + if err != nil { + return items.Checklists{}, err + } + + cl.UUID = checklistNotes[x].UUID + cl.UpdatedAt, err = time.Parse(timeLayout, checklistNotes[x].UpdatedAt) + if err != nil { + return items.Checklists{}, err + } + + cl.Duplicates = duplicatesMap[checklistNotes[x].UUID] + + checklists = append(checklists, cl) + } + + return checklists, nil +} diff --git a/cmd/sncli/checklist.go b/cmd/sncli/checklist.go new file mode 100644 index 0000000..ce8b51c --- /dev/null +++ b/cmd/sncli/checklist.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "github.com/jonhadfield/gosn-v2/cache" + sncli "github.com/jonhadfield/sn-cli" + "github.com/urfave/cli/v2" +) + +func cmdChecklist() *cli.Command { + return &cli.Command{ + Name: "checklist", + Usage: "manage checklists", + BashComplete: func(c *cli.Context) { + addTasks := []string{"list"} + if c.NArg() > 0 { + return + } + for _, t := range addTasks { + fmt.Println(t) + } + }, + Subcommands: []*cli.Command{ + { + Name: "list", + Usage: "list checklists", + Action: func(c *cli.Context) error { + var opts configOptsOutput + opts, err := getOpts(c) + if err != nil { + return err + } + var sess cache.Session + sess, _, err = cache.GetSession(opts.useSession, opts.sessKey, opts.server, opts.debug) + if !sess.SchemaValidation { + panic("schema validation is false") + } + if err != nil { + return err + } + sess.CacheDBPath, err = cache.GenCacheDBPath(sess, opts.cacheDBDir, snAppName) + if err != nil { + return err + } + listChecklistsConfig := sncli.ListChecklistsInput{ + Session: &sess, + } + + return listChecklistsConfig.Run() + }, + }, + // { + // Name: "show", + // Usage: "show checklist", + // BashComplete: func(c *cli.Context) { + // if c.NArg() > 0 { + // return + // } + // fmt.Println("--title") + // }, + // Flags: []cli.Flag{ + // &cli.StringFlag{ + // Name: "title", + // Usage: "new tag title (separate multiple with commas)", + // }, + // }, + // Action: func(c *cli.Context) error { + // var opts configOptsOutput + // opts, err := getOpts(c) + // if err != nil { + // return err + // } + // // useStdOut = opts.useStdOut + // err = processAddTags(c, opts) + // + // return err + // }, + // }, + }, + } +} diff --git a/cmd/sncli/get.go b/cmd/sncli/get.go index dbd276c..321fe0f 100644 --- a/cmd/sncli/get.go +++ b/cmd/sncli/get.go @@ -242,6 +242,10 @@ func cmdGet() *cli.Command { Name: "uuid", Usage: "find by uuid", }, + &cli.StringFlag{ + Name: "editor", + Usage: "find by associated editor", + }, &cli.BoolFlag{ Name: "include-trash", Usage: "include notes in trash", diff --git a/cmd/sncli/main.go b/cmd/sncli/main.go index bde4c3b..877e4db 100644 --- a/cmd/sncli/main.go +++ b/cmd/sncli/main.go @@ -125,6 +125,7 @@ func appSetup() (app *cli.App, err error) { } app.Commands = []*cli.Command{ cmdAdd(), + cmdChecklist(), cmdDebug(), cmdDelete(), cmdEdit(), diff --git a/cmd/sncli/note.go b/cmd/sncli/note.go index 0865f0d..9fd5207 100644 --- a/cmd/sncli/note.go +++ b/cmd/sncli/note.go @@ -306,6 +306,7 @@ func processGetNotes(c *cli.Context, opts configOptsOutput) (err error) { uuid := c.String("uuid") title := c.String("title") text := c.String("text") + editor := c.String("editor") count := c.Bool("count") output := c.String("output") @@ -357,6 +358,16 @@ func processGetNotes(c *cli.Context, opts configOptsOutput) (err error) { getNotesIF.Filters = append(getNotesIF.Filters, titleFilter) } + if editor != "" { + editorFilter := items.Filter{ + Type: common.SNItemTypeNote, + Key: "Editor", + Comparison: "contains", + Value: editor, + } + getNotesIF.Filters = append(getNotesIF.Filters, editorFilter) + } + processedTags := sncli.CommaSplit(c.String("tag")) if len(processedTags) > 0 { diff --git a/go.mod b/go.mod index 3c2b347..ba5166a 100644 --- a/go.mod +++ b/go.mod @@ -8,9 +8,9 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/ryanuber/columnize v2.1.2+incompatible - github.com/spf13/viper v1.18.1 + github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.16.0 // indirect + golang.org/x/crypto v0.17.0 // indirect gopkg.in/yaml.v2 v2.4.0 ) @@ -45,7 +45,7 @@ require ( github.com/divan/num2words v0.0.0-20170904212200-57dba452f942 github.com/dustin/go-humanize v1.0.1 github.com/gookit/color v1.5.4 - github.com/jonhadfield/gosn-v2 v0.0.0-20231218094330-c9b925b73ab2 + github.com/jonhadfield/gosn-v2 v0.0.0-20231220090451-c5c78c5c6cf0 github.com/urfave/cli/v2 v2.26.0 ) diff --git a/go.sum b/go.sum index c3e5bfd..de5ee75 100644 --- a/go.sum +++ b/go.sum @@ -56,10 +56,8 @@ github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/jonhadfield/gosn-v2 v0.0.0-20231217230122-7ad4020ae28b h1:bda/T+FFoy4Spv7+y+Vmj27Ads1IvHZvUJ69GCbaIf4= -github.com/jonhadfield/gosn-v2 v0.0.0-20231217230122-7ad4020ae28b/go.mod h1:qqdCDCvEgB6kNNAObVojNMcf8r7ksmsjHvbTEFaI8uI= -github.com/jonhadfield/gosn-v2 v0.0.0-20231218094330-c9b925b73ab2 h1:t54TnCnAWdwMTyhFB4yhyngxDxU3++odYjSWfYi3zSQ= -github.com/jonhadfield/gosn-v2 v0.0.0-20231218094330-c9b925b73ab2/go.mod h1:qqdCDCvEgB6kNNAObVojNMcf8r7ksmsjHvbTEFaI8uI= +github.com/jonhadfield/gosn-v2 v0.0.0-20231220090451-c5c78c5c6cf0 h1:kInkleKWgmbM49ULHTWYOH0su7cIHxDTTqh+hKItyrY= +github.com/jonhadfield/gosn-v2 v0.0.0-20231220090451-c5c78c5c6cf0/go.mod h1:zaqVJQbjrhGWX0/UWY+Fp9oOJVBe7J/0DJmNmiuifs4= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -121,6 +119,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.1 h1:rmuU42rScKWlhhJDyXZRKJQHXFX02chSVW1IvkPGiVM= github.com/spf13/viper v1.18.1/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -149,8 +149,8 @@ go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= diff --git a/main.go b/main.go index 32ed7e3..258ee46 100644 --- a/main.go +++ b/main.go @@ -214,6 +214,18 @@ type DeleteTagConfig struct { Debug bool } +type ListChecklistsInput struct { + Session *cache.Session + Debug bool +} + +type ShowChecklistInput struct { + Session *cache.Session + Title string + UUID string + Debug bool +} + type AddNoteInput struct { Session *cache.Session Title string