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 }