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