Refactor Print method for lists

This also fixes in the case of nil list

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
This commit is contained in:
Ulysses Souza 2020-09-30 10:32:26 +02:00
parent 8961805412
commit 0f6f547214
19 changed files with 151 additions and 295 deletions

View File

@ -21,15 +21,11 @@ import (
"fmt"
"io"
"os"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/errdefs"
"github.com/docker/compose-cli/formatter"
)
@ -60,26 +56,9 @@ func runList(ctx context.Context, opts composeOptions) error {
return err
}
return printListFormatted(opts.Format, os.Stdout, stackList)
}
func printListFormatted(format string, out io.Writer, stackList []compose.Stack) error {
var err error
switch strings.ToLower(format) {
case formatter.PRETTY, "":
err = formatter.PrintPrettySection(out, func(w io.Writer) {
for _, stack := range stackList {
fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status)
}
}, "NAME", "STATUS")
case formatter.JSON:
outJSON, err := formatter.ToStandardJSON(stackList)
if err != nil {
return err
return formatter.Print(stackList, opts.Format, os.Stdout, func(w io.Writer) {
for _, stack := range stackList {
_, _ = fmt.Fprintf(w, "%s\t%s\n", stack.Name, stack.Status)
}
_, _ = fmt.Fprint(out, outJSON)
default:
err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format)
}
return err
}, "NAME", "STATUS")
}

View File

@ -1,45 +0,0 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package compose
import (
"bytes"
"testing"
"gotest.tools/assert"
"gotest.tools/golden"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/formatter"
)
func TestPrintComposeList(t *testing.T) {
secretList := []compose.Stack{
{
ID: "123",
Name: "myName123",
Status: "Running",
},
}
out := &bytes.Buffer{}
assert.NilError(t, printListFormatted(formatter.PRETTY, out, secretList))
golden.Assert(t, out.String(), "compose-list-out.golden")
out.Reset()
assert.NilError(t, printListFormatted(formatter.JSON, out, secretList))
golden.Assert(t, out.String(), "compose-list-out-json.golden")
}

View File

@ -23,12 +23,9 @@ import (
"os"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/errdefs"
"github.com/docker/compose-cli/formatter"
)
@ -61,26 +58,11 @@ func runPs(ctx context.Context, opts composeOptions) error {
return err
}
return printPsFormatted(opts.Format, os.Stdout, serviceList)
}
func printPsFormatted(format string, out io.Writer, serviceList []compose.ServiceStatus) error {
var err error
switch strings.ToLower(format) {
case formatter.PRETTY, "":
err = formatter.PrintPrettySection(out, func(w io.Writer) {
return formatter.Print(serviceList, opts.Format, os.Stdout,
func(w io.Writer) {
for _, service := range serviceList {
fmt.Fprintf(w, "%s\t%s\t%d/%d\t%s\n", service.ID, service.Name, service.Replicas, service.Desired, strings.Join(service.Ports, ", "))
}
}, "ID", "NAME", "REPLICAS", "PORTS")
case formatter.JSON:
outJSON, err := formatter.ToStandardJSON(serviceList)
if err != nil {
return err
}
_, _ = fmt.Fprint(out, outJSON)
default:
err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format)
}
return err
},
"ID", "NAME", "REPLICAS", "PORTS")
}

View File

