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:
19
adaptors/cors/LICENSE
Normal file
19
adaptors/cors/LICENSE
Normal 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
33
adaptors/cors/cors.go
Normal 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{}) }
|
||||
28
adaptors/gorillamux/LICENSE
Normal file
28
adaptors/gorillamux/LICENSE
Normal 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.
|
||||
103
adaptors/gorillamux/README.md
Normal file
103
adaptors/gorillamux/README.md
Normal 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.
|
||||
150
adaptors/gorillamux/gorillamux.go
Normal file
150
adaptors/gorillamux/gorillamux.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
47
adaptors/httprouter/LICENSE
Normal file
47
adaptors/httprouter/LICENSE
Normal 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.
|
||||
39
adaptors/httprouter/_example/main.go
Normal file
39
adaptors/httprouter/_example/main.go
Normal 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")
|
||||
}
|
||||
725
adaptors/httprouter/httprouter.go
Normal file
725
adaptors/httprouter/httprouter.go
Normal 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(
|
||||
"&", "&",
|
||||
"<", "<",
|
||||
">", ">",
|
||||
// """ is shorter than """.
|
||||
`"`, """,
|
||||
// "'" is shorter than "'" and apos was not in HTML until HTML5.
|
||||
"'", "'",
|
||||
)
|
||||
|
||||
// HTMLEscape returns a string which has no valid html code
|
||||
func HTMLEscape(s string) string {
|
||||
return htmlReplacer.Replace(s)
|
||||
}
|
||||
23
adaptors/httprouter/urlpath.go
Normal file
23
adaptors/httprouter/urlpath.go
Normal 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
|
||||
}
|
||||
21
adaptors/typescript/LICENSE
Normal file
21
adaptors/typescript/LICENSE
Normal 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.
|
||||
89
adaptors/typescript/README.md
Normal file
89
adaptors/typescript/README.md
Normal 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
|
||||
31
adaptors/typescript/_example/main.go
Normal file
31
adaptors/typescript/_example/main.go
Normal 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.
|
||||
8
adaptors/typescript/_example/www/index.html
Normal file
8
adaptors/typescript/_example/www/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Load my script (lawl)</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="scripts/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
16
adaptors/typescript/_example/www/scripts/app.ts
Normal file
16
adaptors/typescript/_example/www/scripts/app.ts
Normal 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);
|
||||
192
adaptors/typescript/config.go
Normal file
192
adaptors/typescript/config.go
Normal 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
|
||||
}
|
||||
21
adaptors/typescript/editor/LICENSE
Normal file
21
adaptors/typescript/editor/LICENSE
Normal 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.
|
||||
35
adaptors/typescript/editor/_example/main.go
Normal file
35
adaptors/typescript/editor/_example/main.go
Normal 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")
|
||||
}
|
||||
8
adaptors/typescript/editor/_example/www/index.html
Normal file
8
adaptors/typescript/editor/_example/www/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Load my script (lawl)</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="scripts/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
16
adaptors/typescript/editor/_example/www/scripts/app.ts
Normal file
16
adaptors/typescript/editor/_example/www/scripts/app.ts
Normal 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);
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
78
adaptors/typescript/editor/config.go
Normal file
78
adaptors/typescript/editor/config.go
Normal 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
|
||||
}
|
||||
216
adaptors/typescript/editor/editor.go
Normal file
216
adaptors/typescript/editor/editor.go
Normal 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)
|
||||
}
|
||||
114
adaptors/typescript/npm/exec.go
Normal file
114
adaptors/typescript/npm/exec.go
Normal 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
|
||||
}
|
||||
124
adaptors/typescript/npm/npm.go
Normal file
124
adaptors/typescript/npm/npm.go
Normal 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)
|
||||
}
|
||||
236
adaptors/typescript/typescript.go
Normal file
236
adaptors/typescript/typescript.go
Normal 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
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
93
adaptors/view/_examples/overview/main.go
Normal file
93
adaptors/view/_examples/overview/main.go
Normal 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")
|
||||
}
|
||||
8
adaptors/view/_examples/overview/templates/hi.html
Normal file
8
adaptors/view/_examples/overview/templates/hi.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Hi Iris</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi {{.Name}} </h1>
|
||||
</body>
|
||||
</html>
|
||||
237
adaptors/view/_examples/template_binary/bindata.go
Normal file
237
adaptors/view/_examples/template_binary/bindata.go
Normal 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, "/")...)...)
|
||||
}
|
||||
24
adaptors/view/_examples/template_binary/main.go
Normal file
24
adaptors/view/_examples/template_binary/main.go
Normal 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"})
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Hi Iris [THE TITLE]</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi {{.Name}}
|
||||
</body>
|
||||
</html>
|
||||
23
adaptors/view/_examples/template_html_0/main.go
Normal file
23
adaptors/view/_examples/template_html_0/main.go
Normal 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"})
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Hi Iris</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hi {{.Name}} </h1>
|
||||
</body>
|
||||
</html>
|
||||
32
adaptors/view/_examples/template_html_1/main.go
Normal file
32
adaptors/view/_examples/template_html_1/main.go
Normal 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")
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>My Layout</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<h1>Body is:</h1>
|
||||
<!-- Render the current template here -->
|
||||
{{ yield }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,4 @@
|
||||
<h1>
|
||||
Title: {{.Title}}
|
||||
</h1>
|
||||
<h3>Message: {{.Message}} </h3>
|
||||
3
adaptors/view/_examples/template_html_2/README.md
Normal file
3
adaptors/view/_examples/template_html_2/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## Info
|
||||
|
||||
This folder examines the {{render "dir/templatefilename"}} functionality to manually render any template inside any template
|
||||
49
adaptors/view/_examples/template_html_2/main.go
Normal file
49
adaptors/view/_examples/template_html_2/main.go
Normal 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")
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div style="background-color: black; color: blue">
|
||||
|
||||
<h1>Page 1 {{ greet "iris developer"}}</h1>
|
||||
|
||||
{{ render "partials/page1_partial1.html"}}
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
<div style="background-color: white; color: red">
|
||||
<h1>Page 1's Partial 1</h1>
|
||||
</div>
|
||||
53
adaptors/view/_examples/template_html_3/main.go
Normal file
53
adaptors/view/_examples/template_html_3/main.go
Normal 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())
|
||||
}
|
||||
27
adaptors/view/_examples/template_html_3/templates/page.html
Normal file
27
adaptors/view/_examples/template_html_3/templates/page.html
Normal 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>
|
||||
32
adaptors/view/_examples/template_html_4/hosts
Normal file
32
adaptors/view/_examples/template_html_4/hosts
Normal 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
|
||||
53
adaptors/view/_examples/template_html_4/main.go
Normal file
53
adaptors/view/_examples/template_html_4/main.go
Normal 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())
|
||||
}
|
||||
16
adaptors/view/_examples/template_html_4/templates/page.html
Normal file
16
adaptors/view/_examples/template_html_4/templates/page.html
Normal 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
136
adaptors/view/adaptor.go
Normal 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
48
adaptors/view/amber.go
Normal 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
93
adaptors/view/django.go
Normal 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
|
||||
}
|
||||
62
adaptors/view/handlebars.go
Normal file
62
adaptors/view/handlebars.go
Normal 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
92
adaptors/view/html.go
Normal 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
21
adaptors/view/pug.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user