github.com/viant/toolbox@v0.34.5/macro.go (about) 1 package toolbox 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "strings" 8 ) 9 10 //MacroEvaluator represents a macro expression evaluator, macros has the following format macro prefix [macro parameter] macro postfix 11 type MacroEvaluator struct { 12 Prefix, Postfix string 13 ValueProviderRegistry ValueProviderRegistry 14 } 15 16 //HasMacro checks if candidate has a macro fragment 17 func (e *MacroEvaluator) HasMacro(candidate string) bool { 18 prefix, postfix := e.Prefix, e.Postfix 19 prefixPosition := strings.Index(candidate, prefix) 20 if prefixPosition == -1 { 21 return false 22 } 23 postfixPosition := strings.Index(string(candidate[prefixPosition:]), postfix) 24 return postfixPosition != -1 25 } 26 27 func (e *MacroEvaluator) expandArguments(context Context, arguments *[]interface{}) error { 28 //expanded macros within the macro 29 for i, argument := range *arguments { 30 if IsString(argument) { 31 if argumentAsText, ok := argument.(string); ok { 32 if e.HasMacro(argumentAsText) { 33 expanded, err := e.Expand(context, argumentAsText) 34 if err != nil { 35 return fmt.Errorf("failed to expand argument: " + argumentAsText + " due to:\n\t" + err.Error()) 36 } 37 (*arguments)[i] = expanded 38 } 39 } 40 } 41 } 42 return nil 43 } 44 45 func (e *MacroEvaluator) decodeArguments(context Context, decodedArguments string, macro string) ([]interface{}, error) { 46 var arguments = make([]interface{}, 0) 47 if len(decodedArguments) > 0 { 48 decodedArguments = strings.Replace(decodedArguments, `\"`, `"`, len(decodedArguments)) 49 decoder := json.NewDecoder(strings.NewReader(decodedArguments)) 50 err := decoder.Decode(&arguments) 51 if err != nil && err != io.EOF { 52 return nil, fmt.Errorf("failed to process macro arguments: " + decodedArguments + " due to:\n\t" + err.Error()) 53 } 54 err = e.expandArguments(context, &arguments) 55 if err != nil { 56 return nil, err 57 } 58 } 59 return arguments, nil 60 } 61 62 func (e *MacroEvaluator) extractMacro(input string) (success bool, macro, macroName, macroArguments string) { 63 prefix, postfix := e.Prefix, e.Postfix 64 var isInQuotes, argumentCount, previousChar, expectArguements, argumentStartPosition, argumentEndPosition = false, 0, "", false, 0, 0 65 prefixPosition := strings.Index(input, prefix) 66 if prefixPosition == -1 { 67 return false, "", "", "" 68 } 69 for i := prefixPosition + len(prefix); i < len(input); i++ { 70 aChar := input[i : i+1] 71 if i > 0 { 72 previousChar = input[i-1 : i] 73 } 74 75 if strings.ContainsAny(aChar, " \b\n[") { 76 expectArguements = true 77 } 78 if aChar == "\"" && previousChar != "\\" { 79 isInQuotes = !isInQuotes 80 } 81 if !isInQuotes && aChar == "[" && previousChar != "\\" { 82 if argumentCount == 0 { 83 argumentStartPosition = i 84 } 85 argumentCount++ 86 } 87 if !isInQuotes && aChar == "]" && previousChar != "\\" { 88 argumentEndPosition = i 89 argumentCount-- 90 } 91 macro = macro + aChar 92 if argumentCount == 0 { 93 if aChar == postfix { 94 break 95 } 96 if !expectArguements { 97 macroName = macroName + aChar 98 } 99 } 100 } 101 if argumentStartPosition > 0 && argumentStartPosition < argumentEndPosition { 102 macroArguments = input[argumentStartPosition : argumentEndPosition+1] 103 } 104 105 return true, prefix + macro, macroName, macroArguments 106 } 107 108 //Expand expands passed in input, it returns expanded value of any type or error 109 func (e *MacroEvaluator) Expand(context Context, input string) (interface{}, error) { 110 success, macro, macroName, macroArguments := e.extractMacro(input) 111 if !success { 112 return input, nil 113 } 114 valueProviderRegistry := e.ValueProviderRegistry 115 if !valueProviderRegistry.Contains(macroName) { 116 return nil, fmt.Errorf("failed to lookup macro: '%v' while processing: %v", macroName, input) 117 } 118 arguments, err := e.decodeArguments(context, macroArguments, macro) 119 if err != nil { 120 return nil, fmt.Errorf("failed expand macro: %v due to %v", macro, err.Error()) 121 } 122 valueProvider := valueProviderRegistry.Get(macroName) 123 value, err := valueProvider.Get(context, arguments...) 124 if err != nil { 125 return nil, err 126 } 127 if len(macro) == len(input) { 128 return value, nil 129 } 130 expandedMacro := fmt.Sprintf("%v", value) 131 result := strings.Replace(input, macro, expandedMacro, 1) 132 if e.HasMacro(result) { 133 return e.Expand(context, result) 134 } 135 return result, nil 136 } 137 138 //NewMacroEvaluator returns a new macro evaluator 139 func NewMacroEvaluator(prefix, postfix string, registry ValueProviderRegistry) *MacroEvaluator { 140 return &MacroEvaluator{ 141 Prefix: prefix, 142 Postfix: postfix, 143 ValueProviderRegistry: registry, 144 } 145 } 146 147 //ExpandParameters expands passed in parameters as strings 148 func ExpandParameters(macroEvaluator *MacroEvaluator, parameters map[string]string) error { 149 for key := range parameters { 150 value := parameters[key] 151 if macroEvaluator.HasMacro(value) { 152 textValue, err := macroEvaluator.Expand(nil, AsString(value)) 153 if err != nil { 154 return err 155 } 156 parameters[key] = AsString(textValue) 157 } 158 } 159 return nil 160 } 161 162 //ExpandValue expands passed in value, it returns expanded string value or error 163 func ExpandValue(macroEvaluator *MacroEvaluator, value string) (string, error) { 164 if macroEvaluator.HasMacro(value) { 165 expanded, err := macroEvaluator.Expand(nil, value) 166 if err != nil { 167 return "", err 168 } 169 return AsString(expanded), nil 170 } 171 return value, nil 172 }