@ -29,7 +29,6 @@ import (
"github.com/docker/compose-cli/cli/mobycli"
apicontext "github.com/docker/compose-cli/context"
"github.com/docker/compose-cli/context/store"
"github.com/docker/compose-cli/errdefs"
"github.com/docker/compose-cli/formatter"
)
@ -98,17 +97,11 @@ func runList(cmd *cobra.Command, opts lsOpts) error {
opts.format = formatter.JSON
}
return printContextLsFormatted(opts.format, currentContext, os.Stdout, contexts)
}
func printContextLsFormatted(format string, currContext string, out io.Writer, contexts []*store.DockerContext) error {
var err error
switch strings.ToLower(format) {
case formatter.PRETTY, "":
err = formatter.PrintPrettySection(out, func(w io.Writer) {
return formatter.Print(contexts, opts.format, os.Stdout,
func(w io.Writer) {
for _, c := range contexts {
contextName := c.Name
if c.Name == currContext {
if c.Name == currentContext {
contextName += " *"
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
@ -119,17 +112,8 @@ func printContextLsFormatted(format string, currContext string, out io.Writer, c
getEndpoint("kubernetes", c.Endpoints),
c.Metadata.StackOrchestrator)
}
}, "NAME", "TYPE", "DESCRIPTION", "DOCKER ENDPOINT", "KUBERNETES ENDPOINT", "ORCHESTRATOR")
case formatter.JSON:
out, err := formatter.ToStandardJSON(contexts)
if err != nil {
return err
}
fmt.Println(out)
default:
err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format)
}
return err
},
"NAME", "TYPE", "DESCRIPTION", "DOCKER ENDPOINT", "KUBERNETES ENDPOINT", "ORCHESTRATOR")
}
func getEndpoint(name string, meta map[string]interface{}) string {

View File

@ -28,7 +28,6 @@ import (
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/containers"
"github.com/docker/compose-cli/errdefs"
formatter2 "github.com/docker/compose-cli/formatter"
"github.com/docker/compose-cli/utils/formatter"
)
@ -79,7 +78,7 @@ func runPs(ctx context.Context, opts psOpts) error {
containerList, err := c.ContainerService().List(ctx, opts.all)
if err != nil {
return errors.Wrap(err, "fetch containerList")
return errors.Wrap(err, "fetch containers")
}
if opts.quiet {
@ -93,30 +92,12 @@ func runPs(ctx context.Context, opts psOpts) error {
opts.format = formatter2.JSON
}
return printPsFormatted(opts.format, os.Stdout, containerList)
}
func printPsFormatted(format string, out io.Writer, containers []containers.Container) error {
var err error
switch strings.ToLower(format) {
case formatter2.PRETTY, "":
err = formatter2.PrintPrettySection(out, func(w io.Writer) {
for _, c := range containers {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", c.ID, c.Image, c.Command, c.Status,
strings.Join(formatter.PortsToStrings(c.Ports, fqdn(c)), ", "))
}
}, "CONTAINER ID", "IMAGE", "COMMAND", "STATUS", "PORTS")
case formatter2.JSON:
out, err := formatter2.ToStandardJSON(containers)
if err != nil {
return err
return formatter2.Print(containerList, opts.format, os.Stdout, func(w io.Writer) {
for _, c := range containerList {
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", c.ID, c.Image, c.Command, c.Status,
strings.Join(formatter.PortsToStrings(c.Ports, fqdn(c)), ", "))
}
fmt.Println(out)
default:
err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format)
}
return err
}, "CONTAINER ID", "IMAGE", "COMMAND", "STATUS", "PORTS")
}
func fqdn(container containers.Container) string {

View File

@ -20,14 +20,11 @@ import (
"fmt"
"io"
"os"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/secrets"
"github.com/docker/compose-cli/errdefs"
"github.com/docker/compose-cli/formatter"
)
@ -122,11 +119,15 @@ func listSecrets() *cobra.Command {
if err != nil {
return err
}
list, err := c.SecretsService().ListSecrets(cmd.Context())
secretsList, err := c.SecretsService().ListSecrets(cmd.Context())
if err != nil {
return err
}
return printSecretList(opts.format, os.Stdout, list)
return formatter.Print(secretsList, opts.format, os.Stdout, func(w io.Writer) {
for _, secret := range secretsList {
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description)
}
}, "ID", "NAME", "DESCRIPTION")
},
}
cmd.Flags().StringVar(&opts.format, "format", "", "Format the output. Values: [pretty | json]. (Default: pretty)")
@ -155,24 +156,3 @@ func deleteSecret() *cobra.Command {
cmd.Flags().BoolVar(&opts.recover, "recover", false, "Enable recovery.")
return cmd
}
func printSecretList(format string, out io.Writer, secrets []secrets.Secret) error {
var err error
switch strings.ToLower(format) {
case formatter.PRETTY, "":
err = formatter.PrintPrettySection(out, func(w io.Writer) {
for _, secret := range secrets {
fmt.Fprintf(w, "%s\t%s\t%s\n", secret.ID, secret.Name, secret.Description) // nolint:errcheck
}
}, "ID", "NAME", "DESCRIPTION")
case formatter.JSON:
outJSON, err := formatter.ToStandardJSON(secrets)
if err != nil {
return err
}
_, _ = fmt.Fprint(out, outJSON)
default:
err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format)
}
return err
}

