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  }