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  }