1
0
mirror of https://github.com/kataras/iris.git synced 2025-12-29 07:47:22 +00:00

20 days of unstoppable work. Waiting fo go 1.8, I didn't finish yet, some touches remains.

Former-commit-id: ed84f99c89f43fe5e980a8e6d0ee22c186f0e1b9
This commit is contained in:
Gerasimos (Makis) Maropoulos
2017-02-14 05:54:11 +02:00
parent 2b2a205e63
commit 244a59e055
108 changed files with 9016 additions and 7596 deletions

19
adaptors/cors/LICENSE Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2014 Olivier Poitrey <rs@dailymotion.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.

33
adaptors/cors/cors.go Normal file
View File

@@ -0,0 +1,33 @@
package cors
// +------------------------------------------------------------+
// | Cors wrapper usage |
// +------------------------------------------------------------+
//
// import "github.com/kataras/iris/adaptors/cors"
//
// app := iris.New()
// app.Adapt(cors.New(cors.Options{})))
import (
"github.com/rs/cors"
"gopkg.in/kataras/iris.v6"
)
// Options is a configuration container to setup the CORS.
type Options cors.Options
// New returns a new cors router wrapper policy
// with the provided options.
//
// create a new cors middleware, pass its options (casted)
// and pass the .ServeHTTP(w,r,next(http.HandlerFunc)) as the wrapper
// for the whole iris' Router
//
// Unlike the cors middleware, this wrapper wraps the entirely router,
// it cannot be registered to specific routes. It works better and all Options are working.
func New(opts Options) iris.RouterWrapperPolicy { return cors.New(cors.Options(opts)).ServeHTTP }
// Default returns a New cors wrapper with the default options
// accepting GET and POST requests and allowing all origins.
func Default() iris.RouterWrapperPolicy { return New(Options{}) }

View File

@@ -0,0 +1,28 @@
Copyright (c) 2012 Rodrigo Moraes author of gorillamux.
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 Google Inc. 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
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.

View File

@@ -0,0 +1,103 @@
## Package information
Gorillamux is a plugin for Iris which overrides the Iris' default router with the [Gorilla Mux](https://github.com/gorilla/mux)
which enables path matching using custom `regexp` ( thing that the Iris' default router doesn't supports for performance reasons).
All these without need to change any of your existing Iris code. All features are supported.
## Install
```sh
$ go get -u github.com/iris-contrib/plugin/gorillamux
```
```go
iris.Plugins.Add(gorillamux.New())
```
## [Example](https://github.com/iris-contrib/examples/tree/master/plugin_gorillamux)
```go
package main
import (
"github.com/iris-contrib/plugin/gorillamux"
"github.com/kataras/iris"
)
func main() {
iris.Plugins.Add(gorillamux.New())
// CUSTOM HTTP ERRORS ARE SUPPORTED
// NOTE: Gorilla mux allows customization only on StatusNotFound(404)
// Iris allows for everything, so you can register any other custom http error
// but you have to call it manually from ctx.EmitError(status_code) // 500 for example
// this will work because it's StatusNotFound:
iris.Default.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
ctx.HTML(iris.StatusNotFound, "<h1> CUSTOM NOT FOUND ERROR PAGE </h1>")
})
// GLOBAL/PARTY MIDDLEWARE ARE SUPPORTED
iris.Default.UseFunc(func(ctx *iris.Context) {
println("Request: " + ctx.Path())
ctx.Next()
})
// http://mydomain.com
iris.Default.Get("/", func(ctx *iris.Context) {
ctx.Writef("Hello from index")
})
/// -------------------------------------- IMPORTANT --------------------------------------
/// GORILLA MUX PARAMETERS(regexp) ARE SUPPORTED
/// http://mydomain.com/api/users/42
/// ---------------------------------------------------------------------------------------
iris.Default.Get("/api/users/{userid:[0-9]+}", func(ctx *iris.Context) {
ctx.Writef("User with id: %s", ctx.Param("userid"))
})
// PER-ROUTE MIDDLEWARE ARE SUPPORTED
// http://mydomain.com/other
iris.Default.Get("/other", func(ctx *iris.Context) {
ctx.Writef("/other 1 middleware \n")
ctx.Next()
}, func(ctx *iris.Context) {
ctx.HTML(iris.StatusOK, "<b>Hello from /other</b>")
})
// SUBDOMAINS ARE SUPPORTED
// http://admin.mydomain.com
iris.Default.Party("admin.").Get("/", func(ctx *iris.Context) {
ctx.Writef("Hello from admin. subdomain!")
})
// WILDCARD SUBDOMAINS ARE SUPPORTED
// http://api.mydomain.com/hi
// http://admin.mydomain.com/hi
// http://x.mydomain.com/hi
// [depends on your host configuration,
// you will see an example(win) outside of this folder].
iris.Default.Party("*.").Get("/hi", func(ctx *iris.Context) {
ctx.Writef("Hello from wildcard subdomain: %s", ctx.Subdomain())
})
// DOMAIN NAMING IS SUPPORTED
iris.Default.Listen("mydomain.com")
// iris.Default.Listen(":80")
}
/* HOSTS FILE LINES TO RUN THIS EXAMPLE:
127.0.0.1 mydomain.com
127.0.0.1 admin.mydomain.com
127.0.0.1 api.mydomain.com
127.0.0.1 x.mydomain.com
*/
```
> Custom domain is totally optionally, you can still use `iris.Default.Listen(":8080")` of course.

View File

@@ -0,0 +1,150 @@
package gorillamux
// +------------------------------------------------------------+
// | Usage |
// +------------------------------------------------------------+
//
//
// package main
//
// import (
// "gopkg.in/kataras/iris.v6/adaptors/gorillamux"
// "gopkg.in/kataras/iris.v6"
// )
//
// func main() {
// app := iris.New()
//
// app.Adapt(gorillamux.New()) // Add this line and you're ready.
//
// app.Get("/api/users/{userid:[0-9]+}", func(ctx *iris.Context) {
// ctx.Writef("User with id: %s", ctx.Param("userid"))
// })
//
// app.Listen(":8080")
// }
import (
"net/http"
"strings"
"github.com/gorilla/mux"
"gopkg.in/kataras/iris.v6"
)
const dynamicSymbol = '{'
// New returns a new gorilla mux router which can be plugged inside iris.
// This is magic.
func New() iris.Policies {
router := mux.NewRouter()
var logger func(iris.LogMode, string)
return iris.Policies{
EventPolicy: iris.EventPolicy{Boot: func(s *iris.Framework) {
logger = s.Log
}},
RouterReversionPolicy: iris.RouterReversionPolicy{
// path normalization done on iris' side
StaticPath: func(path string) string {
i := strings.IndexByte(path, dynamicSymbol)
if i > -1 {
return path[0:i]
}
return path
},
WildcardPath: func(requestPath string, paramName string) string {
return requestPath + "/{" + paramName + ":.*}"
},
// Note: on gorilla mux the {{ url }} and {{ path}} should give the key and the value, not only the values by order.
// {{ url "nameOfTheRoute" "parameterName" "parameterValue"}}.
//
// so: {{ url "providerLink" "facebook"}} should become
// {{ url "providerLink" "provider" "facebook"}}
// for a path: "/auth/{provider}" with name 'providerLink'
URLPath: func(r iris.RouteInfo, args ...string) string {
if r == nil {
return ""
}
if gr := router.Get(r.Name()); gr != nil {
u, err := gr.URLPath(args...)
if err != nil {
logger(iris.DevMode, "error on gorilla mux adaptor's URLPath(reverse routing): "+err.Error())
return ""
}
return u.Path
}
return ""
},
RouteContextLinker: func(r iris.RouteInfo, ctx *iris.Context) {
if r == nil {
return
}
route := router.Get(r.Name())
if route != nil {
mapToContext(ctx.Request, r.Middleware(), ctx)
}
},
},
RouterBuilderPolicy: func(repo iris.RouteRepository, context iris.ContextPool) http.Handler {
repo.Visit(func(route iris.RouteInfo) {
registerRoute(route, router, context)
})
router.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := context.Acquire(w, r)
// to catch custom 404 not found http errors may registered by user
ctx.EmitError(iris.StatusNotFound)
context.Release(ctx)
})
return router
},
}
}
func mapToContext(r *http.Request, middleware iris.Middleware, ctx *iris.Context) {
if params := mux.Vars(r); len(params) > 0 {
// set them with ctx.Set in order to be accesible by ctx.Param in the user's handler
for k, v := range params {
ctx.Set(k, v)
}
}
// including the iris.Default.Use/UseFunc and the route's middleware,
// main handler and any done handlers.
ctx.Middleware = middleware
}
// so easy:
func registerRoute(route iris.RouteInfo, gorillaRouter *mux.Router, context iris.ContextPool) {
if route.IsOnline() {
handler := func(w http.ResponseWriter, r *http.Request) {
ctx := context.Acquire(w, r)
mapToContext(r, route.Middleware(), ctx)
ctx.Do()
context.Release(ctx)
}
// remember, we get a new iris.Route foreach of the HTTP Methods, so this should be work
methods := []string{route.Method()}
// if route has cors then we register the route with the "OPTIONS" method too
if route.HasCors() {
methods = append(methods, http.MethodOptions)
}
gorillaRoute := gorillaRouter.HandleFunc(route.Path(), handler).Methods(methods...).Name(route.Name())
subdomain := route.Subdomain()
if subdomain != "" {
if subdomain == "*." {
// it's an iris wildcard subdomain
// so register it as wildcard on gorilla mux too (hopefuly, it supports these things)
subdomain = "{subdomain}."
} else {
// it's a static subdomain (which contains the dot)
}
// host = subdomain + listening host
gorillaRoute.Host(subdomain + context.Framework().Config.VHost)
}
}
}

View File

@@ -0,0 +1,47 @@
The MIT License (MIT)
Copyright (c) 2016-2017 Gerasimos Maropoulos
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) 2013 Julien Schmidt. 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.
* The names of the contributors may not 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 JULIEN SCHMIDT 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.

View File

@@ -0,0 +1,39 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
)
func hello(ctx *iris.Context) {
ctx.Writef("Hello from %s", ctx.Path())
}
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New())
app.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
ctx.HTML(iris.StatusNotFound, "<h1>Custom not found handler </h1>")
})
app.Get("/", hello)
app.Get("/users/:userid", func(ctx *iris.Context) {
ctx.Writef("Hello user with id: %s", ctx.Param("userid"))
})
app.Get("/myfiles/*file", func(ctx *iris.Context) {
ctx.HTML(iris.StatusOK, "Hello, the dynamic path after /myfiles is:<br/> <b>"+ctx.Param("file")+"</b>")
})
app.Get("/users/:userid/messages/:messageid", func(ctx *iris.Context) {
ctx.HTML(iris.StatusOK, `Message from user with id:<br/> <b>`+ctx.Param("userid")+`</b>,
message id: <b>`+ctx.Param("messageid")+`</b>`)
})
// http://127.0.0.1:8080/users/42
// http://127.0.0.1:8080/myfiles/mydirectory/myfile.zip
// http://127.0.0.1:8080/users/42/messages/1
app.Listen(":8080")
}

View File

