github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/amino/libs/press/press.go (about) 1 package press 2 3 import ( 4 "fmt" 5 "math/rand" 6 "strings" 7 8 "github.com/gnolang/gno/tm2/pkg/amino/libs/detrand" 9 ) 10 11 type line struct { 12 indentStr string // the fully expanded indentation string 13 value string // the original contents of the line 14 } 15 16 func newLine(indentStr string, value string) line { 17 return line{indentStr, value} 18 } 19 20 func (l line) String() string { 21 return l.indentStr + l.value 22 } 23 24 // Press is a tool for printing code. 25 // Press is not concurrency safe. 26 type Press struct { 27 rnd *rand.Rand // for generating a random variable names. 28 indentPrefix string // current indent prefix. 29 indentDelim string // a tab or spaces, whatever. 30 newlineStr string // should probably just remain "\n". 31 lines []line // accumulated lines from printing. 32 } 33 34 func NewPress() *Press { 35 return &Press{ 36 rnd: rand.New(rand.NewSource(0)), //nolint:gosec 37 indentPrefix: "", 38 indentDelim: "\t", 39 newlineStr: "\n", 40 lines: nil, 41 } 42 } 43 44 func (p *Press) SetIndentDelim(s string) *Press { 45 p.indentDelim = s 46 return p 47 } 48 49 func (p *Press) SetNewlineStr(s string) *Press { 50 p.newlineStr = s 51 return p 52 } 53 54 // Main function for printing something on the press. 55 func (p *Press) P(s string, args ...interface{}) *Press { 56 var l *line 57 if len(p.lines) == 0 { 58 // Make a new line. 59 p.lines = []line{newLine(p.indentPrefix, "")} 60 } 61 // Get ref to last line. 62 l = &(p.lines[len(p.lines)-1]) 63 l.value += fmt.Sprintf(s, args...) 64 return p 65 } 66 67 // Appends a new line. 68 // It is also possible to print newline characters directly, 69 // but Press doesn't treat them as newlines for the sake of indentation. 70 func (p *Press) Ln() *Press { 71 p.lines = append(p.lines, newLine(p.indentPrefix, "")) 72 return p 73 } 74 75 // Convenience for P() followed by Nl(). 76 func (p *Press) Pl(s string, args ...interface{}) *Press { 77 return p.P(s, args...).Ln() 78 } 79 80 // auto-indents p2, appends concents to p. 81 // Panics if the last call wasn't Pl() or Ln(). 82 // Regardless of whether Pl or Ln is called on p2, 83 // the indented lines terminate with newlineDelim before 84 // the next unindented line. 85 func (p *Press) I(block func(p2 *Press)) *Press { 86 if len(p.lines) > 0 { 87 lastLine := p.lines[len(p.lines)-1] 88 if lastLine.value != "" { 89 panic("cannot indent after nonempty line") 90 } 91 if lastLine.indentStr != p.indentPrefix { 92 panic("unexpected indent string in last line") 93 } 94 // remove last empty line 95 p.lines = p.lines[:len(p.lines)-1] 96 } 97 p2 := p.SubPress() 98 p2.indentPrefix = p.indentPrefix + p.indentDelim 99 block(p2) 100 ilines := p2.Lines() 101 // remove last empty line from p2 102 ilines = withoutFinalNewline(ilines) 103 p.lines = append(p.lines, ilines...) 104 // (re)introduce last line with original indent 105 p.lines = append(p.lines, newLine(p.indentPrefix, "")) 106 return p 107 } 108 109 // Prints the final representation of the contents. 110 func (p *Press) Print() string { 111 lines := []string{} 112 for _, line := range p.lines { 113 lines = append(lines, line.String()) 114 } 115 return strings.Join(lines, p.newlineStr) 116 } 117 118 // Returns the lines. 119 // This may be useful for adding additional indentation to each line for code blocks. 120 func (p *Press) Lines() (lines []line) { 121 return p.lines 122 } 123 124 // Convenience 125 func (p *Press) RandID(prefix string) string { 126 return prefix + "_" + p.RandStr(8) 127 } 128 129 // Convenience 130 func (p *Press) RandStr(length int) string { 131 const strChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" // 62 characters 132 chars := []byte{} 133 MAIN_LOOP: 134 for { 135 val := p.rnd.Int63() 136 for i := 0; i < 10; i++ { 137 v := int(val & 0x3f) // rightmost 6 bits 138 if v >= 62 { // only 62 characters in strChars 139 val >>= 6 140 continue 141 } else { 142 chars = append(chars, strChars[v]) 143 if len(chars) == length { 144 break MAIN_LOOP 145 } 146 val >>= 6 147 } 148 } 149 } 150 return string(chars) 151 } 152 153 // SubPress creates a blank Press suitable for inlining code. 154 // It starts with no indentation, zero lines, 155 // a derived rand from the original, but the same indent and nl strings.. 156 func (p *Press) SubPress() *Press { 157 p2 := NewPress() 158 p2.rnd = detrand.DeriveRand(p.rnd) 159 p2.indentPrefix = "" 160 p2.indentDelim = p.indentDelim 161 p2.newlineStr = p.newlineStr 162 p2.lines = nil 163 return p2 164 } 165 166 // ref: the reference to the value being encoded. 167 type EncoderPressFunc func(p *Press, ref string) (code string) 168 169 // ---------------------------------------- 170 171 // If the final line is a line with no value, remove it 172 func withoutFinalNewline(lines []line) []line { 173 if len(lines) > 0 && lines[len(lines)-1].value == "" { 174 return lines[:len(lines)-1] 175 } else { 176 return lines 177 } 178 }