...

Source file src/github.com/redhat-developer/odo/pkg/log/status.go

Documentation: github.com/redhat-developer/odo/pkg/log

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  /*
    18  	This package is a FORK of https://github.com/kubernetes-sigs/kind/blob/master/pkg/log/status.go
    19  	See above license
    20  */
    21  
    22  // Package log contains logging related functionality
    23  package log
    24  
    25  import (
    26  	"fmt"
    27  	"io"
    28  	"os"
    29  	"runtime"
    30  	"strings"
    31  	"sync"
    32  
    33  	"github.com/fatih/color"
    34  	"github.com/mattn/go-colorable"
    35  	"github.com/spf13/pflag"
    36  	"golang.org/x/term"
    37  
    38  	"github.com/redhat-developer/odo/pkg/log/fidget"
    39  	"github.com/redhat-developer/odo/pkg/version"
    40  )
    41  
    42  // Spacing for logging
    43  const suffixSpacing = "  "
    44  const prefixSpacing = " "
    45  
    46  var mu sync.Mutex
    47  var colors = []color.Attribute{color.FgRed, color.FgGreen, color.FgYellow, color.FgBlue, color.FgMagenta, color.FgCyan, color.FgWhite}
    48  var colorCounter = 0
    49  
    50  // Status is used to track ongoing status in a CLI, with a nice loading spinner
    51  // when attached to a terminal
    52  type Status struct {
    53  	spinner       *fidget.Spinner
    54  	status        string
    55  	warningStatus string
    56  	writer        io.Writer
    57  }
    58  
    59  // NewStatus creates a new default Status
    60  func NewStatus(w io.Writer) *Status {
    61  	spin := fidget.NewSpinner(w)
    62  	s := &Status{
    63  		spinner: spin,
    64  		writer:  w,
    65  	}
    66  	return s
    67  }
    68  
    69  // IsTerminal returns true if the writer w is a terminal
    70  // This function is modified if we are running within Windows..
    71  // as Golang's built-in "IsTerminal" command only works on UNIX-based systems:
    72  // https://github.com/golang/crypto/blob/master/ssh/terminal/util.go#L5
    73  func IsTerminal(w io.Writer) bool {
    74  	if runtime.GOOS == "windows" {
    75  		return true
    76  	} else if v, ok := (w).(*os.File); ok {
    77  		return term.IsTerminal(int(v.Fd()))
    78  	}
    79  	return false
    80  }
    81  
    82  // WarningStatus puts a warning status within the spinner and then updates the current status
    83  func (s *Status) WarningStatus(status string) {
    84  	s.warningStatus = status
    85  	s.updateStatus()
    86  }
    87  
    88  // Updates the status and makes sure that if the previous status was longer, it
    89  // "clears" the rest of the message.
    90  func (s *Status) updateStatus() {
    91  	mu.Lock()
    92  	if s.warningStatus != "" {
    93  		yellow := color.New(color.FgYellow).SprintFunc()
    94  
    95  		// Determine the warning size, so that we can calculate its length and use that length as padding parameter
    96  		warningSubstring := fmt.Sprintf(" [%s %s]", yellow(getWarningString()), yellow(s.warningStatus))
    97  
    98  		// Combine suffix and spacing, then resize them
    99  		newSuffix := fmt.Sprintf(suffixSpacing+"%s", s.status)
   100  		newSuffix = truncateSuffixIfNeeded(newSuffix, s.writer, len(warningSubstring))
   101  
   102  		// Combine the warning and non-warning text (since we don't want to truncate the warning text)
   103  		s.spinner.SetSuffix(fmt.Sprintf("%s%s", newSuffix, warningSubstring))
   104  	} else {
   105  		newSuffix := fmt.Sprintf(suffixSpacing+"%s", s.status)
   106  		s.spinner.SetSuffix(truncateSuffixIfNeeded(newSuffix, s.writer, 0))
   107  	}
   108  	mu.Unlock()
   109  }
   110  
   111  // Start starts a new phase of the status, if attached to a terminal
   112  // there will be a loading spinner with this status
   113  func (s *Status) Start(status string, debug bool) {
   114  	s.End(true)
   115  
   116  	// set new status
   117  	isTerm := IsTerminal(s.writer)
   118  	s.status = status
   119  
   120  	// If we are in debug mode, don't spin!
   121  	// In under no circumstances do we output if we're using -o json.. to
   122  	// to avoid parsing errors.
   123  	if !IsJSON() {
   124  		if !isTerm || debug {
   125  			fmt.Fprintf(s.writer, prefixSpacing+getSpacingString()+suffixSpacing+"%s  ...\n", s.status)
   126  		} else {
   127  			s.spinner.SetPrefix(prefixSpacing)
   128  			newSuffix := fmt.Sprintf(suffixSpacing+"%s", s.status)
   129  			s.spinner.SetSuffix(truncateSuffixIfNeeded(newSuffix, s.writer, 0))
   130  			s.spinner.Start()
   131  		}
   132  	}
   133  }
   134  
   135  // truncateSuffixIfNeeded returns a representation of the 'suffix' parameter that fits within the terminal
   136  // (including the extra space occupied by the padding parameter).
   137  func truncateSuffixIfNeeded(suffix string, w io.Writer, padding int) string {
   138  
   139  	terminalWidth := getTerminalWidth(w)
   140  	if terminalWidth == nil {
   141  		return suffix
   142  	}
   143  
   144  	// Additional padding to account for animation widget on lefthand side, and to avoid getting too close to the righthand terminal edge
   145  	const additionalPadding = 10
   146  
   147  	maxWidth := *terminalWidth - padding - additionalPadding
   148  
   149  	// For VERY small terminals, or very large padding, just return the suffix unmodified
   150  	if maxWidth <= 20 {
   151  		return suffix
   152  	}
   153  
   154  	// If we are compliant, return the unmodified suffix...
   155  	if len(suffix) <= maxWidth {
   156  		return suffix
   157  	}
   158  
   159  	// Otherwise truncate down to the desired length and append '...'
   160  	abbrevSuffix := "..."
   161  	maxWidth -= len(abbrevSuffix) // maxWidth is necessarily >20 at this point
   162  
   163  	// len(suffix) is necessarily >= maxWidth at this point
   164  	suffix = suffix[:maxWidth] + abbrevSuffix
   165  
   166  	return suffix
   167  }
   168  
   169  func getTerminalWidth(w io.Writer) *int {
   170  
   171  	if runtime.GOOS != "windows" {
   172  
   173  		if v, ok := (w).(*os.File); ok {
   174  			w, _, err := term.GetSize(int(v.Fd()))
   175  			if err == nil {
   176  				return &w
   177  			}
   178  		}
   179  
   180  	}
   181  
   182  	return nil
   183  }
   184  
   185  // End completes the current status, ending any previous spinning and
   186  // marking the status as success or failure
   187  func (s *Status) End(success bool) {
   188  	if s.status == "" {
   189  		return
   190  	}
   191  
   192  	isTerm := IsTerminal(s.writer)
   193  	if isTerm {
   194  		s.spinner.Stop()
   195  		if !IsJSON() {
   196  			fmt.Fprint(s.writer, "\r")
   197  		}
   198  	}
   199  
   200  	if !IsJSON() {
   201  
   202  		time := ""
   203  		if s.spinner.TimeSpent() != "" {
   204  			time = fmt.Sprintf("[%s]", s.spinner.TimeSpent())
   205  		}
   206  
   207  		if success {
   208  			// Clear the warning (unneeded now)
   209  			s.WarningStatus("")
   210  			green := color.New(color.FgGreen).SprintFunc()
   211  			fmt.Fprintf(s.writer, prefixSpacing+"%s"+suffixSpacing+"%s %s\n", green(getSuccessString()), s.status, time)
   212  		} else {
   213  			red := color.New(color.FgRed).SprintFunc()
   214  			if s.warningStatus != "" {
   215  				fmt.Fprintf(s.writer, prefixSpacing+"%s"+suffixSpacing+"%s %s [%s]\n", red(getErrString()), s.status, time, s.warningStatus)
   216  			} else {
   217  				fmt.Fprintf(s.writer, prefixSpacing+"%s"+suffixSpacing+"%s %s\n", red(getErrString()), s.status, time)
   218  			}
   219  		}
   220  	}
   221  
   222  	s.status = ""
   223  }
   224  
   225  // EndWithStatus is similar to End, but lets the user specify a custom message/status while ending
   226  func (s *Status) EndWithStatus(status string, success bool) {
   227  	if status == "" {
   228  		return
   229  	}
   230  	s.status = status
   231  	s.End(success)
   232  }
   233  
   234  // Printf will output in an appropriate "information" manner; for e.g.
   235  // • <message>
   236  func Printf(format string, a ...interface{}) {
   237  	if !IsJSON() {
   238  		fmt.Fprintf(GetStdout(), "%s%s%s%s\n", prefixSpacing, getSpacingString(), suffixSpacing, fmt.Sprintf(format, a...))
   239  	}
   240  }
   241  
   242  // Fprintf will output in an appropriate "information" manner; for e.g.
   243  // • <message>
   244  func Fprintf(w io.Writer, format string, a ...interface{}) {
   245  	if !IsJSON() {
   246  		fmt.Fprintf(w, "%s%s%s%s\n", prefixSpacing, getSpacingString(), suffixSpacing, fmt.Sprintf(format, a...))
   247  	}
   248  }
   249  
   250  // Println will output a new line when applicable
   251  func Println() {
   252  	if !IsJSON() {
   253  		fmt.Fprintln(GetStdout())
   254  	}
   255  }
   256  
   257  // Fprintln will output a new line when applicable
   258  func Fprintln(w io.Writer) {
   259  	if !IsJSON() {
   260  		fmt.Fprintln(w)
   261  	}
   262  }
   263  
   264  // Success will output in an appropriate "success" manner
   265  // ✓  <message>
   266  func Success(a ...interface{}) {
   267  	if !IsJSON() {
   268  		green := color.New(color.FgGreen).SprintFunc()
   269  		fmt.Fprintf(GetStdout(), "%s%s%s%s", prefixSpacing, green(getSuccessString()), suffixSpacing, fmt.Sprintln(a...))
   270  	}
   271  }
   272  
   273  // Successf will output in an appropriate "progress" manner
   274  //
   275  //	✓  <message>
   276  func Successf(format string, a ...interface{}) {
   277  	if !IsJSON() {
   278  		green := color.New(color.FgGreen).SprintFunc()
   279  		fmt.Fprintf(GetStdout(), "%s%s%s%s\n", prefixSpacing, green(getSuccessString()), suffixSpacing, fmt.Sprintf(format, a...))
   280  	}
   281  }
   282  
   283  // Warning will output in an appropriate "progress" manner
   284  //
   285  //	⚠ <message>
   286  func Warning(a ...interface{}) {
   287  	Fwarning(GetStderr(), a...)
   288  }
   289  
   290  // Fwarning will output in an appropriate "progress" manner in out writer
   291  //
   292  //	⚠ <message>
   293  func Fwarning(out io.Writer, a ...interface{}) {
   294  	if !IsJSON() {
   295  		Fwarningf(out, "%s", a...)
   296  	}
   297  }
   298  
   299  // Warningf will output in an appropriate "warning" manner
   300  //
   301  //	⚠ <message>
   302  func Warningf(format string, a ...interface{}) {
   303  	Fwarningf(GetStderr(), format, a...)
   304  }
   305  
   306  // Fwarningf will output in an appropriate "warning" manner
   307  //
   308  //	⚠ <message>
   309  func Fwarningf(w io.Writer, format string, a ...interface{}) {
   310  	if !IsJSON() {
   311  		yellow := color.New(color.FgYellow).SprintFunc()
   312  		fullMessage := fmt.Sprintf("%s%s%s", getWarningString(), suffixSpacing, fmt.Sprintf(format, a...))
   313  		fmt.Fprintln(w, yellow(wrapWarningMessage(fullMessage)))
   314  	}
   315  }
   316  
   317  func wrapWarningMessage(fullMessage string) string {
   318  	if fullMessage == "" || strings.TrimSpace(fullMessage) == "" {
   319  		return fullMessage
   320  	}
   321  	split := strings.Split(fullMessage, "\n")
   322  	max := 0
   323  	for _, s := range split {
   324  		if len(s) > max {
   325  			max = len(s)
   326  		}
   327  	}
   328  	h := strings.Repeat("=", max)
   329  	return fmt.Sprintf(`%[1]s
   330  %[2]s
   331  %[1]s`, h, fullMessage, h)
   332  }
   333  
   334  // Fsuccess will output in an appropriate "progress" manner in out writer
   335  //
   336  //	✓ <message>
   337  func Fsuccess(out io.Writer, a ...interface{}) {
   338  	if !IsJSON() {
   339  		green := color.New(color.FgGreen).SprintFunc()
   340  		fmt.Fprintf(out, "%s%s%s%s", prefixSpacing, green(getSuccessString()), suffixSpacing, fmt.Sprintln(a...))
   341  	}
   342  }
   343  
   344  // DisplayExperimentalWarning displays the experimental mode warning message.
   345  func DisplayExperimentalWarning() {
   346  	if !IsJSON() {
   347  		msg := `Experimental mode enabled. Use at your own risk.
   348  More details on https://odo.dev/docs/user-guides/advanced/experimental-mode`
   349  		Fwarningf(GetStdout(), msg)
   350  	}
   351  }
   352  
   353  // Title Prints the logo as well as the first line being BLUE (indicator of the command information);
   354  // the second line is optional and provides information in regard to what is being run.
   355  // The last line displays information about the current odo version.
   356  //
   357  //	 __
   358  //	/  \__     **First line**
   359  //	\__/  \    Second line
   360  //	/  \__/    odo version: <VERSION>
   361  //	\__/
   362  func Title(firstLine, secondLine string) {
   363  	if !IsJSON() {
   364  		fmt.Fprint(GetStdout(), Stitle(firstLine, secondLine))
   365  	}
   366  }
   367  
   368  // Stitle is the same as Title but returns the string instead
   369  func Stitle(firstLine, secondLine string) string {
   370  	var versionMsg string
   371  	if version.VERSION != "" {
   372  		versionMsg = "odo version: " + version.VERSION
   373  	}
   374  	if version.GITCOMMIT != "" {
   375  		versionMsg += " (" + version.GITCOMMIT + ")"
   376  	}
   377  	return StitleWithVersion(firstLine, secondLine, versionMsg)
   378  }
   379  
   380  // StitleWithVersion is the same as Stitle, but it allows to customize the version message line
   381  func StitleWithVersion(firstLine, secondLine, versionLine string) string {
   382  	blue := color.New(color.FgBlue).SprintFunc()
   383  	return fmt.Sprintf(`  __
   384   /  \__     %s
   385   \__/  \    %s
   386   /  \__/    %s
   387   \__/%s`, blue(firstLine), secondLine, versionLine, "\n")
   388  }
   389  
   390  // Sectionf outputs a title in BLUE and underlined for separating a section (such as building a container, deploying files, etc.)
   391  // T͟h͟i͟s͟ ͟i͟s͟ ͟u͟n͟d͟e͟r͟l͟i͟n͟e͟d͟ ͟b͟l͟u͟e͟ ͟t͟e͟x͟t͟
   392  func Sectionf(format string, a ...interface{}) {
   393  	if !IsJSON() {
   394  		blue := color.New(color.FgBlue).Add(color.Underline).SprintFunc()
   395  		if runtime.GOOS == "windows" {
   396  			fmt.Fprintf(GetStdout(), "\n- %s\n", blue(fmt.Sprintf(format, a...)))
   397  		} else {
   398  			fmt.Fprintf(GetStdout(), "\n↪ %s\n", blue(fmt.Sprintf(format, a...)))
   399  		}
   400  	}
   401  }
   402  
   403  // Section outputs a title in BLUE and underlined for separating a section (such as building a container, deploying files, etc.)
   404  // T͟h͟i͟s͟ ͟i͟s͟ ͟u͟n͟d͟e͟r͟l͟i͟n͟e͟d͟ ͟b͟l͟u͟e͟ ͟t͟e͟x͟t͟
   405  func Section(a ...interface{}) {
   406  	if !IsJSON() {
   407  		blue := color.New(color.FgBlue).Add(color.Underline).SprintFunc()
   408  		if runtime.GOOS == "windows" {
   409  			fmt.Fprintf(GetStdout(), "\n- %s", blue(fmt.Sprintln(a...)))
   410  		} else {
   411  			fmt.Fprintf(GetStdout(), "\n↪ %s", blue(fmt.Sprintln(a...)))
   412  		}
   413  	}
   414  }
   415  
   416  // Deprecate will output a warning symbol and then "Deprecated" at the end of the output in YELLOW
   417  //
   418  //	⚠ <message all yellow>
   419  func Deprecate(what, nextAction string) {
   420  	if !IsJSON() {
   421  		yellow := color.New(color.FgYellow).SprintFunc()
   422  		msg1 := fmt.Sprintf("%s%s%s%s%s", yellow(getWarningString()), suffixSpacing, yellow(fmt.Sprintf("%s Deprecated", what)), suffixSpacing, nextAction)
   423  		fmt.Fprintf(GetStderr(), " %s\n", msg1)
   424  	}
   425  }
   426  
   427  // Errorf will output in an appropriate "progress" manner
   428  // ✗ <message>
   429  func Errorf(format string, a ...interface{}) {
   430  	if !IsJSON() {
   431  		red := color.New(color.FgRed).SprintFunc()
   432  		fmt.Fprintf(GetStderr(), " %s%s%s\n", red(getErrString()), suffixSpacing, fmt.Sprintf(format, a...))
   433  	}
   434  }
   435  
   436  // Ferrorf will output in an appropriate "progress" manner
   437  // ✗ <message>
   438  func Ferrorf(w io.Writer, format string, a ...interface{}) {
   439  	if !IsJSON() {
   440  		red := color.New(color.FgRed).SprintFunc()
   441  		fmt.Fprintf(w, " %s%s%s\n", red(getErrString()), suffixSpacing, fmt.Sprintf(format, a...))
   442  	}
   443  }
   444  
   445  // Error will output in an appropriate "progress" manner
   446  // ✗ <message>
   447  func Error(a ...interface{}) {
   448  	if !IsJSON() {
   449  		red := color.New(color.FgRed).SprintFunc()
   450  		fmt.Fprintf(GetStderr(), "%s%s%s%s", prefixSpacing, red(getErrString()), suffixSpacing, fmt.Sprintln(a...))
   451  	}
   452  }
   453  
   454  // Frror will output in an appropriate "progress" manner
   455  // ✗ <message>
   456  func Ferror(w io.Writer, a ...interface{}) {
   457  	if !IsJSON() {
   458  		red := color.New(color.FgRed).SprintFunc()
   459  		fmt.Fprintf(w, "%s%s%s%s", prefixSpacing, red(getErrString()), suffixSpacing, fmt.Sprintln(a...))
   460  	}
   461  }
   462  
   463  // Info will simply print out information on a new (bolded) line
   464  // this is intended as information *after* something has been deployed
   465  // **Line in bold**
   466  func Info(a ...interface{}) {
   467  	if !IsJSON() {
   468  		bold := color.New(color.Bold).SprintFunc()
   469  		fmt.Fprintf(GetStdout(), "%s", bold(fmt.Sprintln(a...)))
   470  	}
   471  }
   472  
   473  // Infof will simply print out information on a new (bolded) line
   474  // this is intended as information *after* something has been deployed
   475  // **Line in bold**
   476  func Infof(format string, a ...interface{}) {
   477  	if !IsJSON() {
   478  		bold := color.New(color.Bold).SprintFunc()
   479  		fmt.Fprintf(GetStdout(), "%s\n", bold(fmt.Sprintf(format, a...)))
   480  	}
   481  }
   482  
   483  // Finfof will simply print out information on a new (bolded) line
   484  // this is intended as information *after* something has been deployed
   485  // This will also use a WRITER input
   486  // We will have to manually check to see if it's Windows platform or not to
   487  // determine if we are allowed to bold the output or not.
   488  // **Line in bold**
   489  func Finfof(w io.Writer, format string, a ...interface{}) {
   490  	if !IsJSON() {
   491  		bold := color.New(color.Bold).SprintFunc()
   492  
   493  		if runtime.GOOS == "windows" {
   494  			fmt.Fprintf(w, "%s\n", fmt.Sprintf(format, a...))
   495  		} else {
   496  			fmt.Fprintf(w, "%s\n", bold(fmt.Sprintf(format, a...)))
   497  		}
   498  
   499  	}
   500  }
   501  
   502  // Sbold will return a bold string
   503  func Sbold(s string) string {
   504  	bold := color.New(color.Bold).SprintFunc()
   505  	return bold(fmt.Sprint(s))
   506  }
   507  
   508  // Bold will print out a bolded string
   509  func Bold(s string) {
   510  	if !IsJSON() {
   511  		bold := color.New(color.Bold).SprintFunc()
   512  		fmt.Fprintf(GetStdout(), "%s\n", bold(fmt.Sprintln(s)))
   513  	}
   514  }
   515  
   516  // BoldColor will print out a bolded string with a color (that's passed in)
   517  func SboldColor(c color.Attribute, s string) string {
   518  	chosenColor := color.New(c).SprintFunc()
   519  	return chosenColor(fmt.Sprintln(Sbold(s)))
   520  }
   521  
   522  // Describef will print out the first variable as BOLD and then the second not..
   523  // this is intended to be used with `odo describe` and other outputs that list
   524  // a lot of information
   525  func Describef(title string, format string, a ...interface{}) {
   526  	if !IsJSON() {
   527  		bold := color.New(color.Bold).SprintFunc()
   528  		fmt.Fprintf(GetStdout(), "%s%s\n", bold(title), fmt.Sprintf(format, a...))
   529  	}
   530  }
   531  
   532  // Spinner creates a spinner, sets the prefix then returns it.
   533  // Remember to use .End(bool) to stop the spin / when you're done.
   534  // For example: defer s.End(false)
   535  func Spinner(status string) *Status {
   536  	return ExplicitSpinner(status, false)
   537  }
   538  
   539  // Spinnerf creates a spinner, sets the prefix then returns it.
   540  // Remember to use .End(bool) to stop the spin / when you're done.
   541  // For example: defer s.End(false)
   542  // for situations where spinning isn't viable (debug)
   543  func Spinnerf(format string, a ...interface{}) *Status {
   544  	s := NewStatus(GetStdout())
   545  	s.Start(fmt.Sprintf(format, a...), IsDebug())
   546  	return s
   547  }
   548  
   549  // Fspinnerf creates a spinner, sets the prefix then returns it.
   550  // Remember to use .End(bool) to stop the spin / when you're done.
   551  // For example: defer s.End(false)
   552  // for situations where spinning isn't viable (debug)
   553  func Fspinnerf(w io.Writer, format string, a ...interface{}) *Status {
   554  	s := NewStatus(w)
   555  	s.Start(fmt.Sprintf(format, a...), IsDebug())
   556  	return s
   557  }
   558  
   559  // SpinnerNoSpin is the same as the "Spinner" function but forces no spinning
   560  func SpinnerNoSpin(status string) *Status {
   561  	return ExplicitSpinner(status, true)
   562  }
   563  
   564  // ExplicitSpinner creates a spinner that can or not spin based on the value of the preventSpinning parameter
   565  func ExplicitSpinner(status string, preventSpinning bool) *Status {
   566  	doNotSpin := true
   567  	if !preventSpinning {
   568  		doNotSpin = IsDebug()
   569  	}
   570  	s := NewStatus(GetStdout())
   571  	s.Start(status, doNotSpin)
   572  	return s
   573  }
   574  
   575  // IsJSON returns true if we are in machine output mode..
   576  // under NO circumstances should we output any logging.. as we are only outputting json
   577  func IsJSON() bool {
   578  
   579  	flag := pflag.Lookup("o")
   580  	if flag != nil && flag.Changed {
   581  		return strings.Contains(pflag.Lookup("o").Value.String(), "json")
   582  	}
   583  
   584  	return false
   585  }
   586  
   587  // IsDebug returns true if we are debugging (-v is set to anything but 0)
   588  func IsDebug() bool {
   589  
   590  	flag := pflag.Lookup("v")
   591  
   592  	if flag != nil {
   593  		return !strings.Contains(pflag.Lookup("v").Value.String(), "0")
   594  	}
   595  
   596  	return false
   597  }
   598  
   599  // IsAppleSilicon returns true if we are on a Mac M1 / Apple Silicon natively
   600  func IsAppleSilicon() bool {
   601  	return runtime.GOOS == "darwin" && (strings.HasPrefix(runtime.GOARCH, "arm") || strings.HasPrefix(runtime.GOARCH, "arm64"))
   602  }
   603  
   604  // GetStdout gets the appropriate stdout from the OS. If it's Linux, it will use
   605  // the go-colorable library in order to fix any and all color ASCII issues.
   606  // TODO: Test needs to be added once we get Windows testing available on TravisCI / CI platform.
   607  func GetStdout() io.Writer {
   608  	if runtime.GOOS == "windows" {
   609  		return colorable.NewColorableStdout()
   610  	}
   611  	return os.Stdout
   612  }
   613  
   614  // GetStderr gets the appropriate stderrfrom the OS. If it's Linux, it will use
   615  // the go-colorable library in order to fix any and all color ASCII issues.
   616  // TODO: Test needs to be added once we get Windows testing available on TravisCI / CI platform.
   617  func GetStderr() io.Writer {
   618  	if runtime.GOOS == "windows" {
   619  		return colorable.NewColorableStderr()
   620  	}
   621  	return os.Stderr
   622  }
   623  
   624  // getErrString returns a certain string based upon the OS.
   625  // Some Windows terminals do not support unicode and must use ASCII.
   626  // TODO: Test needs to be added once we get Windows testing available on TravisCI / CI platform.
   627  func getErrString() string {
   628  	if runtime.GOOS == "windows" {
   629  		return "X"
   630  	}
   631  	return "✗"
   632  }
   633  
   634  // getWarningString returns a certain string based upon the OS.
   635  // Some Windows terminals do not support unicode and must use ASCII.
   636  // TODO: Test needs to be added once we get Windows testing available on TravisCI / CI platform.
   637  func getWarningString() string {
   638  	if runtime.GOOS == "windows" {
   639  		return "!"
   640  	}
   641  	return "⚠"
   642  }
   643  
   644  // getSuccessString returns a certain string based upon the OS.
   645  // Some Windows terminals do not support unicode and must use ASCII.
   646  // TODO: Test needs to be added once we get Windows testing available on TravisCI / CI platform.
   647  func getSuccessString() string {
   648  	if runtime.GOOS == "windows" {
   649  		return "V"
   650  	}
   651  	return "✓"
   652  }
   653  
   654  // getSpacingString returns a certain string based upon the OS.
   655  // Some Windows terminals do not support unicode and must use ASCII.
   656  // TODO: Test needs to be added once we get Windows testing available on TravisCI / CI platform.
   657  func getSpacingString() string {
   658  	if runtime.GOOS == "windows" {
   659  		return "-"
   660  	}
   661  	return "•"
   662  }
   663  
   664  // ColorPicker picks a color from colors slice defined at the starting of this file
   665  // It increments the colorCounter variable so that next iteration returns a different color
   666  func ColorPicker() color.Attribute {
   667  	colorCounter++
   668  	return colors[(colorCounter)%len(colors)]
   669  }
   670  

View as plain text