Line data Source code
1 : // Copyright 2024 The LevelDB-Go and Pebble Authors. All rights reserved. Use 2 : // of this source code is governed by a BSD-style license that can be found in 3 : // the LICENSE file. 4 : 5 : package metamorphic 6 : 7 : import "strings" 8 : 9 : // TryToGenerateDiagram attempts to generate a user-readable ASCII diagram of 10 : // the keys involved in the operations. 11 : // 12 : // If the diagram would be too large to be practical, returns the empty string 13 : // (with no error). 14 1 : func TryToGenerateDiagram(opsData []byte) (string, error) { 15 1 : ops, err := parse(opsData, parserOpts{}) 16 1 : if err != nil { 17 0 : return "", err 18 0 : } 19 1 : if len(ops) > 200 { 20 0 : return "", nil 21 0 : } 22 1 : keySet := make(map[string]struct{}) 23 1 : for _, o := range ops { 24 1 : for _, r := range o.diagramKeyRanges() { 25 1 : keySet[string(r.Start)] = struct{}{} 26 1 : keySet[string(r.End)] = struct{}{} 27 1 : } 28 : } 29 1 : if len(keySet) == 0 { 30 0 : return "", nil 31 0 : } 32 1 : keys := sortedKeys(keySet) 33 1 : axis1, axis2, pos := genAxis(keys) 34 1 : if len(axis1) > 200 { 35 0 : return "", nil 36 0 : } 37 : 38 1 : var rows []string 39 1 : for _, o := range ops { 40 1 : ranges := o.diagramKeyRanges() 41 1 : var row strings.Builder 42 1 : for _, r := range ranges { 43 1 : s := pos[string(r.Start)] 44 1 : e := pos[string(r.End)] 45 1 : for row.Len() < s { 46 1 : row.WriteByte(' ') 47 1 : } 48 1 : row.WriteByte('|') 49 1 : if e > s { 50 1 : for row.Len() < e { 51 1 : row.WriteByte('-') 52 1 : } 53 1 : row.WriteByte('|') 54 : } 55 : } 56 1 : for row.Len() <= len(axis1) { 57 1 : row.WriteByte(' ') 58 1 : } 59 1 : row.WriteString(o.String()) 60 1 : 61 1 : rows = append(rows, row.String()) 62 : } 63 1 : rows = append(rows, axis1, axis2) 64 1 : return strings.Join(rows, "\n"), nil 65 : } 66 : 67 : // genAxis generates the horizontal key axis and returns two rows (one for axis 68 : // one for labels), along with a map from key to column. 69 : 70 : // Example: 71 : // 72 : // axisRow: |----|----|----| 73 : // labelRow: a bar foo zed 74 : // pos: a:0, bar:5, foo:10, zed:15 75 1 : func genAxis(keys []string) (axisRow string, labelRow string, pos map[string]int) { 76 1 : const minSpaceBetweenKeys = 4 77 1 : const minSpaceBetweenKeyLabels = 2 78 1 : var a, b strings.Builder 79 1 : pos = make(map[string]int) 80 1 : for i, k := range keys { 81 1 : if i > 0 { 82 1 : b.WriteString(strings.Repeat(" ", minSpaceBetweenKeyLabels)) 83 1 : for b.Len() <= a.Len()+minSpaceBetweenKeys { 84 1 : b.WriteByte(' ') 85 1 : } 86 1 : for a.Len() < b.Len() { 87 1 : a.WriteByte('-') 88 1 : } 89 : } 90 1 : pos[k] = a.Len() 91 1 : a.WriteByte('|') 92 1 : b.WriteString(k) 93 : } 94 1 : return a.String(), b.String(), pos 95 : }