github.com/mattermost/mattermost-server/v5@v5.39.3/shared/templates/templates.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package templates 5 6 import ( 7 "bytes" 8 "html/template" 9 "io" 10 "os" 11 "path/filepath" 12 "sync" 13 14 "github.com/fsnotify/fsnotify" 15 "github.com/mattermost/mattermost-server/v5/utils/fileutils" 16 ) 17 18 // Container represents a set of templates that can be render 19 type Container struct { 20 templates *template.Template 21 mutex sync.RWMutex 22 stop chan struct{} 23 stopped chan struct{} 24 watch bool 25 } 26 27 // Data contains the data used to populate the template variables, it has Props 28 // that can be of any type and HTML that only can be `template.HTML` types. 29 type Data struct { 30 Props map[string]interface{} 31 HTML map[string]template.HTML 32 } 33 34 func GetTemplateDirectory() (string, bool) { 35 templatesDir := "templates" 36 if mattermostPath := os.Getenv("MM_SERVER_PATH"); mattermostPath != "" { 37 templatesDir = filepath.Join(mattermostPath, templatesDir) 38 } 39 40 return fileutils.FindDir(templatesDir) 41 } 42 43 // NewFromTemplates creates a new templates container using a 44 // `template.Template` object 45 func NewFromTemplate(templates *template.Template) *Container { 46 return &Container{templates: templates} 47 } 48 49 // New creates a new templates container scanning a directory. 50 func New(directory string) (*Container, error) { 51 c := &Container{} 52 53 htmlTemplates, err := template.ParseGlob(filepath.Join(directory, "*.html")) 54 if err != nil { 55 return nil, err 56 } 57 c.templates = htmlTemplates 58 59 return c, nil 60 } 61 62 // NewWithWatcher creates a new templates container scanning a directory and 63 // watch the directory filesystem changes to apply them to the loaded 64 // templates. This function returns the container and an errors channel to pass 65 // all errors that can happen during the watch process, or an regular error if 66 // we fail to create the templates or the watcher. The caller must consume the 67 // returned errors channel to ensure not blocking the watch process. 68 func NewWithWatcher(directory string) (*Container, <-chan error, error) { 69 htmlTemplates, err := template.ParseGlob(filepath.Join(directory, "*.html")) 70 if err != nil { 71 return nil, nil, err 72 } 73 74 watcher, err := fsnotify.NewWatcher() 75 if err != nil { 76 return nil, nil, err 77 } 78 79 err = watcher.Add(directory) 80 if err != nil { 81 watcher.Close() 82 return nil, nil, err 83 } 84 85 c := &Container{ 86 templates: htmlTemplates, 87 watch: true, 88 stop: make(chan struct{}), 89 stopped: make(chan struct{}), 90 } 91 errors := make(chan error) 92 93 go func() { 94 defer close(errors) 95 defer close(c.stopped) 96 defer watcher.Close() 97 98 for { 99 select { 100 case <-c.stop: 101 return 102 case event := <-watcher.Events: 103 if event.Op&fsnotify.Write == fsnotify.Write { 104 if htmlTemplates, err := template.ParseGlob(filepath.Join(directory, "*.html")); err != nil { 105 errors <- err 106 } else { 107 c.mutex.Lock() 108 c.templates = htmlTemplates 109 c.mutex.Unlock() 110 } 111 } 112 case err := <-watcher.Errors: 113 errors <- err 114 } 115 } 116 }() 117 118 return c, errors, nil 119 } 120 121 // Close stops the templates watcher of the container in case you have created 122 // it with watch parameter set to true 123 func (c *Container) Close() { 124 c.mutex.RLock() 125 defer c.mutex.RUnlock() 126 if c.watch { 127 close(c.stop) 128 <-c.stopped 129 } 130 } 131 132 // RenderToString renders the template referenced with the template name using 133 // the data provided and return a string with the result 134 func (c *Container) RenderToString(templateName string, data Data) (string, error) { 135 var text bytes.Buffer 136 if err := c.Render(&text, templateName, data); err != nil { 137 return "", err 138 } 139 return text.String(), nil 140 } 141 142 // RenderToString renders the template referenced with the template name using 143 // the data provided and write it to the writer provided 144 func (c *Container) Render(w io.Writer, templateName string, data Data) error { 145 c.mutex.RLock() 146 htmlTemplates := c.templates 147 c.mutex.RUnlock() 148 149 if err := htmlTemplates.ExecuteTemplate(w, templateName, data); err != nil { 150 return err 151 } 152 153 return nil 154 }