@@ -0,0 +1,725 @@
package httprouter
// +------------------------------------------------------------+
// | Usage |
// +------------------------------------------------------------+
//
//
// package main
//
// import (
// "gopkg.in/kataras/iris.v6/adaptors/httprouter"
// "gopkg.in/kataras/iris.v6"
// )
//
// func main() {
// app := iris.New()
//
// app.Adapt(httprouter.New()) // Add this line and you're ready.
//
// app.Get("/api/users/:userid", func(ctx *iris.Context) {
// ctx.Writef("User with id: %s", ctx.Param("userid"))
// })
//
// app.Listen(":8080")
// }
import (
"net/http"
"strings"
"github.com/kataras/go-errors"
"gopkg.in/kataras/iris.v6"
)
const (
// parameterStartByte is very used on the node, it's just contains the byte for the ':' rune/char
parameterStartByte = byte(':')
// slashByte is just a byte of '/' rune/char
slashByte = byte('/')
// slash is just a string of "/"
slash = "/"
// matchEverythingByte is just a byte of '*" rune/char
matchEverythingByte = byte('*')
isRoot entryCase = iota
hasParams
matchEverything
)
type (
// entryCase is the type which the type of muxEntryusing in order to determinate what type (parameterized, anything, static...) is the perticular node
entryCase uint8
// muxEntry is the node of a tree of the routes,
// in order to learn how this is working, google 'trie' or watch this lecture: https://www.youtube.com/watch?v=uhAUk63tLRM
// this method is used by the BSD's kernel also
muxEntry struct {
part string
entryCase entryCase
hasWildNode bool
tokens string
nodes []*muxEntry
middleware iris.Middleware
precedence uint64
paramsLen uint8
}
)
var (
errMuxEntryConflictsWildcard = errors.New(`
Router: '%s' in new path '%s'
conflicts with existing wildcarded route with path: '%s'
in existing prefix of'%s' `)
errMuxEntryMiddlewareAlreadyExists = errors.New(`
Router: Middleware were already registered for the path: '%s'`)
errMuxEntryInvalidWildcard = errors.New(`
Router: More than one wildcard found in the path part: '%s' in route's path: '%s'`)
errMuxEntryConflictsExistingWildcard = errors.New(`
Router: Wildcard for route path: '%s' conflicts with existing children in route path: '%s'`)
errMuxEntryWildcardUnnamed = errors.New(`
Router: Unnamed wildcard found in path: '%s'`)
errMuxEntryWildcardInvalidPlace = errors.New(`
Router: Wildcard is only allowed at the end of the path, in the route path: '%s'`)
errMuxEntryWildcardConflictsMiddleware = errors.New(`
Router: Wildcard conflicts with existing middleware for the route path: '%s'`)
errMuxEntryWildcardMissingSlash = errors.New(`
Router: No slash(/) were found before wildcard in the route path: '%s'`)
)
// getParamsLen returns the parameters length from a given path
func getParamsLen(path string) uint8 {
var n uint
for i := 0; i < len(path); i++ {
if path[i] != ':' && path[i] != '*' { // ParameterStartByte & MatchEverythingByte
continue
}
n++
}
if n >= 255 {
return 255
}
return uint8(n)
}
// findLower returns the smaller number between a and b
func findLower(a, b int) int {
if a <= b {
return a
}
return b
}
// add adds a muxEntry to the existing muxEntry or to the tree if no muxEntry has the prefix of
func (e *muxEntry) add(path string, middleware iris.Middleware) error {
fullPath := path
e.precedence++
numParams := getParamsLen(path)
if len(e.part) > 0 || len(e.nodes) > 0 {
loop:
for {
if numParams > e.paramsLen {
e.paramsLen = numParams
}
i := 0
max := findLower(len(path), len(e.part))
for i < max && path[i] == e.part[i] {
i++
}
if i < len(e.part) {
node := muxEntry{
part: e.part[i:],
hasWildNode: e.hasWildNode,
tokens: e.tokens,
nodes: e.nodes,
middleware: e.middleware,
precedence: e.precedence - 1,
}
for i := range node.nodes {
if node.nodes[i].paramsLen > node.paramsLen {
node.paramsLen = node.nodes[i].paramsLen
}
}
e.nodes = []*muxEntry{&node}
e.tokens = string([]byte{e.part[i]})
e.part = path[:i]
e.middleware = nil
e.hasWildNode = false
}
if i < len(path) {
path = path[i:]
if e.hasWildNode {
e = e.nodes[0]
e.precedence++
if numParams > e.paramsLen {
e.paramsLen = numParams
}
numParams--
if len(path) >= len(e.part) && e.part == path[:len(e.part)] &&
// Check for longer wildcard, e.g. :name and :names
(len(e.part) >= len(path) || path[len(e.part)] == '/') {
continue loop
} else {
// Wildcard conflict
part := strings.SplitN(path, "/", 2)[0]
prefix := fullPath[:strings.Index(fullPath, part)] + e.part
return errMuxEntryConflictsWildcard.Format(fullPath, e.part, prefix)
}
}
c := path[0]
if e.entryCase == hasParams && c == slashByte && len(e.nodes) == 1 {
e = e.nodes[0]
e.precedence++
continue loop
}
for i := range e.tokens {
if c == e.tokens[i] {
i = e.precedenceTo(i)
e = e.nodes[i]
continue loop
}
}
if c != parameterStartByte && c != matchEverythingByte {
e.tokens += string([]byte{c})
node := &muxEntry{
paramsLen: numParams,
}
e.nodes = append(e.nodes, node)
e.precedenceTo(len(e.tokens) - 1)
e = node
}
return e.addNode(numParams, path, fullPath, middleware)
} else if i == len(path) {
if e.middleware != nil {
return errMuxEntryMiddlewareAlreadyExists.Format(fullPath)
}
e.middleware = middleware
}
return nil
}
} else {
if err := e.addNode(numParams, path, fullPath, middleware); err != nil {
return err
}
e.entryCase = isRoot
}
return nil
}
// addNode adds a muxEntry as children to other muxEntry
func (e *muxEntry) addNode(numParams uint8, path string, fullPath string, middleware iris.Middleware) error {
var offset int
for i, max := 0, len(path); numParams > 0; i++ {
c := path[i]
if c != parameterStartByte && c != matchEverythingByte {
continue
}
end := i + 1
for end < max && path[end] != slashByte {
switch path[end] {
case parameterStartByte, matchEverythingByte:
return errMuxEntryInvalidWildcard.Format(path[i:], fullPath)
default:
end++
}
}
if len(e.nodes) > 0 {
return errMuxEntryConflictsExistingWildcard.Format(path[i:end], fullPath)
}
if end-i < 2 {
return errMuxEntryWildcardUnnamed.Format(fullPath)
}
if c == parameterStartByte {
if i > 0 {
e.part = path[offset:i]
offset = i
}
child := &muxEntry{
entryCase: hasParams,
paramsLen: numParams,
}
e.nodes = []*muxEntry{child}
e.hasWildNode = true
e = child
e.precedence++
numParams--
if end < max {
e.part = path[offset:end]
offset = end
child := &muxEntry{
paramsLen: numParams,
precedence: 1,
}
e.nodes = []*muxEntry{child}
e = child
}
} else {
if end != max || numParams > 1 {
return errMuxEntryWildcardInvalidPlace.Format(fullPath)
}
if len(e.part) > 0 && e.part[len(e.part)-1] == '/' {
return errMuxEntryWildcardConflictsMiddleware.Format(fullPath)
}
i--
if path[i] != slashByte {
return errMuxEntryWildcardMissingSlash.Format(fullPath)
}
e.part = path[offset:i]
child := &muxEntry{
hasWildNode: true,
entryCase: matchEverything,
paramsLen: 1,
}
e.nodes = []*muxEntry{child}
e.tokens = string(path[i])
e = child
e.precedence++
child = &muxEntry{
part: path[i:],
entryCase: matchEverything,
paramsLen: 1,
middleware: middleware,
precedence: 1,
}
e.nodes = []*muxEntry{child}
return nil
}
}
e.part = path[offset:]
e.middleware = middleware
return nil
}
// get is used by the Router, it finds and returns the correct muxEntry for a path
func (e *muxEntry) get(path string, ctx *iris.Context) (mustRedirect bool) {
loop:
for {
if len(path) > len(e.part) {
if path[:len(e.part)] == e.part {
path = path[len(e.part):]
if !e.hasWildNode {
c := path[0]
for i := range e.tokens {
if c == e.tokens[i] {
e = e.nodes[i]
continue loop
}
}
mustRedirect = (path == slash && e.middleware != nil)
return
}
e = e.nodes[0]
switch e.entryCase {
case hasParams:
end := 0
for end < len(path) && path[end] != '/' {
end++
}
ctx.Set(e.part[1:], path[:end])
if end < len(path) {
if len(e.nodes) > 0 {
path = path[end:]
e = e.nodes[0]
continue loop
}
mustRedirect = (len(path) == end+1)
return
}
if ctx.Middleware = e.middleware; ctx.Middleware != nil {
return
} else if len(e.nodes) == 1 {
e = e.nodes[0]
mustRedirect = (e.part == slash && e.middleware != nil)
}
return
case matchEverything:
ctx.Set(e.part[2:], path)
ctx.Middleware = e.middleware
return
default:
return
}
}
} else if path == e.part {
if ctx.Middleware = e.middleware; ctx.Middleware != nil {
return
}
if path == slash && e.hasWildNode && e.entryCase != isRoot {
mustRedirect = true
return
}
for i := range e.tokens {
if e.tokens[i] == slashByte {
e = e.nodes[i]
mustRedirect = (len(e.part) == 1 && e.middleware != nil) ||
(e.entryCase == matchEverything && e.nodes[0].middleware != nil)
return
}
}
return
}
mustRedirect = (path == slash) ||
(len(e.part) == len(path)+1 && e.part[len(path)] == slashByte &&
path == e.part[:len(e.part)-1] && e.middleware != nil)
return
}
}
// precedenceTo just adds the priority of this muxEntry by an index
func (e *muxEntry) precedenceTo(index int) int {
e.nodes[index].precedence++
_precedence := e.nodes[index].precedence
newindex := index
for newindex > 0 && e.nodes[newindex-1].precedence < _precedence {
tmpN := e.nodes[newindex-1]
e.nodes[newindex-1] = e.nodes[newindex]
e.nodes[newindex] = tmpN
newindex--
}
if newindex != index {
e.tokens = e.tokens[:newindex] +
e.tokens[index:index+1] +
e.tokens[newindex:index] + e.tokens[index+1:]
}
return newindex
}
type (
muxTree struct {
method string
// subdomain is empty for default-hostname routes,
// ex: mysubdomain.
subdomain string
entry *muxEntry
}
serveMux struct {
garden []*muxTree
maxParameters uint8
methodEqual func(string, string) bool
hosts bool
}
)
// New returns a new iris' policy to create and attach the router.
// It's based on the julienschmidt/httprouter with more features and some iris-relative performance tips:
// subdomains(wildcard/dynamic and static) and faster parameters set (use of the already-created context's values)
// and support for reverse routing.
func New() iris.Policies {
var logger func(iris.LogMode, string)
mux := &serveMux{
methodEqual: func(reqMethod string, treeMethod string) bool {
return reqMethod == treeMethod
},
}
matchEverythingString := string(matchEverythingByte)
return iris.Policies{
EventPolicy: iris.EventPolicy{
Boot: func(s *iris.Framework) {
logger = s.Log
},
},
RouterReversionPolicy: iris.RouterReversionPolicy{
// path normalization done on iris' side
StaticPath: func(path string) string {
i := strings.IndexByte(path, parameterStartByte)
x := strings.IndexByte(path, matchEverythingByte)
if i > -1 {
return path[0:i]
}
if x > -1 {
return path[0:x]
}
return path
},
WildcardPath: func(path string, paramName string) string {
return path + slash + matchEverythingString + paramName
},
// URLPath: func(r iris.RouteInfo, args ...string) string {
// argsLen := len(args)
//
// // we have named parameters but arguments not given
// if argsLen == 0 && r.formattedParts > 0 {
// return ""
// } else if argsLen == 0 && r.formattedParts == 0 {
// // it's static then just return the path
// return r.path
// }
//
// // we have arguments but they are much more than the named parameters
//
// // 1 check if we have /*, if yes then join all arguments to one as path and pass that as parameter
// if argsLen > r.formattedParts {
// if r.path[len(r.path)-1] == matchEverythingByte {
// // we have to convert each argument to a string in this case
//
// argsString := make([]string, argsLen, argsLen)
//
// for i, v := range args {
// if s, ok := v.(string); ok {
// argsString[i] = s
// } else if num, ok := v.(int); ok {
// argsString[i] = strconv.Itoa(num)
// } else if b, ok := v.(bool); ok {
// argsString[i] = strconv.FormatBool(b)
// } else if arr, ok := v.([]string); ok {
// if len(arr) > 0 {
// argsString[i] = arr[0]
// argsString = append(argsString, arr[1:]...)
// }
// }
// }
//
// parameter := strings.Join(argsString, slash)
// result := fmt.Sprintf(r.formattedPath, parameter)
// return result
// }
// // 2 if !1 return false
// return ""
// }
//
// arguments := joinPathArguments(args...)
//
// return fmt.Sprintf(r.formattedPath, arguments...)
// },
RouteContextLinker: func(r iris.RouteInfo, ctx *iris.Context) {
tree := mux.getTree(r.Method(), r.Subdomain())
if tree != nil {
tree.entry.get(ctx.Request.URL.Path, ctx)
}
},
},
RouterBuilderPolicy: func(repo iris.RouteRepository, context iris.ContextPool) http.Handler {
fatalErr := false
repo.Visit(func(r iris.RouteInfo) {
if fatalErr {
return
}
// add to the registry tree
method := r.Method()
subdomain := r.Subdomain()
path := r.Path()
middleware := r.Middleware()
tree := mux.getTree(method, subdomain)
if tree == nil {
//first time we register a route to this method with this domain
tree = &muxTree{method: method, subdomain: subdomain, entry: &muxEntry{}}
mux.garden = append(mux.garden, tree)
}
// I decide that it's better to explicit give subdomain and a path to it than registeredPath(mysubdomain./something) now its: subdomain: mysubdomain., path: /something
// we have different tree for each of subdomains, now you can use everything you can use with the normal paths ( before you couldn't set /any/*path)
if err := tree.entry.add(path, middleware); err != nil {
// while ProdMode means that the iris should not continue running
// by-default it panics on these errors, but to make sure let's introduce the fatalErr to stop visiting
fatalErr = true
logger(iris.ProdMode, "fatal error on httprouter build adaptor: "+err.Error())
return
}
if mp := tree.entry.paramsLen; mp > mux.maxParameters {
mux.maxParameters = mp
}
// check for method equality if at least one route has cors
if r.HasCors() {
mux.methodEqual = func(reqMethod string, treeMethod string) bool {
// preflights
return reqMethod == iris.MethodOptions || reqMethod == treeMethod
}
}
if subdomain != "" {
mux.hosts = true
}
})
if !fatalErr {
return mux.buildHandler(context)
}
return nil
},
}
}
func (mux *serveMux) getTree(method string, subdomain string) *muxTree {
for i := range mux.garden {
t := mux.garden[i]
if t.method == method && t.subdomain == subdomain {
return t
}
}
return nil
}
func (mux *serveMux) buildHandler(pool iris.ContextPool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pool.Run(w, r, func(context *iris.Context) {
routePath := context.Path()
for i := range mux.garden {
tree := mux.garden[i]
if !mux.methodEqual(context.Request.Method, tree.method) {
continue
}
if mux.hosts && tree.subdomain != "" {
// context.VirtualHost() is a slow method because it makes
// string.Replaces but user can understand that if subdomain then server will have some nano/or/milleseconds performance cost
requestHost := context.VirtualHostname()
hostname := context.Framework().Config.VHost
if requestHost != hostname {
//println(requestHost + " != " + mux.hostname)
// we have a subdomain
if strings.Contains(tree.subdomain, iris.DynamicSubdomainIndicator) {
} else {
//println(requestHost + " = " + mux.hostname)
// mux.host = iris-go.com:8080, the subdomain for example is api.,
// so the host must be api.iris-go.com:8080
if tree.subdomain+hostname != requestHost {
// go to the next tree, we have a subdomain but it is not the correct
continue
}
}
} else {
//("it's subdomain but the request is the same as the listening addr mux.host == requestHost =>" + mux.host + "=" + requestHost + " ____ and tree's subdomain was: " + tree.subdomain)
continue
}
}
mustRedirect := tree.entry.get(routePath, context) // pass the parameters here for 0 allocation
if context.Middleware != nil {
// ok we found the correct route, serve it and exit entirely from here
//ctx.Request.Header.SetUserAgentBytes(DefaultUserAgent)
context.Do()
return
} else if mustRedirect && !context.Framework().Config.DisablePathCorrection { // && context.Method() == MethodConnect {
reqPath := routePath
pathLen := len(reqPath)
if pathLen > 1 {
if reqPath[pathLen-1] == '/' {
reqPath = reqPath[:pathLen-1] //remove the last /
} else {
//it has path prefix, it doesn't ends with / and it hasn't be found, then just add the slash
reqPath = reqPath + "/"
}
urlToRedirect := reqPath
statusForRedirect := iris.StatusMovedPermanently // StatusMovedPermanently, this document is obselte, clients caches this.
if tree.method == iris.MethodPost ||
tree.method == iris.MethodPut ||
tree.method == iris.MethodDelete {
statusForRedirect = iris.StatusTemporaryRedirect // To maintain POST data
}
context.Redirect(urlToRedirect, statusForRedirect)
// RFC2616 recommends that a short note "SHOULD" be included in the
// response because older user agents may not understand 301/307.
// Shouldn't send the response for POST or HEAD; that leaves GET.
if tree.method == iris.MethodGet {
note := "<a href=\"" + HTMLEscape(urlToRedirect) + "\">Moved Permanently</a>.\n"
// ignore error
context.WriteString(note)
}
return
}
}
// not found
break
}
// https://github.com/kataras/iris/issues/469
if context.Framework().Config.FireMethodNotAllowed {
for i := range mux.garden {
tree := mux.garden[i]
if !mux.methodEqual(context.Method(), tree.method) {
continue
}
}
context.EmitError(iris.StatusMethodNotAllowed)
return
}
context.EmitError(iris.StatusNotFound)
})
})
}
//THESE ARE FROM Go Authors "html" package
var htmlReplacer = strings.NewReplacer(
"&", "&amp;",
"<", "&lt;",
">", "&gt;",
// "&#34;" is shorter than "&quot;".
`"`, "&#34;",
// "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
"'", "&#39;",
)
// HTMLEscape returns a string which has no valid html code
func HTMLEscape(s string) string {
return htmlReplacer.Replace(s)
}

