...

Source file src/k8s.io/kubectl/pkg/explain/formatter.go

Documentation: k8s.io/kubectl/pkg/explain

     1  /*
     2  Copyright 2017 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  package explain
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"regexp"
    23  	"strings"
    24  )
    25  
    26  // Formatter helps you write with indentation, and can wrap text as needed.
    27  type Formatter struct {
    28  	IndentLevel int
    29  	Wrap        int
    30  	Writer      io.Writer
    31  }
    32  
    33  // Indent creates a new Formatter that will indent the code by that much more.
    34  func (f Formatter) Indent(indent int) *Formatter {
    35  	f.IndentLevel = f.IndentLevel + indent
    36  	return &f
    37  }
    38  
    39  // Write writes a string with the indentation set for the
    40  // Formatter. This is not wrapping text.
    41  func (f *Formatter) Write(str string, a ...interface{}) error {
    42  	// Don't indent empty lines
    43  	if str == "" {
    44  		_, err := io.WriteString(f.Writer, "\n")
    45  		return err
    46  	}
    47  
    48  	indent := ""
    49  	for i := 0; i < f.IndentLevel; i++ {
    50  		indent = indent + " "
    51  	}
    52  
    53  	if len(a) > 0 {
    54  		str = fmt.Sprintf(str, a...)
    55  	}
    56  	_, err := io.WriteString(f.Writer, indent+str+"\n")
    57  	return err
    58  }
    59  
    60  // WriteWrapped writes a string with the indentation set for the
    61  // Formatter, and wraps as needed.
    62  func (f *Formatter) WriteWrapped(str string, a ...interface{}) error {
    63  	if f.Wrap == 0 {
    64  		return f.Write(str, a...)
    65  	}
    66  	text := fmt.Sprintf(str, a...)
    67  	strs := wrapString(text, f.Wrap-f.IndentLevel)
    68  	for _, substr := range strs {
    69  		if err := f.Write(substr); err != nil {
    70  			return err
    71  		}
    72  	}
    73  	return nil
    74  }
    75  
    76  type line struct {
    77  	wrap  int
    78  	words []string
    79  }
    80  
    81  func (l *line) String() string {
    82  	return strings.Join(l.words, " ")
    83  }
    84  
    85  func (l *line) Empty() bool {
    86  	return len(l.words) == 0
    87  }
    88  
    89  func (l *line) Len() int {
    90  	return len(l.String())
    91  }
    92  
    93  // Add adds the word to the line, returns true if we could, false if we
    94  // didn't have enough room. It's always possible to add to an empty line.
    95  func (l *line) Add(word string) bool {
    96  	newLine := line{
    97  		wrap:  l.wrap,
    98  		words: append(l.words, word),
    99  	}
   100  	if newLine.Len() <= l.wrap || len(l.words) == 0 {
   101  		l.words = newLine.words
   102  		return true
   103  	}
   104  	return false
   105  }
   106  
   107  var bullet = regexp.MustCompile(`^(\d+\.?|-|\*)\s`)
   108  
   109  func shouldStartNewLine(lastWord, str string) bool {
   110  	// preserve line breaks ending in :
   111  	if strings.HasSuffix(lastWord, ":") {
   112  		return true
   113  	}
   114  
   115  	// preserve code blocks
   116  	if strings.HasPrefix(str, "    ") {
   117  		return true
   118  	}
   119  	str = strings.TrimSpace(str)
   120  	// preserve empty lines
   121  	if len(str) == 0 {
   122  		return true
   123  	}
   124  	// preserve lines that look like they're starting lists
   125  	if bullet.MatchString(str) {
   126  		return true
   127  	}
   128  	// otherwise combine
   129  	return false
   130  }
   131  
   132  func wrapString(str string, wrap int) []string {
   133  	wrapped := []string{}
   134  	l := line{wrap: wrap}
   135  	// track the last word added to the current line
   136  	lastWord := ""
   137  	flush := func() {
   138  		if !l.Empty() {
   139  			lastWord = ""
   140  			wrapped = append(wrapped, l.String())
   141  			l = line{wrap: wrap}
   142  		}
   143  	}
   144  
   145  	// iterate over the lines in the original description
   146  	for _, str := range strings.Split(str, "\n") {
   147  		// preserve code blocks and blockquotes as-is
   148  		if strings.HasPrefix(str, "    ") {
   149  			flush()
   150  			wrapped = append(wrapped, str)
   151  			continue
   152  		}
   153  
   154  		// preserve empty lines after the first line, since they can separate logical sections
   155  		if len(wrapped) > 0 && len(strings.TrimSpace(str)) == 0 {
   156  			flush()
   157  			wrapped = append(wrapped, "")
   158  			continue
   159  		}
   160  
   161  		// flush if we should start a new line
   162  		if shouldStartNewLine(lastWord, str) {
   163  			flush()
   164  		}
   165  		words := strings.Fields(str)
   166  		for _, word := range words {
   167  			lastWord = word
   168  			if !l.Add(word) {
   169  				flush()
   170  				if !l.Add(word) {
   171  					panic("Couldn't add to empty line.")
   172  				}
   173  			}
   174  		}
   175  	}
   176  	flush()
   177  	return wrapped
   178  }
   179  

View as plain text