github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/template_vars/vars.go (about)

     1  // Copyright 2023 The GitBundle Inc. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // Use of this source code is governed by a MIT-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package vars
     7  
     8  import (
     9  	"fmt"
    10  	"strings"
    11  	"unicode"
    12  	"unicode/utf8"
    13  )
    14  
    15  // ErrWrongSyntax represents a wrong syntax with a template
    16  type ErrWrongSyntax struct {
    17  	Template string
    18  }
    19  
    20  func (err ErrWrongSyntax) Error() string {
    21  	return fmt.Sprintf("wrong syntax found in %s", err.Template)
    22  }
    23  
    24  // ErrVarMissing represents an error that no matched variable
    25  type ErrVarMissing struct {
    26  	Template string
    27  	Var      string
    28  }
    29  
    30  func (err ErrVarMissing) Error() string {
    31  	return fmt.Sprintf("the variable %s is missing for %s", err.Var, err.Template)
    32  }
    33  
    34  // Expand replaces all variables like {var} by `vars` map, it always returns the expanded string regardless of errors
    35  // if error occurs, the error part doesn't change and is returned as it is.
    36  func Expand(template string, vars map[string]string) (string, error) {
    37  	// in the future, if necessary, we can introduce some escape-char,
    38  	// for example: it will use `#' as a reversed char, templates will use `{#{}` to do escape and output char '{'.
    39  	var buf strings.Builder
    40  	var err error
    41  
    42  	posBegin := 0
    43  	strLen := len(template)
    44  	for posBegin < strLen {
    45  		// find the next `{`
    46  		pos := strings.IndexByte(template[posBegin:], '{')
    47  		if pos == -1 {
    48  			buf.WriteString(template[posBegin:])
    49  			break
    50  		}
    51  
    52  		// copy texts between vars
    53  		buf.WriteString(template[posBegin : posBegin+pos])
    54  
    55  		// find the var between `{` and `}`/end
    56  		posBegin += pos
    57  		posEnd := posBegin + 1
    58  		for posEnd < strLen {
    59  			if template[posEnd] == '}' {
    60  				posEnd++
    61  				break
    62  			} // in the future, if we need to support escape chars, we can do: if (isEscapeChar) { posEnd+=2 }
    63  			posEnd++
    64  		}
    65  
    66  		// the var part, it can be "{", "{}", "{..." or or "{...}"
    67  		part := template[posBegin:posEnd]
    68  		posBegin = posEnd
    69  		if part == "{}" || part[len(part)-1] != '}' {
    70  			// treat "{}" or "{..." as error
    71  			err = ErrWrongSyntax{Template: template}
    72  			buf.WriteString(part)
    73  		} else {
    74  			// now we get a valid key "{...}"
    75  			key := part[1 : len(part)-1]
    76  			keyFirst, _ := utf8.DecodeRuneInString(key)
    77  			if unicode.IsSpace(keyFirst) || unicode.IsPunct(keyFirst) || unicode.IsControl(keyFirst) {
    78  				// the if key doesn't start with a letter, then we do not treat it as a var now
    79  				buf.WriteString(part)
    80  			} else {
    81  				// look up in the map
    82  				if val, ok := vars[key]; ok {
    83  					buf.WriteString(val)
    84  				} else {
    85  					// write the non-existing var as it is
    86  					buf.WriteString(part)
    87  					err = ErrVarMissing{Template: template, Var: key}
    88  				}
    89  			}
    90  		}
    91  	}
    92  
    93  	return buf.String(), err
    94  }