go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/text/indented/writer.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package indented 16 17 import ( 18 "bytes" 19 "io" 20 ) 21 22 // Writer inserts indentation before each line. 23 type Writer struct { 24 io.Writer // underlying writer. 25 Level int // number of times \t must be inserted before each line. 26 UseSpaces bool // true iff Level is the number of spaces instead of tabs. 27 Width int // number of whitespace characters in each level. Defaults to 1. 28 insideLine bool 29 } 30 31 // Limit is the maximum value of Level. 32 const Limit = 256 33 34 var indentationTabs = bytes.Repeat([]byte{'\t'}, Limit) 35 var indentationSpaces = bytes.Repeat([]byte{' '}, Limit) 36 37 // prefix returns the indentation prefix. 38 func (w *Writer) prefix() []byte { 39 if w.Level == 0 { 40 return nil 41 } 42 43 buf := indentationTabs 44 if w.UseSpaces { 45 buf = indentationSpaces 46 } 47 48 width := w.Width 49 if width <= 0 { 50 width = 1 51 } 52 return buf[:width*w.Level] 53 } 54 55 // Write writes data inserting a newline before each line. 56 // Panics if w.Indent is outside of [0, Limit) range. 57 func (w *Writer) Write(data []byte) (n int, err error) { 58 // Do not print indentation if there is no data. 59 for len(data) > 0 { 60 var printUntil int 61 endsWithNewLine := false 62 63 lineBeginning := !w.insideLine 64 if data[0] == '\n' && lineBeginning { 65 // This is a blank line. Do not indent it, just print as is. 66 printUntil = 1 67 } else { 68 if lineBeginning { 69 // Print indentation. 70 w.Writer.Write(w.prefix()) 71 w.insideLine = true 72 } 73 74 lineEnd := bytes.IndexRune(data, '\n') 75 if lineEnd < 0 { 76 // Print the whole thing. 77 printUntil = len(data) 78 } else { 79 // Print until the newline inclusive. 80 printUntil = lineEnd + 1 81 endsWithNewLine = true 82 } 83 } 84 toPrint := data[:printUntil] 85 data = data[printUntil:] 86 87 // Assertion: none of the runes in toPrint 88 // can be newline except the last rune. 89 // The last rune is newline iff endsWithNewLine==true. 90 91 m, err := w.Writer.Write(toPrint) 92 n += m 93 94 if m == len(toPrint) && endsWithNewLine { 95 // We've printed the newline, so we are the line beginning again. 96 w.insideLine = false 97 } 98 if err != nil { 99 return n, err 100 } 101 } 102 return n, nil 103 }