Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Godeps/Readme

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 51 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ package main

import (
"fmt"
"github.com/ryanuber/columnize"
"github.com/CiscoCloud/columnize"
)

func main() {
Expand Down Expand Up @@ -55,12 +55,18 @@ config.Delim = "|"
config.Glue = " "
config.Prefix = ""
config.Empty = ""
config.MaxWidth = []int{10, 0, 0}
config.OutputWidth = 80
```

* `Delim` is the string by which columns of **input** are delimited
* `Glue` is the string by which columns of **output** are delimited
* `Prefix` is a string by which each line of **output** is prefixed
* `Empty` is a string used to replace blank values found in output
* `MaxWidth` is an int slice specifying the maximum width of each column.
* `OutputWidth` is an int specifying the maximum width of an output line.

If MaxWidth or OutputWidth is specified and output exceeds the configured width, Columnize breaks a column at a word boundary and continues it on the next line. See below for details.

You can then pass the `Config` in using the `Format` method (signature below) to
have text formatted to your liking.
Expand All @@ -73,3 +79,47 @@ SimpleFormat(intput []string) string

Format(input []string, config *Config) string
```

Controlling Output Width
========================
Output exceeding the width of the terminal window - particularly columnized output - can be difficult to read. To address this, Columnize provides two configuration parameters for controlling output width.

* `MaxWidth` is an int slice specifying the maximum width of each column. If the data for a column exceeds its maximum width, Columnize formats the column into two or more lines by breaking its data at a word boundary and continuing it onto the next line. A zero or missing value for a MaxWidth element specifies that the corresponding column is uncontrolled (no maximum width).
* `OutputWidth` is an int value specifying the maximum width of the entire output line (including prefix and glue). If data width exceeds this value, Columnize sets a MaxWidth for the rightmost uncontrolled column so that the output width satisfies the restriction. You can specify `OutputWidth: columnize.AUTO` to use the actual width of the terminal window for OutputWidth.

For example:

input := []string{
"Column a | Column b | Column c",
"xx | yy | zz",
"some quite long data | some more data | even longer data for the last column",
"this one will fit | a break | The quick brown fox jumps over the low lazy dog",
"qq | rr | ss",
}
config := Config{MaxWidth: []int{10, 0, 15}}
output := Format(input, &config)

results in the output:

Column a Column b Column c
xx yy zz
some quite some more data even longer
long data data for the
last column
this one a break The quick brown
will fit fox jumps over
the low lazy
dog
qq rr ss

Specify OutputWidth to restrict the entire output line. For example, the configuration:

config := Config{
OutputWidth: columnize.AUTO,
}

causes the entire output line to fit in the terminal window. Columnize modifies data lines exceeding the width of the window by setting the appropriate MaxWidth for the last column of data. Columnize adjusts the last uncontrolled column, so if you want it to adjust a column other than the last, specify an explicit MaxWidth for any columns to the right of the one you want Columnize to adjust.




149 changes: 139 additions & 10 deletions columnize.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package columnize
import (
"fmt"
"strings"
"unicode"

"github.com/DeMille/termsize"
)

type Config struct {
Expand All @@ -17,14 +20,26 @@ type Config struct {

// A replacement string to replace empty fields
Empty string

// Maximum width of output; set to AUTO to use actual console width
OutputWidth int

// Maximum width of each column
MaxWidth []int
}

const (
AUTO = -1
)

// Returns a Config with default values.
func DefaultConfig() *Config {
return &Config{
Delim: "|",
Glue: " ",
Prefix: "",
Delim: "|",
Glue: " ",
Prefix: "",
OutputWidth: -999,
MaxWidth: []int{},
}
}

Expand All @@ -45,16 +60,70 @@ func getElementsFromLine(config *Config, line string) []interface{} {
// 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 getWidthsFromLines(config *Config, lines []string) []int {
var widths []int
widths := calculateColumnWidths(config, lines)
outputWidth := getOutputWidth(config)
widths = adjustWidths(config, widths, outputWidth)
return widths
}

// Calculate column widths by comparing data width and MaxWidth
func calculateColumnWidths(config *Config, lines []string) (widths []int) {
for _, line := range lines {
elems := getElementsFromLine(config, line)
for i := 0; i < len(elems); i++ {
l := len(elems[i].(string))
lenElem := len(elems[i].(string))
if i < len(config.MaxWidth) {
if config.MaxWidth[i] < 0 {
fmt.Printf("Columnize: negative MaxWidth value not supported - please use OutputWidth\n")
} else if config.MaxWidth[i] > 0 && config.MaxWidth[i] < lenElem {
lenElem = config.MaxWidth[i]
}
}
if len(widths) <= i {
widths = append(widths, l)
} else if widths[i] < l {
widths[i] = l
widths = append(widths, lenElem)
} else if widths[i] < lenElem {
widths[i] = lenElem
}
}
}
return
}

// Get output width specification
func getOutputWidth(config *Config) (outputWidth int) {
outputWidth = config.OutputWidth
if outputWidth == AUTO {
var e error
if outputWidth, e = GetConsoleWidth(); e != nil {
fmt.Printf("Unable to set AUTO OutputWidth: %s\n", e.Error())
}
}
return
}

// If the output width is restricted and the output line will exceed that width,
// attempt to meet the restriction by adjusting the width of the rightmost
// unrestricted column, or the rightmost column if all columns are restricted.
func adjustWidths(config *Config, widths []int, outputWidth int) []int {
if outputWidth > 0 {
totalLineWidth := len(config.Prefix) + len(config.Glue)*(len(widths)-1)
for _, width := range widths {
totalLineWidth += width
}
if totalLineWidth > outputWidth {
adjIndex := -1
for i := len(widths) - 1; i >= 0; i-- {
if i >= len(config.MaxWidth) || config.MaxWidth[i] <= 0 {
adjIndex = i
break
}
}
if adjIndex < 0 {
adjIndex = len(widths) - 1
}
adjustedWidth := outputWidth - (totalLineWidth - widths[adjIndex])
if adjustedWidth > 0 {
widths[adjIndex] = adjustedWidth
}
}
}
Expand Down Expand Up @@ -101,6 +170,12 @@ func MergeConfig(a, b *Config) *Config {
if b.Empty != "" {
result.Empty = b.Empty
}
if b.OutputWidth >= 0 || b.OutputWidth == AUTO {
result.OutputWidth = b.OutputWidth
}
if len(b.MaxWidth) > 0 {
result.MaxWidth = b.MaxWidth
}

return &result
}
Expand All @@ -116,8 +191,13 @@ func Format(lines []string, config *Config) string {
// Create the formatted output using the format string
for _, line := range lines {
elems := getElementsFromLine(conf, line)
stringfmt := conf.getStringFormat(widths, len(elems))
result += fmt.Sprintf(stringfmt, elems...)
extensionLineElems := []string{}
isStillDataToFormat := true
for isStillDataToFormat {
isStillDataToFormat = truncateToWidth(&elems, &extensionLineElems, widths)
stringfmt := conf.getStringFormat(widths, len(elems))
result += fmt.Sprintf(stringfmt, elems...)
}
}

// Remove trailing newline without removing leading/trailing space
Expand All @@ -132,3 +212,52 @@ func Format(lines []string, config *Config) string {
func SimpleFormat(lines []string) string {
return Format(lines, nil)
}

// Truncate any elements exceeding their maximum width, and save their remaining
// data for an extension line.
func truncateToWidth(elems *[]interface{}, extensionLineElems *[]string, widths []int) (isStillDataToFormat bool) {

// If this an extension line, make its list of elements current

if len(*extensionLineElems) > 0 {
for i, elem := range *extensionLineElems {
(*elems)[i] = elem
}
*extensionLineElems = []string{}
}

// Examine each element to determine if it exceeds its maximum allowed width.
// If so, truncate it at the closest whitespace to the limit and save its remaining
// data for the next extension line.

for i, elem := range *elems {
stringElem := strings.TrimSpace(fmt.Sprintf("%s", elem))
if len(stringElem) > widths[i] {
isStillDataToFormat = true
splitPoint := widths[i]
for ; splitPoint > 0; splitPoint-- {
if unicode.IsSpace(rune(stringElem[splitPoint])) {
break
}
}
if splitPoint == 0 {
splitPoint = widths[i]
}
(*elems)[i] = strings.TrimSpace(stringElem[:splitPoint])
if len(*extensionLineElems) == 0 {
(*extensionLineElems) = make([]string, len(*elems))
}
(*extensionLineElems)[i] = strings.TrimSpace(stringElem[splitPoint:])
}

}
return
}

// Get the width of the console
func GetConsoleWidth() (width int, e error) {
if e = termsize.Init(); e == nil {
width, _, e = termsize.Size()
}
return
}
Loading