1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-22 11:25:59 +00:00

Publish the new version ✈️ | Look description please!

# FAQ

### Looking for free support?

	http://support.iris-go.com
    https://kataras.rocket.chat/channel/iris

### Looking for previous versions?

    https://github.com/kataras/iris#version

### Should I upgrade my Iris?

Developers are not forced to upgrade if they don't really need it. Upgrade whenever you feel ready.
> Iris uses the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature, so you get truly reproducible builds, as this method guards against upstream renames and deletes.

**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
For further installation support, please click [here](http://support.iris-go.com/d/16-how-to-install-iris-web-framework).

### About our new home page
    http://iris-go.com

Thanks to [Santosh Anand](https://github.com/santoshanand) the http://iris-go.com has been upgraded and it's really awesome!

[Santosh](https://github.com/santoshanand) is a freelancer, he has a great knowledge of nodejs and express js, Android, iOS, React Native, Vue.js etc, if you need a developer to find or create a solution for your problem or task, please contact with him.

The amount of the next two or three donations you'll send they will be immediately transferred to his own account balance, so be generous please!

Read more at https://github.com/kataras/iris/blob/master/HISTORY.md


Former-commit-id: eec2d71bbe011d6b48d2526eb25919e36e5ad94e
This commit is contained in:
kataras
2017-06-03 23:22:52 +03:00
parent 03bcadadec
commit 5e4b63acb2
330 changed files with 35786 additions and 17316 deletions

111
view/LICENSE Normal file
View File

@@ -0,0 +1,111 @@
Copyright (c) 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Gerasimos Maropoulos nor the name of his
username, kataras, may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Third-Parties:
The MIT License (MIT)
Copyright (c) 2013-2014 Florian Schlachter
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Copyright (c) 2015, Joker
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of jade nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
(The MIT License)
Copyright (c) 2012 Ekin Koc ekin@eknkc.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The MIT License (MIT)
Copyright (c) 2015 Aymerick JEHANNE
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

224
view/amber.go Normal file
View File

@@ -0,0 +1,224 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package view
import (
"fmt"
"html/template"
"io"
"path/filepath"
"strings"
"sync"
"github.com/eknkc/amber"
)
// AmberEngine contains the amber view engine structure.
type AmberEngine struct {
// files configuration
directory string
extension string
assetFn func(name string) ([]byte, error) // for embedded, in combination with directory & extension
namesFn func() []string // for embedded, in combination with directory & extension
reload bool
//
rmu sync.RWMutex // locks for funcs
funcs map[string]interface{}
mu sync.Mutex // locks for template files load
templateCache map[string]*template.Template
}
var _ Engine = &AmberEngine{}
// Amber creates and returns a new amber view engine.
func Amber(directory, extension string) *AmberEngine {
s := &AmberEngine{
directory: directory,
extension: extension,
templateCache: make(map[string]*template.Template, 0),
funcs: make(map[string]interface{}, 0),
}
return s
}
// Ext returns the file extension which this view engine is responsible to render.
func (s *AmberEngine) Ext() string {
return s.extension
}
// Binary optionally, use it when template files are distributed
// inside the app executable (.go generated files).
//
// The assetFn and namesFn can come from the go-bindata library.
func (s *AmberEngine) Binary(assetFn func(name string) ([]byte, error), namesFn func() []string) *AmberEngine {
s.assetFn, s.namesFn = assetFn, namesFn
return s
}
// Reload if setted to true the templates are reloading on each render,
// use it when you're in development and you're boring of restarting
// the whole app when you edit a template file.
func (s *AmberEngine) Reload(developmentMode bool) *AmberEngine {
s.reload = developmentMode
return s
}
// AddFunc adds the function to the template's function map.
// It is legal to overwrite elements of the default actions:
// - url func(routeName string, args ...string) string
// - urlpath func(routeName string, args ...string) string
// - render func(fullPartialName string) (template.HTML, error).
func (s *AmberEngine) AddFunc(funcName string, funcBody interface{}) {
s.rmu.Lock()
s.funcs[funcName] = funcBody
s.rmu.Unlock()
}
// Load parses the templates to the engine.
// It's alos responsible to add the necessary global functions.
//
// Returns an error if something bad happens, user is responsible to catch it.
func (s *AmberEngine) Load() error {
if s.assetFn != nil && s.namesFn != nil {
// embedded
return s.loadAssets()
}
// load from directory, make the dir absolute here too.
dir, err := filepath.Abs(s.directory)
if err != nil {
return err
}
// change the directory field configuration, load happens after directory has been setted, so we will not have any problems here.
s.directory = dir
return s.loadDirectory()
}
// loadDirectory loads the amber templates from directory.
func (s *AmberEngine) loadDirectory() error {
dir, extension := s.directory, s.extension
opt := amber.DirOptions{}
opt.Recursive = true
// prepare the global amber funcs
funcs := template.FuncMap{}
for k, v := range amber.FuncMap { // add the amber's default funcs
funcs[k] = v
}
for k, v := range s.funcs {
funcs[k] = v
}
amber.FuncMap = funcs //set the funcs
opt.Ext = extension
templates, err := amber.CompileDir(dir, opt, amber.DefaultOptions) // this returns the map with stripped extension, we want extension so we copy the map
if err == nil {
s.mu.Lock()
defer s.mu.Unlock()
s.templateCache = make(map[string]*template.Template)
for k, v := range templates {
name := filepath.ToSlash(k + opt.Ext)
s.templateCache[name] = v
delete(templates, k)
}
}
return err
}
// loadAssets builds the templates by binary, embedded.
func (s *AmberEngine) loadAssets() error {
virtualDirectory, virtualExtension := s.directory, s.extension
assetFn, namesFn := s.assetFn, s.namesFn
// prepare the global amber funcs
funcs := template.FuncMap{}
for k, v := range amber.FuncMap { // add the amber's default funcs
funcs[k] = v
}
for k, v := range s.funcs {
funcs[k] = v
}
if len(virtualDirectory) > 0 {
if virtualDirectory[0] == '.' { // first check for .wrong
virtualDirectory = virtualDirectory[1:]
}
if virtualDirectory[0] == '/' || virtualDirectory[0] == filepath.Separator { // second check for /something, (or ./something if we had dot on 0 it will be removed
virtualDirectory = virtualDirectory[1:]
}
}
amber.FuncMap = funcs //set the funcs
s.mu.Lock()
defer s.mu.Unlock()
names := namesFn()
for _, path := range names {
if !strings.HasPrefix(path, virtualDirectory) {
continue
}
ext := filepath.Ext(path)
if ext == virtualExtension {
rel, err := filepath.Rel(virtualDirectory, path)
if err != nil {
return err
}
buf, err := assetFn(path)
if err != nil {
return err
}
name := filepath.ToSlash(rel)
tmpl, err := amber.CompileData(buf, name, amber.DefaultOptions)
if err != nil {
return err
}
s.templateCache[name] = tmpl
}
}
return nil
}
func (s *AmberEngine) fromCache(relativeName string) *template.Template {
s.mu.Lock()
tmpl, ok := s.templateCache[relativeName]
if ok {
s.mu.Unlock()
return tmpl
}
s.mu.Unlock()
return nil
}
// ExecuteWriter executes a template and writes its result to the w writer.
// layout here is useless.
func (s *AmberEngine) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error {
// reload the templates if reload configuration field is true
if s.reload {
if err := s.Load(); err != nil {
return err
}
}
if tmpl := s.fromCache(filename); tmpl != nil {
return tmpl.ExecuteTemplate(w, filename, bindingData)
}
return fmt.Errorf("Template with name %s doesn't exists in the dir", filename)
}

303
view/django.go Normal file
View File

@@ -0,0 +1,303 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package view
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"github.com/flosch/pongo2"
)
type (
// Value conversion for pongo2.Value
Value pongo2.Value
// Error conversion for pongo2.Error
Error pongo2.Error
// FilterFunction conversion for pongo2.FilterFunction
FilterFunction func(in *Value, param *Value) (out *Value, err *Error)
)
// DjangoEngine contains the amber view engine structure.
type DjangoEngine struct {
// files configuration
directory string
extension string
assetFn func(name string) ([]byte, error) // for embedded, in combination with directory & extension
namesFn func() []string // for embedded, in combination with directory & extension
reload bool
//
rmu sync.RWMutex // locks for filters and globals
// filters for pongo2, map[name of the filter] the filter function . The filters are auto register
filters map[string]FilterFunction
// globals share context fields between templates. https://github.com/flosch/pongo2/issues/35
globals map[string]interface{}
mu sync.Mutex // locks for template cache
templateCache map[string]*pongo2.Template
}
var _ Engine = &DjangoEngine{}
// Django creates and returns a new amber view engine.
func Django(directory, extension string) *DjangoEngine {
s := &DjangoEngine{
directory: directory,
extension: extension,
globals: make(map[string]interface{}, 0),
filters: make(map[string]FilterFunction, 0),
templateCache: make(map[string]*pongo2.Template, 0),
}
return s
}
// Ext returns the file extension which this view engine is responsible to render.
func (s *DjangoEngine) Ext() string {
return s.extension
}
// Binary optionally, use it when template files are distributed
// inside the app executable (.go generated files).
//
// The assetFn and namesFn can come from the go-bindata library.
func (s *DjangoEngine) Binary(assetFn func(name string) ([]byte, error), namesFn func() []string) *DjangoEngine {
s.assetFn, s.namesFn = assetFn, namesFn
return s
}
// Reload if setted to true the templates are reloading on each render,
// use it when you're in development and you're boring of restarting
// the whole app when you edit a template file.
func (s *DjangoEngine) Reload(developmentMode bool) *DjangoEngine {
s.reload = developmentMode
return s
}
// AddFunc adds the function to the template's Globals.
// It is legal to overwrite elements of the default actions:
// - url func(routeName string, args ...string) string
// - urlpath func(routeName string, args ...string) string
// - render func(fullPartialName string) (template.HTML, error).
func (s *DjangoEngine) AddFunc(funcName string, funcBody interface{}) {
s.rmu.Lock()
s.globals[funcName] = funcBody
s.rmu.Unlock()
}
// AddFilter adds a filter to the template.
func (s *DjangoEngine) AddFilter(filterName string, filterBody FilterFunction) *DjangoEngine {
s.rmu.Lock()
s.filters[filterName] = filterBody
s.rmu.Unlock()
return s
}
// Load parses the templates to the engine.
// It's alos responsible to add the necessary global functions.
//
// Returns an error if something bad happens, user is responsible to catch it.
func (s *DjangoEngine) Load() error {
if s.assetFn != nil && s.namesFn != nil {
// embedded
return s.loadAssets()
}
// load from directory, make the dir absolute here too.
dir, err := filepath.Abs(s.directory)
if err != nil {
return err
}
// change the directory field configuration, load happens after directory has been setted, so we will not have any problems here.
s.directory = dir
return s.loadDirectory()
}
// this exists because of moving the pongo2 to the vendors without conflictitions if users
// wants to register pongo2 filters they can use this django.FilterFunc to do so.
func (s *DjangoEngine) convertFilters() map[string]pongo2.FilterFunction {
filters := make(map[string]pongo2.FilterFunction, len(s.filters))
for k, v := range s.filters {
func(filterName string, filterFunc FilterFunction) {
fn := pongo2.FilterFunction(func(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
theOut, theErr := filterFunc((*Value)(in), (*Value)(param))
return (*pongo2.Value)(theOut), (*pongo2.Error)(theErr)
})
filters[filterName] = fn
}(k, v)
}
return filters
}
// LoadDirectory loads the templates from directory.
func (s *DjangoEngine) loadDirectory() (templateErr error) {
dir, extension := s.directory, s.extension
fsLoader, err := pongo2.NewLocalFileSystemLoader(dir) // I see that this doesn't read the content if already parsed, so do it manually via filepath.Walk
if err != nil {
return err
}
set := pongo2.NewSet("", fsLoader)
set.Globals = getPongoContext(s.globals)
// set the filters
filters := s.convertFilters()
for filterName, filterFunc := range filters {
pongo2.RegisterFilter(filterName, filterFunc)
}
s.mu.Lock()
defer s.mu.Unlock()
// Walk the supplied directory and compile any files that match our extension list.
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
// Fix same-extension-dirs bug: some dir might be named to: "users.tmpl", "local.html".
// These dirs should be excluded as they are not valid golang templates, but files under
// them should be treat as normal.
// If is a dir, return immediately (dir is not a valid golang template).
if info == nil || info.IsDir() {
} else {
rel, err := filepath.Rel(dir, path)
if err != nil {
templateErr = err
return err
}
ext := filepath.Ext(rel)
if ext == extension {
buf, err := ioutil.ReadFile(path)
if err != nil {
templateErr = err
return err
}
name := filepath.ToSlash(rel)
s.templateCache[name], templateErr = set.FromString(string(buf))
if templateErr != nil {
return templateErr
}
}
}
return nil
})
return
}
// loadAssets loads the templates by binary (go-bindata for embedded).
func (s *DjangoEngine) loadAssets() error {
virtualDirectory, virtualExtension := s.directory, s.extension
assetFn, namesFn := s.assetFn, s.namesFn
var templateErr error
/*fsLoader, err := pongo2.NewLocalFileSystemLoader(virtualDirectory)
if err != nil {
return err
}*/
set := pongo2.NewSet("", pongo2.DefaultLoader)
set.Globals = getPongoContext(s.globals)
// set the filters
filters := s.convertFilters()
for filterName, filterFunc := range filters {
pongo2.RegisterFilter(filterName, filterFunc)
}
if len(virtualDirectory) > 0 {
if virtualDirectory[0] == '.' { // first check for .wrong
virtualDirectory = virtualDirectory[1:]
}
if virtualDirectory[0] == '/' || virtualDirectory[0] == os.PathSeparator { // second check for /something, (or ./something if we had dot on 0 it will be removed
virtualDirectory = virtualDirectory[1:]
}
}
s.mu.Lock()
defer s.mu.Unlock()
names := namesFn()
for _, path := range names {
if !strings.HasPrefix(path, virtualDirectory) {
continue
}
rel, err := filepath.Rel(virtualDirectory, path)
if err != nil {
templateErr = err
return err
}
ext := filepath.Ext(rel)
if ext == virtualExtension {
buf, err := assetFn(path)
if err != nil {
templateErr = err
return err
}
name := filepath.ToSlash(rel)
s.templateCache[name], err = set.FromString(string(buf))
if err != nil {
templateErr = err
return err
}
}
}
return templateErr
}
// getPongoContext returns the pongo2.Context from map[string]interface{} or from pongo2.Context, used internaly
func getPongoContext(templateData interface{}) pongo2.Context {
if templateData == nil {
return nil
}
if contextData, isPongoContext := templateData.(pongo2.Context); isPongoContext {
return contextData
}
return templateData.(map[string]interface{})
}
func (s *DjangoEngine) fromCache(relativeName string) *pongo2.Template {
s.mu.Lock()
tmpl, ok := s.templateCache[relativeName]
if ok {
s.mu.Unlock()
return tmpl
}
s.mu.Unlock()
return nil
}
// ExecuteWriter executes a templates and write its results to the w writer
// layout here is useless.
func (s *DjangoEngine) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error {
// reload the templates if reload configuration field is true
if s.reload {
if err := s.Load(); err != nil {
return err
}
}
if tmpl := s.fromCache(filename); tmpl != nil {
return tmpl.ExecuteWriter(getPongoContext(bindingData), w)
}
return fmt.Errorf("template with name %s doesn't exists in the dir", filename)
}