View File

@@ -0,0 +1,23 @@
package httprouter
func joinPathArguments(args ...interface{}) []interface{} {
arguments := args[0:]
for i, v := range arguments {
if arr, ok := v.([]string); ok {
if len(arr) > 0 {
interfaceArr := make([]interface{}, len(arr))
for j, sv := range arr {
interfaceArr[j] = sv
}
// replace the current slice
// with the first string element (always as interface{})
arguments[i] = interfaceArr[0]
// append the rest of them to the slice itself
// the range is not affected by these things in go,
// so we are safe to do it.
arguments = append(args, interfaceArr[1:]...)
}
}
}
return arguments
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-2017 Gerasimos Maropoulos
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.

View File

@@ -0,0 +1,89 @@
## Package information
This is an Iris and typescript bridge plugin.
1. Search for typescript files (.ts)
2. Search for typescript projects (.tsconfig)
3. If 1 || 2 continue else stop
4. Check if typescript is installed, if not then auto-install it (always inside npm global modules, -g)
5. If typescript project then build the project using tsc -p $dir
6. If typescript files and no project then build each typescript using tsc $filename
7. Watch typescript files if any changes happens, then re-build (5|6)
> Note: Ignore all typescript files & projects whose path has '/node_modules/'
## Install
```sh
$ go get -u github.com/iris-contrib/plugin/typescript
```
## Options
This plugin has **optionally** options
1. Bin: string, the typescript installation path/bin/tsc or tsc.cmd, if empty then it will search to the global npm modules
2. Dir: string, Dir set the root, where to search for typescript files/project. Default "./"
3. Ignore: string, comma separated ignore typescript files/project from these directories. Default "" (node_modules are always ignored)
4. Tsconfig: &typescript.Tsconfig{}, here you can set all compilerOptions if no tsconfig.json exists inside the 'Dir'
5. Editor: typescript.Editor(), if setted then alm-tools browser-based typescript IDE will be available. Defailt is nil
> Note: if any string in Ignore doesn't start with './' then it will ignore all files which contains this path string.
For example /node_modules/ will ignore all typescript files that are inside at ANY '/node_modules/', that means and the submodules.
## How to use
```go
package main
import (
"github.com/kataras/iris"
"github.com/iris-contrib/plugin/typescript"
)
func main(){
/* Options
Bin -> the typescript installation path/bin/tsc or tsc.cmd, if empty then it will search to the global npm modules
Dir -> where to search for typescript files/project. Default "./"
Ignore -> comma separated ignore typescript files/project from these directories (/node_modules/ are always ignored). Default ""
Tsconfig -> &typescript.Tsconfig{}, here you can set all compilerOptions if no tsconfig.json exists inside the 'Dir'
Editor -> typescript.Editor(), if setted then alm-tools browser-based typescript IDE will be available. Default is nil.
*/
config := typescript.Config {
Dir: "./scripts/src",
Tsconfig: &typescript.Tsconfig{Module: "commonjs", Target: "es5"}, // or typescript.DefaultTsconfig()
}
//if you want to change only certain option(s) but you want default to all others then you have to do this:
config = typescript.DefaultConfig()
//
iris.Plugins.Add(typescript.New(config)) //or with the default options just: typescript.New()
iris.Default.Get("/", func (ctx *iris.Context){})
iris.Default.Listen(":8080")
}
```
## Editor
[alm-tools](http://alm.tools) is a typescript online IDE/Editor, made by [@basarat](https://twitter.com/basarat) one of the top contributors of the [Typescript](http://www.typescriptlang.org).
Iris gives you the opportunity to edit your client-side using the alm-tools editor, via the editor plugin.
With typescript plugin you have to set the Editor option and you're ready:
```go
typescript.Config {
//...
Editor: typescript.Editor("username","passowrd")
//...
}
```
> [Read more](https://github.com/kataras/iris/tree/development/plugin/editor) for Editor

View File

@@ -0,0 +1,31 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/typescript"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New()) // adapt a router, order doesn't matters but before Listen.
ts := typescript.New()
ts.Config.Dir = "./www/scripts"
app.Adapt(ts) // adapt the typescript compiler adaptor
app.StaticWeb("/", "./www") // serve the index.html
app.Listen(":8080")
}
// open http://localhost:8080
// go to ./www/scripts/app.ts
// make a change
// reload the http://localhost:8080 and you should see the changes
//
// what it does?
// - compiles the typescript files using default compiler options if not tsconfig found
// - watches for changes on typescript files, if a change then it recompiles the .ts to .js
//
// same as you used to do with gulp-like tools, but here at Iris I do my bests to help GO developers.

View File

@@ -0,0 +1,8 @@
<html>
<head>
<title>Load my script (lawl)</title>
</head>
<body>
<script src="scripts/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
class User{
private name: string;
constructor(fullname:string) {
this.name = fullname;
}
Hi(msg: string): string {
return msg + " "+ this.name;
}
}
var user = new User("kataras");
var hi = user.Hi("Hello");
window.alert(hi);

View File

@@ -0,0 +1,192 @@
package typescript
import (
"encoding/json"
"io/ioutil"
"os"
"reflect"
"strconv"
"gopkg.in/kataras/iris.v6/adaptors/typescript/npm"
)
var (
pathSeparator = string(os.PathSeparator)
nodeModules = pathSeparator + "node_modules" + pathSeparator
)
type (
// Tsconfig the struct for tsconfig.json
Tsconfig struct {
CompilerOptions CompilerOptions `json:"compilerOptions"`
Exclude []string `json:"exclude"`
}
// CompilerOptions contains all the compiler options used by the tsc (typescript compiler)
CompilerOptions struct {
Declaration bool `json:"declaration"`
Module string `json:"module"`
Target string `json:"target"`
Watch bool `json:"watch"`
Charset string `json:"charset"`
Diagnostics bool `json:"diagnostics"`
EmitBOM bool `json:"emitBOM"`
EmitDecoratorMetadata bool `json:"emitDecoratorMetadata"`
ExperimentalDecorators bool `json:"experimentalDecorators"`
InlineSourceMap bool `json:"inlineSourceMap"`
InlineSources bool `json:"inlineSources"`
IsolatedModules bool `json:"isolatedModules"`
Jsx string `json:"jsx"`
ReactNamespace string `json:"reactNamespace"`
ListFiles bool `json:"listFiles"`
Locale string `json:"locale"`
MapRoot string `json:"mapRoot"`
ModuleResolution string `json:"moduleResolution"`
NewLine string `json:"newLine"`
NoEmit bool `json:"noEmit"`
NoEmitOnError bool `json:"noEmitOnError"`
NoEmitHelpers bool `json:"noEmitHelpers"`
NoImplicitAny bool `json:"noImplicitAny"`
NoLib bool `json:"noLib"`
NoResolve bool `json:"noResolve"`
SkipDefaultLibCheck bool `json:"skipDefaultLibCheck"`
OutDir string `json:"outDir"`
OutFile string `json:"outFile"`
PreserveConstEnums bool `json:"preserveConstEnums"`
Pretty bool `json:"pretty"`
RemoveComments bool `json:"removeComments"`
RootDir string `json:"rootDir"`
SourceMap bool `json:"sourceMap"`
SourceRoot string `json:"sourceRoot"`
StripInternal bool `json:"stripInternal"`
SuppressExcessPropertyErrors bool `json:"suppressExcessPropertyErrors"`
SuppressImplicitAnyIndexErrors bool `json:"suppressImplicitAnyIndexErrors"`
AllowUnusedLabels bool `json:"allowUnusedLabels"`
NoImplicitReturns bool `json:"noImplicitReturns"`
NoFallthroughCasesInSwitch bool `json:"noFallthroughCasesInSwitch"`
AllowUnreachableCode bool `json:"allowUnreachableCode"`
ForceConsistentCasingInFileNames bool `json:"forceConsistentCasingInFileNames"`
AllowSyntheticDefaultImports bool `json:"allowSyntheticDefaultImports"`
AllowJs bool `json:"allowJs"`
NoImplicitUseStrict bool `json:"noImplicitUseStrict"`
}
// Config the configs for the Typescript plugin
// Has five (5) fields
//
// 1. Bin: string, the typescript installation directory/typescript/lib/tsc.js, if empty it will search inside global npm modules
// 2. Dir: string, Dir set the root, where to search for typescript files/project. Default "./"
// 3. Ignore: string, comma separated ignore typescript files/project from these directories. Default "" (node_modules are always ignored)
// 4. Tsconfig: &typescript.Tsconfig{}, here you can set all compilerOptions if no tsconfig.json exists inside the 'Dir'
// 5. Editor: typescript.Editor("username","password"), if setted then alm-tools browser-based typescript IDE will be available. Defailt is nil
Config struct {
// Bin the path of the tsc binary file
// if empty then the plugin tries to find it
Bin string
// Dir the client side directory, which typescript (.ts) files are live
Dir string
// Ignore ignore folders, default is /node_modules/
Ignore string
// Tsconfig the typescript build configs, including the compiler's options
Tsconfig *Tsconfig
}
)
// CompilerArgs returns the CompilerOptions' contents of the Tsconfig
// it reads the json tags, add '--' at the start of each one and returns an array of strings
// this is from file
func (tsconfig *Tsconfig) CompilerArgs() []string {
val := reflect.ValueOf(tsconfig).Elem().FieldByName("CompilerOptions") // -> for tsconfig *Tsconfig
// val := reflect.ValueOf(tsconfig.CompilerOptions)
compilerOpts := make([]string, 0) // 0 because we don't know the real valid options yet.
for i := 0; i < val.NumField(); i++ {
typeField := val.Type().Field(i)
valueFieldG := val.Field(i)
var valueField string
// only if it's string or int we need to put that
if valueFieldG.Kind() == reflect.String {
//if valueFieldG.String() != "" {
//valueField = strconv.QuoteToASCII(valueFieldG.String())
// }
valueField = valueFieldG.String()
} else if valueFieldG.Kind() == reflect.Int {
if valueFieldG.Int() > 0 {
valueField = strconv.Itoa(int(valueFieldG.Int()))
}
} else if valueFieldG.Kind() == reflect.Bool {
valueField = strconv.FormatBool(valueFieldG.Bool())
}
if valueField != "" && valueField != "false" {
// var opt string
// key := typeField.Tag.Get("json")
// // it's bool value of true then just --key, for example --watch
// if valueField == "true" {
// opt = "--" + key
// } else {
// // it's a string now, for example -m commonjs
// opt = "-" + string(key[0]) + " " + valueField
// }
key := "--" + typeField.Tag.Get("json")
compilerOpts = append(compilerOpts, key)
// the form is not '--module ES6' but os.Exec should recognise them as arguments
// so we need to put the values on the next index
if valueField != "true" {
// it's a string now, for example -m commonjs
compilerOpts = append(compilerOpts, valueField)
}
}
}
return compilerOpts
}
// FromFile reads a file & returns the Tsconfig by its contents
func FromFile(tsConfigAbsPath string) (config Tsconfig, err error) {
file, err := ioutil.ReadFile(tsConfigAbsPath)
if err != nil {
return
}
config = Tsconfig{}
err = json.Unmarshal(file, &config)
return
}
// DefaultTsconfig returns the default Tsconfig, with CompilerOptions module: commonjs, target: es5 and ignore the node_modules
func DefaultTsconfig() Tsconfig {
return Tsconfig{
CompilerOptions: CompilerOptions{
Module: "commonjs",
Target: "ES6",
Jsx: "react",
ModuleResolution: "classic",
Locale: "en",
Watch: true,
NoImplicitAny: false,
SourceMap: false,
},
Exclude: []string{"node_modules"},
}
}
// DefaultConfig returns the default Options of the Typescript adaptor
// Bin and Editor are setting in runtime via the adaptor
func DefaultConfig() Config {
root, err := os.Getwd()
if err != nil {
panic("Typescript Adaptor: Cannot get the Current Working Directory !!! [os.getwd()]")
}
compilerTsConfig := DefaultTsconfig()
c := Config{
Dir: root + pathSeparator,
Ignore: nodeModules,
Tsconfig: &compilerTsConfig,
}
c.Bin = npm.NodeModuleAbs("typescript/lib/tsc.js")
return c
}

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Basarat Ali Syed author of alm-tools
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.

View File

@@ -0,0 +1,35 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/typescript" // optinally
"gopkg.in/kataras/iris.v6/adaptors/typescript/editor"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New()) // adapt a router, order doesn't matters
// optionally but good to have, I didn't put inside editor or the editor in the typescript compiler adaptors
// because you may use tools like gulp and you may use the editor without the typescript compiler adaptor.
// but if you need auto-compilation on .ts, we have a solution:
ts := typescript.New()
ts.Config.Dir = "./www/scripts/"
app.Adapt(ts) // adapt the typescript compiler adaptor
editorConfig := editor.Config{
Hostname: "127.0.0.1",
Port: 4444,
WorkingDir: "./www/scripts/", // "/path/to/the/client/side/directory/",
Username: "myusername",
Password: "mypassword",
}
e := editor.New(editorConfig)
app.Adapt(e) // adapt the editor
app.StaticWeb("/", "./www") // serve the index.html
app.Listen(":8080")
}

