github.com/zhongdalu/gf@v1.0.0/g/os/gview/gview_doparse.go (about) 1 // Copyright 2017 gf Author(https://github.com/zhongdalu/gf). 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/zhongdalu/gf. 6 7 package gview 8 9 import ( 10 "bytes" 11 "errors" 12 "fmt" 13 "github.com/zhongdalu/gf/g/container/gmap" 14 "github.com/zhongdalu/gf/g/encoding/ghash" 15 "github.com/zhongdalu/gf/g/os/gfcache" 16 "github.com/zhongdalu/gf/g/os/gfile" 17 "github.com/zhongdalu/gf/g/os/gfsnotify" 18 "github.com/zhongdalu/gf/g/os/glog" 19 "github.com/zhongdalu/gf/g/os/gmlock" 20 "github.com/zhongdalu/gf/g/os/gspath" 21 "github.com/zhongdalu/gf/g/text/gstr" 22 "github.com/zhongdalu/gf/g/util/gconv" 23 "text/template" 24 ) 25 26 const ( 27 // Template name for content parsing. 28 gCONTENT_TEMPLATE_NAME = "template content" 29 ) 30 31 var ( 32 // Templates cache map for template folder. 33 // TODO Note that there's no expiring logic for this map. 34 templates = gmap.NewStrAnyMap() 35 ) 36 37 // getTemplate returns the template object associated with given template folder <path>. 38 // It uses template cache to enhance performance, that is, it will return the same template object 39 // with the same given <path>. It will also refresh the template cache 40 // if the template files under <path> changes (recursively). 41 func (view *View) getTemplate(path string, pattern string) (tpl *template.Template, err error) { 42 r := templates.GetOrSetFuncLock(path, func() interface{} { 43 files := ([]string)(nil) 44 files, err = gfile.ScanDir(path, pattern, true) 45 if err != nil { 46 return nil 47 } 48 tpl = template.New(path).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap) 49 if tpl, err = tpl.ParseFiles(files...); err != nil { 50 return nil 51 } 52 _, _ = gfsnotify.Add(path, func(event *gfsnotify.Event) { 53 templates.Remove(path) 54 gfsnotify.Exit() 55 }) 56 return tpl 57 }) 58 if r != nil { 59 return r.(*template.Template), nil 60 } 61 return 62 } 63 64 // searchFile returns the found absolute path for <file>, and its template folder path. 65 func (view *View) searchFile(file string) (path string, folder string, err error) { 66 view.paths.RLockFunc(func(array []string) { 67 for _, v := range array { 68 if path, _ = gspath.Search(v, file); path != "" { 69 folder = v 70 break 71 } 72 if path, _ = gspath.Search(v+gfile.Separator+"template", file); path != "" { 73 folder = v + gfile.Separator + "template" 74 break 75 } 76 } 77 }) 78 if path == "" { 79 buffer := bytes.NewBuffer(nil) 80 if view.paths.Len() > 0 { 81 buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file)) 82 view.paths.RLockFunc(func(array []string) { 83 index := 1 84 for _, v := range array { 85 buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v)) 86 index++ 87 buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v+gfile.Separator+"template")) 88 index++ 89 } 90 }) 91 } else { 92 buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" with no path set/add", file)) 93 } 94 if errorPrint() { 95 glog.Error(buffer.String()) 96 } 97 err = errors.New(fmt.Sprintf(`template file "%s" not found`, file)) 98 } 99 return 100 } 101 102 // ParseContent parses given template file <file> 103 // with given template parameters <params> and function map <funcMap> 104 // and returns the parsed string content. 105 func (view *View) Parse(file string, params ...Params) (parsed string, err error) { 106 view.mu.RLock() 107 defer view.mu.RUnlock() 108 path, folder, err := view.searchFile(file) 109 if err != nil { 110 return "", err 111 } 112 tpl, err := view.getTemplate(folder, fmt.Sprintf(`*%s`, gfile.Ext(path))) 113 if err != nil { 114 return "", err 115 } 116 // Using memory lock to ensure concurrent safety for template parsing. 117 gmlock.LockFunc("gview-parsing:"+folder, func() { 118 tpl, err = tpl.Parse(gfcache.GetContents(path)) 119 }) 120 if err != nil { 121 return "", err 122 } 123 // Note that the template variable assignment cannot change the value 124 // of the existing <params> or view.data because both variables are pointers. 125 // It's need to merge the values of the two maps into a new map. 126 vars := (map[string]interface{})(nil) 127 length := len(view.data) 128 if len(params) > 0 { 129 length += len(params[0]) 130 } 131 if length > 0 { 132 vars = make(map[string]interface{}, length) 133 } 134 if len(view.data) > 0 { 135 if len(params) > 0 { 136 for k, v := range params[0] { 137 vars[k] = v 138 } 139 for k, v := range view.data { 140 vars[k] = v 141 } 142 } else { 143 vars = view.data 144 } 145 } else { 146 if len(params) > 0 { 147 vars = params[0] 148 } 149 } 150 buffer := bytes.NewBuffer(nil) 151 if err := tpl.Execute(buffer, vars); err != nil { 152 return "", err 153 } 154 return gstr.Replace(buffer.String(), "<no value>", ""), nil 155 } 156 157 // ParseContent parses given template content <content> 158 // with given template parameters <params> and function map <funcMap> 159 // and returns the parsed content in []byte. 160 func (view *View) ParseContent(content string, params ...Params) (string, error) { 161 view.mu.RLock() 162 defer view.mu.RUnlock() 163 err := (error)(nil) 164 tpl := templates.GetOrSetFuncLock(gCONTENT_TEMPLATE_NAME, func() interface{} { 165 return template.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap) 166 }).(*template.Template) 167 // Using memory lock to ensure concurrent safety for content parsing. 168 hash := gconv.String(ghash.DJBHash64([]byte(content))) 169 gmlock.LockFunc("gview-parsing-content:"+hash, func() { 170 tpl, err = tpl.Parse(content) 171 }) 172 if err != nil { 173 return "", err 174 } 175 // Note that the template variable assignment cannot change the value 176 // of the existing <params> or view.data because both variables are pointers. 177 // It's need to merge the values of the two maps into a new map. 178 vars := (map[string]interface{})(nil) 179 length := len(view.data) 180 if len(params) > 0 { 181 length += len(params[0]) 182 } 183 if length > 0 { 184 vars = make(map[string]interface{}, length) 185 } 186 if len(view.data) > 0 { 187 if len(params) > 0 { 188 for k, v := range params[0] { 189 vars[k] = v 190 } 191 for k, v := range view.data { 192 vars[k] = v 193 } 194 } else { 195 vars = view.data 196 } 197 } else { 198 if len(params) > 0 { 199 vars = params[0] 200 } 201 } 202 buffer := bytes.NewBuffer(nil) 203 if err := tpl.Execute(buffer, vars); err != nil { 204 return "", err 205 } 206 return gstr.Replace(buffer.String(), "<no value>", ""), nil 207 }