helper.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. package raymond
  2. import (
  3. "errors"
  4. "fmt"
  5. "reflect"
  6. "regexp"
  7. "strconv"
  8. "sync"
  9. )
  10. // Options represents the options argument provided to helpers and context functions.
  11. type Options struct {
  12. // evaluation visitor
  13. eval *evalVisitor
  14. // params
  15. params []interface{}
  16. hash map[string]interface{}
  17. }
  18. var (
  19. // helpers stores all globally registered helpers
  20. helpers = make(map[string]reflect.Value)
  21. paramHelpers = make(map[string]paramHelperFunc)
  22. // protects global helpers
  23. helpersMutex sync.RWMutex
  24. // protects global param helpers
  25. paramHelpersMutex sync.RWMutex
  26. )
  27. func init() {
  28. // Register builtin helpers.
  29. RegisterHelper("if", ifHelper)
  30. RegisterHelper("unless", unlessHelper)
  31. RegisterHelper("with", withHelper)
  32. RegisterHelper("each", eachHelper)
  33. RegisterHelper("log", logHelper)
  34. RegisterHelper("lookup", lookupHelper)
  35. RegisterHelper("equal", equalHelper)
  36. RegisterHelper("ifGt", ifGtHelper)
  37. RegisterHelper("ifLt", ifLtHelper)
  38. RegisterHelper("ifEq", ifEqHelper)
  39. RegisterHelper("ifMatchesRegexStr", ifMatchesRegexStr)
  40. RegisterHelper("pluralize", pluralizeHelper)
  41. // Register builtin param helpers.
  42. RegisterParamHelper("length", lengthParamHelper)
  43. }
  44. // RegisterHelper registers a global helper. That helper will be available to all templates.
  45. func RegisterHelper(name string, helper interface{}) {
  46. helpersMutex.Lock()
  47. defer helpersMutex.Unlock()
  48. if helpers[name] != zero {
  49. panic(fmt.Errorf("Helper already registered: %s", name))
  50. }
  51. val := reflect.ValueOf(helper)
  52. ensureValidHelper(name, val)
  53. helpers[name] = val
  54. }
  55. // RegisterHelpers registers several global helpers. Those helpers will be available to all templates.
  56. func RegisterHelpers(helpers map[string]interface{}) {
  57. for name, helper := range helpers {
  58. RegisterHelper(name, helper)
  59. }
  60. }
  61. // RemoveHelper unregisters a global helper
  62. func RemoveHelper(name string) {
  63. helpersMutex.Lock()
  64. defer helpersMutex.Unlock()
  65. delete(helpers, name)
  66. }
  67. // RemoveAllHelpers unregisters all global helpers
  68. func RemoveAllHelpers() {
  69. helpersMutex.Lock()
  70. defer helpersMutex.Unlock()
  71. helpers = make(map[string]reflect.Value)
  72. }
  73. // ensureValidHelper panics if given helper is not valid
  74. func ensureValidHelper(name string, funcValue reflect.Value) {
  75. if funcValue.Kind() != reflect.Func {
  76. panic(fmt.Errorf("Helper must be a function: %s", name))
  77. }
  78. funcType := funcValue.Type()
  79. if funcType.NumOut() != 1 {
  80. panic(fmt.Errorf("Helper function must return a string or a SafeString: %s", name))
  81. }
  82. // @todo Check if first returned value is a string, SafeString or interface{} ?
  83. }
  84. // findHelper finds a globally registered helper
  85. func findHelper(name string) reflect.Value {
  86. helpersMutex.RLock()
  87. defer helpersMutex.RUnlock()
  88. return helpers[name]
  89. }
  90. // newOptions instanciates a new Options
  91. func newOptions(eval *evalVisitor, params []interface{}, hash map[string]interface{}) *Options {
  92. return &Options{
  93. eval: eval,
  94. params: params,
  95. hash: hash,
  96. }
  97. }
  98. // newEmptyOptions instanciates a new empty Options
  99. func newEmptyOptions(eval *evalVisitor) *Options {
  100. return &Options{
  101. eval: eval,
  102. hash: make(map[string]interface{}),
  103. }
  104. }
  105. //
  106. // Context Values
  107. //
  108. // Value returns field value from current context.
  109. func (options *Options) Value(name string) interface{} {
  110. value := options.eval.evalField(options.eval.curCtx(), name, false)
  111. if !value.IsValid() {
  112. return nil
  113. }
  114. return value.Interface()
  115. }
  116. // ValueStr returns string representation of field value from current context.
  117. func (options *Options) ValueStr(name string) string {
  118. return Str(options.Value(name))
  119. }
  120. // Ctx returns current evaluation context.
  121. func (options *Options) Ctx() interface{} {
  122. return options.eval.curCtx().Interface()
  123. }
  124. //
  125. // Hash Arguments
  126. //
  127. // HashProp returns hash property.
  128. func (options *Options) HashProp(name string) interface{} {
  129. return options.hash[name]
  130. }
  131. // HashStr returns string representation of hash property.
  132. func (options *Options) HashStr(name string) string {
  133. return Str(options.hash[name])
  134. }
  135. // Hash returns entire hash.
  136. func (options *Options) Hash() map[string]interface{} {
  137. return options.hash
  138. }
  139. //
  140. // Parameters
  141. //
  142. // Param returns parameter at given position.
  143. func (options *Options) Param(pos int) interface{} {
  144. if len(options.params) > pos {
  145. return options.params[pos]
  146. }
  147. return nil
  148. }
  149. // ParamStr returns string representation of parameter at given position.
  150. func (options *Options) ParamStr(pos int) string {
  151. return Str(options.Param(pos))
  152. }
  153. // Params returns all parameters.
  154. func (options *Options) Params() []interface{} {
  155. return options.params
  156. }
  157. //
  158. // Private data
  159. //
  160. // Data returns private data value.
  161. func (options *Options) Data(name string) interface{} {
  162. return options.eval.dataFrame.Get(name)
  163. }
  164. // DataStr returns string representation of private data value.
  165. func (options *Options) DataStr(name string) string {
  166. return Str(options.eval.dataFrame.Get(name))
  167. }
  168. // DataFrame returns current private data frame.
  169. func (options *Options) DataFrame() *DataFrame {
  170. return options.eval.dataFrame
  171. }
  172. // NewDataFrame instanciates a new data frame that is a copy of current evaluation data frame.
  173. //
  174. // Parent of returned data frame is set to current evaluation data frame.
  175. func (options *Options) NewDataFrame() *DataFrame {
  176. return options.eval.dataFrame.Copy()
  177. }
  178. // newIterDataFrame instanciates a new data frame and set iteration specific vars
  179. func (options *Options) newIterDataFrame(length int, i int, key interface{}) *DataFrame {
  180. return options.eval.dataFrame.newIterDataFrame(length, i, key)
  181. }
  182. //
  183. // Evaluation
  184. //
  185. // evalBlock evaluates block with given context, private data and iteration key
  186. func (options *Options) evalBlock(ctx interface{}, data *DataFrame, key interface{}) string {
  187. result := ""
  188. if block := options.eval.curBlock(); (block != nil) && (block.Program != nil) {
  189. result = options.eval.evalProgram(block.Program, ctx, data, key)
  190. }
  191. return result
  192. }
  193. // Fn evaluates block with current evaluation context.
  194. func (options *Options) Fn() string {
  195. return options.evalBlock(nil, nil, nil)
  196. }
  197. // FnCtxData evaluates block with given context and private data frame.
  198. func (options *Options) FnCtxData(ctx interface{}, data *DataFrame) string {
  199. return options.evalBlock(ctx, data, nil)
  200. }
  201. // FnWith evaluates block with given context.
  202. func (options *Options) FnWith(ctx interface{}) string {
  203. return options.evalBlock(ctx, nil, nil)
  204. }
  205. // FnData evaluates block with given private data frame.
  206. func (options *Options) FnData(data *DataFrame) string {
  207. return options.evalBlock(nil, data, nil)
  208. }
  209. // Inverse evaluates "else block".
  210. func (options *Options) Inverse() string {
  211. result := ""
  212. if block := options.eval.curBlock(); (block != nil) && (block.Inverse != nil) {
  213. result, _ = block.Inverse.Accept(options.eval).(string)
  214. }
  215. return result
  216. }
  217. // Eval evaluates field for given context.
  218. func (options *Options) Eval(ctx interface{}, field string) interface{} {
  219. if ctx == nil {
  220. return nil
  221. }
  222. if field == "" {
  223. return nil
  224. }
  225. val := options.eval.evalField(reflect.ValueOf(ctx), field, false)
  226. if !val.IsValid() {
  227. return nil
  228. }
  229. return val.Interface()
  230. }
  231. //
  232. // Misc
  233. //
  234. // isIncludableZero returns true if 'includeZero' option is set and first param is the number 0
  235. func (options *Options) isIncludableZero() bool {
  236. b, ok := options.HashProp("includeZero").(bool)
  237. if ok && b {
  238. nb, ok := options.Param(0).(int)
  239. if ok && nb == 0 {
  240. return true
  241. }
  242. }
  243. return false
  244. }
  245. //
  246. // Builtin helpers
  247. //
  248. // #if block helper
  249. func ifHelper(conditional interface{}, options *Options) interface{} {
  250. if options.isIncludableZero() || IsTrue(conditional) {
  251. return options.Fn()
  252. }
  253. return options.Inverse()
  254. }
  255. func ifGtHelper(a, b interface{}, options *Options) interface{} {
  256. var aFloat, bFloat float64
  257. var err error
  258. if aFloat, err = floatValue(a); err != nil {
  259. log.WithError(err).Errorf("failed to convert value to float '%v'", a)
  260. return options.Inverse()
  261. }
  262. if bFloat, err = floatValue(b); err != nil {
  263. log.WithError(err).Errorf("failed to convert value to float '%v'", b)
  264. return options.Inverse()
  265. }
  266. if aFloat > bFloat {
  267. return options.Fn()
  268. }
  269. // Evaluate possible else condition.
  270. return options.Inverse()
  271. }
  272. func ifLtHelper(a, b interface{}, options *Options) interface{} {
  273. var aFloat, bFloat float64
  274. var err error
  275. if aFloat, err = floatValue(a); err != nil {
  276. log.WithError(err).Errorf("failed to convert value to float '%v'", a)
  277. return options.Inverse()
  278. }
  279. if bFloat, err = floatValue(b); err != nil {
  280. log.WithError(err).Errorf("failed to convert value to float '%v'", b)
  281. return options.Inverse()
  282. }
  283. if aFloat < bFloat {
  284. return options.Fn()
  285. }
  286. // Evaluate possible else condition.
  287. return options.Inverse()
  288. }
  289. func ifEqHelper(a, b interface{}, options *Options) interface{} {
  290. var aFloat, bFloat float64
  291. var err error
  292. if aFloat, err = floatValue(a); err != nil {
  293. log.WithError(err).Errorf("failed to convert value to float '%v'", a)
  294. return options.Inverse()
  295. }
  296. if bFloat, err = floatValue(b); err != nil {
  297. log.WithError(err).Errorf("failed to convert value to float '%v'", b)
  298. return options.Inverse()
  299. }
  300. if aFloat == bFloat {
  301. return options.Fn()
  302. }
  303. // Evaluate possible else condition.
  304. return options.Inverse()
  305. }
  306. // ifMatchesRegexStr is helper function which does a regex match, where a is the expression to compile and
  307. // b is the string to match against.
  308. func ifMatchesRegexStr(a, b interface{}, options *Options) interface{} {
  309. exp := Str(a)
  310. match := Str(b)
  311. re, err := regexp.Compile(exp)
  312. if err != nil {
  313. log.WithError(err).Errorf("failed to compile regex '%v'", a)
  314. return options.Inverse()
  315. }
  316. if re.MatchString(match) {
  317. return options.Fn()
  318. }
  319. return options.Inverse()
  320. }
  321. func pluralizeHelper(count, plural, singular interface{}) interface{} {
  322. if c, err := floatValue(count); err != nil || c <= 1 {
  323. return singular
  324. }
  325. return plural
  326. }
  327. // #unless block helper
  328. func unlessHelper(conditional interface{}, options *Options) interface{} {
  329. if options.isIncludableZero() || IsTrue(conditional) {
  330. return options.Inverse()
  331. }
  332. return options.Fn()
  333. }
  334. // #with block helper
  335. func withHelper(context interface{}, options *Options) interface{} {
  336. if IsTrue(context) {
  337. return options.FnWith(context)
  338. }
  339. return options.Inverse()
  340. }
  341. // #each block helper
  342. func eachHelper(context interface{}, options *Options) interface{} {
  343. if !IsTrue(context) {
  344. return options.Inverse()
  345. }
  346. result := ""
  347. val := reflect.ValueOf(context)
  348. switch val.Kind() {
  349. case reflect.Array, reflect.Slice:
  350. for i := 0; i < val.Len(); i++ {
  351. // computes private data
  352. data := options.newIterDataFrame(val.Len(), i, nil)
  353. // evaluates block
  354. result += options.evalBlock(val.Index(i).Interface(), data, i)
  355. }
  356. case reflect.Map:
  357. // note: a go hash is not ordered, so result may vary, this behaviour differs from the JS implementation
  358. keys := val.MapKeys()
  359. for i := 0; i < len(keys); i++ {
  360. key := keys[i].Interface()
  361. ctx := val.MapIndex(keys[i]).Interface()
  362. // computes private data
  363. data := options.newIterDataFrame(len(keys), i, key)
  364. // evaluates block
  365. result += options.evalBlock(ctx, data, key)
  366. }
  367. case reflect.Struct:
  368. var exportedFields []int
  369. // collect exported fields only
  370. for i := 0; i < val.NumField(); i++ {
  371. if tField := val.Type().Field(i); tField.PkgPath == "" {
  372. exportedFields = append(exportedFields, i)
  373. }
  374. }
  375. for i, fieldIndex := range exportedFields {
  376. key := val.Type().Field(fieldIndex).Name
  377. ctx := val.Field(fieldIndex).Interface()
  378. // computes private data
  379. data := options.newIterDataFrame(len(exportedFields), i, key)
  380. // evaluates block
  381. result += options.evalBlock(ctx, data, key)
  382. }
  383. }
  384. return result
  385. }
  386. // #log helper
  387. func logHelper(message string) interface{} {
  388. log.Print(message)
  389. return ""
  390. }
  391. // #lookup helper
  392. func lookupHelper(obj interface{}, field string, options *Options) interface{} {
  393. return Str(options.Eval(obj, field))
  394. }
  395. // #equal helper
  396. // Ref: https://github.com/aymerick/raymond/issues/7
  397. func equalHelper(a interface{}, b interface{}, options *Options) interface{} {
  398. if Str(a) == Str(b) {
  399. return options.Fn()
  400. }
  401. return ""
  402. }
  403. // floatValue attempts to convert value into a float64 and returns an error if it fails.
  404. func floatValue(value interface{}) (result float64, err error) {
  405. val := reflect.ValueOf(value)
  406. switch val.Kind() {
  407. case reflect.Bool:
  408. result = 0
  409. if val.Bool() {
  410. result = 1
  411. }
  412. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  413. result = float64(val.Int())
  414. case reflect.Float32, reflect.Float64:
  415. result = val.Float()
  416. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
  417. result = float64(val.Uint())
  418. case reflect.String:
  419. result, err = strconv.ParseFloat(val.String(), 64)
  420. default:
  421. err = errors.New(fmt.Sprintf("uable to convert type '%s' to float64", val.Kind().String()))
  422. }
  423. return
  424. }
  425. // A paramHelperFunc is a function that will mutate the input by performing some kind of
  426. // operation on it. Such as getting the length of a string, slice, or map.
  427. type paramHelperFunc func(value reflect.Value) reflect.Value
  428. // RegisterParamHelper registers a global param helper. That helper will be available to all templates.
  429. func RegisterParamHelper(name string, helper paramHelperFunc) {
  430. paramHelpersMutex.Lock()
  431. defer paramHelpersMutex.Unlock()
  432. if _, ok := paramHelpers[name]; ok {
  433. panic(fmt.Errorf("Param helper already registered: %s", name))
  434. }
  435. paramHelpers[name] = helper
  436. }
  437. // RemoveParamHelper unregisters a global param helper
  438. func RemoveParamHelper(name string) {
  439. paramHelpersMutex.Lock()
  440. defer paramHelpersMutex.Unlock()
  441. delete(paramHelpers, name)
  442. }
  443. // findParamHelper finds a globally registered param helper
  444. func findParamHelper(name string) paramHelperFunc {
  445. paramHelpersMutex.RLock()
  446. defer paramHelpersMutex.RUnlock()
  447. return paramHelpers[name]
  448. }
  449. // lengthParamHelper is a helper func to return the length of the value passed. It
  450. // will only return the length if the value is an array, slice, map, or string. Otherwise,
  451. // it returns zero value.
  452. // e.g. foo == "foo" -> foo.length -> 3
  453. func lengthParamHelper(ctx reflect.Value) reflect.Value {
  454. if ctx == zero {
  455. return ctx
  456. }
  457. switch ctx.Kind() {
  458. case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
  459. return reflect.ValueOf(ctx.Len())
  460. }
  461. return zero
  462. }