...

Source file src/github.com/noirbizarre/gonja/builtins/filters.go

Documentation: github.com/noirbizarre/gonja/builtins

     1  package builtins
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math"
     7  	"math/rand"
     8  	"net/url"
     9  	"regexp"
    10  	"sort"
    11  	"strings"
    12  	"time"
    13  	"unicode"
    14  	"unicode/utf8"
    15  
    16  	"github.com/pkg/errors"
    17  
    18  	"github.com/noirbizarre/gonja/exec"
    19  	u "github.com/noirbizarre/gonja/utils"
    20  )
    21  
    22  func init() {
    23  	rand.Seed(time.Now().Unix())
    24  }
    25  
    26  // Filters export all builtin filters
    27  var Filters = exec.FilterSet{
    28  	"abs":            filterAbs,
    29  	"attr":           filterAttr,
    30  	"batch":          filterBatch,
    31  	"capitalize":     filterCapitalize,
    32  	"center":         filterCenter,
    33  	"d":              filterDefault,
    34  	"default":        filterDefault,
    35  	"dictsort":       filterDictSort,
    36  	"e":              filterEscape,
    37  	"escape":         filterEscape,
    38  	"filesizeformat": filterFileSize,
    39  	"first":          filterFirst,
    40  	"float":          filterFloat,
    41  	"forceescape":    filterForceEscape,
    42  	"format":         filterFormat,
    43  	"groupby":        filterGroupBy,
    44  	"indent":         filterIndent,
    45  	"int":            filterInteger,
    46  	"join":           filterJoin,
    47  	"last":           filterLast,
    48  	"length":         filterLength,
    49  	"list":           filterList,
    50  	"lower":          filterLower,
    51  	"map":            filterMap,
    52  	"max":            filterMax,
    53  	"min":            filterMin,
    54  	"pprint":         filterPPrint,
    55  	"random":         filterRandom,
    56  	"reject":         filterReject,
    57  	"rejectattr":     filterRejectAttr,
    58  	"replace":        filterReplace,
    59  	"reverse":        filterReverse,
    60  	"round":          filterRound,
    61  	"safe":           filterSafe,
    62  	"select":         filterSelect,
    63  	"selectattr":     filterSelectAttr,
    64  	"slice":          filterSlice,
    65  	"sort":           filterSort,
    66  	"string":         filterString,
    67  	"striptags":      filterStriptags,
    68  	"sum":            filterSum,
    69  	"title":          filterTitle,
    70  	"tojson":         filterToJSON,
    71  	"trim":           filterTrim,
    72  	"truncate":       filterTruncate,
    73  	"unique":         filterUnique,
    74  	"upper":          filterUpper,
    75  	"urlencode":      filterUrlencode,
    76  	"urlize":         filterUrlize,
    77  	"wordcount":      filterWordcount,
    78  	"wordwrap":       filterWordwrap,
    79  	"xmlattr":        filterXMLAttr,
    80  }
    81  
    82  func filterAbs(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
    83  	if p := params.ExpectNothing(); p.IsError() {
    84  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'abs'"))
    85  	}
    86  	if in.IsInteger() {
    87  		asInt := in.Integer()
    88  		if asInt < 0 {
    89  			return exec.AsValue(-asInt)
    90  		}
    91  		return in
    92  	} else if in.IsFloat() {
    93  		return exec.AsValue(math.Abs(in.Float()))
    94  	}
    95  	return exec.AsValue(math.Abs(in.Float())) // nothing to do here, just to keep track of the safe application
    96  }
    97  
    98  func filterAttr(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
    99  	p := params.ExpectArgs(1)
   100  	if p.IsError() {
   101  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'attr'"))
   102  	}
   103  	attr := p.First().String()
   104  	value, _ := in.Getattr(attr)
   105  	return value
   106  }
   107  
   108  func filterBatch(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   109  	p := params.Expect(1, []*exec.KwArg{{"fill_with", nil}})
   110  	if p.IsError() {
   111  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'batch'"))
   112  	}
   113  	size := p.First().Integer()
   114  	out := []*exec.Value{}
   115  	var row []*exec.Value
   116  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   117  		if math.Mod(float64(idx), float64(size)) == 0 {
   118  			if row != nil {
   119  				out = append(out, exec.AsValue(row))
   120  			}
   121  			row = []*exec.Value{}
   122  		}
   123  		row = append(row, key)
   124  		return true
   125  	}, func() {})
   126  	if len(row) > 0 {
   127  		fillWith := p.KwArgs["fill_with"]
   128  		if !fillWith.IsNil() {
   129  			for len(row) < size {
   130  				row = append(row, fillWith)
   131  			}
   132  		}
   133  		out = append(out, exec.AsValue(row))
   134  	}
   135  	return exec.AsValue(out)
   136  }
   137  
   138  func filterCapitalize(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   139  	if p := params.ExpectNothing(); p.IsError() {
   140  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'capitalize'"))
   141  	}
   142  	if in.Len() <= 0 {
   143  		return exec.AsValue("")
   144  	}
   145  	t := in.String()
   146  	r, size := utf8.DecodeRuneInString(t)
   147  	return exec.AsValue(strings.ToUpper(string(r)) + strings.ToLower(t[size:]))
   148  }
   149  
   150  func filterCenter(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   151  	p := params.ExpectArgs(1)
   152  	if p.IsError() {
   153  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'center'"))
   154  	}
   155  	width := p.First().Integer()
   156  	slen := in.Len()
   157  	if width <= slen {
   158  		return in
   159  	}
   160  
   161  	spaces := width - slen
   162  	left := spaces/2 + spaces%2
   163  	right := spaces / 2
   164  
   165  	return exec.AsValue(fmt.Sprintf("%s%s%s", strings.Repeat(" ", left),
   166  		in.String(), strings.Repeat(" ", right)))
   167  }
   168  
   169  func filterDefault(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   170  	p := params.Expect(1, []*exec.KwArg{{"boolean", false}})
   171  	if p.IsError() {
   172  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'default'"))
   173  	}
   174  	defaultVal := p.First()
   175  	falsy := p.KwArgs["boolean"]
   176  	if falsy.Bool() && (in.IsError() || !in.IsTrue()) {
   177  		return defaultVal
   178  	} else if in.IsError() || in.IsNil() {
   179  		return defaultVal
   180  	}
   181  	return in
   182  }
   183  
   184  func sortByKey(in *exec.Value, caseSensitive bool, reverse bool) [][2]*exec.Value {
   185  	out := [][2]*exec.Value{}
   186  	in.IterateOrder(func(idx, count int, key, value *exec.Value) bool {
   187  		out = append(out, [2]*exec.Value{key, value})
   188  		return true
   189  	}, func() {}, reverse, true, caseSensitive)
   190  	return out
   191  }
   192  
   193  func sortByValue(in *exec.Value, caseSensitive, reverse bool) [][2]*exec.Value {
   194  	out := [][2]*exec.Value{}
   195  	items := in.Items()
   196  	var sorter func(i, j int) bool
   197  	switch {
   198  	case caseSensitive && reverse:
   199  		sorter = func(i, j int) bool {
   200  			return items[i].Value.String() > items[j].Value.String()
   201  		}
   202  	case caseSensitive && !reverse:
   203  		sorter = func(i, j int) bool {
   204  			return items[i].Value.String() < items[j].Value.String()
   205  		}
   206  	case !caseSensitive && reverse:
   207  		sorter = func(i, j int) bool {
   208  			return strings.ToLower(items[i].Value.String()) > strings.ToLower(items[j].Value.String())
   209  		}
   210  	case !caseSensitive && !reverse:
   211  		sorter = func(i, j int) bool {
   212  			return strings.ToLower(items[i].Value.String()) < strings.ToLower(items[j].Value.String())
   213  		}
   214  	}
   215  	sort.Slice(items, sorter)
   216  	for _, item := range items {
   217  		out = append(out, [2]*exec.Value{item.Key, item.Value})
   218  	}
   219  	return out
   220  }
   221  
   222  func filterDictSort(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   223  	p := params.Expect(0, []*exec.KwArg{
   224  		{"case_sensitive", false},
   225  		{"by", "key"},
   226  		{"reverse", false},
   227  	})
   228  	if p.IsError() {
   229  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'dictsort'"))
   230  	}
   231  
   232  	caseSensitive := p.KwArgs["case_sensitive"].Bool()
   233  	by := p.KwArgs["by"].String()
   234  	reverse := p.KwArgs["reverse"].Bool()
   235  
   236  	switch by {
   237  	case "key":
   238  		return exec.AsValue(sortByKey(in, caseSensitive, reverse))
   239  	case "value":
   240  		return exec.AsValue(sortByValue(in, caseSensitive, reverse))
   241  	default:
   242  		return exec.AsValue(errors.New(`by should be either 'key' or 'value`))
   243  	}
   244  }
   245  
   246  func filterEscape(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   247  	if p := params.ExpectNothing(); p.IsError() {
   248  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'escape'"))
   249  	}
   250  	if in.Safe {
   251  		return in
   252  	}
   253  	return exec.AsSafeValue(in.Escaped())
   254  }
   255  
   256  var (
   257  	bytesPrefixes  = []string{"kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
   258  	binaryPrefixes = []string{"KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
   259  )
   260  
   261  func filterFileSize(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   262  	p := params.Expect(0, []*exec.KwArg{{"binary", false}})
   263  	if p.IsError() {
   264  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'filesizeformat'"))
   265  	}
   266  	bytes := in.Float()
   267  	binary := p.KwArgs["binary"].Bool()
   268  	var base float64
   269  	var prefixes []string
   270  	if binary {
   271  		base = 1024.0
   272  		prefixes = binaryPrefixes
   273  	} else {
   274  		base = 1000.0
   275  		prefixes = bytesPrefixes
   276  	}
   277  	if bytes == 1.0 {
   278  		return exec.AsValue("1 Byte")
   279  	} else if bytes < base {
   280  		return exec.AsValue(fmt.Sprintf("%.0f Bytes", bytes))
   281  	} else {
   282  		var i int
   283  		var unit float64
   284  		var prefix string
   285  		for i, prefix = range prefixes {
   286  			unit = math.Pow(base, float64(i+2))
   287  			if bytes < unit {
   288  				return exec.AsValue(fmt.Sprintf("%.1f %s", (base * bytes / unit), prefix))
   289  			}
   290  		}
   291  		return exec.AsValue(fmt.Sprintf("%.1f %s", (base * bytes / unit), prefix))
   292  	}
   293  }
   294  
   295  func filterFirst(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   296  	if p := params.ExpectNothing(); p.IsError() {
   297  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'first'"))
   298  	}
   299  	if in.CanSlice() && in.Len() > 0 {
   300  		return in.Index(0)
   301  	}
   302  	return exec.AsValue("")
   303  }
   304  
   305  func filterFloat(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   306  	if p := params.ExpectNothing(); p.IsError() {
   307  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'float'"))
   308  	}
   309  	return exec.AsValue(in.Float())
   310  }
   311  
   312  func filterForceEscape(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   313  	if p := params.ExpectNothing(); p.IsError() {
   314  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'forceescape'"))
   315  	}
   316  	return exec.AsSafeValue(in.Escaped())
   317  }
   318  
   319  func filterFormat(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   320  	args := []interface{}{}
   321  	for _, arg := range params.Args {
   322  		args = append(args, arg.Interface())
   323  	}
   324  	return exec.AsValue(fmt.Sprintf(in.String(), args...))
   325  }
   326  
   327  func filterGroupBy(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   328  	p := params.ExpectArgs(1)
   329  	if p.IsError() {
   330  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'groupby"))
   331  	}
   332  	field := p.First().String()
   333  	groups := map[interface{}][]*exec.Value{}
   334  	groupers := []interface{}{}
   335  
   336  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   337  		attr, found := key.Get(field)
   338  		if !found {
   339  			return true
   340  		}
   341  		lst, exists := groups[attr.Interface()]
   342  		if !exists {
   343  			lst = []*exec.Value{}
   344  			groupers = append(groupers, attr.Interface())
   345  		}
   346  		lst = append(lst, key)
   347  		groups[attr.Interface()] = lst
   348  		return true
   349  	}, func() {})
   350  
   351  	out := []map[string]*exec.Value{}
   352  	for _, grouper := range groupers {
   353  		out = append(out, map[string]*exec.Value{
   354  			"grouper": exec.AsValue(grouper),
   355  			"list":    exec.AsValue(groups[grouper]),
   356  		})
   357  	}
   358  	return exec.AsValue(out)
   359  }
   360  
   361  func filterIndent(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   362  	p := params.Expect(0, []*exec.KwArg{
   363  		{"width", 4},
   364  		{"first", false},
   365  		{"blank", false},
   366  	})
   367  	if p.IsError() {
   368  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'indent'"))
   369  	}
   370  	width := p.KwArgs["width"].Integer()
   371  	first := p.KwArgs["first"].Bool()
   372  	blank := p.KwArgs["blank"].Bool()
   373  	indent := strings.Repeat(" ", width)
   374  	lines := strings.Split(in.String(), "\n")
   375  	// start := 1
   376  	// if first {start = 0}
   377  	var out strings.Builder
   378  	for idx, line := range lines {
   379  		if line == "" && !blank {
   380  			out.WriteByte('\n')
   381  			continue
   382  		}
   383  		if idx > 0 || first {
   384  			out.WriteString(indent)
   385  		}
   386  		out.WriteString(line)
   387  		out.WriteByte('\n')
   388  	}
   389  	return exec.AsValue(out.String())
   390  }
   391  
   392  func filterInteger(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   393  	if p := params.ExpectNothing(); p.IsError() {
   394  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'int'"))
   395  	}
   396  	return exec.AsValue(in.Integer())
   397  }
   398  
   399  func filterJoin(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   400  	p := params.Expect(0, []*exec.KwArg{
   401  		{"d", ""},
   402  		{"attribute", nil},
   403  	})
   404  	if p.IsError() {
   405  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'join'"))
   406  	}
   407  	if !in.CanSlice() {
   408  		return in
   409  	}
   410  	sep := p.KwArgs["d"].String()
   411  	sl := make([]string, 0, in.Len())
   412  	for i := 0; i < in.Len(); i++ {
   413  		sl = append(sl, in.Index(i).String())
   414  	}
   415  	return exec.AsValue(strings.Join(sl, sep))
   416  }
   417  
   418  func filterLast(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   419  	if p := params.ExpectNothing(); p.IsError() {
   420  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'last'"))
   421  	}
   422  	if in.CanSlice() && in.Len() > 0 {
   423  		return in.Index(in.Len() - 1)
   424  	}
   425  	return exec.AsValue("")
   426  }
   427  
   428  func filterLength(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   429  	if p := params.ExpectNothing(); p.IsError() {
   430  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'length'"))
   431  	}
   432  	return exec.AsValue(in.Len())
   433  }
   434  
   435  func filterList(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   436  	if p := params.ExpectNothing(); p.IsError() {
   437  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'list'"))
   438  	}
   439  	if in.IsString() {
   440  		out := []string{}
   441  		for _, r := range in.String() {
   442  			out = append(out, string(r))
   443  		}
   444  		return exec.AsValue(out)
   445  	}
   446  	out := []*exec.Value{}
   447  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   448  		out = append(out, key)
   449  		return true
   450  	}, func() {})
   451  	return exec.AsValue(out)
   452  }
   453  
   454  func filterLower(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   455  	if p := params.ExpectNothing(); p.IsError() {
   456  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'lower'"))
   457  	}
   458  	return exec.AsValue(strings.ToLower(in.String()))
   459  }
   460  
   461  func filterMap(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   462  	p := params.Expect(0, []*exec.KwArg{
   463  		{"filter", ""},
   464  		{"attribute", nil},
   465  		{"default", nil},
   466  	})
   467  	if p.IsError() {
   468  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'map'"))
   469  	}
   470  	filter := p.KwArgs["filter"].String()
   471  	attribute := p.KwArgs["attribute"].String()
   472  	defaultVal := p.KwArgs["default"]
   473  	out := []*exec.Value{}
   474  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   475  		val := key
   476  		if len(attribute) > 0 {
   477  			attr, found := val.Get(attribute)
   478  			if found {
   479  				val = attr
   480  			} else if defaultVal != nil {
   481  				val = defaultVal
   482  			} else {
   483  				return true
   484  			}
   485  		}
   486  		if len(filter) > 0 {
   487  			val = e.ExecuteFilterByName(filter, val, exec.NewVarArgs())
   488  		}
   489  		out = append(out, val)
   490  		return true
   491  	}, func() {})
   492  	return exec.AsValue(out)
   493  }
   494  
   495  func filterMax(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   496  	p := params.Expect(0, []*exec.KwArg{
   497  		{"case_sensitive", false},
   498  		{"attribute", nil},
   499  	})
   500  	if p.IsError() {
   501  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'max'"))
   502  	}
   503  	caseSensitive := p.KwArgs["case_sensitive"].Bool()
   504  	attribute := p.KwArgs["attribute"].String()
   505  
   506  	var max *exec.Value
   507  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   508  		val := key
   509  		if len(attribute) > 0 {
   510  			attr, found := val.Get(attribute)
   511  			if found {
   512  				val = attr
   513  			} else {
   514  				val = nil
   515  			}
   516  		}
   517  		if max == nil {
   518  			max = val
   519  			return true
   520  		}
   521  		if val == nil || max == nil {
   522  			return true
   523  		}
   524  		switch {
   525  		case max.IsFloat() || max.IsInteger() && val.IsFloat() || val.IsInteger():
   526  			if val.Float() > max.Float() {
   527  				max = val
   528  			}
   529  		case max.IsString() && val.IsString():
   530  			if !caseSensitive && strings.ToLower(val.String()) > strings.ToLower(max.String()) {
   531  				max = val
   532  			} else if caseSensitive && val.String() > max.String() {
   533  				max = val
   534  			}
   535  		default:
   536  			max = exec.AsValue(errors.Errorf(`%s and %s are not comparable`, max.Val.Type(), val.Val.Type()))
   537  		}
   538  		return true
   539  	}, func() {})
   540  
   541  	if max == nil {
   542  		return exec.AsValue("")
   543  	}
   544  	return max
   545  }
   546  
   547  func filterMin(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   548  	p := params.Expect(0, []*exec.KwArg{
   549  		{"case_sensitive", false},
   550  		{"attribute", nil},
   551  	})
   552  	if p.IsError() {
   553  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'min'"))
   554  	}
   555  	caseSensitive := p.KwArgs["case_sensitive"].Bool()
   556  	attribute := p.KwArgs["attribute"].String()
   557  
   558  	var min *exec.Value
   559  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   560  		val := key
   561  		if len(attribute) > 0 {
   562  			attr, found := val.Get(attribute)
   563  			if found {
   564  				val = attr
   565  			} else {
   566  				val = nil
   567  			}
   568  		}
   569  		if min == nil {
   570  			min = val
   571  			return true
   572  		}
   573  		if val == nil || min == nil {
   574  			return true
   575  		}
   576  		switch {
   577  		case min.IsFloat() || min.IsInteger() && val.IsFloat() || val.IsInteger():
   578  			if val.Float() < min.Float() {
   579  				min = val
   580  			}
   581  		case min.IsString() && val.IsString():
   582  			if !caseSensitive && strings.ToLower(val.String()) < strings.ToLower(min.String()) {
   583  				min = val
   584  			} else if caseSensitive && val.String() < min.String() {
   585  				min = val
   586  			}
   587  		default:
   588  			min = exec.AsValue(errors.Errorf(`%s and %s are not comparable`, min.Val.Type(), val.Val.Type()))
   589  		}
   590  		return true
   591  	}, func() {})
   592  
   593  	if min == nil {
   594  		return exec.AsValue("")
   595  	}
   596  	return min
   597  }
   598  
   599  func filterPPrint(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   600  	p := params.Expect(0, []*exec.KwArg{{"verbose", false}})
   601  	if p.IsError() {
   602  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'pprint'"))
   603  	}
   604  	b, err := json.MarshalIndent(in.Interface(), "", "  ")
   605  	if err != nil {
   606  		return exec.AsValue(errors.Wrapf(err, `Unable to pretty print '%s'`, in.String()))
   607  	}
   608  	return exec.AsSafeValue(string(b))
   609  }
   610  
   611  func filterRandom(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   612  	if p := params.ExpectNothing(); p.IsError() {
   613  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'random'"))
   614  	}
   615  	if !in.CanSlice() || in.Len() <= 0 {
   616  		return in
   617  	}
   618  	i := rand.Intn(in.Len())
   619  	return in.Index(i)
   620  }
   621  
   622  func filterReject(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   623  	var test func(*exec.Value) bool
   624  	if len(params.Args) == 0 {
   625  		// Reject truthy value
   626  		test = func(in *exec.Value) bool {
   627  			return in.IsTrue()
   628  		}
   629  	} else {
   630  		name := params.First().String()
   631  		testParams := &exec.VarArgs{
   632  			Args:   params.Args[1:],
   633  			KwArgs: params.KwArgs,
   634  		}
   635  		test = func(in *exec.Value) bool {
   636  			out := e.ExecuteTestByName(name, in, testParams)
   637  			return out.IsTrue()
   638  		}
   639  	}
   640  
   641  	out := []*exec.Value{}
   642  
   643  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   644  		if !test(key) {
   645  			out = append(out, key)
   646  		}
   647  		return true
   648  	}, func() {})
   649  
   650  	return exec.AsValue(out)
   651  }
   652  
   653  func filterRejectAttr(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   654  	var test func(*exec.Value) *exec.Value
   655  	if len(params.Args) < 1 {
   656  		return exec.AsValue(errors.New("Wrong signature for 'rejectattr', expect at least an attribute name as argument"))
   657  	}
   658  	attribute := params.First().String()
   659  	if len(params.Args) == 1 {
   660  		// Reject truthy value
   661  		test = func(in *exec.Value) *exec.Value {
   662  			attr, found := in.Get(attribute)
   663  			if !found {
   664  				return exec.AsValue(errors.Errorf(`%s has no attribute '%s'`, in.String(), attribute))
   665  			}
   666  			return attr
   667  		}
   668  	} else {
   669  		name := params.Args[1].String()
   670  		testParams := &exec.VarArgs{
   671  			Args:   params.Args[2:],
   672  			KwArgs: params.KwArgs,
   673  		}
   674  		test = func(in *exec.Value) *exec.Value {
   675  			attr, found := in.Get(attribute)
   676  			if !found {
   677  				return exec.AsValue(errors.Errorf(`%s has no attribute '%s'`, in.String(), attribute))
   678  			}
   679  			out := e.ExecuteTestByName(name, attr, testParams)
   680  			return out
   681  		}
   682  	}
   683  
   684  	out := []*exec.Value{}
   685  	var err *exec.Value
   686  
   687  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   688  		result := test(key)
   689  		if result.IsError() {
   690  			err = result
   691  			return false
   692  		}
   693  		if !result.IsTrue() {
   694  			out = append(out, key)
   695  		}
   696  		return true
   697  	}, func() {})
   698  
   699  	if err != nil {
   700  		return err
   701  	}
   702  	return exec.AsValue(out)
   703  }
   704  
   705  func filterReplace(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   706  	p := params.Expect(2, []*exec.KwArg{{"count", nil}})
   707  	if p.IsError() {
   708  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'replace'"))
   709  	}
   710  	old := p.Args[0].String()
   711  	new := p.Args[1].String()
   712  	count := p.KwArgs["count"]
   713  	if count.IsNil() {
   714  		return exec.AsValue(strings.ReplaceAll(in.String(), old, new))
   715  	}
   716  	return exec.AsValue(strings.Replace(in.String(), old, new, count.Integer()))
   717  }
   718  
   719  func filterReverse(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   720  	if p := params.ExpectNothing(); p.IsError() {
   721  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'safe'"))
   722  	}
   723  	if in.IsString() {
   724  		var out strings.Builder
   725  		in.IterateOrder(func(idx, count int, key, value *exec.Value) bool {
   726  			out.WriteString(key.String())
   727  			return true
   728  		}, func() {}, true, false, false)
   729  		return exec.AsValue(out.String())
   730  	}
   731  	out := []*exec.Value{}
   732  	in.IterateOrder(func(idx, count int, key, value *exec.Value) bool {
   733  		out = append(out, key)
   734  		return true
   735  	}, func() {}, true, true, false)
   736  	return exec.AsValue(out)
   737  }
   738  
   739  func filterRound(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   740  	p := params.Expect(0, []*exec.KwArg{{"precision", 0}, {"method", "common"}})
   741  	if p.IsError() {
   742  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'round'"))
   743  	}
   744  	method := p.KwArgs["method"].String()
   745  	var op func(float64) float64
   746  	switch method {
   747  	case "common":
   748  		op = math.Round
   749  	case "floor":
   750  		op = math.Floor
   751  	case "ceil":
   752  		op = math.Ceil
   753  	default:
   754  		return exec.AsValue(errors.Errorf(`Unknown method '%s', mush be one of 'common, 'floor', 'ceil`, method))
   755  	}
   756  	value := in.Float()
   757  	factor := float64(10 * p.KwArgs["precision"].Integer())
   758  	if factor > 0 {
   759  		value = value * factor
   760  	}
   761  	value = op(value)
   762  	if factor > 0 {
   763  		value = value / factor
   764  	}
   765  	return exec.AsValue(value)
   766  }
   767  
   768  func filterSafe(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   769  	if p := params.ExpectNothing(); p.IsError() {
   770  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'safe'"))
   771  	}
   772  	in.Safe = true
   773  	return in // nothing to do here, just to keep track of the safe application
   774  }
   775  
   776  func filterSelect(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   777  	var test func(*exec.Value) bool
   778  	if len(params.Args) == 0 {
   779  		// Reject truthy value
   780  		test = func(in *exec.Value) bool {
   781  			return in.IsTrue()
   782  		}
   783  	} else {
   784  		name := params.First().String()
   785  		testParams := &exec.VarArgs{
   786  			Args:   params.Args[1:],
   787  			KwArgs: params.KwArgs,
   788  		}
   789  		test = func(in *exec.Value) bool {
   790  			out := e.ExecuteTestByName(name, in, testParams)
   791  			return out.IsTrue()
   792  		}
   793  	}
   794  
   795  	out := []*exec.Value{}
   796  
   797  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   798  		if test(key) {
   799  			out = append(out, key)
   800  		}
   801  		return true
   802  	}, func() {})
   803  
   804  	return exec.AsValue(out)
   805  }
   806  
   807  func filterSelectAttr(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   808  	var test func(*exec.Value) *exec.Value
   809  	if len(params.Args) < 1 {
   810  		return exec.AsValue(errors.New("Wrong signature for 'selectattr', expect at least an attribute name as argument"))
   811  	}
   812  	attribute := params.First().String()
   813  	if len(params.Args) == 1 {
   814  		// Reject truthy value
   815  		test = func(in *exec.Value) *exec.Value {
   816  			attr, found := in.Get(attribute)
   817  			if !found {
   818  				return exec.AsValue(errors.Errorf(`%s has no attribute '%s'`, in.String(), attribute))
   819  			}
   820  			return attr
   821  		}
   822  	} else {
   823  		name := params.Args[1].String()
   824  		testParams := &exec.VarArgs{
   825  			Args:   params.Args[2:],
   826  			KwArgs: params.KwArgs,
   827  		}
   828  		test = func(in *exec.Value) *exec.Value {
   829  			attr, found := in.Get(attribute)
   830  			if !found {
   831  				return exec.AsValue(errors.Errorf(`%s has no attribute '%s'`, in.String(), attribute))
   832  			}
   833  			out := e.ExecuteTestByName(name, attr, testParams)
   834  			return out
   835  		}
   836  	}
   837  
   838  	out := []*exec.Value{}
   839  	var err *exec.Value
   840  
   841  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   842  		result := test(key)
   843  		if result.IsError() {
   844  			err = result
   845  			return false
   846  		}
   847  		if result.IsTrue() {
   848  			out = append(out, key)
   849  		}
   850  		return true
   851  	}, func() {})
   852  
   853  	if err != nil {
   854  		return err
   855  	}
   856  	return exec.AsValue(out)
   857  }
   858  
   859  func filterSlice(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   860  	comp := strings.Split(params.Args[0].String(), ":")
   861  	if len(comp) != 2 {
   862  		return exec.AsValue(errors.New("Slice string must have the format 'from:to' [from/to can be omitted, but the ':' is required]"))
   863  	}
   864  
   865  	if !in.CanSlice() {
   866  		return in
   867  	}
   868  
   869  	from := exec.AsValue(comp[0]).Integer()
   870  	to := in.Len()
   871  
   872  	if from > to {
   873  		from = to
   874  	}
   875  
   876  	vto := exec.AsValue(comp[1]).Integer()
   877  	if vto >= from && vto <= in.Len() {
   878  		to = vto
   879  	}
   880  
   881  	return in.Slice(from, to)
   882  }
   883  
   884  func filterSort(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   885  	p := params.Expect(0, []*exec.KwArg{{"reverse", false}, {"case_sensitive", false}})
   886  	if p.IsError() {
   887  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'sort'"))
   888  	}
   889  	reverse := p.KwArgs["reverse"].Bool()
   890  	caseSensitive := p.KwArgs["case_sensitive"].Bool()
   891  	out := []*exec.Value{}
   892  	in.IterateOrder(func(idx, count int, key, value *exec.Value) bool {
   893  		out = append(out, key)
   894  		return true
   895  	}, func() {}, reverse, true, caseSensitive)
   896  	return exec.AsValue(out)
   897  }
   898  
   899  func filterString(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   900  	if p := params.ExpectNothing(); p.IsError() {
   901  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'string'"))
   902  	}
   903  	return exec.AsValue(in.String())
   904  }
   905  
   906  var reStriptags = regexp.MustCompile("<[^>]*?>")
   907  
   908  func filterStriptags(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   909  	if p := params.ExpectNothing(); p.IsError() {
   910  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'striptags'"))
   911  	}
   912  	s := in.String()
   913  
   914  	// Strip all tags
   915  	s = reStriptags.ReplaceAllString(s, "")
   916  
   917  	return exec.AsValue(strings.TrimSpace(s))
   918  }
   919  
   920  func filterSum(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   921  	p := params.Expect(0, []*exec.KwArg{{"attribute", nil}, {"start", 0}})
   922  	if p.IsError() {
   923  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'sum'"))
   924  	}
   925  
   926  	attribute := p.KwArgs["attribute"]
   927  	sum := p.KwArgs["start"].Float()
   928  	var err error
   929  
   930  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
   931  		if attribute.IsString() {
   932  			val := key
   933  			found := true
   934  			for _, attr := range strings.Split(attribute.String(), ".") {
   935  				val, found = val.Get(attr)
   936  				if !found {
   937  					err = errors.Errorf("'%s' has no attribute '%s'", key.String(), attribute.String())
   938  					return false
   939  				}
   940  			}
   941  			if found && val.IsNumber() {
   942  				sum += val.Float()
   943  			}
   944  		} else if attribute.IsInteger() {
   945  			value, found := key.Getitem(attribute.Integer())
   946  			if found {
   947  				sum += value.Float()
   948  			}
   949  		} else {
   950  			sum += key.Float()
   951  		}
   952  		return true
   953  	}, func() {})
   954  
   955  	if err != nil {
   956  		return exec.AsValue(err)
   957  	} else if sum == math.Trunc(sum) {
   958  		return exec.AsValue(int64(sum))
   959  	}
   960  	return exec.AsValue(sum)
   961  }
   962  
   963  func filterTitle(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   964  	if p := params.ExpectNothing(); p.IsError() {
   965  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'title'"))
   966  	}
   967  	if !in.IsString() {
   968  		return exec.AsValue("")
   969  	}
   970  	return exec.AsValue(strings.Title(strings.ToLower(in.String())))
   971  }
   972  
   973  func filterTrim(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   974  	if p := params.ExpectNothing(); p.IsError() {
   975  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'trim'"))
   976  	}
   977  	return exec.AsValue(strings.TrimSpace(in.String()))
   978  }
   979  
   980  func filterToJSON(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
   981  	p := params.Expect(0, []*exec.KwArg{{"indent", nil}})
   982  	if p.IsError() {
   983  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'tojson'"))
   984  	}
   985  
   986  	indent := p.KwArgs["indent"]
   987  	var out string
   988  	if indent.IsNil() {
   989  		b, err := json.Marshal(in.Interface())
   990  		if err != nil {
   991  			return exec.AsValue(errors.Wrap(err, "Unable to marhsall to json"))
   992  		}
   993  		out = string(b)
   994  	} else if indent.IsInteger() {
   995  		b, err := json.MarshalIndent(in.Interface(), "", strings.Repeat(" ", indent.Integer()))
   996  		if err != nil {
   997  			return exec.AsValue(errors.Wrap(err, "Unable to marhsall to json"))
   998  		}
   999  		out = string(b)
  1000  	} else {
  1001  		return exec.AsValue(errors.Errorf("Expected an integer for 'indent', got %s", indent.String()))
  1002  	}
  1003  	return exec.AsSafeValue(out)
  1004  }
  1005  
  1006  func filterTruncate(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
  1007  	p := params.Expect(0, []*exec.KwArg{
  1008  		{"length", 255},
  1009  		{"killwords", false},
  1010  		{"end", "..."},
  1011  		{"leeway", 0},
  1012  	})
  1013  	if p.IsError() {
  1014  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'truncate'"))
  1015  	}
  1016  
  1017  	source := in.String()
  1018  	length := p.KwArgs["length"].Integer()
  1019  	leeway := p.KwArgs["leeway"].Integer()
  1020  	killwords := p.KwArgs["killwords"].Bool()
  1021  	end := p.KwArgs["end"].String()
  1022  	rEnd := []rune(end)
  1023  	fullLength := length + leeway
  1024  	runes := []rune(source)
  1025  
  1026  	if length < len(rEnd) {
  1027  		return exec.AsValue(errors.Errorf(`expected length >= %d, got %d`, len(rEnd), length))
  1028  	}
  1029  
  1030  	if len(runes) <= fullLength {
  1031  		return exec.AsValue(source)
  1032  	}
  1033  
  1034  	atLength := string(runes[:length-len(rEnd)])
  1035  	if !killwords {
  1036  		atLength = strings.TrimRightFunc(atLength, func(r rune) bool {
  1037  			return !unicode.IsSpace(r)
  1038  		})
  1039  		atLength = strings.TrimRight(atLength, " \n\t")
  1040  	}
  1041  	return exec.AsValue(fmt.Sprintf("%s%s", atLength, end))
  1042  }
  1043  
  1044  func filterUnique(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
  1045  	p := params.Expect(0, []*exec.KwArg{{"case_sensitive", false}, {"attribute", nil}})
  1046  	if p.IsError() {
  1047  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'unique'"))
  1048  	}
  1049  
  1050  	caseSensitive := p.KwArgs["case_sensitive"].Bool()
  1051  	attribute := p.KwArgs["attribute"]
  1052  
  1053  	out := exec.ValuesList{}
  1054  	tracker := map[interface{}]bool{}
  1055  	var err error
  1056  
  1057  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
  1058  		val := key
  1059  		if attribute.IsString() {
  1060  			attr := attribute.String()
  1061  			nested, found := key.Get(attr)
  1062  			if !found {
  1063  				err = errors.Errorf(`%s has no attribute %s`, key.String(), attr)
  1064  				return false
  1065  			}
  1066  			val = nested
  1067  		}
  1068  		tracked := val.Interface()
  1069  		if !caseSensitive && val.IsString() {
  1070  			tracked = strings.ToLower(val.String())
  1071  		}
  1072  		if _, contains := tracker[tracked]; !contains {
  1073  			tracker[tracked] = true
  1074  			out = append(out, key)
  1075  		}
  1076  		return true
  1077  	}, func() {})
  1078  
  1079  	if err != nil {
  1080  		return exec.AsValue(err)
  1081  	}
  1082  	return exec.AsValue(out)
  1083  }
  1084  
  1085  func filterUpper(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
  1086  	if p := params.ExpectNothing(); p.IsError() {
  1087  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'upper'"))
  1088  	}
  1089  	return exec.AsValue(strings.ToUpper(in.String()))
  1090  }
  1091  
  1092  func filterUrlencode(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
  1093  	if p := params.ExpectNothing(); p.IsError() {
  1094  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'urlencode'"))
  1095  	}
  1096  	return exec.AsValue(url.QueryEscape(in.String()))
  1097  }
  1098  
  1099  // TODO: This regexp could do some work
  1100  var filterUrlizeURLRegexp = regexp.MustCompile(`((((http|https)://)|www\.|((^|[ ])[0-9A-Za-z_\-]+(\.com|\.net|\.org|\.info|\.biz|\.de))))(?U:.*)([ ]+|$)`)
  1101  var filterUrlizeEmailRegexp = regexp.MustCompile(`(\w+@\w+\.\w{2,4})`)
  1102  
  1103  func filterUrlizeHelper(input string, trunc int, rel string, target string) (string, error) {
  1104  	var soutErr error
  1105  	sout := filterUrlizeURLRegexp.ReplaceAllStringFunc(input, func(raw_url string) string {
  1106  		var prefix string
  1107  		var suffix string
  1108  		if strings.HasPrefix(raw_url, " ") {
  1109  			prefix = " "
  1110  		}
  1111  		if strings.HasSuffix(raw_url, " ") {
  1112  			suffix = " "
  1113  		}
  1114  
  1115  		raw_url = strings.TrimSpace(raw_url)
  1116  
  1117  		url := u.IRIEncode(raw_url)
  1118  
  1119  		if !strings.HasPrefix(url, "http") {
  1120  			url = fmt.Sprintf("http://%s", url)
  1121  		}
  1122  
  1123  		title := raw_url
  1124  
  1125  		if trunc > 3 && len(title) > trunc {
  1126  			title = fmt.Sprintf("%s...", title[:trunc-3])
  1127  		}
  1128  
  1129  		title = u.Escape(title)
  1130  
  1131  		attrs := ""
  1132  		if len(target) > 0 {
  1133  			attrs = fmt.Sprintf(` target="%s"`, target)
  1134  		}
  1135  
  1136  		rels := []string{}
  1137  		cleanedRel := strings.Trim(strings.Replace(rel, "noopener", "", -1), " ")
  1138  		if len(cleanedRel) > 0 {
  1139  			rels = append(rels, cleanedRel)
  1140  		}
  1141  		rels = append(rels, "noopener")
  1142  		rel = strings.Join(rels, " ")
  1143  
  1144  		return fmt.Sprintf(`%s<a href="%s" rel="%s"%s>%s</a>%s`, prefix, url, rel, attrs, title, suffix)
  1145  	})
  1146  	if soutErr != nil {
  1147  		return "", soutErr
  1148  	}
  1149  
  1150  	sout = filterUrlizeEmailRegexp.ReplaceAllStringFunc(sout, func(mail string) string {
  1151  		title := mail
  1152  
  1153  		if trunc > 3 && len(title) > trunc {
  1154  			title = fmt.Sprintf("%s...", title[:trunc-3])
  1155  		}
  1156  
  1157  		return fmt.Sprintf(`<a href="mailto:%s">%s</a>`, mail, title)
  1158  	})
  1159  	return sout, nil
  1160  }
  1161  
  1162  func filterUrlize(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
  1163  	p := params.Expect(0, []*exec.KwArg{
  1164  		{"trim_url_limit", nil},
  1165  		{"nofollow", false},
  1166  		{"target", nil},
  1167  		{"rel", nil},
  1168  	})
  1169  	if p.IsError() {
  1170  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'urlize'"))
  1171  	}
  1172  	truncate := -1
  1173  	if param := p.KwArgs["trim_url_limit"]; param.IsInteger() {
  1174  		truncate = param.Integer()
  1175  	}
  1176  	rel := p.KwArgs["rel"]
  1177  	target := p.KwArgs["target"]
  1178  
  1179  	s, err := filterUrlizeHelper(in.String(), truncate, rel.String(), target.String())
  1180  	if err != nil {
  1181  		return exec.AsValue(err)
  1182  	}
  1183  
  1184  	return exec.AsValue(s)
  1185  }
  1186  
  1187  func filterWordcount(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
  1188  	if p := params.ExpectNothing(); p.IsError() {
  1189  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'wordcount'"))
  1190  	}
  1191  	return exec.AsValue(len(strings.Fields(in.String())))
  1192  }
  1193  
  1194  func filterWordwrap(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
  1195  	words := strings.Fields(in.String())
  1196  	wordsLen := len(words)
  1197  	wrapAt := params.Args[0].Integer()
  1198  	if wrapAt <= 0 {
  1199  		return in
  1200  	}
  1201  
  1202  	linecount := wordsLen/wrapAt + wordsLen%wrapAt
  1203  	lines := make([]string, 0, linecount)
  1204  	for i := 0; i < linecount; i++ {
  1205  		lines = append(lines, strings.Join(words[wrapAt*i:u.Min(wrapAt*(i+1), wordsLen)], " "))
  1206  	}
  1207  	return exec.AsValue(strings.Join(lines, "\n"))
  1208  }
  1209  
  1210  func filterXMLAttr(e *exec.Evaluator, in *exec.Value, params *exec.VarArgs) *exec.Value {
  1211  	p := params.ExpectKwArgs([]*exec.KwArg{{"autospace", true}})
  1212  	if p.IsError() {
  1213  		return exec.AsValue(errors.Wrap(p, "Wrong signature for 'xmlattr'"))
  1214  	}
  1215  	autospace := p.KwArgs["autospace"].Bool()
  1216  	kvs := []string{}
  1217  	in.Iterate(func(idx, count int, key, value *exec.Value) bool {
  1218  		if !value.IsTrue() {
  1219  			return true
  1220  		}
  1221  		kv := fmt.Sprintf(`%s="%s"`, key.Escaped(), value.Escaped())
  1222  		kvs = append(kvs, kv)
  1223  		return true
  1224  	}, func() {})
  1225  	out := strings.Join(kvs, " ")
  1226  	if autospace {
  1227  		out = " " + out
  1228  	}
  1229  	return exec.AsValue(out)
  1230  }
  1231  

View as plain text