helper.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. package raymond
  2. import (
  3. "fmt"
  4. "log"
  5. "reflect"
  6. "sync"
  7. )
  8. // Options represents the options argument provided to helpers and context functions.
  9. type Options struct {
  10. // evaluation visitor
  11. eval *evalVisitor
  12. // params
  13. params []interface{}
  14. hash map[string]interface{}
  15. }
  16. // helpers stores all globally registered helpers
  17. var helpers = make(map[string]reflect.Value)
  18. // protects global helpers
  19. var helpersMutex sync.RWMutex
  20. func init() {
  21. // register builtin helpers
  22. RegisterHelper("if", ifHelper)
  23. RegisterHelper("unless", unlessHelper)
  24. RegisterHelper("with", withHelper)
  25. RegisterHelper("each", eachHelper)
  26. RegisterHelper("log", logHelper)
  27. RegisterHelper("lookup", lookupHelper)
  28. RegisterHelper("equal", equalHelper)
  29. }
  30. // RegisterHelper registers a global helper. That helper will be available to all templates.
  31. func RegisterHelper(name string, helper interface{}) {
  32. helpersMutex.Lock()
  33. defer helpersMutex.Unlock()
  34. if helpers[name] != zero {
  35. panic(fmt.Errorf("Helper already registered: %s", name))
  36. }
  37. val := reflect.ValueOf(helper)
  38. ensureValidHelper(name, val)
  39. helpers[name] = val
  40. }
  41. // RegisterHelpers registers several global helpers. Those helpers will be available to all templates.
  42. func RegisterHelpers(helpers map[string]interface{}) {
  43. for name, helper := range helpers {
  44. RegisterHelper(name, helper)
  45. }
  46. }
  47. // RemoveHelper unregisters a global helper
  48. func RemoveHelper(name string) {
  49. helpersMutex.Lock()
  50. defer helpersMutex.Unlock()
  51. delete(helpers, name)
  52. }
  53. // RemoveAllHelpers unregisters all global helpers
  54. func RemoveAllHelpers() {
  55. helpersMutex.Lock()
  56. defer helpersMutex.Unlock()
  57. helpers = make(map[string]reflect.Value)
  58. }
  59. // ensureValidHelper panics if given helper is not valid
  60. func ensureValidHelper(name string, funcValue reflect.Value) {
  61. if funcValue.Kind() != reflect.Func {
  62. panic(fmt.Errorf("Helper must be a function: %s", name))
  63. }
  64. funcType := funcValue.Type()
  65. if funcType.NumOut() != 1 {
  66. panic(fmt.Errorf("Helper function must return a string or a SafeString: %s", name))
  67. }
  68. // @todo Check if first returned value is a string, SafeString or interface{} ?
  69. }
  70. // findHelper finds a globally registered helper
  71. func findHelper(name string) reflect.Value {
  72. helpersMutex.RLock()
  73. defer helpersMutex.RUnlock()
  74. return helpers[name]
  75. }
  76. // newOptions instanciates a new Options
  77. func newOptions(eval *evalVisitor, params []interface{}, hash map[string]interface{}) *Options {
  78. return &Options{
  79. eval: eval,
  80. params: params,
  81. hash: hash,
  82. }
  83. }
  84. // newEmptyOptions instanciates a new empty Options
  85. func newEmptyOptions(eval *evalVisitor) *Options {
  86. return &Options{
  87. eval: eval,
  88. hash: make(map[string]interface{}),
  89. }
  90. }
  91. //
  92. // Context Values
  93. //
  94. // Value returns field value from current context.
  95. func (options *Options) Value(name string) interface{} {
  96. value := options.eval.evalField(options.eval.curCtx(), name, false)
  97. if !value.IsValid() {
  98. return nil
  99. }
  100. return value.Interface()
  101. }
  102. // ValueStr returns string representation of field value from current context.
  103. func (options *Options) ValueStr(name string) string {
  104. return Str(options.Value(name))
  105. }
  106. // Ctx returns current evaluation context.
  107. func (options *Options) Ctx() interface{} {
  108. return options.eval.curCtx().Interface()
  109. }
  110. //
  111. // Hash Arguments
  112. //
  113. // HashProp returns hash property.
  114. func (options *Options) HashProp(name string) interface{} {
  115. return options.hash[name]
  116. }
  117. // HashStr returns string representation of hash property.
  118. func (options *Options) HashStr(name string) string {
  119. return Str(options.hash[name])
  120. }
  121. // Hash returns entire hash.
  122. func (options *Options) Hash() map[string]interface{} {
  123. return options.hash
  124. }
  125. //
  126. // Parameters
  127. //
  128. // Param returns parameter at given position.
  129. func (options *Options) Param(pos int) interface{} {
  130. if len(options.params) > pos {
  131. return options.params[pos]
  132. }
  133. return nil
  134. }
  135. // ParamStr returns string representation of parameter at given position.
  136. func (options *Options) ParamStr(pos int) string {
  137. return Str(options.Param(pos))
  138. }
  139. // Params returns all parameters.
  140. func (options *Options) Params() []interface{} {
  141. return options.params
  142. }
  143. //
  144. // Private data
  145. //
  146. // Data returns private data value.
  147. func (options *Options) Data(name string) interface{} {
  148. return options.eval.dataFrame.Get(name)
  149. }
  150. // DataStr returns string representation of private data value.
  151. func (options *Options) DataStr(name string) string {
  152. return Str(options.eval.dataFrame.Get(name))
  153. }
  154. // DataFrame returns current private data frame.
  155. func (options *Options) DataFrame() *DataFrame {
  156. return options.eval.dataFrame
  157. }
  158. // NewDataFrame instanciates a new data frame that is a copy of current evaluation data frame.
  159. //
  160. // Parent of returned data frame is set to current evaluation data frame.
  161. func (options *Options) NewDataFrame() *DataFrame {
  162. return options.eval.dataFrame.Copy()
  163. }
  164. // newIterDataFrame instanciates a new data frame and set iteration specific vars
  165. func (options *Options) newIterDataFrame(length int, i int, key interface{}) *DataFrame {
  166. return options.eval.dataFrame.newIterDataFrame(length, i, key)
  167. }
  168. //
  169. // Evaluation
  170. //
  171. // evalBlock evaluates block with given context, private data and iteration key
  172. func (options *Options) evalBlock(ctx interface{}, data *DataFrame, key interface{}) string {
  173. result := ""
  174. if block := options.eval.curBlock(); (block != nil) && (block.Program != nil) {
  175. result = options.eval.evalProgram(block.Program, ctx, data, key)
  176. }
  177. return result
  178. }
  179. // Fn evaluates block with current evaluation context.
  180. func (options *Options) Fn() string {
  181. return options.evalBlock(nil, nil, nil)
  182. }
  183. // FnCtxData evaluates block with given context and private data frame.
  184. func (options *Options) FnCtxData(ctx interface{}, data *DataFrame) string {
  185. return options.evalBlock(ctx, data, nil)
  186. }
  187. // FnWith evaluates block with given context.
  188. func (options *Options) FnWith(ctx interface{}) string {
  189. return options.evalBlock(ctx, nil, nil)
  190. }
  191. // FnData evaluates block with given private data frame.
  192. func (options *Options) FnData(data *DataFrame) string {
  193. return options.evalBlock(nil, data, nil)
  194. }
  195. // Inverse evaluates "else block".
  196. func (options *Options) Inverse() string {
  197. result := ""
  198. if block := options.eval.curBlock(); (block != nil) && (block.Inverse != nil) {
  199. result, _ = block.Inverse.Accept(options.eval).(string)
  200. }
  201. return result
  202. }
  203. // Eval evaluates field for given context.
  204. func (options *Options) Eval(ctx interface{}, field string) interface{} {
  205. if ctx == nil {
  206. return nil
  207. }
  208. if field == "" {
  209. return nil
  210. }
  211. val := options.eval.evalField(reflect.ValueOf(ctx), field, false)
  212. if !val.IsValid() {
  213. return nil
  214. }
  215. return val.Interface()
  216. }
  217. //
  218. // Misc
  219. //
  220. // isIncludableZero returns true if 'includeZero' option is set and first param is the number 0
  221. func (options *Options) isIncludableZero() bool {
  222. b, ok := options.HashProp("includeZero").(bool)
  223. if ok && b {
  224. nb, ok := options.Param(0).(int)
  225. if ok && nb == 0 {
  226. return true
  227. }
  228. }
  229. return false
  230. }
  231. //
  232. // Builtin helpers
  233. //
  234. // #if block helper
  235. func ifHelper(conditional interface{}, options *Options) interface{} {
  236. if options.isIncludableZero() || IsTrue(conditional) {
  237. return options.Fn()
  238. }
  239. return options.Inverse()
  240. }
  241. // #unless block helper
  242. func unlessHelper(conditional interface{}, options *Options) interface{} {
  243. if options.isIncludableZero() || IsTrue(conditional) {
  244. return options.Inverse()
  245. }
  246. return options.Fn()
  247. }
  248. // #with block helper
  249. func withHelper(context interface{}, options *Options) interface{} {
  250. if IsTrue(context) {
  251. return options.FnWith(context)
  252. }
  253. return options.Inverse()
  254. }
  255. // #each block helper
  256. func eachHelper(context interface{}, options *Options) interface{} {
  257. if !IsTrue(context) {
  258. return options.Inverse()
  259. }
  260. result := ""
  261. val := reflect.ValueOf(context)
  262. switch val.Kind() {
  263. case reflect.Array, reflect.Slice:
  264. for i := 0; i < val.Len(); i++ {
  265. // computes private data
  266. data := options.newIterDataFrame(val.Len(), i, nil)
  267. // evaluates block
  268. result += options.evalBlock(val.Index(i).Interface(), data, i)
  269. }
  270. case reflect.Map:
  271. // note: a go hash is not ordered, so result may vary, this behaviour differs from the JS implementation
  272. keys := val.MapKeys()
  273. for i := 0; i < len(keys); i++ {
  274. key := keys[i].Interface()
  275. ctx := val.MapIndex(keys[i]).Interface()
  276. // computes private data
  277. data := options.newIterDataFrame(len(keys), i, key)
  278. // evaluates block
  279. result += options.evalBlock(ctx, data, key)
  280. }
  281. case reflect.Struct:
  282. var exportedFields []int
  283. // collect exported fields only
  284. for i := 0; i < val.NumField(); i++ {
  285. if tField := val.Type().Field(i); tField.PkgPath == "" {
  286. exportedFields = append(exportedFields, i)
  287. }
  288. }
  289. for i, fieldIndex := range exportedFields {
  290. key := val.Type().Field(fieldIndex).Name
  291. ctx := val.Field(fieldIndex).Interface()
  292. // computes private data
  293. data := options.newIterDataFrame(len(exportedFields), i, key)
  294. // evaluates block
  295. result += options.evalBlock(ctx, data, key)
  296. }
  297. }
  298. return result
  299. }
  300. // #log helper
  301. func logHelper(message string) interface{} {
  302. log.Print(message)
  303. return ""
  304. }
  305. // #lookup helper
  306. func lookupHelper(obj interface{}, field string, options *Options) interface{} {
  307. return Str(options.Eval(obj, field))
  308. }
  309. // #equal helper
  310. // Ref: https://github.com/aymerick/raymond/issues/7
  311. func equalHelper(a interface{}, b interface{}, options *Options) interface{} {
  312. if Str(a) == Str(b) {
  313. return options.Fn()
  314. }
  315. return ""
  316. }