package runner import ( "errors" "fmt" "regexp" "strings" ) var ( variableNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`) // Matches $Name or ${Name}; only known names in the vars map are replaced. dollarVarPattern = regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)`) ) // ValidateVariableName checks that name is a valid run-scoped variable identifier. func ValidateVariableName(name string) error { trimmed := strings.TrimSpace(name) if trimmed == "" { return errors.New("variable name is required") } if trimmed != name { return errors.New("variable name must not have leading or trailing spaces") } if !variableNamePattern.MatchString(trimmed) { return fmt.Errorf("invalid variable name %q", name) } return nil } // ExpandDollarVars replaces $Name and ${Name} when Name exists in vars. // Unknown identifiers are left unchanged for the remote shell. func ExpandDollarVars(text string, vars map[string]string) string { if vars == nil || len(vars) == 0 || text == "" { return text } return dollarVarPattern.ReplaceAllStringFunc(text, func(match string) string { submatches := dollarVarPattern.FindStringSubmatch(match) if len(submatches) < 3 { return match } name := submatches[1] if name == "" { name = submatches[2] } value, ok := vars[name] if !ok { return match } return value }) }