helper.go 9.0 KB

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