View File

@@ -0,0 +1,8 @@
<html>
<head>
<title>Load my script (lawl)</title>
</head>
<body>
<script src="scripts/app.js"></script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
class User{
private name: string;
constructor(fullname:string) {
this.name = fullname;
}
Hi(msg: string): string {
return msg + " " + this.name;
}
}
var user = new User("kataras");
var hi = user.Hi("Hello");
window.alert(hi);

View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": false,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": false,
"target": "ES5",
"noEmit": false,
"watch":true,
"noEmitOnError": true,
"experimentalDecorators": false,
"outDir": "./",
"charset": "UTF-8",
"noLib": false,
"diagnostics": true,
"declaration": false
},
"files": [
"./app.ts"
]
}

View File

@@ -0,0 +1,78 @@
package editor
import (
"github.com/imdario/mergo"
"os"
)
// Default values for the configuration
const (
DefaultPort = 4444
)
// Config the configs for the Editor plugin
type Config struct {
// Hostname if empty used the iris server's hostname
Hostname string
// Port if 0 4444
Port int
// KeyFile the key file(ssl optional)
KeyFile string
// CertFile the cert file (ssl optional)
CertFile string
// WorkingDir if empty "./"
WorkingDir string
// Username defaults to empty, you should set this
Username string
// Password defaults to empty, you should set this
Password string
// DisableOutput set that to true if you don't care about alm-tools' messages
// they are useful because that the default value is "false"
DisableOutput bool
}
// DefaultConfig returns the default configs for the Editor plugin
func DefaultConfig() Config {
// explicit
return Config{
Hostname: "",
Port: 4444,
KeyFile: "",
CertFile: "",
WorkingDir: "." + string(os.PathSeparator), // alm-tools should end with path separator.
Username: "",
Password: "",
DisableOutput: false,
}
}
// Merge merges the default with the given config and returns the result
func (c Config) Merge(cfg []Config) (config Config) {
if len(cfg) > 0 {
config = cfg[0]
if err := mergo.Merge(&config, c); err != nil {
if !c.DisableOutput {
panic(err)
}
}
} else {
_default := c
config = _default
}
return
}
// MergeSingle merges the default with the given config and returns the result
func (c Config) MergeSingle(cfg Config) (config Config) {
config = cfg
if err := mergo.Merge(&config, c); err != nil {
if !c.DisableOutput {
panic(err)
}
}
return
}

View File