44
view/engine.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package view
import (
"io"
)
// NoLayout disables the configuration's layout for a specific execution.
const NoLayout = "iris.nolayout"
// returns empty if it's no layout or empty layout and empty configuration's layout.
func getLayout(layout string, globalLayout string) string {
if layout == NoLayout {
return ""
}
if layout == "" && globalLayout != "" {
return globalLayout
}
return layout
}
// Options should contains the dynamic options for the engine's ExecuteWriter.
type Options interface {
// the per-execute layout,
// most view engines will have a static configuration field for that too.
GetLayout() string
// should returns the dynamic binding data, which will be used inside the template file
GetData() interface{}
} // this Options interface is implemented inside context, in order to use one import path for all context's methods.
// Engine is the interface which all viwe engines should be implemented in order to be adapted inside Iris.
type Engine interface {
// Load should load the templates from a directory of by binary(assets/go-bindata).
Load() error
// ExecuteWriter should execute a template by its filename with an optional layout and bindingData.
ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error
// Ext should return the final file extension which this view engine is responsible to render.
Ext() string
}

22
view/funcs.go Normal file
View File

@@ -0,0 +1,22 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package view
// EngineFuncer is an addition of a view engine,
// if a view engine implements that interface
// then iris can add some closed-relative iris functions
// like {{ urlpath }} and {{ urlpath }}.
type EngineFuncer interface {
// AddFunc should adds a function to the template's function map.
AddFunc(funcName string, funcBody interface{})
}
// these will be added to all template engines used
// and completes the EngineFuncer interface.
//
// There are a lot of default functions but some of them are placed inside of each
// template engine because of the different behavior, i.e urlpath and url are inside framework itself,
// yield,partial,partial_r,current and render as inside html engine etc...
var defaultSharedFuncs = map[string]interface{}{}

