github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/flexibletable/cells.go (about)

     1  // Copyright 2016 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package flexibletable
     5  
     6  import (
     7  	"math"
     8  	"strconv"
     9  	"strings"
    10  )
    11  
    12  // Alignment defines how the content of a cell should align
    13  type Alignment int
    14  
    15  // Possible Alignment values
    16  const (
    17  	Left Alignment = iota // default
    18  	Right
    19  	Center
    20  )
    21  
    22  // Cell defines a cell (column) in a row
    23  type Cell struct {
    24  	// Content is the content of the cell. It can be either a SingleCell or a
    25  	// MultiCell
    26  	Content cellContent
    27  
    28  	// Alignment specifies how the content of a cell should align
    29  	Alignment Alignment
    30  
    31  	// Frame defines a "frame" around the content. The array defines two strings,
    32  	// which are added before and after the content, before any paddings are
    33  	// inserted.
    34  	Frame [2]string
    35  }
    36  
    37  func (c Cell) full() string {
    38  	return c.Content.full()
    39  }
    40  
    41  func (c Cell) render(widthConstraint int) (string, error) {
    42  	frameWidth := len(c.Frame[0]) + len(c.Frame[1])
    43  	minWidth := c.Content.minWidth() + frameWidth
    44  	if minWidth > widthConstraint {
    45  		return "", WidthTooSmallError{}
    46  	}
    47  	return c.Frame[0] + c.Content.render(widthConstraint-frameWidth) + c.Frame[1], nil
    48  }
    49  
    50  func (c Cell) renderWithPadding(width int) (string, error) {
    51  	str, err := c.render(width)
    52  	if err != nil {
    53  		return "", err
    54  	}
    55  	return c.addPadding(str, width)
    56  }
    57  
    58  func (c Cell) addPadding(str string, width int) (string, error) {
    59  	padding := width - len(str)
    60  	if padding == 0 {
    61  		return str, nil
    62  	}
    63  	switch c.Alignment {
    64  	case Left:
    65  		return str + strings.Repeat(" ", padding), nil
    66  	case Right:
    67  		return strings.Repeat(" ", padding) + str, nil
    68  	case Center:
    69  		return strings.Repeat(" ", padding/2) + str + strings.Repeat(" ", padding-padding/2), nil
    70  	default:
    71  		return "", BadOptionError{optionName: "Alignment"}
    72  	}
    73  }
    74  
    75  type cellContent interface {
    76  	render(maxWidth int) string
    77  	minWidth() int
    78  	full() string
    79  }
    80  
    81  // emptyCell is an implementation of cellContent that is only used internally
    82  // in flexibletable package during rendering.
    83  type emptyCell struct{}
    84  
    85  func (emptyCell) render(int) string { return "" }
    86  func (emptyCell) minWidth() int     { return 0 }
    87  func (emptyCell) full() string      { return "" }
    88  
    89  // SingleCell defines cell content with a single string. If being truncated,
    90  // the truncated part is replaced with "..."
    91  type SingleCell struct {
    92  	// Item is, well, the content.
    93  	Item string
    94  }
    95  
    96  func (c SingleCell) full() string {
    97  	return c.Item
    98  }
    99  
   100  func (c SingleCell) render(maxWidth int) string {
   101  	if len(c.Item) <= maxWidth {
   102  		return c.Item
   103  	}
   104  	return c.Item[:maxWidth-3] + "..."
   105  }
   106  
   107  func (c SingleCell) minWidth() int {
   108  	if len(c.Item) < 3 {
   109  		return len(c.Item)
   110  	}
   111  	return 3 // "..."
   112  }
   113  
   114  // MultiCell defines cell content with multiple strings. If being truncated, it
   115  // looks like this: "item1,item2,+4..."
   116  type MultiCell struct {
   117  	// Sep is the separator between different items
   118  	Sep string
   119  	// Items are the content
   120  	Items []string
   121  }
   122  
   123  func (c MultiCell) full() string {
   124  	return strings.Join(c.Items, c.Sep)
   125  }
   126  
   127  func (c MultiCell) render(maxWidth int) (ret string) {
   128  	retIfFull := "+" + strconv.Itoa(len(c.Items)) + "..."
   129  
   130  	for i, item := range c.Items {
   131  		var plus string
   132  		if len(ret) > 0 {
   133  			plus = c.Sep + item
   134  		} else {
   135  			plus = item
   136  		}
   137  
   138  		if len(plus)+len(ret) <= maxWidth {
   139  			ret += plus
   140  		} else {
   141  			return retIfFull
   142  		}
   143  
   144  		newRetIfFull := ret + c.Sep + "+" + strconv.Itoa(len(c.Items)-i-1) + "..."
   145  		if len(newRetIfFull) <= maxWidth {
   146  			retIfFull = newRetIfFull
   147  		}
   148  	}
   149  
   150  	return ret
   151  }
   152  
   153  func (c MultiCell) minWidth() int {
   154  	simpleLen := len(strings.Join(c.Items, c.Sep))
   155  	digestMin := int(math.Ceil(math.Log10(float64(len(c.Items)+1)))) + 4 // "+9..."
   156  	if simpleLen < digestMin {
   157  		return simpleLen
   158  	}
   159  	return digestMin
   160  }