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 0 : func TryToGenerateDiagram(keyFormat KeyFormat, opsData []byte) (string, error) {
15 0 : ops, err := parse(opsData, parserOpts{
16 0 : parseFormattedUserKey: keyFormat.ParseFormattedKey,
17 0 : parseFormattedUserKeySuffix: keyFormat.ParseFormattedKeySuffix,
18 0 : })
19 0 : if err != nil {
20 0 : return "", err
21 0 : }
22 0 : if len(ops) > 200 {
23 0 : return "", nil
24 0 : }
25 0 : keySet := make(map[string]struct{})
26 0 : for _, o := range ops {
27 0 : for _, r := range o.diagramKeyRanges() {
28 0 : keySet[string(r.Start)] = struct{}{}
29 0 : keySet[string(r.End)] = struct{}{}
30 0 : }
31 : }
32 0 : if len(keySet) == 0 {
33 0 : return "", nil
34 0 : }
35 0 : keys := sortedKeys(keyFormat.Comparer.Compare, keySet)
36 0 : axis1, axis2, pos := genAxis(keys)
37 0 : if len(axis1) > 200 {
38 0 : return "", nil
39 0 : }
40 :
41 0 : var rows []string
42 0 : for _, o := range ops {
43 0 : ranges := o.diagramKeyRanges()
44 0 : var row strings.Builder
45 0 : for _, r := range ranges {
46 0 : s := pos[string(r.Start)]
47 0 : e := pos[string(r.End)]
48 0 : for row.Len() < s {
49 0 : row.WriteByte(' ')
50 0 : }
51 0 : row.WriteByte('|')
52 0 : if e > s {
53 0 : for row.Len() < e {
54 0 : row.WriteByte('-')
55 0 : }
56 0 : row.WriteByte('|')
57 : }
58 : }
59 0 : for row.Len() <= len(axis1) {
60 0 : row.WriteByte(' ')
61 0 : }
62 0 : row.WriteString(o.formattedString(keyFormat))
63 0 :
64 0 : rows = append(rows, row.String())
65 : }
66 0 : rows = append(rows, axis1, axis2)
67 0 : return strings.Join(rows, "\n"), nil
68 : }
69 :
70 : // genAxis generates the horizontal key axis and returns two rows (one for axis
71 : // one for labels), along with a map from key to column.
72 :
73 : // Example:
74 : //
75 : // axisRow: |----|----|----|
76 : // labelRow: a bar foo zed
77 : // pos: a:0, bar:5, foo:10, zed:15
78 0 : func genAxis(keys []string) (axisRow string, labelRow string, pos map[string]int) {
79 0 : const minSpaceBetweenKeys = 4
80 0 : const minSpaceBetweenKeyLabels = 2
81 0 : var a, b strings.Builder
82 0 : pos = make(map[string]int)
83 0 : for i, k := range keys {
84 0 : if i > 0 {
85 0 : b.WriteString(strings.Repeat(" ", minSpaceBetweenKeyLabels))
86 0 : for b.Len() <= a.Len()+minSpaceBetweenKeys {
87 0 : b.WriteByte(' ')
88 0 : }
89 0 : for a.Len() < b.Len() {
90 0 : a.WriteByte('-')
91 0 : }
92 : }
93 0 : pos[k] = a.Len()
94 0 : a.WriteByte('|')
95 0 : b.WriteString(k)
96 : }
97 0 : return a.String(), b.String(), pos
98 : }
|