293
view/handlebars.go Normal file
View File

@@ -0,0 +1,293 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package view
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"github.com/aymerick/raymond"
)
// HandlebarsEngine contains the handlebars view engine structure.
type HandlebarsEngine struct {
// files configuration
directory string
extension string
assetFn func(name string) ([]byte, error) // for embedded, in combination with directory & extension
namesFn func() []string // for embedded, in combination with directory & extension
reload bool // if true, each time the ExecuteWriter is called the templates will be reloaded.
// parser configuration
layout string
rmu sync.RWMutex // locks for helpers
helpers map[string]interface{}
mu sync.Mutex // locks for template files load
templateCache map[string]*raymond.Template
}
// Handlebars creates and returns a new handlebars view engine.
func Handlebars(directory, extension string) *HandlebarsEngine {
s := &HandlebarsEngine{
directory: directory,
extension: extension,
templateCache: make(map[string]*raymond.Template, 0),
helpers: make(map[string]interface{}, 0),
}
// register the render helper here
raymond.RegisterHelper("render", func(partial string, binding interface{}) raymond.SafeString {
contents, err := s.executeTemplateBuf(partial, binding)
if err != nil {
return raymond.SafeString("template with name: " + partial + " couldn't not be found.")
}
return raymond.SafeString(contents)
})
return s
}
// Ext returns the file extension which this view engine is responsible to render.
func (s *HandlebarsEngine) Ext() string {
return s.extension
}
// Binary optionally, use it when template files are distributed
// inside the app executable (.go generated files).
//
// The assetFn and namesFn can come from the go-bindata library.
func (s *HandlebarsEngine) Binary(assetFn func(name string) ([]byte, error), namesFn func() []string) *HandlebarsEngine {
s.assetFn, s.namesFn = assetFn, namesFn
return s
}
// Reload if setted to true the templates are reloading on each render,
// use it when you're in development and you're boring of restarting
// the whole app when you edit a template file.
func (s *HandlebarsEngine) Reload(developmentMode bool) *HandlebarsEngine {
s.reload = developmentMode
return s
}
// Layout sets the layout template file which should use
// the {{ yield }} func to yield the main template file
// and optionally {{partial/partial_r/render}} to render
// other template files like headers and footers.
func (s *HandlebarsEngine) Layout(layoutFile string) *HandlebarsEngine {
s.layout = layoutFile
return s
}
// AddFunc adds the function to the template's function map.
// It is legal to overwrite elements of the default actions:
// - url func(routeName string, args ...string) string
// - urlpath func(routeName string, args ...string) string
// - render func(fullPartialName string) (raymond.HTML, error).
func (s *HandlebarsEngine) AddFunc(funcName string, funcBody interface{}) {
s.rmu.Lock()
s.helpers[funcName] = funcBody
s.rmu.Unlock()
}
// Load parses the templates to the engine.
// It's alos responsible to add the necessary global functions.
//
// Returns an error if something bad happens, user is responsible to catch it.
func (s *HandlebarsEngine) Load() error {
if s.assetFn != nil && s.namesFn != nil {
// embedded
return s.loadAssets()
}
// load from directory, make the dir absolute here too.
dir, err := filepath.Abs(s.directory)
if err != nil {
return err
}
// change the directory field configuration, load happens after directory has been setted, so we will not have any problems here.
s.directory = dir
return s.loadDirectory()
}
// loadDirectory builds the handlebars templates from directory.
func (s *HandlebarsEngine) loadDirectory() error {
// register the global helpers on the first load
if len(s.templateCache) == 0 && s.helpers != nil {
raymond.RegisterHelpers(s.helpers)
}
dir, extension := s.directory, s.extension
// the render works like {{ render "myfile.html" theContext.PartialContext}}
// instead of the html/template engine which works like {{ render "myfile.html"}} and accepts the parent binding, with handlebars we can't do that because of lack of runtime helpers (dublicate error)
s.mu.Lock()
defer s.mu.Unlock()
var templateErr error
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if info == nil || info.IsDir() {
return nil
}
rel, err := filepath.Rel(dir, path)
if err != nil {
return err
}
ext := filepath.Ext(rel)
if ext == extension {
buf, err := ioutil.ReadFile(path)
contents := string(buf)
if err != nil {
templateErr = err
return err
}
name := filepath.ToSlash(rel)
tmpl, err := raymond.Parse(contents)
if err != nil {
templateErr = err
return err
}
s.templateCache[name] = tmpl
}
return nil
})
return templateErr
}
// loadAssets loads the templates by binary, embedded.
func (s *HandlebarsEngine) loadAssets() error {
// register the global helpers
if len(s.templateCache) == 0 && s.helpers != nil {
raymond.RegisterHelpers(s.helpers)
}
virtualDirectory, virtualExtension := s.directory, s.extension
assetFn, namesFn := s.assetFn, s.namesFn
if len(virtualDirectory) > 0 {
if virtualDirectory[0] == '.' { // first check for .wrong
virtualDirectory = virtualDirectory[1:]
}
if virtualDirectory[0] == '/' || virtualDirectory[0] == os.PathSeparator { // second check for /something, (or ./something if we had dot on 0 it will be removed
virtualDirectory = virtualDirectory[1:]
}
}
var templateErr error
s.mu.Lock()
defer s.mu.Unlock()
names := namesFn()
for _, path := range names {
if !strings.HasPrefix(path, virtualDirectory) {
continue
}
ext := filepath.Ext(path)
if ext == virtualExtension {
rel, err := filepath.Rel(virtualDirectory, path)
if err != nil {
templateErr = err
return err
}
buf, err := assetFn(path)
if err != nil {
templateErr = err
return err
}
contents := string(buf)
name := filepath.ToSlash(rel)
tmpl, err := raymond.Parse(contents)
if err != nil {
templateErr = err
return err
}
s.templateCache[name] = tmpl
}
}
return templateErr
}
func (s *HandlebarsEngine) fromCache(relativeName string) *raymond.Template {
s.mu.Lock()
tmpl, ok := s.templateCache[relativeName]
if ok {
s.mu.Unlock()
return tmpl
}
s.mu.Unlock()
return nil
}
func (s *HandlebarsEngine) executeTemplateBuf(name string, binding interface{}) (string, error) {
if tmpl := s.fromCache(name); tmpl != nil {
return tmpl.Exec(binding)
}
return "", nil
}
// ExecuteWriter executes a template and writes its result to the w writer.
func (s *HandlebarsEngine) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error {
// reload the templates if reload configuration field is true
if s.reload {
if err := s.Load(); err != nil {
return err
}
}
isLayout := false
layout = getLayout(layout, s.layout)
renderFilename := filename
if layout != "" {
isLayout = true
renderFilename = layout // the render becomes the layout, and the name is the partial.
}
if tmpl := s.fromCache(renderFilename); tmpl != nil {
binding := bindingData
if isLayout {
var context map[string]interface{}
if m, is := binding.(map[string]interface{}); is { //handlebars accepts maps,
context = m
} else {
return fmt.Errorf("Please provide a map[string]interface{} type as the binding instead of the %#v", binding)
}
contents, err := s.executeTemplateBuf(filename, binding)
if err != nil {
return err
}
if context == nil {
context = make(map[string]interface{}, 1)
}
// I'm implemented the {{ yield }} as with the rest of template engines, so this is not inneed for iris, but the user can do that manually if want
// there is no performanrce different: raymond.RegisterPartialTemplate(name, tmpl)
context["yield"] = raymond.SafeString(contents)
}
res, err := tmpl.Exec(binding)
if err != nil {
return err
}
_, err = fmt.Fprint(w, res)
return err
}
return fmt.Errorf("template with name %s[original name = %s] doesn't exists in the dir", renderFilename, filename)
}

