github.com/wolfi-dev/wolfictl@v0.16.11/pkg/cli/internal/wrapped/wrapped.go (about)

     1  package wrapped
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strings"
     7  
     8  	"github.com/charmbracelet/lipgloss"
     9  	"github.com/muesli/reflow/wordwrap"
    10  	"golang.org/x/term"
    11  )
    12  
    13  var (
    14  	// LineLength is the maximum length allowed of a printed line of text. This is
    15  	// the primary control for this package. It defaults to the greater of the
    16  	// current terminal width (detected at init time) and MaxLineLength.
    17  	LineLength = initialLineLength
    18  
    19  	// MaxLineLength sets the upper bound for how long a line can be. This is a
    20  	// secondary control that adjusts how LineLength is calculated. It defaults to
    21  	// 120 (for readability).
    22  	MaxLineLength = 120
    23  )
    24  
    25  // Println wraps the given message using LineLength and prints it to stdout with
    26  // a trailing newline.
    27  func Println(msg string) {
    28  	fmt.Println(wrapToLineLength(msg))
    29  }
    30  
    31  // Sprint wraps the given message using LineLength and returns it as a string.
    32  func Sprint(msg string) string {
    33  	return wrapToLineLength(msg)
    34  }
    35  
    36  // Fatal wraps the given message using LineLength and prints it to stderr with a
    37  // trailing newline, then exits with a non-zero status code.
    38  func Fatal(msg string) {
    39  	fmt.Fprintln(os.Stderr, wrapToLineLength(msg))
    40  	os.Exit(1)
    41  }
    42  
    43  // Repeat repeats the given message string until the LineLength is reached, such
    44  // that the returned string is exactly LineLength long. If the given string is
    45  // already longer than LineLength, it is truncated to LineLength.
    46  func Repeat(s string) string {
    47  	if length := lipgloss.Width(s); length > LineLength {
    48  		// Repeating is not necessary.
    49  		return truncate(s)
    50  	}
    51  
    52  	sb := new(strings.Builder)
    53  
    54  	// We're using lipgloss to measure the rendered length of the string (we don't
    55  	// want to measure the raw length of the string — some characters are rendered
    56  	// wider than others, while others are not rendered at all).
    57  	currentLength := lipgloss.Width(sb.String())
    58  	for currentLength < LineLength {
    59  		sb.WriteString(s)
    60  
    61  		currentLength = lipgloss.Width(sb.String())
    62  	}
    63  
    64  	// Truncate the string to the line length.
    65  	line := truncate(sb.String())
    66  
    67  	return line
    68  }
    69  
    70  const (
    71  	// This is only used if we can't detect the terminal width.
    72  	fallbackLineLength = 80
    73  )
    74  
    75  var (
    76  	initialLineLength = func() int {
    77  		// Defer to smaller-width terminal screens, but don't go bigger than
    78  		// MaxLineLength.
    79  		if terminalWidth < MaxLineLength {
    80  			return terminalWidth
    81  		}
    82  
    83  		return MaxLineLength
    84  	}()
    85  
    86  	terminalWidth = getTerminalWidth()
    87  )
    88  
    89  func getTerminalWidth() int {
    90  	w, _, err := term.GetSize(int(os.Stdout.Fd()))
    91  	if err != nil {
    92  		return fallbackLineLength
    93  	}
    94  	return w
    95  }
    96  
    97  func wrapToLineLength(msg string) string {
    98  	return wordwrap.String(msg, LineLength)
    99  }
   100  
   101  var styleTruncated = lipgloss.NewStyle().Width(LineLength)
   102  
   103  func truncate(msg string) string {
   104  	if length := lipgloss.Width(msg); length > LineLength {
   105  		return styleTruncated.Render(msg)
   106  	}
   107  
   108  	return msg
   109  }