github.com/coveo/gotemplate@v2.7.7+incompatible/template/func_table.go (about)

     1  package template
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"sort"
     7  	"strings"
     8  	"text/template"
     9  
    10  	"github.com/coveo/gotemplate/collections"
    11  	"github.com/fatih/color"
    12  )
    13  
    14  // FuncInfo contains the information related to a function made available to go template.
    15  type FuncInfo struct {
    16  	name        string
    17  	function    interface{}
    18  	group       string
    19  	aliases     []string
    20  	arguments   []string
    21  	description string
    22  	in, out     string
    23  	alias       *FuncInfo
    24  }
    25  
    26  // Aliases returns the aliases related to the entry.
    27  func (fi FuncInfo) Aliases() []string { return ifUndef(&fi, fi.alias).(*FuncInfo).aliases }
    28  
    29  // Arguments returns the list of arguments that must be supplied to the function.
    30  func (fi FuncInfo) Arguments() string { return fi.getArguments(false) }
    31  
    32  // Description returns the description related to the entry.
    33  func (fi FuncInfo) Description() string { return ifUndef(&fi, fi.alias).(*FuncInfo).description }
    34  
    35  // Group returns the group name associated to the entry.
    36  func (fi FuncInfo) Group() string { return ifUndef(&fi, fi.alias).(*FuncInfo).group }
    37  
    38  // Name returns the name related to the entry.
    39  func (fi FuncInfo) Name() string { return fi.name }
    40  
    41  // Signature returns the function signature
    42  func (fi FuncInfo) Signature() string { return fi.getSignature(false) }
    43  
    44  // IsAlias indicates if the current function is an alias.
    45  func (fi FuncInfo) IsAlias() bool { return fi.alias != nil }
    46  
    47  // String returns the presentation of the FuncInfo entry.
    48  func (fi FuncInfo) String() (result string) {
    49  	signature := fi.Signature()
    50  	if fi.alias != nil {
    51  		fi = *fi.alias
    52  	}
    53  
    54  	if fi.group != "" {
    55  		result += fmt.Sprintf(color.GreenString("Group: %s\n"), fi.group)
    56  	}
    57  	if fi.description != "" {
    58  		result += fmt.Sprintf(color.GreenString("%s\n"), fi.description)
    59  	}
    60  	return result + signature
    61  }
    62  
    63  // Signature returns the function signature
    64  func (fi FuncInfo) getSignature(isMethod bool) string {
    65  	col := color.HiBlueString
    66  	name := fi.name
    67  	if fi.alias != nil {
    68  		fi = *fi.alias
    69  		col = color.HiBlackString
    70  	}
    71  
    72  	return fmt.Sprintf("%s(%s) %s", col(name), fi.getArguments(isMethod), color.HiBlackString(fi.Result()))
    73  }
    74  
    75  func (fi FuncInfo) getArguments(isMethod bool) string {
    76  	if fi.in != "" || fi.function == nil {
    77  		return fi.in
    78  	}
    79  
    80  	if fi.alias != nil {
    81  		fi = *fi.alias
    82  	}
    83  
    84  	signature := reflect.ValueOf(fi.function).Type()
    85  	var parameters []string
    86  	for i := iif(isMethod, 1, 0).(int); i < signature.NumIn(); i++ {
    87  		arg := strings.Replace(fmt.Sprint(signature.In(i)), "interface {}", "interface{}", -1)
    88  		arg = strings.Replace(arg, "collections.", "", -1)
    89  		var argName string
    90  		if i < len(fi.arguments) {
    91  			argName = fi.arguments[i]
    92  		} else {
    93  			if signature.IsVariadic() && i == signature.NumIn()-1 {
    94  				argName = "args"
    95  			} else {
    96  				argName = fmt.Sprintf("arg%d", i+1)
    97  			}
    98  		}
    99  		if signature.IsVariadic() && i == signature.NumIn()-1 {
   100  			arg = "..." + arg[2:]
   101  		}
   102  		if isMethod {
   103  			parameters = append(parameters, color.CyanString(arg))
   104  		} else {
   105  			parameters = append(parameters, fmt.Sprintf("%s %s", argName, color.CyanString(arg)))
   106  		}
   107  	}
   108  	return strings.Join(parameters, ", ")
   109  }
   110  
   111  // Result returns the list of output produced by the function.
   112  func (fi FuncInfo) Result() string {
   113  	if fi.out != "" || fi.function == nil {
   114  		return fi.out
   115  	}
   116  	signature := reflect.ValueOf(fi.function).Type()
   117  	var outputs []string
   118  	for i := 0; i < signature.NumOut(); i++ {
   119  		r := strings.Replace(fmt.Sprint(signature.Out(i)), "interface {}", "interface{}", -1)
   120  		r = strings.Replace(r, "collections.", "", -1)
   121  		outputs = append(outputs, r)
   122  	}
   123  	return strings.Join(outputs, ", ")
   124  }
   125  
   126  type funcTableMap map[string]FuncInfo
   127  
   128  func (ftm funcTableMap) convert() template.FuncMap {
   129  	result := collections.CreateDictionary(len(ftm))
   130  	for key, val := range ftm {
   131  		if val.function == nil {
   132  			continue
   133  		}
   134  		result.Set(key, val.function)
   135  	}
   136  	return result.AsMap()
   137  }
   138  
   139  // FuncOptionsSet defines categories that could be used to define elaborated help context when adding functions to go template.
   140  type FuncOptionsSet int8
   141  
   142  const (
   143  	// FuncHelp is used to associate help to functions added to go templates.
   144  	FuncHelp FuncOptionsSet = iota
   145  	// FuncArgs is used to associate arguments name to functions added to go templates.
   146  	FuncArgs
   147  	// FuncAliases is used to associate aliases to functions added to go templates.
   148  	FuncAliases
   149  	// FuncGroup is used to associate a group to functions added to go templates.
   150  	FuncGroup
   151  )
   152  
   153  // FuncOptions is a map of FuncOptionsSet that is used to associates help, aliases, arguments and groups to functions added to go template.
   154  type FuncOptions map[FuncOptionsSet]interface{}
   155  
   156  type aliases = map[string][]string
   157  type arguments = map[string][]string
   158  type descriptions = map[string]string
   159  type dictionary = map[string]interface{}
   160  type groups = map[string]string
   161  
   162  // AddFunctions add functions to the template, but keep a detailled definition of the function added for helping purpose
   163  func (t *Template) AddFunctions(funcs dictionary, group string, options FuncOptions) *Template {
   164  	ft := make(funcTableMap, len(funcs))
   165  	help := defval(options[FuncHelp], descriptions{}).(descriptions)
   166  	aliases := defval(options[FuncAliases], aliases{}).(aliases)
   167  	arguments := defval(options[FuncArgs], arguments{}).(arguments)
   168  	groups := defval(options[FuncGroup], groups{}).(groups)
   169  	for key, val := range funcs {
   170  		ft[key] = FuncInfo{
   171  			function:    val,
   172  			group:       defval(group, groups[key]).(string),
   173  			aliases:     aliases[key],
   174  			arguments:   arguments[key],
   175  			description: help[key],
   176  		}
   177  	}
   178  
   179  	return t.addFunctions(ft)
   180  }
   181  
   182  func (t *Template) addFunctions(funcMap funcTableMap) *Template {
   183  	templateMutex.Lock()
   184  	defer templateMutex.Unlock()
   185  
   186  	if t.functions == nil {
   187  		t.functions = make(funcTableMap)
   188  	}
   189  	for key, value := range funcMap {
   190  		value.name = key
   191  		t.functions[key] = value
   192  		for i := range value.aliases {
   193  			// It is necessary here to take a distinct copy of the variable since
   194  			// val will change over the iteration and we cannot rely on its address
   195  			copy := value
   196  			funcMap[value.aliases[i]] = FuncInfo{alias: &copy, function: value.function, name: value.aliases[i]}
   197  			t.functions[value.aliases[i]] = funcMap[value.aliases[i]]
   198  		}
   199  	}
   200  	t.Funcs(funcMap.convert())
   201  	return t
   202  }
   203  
   204  // List the available functions in the template
   205  func (t Template) getFunctionsInternal(original, alias bool) (result []string) {
   206  	for name := range t.functions {
   207  		fi := t.functions[name]
   208  		if original && fi.alias == nil {
   209  			result = append(result, name)
   210  		}
   211  		if alias && fi.alias != nil {
   212  			result = append(result, name)
   213  		}
   214  	}
   215  	sort.Strings(result)
   216  	return
   217  }
   218  
   219  func (t Template) getAliases() []string      { return t.getFunctionsInternal(false, true) }
   220  func (t Template) getAllFunctions() []string { return t.getFunctionsInternal(true, true) }
   221  func (t Template) getFunctions() []string    { return t.getFunctionsInternal(true, false) }
   222  
   223  // List the available functions in the template
   224  func (t Template) getFunction(name string) FuncInfo {
   225  	return t.functions[name]
   226  }