@@ -0,0 +1,216 @@
package editor
// +------------------------------------------------------------+
// | Editor usage |
// +------------------------------------------------------------+
//
// import "gopkg.in/kataras/iris.v6/adaptors/editor"
//
// e := editor.New(editor.Config{})
// app.Adapt(e)
//
// app.Listen(":8080")
//
// +------------------------------------------------------------+
// | General notes for authentication |
// +------------------------------------------------------------+
//
// The Authorization specifies the authentication mechanism (in this case Basic) followed by the username and password.
// Although, the string aHR0cHdhdGNoOmY= may look encrypted it is simply a base64 encoded version of <username>:<password>.
// Would be readily available to anyone who could intercept the HTTP request.
import (
"bufio"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/typescript/npm"
)
type (
// Editor is the alm-tools adaptor.
//
// It holds a logger from the iris' station
// username,password for basic auth
// directory which the client side code is
// keyfile,certfile for TLS listening
// and a host which is listening for
Editor struct {
config *Config
logger func(iris.LogMode, string)
enabled bool // default true
// after alm started
process *os.Process
debugOutput io.Writer
}
)
// New creates and returns an Editor Plugin instance
func New(cfg ...Config) *Editor {
c := DefaultConfig().Merge(cfg)
c.WorkingDir = validateWorkingDir(c.WorkingDir) // add "/" if not exists
return &Editor{
enabled: true,
config: &c,
}
}
// Adapt adapts the editor with Iris.
// Note:
// We use that method and not the return on New because we
// want to export the Editor's functionality to the user.
func (e *Editor) Adapt(frame *iris.Policies) {
policy := iris.EventPolicy{
Build: e.build,
Interrupted: e.close,
}
policy.Adapt(frame)
}
// User set a user, accepts two parameters: username (string), string (string)
func (e *Editor) User(username string, password string) *Editor {
e.config.Username = username
e.config.Password = password
return e
}
func validateWorkingDir(workingDir string) string {
l := workingDir[len(workingDir)-1]
if l != '/' && l != os.PathSeparator {
workingDir += "/"
}
return workingDir
}
// Dir sets the directory which the client side source code alive
func (e *Editor) Dir(workingDir string) *Editor {
e.config.WorkingDir = validateWorkingDir(workingDir)
return e
}
// Port sets the port (int) for the editor adaptor's standalone server
func (e *Editor) Port(port int) *Editor {
e.config.Port = port
return e
}
// SetEnable if true enables the editor adaptor, otherwise disables it
func (e *Editor) SetEnable(enable bool) {
e.enabled = enable
}
// DisableOutput call that if you don't care about alm-tools' messages
// they are useful because that the default configuration shows them
func (e *Editor) DisableOutput() {
e.config.DisableOutput = true
}
// GetDescription EditorPlugin is a bridge between Iris and the alm-tools, the browser-based IDE for client-side sources.
func (e *Editor) GetDescription() string {
return "A bridge between Iris and the alm-tools, the browser-based IDE."
}
// we use that editorWriter to prefix the editor's output with "Editor Adaptor: "
type editorWriter struct {
underline io.Writer
}
// build runs before the server's listens, creates the listener ( use of port parent hostname:DefaultPort if not exist)
func (e *Editor) build(s *iris.Framework) {
e.logger = s.Log
if e.config.Hostname == "" {
e.config.Hostname = iris.ParseHostname(s.Config.VHost)
}
if e.config.Port <= 0 {
e.config.Port = DefaultPort
}
if s, err := filepath.Abs(e.config.WorkingDir); err == nil {
e.config.WorkingDir = s
}
e.start()
}
// close kills the editor's server when Iris is closed
func (e *Editor) close(s *iris.Framework) {
if e.process != nil {
err := e.process.Kill()
if err != nil {
e.logger(iris.DevMode, fmt.Sprintf(`Error while trying to terminate the Editor,
please kill this process by yourself, process id: %d`, e.process.Pid))
}
}
}
// start starts the job
func (e *Editor) start() {
if e.config.Username == "" || e.config.Password == "" {
e.logger(iris.ProdMode, `Error before running alm-tools.
You have to set username & password for security reasons, otherwise this adaptor won't run.`)
return
}
if !npm.NodeModuleExists("alm/bin/alm") {
e.logger(iris.DevMode, "Installing alm-tools, please wait...")
res := npm.NodeModuleInstall("alm")
if res.Error != nil {
e.logger(iris.ProdMode, res.Error.Error())
return
}
e.logger(iris.DevMode, res.Message)
}
cmd := npm.CommandBuilder("node", npm.NodeModuleAbs("alm/src/server.js"))
cmd.AppendArguments("-a", e.config.Username+":"+e.config.Password,
"-h", e.config.Hostname, "-t", strconv.Itoa(e.config.Port), "-d", e.config.WorkingDir[0:len(e.config.WorkingDir)-1])
// for auto-start in the browser: cmd.AppendArguments("-o")
if e.config.KeyFile != "" && e.config.CertFile != "" {
cmd.AppendArguments("--httpskey", e.config.KeyFile, "--httpscert", e.config.CertFile)
}
// when debug is not disabled
// show any messages to the user( they are useful here)
// to the io.Writer that iris' user is defined from configuration
if !e.config.DisableOutput {
outputReader, err := cmd.StdoutPipe()
if err == nil {
outputScanner := bufio.NewScanner(outputReader)
go func() {
for outputScanner.Scan() {
e.logger(iris.DevMode, "Editor: "+outputScanner.Text())
}
}()
errReader, err := cmd.StderrPipe()
if err == nil {
errScanner := bufio.NewScanner(errReader)
go func() {
for errScanner.Scan() {
e.logger(iris.DevMode, "Editor: "+errScanner.Text())
}
}()
}
}
}
err := cmd.Start()
if err != nil {
e.logger(iris.ProdMode, "Error while running alm-tools. Trace: "+err.Error())
return
}
// no need, alm-tools post these
// e.logger.Printf("Editor is running at %s:%d | %s", e.config.Hostname, e.config.Port, e.config.WorkingDir)
}

View File

@@ -0,0 +1,114 @@
package npm // #nosec
import (
"fmt"
"os"
"os/exec"
"strings"
)
var (
// PathSeparator is the string of os.PathSeparator
PathSeparator = string(os.PathSeparator)
)
type (
// Cmd is a custom struch which 'implements' the *exec.Cmd
Cmd struct {
*exec.Cmd
}
)
// Arguments sets the command line arguments, including the command as Args[0].
// If the args parameter is empty or nil, Run uses {Path}.
//
// In typical use, both Path and args are set by calling Command.
func (cmd *Cmd) Arguments(args ...string) *Cmd {
cmd.Cmd.Args = append(cmd.Cmd.Args[0:1], args...) //we need the first argument which is the command
return cmd
}
// AppendArguments appends the arguments to the exists
func (cmd *Cmd) AppendArguments(args ...string) *Cmd {
cmd.Cmd.Args = append(cmd.Cmd.Args, args...)
return cmd
}
// ResetArguments resets the arguments
func (cmd *Cmd) ResetArguments() *Cmd {
cmd.Args = cmd.Args[0:1] //keep only the first because is the command
return cmd
}
// Directory sets the working directory of the command.
// If workingDirectory is the empty string, Run runs the command in the
// calling process's current directory.
func (cmd *Cmd) Directory(workingDirectory string) *Cmd {
cmd.Cmd.Dir = workingDirectory
return cmd
}
// CommandBuilder creates a Cmd object and returns it
// accepts 2 parameters, one is optionally
// first parameter is the command (string)
// second variatic parameter is the argument(s) (slice of string)
//
// the difference from the normal Command function is that you can re-use this Cmd, it doesn't execute until you call its Command function
func CommandBuilder(command string, args ...string) *Cmd {
return &Cmd{Cmd: exec.Command(command, args...)}
}
//the below is just for exec.Command:
// Command executes a command in shell and returns it's output, it's block version
func Command(command string, a ...string) (output string, err error) {
var out []byte
//if no args given, try to get them from the command
if len(a) == 0 {
commandArgs := strings.Split(command, " ")
for _, commandArg := range commandArgs {
if commandArg[0] == '-' { // if starts with - means that this is an argument, append it to the arguments
a = append(a, commandArg)
}
}
}
out, err = exec.Command(command, a...).Output()
if err == nil {
output = string(out)
}
return
}
// MustCommand executes a command in shell and returns it's output, it's block version. It panics on an error
func MustCommand(command string, a ...string) (output string) {
var out []byte
var err error
if len(a) == 0 {
commandArgs := strings.Split(command, " ")
for _, commandArg := range commandArgs {
if commandArg[0] == '-' { // if starts with - means that this is an argument, append it to the arguments
a = append(a, commandArg)
}
}
}
out, err = exec.Command(command, a...).Output()
if err != nil {
argsToString := strings.Join(a, " ")
panic(fmt.Sprintf("\nError running the command %s", command+" "+argsToString))
}
output = string(out)
return
}
// Exists returns true if directory||file exists
func Exists(dir string) bool {
if _, err := os.Stat(dir); os.IsNotExist(err) {
return false
}
return true
}

View File

@@ -0,0 +1,124 @@
package npm
import (
"fmt"
"strings"
"time"
)
var (
// nodeModulesPath is the path of the root npm modules
// Ex: C:\\Users\\kataras\\AppData\\Roaming\\npm\\node_modules
nodeModulesPath string
)
type (
// NodeModuleResult holds Message and Error, if error != nil then the npm command has failed
NodeModuleResult struct {
// Message the message (string)
Message string
// Error the error (if any)
Error error
}
)
// NodeModulesPath sets the root directory for the node_modules and returns that
func NodeModulesPath() string {
if nodeModulesPath == "" {
nodeModulesPath = MustCommand("npm", "root", "-g") //here it ends with \n we have to remove it
nodeModulesPath = nodeModulesPath[0 : len(nodeModulesPath)-1]
}
return nodeModulesPath
}
func success(output string, a ...interface{}) NodeModuleResult {
return NodeModuleResult{fmt.Sprintf(output, a...), nil}
}
func fail(errMsg string, a ...interface{}) NodeModuleResult {
return NodeModuleResult{"", fmt.Errorf("\n"+errMsg, a...)}
}
// Output returns the error message if result.Error exists, otherwise returns the result.Message
func (res NodeModuleResult) Output() (out string) {
if res.Error != nil {
out = res.Error.Error()
} else {
out = res.Message
}
return
}
// NodeModuleInstall installs a module
func NodeModuleInstall(moduleName string) NodeModuleResult {
finish := make(chan bool)
go func() {
print("\n|")
print("_")
print("|")
for {
select {
case v := <-finish:
{
if v {
print("\010\010\010") //remove the loading chars
close(finish)
return
}
}
default:
print("\010\010-")
time.Sleep(time.Second / 2)
print("\010\\")
time.Sleep(time.Second / 2)
print("\010|")
time.Sleep(time.Second / 2)
print("\010/")
time.Sleep(time.Second / 2)
print("\010-")
time.Sleep(time.Second / 2)
print("|")
}
}
}()
out, err := Command("npm", "install", moduleName, "-g")
finish <- true
if err != nil {
return fail("Error installing module %s. Trace: %s", moduleName, err.Error())
}
return success("\n%s installed %s", moduleName, out)
}
// NodeModuleUnistall removes a module
func NodeModuleUnistall(moduleName string) NodeModuleResult {
out, err := Command("npm", "unistall", "-g", moduleName)
if err != nil {
return fail("Error unstalling module %s. Trace: %s", moduleName, err.Error())
}
return success("\n %s unistalled %s", moduleName, out)
}
// NodeModuleAbs returns the absolute path of the global node_modules directory + relative
func NodeModuleAbs(relativePath string) string {
return NodeModulesPath() + PathSeparator + strings.Replace(relativePath, "/", PathSeparator, -1)
}
// NodeModuleExists returns true if a module exists
// here we have two options
//1 . search by command something like npm -ls -g --depth=x
//2. search on files, we choose the second
func NodeModuleExists(executableRelativePath string) bool {
execAbsPath := NodeModuleAbs(executableRelativePath)
if execAbsPath == "" {
return false
}
return Exists(execAbsPath)
}

View File

