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  }