github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/easy/yamlx/directive.go (about) 1 package yamlx 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 ) 8 9 const ( 10 directiveEnv = "@@env" 11 directiveVariable = "@@var" 12 directiveInclude = "@@incl" 13 directiveRefer = "@@ref" 14 directiveFunction = "@@fn" 15 ) 16 17 type directive struct { 18 name string 19 args map[string]any 20 } 21 22 func (d *directive) getRefPath(nodePfx []string) (path, origPath string, isTostr bool, modifier string) { 23 origPath = d.args["path"].(string) 24 isRelative, level, tail := isRelativeJSONPath(origPath) 25 path = tail 26 if isRelative { 27 prefix := strings.Join(nodePfx[:max(len(nodePfx)-level, 0)], ".") 28 if prefix != "" { 29 path = prefix + "." + tail 30 } 31 } 32 isTostr, modifier = hasTostrModifier(path) 33 if isTostr { 34 path = strings.TrimSuffix(path, modifier) 35 } 36 return path, origPath, isTostr, modifier 37 } 38 39 func parseDirective(str string) (d directive, ok bool, err error) { 40 if !strings.HasPrefix(str, "@@") { 41 return 42 } 43 switch { 44 case hasDirectivePrefix(str, directiveEnv): 45 d, err = parseEnvDirective(str) 46 case hasDirectivePrefix(str, directiveVariable): 47 d, err = parseVariableDirective(str) 48 case hasDirectivePrefix(str, directiveInclude): 49 d, err = parseIncludeDirective(str) 50 case hasDirectivePrefix(str, directiveRefer): 51 d, err = parseReferDirective(str) 52 case hasDirectivePrefix(str, directiveFunction): 53 d, err = parseFunctionDirective(str) 54 default: 55 err = fmt.Errorf("unrecognized directive: %q", str) 56 return directive{}, false, err 57 } 58 ok = err == nil 59 return 60 } 61 62 func hasDirectivePrefix(str, directive string) bool { 63 return strings.HasPrefix(str, directive+" ") 64 } 65 66 func parseEnvDirective(str string) (directive, error) { 67 str = strings.TrimPrefix(str, directiveEnv) 68 str = strings.TrimSpace(str) 69 70 var envNames []string 71 for _, x := range strings.Split(str, ",") { 72 if x = strings.TrimSpace(x); x != "" { 73 envNames = append(envNames, x) 74 } 75 } 76 if len(envNames) == 0 { 77 err := errors.New("missing environment variable name for @@env directive") 78 return directive{}, err 79 } 80 args := map[string]any{ 81 "envNames": envNames, 82 } 83 return directive{name: directiveEnv, args: args}, nil 84 } 85 86 func parseVariableDirective(str string) (directive, error) { 87 str = strings.TrimPrefix(str, directiveVariable) 88 str = strings.TrimSpace(str) 89 if str == "" { 90 err := errors.New("missing variable name for @@var directive") 91 return directive{}, err 92 } 93 args := map[string]any{ 94 "varName": str, 95 } 96 return directive{name: directiveVariable, args: args}, nil 97 } 98 99 func parseIncludeDirective(str string) (directive, error) { 100 str = strings.TrimPrefix(str, directiveInclude) 101 filename := strings.TrimSpace(str) 102 if filename == "" { 103 err := errors.New("missing filename for @@inc directive") 104 return directive{}, err 105 } 106 args := map[string]any{ 107 "filename": filename, 108 } 109 return directive{name: directiveInclude, args: args}, nil 110 } 111 112 func parseReferDirective(str string) (directive, error) { 113 str = strings.TrimPrefix(str, directiveRefer) 114 str = strings.TrimSpace(str) 115 if str == "" { 116 err := errors.New("missing JSON path for @@ref directive") 117 return directive{}, err 118 } 119 args := map[string]any{ 120 "path": str, 121 } 122 return directive{name: directiveRefer, args: args}, nil 123 } 124 125 func isRelativeJSONPath(path string) (ok bool, level int, tail string) { 126 if path[0] == '.' && 127 strings.HasPrefix(strings.TrimLeft(path, "."), "/") { 128 parts := strings.SplitN(path, "/", 2) 129 if len(parts) == 2 { 130 return true, len(parts[0]), parts[1] 131 } 132 } 133 return false, 0, path 134 } 135 136 func hasTostrModifier(path string) (ok bool, modifier string) { 137 pos := strings.Index(path, "@tostr") 138 if pos > 1 { 139 if path[pos-1] == '|' || path[pos-1] == '.' { 140 return true, path[pos-1:] 141 } 142 } 143 return false, "" 144 } 145 146 func parseFunctionDirective(str string) (directive, error) { 147 str = strings.TrimPrefix(str, directiveFunction) 148 str = strings.TrimSpace(str) 149 if str == "" { 150 err := errors.New("missing function expression for @@fn directive") 151 return directive{}, err 152 } 153 args := map[string]any{ 154 "expr": str, 155 } 156 return directive{name: directiveFunction, args: args}, nil 157 } 158 159 //nolint:unused 160 func trimParensAndSpace(str string) string { 161 if str != "" && str[0] == '(' && str[len(str)-1] == ')' { 162 str = str[1 : len(str)-1] 163 str = strings.TrimSpace(str) 164 } 165 return str 166 } 167 168 //nolint:unused 169 func trimQuotAndSpace(str string) string { 170 if str != "" { 171 if (str[0] == '"' && str[len(str)-1] == '"') || 172 (str[0] == '\'' && str[len(str)-1] == '\'') { 173 str = str[1 : len(str)-1] 174 str = strings.TrimSpace(str) 175 } 176 } 177 return str 178 } 179 180 func unescapeStrValue(str string) string { 181 bsCount := 0 182 isAtAt := false 183 if strings.HasPrefix(str, "\\") { 184 for i := 0; i < len(str); i++ { 185 if str[i] == '\\' { 186 bsCount++ 187 continue 188 } 189 isAtAt = strings.HasPrefix(str[i:], "@@") 190 break 191 } 192 } 193 if bsCount == 0 || !isAtAt { 194 return str 195 } 196 return str[:bsCount-1] + str[bsCount:] 197 }