From 964e24697d8d4f1267cca0b80ccb624d560afcb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Barroso?= Date: Mon, 27 Jan 2025 12:29:06 +0100 Subject: [PATCH] feat: add display decorator (#11) * add display decorator * rename decorator name * add header * update type * add tests and uses cobra stdout and err * add header * update description --- decorators.go | 32 ++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ output.go | 59 ++++++++++++++++++++++++++++++++++++++++ output_test.go | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 167 insertions(+) create mode 100644 output.go create mode 100644 output_test.go diff --git a/decorators.go b/decorators.go index 8b24392..7768980 100644 --- a/decorators.go +++ b/decorators.go @@ -30,6 +30,8 @@ import ( var DefaultDecorators = []Decorator{ CommandWithLoggerDecorator{}, + CommandWithOutputDecorator{}, + CommandWithAliasesDecorator{}, CommandWithFlagsDecorator{}, @@ -81,6 +83,36 @@ func (d CommandWithLoggerDecorator) Decorate(_ *Ecdysis, _ *cobra.Command, c Com return nil } +// -- OUTPUT ------------------------------------------------------------------- + +// CommandWithOutput can be implemented by a command to provide its own stdout and stderr. +type CommandWithOutput interface { + Command + Output(output Output) +} + +// CommandWithOutputDecorator is a decorator that provides a Stdout to the command. +// If the Stdout field is not set, the default stdout will be provided. +type CommandWithOutputDecorator struct { + Output Output +} + +// Decorate provides the logger to the command. +func (d CommandWithOutputDecorator) Decorate(_ *Ecdysis, cmd *cobra.Command, c Command) error { + v, ok := c.(CommandWithOutput) + if !ok { + return nil + } + + if d.Output == nil { + v.Output(NewDefaultOutput(cmd)) + } else { + v.Output(d.Output) + } + + return nil +} + // -- ALIASES ------------------------------------------------------------------ // CommandWithAliases can be implemented by a command to provide aliases. diff --git a/go.mod b/go.mod index f3bd266..d2c7e9f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.0 require ( github.com/google/go-cmp v0.6.0 + github.com/matryer/is v1.4.1 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 diff --git a/go.sum b/go.sum index 1b62cd6..82baa5f 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= +github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= diff --git a/output.go b/output.go new file mode 100644 index 0000000..502d0ca --- /dev/null +++ b/output.go @@ -0,0 +1,59 @@ +// Copyright © 2025 Meroxa, Inc. +// +// 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 ecdysis + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" +) + +type Output interface { + Stdout(any) + Stderr(any) +} + +type DefaultOutput struct { + stdout io.Writer + stderr io.Writer +} + +func NewDefaultOutput(cmd *cobra.Command) *DefaultOutput { + return &DefaultOutput{ + stdout: cmd.OutOrStdout(), + stderr: cmd.OutOrStderr(), + } +} + +// Stdout writes a message to the configured standard output. +func (d *DefaultOutput) Stdout(msg any) { + fmt.Fprint(d.stdout, msg) +} + +// Stderr writes a message to the configured standard error. +func (d *DefaultOutput) Stderr(msg any) { + fmt.Fprint(d.stderr, msg) +} + +// Output allows overwriting the stdout and/or stderr for specific use cases (like testing). +func (d *DefaultOutput) Output(stdout, stderr io.Writer) { + if stdout != nil { + d.stdout = stdout + } + if stderr != nil { + d.stderr = stderr + } +} diff --git a/output_test.go b/output_test.go new file mode 100644 index 0000000..628c6e7 --- /dev/null +++ b/output_test.go @@ -0,0 +1,73 @@ +// Copyright © 2025 Meroxa, Inc. +// +// 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 ecdysis + +import ( + "bytes" + "testing" + + "github.com/matryer/is" + "github.com/spf13/cobra" +) + +func TestNewDefaultOutput(t *testing.T) { + is := is.New(t) + cmd := &cobra.Command{} + out := NewDefaultOutput(cmd) + + is.Equal(out.stdout, cmd.OutOrStdout()) + is.Equal(out.stderr, cmd.OutOrStderr()) +} + +func TestStdout(t *testing.T) { + is := is.New(t) + + var buf bytes.Buffer + out := &DefaultOutput{stdout: &buf} + + message := "hello stdout" + out.Stdout(message) + + is.Equal(buf.String(), message) +} + +func TestStderr(t *testing.T) { + is := is.New(t) + + var buf bytes.Buffer + out := &DefaultOutput{stderr: &buf} + + message := "hello stderr" + out.Stderr(message) + + is.Equal(buf.String(), message) +} + +func TestOutput(t *testing.T) { + is := is.New(t) + + var stdoutBuf, stderrBuf bytes.Buffer + out := &DefaultOutput{} + + out.Output(&stdoutBuf, &stderrBuf) + + stdoutMsg := "stdout test" + stderrMsg := "stderr test" + out.Stdout(stdoutMsg) + out.Stderr(stderrMsg) + + is.Equal(stdoutMsg, stdoutBuf.String()) + is.Equal(stderrMsg, stderrBuf.String()) +}