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  }