123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- package columnize
- import (
- "bytes"
- "fmt"
- "strings"
- )
- // Config can be used to tune certain parameters which affect the way
- // in which Columnize will format output text.
- type Config struct {
- // The string by which the lines of input will be split.
- Delim string
- // The string by which columns of output will be separated.
- Glue string
- // The string by which columns of output will be prefixed.
- Prefix string
- // A replacement string to replace empty fields.
- Empty string
- // NoTrim disables automatic trimming of inputs.
- NoTrim bool
- }
- // DefaultConfig returns a *Config with default values.
- func DefaultConfig() *Config {
- return &Config{
- Delim: "|",
- Glue: " ",
- Prefix: "",
- Empty: "",
- NoTrim: false,
- }
- }
- // MergeConfig merges two config objects together and returns the resulting
- // configuration. Values from the right take precedence over the left side.
- func MergeConfig(a, b *Config) *Config {
- // Return quickly if either side was nil
- if a == nil {
- return b
- }
- if b == nil {
- return a
- }
- var result Config = *a
- if b.Delim != "" {
- result.Delim = b.Delim
- }
- if b.Glue != "" {
- result.Glue = b.Glue
- }
- if b.Prefix != "" {
- result.Prefix = b.Prefix
- }
- if b.Empty != "" {
- result.Empty = b.Empty
- }
- if b.NoTrim {
- result.NoTrim = true
- }
- return &result
- }
- // stringFormat, given a set of column widths and the number of columns in
- // the current line, returns a sprintf-style format string which can be used
- // to print output aligned properly with other lines using the same widths set.
- func stringFormat(c *Config, widths []int, columns int) string {
- // Create the buffer with an estimate of the length
- buf := bytes.NewBuffer(make([]byte, 0, (6+len(c.Glue))*columns))
- // Start with the prefix, if any was given. The buffer will not return an
- // error so it does not need to be handled
- buf.WriteString(c.Prefix)
- // Create the format string from the discovered widths
- for i := 0; i < columns && i < len(widths); i++ {
- if i == columns-1 {
- buf.WriteString("%s\n")
- } else {
- fmt.Fprintf(buf, "%%-%ds%s", widths[i], c.Glue)
- }
- }
- return buf.String()
- }
- // elementsFromLine returns a list of elements, each representing a single
- // item which will belong to a column of output.
- func elementsFromLine(config *Config, line string) []interface{} {
- separated := strings.Split(line, config.Delim)
- elements := make([]interface{}, len(separated))
- for i, field := range separated {
- value := field
- if !config.NoTrim {
- value = strings.TrimSpace(field)
- }
- // Apply the empty value, if configured.
- if value == "" && config.Empty != "" {
- value = config.Empty
- }
- elements[i] = value
- }
- return elements
- }
- // runeLen calculates the number of visible "characters" in a string
- func runeLen(s string) int {
- l := 0
- for _ = range s {
- l++
- }
- return l
- }
- // widthsFromLines examines a list of strings and determines how wide each
- // column should be considering all of the elements that need to be printed
- // within it.
- func widthsFromLines(config *Config, lines []string) []int {
- widths := make([]int, 0, 8)
- for _, line := range lines {
- elems := elementsFromLine(config, line)
- for i := 0; i < len(elems); i++ {
- l := runeLen(elems[i].(string))
- if len(widths) <= i {
- widths = append(widths, l)
- } else if widths[i] < l {
- widths[i] = l
- }
- }
- }
- return widths
- }
- // Format is the public-facing interface that takes a list of strings and
- // returns nicely aligned column-formatted text.
- func Format(lines []string, config *Config) string {
- conf := MergeConfig(DefaultConfig(), config)
- widths := widthsFromLines(conf, lines)
- // Estimate the buffer size
- glueSize := len(conf.Glue)
- var size int
- for _, w := range widths {
- size += w + glueSize
- }
- size *= len(lines)
- // Create the buffer
- buf := bytes.NewBuffer(make([]byte, 0, size))
- // Create a cache for the string formats
- fmtCache := make(map[int]string, 16)
- // Create the formatted output using the format string
- for _, line := range lines {
- elems := elementsFromLine(conf, line)
- // Get the string format using cache
- numElems := len(elems)
- stringfmt, ok := fmtCache[numElems]
- if !ok {
- stringfmt = stringFormat(conf, widths, numElems)
- fmtCache[numElems] = stringfmt
- }
- fmt.Fprintf(buf, stringfmt, elems...)
- }
- // Get the string result
- result := buf.String()
- // Remove trailing newline without removing leading/trailing space
- if n := len(result); n > 0 && result[n-1] == '\n' {
- result = result[:n-1]
- }
- return result
- }
- // SimpleFormat is a convenience function to format text with the defaults.
- func SimpleFormat(lines []string) string {
- return Format(lines, nil)
- }
|