gview_config.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. package gview
  7. import (
  8. "context"
  9. "github.com/gogf/gf/v2/errors/gcode"
  10. "github.com/gogf/gf/v2/errors/gerror"
  11. "github.com/gogf/gf/v2/i18n/gi18n"
  12. "github.com/gogf/gf/v2/internal/intlog"
  13. "github.com/gogf/gf/v2/os/gfile"
  14. "github.com/gogf/gf/v2/os/glog"
  15. "github.com/gogf/gf/v2/os/gres"
  16. "github.com/gogf/gf/v2/os/gspath"
  17. "github.com/gogf/gf/v2/util/gconv"
  18. "github.com/gogf/gf/v2/util/gutil"
  19. )
  20. // Config is the configuration object for template engine.
  21. type Config struct {
  22. Paths []string `json:"paths"` // Searching array for path, NOT concurrent-safe for performance purpose.
  23. Data map[string]interface{} `json:"data"` // Global template variables including configuration.
  24. DefaultFile string `json:"defaultFile"` // Default template file for parsing.
  25. Delimiters []string `json:"delimiters"` // Custom template delimiters.
  26. AutoEncode bool `json:"autoEncode"` // Automatically encodes and provides safe html output, which is good for avoiding XSS.
  27. I18nManager *gi18n.Manager `json:"-"` // I18n manager for the view.
  28. }
  29. const (
  30. // Default template file for parsing.
  31. defaultParsingFile = "index.html"
  32. )
  33. // DefaultConfig creates and returns a configuration object with default configurations.
  34. func DefaultConfig() Config {
  35. return Config{
  36. DefaultFile: defaultParsingFile,
  37. I18nManager: gi18n.Instance(),
  38. Delimiters: make([]string, 2),
  39. }
  40. }
  41. // SetConfig sets the configuration for view.
  42. func (view *View) SetConfig(config Config) error {
  43. var err error
  44. if len(config.Paths) > 0 {
  45. for _, v := range config.Paths {
  46. if err = view.AddPath(v); err != nil {
  47. return err
  48. }
  49. }
  50. }
  51. if len(config.Data) > 0 {
  52. view.Assigns(config.Data)
  53. }
  54. if config.DefaultFile != "" {
  55. view.SetDefaultFile(config.DefaultFile)
  56. }
  57. if len(config.Delimiters) > 1 {
  58. view.SetDelimiters(config.Delimiters[0], config.Delimiters[1])
  59. }
  60. view.config = config
  61. // Clear global template object cache.
  62. // It's just cache, do not hesitate clearing it.
  63. templates.Clear()
  64. intlog.Printf(context.TODO(), "SetConfig: %+v", view.config)
  65. return nil
  66. }
  67. // SetConfigWithMap set configurations with map for the view.
  68. func (view *View) SetConfigWithMap(m map[string]interface{}) error {
  69. if len(m) == 0 {
  70. return gerror.NewCode(gcode.CodeInvalidParameter, "configuration cannot be empty")
  71. }
  72. // The m now is a shallow copy of m.
  73. // Any changes to m does not affect the original one.
  74. // A little tricky, isn't it?
  75. m = gutil.MapCopy(m)
  76. // Most common used configuration support for single view path.
  77. _, v1 := gutil.MapPossibleItemByKey(m, "paths")
  78. _, v2 := gutil.MapPossibleItemByKey(m, "path")
  79. if v1 == nil && v2 != nil {
  80. switch v2.(type) {
  81. case string:
  82. m["paths"] = []string{v2.(string)}
  83. case []string:
  84. m["paths"] = v2
  85. }
  86. }
  87. err := gconv.Struct(m, &view.config)
  88. if err != nil {
  89. return err
  90. }
  91. return view.SetConfig(view.config)
  92. }
  93. // SetPath sets the template directory path for template file search.
  94. // The parameter `path` can be absolute or relative path, but absolute path is suggested.
  95. func (view *View) SetPath(path string) error {
  96. var (
  97. ctx = context.TODO()
  98. isDir = false
  99. realPath = ""
  100. )
  101. if file := gres.Get(path); file != nil {
  102. realPath = path
  103. isDir = file.FileInfo().IsDir()
  104. } else {
  105. // Absolute path.
  106. realPath = gfile.RealPath(path)
  107. if realPath == "" {
  108. // Relative path.
  109. view.searchPaths.RLockFunc(func(array []string) {
  110. for _, v := range array {
  111. if path, _ := gspath.Search(v, path); path != "" {
  112. realPath = path
  113. break
  114. }
  115. }
  116. })
  117. }
  118. if realPath != "" {
  119. isDir = gfile.IsDir(realPath)
  120. }
  121. }
  122. // Path not exist.
  123. if realPath == "" {
  124. err := gerror.NewCodef(gcode.CodeInvalidParameter, `View.SetPath failed: path "%s" does not exist`, path)
  125. if errorPrint() {
  126. glog.Error(ctx, err)
  127. }
  128. return err
  129. }
  130. // Should be a directory.
  131. if !isDir {
  132. err := gerror.NewCodef(gcode.CodeInvalidParameter, `View.SetPath failed: path "%s" should be directory type`, path)
  133. if errorPrint() {
  134. glog.Error(ctx, err)
  135. }
  136. return err
  137. }
  138. // Repeated path adding check.
  139. if view.searchPaths.Search(realPath) != -1 {
  140. return nil
  141. }
  142. view.searchPaths.Clear()
  143. view.searchPaths.Append(realPath)
  144. view.fileCacheMap.Clear()
  145. return nil
  146. }
  147. // AddPath adds an absolute or relative path to the search paths.
  148. func (view *View) AddPath(path string) error {
  149. var (
  150. ctx = context.TODO()
  151. isDir = false
  152. realPath = ""
  153. )
  154. if file := gres.Get(path); file != nil {
  155. realPath = path
  156. isDir = file.FileInfo().IsDir()
  157. } else {
  158. // Absolute path.
  159. if realPath = gfile.RealPath(path); realPath == "" {
  160. // Relative path.
  161. view.searchPaths.RLockFunc(func(array []string) {
  162. for _, v := range array {
  163. if searchedPath, _ := gspath.Search(v, path); searchedPath != "" {
  164. realPath = searchedPath
  165. break
  166. }
  167. }
  168. })
  169. }
  170. if realPath != "" {
  171. isDir = gfile.IsDir(realPath)
  172. }
  173. }
  174. // Path not exist.
  175. if realPath == "" {
  176. err := gerror.NewCodef(gcode.CodeInvalidParameter, `View.AddPath failed: path "%s" does not exist`, path)
  177. if errorPrint() {
  178. glog.Error(ctx, err)
  179. }
  180. return err
  181. }
  182. // realPath should be type of folder.
  183. if !isDir {
  184. err := gerror.NewCodef(gcode.CodeInvalidParameter, `View.AddPath failed: path "%s" should be directory type`, path)
  185. if errorPrint() {
  186. glog.Error(ctx, err)
  187. }
  188. return err
  189. }
  190. // Repeated path adding check.
  191. if view.searchPaths.Search(realPath) != -1 {
  192. return nil
  193. }
  194. view.searchPaths.Append(realPath)
  195. view.fileCacheMap.Clear()
  196. return nil
  197. }
  198. // Assigns binds multiple global template variables to current view object.
  199. // Note that it's not concurrent-safe, which means it would panic
  200. // if it's called in multiple goroutines in runtime.
  201. func (view *View) Assigns(data Params) {
  202. for k, v := range data {
  203. view.data[k] = v
  204. }
  205. }
  206. // Assign binds a global template variable to current view object.
  207. // Note that it's not concurrent-safe, which means it would panic
  208. // if it's called in multiple goroutines in runtime.
  209. func (view *View) Assign(key string, value interface{}) {
  210. view.data[key] = value
  211. }
  212. // SetDefaultFile sets default template file for parsing.
  213. func (view *View) SetDefaultFile(file string) {
  214. view.config.DefaultFile = file
  215. }
  216. // GetDefaultFile returns default template file for parsing.
  217. func (view *View) GetDefaultFile() string {
  218. return view.config.DefaultFile
  219. }
  220. // SetDelimiters sets customized delimiters for template parsing.
  221. func (view *View) SetDelimiters(left, right string) {
  222. view.config.Delimiters = []string{left, right}
  223. }
  224. // SetAutoEncode enables/disables automatically html encoding feature.
  225. // When AutoEncode feature is enables, view engine automatically encodes and provides safe html output,
  226. // which is good for avoid XSS.
  227. func (view *View) SetAutoEncode(enable bool) {
  228. view.config.AutoEncode = enable
  229. }
  230. // BindFunc registers customized global template function named `name`
  231. // with given function `function` to current view object.
  232. // The `name` is the function name which can be called in template content.
  233. func (view *View) BindFunc(name string, function interface{}) {
  234. view.funcMap[name] = function
  235. // Clear global template object cache.
  236. templates.Clear()
  237. }
  238. // BindFuncMap registers customized global template functions by map to current view object.
  239. // The key of map is the template function name
  240. // and the value of map is the address of customized function.
  241. func (view *View) BindFuncMap(funcMap FuncMap) {
  242. for k, v := range funcMap {
  243. view.funcMap[k] = v
  244. }
  245. // Clear global template object cache.
  246. templates.Clear()
  247. }
  248. // SetI18n binds i18n manager to current view engine.
  249. func (view *View) SetI18n(manager *gi18n.Manager) {
  250. view.config.I18nManager = manager
  251. }