381
view/html.go Normal file
View File

@@ -0,0 +1,381 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package view
import (
"bytes"
"fmt"
"html/template"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
)
type (
// HTMLEngine contains the html view engine structure.
HTMLEngine struct {
// files configuration
directory string
extension string
assetFn func(name string) ([]byte, error) // for embedded, in combination with directory & extension
namesFn func() []string // for embedded, in combination with directory & extension
reload bool // if true, each time the ExecuteWriter is called the templates will be reloaded.
// parser configuration
left string
right string
layout string
rmu sync.RWMutex // locks for layoutFuncs and funcs
layoutFuncs map[string]interface{}
funcs map[string]interface{}
//
middleware func(name string, contents string) (string, error)
mu sync.Mutex // locks for template files loader
Templates *template.Template
//
}
)
var _ Engine = &HTMLEngine{}
var emptyFuncs = template.FuncMap{
"yield": func() (string, error) {
return "", fmt.Errorf("yield was called, yet no layout defined")
},
"partial": func() (string, error) {
return "", fmt.Errorf("block was called, yet no layout defined")
},
"partial_r": func() (string, error) {
return "", fmt.Errorf("block was called, yet no layout defined")
},
"current": func() (string, error) {
return "", nil
}, "render": func() (string, error) {
return "", nil
},
}
// HTML creates and returns a new html view engine.
// The html engine used like the "html/template" standard go package
// but with a lot of extra features.
func HTML(directory, extension string) *HTMLEngine {
s := &HTMLEngine{
directory: directory,
extension: extension,
assetFn: nil,
namesFn: nil,
reload: false,
left: "{{",
right: "}}",
layout: "",
layoutFuncs: make(map[string]interface{}, 0),
funcs: make(map[string]interface{}, 0),
}
return s
}
// Ext returns the file extension which this view engine is responsible to render.
func (s *HTMLEngine) Ext() string {
return s.extension
}
// Binary optionally, use it when template files are distributed
// inside the app executable (.go generated files).
//
// The assetFn and namesFn can come from the go-bindata library.
func (s *HTMLEngine) Binary(assetFn func(name string) ([]byte, error), namesFn func() []string) *HTMLEngine {
s.assetFn, s.namesFn = assetFn, namesFn
return s
}
// Reload if setted to true the templates are reloading on each render,
// use it when you're in development and you're boring of restarting
// the whole app when you edit a template file.
func (s *HTMLEngine) Reload(developmentMode bool) *HTMLEngine {
s.reload = developmentMode
return s
}
// Delims sets the action delimiters to the specified strings, to be used in
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
// definitions will inherit the settings. An empty delimiter stands for the
// corresponding default: {{ or }}.
func (s *HTMLEngine) Delims(left, right string) *HTMLEngine {
s.left, s.right = left, right
return s
}
// Layout sets the layout template file which inside should use
// the {{ yield }} func to yield the main template file
// and optionally {{partial/partial_r/render}} to render other template files like headers and footers
//
// The 'tmplLayoutFile' is a relative path of the templates base directory,
// for the template file with its extension.
//
// Example: HTML("./templates", ".html").Layout("layouts/mainLayout.html")
// // mainLayout.html is inside: "./templates/layouts/".
//
// Note: Layout can be changed for a specific call
// action with the option: "layout" on the Iris' context.Render function.
func (s *HTMLEngine) Layout(layoutFile string) *HTMLEngine {
s.layout = layoutFile
return s
}
// AddLayoutFunc adds the function to the template's layout-only function map.
// It is legal to overwrite elements of the default layout actions:
// - yield func() (template.HTML, error)
// - current func() (string, error)
// - partial func(partialName string) (template.HTML, error)
// - partial_r func(partialName string) (template.HTML, error)
// - render func(fullPartialName string) (template.HTML, error).
func (s *HTMLEngine) AddLayoutFunc(funcName string, funcBody interface{}) *HTMLEngine {
s.rmu.Lock()
s.layoutFuncs[funcName] = funcBody
s.rmu.Unlock()
return s
}
// AddFunc adds the function to the template's function map.
// It is legal to overwrite elements of the default actions:
// - url func(routeName string, args ...string) string
// - urlpath func(routeName string, args ...string) string
// - render func(fullPartialName string) (template.HTML, error).
func (s *HTMLEngine) AddFunc(funcName string, funcBody interface{}) {
s.rmu.Lock()
s.funcs[funcName] = funcBody
s.rmu.Unlock()
}
// Load parses the templates to the engine.
// It's alos responsible to add the necessary global functions.
//
// Returns an error if something bad happens, user is responsible to catch it.
func (s *HTMLEngine) Load() error {
if s.assetFn != nil && s.namesFn != nil {
// embedded
return s.loadAssets()
}
// load from directory, make the dir absolute here too.
dir, err := filepath.Abs(s.directory)
if err != nil {
return err
}
// change the directory field configuration, load happens after directory has been setted, so we will not have any problems here.
s.directory = dir
return s.loadDirectory()
}
// loadDirectory builds the templates from directory.
func (s *HTMLEngine) loadDirectory() error {
dir, extension := s.directory, s.extension
var templateErr error
s.Templates = template.New(dir)
s.Templates.Delims(s.left, s.right)
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if info == nil || info.IsDir() {
} else {
rel, err := filepath.Rel(dir, path)
if err != nil {
templateErr = err
return err
}
ext := filepath.Ext(path)
if ext == extension {
buf, err := ioutil.ReadFile(path)
if err != nil {
templateErr = err
return err
}
contents := string(buf)
if err == nil {
name := filepath.ToSlash(rel)
tmpl := s.Templates.New(name)
if s.middleware != nil {
contents, err = s.middleware(name, contents)
}
if err != nil {
templateErr = err
return err
}
s.mu.Lock()
// Add our funcmaps.
if s.funcs != nil {
tmpl.Funcs(s.funcs)
}
tmpl.Funcs(emptyFuncs).Parse(contents)
s.mu.Unlock()
}
}
}
return nil
})
return templateErr
}
// loadAssets loads the templates by binary (go-bindata for embedded).
func (s *HTMLEngine) loadAssets() error {
virtualDirectory, virtualExtension := s.directory, s.extension
assetFn, namesFn := s.assetFn, s.namesFn
var templateErr error
s.Templates = template.New(virtualDirectory)
s.Templates.Delims(s.left, s.right)
names := namesFn()
if len(virtualDirectory) > 0 {
if virtualDirectory[0] == '.' { // first check for .wrong
virtualDirectory = virtualDirectory[1:]
}
if virtualDirectory[0] == '/' || virtualDirectory[0] == os.PathSeparator { // second check for /something, (or ./something if we had dot on 0 it will be removed
virtualDirectory = virtualDirectory[1:]
}
}
for _, path := range names {
if !strings.HasPrefix(path, virtualDirectory) {
continue
}
ext := filepath.Ext(path)
if ext == virtualExtension {
rel, err := filepath.Rel(virtualDirectory, path)
if err != nil {
templateErr = err
return err
}
buf, err := assetFn(path)
if err != nil {
templateErr = err
return err
}
contents := string(buf)
name := filepath.ToSlash(rel)
tmpl := s.Templates.New(name)
if s.middleware != nil {
contents, err = s.middleware(name, contents)
}
if err != nil {
templateErr = err
return err
}
// Add our funcmaps.
if s.funcs != nil {
tmpl.Funcs(s.funcs)
}
tmpl.Funcs(emptyFuncs).Parse(contents)
}
}
return templateErr
}
func (s *HTMLEngine) executeTemplateBuf(name string, binding interface{}) (*bytes.Buffer, error) {
buf := new(bytes.Buffer)
err := s.Templates.ExecuteTemplate(buf, name, binding)
return buf, err
}
func (s *HTMLEngine) layoutFuncsFor(name string, binding interface{}) {
funcs := template.FuncMap{
"yield": func() (template.HTML, error) {
buf, err := s.executeTemplateBuf(name, binding)
// Return safe HTML here since we are rendering our own template.
return template.HTML(buf.String()), err
},
"current": func() (string, error) {
return name, nil
},
"partial": func(partialName string) (template.HTML, error) {
fullPartialName := fmt.Sprintf("%s-%s", partialName, name)
if s.Templates.Lookup(fullPartialName) != nil {
buf, err := s.executeTemplateBuf(fullPartialName, binding)
return template.HTML(buf.String()), err
}
return "", nil
},
//partial related to current page,
//it would be easier for adding pages' style/script inline
//for example when using partial_r '.script' in layout.html
//templates/users/index.html would load templates/users/index.script.html
"partial_r": func(partialName string) (template.HTML, error) {
ext := filepath.Ext(name)
root := name[:len(name)-len(ext)]
fullPartialName := fmt.Sprintf("%s%s%s", root, partialName, ext)
if s.Templates.Lookup(fullPartialName) != nil {
buf, err := s.executeTemplateBuf(fullPartialName, binding)
return template.HTML(buf.String()), err
}
return "", nil
},
"render": func(fullPartialName string) (template.HTML, error) {
buf, err := s.executeTemplateBuf(fullPartialName, binding)
return template.HTML(buf.String()), err
},
}
s.rmu.RLock()
for k, v := range s.layoutFuncs {
funcs[k] = v
}
s.rmu.RUnlock()
if tpl := s.Templates.Lookup(name); tpl != nil {
tpl.Funcs(funcs)
}
}
func (s *HTMLEngine) runtimeFuncsFor(name string, binding interface{}) {
funcs := template.FuncMap{
"render": func(fullPartialName string) (template.HTML, error) {
buf, err := s.executeTemplateBuf(fullPartialName, binding)
return template.HTML(buf.String()), err
},
}
if tpl := s.Templates.Lookup(name); tpl != nil {
tpl.Funcs(funcs)
}
}
// ExecuteWriter executes a template and writes its result to the w writer.
func (s *HTMLEngine) ExecuteWriter(w io.Writer, name string, layout string, bindingData interface{}) error {
// reload the templates if reload configuration field is true
if s.reload {
if err := s.Load(); err != nil {
return err
}
}
layout = getLayout(layout, s.layout)
if layout != "" {
s.layoutFuncsFor(name, bindingData)
name = layout
} else {
s.runtimeFuncsFor(name, bindingData)
}
return s.Templates.ExecuteTemplate(w, name, bindingData)
}

