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  }