github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/pretty/util.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package pretty 12 13 // This file contains utility and other non-standard functions not listed 14 // in the paper (see the package documentation). Utility functions are those 15 // that are generally useful or need to create certain structures (like union) 16 // in a correct way. 17 18 // Join joins Docs d with string s and Line. There is no space between each 19 // item in d and the subsequent instance of s. 20 func Join(s string, d ...Doc) Doc { 21 return JoinDoc(Concat(Text(s), Line), d...) 22 } 23 24 // JoinDoc joins Docs d with Doc s. 25 func JoinDoc(s Doc, d ...Doc) Doc { 26 switch len(d) { 27 case 0: 28 return Nil 29 case 1: 30 return d[0] 31 default: 32 return Fold(Concat, d[0], s, JoinDoc(s, d[1:]...)) 33 } 34 } 35 36 // JoinNestedRight nests nested with string s. 37 // Every item after the first is indented. 38 // For example: 39 // aaaa 40 // <sep> bbb 41 // bbb 42 // <sep> ccc 43 // ccc 44 func JoinNestedRight(sep Doc, nested ...Doc) Doc { 45 switch len(nested) { 46 case 0: 47 return Nil 48 case 1: 49 return nested[0] 50 default: 51 return Concat( 52 nested[0], 53 FoldMap(Concat, 54 func(a Doc) Doc { return Concat(Line, ConcatSpace(sep, NestT(Group(a)))) }, 55 nested[1:]...)) 56 } 57 } 58 59 // ConcatDoc concatenates two Docs with between. 60 func ConcatDoc(a, b, between Doc) Doc { 61 return simplifyNil(a, b, func(a, b Doc) Doc { 62 return Concat( 63 a, 64 Concat( 65 between, 66 b, 67 ), 68 ) 69 }) 70 } 71 72 // ConcatLine concatenates two Docs with a Line. 73 func ConcatLine(a, b Doc) Doc { 74 return ConcatDoc(a, b, Line) 75 } 76 77 // ConcatSpace concatenates two Docs with a space. 78 func ConcatSpace(a, b Doc) Doc { 79 return ConcatDoc(a, b, textSpace) 80 } 81 82 // Stack concats Docs with a Line between each. 83 func Stack(d ...Doc) Doc { 84 return Fold(ConcatLine, d...) 85 } 86 87 // Fillwords fills lines with as many docs as will fit joined with a space or line. 88 func Fillwords(d ...Doc) Doc { 89 u := &union{textSpace, line{}} 90 fill := func(a, b Doc) Doc { 91 return ConcatDoc(a, b, u) 92 } 93 return Fold(fill, d...) 94 } 95 96 // JoinGroupAligned nests joined d with divider under head. 97 func JoinGroupAligned(head, divider string, d ...Doc) Doc { 98 return AlignUnder(Text(head), Join(divider, d...)) 99 } 100 101 // NestUnder nests nested under head. 102 func NestUnder(head, nested Doc) Doc { 103 return Group(Concat( 104 head, 105 NestT(Concat( 106 Line, 107 Group(nested), 108 )), 109 )) 110 } 111 112 // AlignUnder aligns nested to the right of head, and, if 113 // this does not fit on the line, nests nested under head. 114 func AlignUnder(head, nested Doc) Doc { 115 g := Group(nested) 116 a := ConcatSpace(head, Align(g)) 117 b := Concat(head, NestT(Concat(Line, g))) 118 return Group(&union{a, b}) 119 } 120 121 // Fold applies f recursively to all Docs in d. 122 func Fold(f func(a, b Doc) Doc, d ...Doc) Doc { 123 switch len(d) { 124 case 0: 125 return Nil 126 case 1: 127 return d[0] 128 default: 129 return f(d[0], Fold(f, d[1:]...)) 130 } 131 } 132 133 // FoldMap applies f recursively to all Docs in d processed through g. 134 func FoldMap(f func(a, b Doc) Doc, g func(Doc) Doc, d ...Doc) Doc { 135 switch len(d) { 136 case 0: 137 return Nil 138 case 1: 139 return g(d[0]) 140 default: 141 return f(g(d[0]), FoldMap(f, g, d[1:]...)) 142 } 143 } 144 145 // BracketDoc is like Bracket except it accepts Docs instead of strings. 146 func BracketDoc(l, x, r Doc) Doc { 147 return Group(Fold(Concat, 148 l, 149 NestT(Concat(SoftBreak, x)), 150 SoftBreak, 151 r, 152 )) 153 } 154 155 // simplifyNil returns fn(a, b). If either Doc is Nil, the other Doc 156 // is returned without invoking fn. 157 func simplifyNil(a, b Doc, fn func(Doc, Doc) Doc) Doc { 158 if a == Nil { 159 return b 160 } 161 if b == Nil { 162 return a 163 } 164 return fn(a, b) 165 } 166 167 // JoinNestedOuter attempts to de-indent the items after the first so 168 // that the operator appears in the right margin. This replacement is 169 // only done if there are enough "simple spaces" (as per previous uses 170 // of Align) to de-indent. No attempt is made to de-indent hard tabs, 171 // otherwise alignment may break on output devices with a different 172 // physical tab width. docFn should be set to Text or Keyword and will be 173 // used when converting lbl to a Doc. 174 func JoinNestedOuter(lbl string, docFn func(string) Doc, d ...Doc) Doc { 175 sep := docFn(lbl) 176 return &snesting{ 177 f: func(k int16) Doc { 178 if k < int16(len(lbl)+1) { 179 // If there is not enough space, don't even try. Just use a 180 // regular nested section. 181 return JoinNestedRight(sep, d...) 182 } 183 // If there is enough space, on the other hand, we can de-indent 184 // every line after the first. 185 return Concat(d[0], 186 NestS(int16(-len(lbl)-1), 187 FoldMap( 188 Concat, 189 func(x Doc) Doc { 190 return Concat( 191 Line, 192 ConcatSpace(sep, Align(Group(x)))) 193 }, 194 d[1:]...))) 195 }, 196 } 197 } 198 199 // TableRow is the data for one row of a RLTable (see below). 200 type TableRow struct { 201 Label string 202 Doc Doc 203 } 204 205 // TableAlignment should be used as first argument to Table(). 206 type TableAlignment int 207 208 const ( 209 // TableNoAlign does not use alignment and instead uses NestUnder. 210 TableNoAlign TableAlignment = iota 211 // TableRightAlignFirstColumn right-aligns (left-pads) the first column. 212 TableRightAlignFirstColumn 213 // TableLeftAlignFirstColumn left-aligns (right-pads) the first column. 214 TableLeftAlignFirstColumn 215 ) 216 217 // Table defines a document that formats a list of pairs of items either: 218 // - as a 2-column table, with the two columns aligned for example: 219 // SELECT aaa 220 // bbb 221 // FROM ccc 222 // - as sections, for example: 223 // SELECT 224 // aaa 225 // bbb 226 // FROM 227 // ccc 228 // 229 // We restrict the left value in each list item to be a one-line string 230 // to make the width computation efficient. 231 // 232 // For convenience, the function also skips over rows with a nil 233 // pointer as doc. 234 // 235 // docFn should be set to Text or Keyword and will be used when converting 236 // TableRow label's to Docs. 237 func Table(alignment TableAlignment, docFn func(string) Doc, rows ...TableRow) Doc { 238 // Compute the nested formatting in "sections". It's simple. 239 // Note that we do not use NestUnder() because we are not grouping 240 // at this level (the group is done for the final form below). 241 items := makeTableNestedSections(docFn, rows) 242 nestedSections := Stack(items...) 243 244 finalDoc := nestedSections 245 246 if alignment != TableNoAlign { 247 items = makeAlignedTableItems(alignment == TableRightAlignFirstColumn /* leftPad */, docFn, rows, items) 248 alignedTable := Stack(items...) 249 finalDoc = &union{alignedTable, nestedSections} 250 } 251 252 return Group(finalDoc) 253 } 254 255 func computeLeftColumnWidth(rows []TableRow) int { 256 leftwidth := 0 257 for _, r := range rows { 258 if r.Doc == nil { 259 continue 260 } 261 if leftwidth < len(r.Label) { 262 leftwidth = len(r.Label) 263 } 264 } 265 return leftwidth 266 } 267 268 func makeAlignedTableItems( 269 leftPad bool, docFn func(string) Doc, rows []TableRow, items []Doc, 270 ) []Doc { 271 // We'll need the left column width below. 272 leftWidth := computeLeftColumnWidth(rows) 273 // Now convert the rows. 274 items = items[:0] 275 for _, r := range rows { 276 if r.Doc == nil || (r.Label == "" && r.Doc == Nil) { 277 continue 278 } 279 if r.Label != "" { 280 lbl := docFn(r.Label) 281 var pleft Doc 282 if leftPad { 283 pleft = Concat(pad{int16(leftWidth - len(r.Label))}, lbl) 284 } else { 285 pleft = Concat(lbl, pad{int16(leftWidth - len(r.Label))}) 286 } 287 d := simplifyNil(pleft, r.Doc, 288 func(a, b Doc) Doc { 289 return ConcatSpace(a, Align(Group(b))) 290 }) 291 items = append(items, d) 292 } else { 293 items = append(items, Concat(pad{int16(leftWidth + 1)}, Align(Group(r.Doc)))) 294 } 295 } 296 return items 297 } 298 299 func makeTableNestedSections(docFn func(string) Doc, rows []TableRow) []Doc { 300 items := make([]Doc, 0, len(rows)) 301 for _, r := range rows { 302 if r.Doc == nil || (r.Label == "" && r.Doc == Nil) { 303 continue 304 } 305 if r.Label != "" { 306 d := simplifyNil(docFn(r.Label), r.Doc, 307 func(a, b Doc) Doc { 308 return Concat(a, NestT(Concat(Line, Group(b)))) 309 }) 310 items = append(items, d) 311 } else { 312 items = append(items, Group(r.Doc)) 313 } 314 } 315 return items 316 }