@@ -0,0 +1,236 @@
package typescript
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/typescript/npm"
)
type (
// TsAdaptor the struct of the Typescript TsAdaptor, holds all necessary fields & methods
TsAdaptor struct {
Config *Config
// taken from framework
logger func(iris.LogMode, string)
}
)
// New creates & returns a new instnace typescript plugin
func New() *TsAdaptor {
c := DefaultConfig()
if !strings.Contains(c.Ignore, nodeModules) {
c.Ignore += "," + nodeModules
}
return &TsAdaptor{Config: &c}
}
// Adapt addapts a TsAdaptor to the Policies via EventPolicy.
// We use that method instead of direct return EventPolicy from new because
// the user should be able to change its configuration from that public API
func (t *TsAdaptor) Adapt(frame *iris.Policies) {
policy := iris.EventPolicy{
Build: t.build,
}
policy.Adapt(frame)
}
func (t *TsAdaptor) build(s *iris.Framework) {
t.logger = s.Log
t.start()
}
//
// implementation
func (t *TsAdaptor) start() {
if t.hasTypescriptFiles() {
//Can't check if permission denied returns always exists = true....
if !npm.NodeModuleExists(t.Config.Bin) {
t.logger(iris.DevMode, "Installing typescript, please wait...")
res := npm.NodeModuleInstall("typescript")
if res.Error != nil {
t.logger(iris.ProdMode, res.Error.Error())
return
}
t.logger(iris.DevMode, res.Message)
}
projects := t.getTypescriptProjects()
if len(projects) > 0 {
watchedProjects := 0
//typescript project (.tsconfig) found
for _, project := range projects {
cmd := npm.CommandBuilder("node", t.Config.Bin, "-p", project[0:strings.LastIndex(project, npm.PathSeparator)]) //remove the /tsconfig.json)
projectConfig, perr := FromFile(project)
if perr != nil {
t.logger(iris.ProdMode, "error while trying to read tsconfig: "+perr.Error())
continue
}
if projectConfig.CompilerOptions.Watch {
watchedProjects++
// if has watch : true then we have to wrap the command to a goroutine (I don't want to use the .Start here)
go func() {
_, err := cmd.Output()
if err != nil {
t.logger(iris.DevMode, err.Error())
return
}
}()
} else {
_, err := cmd.Output()
if err != nil {
t.logger(iris.DevMode, err.Error())
return
}
}
}
t.logger(iris.DevMode, fmt.Sprintf("%d Typescript project(s) compiled ( %d monitored by a background file watcher ) ", len(projects), watchedProjects))
} else {
//search for standalone typescript (.ts) files and compile them
files := t.getTypescriptFiles()
if len(files) > 0 {
watchedFiles := 0
if t.Config.Tsconfig.CompilerOptions.Watch {
watchedFiles = len(files)
}
//it must be always > 0 if we came here, because of if hasTypescriptFiles == true.
for _, file := range files {
absPath, err := filepath.Abs(file)
if err != nil {
continue
}
//these will be used if no .tsconfig found.
// cmd := npm.CommandBuilder("node", t.Config.Bin)
// cmd.Arguments(t.Config.Bin, t.Config.Tsconfig.CompilerArgs()...)
// cmd.AppendArguments(absPath)
compilerArgs := t.Config.Tsconfig.CompilerArgs()
cmd := npm.CommandBuilder("node", t.Config.Bin)
for _, s := range compilerArgs {
cmd.AppendArguments(s)
}
cmd.AppendArguments(absPath)
go func() {
compilerMsgB, _ := cmd.Output()
compilerMsg := string(compilerMsgB)
cmd.Args = cmd.Args[0 : len(cmd.Args)-1] //remove the last, which is the file
if strings.Contains(compilerMsg, "error") {
t.logger(iris.DevMode, compilerMsg)
}
}()
}
t.logger(iris.DevMode, fmt.Sprintf("%d Typescript file(s) compiled ( %d monitored by a background file watcher )", len(files), watchedFiles))
}
}
}
}
func (t *TsAdaptor) hasTypescriptFiles() bool {
root := t.Config.Dir
ignoreFolders := strings.Split(t.Config.Ignore, ",")
hasTs := false
if !npm.Exists(root) {
t.logger(iris.ProdMode, fmt.Sprintf("Typescript Adaptor Error: Directory '%s' couldn't be found,\nplease specify a valid path for your *.ts files", root))
return false
}
// ignore error
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
return nil
}
}
if strings.HasSuffix(path, ".ts") {
hasTs = true
return errors.New("Typescript found, hope that will stop here")
}
return nil
})
return hasTs
}
func (t *TsAdaptor) getTypescriptProjects() []string {
var projects []string
ignoreFolders := strings.Split(t.Config.Ignore, ",")
root := t.Config.Dir
//t.logger.Printf("\nSearching for typescript projects in %s", root)
// ignore error
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
//t.logger.Println(path + " ignored")
return filepath.SkipDir
}
}
if strings.HasSuffix(path, npm.PathSeparator+"tsconfig.json") {
//t.logger.Printf("\nTypescript project found in %s", path)
projects = append(projects, path)
}
return nil
})
return projects
}
// this is being called if getTypescriptProjects return 0 len, then we are searching for files using that:
func (t *TsAdaptor) getTypescriptFiles() []string {
var files []string
ignoreFolders := strings.Split(t.Config.Ignore, ",")
root := t.Config.Dir
// ignore error
filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
if fi.IsDir() {
return nil
}
for i := range ignoreFolders {
if strings.Contains(path, ignoreFolders[i]) {
//t.logger.Println(path + " ignored")
return nil
}
}
if strings.HasSuffix(path, ".ts") {
//t.logger.Printf("\nTypescript file found in %s", path)
files = append(files, path)
}
return nil
})
return files
}
//
//

View File

@@ -0,0 +1,93 @@
package main
import (
"encoding/xml"
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
// ExampleXML just a test struct to view represents xml content-type
type ExampleXML struct {
XMLName xml.Name `xml:"example"`
One string `xml:"one,attr"`
Two string `xml:"two,attr"`
}
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(gorillamux.New())
app.Get("/data", func(ctx *iris.Context) {
ctx.Data(iris.StatusOK, []byte("Some binary data here."))
})
app.Get("/text", func(ctx *iris.Context) {
ctx.Text(iris.StatusOK, "Plain text here")
})
app.Get("/json", func(ctx *iris.Context) {
ctx.JSON(iris.StatusOK, map[string]string{"hello": "json"}) // or myjsonStruct{hello:"json}
})
app.Get("/jsonp", func(ctx *iris.Context) {
ctx.JSONP(iris.StatusOK, "callbackName", map[string]string{"hello": "jsonp"})
})
app.Get("/xml", func(ctx *iris.Context) {
ctx.XML(iris.StatusOK, ExampleXML{One: "hello", Two: "xml"}) // or iris.Map{"One":"hello"...}
})
app.Get("/markdown", func(ctx *iris.Context) {
ctx.Markdown(iris.StatusOK, "# Hello Dynamic Markdown Iris")
})
app.Adapt(view.HTML("./templates", ".html"))
app.Get("/template", func(ctx *iris.Context) {
ctx.MustRender(
"hi.html", // the file name of the template relative to the './templates'
iris.Map{"Name": "Iris"}, // the .Name inside the ./templates/hi.html
iris.Map{"gzip": false}, // enable gzip for big files
)
})
// ------ first customization without even the need of *Context or a Handler--------
//
// Custom new content-/type:
// app.Adapt(iris.RenderPolicy(func(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (error, bool) {
// if name == "customcontent-type" {
//
// // some very advanced things here:
// out.Write([]byte(binding.(string)))
// return nil, true
// }
// return nil, false
// }))
//
// app.Get("/custom", func(ctx *iris.Context) {
// ctx.RenderWithStatus(iris.StatusOK, // or MustRender
// "customcontent-type",
// "my custom content here!",
// )
// })
//
// ---- second -----------------------------------------------------------------------
//
// Override the defaults (the json,xml,jsonp,text,data and so on), an existing content-type:
// app.Adapt(iris.RenderPolicy(func(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (error, bool) {
// if name == "text/plain" {
// out.Write([]byte("From the custom text/plain renderer: " + binding.(string)))
// return nil, true
// }
//
// return nil, false
// }))
// // the context.Text's behaviors was changed now by your custom renderer.
//
app.Listen(":8080")
}

View File

@@ -0,0 +1,8 @@
<html>
<head>
<title>Hi Iris</title>
</head>
<body>
<h1>Hi {{.Name}} </h1>
</body>
</html>

View File

@@ -0,0 +1,237 @@
// Code generated by go-bindata.
// sources:
// templates/hi.html
// DO NOT EDIT!
package main
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
)
func bindataRead(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
clErr := gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
if clErr != nil {
return nil, err
}
return buf.Bytes(), nil
}
type asset struct {
bytes []byte
info os.FileInfo
}
type bindataFileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
}
func (fi bindataFileInfo) Name() string {
return fi.name
}
func (fi bindataFileInfo) Size() int64 {
return fi.size
}
func (fi bindataFileInfo) Mode() os.FileMode {
return fi.mode
}
func (fi bindataFileInfo) ModTime() time.Time {
return fi.modTime
}
func (fi bindataFileInfo) IsDir() bool {
return false
}
func (fi bindataFileInfo) Sys() interface{} {
return nil
}
var _templatesHiHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xb2\xc9\x28\xc9\xcd\xb1\xe3\xe5\xb2\xc9\x48\x4d\x4c\x01\xd1\x25\x99\x25\x39\xa9\x76\x1e\x99\x0a\x9e\x45\x99\xc5\x0a\xd1\x21\x1e\xae\x0a\x21\x9e\x21\x3e\xae\xb1\x36\xfa\x10\x29\xa0\x1a\x7d\x98\xe2\xa4\xfc\x94\x4a\x20\xcd\x69\x93\x61\x08\xd2\x52\x5d\xad\xe7\x97\x98\x9b\x5a\x5b\x0b\x52\x03\x95\x03\x2a\x86\xd8\x00\x08\x00\x00\xff\xff\xed\x0e\xad\x42\x6a\x00\x00\x00")
func templatesHiHtmlBytes() ([]byte, error) {
return bindataRead(
_templatesHiHtml,
"templates/hi.html",
)
}
func templatesHiHtml() (*asset, error) {
bytes, err := templatesHiHtmlBytes()
if err != nil {
return nil, err
}
info := bindataFileInfo{name: "templates/hi.html", size: 106, mode: os.FileMode(438), modTime: time.Unix(1468907204, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
}
return a.bytes, nil
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// MustAsset is like Asset but panics when Asset would return an error.
// It simplifies safe initialization of global variables.
func MustAsset(name string) []byte {
a, err := Asset(name)
if err != nil {
panic("asset: Asset(" + name + "): " + err.Error())
}
return a
}
// AssetInfo loads and returns the asset info for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func AssetInfo(name string) (os.FileInfo, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
a, err := f()
if err != nil {
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
}
return a.info, nil
}
return nil, fmt.Errorf("AssetInfo %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() (*asset, error){
"templates/hi.html": templatesHiHtml,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for childName := range node.Children {
rv = append(rv, childName)
}
return rv, nil
}
type bintree struct {
Func func() (*asset, error)
Children map[string]*bintree
}
var _bintree = &bintree{nil, map[string]*bintree{
"templates": {nil, map[string]*bintree{
"hi.html": {templatesHiHtml, map[string]*bintree{}},
}},
}}
// RestoreAsset restores an asset under the given directory
func RestoreAsset(dir, name string) error {
data, err := Asset(name)
if err != nil {
return err
}
info, err := AssetInfo(name)
if err != nil {
return err
}
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
if err != nil {
return err
}
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
if err != nil {
return err
}
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
if err != nil {
return err
}
return nil
}
// RestoreAssets restores an asset under the given directory recursively
func RestoreAssets(dir, name string) error {
children, err := AssetDir(name)
// File
if err != nil {
return RestoreAsset(dir, name)
}
// Dir
for _, child := range children {
err = RestoreAssets(dir, filepath.Join(name, child))
if err != nil {
return err
}
}
return nil
}
func _filePath(dir, name string) string {
cannonicalName := strings.Replace(name, "\\", "/", -1)
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
}

View File

@@ -0,0 +1,24 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New())
//$ go-bindata ./templates/...
// templates are not used, you can delete the folder and run the example
app.Adapt(view.HTML("./templates", ".html").Binary(Asset, AssetNames))
app.Get("/hi", hi)
app.Listen(":8080")
}
func hi(ctx *iris.Context) {
ctx.MustRender("hi.html", struct{ Name string }{Name: "iris"})
}

View File

@@ -0,0 +1,8 @@
<html>
<head>
<title>Hi Iris [THE TITLE]</title>
</head>
<body>
<h1>Hi {{.Name}}
</body>
</html>

View File

