From e02f1a367d2feef0de4deee7c3d035ab87f7ae75 Mon Sep 17 00:00:00 2001 From: Lucas Pearson Date: Wed, 20 Sep 2023 20:21:04 -0400 Subject: [PATCH 1/6] adds json to the list command --- go.mod | 4 +- go.sum | 2 + internal/cmd/list.go | 96 +++++++++++++++++++++++++++++++------------- 3 files changed, 72 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index a6077fd9..d8b5ff82 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/charmbracelet/bubbletea v0.24.2 github.com/charmbracelet/lipgloss v0.8.0 github.com/cli/cli/v2 v2.34.0 + github.com/cli/go-gh/v2 v2.3.0 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 github.com/creack/pty v1.1.18 github.com/fatih/color v1.15.0 @@ -19,6 +20,7 @@ require ( github.com/golang/mock v1.6.0 github.com/google/go-github/v45 v45.2.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 + github.com/itchyny/gojq v0.12.13 github.com/joho/godotenv v1.5.1 github.com/mattn/go-isatty v0.0.19 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d @@ -43,10 +45,10 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect - github.com/cli/go-gh/v2 v2.3.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/itchyny/timefmt-go v0.1.5 // indirect ) require ( diff --git a/go.sum b/go.sum index 574ddc45..a306d5cd 100644 --- a/go.sum +++ b/go.sum @@ -114,6 +114,8 @@ github.com/henvic/httpretty v0.1.2 h1:EQo556sO0xeXAjP10eB+BZARMuvkdGqtfeS4Ntjvki github.com/henvic/httpretty v0.1.2/go.mod h1:ViEsly7wgdugYtymX54pYp6Vv2wqZmNHayJ6q8tlKCc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= +github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= diff --git a/internal/cmd/list.go b/internal/cmd/list.go index 00a044b0..d0938b83 100644 --- a/internal/cmd/list.go +++ b/internal/cmd/list.go @@ -1,11 +1,14 @@ package cmd import ( + "bytes" + "encoding/json" "path/filepath" "sort" "strings" "github.com/cli/cli/v2/pkg/iostreams" + "github.com/cli/go-gh/pkg/jsonpretty" "github.com/cli/go-gh/pkg/tableprinter" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -13,6 +16,8 @@ import ( "github.com/stateful/runme/internal/shell" ) +var isJson bool + func listCmd() *cobra.Command { cmd := cobra.Command{ Use: "list [search]", @@ -49,38 +54,71 @@ func listCmd() *cobra.Command { // TODO: this should be taken from cmd. io := iostreams.System() - table := tableprinter.New(io.Out, io.IsStdoutTTY(), io.TerminalWidth()) - - // table header - table.AddField(strings.ToUpper("Name")) - table.AddField(strings.ToUpper("File")) - table.AddField(strings.ToUpper("First Command")) - table.AddField(strings.ToUpper("Description")) - table.AddField(strings.ToUpper("Named")) - table.EndRow() - - for _, fileBlock := range blocks { - block := fileBlock.Block - - lines := block.Lines() - - isNamedField := "Yes" - if block.IsUnnamed() { - isNamedField = "No" - } - - table.AddField(block.Name()) - table.AddField(fileBlock.File) - table.AddField(shell.TryGetNonCommentLine(lines)) - table.AddField(block.Intro()) - table.AddField(isNamedField) - table.EndRow() - } - - return errors.Wrap(table.Render(), "failed to render") + if !isJson { + table := tableprinter.New(io.Out, io.IsStdoutTTY(), io.TerminalWidth()) + + // table header + table.AddField(strings.ToUpper("Name")) + table.AddField(strings.ToUpper("File")) + table.AddField(strings.ToUpper("First Command")) + table.AddField(strings.ToUpper("Description")) + table.AddField(strings.ToUpper("Named")) + table.EndRow() + + for _, fileBlock := range blocks { + block := fileBlock.Block + + lines := block.Lines() + + isNamedField := "Yes" + if block.IsUnnamed() { + isNamedField = "No" + } + + table.AddField(block.Name()) + table.AddField(fileBlock.File) + table.AddField(shell.TryGetNonCommentLine(lines)) + table.AddField(block.Intro()) + table.AddField(isNamedField) + table.EndRow() + } + + return errors.Wrap(table.Render(), "failed to render") + } else { + type row struct { + Name string `json:"name"` + File string `json:"file"` + FirstCommand string `json:"first_command"` + Description string `json:"description"` + Named bool `json:"named"` + } + var rows []row + for _, fileBlock := range blocks { + block := fileBlock.Block + lines := block.Lines() + r := row{ + Name: block.Name(), + File: fileBlock.File, + FirstCommand: shell.TryGetNonCommentLine(lines), + Description: block.Intro(), + Named: !block.IsUnnamed(), + } + rows = append(rows, r) + } + by, err := json.Marshal(&rows) + if err != nil { + return err + } + err = jsonpretty.Format(io.Out, bytes.NewReader(by), " ", false) + if err != nil { + return err + } + return nil + } }, } + cmd.PersistentFlags().BoolVar(&isJson, "json", false, "This flag tells the list command to print the output in json") setDefaultFlags(&cmd) return &cmd From 31d5b4f5cf823004b4eee615a62fdac2b5984214 Mon Sep 17 00:00:00 2001 From: Lucas Pearson Date: Wed, 20 Sep 2023 21:55:28 -0400 Subject: [PATCH 2/6] fixed code with linting suggestions --- internal/cmd/list.go | 64 ++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/internal/cmd/list.go b/internal/cmd/list.go index d0938b83..bbdd2959 100644 --- a/internal/cmd/list.go +++ b/internal/cmd/list.go @@ -16,7 +16,7 @@ import ( "github.com/stateful/runme/internal/shell" ) -var isJson bool +var isJSON bool func listCmd() *cobra.Command { cmd := cobra.Command{ @@ -54,7 +54,7 @@ func listCmd() *cobra.Command { // TODO: this should be taken from cmd. io := iostreams.System() - if !isJson { + if !isJSON { table := tableprinter.New(io.Out, io.IsStdoutTTY(), io.TerminalWidth()) // table header @@ -84,41 +84,41 @@ func listCmd() *cobra.Command { } return errors.Wrap(table.Render(), "failed to render") - } else { - type row struct { - Name string `json:"name"` - File string `json:"file"` - FirstCommand string `json:"first_command"` - Description string `json:"description"` - Named bool `json:"named"` - } - var rows []row - for _, fileBlock := range blocks { - block := fileBlock.Block - lines := block.Lines() - r := row{ - Name: block.Name(), - File: fileBlock.File, - FirstCommand: shell.TryGetNonCommentLine(lines), - Description: block.Intro(), - Named: !block.IsUnnamed(), - } - rows = append(rows, r) - } - by, err := json.Marshal(&rows) - if err != nil { - return err - } - err = jsonpretty.Format(io.Out, bytes.NewReader(by), " ", false) - if err != nil { - return err + } + + type row struct { + Name string `json:"name"` + File string `json:"file"` + FirstCommand string `json:"first_command"` + Description string `json:"description"` + Named bool `json:"named"` + } + var rows []row + for _, fileBlock := range blocks { + block := fileBlock.Block + lines := block.Lines() + r := row{ + Name: block.Name(), + File: fileBlock.File, + FirstCommand: shell.TryGetNonCommentLine(lines), + Description: block.Intro(), + Named: !block.IsUnnamed(), } - return nil + rows = append(rows, r) + } + by, err := json.Marshal(&rows) + if err != nil { + return err + } + err = jsonpretty.Format(io.Out, bytes.NewReader(by), " ", false) + if err != nil { + return err } + return nil }, } - cmd.PersistentFlags().BoolVar(&isJson, "json", false, "This flag tells the list command to print the output in json") + cmd.PersistentFlags().BoolVar(&isJSON, "json", false, "This flag tells the list command to print the output in json") setDefaultFlags(&cmd) return &cmd From 3a7626048bc55d5dfe8401f5736fe86e55273e07 Mon Sep 17 00:00:00 2001 From: Lucas Pearson Date: Thu, 21 Sep 2023 14:02:55 -0400 Subject: [PATCH 3/6] move json and table displaying into their own functions move row struct up a level --- internal/cmd/list.go | 98 ++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/internal/cmd/list.go b/internal/cmd/list.go index bbdd2959..a6bca654 100644 --- a/internal/cmd/list.go +++ b/internal/cmd/list.go @@ -16,9 +16,16 @@ import ( "github.com/stateful/runme/internal/shell" ) -var isJSON bool +type row struct { + Name string `json:"name"` + File string `json:"file"` + FirstCommand string `json:"first_command"` + Description string `json:"description"` + Named bool `json:"named"` +} func listCmd() *cobra.Command { + var formatJSON bool cmd := cobra.Command{ Use: "list [search]", Aliases: []string{"ls"}, @@ -54,45 +61,6 @@ func listCmd() *cobra.Command { // TODO: this should be taken from cmd. io := iostreams.System() - if !isJSON { - table := tableprinter.New(io.Out, io.IsStdoutTTY(), io.TerminalWidth()) - - // table header - table.AddField(strings.ToUpper("Name")) - table.AddField(strings.ToUpper("File")) - table.AddField(strings.ToUpper("First Command")) - table.AddField(strings.ToUpper("Description")) - table.AddField(strings.ToUpper("Named")) - table.EndRow() - - for _, fileBlock := range blocks { - block := fileBlock.Block - - lines := block.Lines() - - isNamedField := "Yes" - if block.IsUnnamed() { - isNamedField = "No" - } - - table.AddField(block.Name()) - table.AddField(fileBlock.File) - table.AddField(shell.TryGetNonCommentLine(lines)) - table.AddField(block.Intro()) - table.AddField(isNamedField) - table.EndRow() - } - - return errors.Wrap(table.Render(), "failed to render") - } - - type row struct { - Name string `json:"name"` - File string `json:"file"` - FirstCommand string `json:"first_command"` - Description string `json:"description"` - Named bool `json:"named"` - } var rows []row for _, fileBlock := range blocks { block := fileBlock.Block @@ -106,24 +74,56 @@ func listCmd() *cobra.Command { } rows = append(rows, r) } - by, err := json.Marshal(&rows) - if err != nil { - return err - } - err = jsonpretty.Format(io.Out, bytes.NewReader(by), " ", false) - if err != nil { - return err + if !formatJSON { + return displayTable(io, rows) } - return nil + + return displayJSON(io, rows) }, } - cmd.PersistentFlags().BoolVar(&isJSON, "json", false, "This flag tells the list command to print the output in json") + cmd.PersistentFlags().BoolVar(&formatJSON, "json", false, "This flag tells the list command to print the output in json") setDefaultFlags(&cmd) return &cmd } +func displayTable(io *iostreams.IOStreams, rows []row) error { + table := tableprinter.New(io.Out, io.IsStdoutTTY(), io.TerminalWidth()) + + // table header + table.AddField(strings.ToUpper("Name")) + table.AddField(strings.ToUpper("File")) + table.AddField(strings.ToUpper("First Command")) + table.AddField(strings.ToUpper("Description")) + table.AddField(strings.ToUpper("Named")) + table.EndRow() + + for _, row := range rows { + named := "YES" + if !row.Named { + named = "NO" + } + table.AddField(row.Name) + table.AddField(row.File) + table.AddField(row.FirstCommand) + table.AddField(row.Description) + table.AddField(named) + table.EndRow() + } + + return errors.Wrap(table.Render(), "failed to render") + +} + +func displayJSON(io *iostreams.IOStreams, rows []row) error { + by, err := json.Marshal(&rows) + if err != nil { + return err + } + return jsonpretty.Format(io.Out, bytes.NewReader(by), " ", false) +} + // sort blocks in ascending nested order func sortBlocks(blocks []project.CodeBlock) (res []project.CodeBlock) { blocksByFile := make(map[string][]project.CodeBlock, 0) From 55c716de44a19ec61ea0d08d86a7b262d038f239 Mon Sep 17 00:00:00 2001 From: Lucas Pearson Date: Thu, 21 Sep 2023 21:40:32 -0400 Subject: [PATCH 4/6] correct table output added test for json --- internal/cmd/list.go | 4 ++-- testdata/script/basic.txtar | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/internal/cmd/list.go b/internal/cmd/list.go index a6bca654..0044b853 100644 --- a/internal/cmd/list.go +++ b/internal/cmd/list.go @@ -100,9 +100,9 @@ func displayTable(io *iostreams.IOStreams, rows []row) error { table.EndRow() for _, row := range rows { - named := "YES" + named := "Yes" if !row.Named { - named = "NO" + named = "No" } table.AddField(row.Name) table.AddField(row.File) diff --git a/testdata/script/basic.txtar b/testdata/script/basic.txtar index e2297837..7931490d 100644 --- a/testdata/script/basic.txtar +++ b/testdata/script/basic.txtar @@ -2,6 +2,10 @@ exec runme ls cmp stdout golden-list.txt ! stderr . +exec runme ls --json +cmp stdout golden-list-json.txt +! stderr . + exec runme ls --allow-unnamed=true cmp stdout golden-list-allow-unnamed.txt ! stderr . @@ -148,3 +152,27 @@ hello-js README.md console.log("Hello, runme, from javascript!") It can even run hello-js-cat README.md console.log("Hello, runme, from javascript!") And it can even run a cell with a custom interpreter. Yes tempdir README.md temp_dir=$(mktemp -d -t "runme-XXXXXXX") It works with cd, pushd, and similar because all lines are executed as a single script. No package-main README.md package main It can also execute a snippet of Go code. No +-- golden-list-json.txt -- +[ + { + "name": "echo", + "file": "README.md", + "first_command": "echo \"Hello, runme!\"", + "description": "With {name=hello} you can annotate it and give it a nice name.", + "named": true + }, + { + "name": "hello-js", + "file": "README.md", + "first_command": "console.log(\"Hello, runme, from javascript!\")", + "description": "It can even run scripting languages.", + "named": true + }, + { + "name": "hello-js-cat", + "file": "README.md", + "first_command": "console.log(\"Hello, runme, from javascript!\")", + "description": "And it can even run a cell with a custom interpreter.", + "named": true + } +] From 409f5b0da8f9d4a5f98606daa4b3cea2cc6b4920 Mon Sep 17 00:00:00 2001 From: Sebastian Tiedtke Date: Mon, 25 Sep 2023 10:28:35 -0400 Subject: [PATCH 5/6] Tidy modules --- go.mod | 4 +--- go.sum | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/go.mod b/go.mod index d8b5ff82..a6077fd9 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/charmbracelet/bubbletea v0.24.2 github.com/charmbracelet/lipgloss v0.8.0 github.com/cli/cli/v2 v2.34.0 - github.com/cli/go-gh/v2 v2.3.0 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 github.com/creack/pty v1.1.18 github.com/fatih/color v1.15.0 @@ -20,7 +19,6 @@ require ( github.com/golang/mock v1.6.0 github.com/google/go-github/v45 v45.2.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 - github.com/itchyny/gojq v0.12.13 github.com/joho/godotenv v1.5.1 github.com/mattn/go-isatty v0.0.19 github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d @@ -45,10 +43,10 @@ require ( dario.cat/mergo v1.0.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/benbjohnson/clock v1.3.5 // indirect + github.com/cli/go-gh/v2 v2.3.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/itchyny/timefmt-go v0.1.5 // indirect ) require ( diff --git a/go.sum b/go.sum index a306d5cd..574ddc45 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,6 @@ github.com/henvic/httpretty v0.1.2 h1:EQo556sO0xeXAjP10eB+BZARMuvkdGqtfeS4Ntjvki github.com/henvic/httpretty v0.1.2/go.mod h1:ViEsly7wgdugYtymX54pYp6Vv2wqZmNHayJ6q8tlKCc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/itchyny/gojq v0.12.13/go.mod h1:JzwzAqenfhrPUuwbmEz3nu3JQmFLlQTQMUcOdnu/Sf4= -github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= From f9d152f2b2c54ee9425a48a79afa11d1bc26efd8 Mon Sep 17 00:00:00 2001 From: Sebastian Tiedtke Date: Mon, 25 Sep 2023 10:33:11 -0400 Subject: [PATCH 6/6] Make gofumpt happy --- internal/cmd/list.go | 107 +++++++++++++++++++++---------------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/internal/cmd/list.go b/internal/cmd/list.go index 0044b853..8984bcdb 100644 --- a/internal/cmd/list.go +++ b/internal/cmd/list.go @@ -17,15 +17,15 @@ import ( ) type row struct { - Name string `json:"name"` - File string `json:"file"` - FirstCommand string `json:"first_command"` - Description string `json:"description"` - Named bool `json:"named"` + Name string `json:"name"` + File string `json:"file"` + FirstCommand string `json:"first_command"` + Description string `json:"description"` + Named bool `json:"named"` } func listCmd() *cobra.Command { - var formatJSON bool + var formatJSON bool cmd := cobra.Command{ Use: "list [search]", Aliases: []string{"ls"}, @@ -61,67 +61,66 @@ func listCmd() *cobra.Command { // TODO: this should be taken from cmd. io := iostreams.System() - var rows []row - for _, fileBlock := range blocks { - block := fileBlock.Block - lines := block.Lines() - r := row{ - Name: block.Name(), - File: fileBlock.File, - FirstCommand: shell.TryGetNonCommentLine(lines), - Description: block.Intro(), - Named: !block.IsUnnamed(), - } - rows = append(rows, r) - } - if !formatJSON { - return displayTable(io, rows) - } - - return displayJSON(io, rows) + var rows []row + for _, fileBlock := range blocks { + block := fileBlock.Block + lines := block.Lines() + r := row{ + Name: block.Name(), + File: fileBlock.File, + FirstCommand: shell.TryGetNonCommentLine(lines), + Description: block.Intro(), + Named: !block.IsUnnamed(), + } + rows = append(rows, r) + } + if !formatJSON { + return displayTable(io, rows) + } + + return displayJSON(io, rows) }, } - cmd.PersistentFlags().BoolVar(&formatJSON, "json", false, "This flag tells the list command to print the output in json") + cmd.PersistentFlags().BoolVar(&formatJSON, "json", false, "This flag tells the list command to print the output in json") setDefaultFlags(&cmd) return &cmd } func displayTable(io *iostreams.IOStreams, rows []row) error { - table := tableprinter.New(io.Out, io.IsStdoutTTY(), io.TerminalWidth()) - - // table header - table.AddField(strings.ToUpper("Name")) - table.AddField(strings.ToUpper("File")) - table.AddField(strings.ToUpper("First Command")) - table.AddField(strings.ToUpper("Description")) - table.AddField(strings.ToUpper("Named")) - table.EndRow() - - for _, row := range rows { - named := "Yes" - if !row.Named { - named = "No" - } - table.AddField(row.Name) - table.AddField(row.File) - table.AddField(row.FirstCommand) - table.AddField(row.Description) - table.AddField(named) - table.EndRow() - } - - return errors.Wrap(table.Render(), "failed to render") + table := tableprinter.New(io.Out, io.IsStdoutTTY(), io.TerminalWidth()) + + // table header + table.AddField(strings.ToUpper("Name")) + table.AddField(strings.ToUpper("File")) + table.AddField(strings.ToUpper("First Command")) + table.AddField(strings.ToUpper("Description")) + table.AddField(strings.ToUpper("Named")) + table.EndRow() + + for _, row := range rows { + named := "Yes" + if !row.Named { + named = "No" + } + table.AddField(row.Name) + table.AddField(row.File) + table.AddField(row.FirstCommand) + table.AddField(row.Description) + table.AddField(named) + table.EndRow() + } + return errors.Wrap(table.Render(), "failed to render") } func displayJSON(io *iostreams.IOStreams, rows []row) error { - by, err := json.Marshal(&rows) - if err != nil { - return err - } - return jsonpretty.Format(io.Out, bytes.NewReader(by), " ", false) + by, err := json.Marshal(&rows) + if err != nil { + return err + } + return jsonpretty.Format(io.Out, bytes.NewReader(by), " ", false) } // sort blocks in ascending nested order