View File

@ -1,45 +0,0 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"bytes"
"testing"
"gotest.tools/assert"
"gotest.tools/v3/golden"
"github.com/docker/compose-cli/api/secrets"
"github.com/docker/compose-cli/formatter"
)
func TestPrintList(t *testing.T) {
secretList := []secrets.Secret{
{
ID: "123",
Name: "secret123",
Description: "secret 1,2,3",
},
}
out := &bytes.Buffer{}
assert.NilError(t, printSecretList(formatter.PRETTY, out, secretList))
golden.Assert(t, out.String(), "secrets-out.golden")
out.Reset()
assert.NilError(t, printSecretList(formatter.JSON, out, secretList))
golden.Assert(t, out.String(), "secrets-out-json.golden")
}

View File

@ -52,14 +52,14 @@ func VersionCommand(version string) *cobra.Command {
func runVersion(cmd *cobra.Command, version string) {
var versionString string
format := strings.TrimSpace(cmd.Flag(formatOpt).Value.String())
format := strings.ToLower(strings.ReplaceAll(cmd.Flag(formatOpt).Value.String(), " ", ""))
displayedVersion := strings.TrimPrefix(version, "v")
// Replace is preferred in this case to keep the order.
switch format {
case formatter.PRETTY, "":
versionString = strings.Replace(getOutFromMoby(cmd, fixedPrettyArgs(os.Args[1:])...),
"\n Version:", "\n Cloud integration: "+displayedVersion+"\n Version:", 1)
case formatter.JSON, "{{json .}}", "{{json . }}", "{{ json .}}", "{{ json . }}": // Try to catch full JSON formats
case formatter.JSON, "{{json.}}": // Try to catch full JSON formats
versionString = strings.Replace(getOutFromMoby(cmd, fixedJSONArgs(os.Args[1:])...),
`"Version":`, fmt.Sprintf(`"CloudIntegration":%q,"Version":`, displayedVersion), 1)
}
@ -78,7 +78,7 @@ func getOutFromMoby(cmd *cobra.Command, args ...string) string {
}
func fixedPrettyArgs(oArgs []string) []string {
var args []string
args := make([]string, 0)
for i := 0; i < len(oArgs); i++ {
if isFormatOpt(oArgs[i]) &&
len(oArgs) > i &&
@ -92,7 +92,7 @@ func fixedPrettyArgs(oArgs []string) []string {
}
func fixedJSONArgs(oArgs []string) []string {
var args []string
args := make([]string, 0)
for i := 0; i < len(oArgs); i++ {
if isFormatOpt(oArgs[i]) &&
len(oArgs) > i &&

View File

@ -20,14 +20,10 @@ import (
"fmt"
"io"
"os"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/docker/compose-cli/api/client"
"github.com/docker/compose-cli/api/volumes"
"github.com/docker/compose-cli/errdefs"
"github.com/docker/compose-cli/formatter"
)
@ -50,30 +46,13 @@ func listVolume() *cobra.Command {
if err != nil {
return err
}
return printList(opts.format, os.Stdout, vols)
return formatter.Print(vols, opts.format, os.Stdout, func(w io.Writer) {
for _, vol := range vols {
_, _ = fmt.Fprintf(w, "%s\t%s\n", vol.ID, vol.Description)
}
}, "ID", "DESCRIPTION")
},
}
cmd.Flags().StringVar(&opts.format, "format", formatter.PRETTY, "Format the output. Values: [pretty | json]. (Default: pretty)")
return cmd
}
func printList(format string, out io.Writer, volumes []volumes.Volume) error {
var err error
switch strings.ToLower(format) {
case formatter.PRETTY, "":
_ = formatter.PrintPrettySection(out, func(w io.Writer) {
for _, vol := range volumes {
_, _ = fmt.Fprintf(w, "%s\t%s\n", vol.ID, vol.Description)
}
}, "ID", "DESCRIPTION")
case formatter.JSON:
outJSON, err := formatter.ToStandardJSON(volumes)
if err != nil {
return err
}
_, _ = fmt.Fprint(out, outJSON)
default:
err = errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format)
}
return err
}

View File

@ -1,45 +0,0 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package volume
import (
"bytes"
"testing"
"gotest.tools/assert"
"gotest.tools/v3/golden"
"github.com/docker/compose-cli/api/volumes"
"github.com/docker/compose-cli/formatter"
)
func TestPrintList(t *testing.T) {
secrets := []volumes.Volume{
{
ID: "volume/123",
Description: "volume 123",
},
}
out := &bytes.Buffer{}
assert.NilError(t, printList(formatter.PRETTY, out, secrets))
golden.Assert(t, out.String(), "volumes-out.golden")
out.Reset()
assert.NilError(t, printList(formatter.JSON, out, secrets))
golden.Assert(t, out.String(), "volumes-out-json.golden")
}

View File

@ -1,6 +0,0 @@
[
{
"ID": "volume/123",
"Description": "volume 123"
}
]

View File

@ -1,2 +0,0 @@
ID DESCRIPTION
volume/123 volume 123

44
formatter/formatter.go Normal file
View File

@ -0,0 +1,44 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package formatter
import (
"fmt"
"io"
"strings"
"github.com/pkg/errors"
"github.com/docker/compose-cli/errdefs"
)
// Print prints formatted lists in different formats
func Print(list interface{}, format string, outWriter io.Writer, writerFn func(w io.Writer), headers ...string) error {
switch strings.ToLower(format) {
case PRETTY, "":
return PrintPrettySection(outWriter, writerFn, headers...)
case JSON:
outJSON, err := ToStandardJSON(list)
if err != nil {
return err
}
_, _ = fmt.Fprint(outWriter, outJSON)
default:
return errors.Wrapf(errdefs.ErrParsingFailed, "format value %q could not be parsed", format)
}
return nil
}

View File

@ -0,0 +1,62 @@
/*
Copyright 2020 Docker Compose CLI authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package formatter
import (
"fmt"
"io"
"testing"
"go.uber.org/zap/buffer"
"gotest.tools/assert"
)
type testStruct struct {
Name string
Status string
}
// Print prints formatted lists in different formats
func TestPrint(t *testing.T) {
testList := []testStruct{
{
Name: "myName",
Status: "myStatus",
},
}
b := &buffer.Buffer{}
assert.NilError(t, Print(testList, PRETTY, b, func(w io.Writer) {
for _, t := range testList {
_, _ = fmt.Fprintf(w, "%s\t%s\n", t.Name, t.Status)
}
}, "NAME", "STATUS"))
assert.Equal(t, b.String(), "NAME STATUS\nmyName myStatus\n")
b.Reset()
assert.NilError(t, Print(testList, JSON, b, func(w io.Writer) {
for _, t := range testList {
_, _ = fmt.Fprintf(w, "%s\t%s\n", t.Name, t.Status)
}
}, "NAME", "STATUS"))
assert.Equal(t, b.String(), `[
{
"Name": "myName",
"Status": "myStatus"
}
]`)
}

View File

@ -16,12 +16,18 @@
package formatter
import "encoding/json"
import (
"encoding/json"
"reflect"
)
const standardIndentation = " "
// ToStandardJSON return a string with the JSON representation of the interface{}
func ToStandardJSON(i interface{}) (string, error) {
if reflect.ValueOf(i).IsNil() {
return "{}", nil
}
b, err := json.MarshalIndent(i, "", standardIndentation)
if err != nil {
return "", err

1
go.mod
View File

@ -54,6 +54,7 @@ require (
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.6.1
go.uber.org/zap v1.10.0
golang.org/x/mod v0.3.0
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43

1
go.sum
View File

@ -480,6 +480,7 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

View File

@ -13,4 +13,4 @@
"kubernetes": {}
}
}
]
]

View File

@ -27,4 +27,4 @@
"Platform": "",
"RestartPolicyCondition": ""
}
]
]