@@ -0,0 +1,23 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New(iris.Configuration{Gzip: false, Charset: "UTF-8"}) // defaults to these
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New())
app.Adapt(view.HTML("./templates", ".html"))
app.Get("/hi", hi)
app.Listen(":8080")
}
func hi(ctx *iris.Context) {
ctx.MustRender("hi.html", struct{ Name string }{Name: "iris"})
}

View File

@@ -0,0 +1,8 @@
<html>
<head>
<title>Hi Iris</title>
</head>
<body>
<h1>Hi {{.Name}} </h1>
</body>
</html>

View File

@@ -0,0 +1,32 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
type mypage struct {
Title string
Message string
}
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New())
tmpl := view.HTML("./templates", ".html")
tmpl.Layout("layout.html")
app.Adapt(tmpl)
app.Get("/", func(ctx *iris.Context) {
ctx.Render("mypage.html", mypage{"My Page title", "Hello world!"}, iris.Map{"gzip": true})
// Note that: you can pass "layout" : "otherLayout.html" to bypass the config's Layout property
// or iris.NoLayout to disable layout on this render action.
// third is an optional parameter
})
app.Listen(":8080")
}

View File

@@ -0,0 +1,11 @@
<html>
<head>
<title>My Layout</title>
</head>
<body>
<h1>Body is:</h1>
<!-- Render the current template here -->
{{ yield }}
</body>
</html>

View File

@@ -0,0 +1,4 @@
<h1>
Title: {{.Title}}
</h1>
<h3>Message: {{.Message}} </h3>

View File

@@ -0,0 +1,3 @@
## Info
This folder examines the {{render "dir/templatefilename"}} functionality to manually render any template inside any template

View File

@@ -0,0 +1,49 @@
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New())
tmpl := view.HTML("./templates", ".html")
tmpl.Layout("layouts/layout.html")
tmpl.Funcs(map[string]interface{}{
"greet": func(s string) string {
return "Greetings " + s + "!"
},
})
app.Adapt(tmpl)
app.Get("/", func(ctx *iris.Context) {
if err := ctx.Render("page1.html", nil); err != nil {
println(err.Error())
}
})
// remove the layout for a specific route
app.Get("/nolayout", func(ctx *iris.Context) {
if err := ctx.Render("page1.html", nil, iris.RenderOptions{"layout": iris.NoLayout}); err != nil {
println(err.Error())
}
})
// set a layout for a party, .Layout should be BEFORE any Get or other Handle party's method
my := app.Party("/my").Layout("layouts/mylayout.html")
{
my.Get("/", func(ctx *iris.Context) {
ctx.MustRender("page1.html", nil)
})
my.Get("/other", func(ctx *iris.Context) {
ctx.MustRender("page1.html", nil)
})
}
app.Listen(":8080")
}

View File

@@ -0,0 +1,12 @@
<html>
<head>
<title>Layout</title>
</head>
<body>
<h1>This is the global layout</h1>
<br />
<!-- Render the current template here -->
{{ yield }}
</body>
</html>

View File

@@ -0,0 +1,12 @@
<html>
<head>
<title>my Layout</title>
</head>
<body>
<h1>This is the layout for the /my/ and /my/other routes only</h1>
<br />
<!-- Render the current template here -->
{{ yield }}
</body>
</html>

View File

@@ -0,0 +1,7 @@
<div style="background-color: black; color: blue">
<h1>Page 1 {{ greet "iris developer"}}</h1>
{{ render "partials/page1_partial1.html"}}
</div>

View File

@@ -0,0 +1,3 @@
<div style="background-color: white; color: red">
<h1>Page 1's Partial 1</h1>
</div>

View File

@@ -0,0 +1,53 @@
// Package main an example on how to naming your routes & use the custom 'url' HTML Template Engine, same for other template engines.
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(gorillamux.New())
app.Adapt(view.HTML("./templates", ".html"))
app.Get("/mypath", emptyHandler).ChangeName("my-page1")
app.Get("/mypath2/{param1}/{param2}", emptyHandler).ChangeName("my-page2")
app.Get("/mypath3/{param1}/statichere/{param2}", emptyHandler).ChangeName("my-page3")
app.Get("/mypath4/{param1}/statichere/{param2}/{otherparam}/{something:.*}", emptyHandler).ChangeName("my-page4")
// same with Handle/Func
app.HandleFunc("GET", "/mypath5/{param1}/statichere/{param2}/{otherparam}/anything/{something:.*}", emptyHandler).ChangeName("my-page5")
app.Get("/mypath6/{param1}/{param2}/staticParam/{param3AfterStatic}", emptyHandler).ChangeName("my-page6")
app.Get("/", func(ctx *iris.Context) {
// for /mypath6...
paramsAsArray := []string{"param1", "theParam1",
"param2", "theParam2",
"param3AfterStatic", "theParam3"}
if err := ctx.Render("page.html", iris.Map{"ParamsAsArray": paramsAsArray}); err != nil {
panic(err)
}
})
app.Get("/redirect/{namedRoute}", func(ctx *iris.Context) {
routeName := ctx.Param("namedRoute")
println("The full uri of " + routeName + "is: " + app.URL(routeName))
// if routeName == "my-page1"
// prints: The full uri of my-page1 is: http://127.0.0.1:8080/mypath
ctx.RedirectTo(routeName)
// http://127.0.0.1:8080/redirect/my-page1 will redirect to -> http://127.0.0.1:8080/mypath
})
app.Listen("localhost:8080")
}
func emptyHandler(ctx *iris.Context) {
ctx.Writef("Hello from %s.", ctx.Path())
}

View File

@@ -0,0 +1,27 @@
<a href="{{url "my-page1"}}">http://127.0.0.1:8080/mypath</a>
<br />
<br />
<a href="{{url "my-page2" "param1" "theParam1" "param2" "theParam2"}}">http://localhost:8080/mypath2/:param1/:param2</a>
<br />
<br />
<a href="{{url "my-page3" "param1" "theParam1" "param2" "theParam2AfterStatic"}}">
http://localhost:8080/mypath3/:param1/statichere/:param2</a>
<br />
<br />
<a href="{{url "my-page4" "param1" "theParam1" "param2" "theparam2AfterStatic" "otherparam" "otherParam" "something" "matchAnything"}}">http://localhost/mypath4/:param1/statichere/:param2/:otherparam/*something</a>
<br />
<br />
<a href="{{url "my-page5" "param1" "theParam1" "param2" "theParam2AfterStatic" "otherparam" "otherParam" "something" "matchAnythingAfterStatic"}}">
http://localhost:8080/mypath5/:param1/statichere/:param2/:otherparam/anything/*anything</a>
<br />
<br />
<a href={{url "my-page6" .ParamsAsArray }}>http://localhost:8080/mypath6/{param1}/{param2}/staticParam/{param3AfterStatic}</a>

View File

@@ -0,0 +1,32 @@
# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
# 102.54.94.97 rhino.acme.com # source server
# 38.25.63.10 x.acme.com # x client host
# localhost name resolution is handled within DNS itself.
127.0.0.1 localhost
::1 localhost
#-IRIS-For development machine, you have to configure your dns also for online, search google how to do it if you don't know
127.0.0.1 username1.127.0.0.1
127.0.0.1 username2.127.0.0.1
127.0.0.1 username3.127.0.0.1
127.0.0.1 username4.127.0.0.1
127.0.0.1 username5.127.0.0.1
# note that you can always use custom subdomains
#-END IRIS-
# Windows: Drive:/Windows/system32/drivers/etc/hosts, on Linux: /etc/hosts

View File

@@ -0,0 +1,53 @@
// Package main an example on how to naming your routes & use the custom 'url' HTML Template Engine, same for other template engines.
package main
import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/gorillamux"
"gopkg.in/kataras/iris.v6/adaptors/view"
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(gorillamux.New())
app.Adapt(view.HTML("./templates", ".html"))
app.Get("/mypath", emptyHandler).ChangeName("my-page1")
app.Get("/mypath2/{param1}/{param2}", emptyHandler).ChangeName("my-page2")
app.Get("/mypath3/{param1}/statichere/{param2}", emptyHandler).ChangeName("my-page3")
app.Get("/mypath4/{param1}/statichere/{param2}/{otherparam}/{something:.*}", emptyHandler).ChangeName("my-page4")
// same with Handle/Func
app.HandleFunc("GET", "/mypath5/{param1}/statichere/{param2}/{otherparam}/anything/{something:.*}", emptyHandler).ChangeName("my-page5")
app.Get("/mypath6/{param1}/{param2}/staticParam/{param3AfterStatic}", emptyHandler).ChangeName("my-page6")
app.Get("/", func(ctx *iris.Context) {
// for /mypath6...
paramsAsArray := []string{"param1", "theParam1",
"param2", "theParam2",
"param3AfterStatic", "theParam3"}
if err := ctx.Render("page.html", iris.Map{"ParamsAsArray": paramsAsArray}); err != nil {
panic(err)
}
})
app.Get("/redirect/{namedRoute}", func(ctx *iris.Context) {
routeName := ctx.Param("namedRoute")
println("The full uri of " + routeName + "is: " + app.URL(routeName))
// if routeName == "my-page1"
// prints: The full uri of my-page1 is: http://127.0.0.1:8080/mypath
ctx.RedirectTo(routeName)
// http://127.0.0.1:8080/redirect/my-page1 will redirect to -> http://127.0.0.1:8080/mypath
})
app.Listen("localhost:8080")
}
func emptyHandler(ctx *iris.Context) {
ctx.Writef("Hello from %s.", ctx.Path())
}

View File

