code.gitea.io/gitea@v1.19.3/modules/templates/vars/vars.go (about)

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