21
view/pug.go Normal file
View File

@@ -0,0 +1,21 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package view
import (
"github.com/Joker/jade"
)
// Pug (or Jade) returns a new pug view engine.
// It shares the same exactly logic with the
// html view engine, it uses the same exactly configuration.
// It has got some features and a lot of functions
// which will make your life easier.
// Read more about the Jade Go Template: https://github.com/Joker/jade
func Pug(directory, extension string) *HTMLEngine {
s := HTML(directory, extension)
s.middleware = jade.Parse
return s
}

79
view/view.go Normal file
View File

@@ -0,0 +1,79 @@
// Copyright 2017 Gerasimos Maropoulos, ΓΜ. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package view
import (
"io"
"path/filepath"
"github.com/kataras/iris/core/errors"
)
// View is responsible to
// load the correct templates
// for each of the registered view engines.
type View struct {
engines []Engine
}
// Register loads all the view engines' template files or embedded assets.
func (v *View) Register(e Engine) error {
v.engines = append(v.engines, e)
return nil
}
// Find receives a filename, gets its extension and returns the view engine responsible for that file extension
func (v *View) Find(filename string) Engine {
extension := filepath.Ext(filename)
// Read-Only no locks needed, at serve/runtime-time the library is not supposed to add new view engines
for i, n := 0, len(v.engines); i < n; i++ {
e := v.engines[i]
if e.Ext() == extension {
return e
}
}
return nil
}
// Len returns the length of view engines registered so far.
func (v *View) Len() int {
return len(v.engines)
}
var (
errNoViewEngineForExt = errors.New("no view engine found for '%s'")
)
// ExecuteWriter calls the correct view Engine's ExecuteWriter func
func (v *View) ExecuteWriter(w io.Writer, filename string, layout string, bindingData interface{}) error {
e := v.Find(filename)
if e == nil {
return errNoViewEngineForExt.Format(filepath.Ext(filename))
}
return e.ExecuteWriter(w, filename, layout, bindingData)
}
// AddFunc adds a function to all registered engines.
// Each template engine that supports functions has its own AddFunc too.
func (v *View) AddFunc(funcName string, funcBody interface{}) {
for i, n := 0, len(v.engines); i < n; i++ {
e := v.engines[i]
if engineFuncer, ok := e.(EngineFuncer); ok {
engineFuncer.AddFunc(funcName, funcBody)
}
}
}
// Load compiles all the registered engines.
func (v *View) Load() error {
for i, n := 0, len(v.engines); i < n; i++ {
e := v.engines[i]
if err := e.Load(); err != nil {
return err
}
}
return nil
}