diff --git a/.gitignore b/.gitignore index 82608cd..58b8639 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ y.output _vendor-* vendor + +.vscode/ diff --git a/completed.go b/completed.go index 4b426e7..584dbe2 100644 --- a/completed.go +++ b/completed.go @@ -2,12 +2,20 @@ package main import ( "context" + "encoding/json" - "github.com/sachaos/todoist/lib" + todoist "github.com/sachaos/todoist/lib" "github.com/urfave/cli" ) +type CompletedJSON struct { + ID string `json:"id"` + CompletedDate string `json:"completed_date"` + Project string `json:"project"` + Content string `json:"content"` +} + func CompletedList(c *cli.Context) error { client := GetClient(c) @@ -25,12 +33,15 @@ func CompletedList(c *cli.Context) error { return err } + isJson := c.GlobalBool("json") + defer writer.Flush() - if c.GlobalBool("header") { + if !isJson && c.GlobalBool("header") { writer.Write([]string{"ID", "CompletedDate", "Project", "Content"}) } + var jsonObjects []CompletedJSON for _, item := range completed.Items { result, err := Eval(ex, item, client.Store.Projects, client.Store.Labels) if err != nil { @@ -39,12 +50,27 @@ func CompletedList(c *cli.Context) error { if !result { continue } - writer.Write([]string{ - IdFormat(item), - CompletedDateFormat(item.DateTime()), - ProjectFormat(item.ProjectID, client.Store, projectColorHash, c), - ContentFormat(item), - }) + + obj := CompletedJSON{ + ID: IdFormat(item), + CompletedDate: CompletedDateFormat(item.DateTime()), + Project: ProjectFormat(item.ProjectID, client.Store, projectColorHash, c), + Content: ContentFormat(item), + } + if isJson { + jsonObjects = append(jsonObjects, obj) + } else { + writer.Write([]string{obj.ID, obj.CompletedDate, obj.Project, obj.Content}) + } + + } + + if isJson { + jsonData, err := json.Marshal(jsonObjects) + if err != nil { + return CommandFailed + } + writer.Write([]string{string(jsonData)}) } return nil diff --git a/labels.go b/labels.go index 4c713c8..bb12727 100644 --- a/labels.go +++ b/labels.go @@ -1,26 +1,50 @@ package main import ( + "encoding/json" "os" "text/tabwriter" "github.com/urfave/cli" ) +type LabelJSON struct { + ID string `json:"id"` + Name string `json:"name"` +} + func Labels(c *cli.Context) error { client := GetClient(c) w := new(tabwriter.Writer) w.Init(os.Stdout, 0, 4, 1, ' ', 0) - defer writer.Flush() + isJson := c.GlobalBool("json") - if c.GlobalBool("header") { + defer writer.Flush() + if !isJson && c.GlobalBool("header") { writer.Write([]string{"ID", "Name"}) } + var jsonObjects []LabelJSON for _, label := range client.Store.Labels { - writer.Write([]string{IdFormat(label), "@" + label.Name}) + obj := LabelJSON{ + ID: IdFormat(label), + Name: "@" + label.Name, + } + if isJson { + jsonObjects = append(jsonObjects, obj) + } else { + writer.Write([]string{obj.ID, obj.Name}) + } + } + + if isJson { + jsonData, err := json.Marshal(jsonObjects) + if err != nil { + return CommandFailed + } + writer.Write([]string{string(jsonData)}) } return nil diff --git a/list.go b/list.go index d1771b9..cb3a997 100644 --- a/list.go +++ b/list.go @@ -1,8 +1,10 @@ package main import ( + "encoding/json" "fmt" "os" + "sort" "github.com/acarl005/stripansi" todoist "github.com/sachaos/todoist/lib" @@ -21,18 +23,13 @@ func traverseItems(item *todoist.Item, f func(item *todoist.Item, depth int), de } } -func sortItems(itemListPtr *[][]string, byIndex int) { - itemList := *itemListPtr - length := len(itemList) - for i := 0; i < length-1; i++ { - for j := 0; j < length-1-i; j++ { - if stripansi.Strip(itemList[j][byIndex]) > stripansi.Strip(itemList[j+1][byIndex]) { - tmp := itemList[j] - itemList[j] = itemList[j+1] - itemList[j+1] = tmp - } - } - } +type TaskJSON struct { + ID string `json:"id"` + Priority string `json:"priority"` + DueDate string `json:"due_date"` + Project string `json:"project"` + Labels string `json:"labels"` + Content string `json:"content"` } func List(c *cli.Context) error { @@ -47,7 +44,6 @@ func List(c *cli.Context) error { projectColorHash := GenerateColorHash(projectIds, colorList) ex := Filter(c.String("filter")) - itemList := [][]string{} rootItem := client.Store.RootItem if rootItem == nil { @@ -55,6 +51,9 @@ func List(c *cli.Context) error { return nil } + isJson := c.GlobalBool("json") + + var jsonObjects []TaskJSON traverseItems(rootItem, func(item *todoist.Item, depth int) { r, err := Eval(ex, item, client.Store.Projects, client.Store.Labels) if err != nil { @@ -63,31 +62,42 @@ func List(c *cli.Context) error { if !r || item.Checked == 1 { return } - itemList = append(itemList, []string{ - IdFormat(item), - PriorityFormat(item.Priority), - DueDateFormat(item.DateTime(), item.AllDay), - ProjectFormat(item.ProjectID, client.Store, projectColorHash, c) + + obj := TaskJSON{ + ID: IdFormat(item), + Priority: PriorityFormat(item.Priority), + DueDate: DueDateFormat(item.DateTime(), item.AllDay), + Project: ProjectFormat(item.ProjectID, client.Store, projectColorHash, c) + SectionFormat(item.SectionID, client.Store, c), - item.LabelsString(client.Store), - ContentPrefix(client.Store, item, depth, c) + ContentFormat(item), - }) + Labels: item.LabelsString(client.Store), + Content: ContentPrefix(client.Store, item, depth, c) + ContentFormat(item), + } + jsonObjects = append(jsonObjects, obj) }, 0) - if c.Bool("priority") == true { + if c.Bool("priority") { // sort output by priority // and no need to use "else block" as items returned by API are already sorted by task id - sortItems(&itemList, 1) + sort.Slice(jsonObjects, func(i, j int) bool { + return stripansi.Strip(jsonObjects[i].Priority) < stripansi.Strip(jsonObjects[j].Priority) + }) } defer writer.Flush() - if c.GlobalBool("header") { - writer.Write([]string{"ID", "Priority", "DueDate", "Project", "Labels", "Content"}) - } + if isJson { + jsonData, err := json.Marshal(jsonObjects) + if err != nil { + return CommandFailed + } + writer.Write([]string{string(jsonData)}) + } else { + if c.GlobalBool("header") { + writer.Write([]string{"ID", "Priority", "DueDate", "Project", "Labels", "Content"}) + } - for _, strings := range itemList { - writer.Write(strings) + for _, obj := range jsonObjects { + writer.Write([]string{obj.ID, obj.Priority, obj.DueDate, obj.Project, obj.Labels, obj.Content}) + } } return nil diff --git a/main.go b/main.go index f183320..9691ae0 100644 --- a/main.go +++ b/main.go @@ -13,7 +13,7 @@ import ( "github.com/fatih/color" "github.com/rkoesters/xdg/basedir" - "github.com/sachaos/todoist/lib" + todoist "github.com/sachaos/todoist/lib" "github.com/spf13/viper" "github.com/urfave/cli" ) @@ -101,6 +101,10 @@ func main() { Name: "csv", Usage: "output in CSV format", }, + cli.BoolFlag{ + Name: "json, j", + Usage: "output in JSON format", + }, cli.BoolFlag{ Name: "debug", Usage: "output logs", diff --git a/projects.go b/projects.go index 94eb934..f54ba00 100644 --- a/projects.go +++ b/projects.go @@ -1,15 +1,22 @@ package main import ( - "github.com/sachaos/todoist/lib" + "encoding/json" + + todoist "github.com/sachaos/todoist/lib" "github.com/urfave/cli" ) +type ProjectJSON struct { + ID string `json:"id"` + Name string `json:"name"` +} + func traverseProjects(pjt *todoist.Project, f func(pjt *todoist.Project, depth int), depth int) { f(pjt, depth) if pjt.ChildProject != nil { - traverseProjects(pjt.ChildProject, f, depth + 1) + traverseProjects(pjt.ChildProject, f, depth+1) } if pjt.BrotherProject != nil { @@ -27,21 +34,32 @@ func Projects(c *cli.Context) error { } projectColorHash := GenerateColorHash(projectIds, colorList) - itemList := [][]string{} project := client.Store.RootProject + var jsonObjects []ProjectJSON traverseProjects(project, func(pjt *todoist.Project, depth int) { - itemList = append(itemList, []string{IdFormat(pjt), ProjectFormat(pjt.ID, client.Store, projectColorHash, c)}) + jsonObjects = append(jsonObjects, ProjectJSON{ + ID: IdFormat(pjt), + Name: ProjectFormat(pjt.ID, client.Store, projectColorHash, c), + }) }, 0) defer writer.Flush() - if c.GlobalBool("header") { - writer.Write([]string{"ID", "Name"}) - } + if c.GlobalBool("json") { + jsonData, err := json.Marshal(jsonObjects) + if err != nil { + return CommandFailed + } + writer.Write([]string{string(jsonData)}) + } else { + if c.GlobalBool("header") { + writer.Write([]string{"ID", "Name"}) + } - for _, strings := range itemList { - writer.Write(strings) + for _, obj := range jsonObjects { + writer.Write([]string{obj.ID, obj.Name}) + } } return nil diff --git a/todoist_functions_fzf_bash.sh b/todoist_functions_fzf_bash.sh index b804b4f..ee59a39 100644 --- a/todoist_functions_fzf_bash.sh +++ b/todoist_functions_fzf_bash.sh @@ -70,7 +70,7 @@ _todoist() { __todoist_debug "${FUNCNAME[0]}(): cmd=$cmd" # Global options present in all commands - opts='--header --color --csv --debug --namespace --indent'\ + opts='--header --color --csv --debug --namespace --indent --json -j'\ ' --project-namespace --help -h --version -v ' case "$cmd" in