From dfa74faf431c627c44b943356a96bf00e43a4d6e Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Tue, 7 Feb 2017 18:28:03 +0200 Subject: [PATCH 1/2] support for multiple view paths --- controller.go | 16 +++++++++---- controller_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++ hooks.go | 2 +- template.go | 42 ++++++++++++++++++++++----------- template_test.go | 10 +++++++- 5 files changed, 109 insertions(+), 19 deletions(-) diff --git a/controller.go b/controller.go index a6fbdd23..488ffcda 100644 --- a/controller.go +++ b/controller.go @@ -69,6 +69,7 @@ type Controller struct { // template data TplName string + ViewPath string Layout string LayoutSections map[string]string // the key is the section name and the value is the template name TplPrefix string @@ -213,7 +214,7 @@ func (c *Controller) RenderBytes() ([]byte, error) { continue } buf.Reset() - err = ExecuteTemplate(&buf, sectionTpl, c.Data) + err = ExecuteViewPathTemplate(&buf, sectionTpl, c.viewPath(), c.Data) if err != nil { return nil, err } @@ -222,7 +223,7 @@ func (c *Controller) RenderBytes() ([]byte, error) { } buf.Reset() - ExecuteTemplate(&buf, c.Layout, c.Data) + ExecuteViewPathTemplate(&buf, c.Layout, c.viewPath() ,c.Data) } return buf.Bytes(), err } @@ -248,9 +249,16 @@ func (c *Controller) renderTemplate() (bytes.Buffer, error) { } } } - BuildTemplate(BConfig.WebConfig.ViewsPath, buildFiles...) + BuildTemplate(c.viewPath() , buildFiles...) } - return buf, ExecuteTemplate(&buf, c.TplName, c.Data) + return buf, ExecuteViewPathTemplate(&buf, c.TplName, c.viewPath(), c.Data) +} + +func (c *Controller) viewPath() string { + if c.ViewPath == "" { + return BConfig.WebConfig.ViewsPath + } + return c.ViewPath } // Redirect sends the redirection response to url with status code. diff --git a/controller_test.go b/controller_test.go index 132971a1..c2025860 100644 --- a/controller_test.go +++ b/controller_test.go @@ -20,6 +20,8 @@ import ( "testing" "github.com/astaxie/beego/context" + "os" + "path/filepath" ) func TestGetInt(t *testing.T) { @@ -121,3 +123,59 @@ func TestGetUint64(t *testing.T) { t.Errorf("TestGetUint64 expect %v,get %T,%v", uint64(math.MaxUint64), val, val) } } + +func TestAdditionalViewPaths(t *testing.T) { + dir1 := "_beeTmp" + dir2 := "_beeTmp2" + defer os.RemoveAll(dir1) + defer os.RemoveAll(dir2) + + dir1file := "file1.tpl" + dir2file := "file2.tpl" + + genFile := func(dir string, name string, content string) { + os.MkdirAll(filepath.Dir(filepath.Join(dir, name)), 0777) + if f, err := os.Create(filepath.Join(dir, name)); err != nil { + t.Fatal(err) + } else { + defer f.Close() + f.WriteString(content) + f.Close() + } + + } + genFile(dir1, dir1file, `
{{.Content}}
`) + genFile(dir2, dir2file, `{{.Content}}`) + + AddViewPath(dir1) + AddViewPath(dir2) + + ctrl := Controller{ + TplName: "file1.tpl", + ViewPath: dir1, + } + ctrl.Data = map[interface{}]interface{}{ + "Content": "value2", + } + if result, err := ctrl.RenderString(); err != nil { + t.Fatal(err) + } else { + if result != "
value2
" { + t.Fatalf("TestAdditionalViewPaths expect %s got %s", "
value2
", result) + } + } + + func() { + ctrl.TplName = "file2.tpl" + defer func() { + if r := recover(); r == nil { + t.Fatal("TestAdditionalViewPaths expected error") + } + }() + ctrl.RenderString(); + }() + + ctrl.TplName = "file2.tpl" + ctrl.ViewPath = dir2 + ctrl.RenderString(); +} diff --git a/hooks.go b/hooks.go index b5a5e6c5..528b58cc 100644 --- a/hooks.go +++ b/hooks.go @@ -72,7 +72,7 @@ func registerSession() error { } func registerTemplate() error { - if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil { + if err := AddViewPath(BConfig.WebConfig.ViewsPath); err != nil { if BConfig.RunMode == DEV { logs.Warn(err) } diff --git a/template.go b/template.go index 5415f5f0..d4d0ed12 100644 --- a/template.go +++ b/template.go @@ -32,8 +32,8 @@ import ( var ( beegoTplFuncMap = make(template.FuncMap) - // beeTemplates caching map and supported template file extensions. - beeTemplates = make(map[string]*template.Template) + // beeViewPathTemplates caching map and supported template file extensions. + beeViewPathTemplates = make(map[string]map[string]*template.Template) templatesLock sync.RWMutex // beeTemplateExt stores the template extension which will build beeTemplateExt = []string{"tpl", "html"} @@ -45,23 +45,30 @@ var ( // writing the output to wr. // A template will be executed safely in parallel. func ExecuteTemplate(wr io.Writer, name string, data interface{}) error { + return ExecuteViewPathTemplate(wr,name, BConfig.WebConfig.ViewsPath, data) +} + +func ExecuteViewPathTemplate(wr io.Writer, name string, viewPath string, data interface{}) error { if BConfig.RunMode == DEV { templatesLock.RLock() defer templatesLock.RUnlock() } - if t, ok := beeTemplates[name]; ok { - var err error - if t.Lookup(name) != nil { - err = t.ExecuteTemplate(wr, name, data) - } else { - err = t.Execute(wr, data) + if beeTemplates,ok := beeViewPathTemplates[viewPath]; ok { + if t, ok := beeTemplates[name]; ok { + var err error + if t.Lookup(name) != nil { + err = t.ExecuteTemplate(wr, name, data) + } else { + err = t.Execute(wr, data) + } + if err != nil { + logs.Trace("template Execute err:", err) + } + return err } - if err != nil { - logs.Trace("template Execute err:", err) - } - return err + panic("can't find templatefile in the path:" + viewPath + "/" + name) } - panic("can't find templatefile in the path:" + name) + panic("Uknown view path:" + viewPath) } func init() { @@ -149,6 +156,11 @@ func AddTemplateExt(ext string) { beeTemplateExt = append(beeTemplateExt, ext) } +func AddViewPath(viewPath string) error { + beeViewPathTemplates[viewPath] = make(map[string]*template.Template) + return BuildTemplate(viewPath) +} + // BuildTemplate will build all template files in a directory. // it makes beego can render any template file in view directory. func BuildTemplate(dir string, files ...string) error { @@ -158,6 +170,10 @@ func BuildTemplate(dir string, files ...string) error { } return errors.New("dir open err") } + beeTemplates,ok := beeViewPathTemplates[dir]; + if !ok { + panic("Unknown view path: " + dir) + } self := &templateFile{ root: dir, files: make(map[string][]string), diff --git a/template_test.go b/template_test.go index 4f13736c..17690965 100644 --- a/template_test.go +++ b/template_test.go @@ -67,9 +67,10 @@ func TestTemplate(t *testing.T) { f.Close() } } - if err := BuildTemplate(dir); err != nil { + if err := AddViewPath(dir); err != nil { t.Fatal(err) } + beeTemplates := beeViewPathTemplates[dir] if len(beeTemplates) != 3 { t.Fatalf("should be 3 but got %v", len(beeTemplates)) } @@ -103,6 +104,12 @@ var user = ` func TestRelativeTemplate(t *testing.T) { dir := "_beeTmp" + + //Just add dir to known viewPaths + if err := AddViewPath(dir); err != nil { + t.Fatal(err) + } + files := []string{ "easyui/public/menu.tpl", "easyui/rbac/user.tpl", @@ -126,6 +133,7 @@ func TestRelativeTemplate(t *testing.T) { if err := BuildTemplate(dir, files[1]); err != nil { t.Fatal(err) } + beeTemplates := beeViewPathTemplates[dir] if err := beeTemplates["easyui/rbac/user.tpl"].ExecuteTemplate(os.Stdout, "easyui/rbac/user.tpl", nil); err != nil { t.Fatal(err) } From fc2c0f4fbac53d3df06c706c7d79c77fc61e0ee1 Mon Sep 17 00:00:00 2001 From: Eyal Post Date: Sat, 11 Feb 2017 22:00:30 +0200 Subject: [PATCH 2/2] Don't allow AddViewPath after beego run --- hooks.go | 1 + template.go | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/hooks.go b/hooks.go index 528b58cc..091ecbc7 100644 --- a/hooks.go +++ b/hooks.go @@ -72,6 +72,7 @@ func registerSession() error { } func registerTemplate() error { + defer lockViewPaths() if err := AddViewPath(BConfig.WebConfig.ViewsPath); err != nil { if BConfig.RunMode == DEV { logs.Warn(err) diff --git a/template.go b/template.go index d4d0ed12..2dba9d1f 100644 --- a/template.go +++ b/template.go @@ -32,7 +32,8 @@ import ( var ( beegoTplFuncMap = make(template.FuncMap) - // beeViewPathTemplates caching map and supported template file extensions. + beeViewPathTemplateLocked = false + // beeViewPathTemplates caching map and supported template file extensions per view beeViewPathTemplates = make(map[string]map[string]*template.Template) templatesLock sync.RWMutex // beeTemplateExt stores the template extension which will build @@ -48,6 +49,9 @@ func ExecuteTemplate(wr io.Writer, name string, data interface{}) error { return ExecuteViewPathTemplate(wr,name, BConfig.WebConfig.ViewsPath, data) } +// ExecuteViewPathTemplate applies the template with name and from specific viewPath to the specified data object, +// writing the output to wr. +// A template will be executed safely in parallel. func ExecuteViewPathTemplate(wr io.Writer, name string, viewPath string, data interface{}) error { if BConfig.RunMode == DEV { templatesLock.RLock() @@ -156,11 +160,21 @@ func AddTemplateExt(ext string) { beeTemplateExt = append(beeTemplateExt, ext) } +// AddViewPath adds a new path to the supported view paths. +//Can later be used by setting a controller ViewPath to this folder +//will panic if called after beego.Run() func AddViewPath(viewPath string) error { + if beeViewPathTemplateLocked { + panic("Can not add new view paths after beego.Run()") + } beeViewPathTemplates[viewPath] = make(map[string]*template.Template) return BuildTemplate(viewPath) } +func lockViewPaths() { + beeViewPathTemplateLocked = true +} + // BuildTemplate will build all template files in a directory. // it makes beego can render any template file in view directory. func BuildTemplate(dir string, files ...string) error {