@@ -0,0 +1,16 @@
<!-- the only difference between normal named routes and dynamic subdomains named routes is that the first argument of url
is the subdomain part instead of named parameter-->
<a href="{{url "dynamic-subdomain1" "username1"}}">username1.127.0.0.1:8080/mypath</a>
<br />
<br />
<a href="{{url "dynamic-subdomain2" "username2" "theParam1" "theParam2"}}">username2.127.0.0.1:8080/mypath2/:param1/:param2</a>
<br />
<br />
<a href="{{url "dynamic-subdomain3" "username3" "theParam1" "theParam2AfterStatic"}}">username3.127.0.0.1:8080/mypath3/:param1/statichere/:param2</a>
<br />
<br />
<a href="{{url "dynamic-subdomain4" "username4" "theParam1" "theparam2AfterStatic" "otherParam" "matchAnything"}}">username4.127.0.0.1:8080/mypath4/:param1/statichere/:param2/:otherparam/*something</a>
<br />
<br />
<a href="{{url "dynamic-subdomain5" .ParamsAsArray }}" >username5.127.0.0.1:8080/mypath6/:param1/:param2/staticParam/:param3AfterStatic</a>

136
adaptors/view/adaptor.go Normal file
View File

@@ -0,0 +1,136 @@
package view
import (
"io"
"strings"
"github.com/kataras/go-template"
"gopkg.in/kataras/iris.v6"
)
// Adaptor contains the common actions
// that all template engines share.
//
// We need to export that as it is without an interface
// because it may be used as a wrapper for a template engine
// that is not exists yet but community can create.
type Adaptor struct {
dir string
extension string
// for a .go template file lives inside the executable
assetFn func(name string) ([]byte, error)
namesFn func() []string
reload bool
engine template.Engine // used only on Adapt, we could make
//it as adaptEngine and pass a second parameter there but this would break the pattern.
}
// NewAdaptor returns a new general template engine policy adaptor.
func NewAdaptor(directory string, extension string, e template.Engine) *Adaptor {
return &Adaptor{
dir: directory,
extension: extension,
engine: e,
}
}
// Binary optionally, use it when template files are distributed
// inside the app executable (.go generated files).
func (h *Adaptor) Binary(assetFn func(name string) ([]byte, error), namesFn func() []string) *Adaptor {
h.assetFn = assetFn
h.namesFn = namesFn
return h
}
// Reload if setted to true the templates are reloading on each call,
// use it when you're in development and you're boring of restarting
// the whole app when you edit a template file
func (h *Adaptor) Reload(developmentMode bool) *Adaptor {
h.reload = developmentMode
return h
}
// Adapt adapts a template engine to the main Iris' policies.
// this specific Adapt is a multi-policies adaptors
// we use that instead of just return New() iris.RenderPolicy
// for two reasons:
// - the user may need to edit the adaptor's fields
// like Directory, Binary
// - we need to adapt an event policy to add the engine to the external mux
// and load it.
func (h *Adaptor) Adapt(frame *iris.Policies) {
mux := template.DefaultMux
// on the build state in order to have the shared funcs also
evt := iris.EventPolicy{
Build: func(s *iris.Framework) {
// mux has default to ./templates and .html ext
// no need for any checks here.
// the RenderPolicy will give a "no templates found on 'directory'"
// if user tries to use the context.Render without having the template files
// no need to panic here because we will use the html as the default template engine
// even if the user doesn't asks for
// or no? we had the defaults before... maybe better to give the user
// the oportunity to learn about the template's configuration
// (now 6.1.4 ) they are defaulted and users may don't know how and if they can change the configuration
// even if the book and examples covers these things, many times they're asking me on chat.............
// so no defaults? ok no defaults. This file then will be saved to /adaptors as with other template engines.
// simple.
mux.AddEngine(h.engine).
Directory(h.dir, h.extension).
Binary(h.assetFn, h.namesFn)
mux.Reload = h.reload
// notes for me: per-template engine funcs are setted by each template engine adaptor itself,
// here we will set the template funcs policy'.
// as I explain on the TemplateFuncsPolicy it exists in order to allow community to create plugins
// even by adding custom template funcs to their behaviors.
// We know that iris.TemplateFuncsPolicy is useless without this specific
// adaptor. We also know that it is not a good idea to have two
// policies with the same function or we can? wait. No we can't.
// We can't because:
// - the RenderPolicy should accept any type of render process, not only tempaltes.
// - I didn't design iris/policy.go to keep logic about implementation, this would make that very limited
// and I don't want to break that just for the templates.
// - We want community to be able to create packages which can adapt template functions but
// not to worry about the rest of the template engine adaptor policy.
// And even don't worry if the user has registered or use a template engine at all.
// So we should keep separate the TemplateFuncsPolicy(just map[string]interface{})
// from the rest of the implementation.
//
// So when the community wants to create a template adaptor has two options:
// - Use the RenderPolicy which is just a func
// - Use the kataras/iris/adaptors/view.Adaptor adaptor wrapper for RenderPolicy with a compination of kataras/go-template/.Engine
//
//
// So here is the only place we adapt the iris.TemplateFuncsPolicy to the tempaltes, if and only if templates are used,
// otherwise they are just ignored without any creepy things.
//
// The TemplateFuncsPolicy will work in compination with the specific template adaptor's functions(see html.go and the rest)
if len(frame.TemplateFuncsPolicy) > 0 {
mux.SetFuncMapToEngine(frame.TemplateFuncsPolicy, h.engine)
}
if err := mux.Load(); err != nil {
s.Log(iris.ProdMode, err.Error())
}
},
}
// adapt the build event to the main policies
evt.Adapt(frame)
r := iris.RenderPolicy(func(out io.Writer, file string, tmplContext interface{}, options ...map[string]interface{}) (error, bool) {
// template mux covers that but maybe we have more than one RenderPolicy
// and each of them carries a different mux on the new design.
if strings.Contains(file, h.extension) {
return mux.ExecuteWriter(out, file, tmplContext, options...), true
}
return nil, false
})
r.Adapt(frame)
}

48
adaptors/view/amber.go Normal file
View File

@@ -0,0 +1,48 @@
package view
import (
"github.com/kataras/go-template/amber"
)
// AmberAdaptor is the adaptor for the Amber, simple, engine.
// Read more about the Amber Go Template at:
// https://github.com/eknkc/amber
// and https://github.com/kataras/go-template/tree/master/amber
type AmberAdaptor struct {
*Adaptor
engine *amber.Engine
}
// Amber returns a new kataras/go-template/amber template engine
// with the same features as all iris' view engines have:
// Binary assets load (templates inside your executable with .go extension)
// Layout, Funcs, {{ url }} {{ urlpath}} for reverse routing and much more.
//
// Read more: https://github.com/eknkc/amber
func Amber(directory string, extension string) *AmberAdaptor {
e := amber.New()
return &AmberAdaptor{
Adaptor: NewAdaptor(directory, extension, e),
engine: e,
}
}
// Funcs adds the elements of the argument map 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 (a *AmberAdaptor) Funcs(funcMap map[string]interface{}) *AmberAdaptor {
if len(funcMap) == 0 {
return a
}
// configuration maps are never nil, because
// they are initialized at each of the engine's New func
// so we're just passing them inside it.
for k, v := range funcMap {
a.engine.Config.Funcs[k] = v
}
return a
}

93
adaptors/view/django.go Normal file
View File

@@ -0,0 +1,93 @@
package view
import (
"github.com/kataras/go-template/django"
)
type (
// Value conversion for django.Value
Value django.Value
// Error conversion for django.Error
Error django.Error
// FilterFunction conversion for django.FilterFunction
FilterFunction func(in *Value, param *Value) (out *Value, err *Error)
)
// 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 convertFilters(djangoFilters map[string]FilterFunction) map[string]django.FilterFunction {
filters := make(map[string]django.FilterFunction, len(djangoFilters))
for k, v := range djangoFilters {
func(filterName string, filterFunc FilterFunction) {
fn := django.FilterFunction(func(in *django.Value, param *django.Value) (*django.Value, *django.Error) {
theOut, theErr := filterFunc((*Value)(in), (*Value)(param))
return (*django.Value)(theOut), (*django.Error)(theErr)
})
filters[filterName] = fn
}(k, v)
}
return filters
}
// DjangoAdaptor is the adaptor for the Django engine.
// Read more about the Django Go Template at:
// https://github.com/flosch/pongo2
// and https://github.com/kataras/go-template/tree/master/django
type DjangoAdaptor struct {
*Adaptor
engine *django.Engine
filters map[string]FilterFunction
}
// Django returns a new kataras/go-template/django template engine
// with the same features as all iris' view engines have:
// Binary assets load (templates inside your executable with .go extension)
// Layout, Funcs, {{ url }} {{ urlpath}} for reverse routing and much more.
//
// Read more: https://github.com/flosch/pongo2
func Django(directory string, extension string) *DjangoAdaptor {
e := django.New()
return &DjangoAdaptor{
Adaptor: NewAdaptor(directory, extension, e),
engine: e,
filters: make(map[string]FilterFunction, 0),
}
}
// Filters for pongo2, map[name of the filter] the filter function .
//
// Note, these Filters function overrides ALL the previous filters
// It SETS a new filter map based on the given 'filtersMap' parameter.
func (d *DjangoAdaptor) Filters(filtersMap map[string]FilterFunction) *DjangoAdaptor {
if len(filtersMap) == 0 {
return d
}
// configuration maps are never nil, because
// they are initialized at each of the engine's New func
// so we're just passing them inside it.
filters := convertFilters(filtersMap)
d.engine.Config.Filters = filters
return d
}
// Globals share context fields between templates. https://github.com/flosch/pongo2/issues/35
func (d *DjangoAdaptor) Globals(globalsMap map[string]interface{}) *DjangoAdaptor {
if len(globalsMap) == 0 {
return d
}
for k, v := range globalsMap {
d.engine.Config.Globals[k] = v
}
return d
}
// DebugTemplates enables template debugging.
// The verbose error messages will appear in browser instead of quiet passes with error code.
func (d *DjangoAdaptor) DebugTemplates(debug bool) *DjangoAdaptor {
d.engine.Config.DebugTemplates = debug
return d
}

View File

@@ -0,0 +1,62 @@
package view
import (
"github.com/kataras/go-template/handlebars"
)
// HandlebarsAdaptor is the adaptor for the Handlebars engine.
// Read more about the Handlebars Go Template at:
// https://github.com/aymerick/raymond
// and https://github.com/kataras/go-template/tree/master/handlebars
type HandlebarsAdaptor struct {
*Adaptor
engine *handlebars.Engine
}
// Handlebars returns a new kataras/go-template/handlebars template engine
// with the same features as all iris' view engines have:
// Binary assets load (templates inside your executable with .go extension)
// Layout, Funcs, {{ url }} {{ urlpath}} for reverse routing and much more.
//
// Read more: https://github.com/aymerick/raymond
func Handlebars(directory string, extension string) *HandlebarsAdaptor {
e := handlebars.New()
return &HandlebarsAdaptor{
Adaptor: NewAdaptor(directory, extension, e),
engine: e,
}
}
// 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 whith its extension.
//
// Example: Handlebars("./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 (h *HandlebarsAdaptor) Layout(tmplLayoutFile string) *HandlebarsAdaptor {
h.engine.Config.Layout = tmplLayoutFile
return h
}
// Funcs adds the elements of the argument map 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
// - and handlebars specific, read more: https://github.com/aymerick/raymond.
func (h *HandlebarsAdaptor) Funcs(funcMap map[string]interface{}) *HandlebarsAdaptor {
if funcMap == nil {
return h
}
for k, v := range funcMap {
h.engine.Config.Helpers[k] = v
}
return h
}

92
adaptors/view/html.go Normal file
View File

@@ -0,0 +1,92 @@
package view
import (
"github.com/kataras/go-template/html"
)
// HTMLAdaptor is the html engine policy adaptor
// used like the "html/template" standard go package
// but with a lot of extra features by.
//
// This is just a wrapper of kataras/go-template/html.
type HTMLAdaptor struct {
*Adaptor
engine *html.Engine
}
// HTML creates and returns a new kataras/go-template/html engine.
// The html engine used like the "html/template" standard go package
// but with a lot of extra features.
func HTML(directory string, extension string) *HTMLAdaptor {
engine := html.New()
return &HTMLAdaptor{
Adaptor: NewAdaptor(directory, extension, engine),
// create the underline engine with the default configuration,
// which can be changed by this type.
//The whole funcs should called only before Iris' build & listen state.
engine: engine, // we need that for the configuration only.
}
}
// 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 (h *HTMLAdaptor) Delims(left string, right string) *HTMLAdaptor {
if left != "" && right != "" {
h.engine.Config.Left = left
h.engine.Config.Right = right
}
return h
}
// 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 whith 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 (h *HTMLAdaptor) Layout(tmplLayoutFile string) *HTMLAdaptor {
h.engine.Config.Layout = tmplLayoutFile
return h
}
// LayoutFuncs adds the elements of the argument map 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 (h *HTMLAdaptor) LayoutFuncs(funcMap map[string]interface{}) *HTMLAdaptor {
if funcMap != nil {
h.engine.Config.LayoutFuncs = funcMap
}
return h
}
// Funcs adds the elements of the argument map 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 (h *HTMLAdaptor) Funcs(funcMap map[string]interface{}) *HTMLAdaptor {
if len(funcMap) == 0 {
return h
}
// configuration maps are never nil, because
// they are initialized at each of the engine's New func
// so we're just passing them inside it.
for k, v := range funcMap {
h.engine.Config.Funcs[k] = v
}
return h
}

21
adaptors/view/pug.go Normal file
View File

@@ -0,0 +1,21 @@
package view
import (
"github.com/Joker/jade"
)
// Pug (or Jade) returns a new kataras/go-template/pug engine.
// It shares the same exactly logic with the
// HTMLAdaptor, 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 string, extension string) *HTMLAdaptor {
h := HTML(directory, extension)
// because I has designed the kataras/go-template
// so powerful, we can just pass a parser middleware
// into the standard html template engine
// and we're ready to go.
h.engine.Middleware = jade.Parse
return h
}