From 648ce1336f96787383fb97b3c79df6f9b9334231 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 23 Aug 2018 06:30:12 +0300 Subject: [PATCH 01/91] New ':int64' and ':uint64' route path parameters - and - support the new uint64 for MVC (int64 was already supported there) - and - add ctx.Params().GetUint64 (GetInt64 was already there) - and - make the ':int or :number' to accept negative numbers with no digit limit (at low level) and rename the 'app.Macros().Int.RegisterFunc' to 'Number.RegisterFunc' because number can be any type of number not only standard go type limited - and - add alias for ':boolean' -> ':bool'. Finally, Update the examples but not the version yet, I have to provide a good README table to explain the end-developers how they can benefit by those changes and why the breaking change (which is to accept negative numbers via ':int') is for their own good and how they can make their own macro functions so they do not depend on the Iris builtn macro funcs only. More to come tomorrow, stay tuned --- README.md | 8 ++ _examples/README.md | 6 +- _examples/README_ZH.md | 6 +- _examples/overview/main.go | 2 +- _examples/routing/README.md | 67 +++++++------ _examples/routing/basic/main.go | 8 +- _examples/routing/dynamic-path/main.go | 56 ++++++----- _examples/routing/main.go | 30 +++--- _examples/routing/overview/main.go | 12 +-- _examples/subdomains/www/main.go | 4 +- context/route.go | 2 +- core/router/api_builder.go | 4 +- core/router/api_builder_benchmark_test.go | 2 +- core/router/macro.go | 99 ++++++++++++++++++- core/router/macro/interpreter/ast/ast.go | 69 ++++++++----- core/router/macro/interpreter/lexer/lexer.go | 2 +- .../macro/interpreter/lexer/lexer_test.go | 32 +++--- .../router/macro/interpreter/parser/parser.go | 4 +- .../macro/interpreter/parser/parser_test.go | 56 +++++++---- core/router/macro/interpreter/token/token.go | 4 +- core/router/macro/macro.go | 50 +++++++--- core/router/macro/macro_test.go | 88 ++++++++++++++--- core/router/party.go | 4 +- core/router/path_test.go | 14 +-- core/router/route.go | 4 +- doc.go | 69 ++++++------- mvc/controller_test.go | 9 +- mvc/param.go | 9 +- 28 files changed, 478 insertions(+), 242 deletions(-) diff --git a/README.md b/README.md index 1168a196f8..9e1c4f543d 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,14 @@ func main() { ctx.Writef("Hello %s", name) }) + // This handler will match /users/42 + // but will not match /users/-1 because uint should be bigger than zero + // neither /users or /users/. + app.Get("/users/{id:uint64}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") + ctx.Writef("User with ID: %d", id) + }) + // However, this one will match /user/john/ and also /user/john/send. app.Post("/user/{name:string}/{action:path}", func(ctx iris.Context) { name := ctx.Params().Get("name") diff --git a/_examples/README.md b/_examples/README.md index 26530a1931..4caf6d3db4 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -104,7 +104,7 @@ Structuring depends on your own needs. We can't tell you how to design your own ### Routing, Grouping, Dynamic Path Parameters, "Macros" and Custom Context -* `app.Get("{userid:int min(1)}", myHandler)` +* `app.Get("{userid:number min(1)}", myHandler)` * `app.Post("{asset:path}", myHandler)` * `app.Put("{custom:string regexp([a-z]+)}", myHandler)` @@ -128,10 +128,10 @@ app.Get("/profile/me", userHandler) // Matches all GET requests prefixed with /users/ // and followed by a number which should be equal or bigger than 1 -app.Get("/user/{userid:int min(1)}", getUserHandler) +app.Get("/user/{userid:number min(1)}", getUserHandler) // Matches all requests DELETE prefixed with /users/ // and following by a number which should be equal or bigger than 1 -app.Delete("/user/{userid:int min(1)}", deleteUserHandler) +app.Delete("/user/{userid:number min(1)}", deleteUserHandler) // Matches all GET requests except "/", "/about", anything starts with "/assets/" etc... // because it does not conflict with the rest of the routes. diff --git a/_examples/README_ZH.md b/_examples/README_ZH.md index 3958a5d4a0..a10ca6ada7 100644 --- a/_examples/README_ZH.md +++ b/_examples/README_ZH.md @@ -63,7 +63,7 @@ Iris 是个底层框架, 对 MVC 模式有很好的支持,但不限制文件 ### 路由、路由分组、路径动态参数、路由参数处理宏 、 自定义上下文 -* `app.Get("{userid:int min(1)}", myHandler)` +* `app.Get("{userid:number min(1)}", myHandler)` * `app.Post("{asset:path}", myHandler)` * `app.Put("{custom:string regexp([a-z]+)}", myHandler)` @@ -87,10 +87,10 @@ app.Get("/profile/me", userHandler) // 匹配所有前缀为 /users/ 的 GET 请求 // 参数为数字,且 >= 1 -app.Get("/user/{userid:int min(1)}", getUserHandler) +app.Get("/user/{userid:number min(1)}", getUserHandler) // 匹配所有前缀为 /users/ 的 DELETE 请求 // 参数为数字,且 >= 1 -app.Delete("/user/{userid:int min(1)}", deleteUserHandler) +app.Delete("/user/{userid:number min(1)}", deleteUserHandler) // 匹配所有 GET 请求,除了 "/", "/about", 或其他以 "/assets/" 开头 // 因为它不会与其他路线冲突。 diff --git a/_examples/overview/main.go b/_examples/overview/main.go index 29e52560e3..a834876e24 100644 --- a/_examples/overview/main.go +++ b/_examples/overview/main.go @@ -68,7 +68,7 @@ func main() { usersRoutes := app.Party("/users", logThisMiddleware) { // Method GET: http://localhost:8080/users/42 - usersRoutes.Get("/{id:int min(1)}", getUserByID) + usersRoutes.Get("/{id:number min(1)}", getUserByID) // Method POST: http://localhost:8080/users/create usersRoutes.Post("/create", createUser) } diff --git a/_examples/routing/README.md b/_examples/routing/README.md index 62cde33e32..14c3f90444 100644 --- a/_examples/routing/README.md +++ b/_examples/routing/README.md @@ -110,9 +110,9 @@ app := iris.New() users := app.Party("/users", myAuthMiddlewareHandler) // http://localhost:8080/users/42/profile -users.Get("/{id:int}/profile", userProfileHandler) +users.Get("/{id:uint64}/profile", userProfileHandler) // http://localhost:8080/users/messages/1 -users.Get("/inbox/{id:int}", userMessageHandler) +users.Get("/inbox/{id:uint64}", userMessageHandler) ``` The same could be also written using a function which accepts the child router(the Party). @@ -124,13 +124,13 @@ app.PartyFunc("/users", func(users iris.Party) { users.Use(myAuthMiddlewareHandler) // http://localhost:8080/users/42/profile - users.Get("/{id:int}/profile", userProfileHandler) + users.Get("/{id:uint64}/profile", userProfileHandler) // http://localhost:8080/users/messages/1 - users.Get("/inbox/{id:int}", userMessageHandler) + users.Get("/inbox/{id:uint64}", userMessageHandler) }) ``` -> `id:int` is a (typed) dynamic path parameter, learn more by scrolling down. +> `id:uint` is a (typed) dynamic path parameter, learn more by scrolling down. # Dynamic Path Parameters @@ -154,21 +154,27 @@ Standard macro types for route path parameters string type anything -+------------------------+ -| {param:int} | -+------------------------+ ++-------------------------------+ +| {param:number} or {param:int} | ++-------------------------------+ int type -only numbers (0-9) +both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch) -+------------------------+ -| {param:long} | -+------------------------+ ++-------------------------------+ +| {param:long} or {param:int64} | ++-------------------------------+ int64 type -only numbers (0-9) +-9223372036854775808 to 9223372036854775807 +------------------------+ -| {param:boolean} | +| {param:uint64} | +------------------------+ +uint64 type +0 to 18446744073709551615 + ++---------------------------------+ +| {param:bool} or {param:boolean} | ++---------------------------------+ bool type only "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" @@ -209,7 +215,7 @@ you are able to register your own too!. Register a named path parameter function ```go -app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) bool { +app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool { // [...] return true // -> true means valid, false means invalid fire 404 or if "else 500" is appended to the macro syntax then internal server error. @@ -219,7 +225,10 @@ app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) At the `func(argument ...)` you can have any standard type, it will be validated before the server starts so don't care about any performance cost there, the only thing it runs at serve time is the returning `func(paramValue string) bool`. ```go -{param:string equal(iris)} , "iris" will be the argument here: +{param:string equal(iris)} +``` +The "iris" will be the argument here: +```go app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool { return func(paramValue string){ return argument == paramValue } }) @@ -234,12 +243,12 @@ app.Get("/username/{name}", func(ctx iris.Context) { ctx.Writef("Hello %s", ctx.Params().Get("name")) }) // type is missing = {name:string} -// Let's register our first macro attached to int macro type. +// Let's register our first macro attached to number macro type. // "min" = the function // "minValue" = the argument of the function // func(string) bool = the macro's path parameter evaluator, this executes in serve time when -// a user requests a path which contains the :int macro type with the min(...) macro parameter function. -app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool { +// a user requests a path which contains the :number macro type with the min(...) macro parameter function. +app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool { // do anything before serve here [...] // at this case we don't need to do anything return func(paramValue string) bool { @@ -254,21 +263,21 @@ app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool { // http://localhost:8080/profile/id>=1 // this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1 // macro parameter functions are optional of course. -app.Get("/profile/{id:int min(1)}", func(ctx iris.Context) { +app.Get("/profile/{id:uint64 min(1)}", func(ctx iris.Context) { // second parameter is the error but it will always nil because we use macros, // the validaton already happened. - id, _ := ctx.Params().GetInt("id") + id, _ := ctx.Params().GetUint64("id") ctx.Writef("Hello id: %d", id) }) // to change the error code per route's macro evaluator: -app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") - friendid, _ := ctx.Params().GetInt("friendid") +app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") + friendid, _ := ctx.Params().GetUint64("friendid") ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. -// http://localhost:8080/game/a-zA-Z/level/0-9 +// http://localhost:8080/game/a-zA-Z/level/42 // remember, alphabetical is lowercase or uppercase letters only. app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) { ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level")) @@ -297,12 +306,10 @@ app.Run(iris.Addr(":8080")) } ``` -A **path parameter name should contain only alphabetical letters. Symbols like '_' and numbers are NOT allowed**. - +A path parameter name should contain only alphabetical letters or digits. Symbols like '_' are NOT allowed. Last, do not confuse `ctx.Params()` with `ctx.Values()`. -Path parameter's values goes to `ctx.Params()` and context's local storage -that can be used to communicate between handlers and middleware(s) goes to -`ctx.Values()`. +Path parameter's values can be retrieved from `ctx.Params()`, +context's local storage that can be used to communicate between handlers and middleware(s) can be stored to `ctx.Values()`. # Routing and reverse lookups diff --git a/_examples/routing/basic/main.go b/_examples/routing/basic/main.go index d694b20cff..9a62bdef92 100644 --- a/_examples/routing/basic/main.go +++ b/_examples/routing/basic/main.go @@ -29,12 +29,12 @@ func main() { app.Get("/donate", donateHandler, donateFinishHandler) // Pssst, don't forget dynamic-path example for more "magic"! - app.Get("/api/users/{userid:int min(1)}", func(ctx iris.Context) { - userID, err := ctx.Params().GetInt("userid") + app.Get("/api/users/{userid:uint64 min(1)}", func(ctx iris.Context) { + userID, err := ctx.Params().GetUint64("userid") if err != nil { ctx.Writef("error while trying to parse userid parameter," + - "this will never happen if :int is being used because if it's not integer it will fire Not Found automatically.") + "this will never happen if :uint64 is being used because if it's not a valid uint64 it will fire Not Found automatically.") ctx.StatusCode(iris.StatusBadRequest) return } @@ -103,7 +103,7 @@ func main() { ctx.Writef("All users") }) // http://v1.localhost:8080/api/users/42 - usersAPI.Get("/{userid:int}", func(ctx iris.Context) { + usersAPI.Get("/{userid:number}", func(ctx iris.Context) { ctx.Writef("user with id: %s", ctx.Params().Get("userid")) }) } diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 5a3f0b5751..de0f84d1e6 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -14,15 +14,14 @@ func main() { // we've seen static routes, group of routes, subdomains, wildcard subdomains, a small example of parameterized path // with a single known paramete and custom http errors, now it's time to see wildcard parameters and macros. - // iris, like net/http std package registers route's handlers + // Iris, like net/http std package registers route's handlers // by a Handler, the iris' type of handler is just a func(ctx iris.Context) // where context comes from github.com/kataras/iris/context. - // Until go 1.9 you will have to import that package too, after go 1.9 this will be not be necessary. // - // iris has the easiest and the most powerful routing process you have ever meet. + // Iris has the easiest and the most powerful routing process you have ever meet. // // At the same time, - // iris has its own interpeter(yes like a programming language) + // Iris has its own interpeter(yes like a programming language) // for route's path syntax and their dynamic path parameters parsing and evaluation, // We call them "macros" for shortcut. // How? It calculates its needs and if not any special regexp needed then it just @@ -36,21 +35,27 @@ func main() { // string type // anything // - // +------------------------+ - // | {param:int} | - // +------------------------+ + // +-------------------------------+ + // | {param:int} or {param:number} | + // +-------------------------------+ // int type - // only numbers (0-9) + // both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch) // - // +------------------------+ - // | {param:long} | - // +------------------------+ + // +-------------------------------+ + // | {param:int64} or {param:long} | + // +-------------------------------+ // int64 type - // only numbers (0-9) + // -9223372036854775808 to 9223372036854775807 // // +------------------------+ - // | {param:boolean} | + // | {param:uint64} | // +------------------------+ + // uint64 type + // 0 to 18446744073709551615 + // + // +---------------------------------+ + // | {param:bool} or {param:boolean} | + // +---------------------------------+ // bool type // only "1" or "t" or "T" or "TRUE" or "true" or "True" // or "0" or "f" or "F" or "FALSE" or "false" or "False" @@ -89,7 +94,7 @@ func main() { // you are able to register your own too!. // // Register a named path parameter function: - // app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) bool { + // app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool { // [...] // return true/false -> true means valid. // }) @@ -107,12 +112,12 @@ func main() { ctx.Writef("Hello %s", ctx.Params().Get("name")) }) // type is missing = {name:string} - // Let's register our first macro attached to int macro type. + // Let's register our first macro attached to number macro type. // "min" = the function // "minValue" = the argument of the function // func(string) bool = the macro's path parameter evaluator, this executes in serve time when - // a user requests a path which contains the :int macro type with the min(...) macro parameter function. - app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool { + // a user requests a path which contains the :number macro type with the min(...) macro parameter function. + app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool { // do anything before serve here [...] // at this case we don't need to do anything return func(paramValue string) bool { @@ -127,7 +132,7 @@ func main() { // http://localhost:8080/profile/id>=1 // this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1 // macro parameter functions are optional of course. - app.Get("/profile/{id:int min(1)}", func(ctx iris.Context) { + app.Get("/profile/{id:number min(1)}", func(ctx iris.Context) { // second parameter is the error but it will always nil because we use macros, // the validaton already happened. id, _ := ctx.Params().GetInt("id") @@ -135,8 +140,8 @@ func main() { }) // to change the error code per route's macro evaluator: - app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") + app.Get("/profile/{id:number min(1)}/friends/{friendid:number min(1) else 504}", func(ctx iris.Context) { + id, _ := ctx.Params().GetInt("id") // or GetUint64. friendid, _ := ctx.Params().GetInt("friendid") ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. @@ -159,9 +164,9 @@ func main() { // - // http://localhost:8080/game/a-zA-Z/level/0-9 + // http://localhost:8080/game/a-zA-Z/level/42 // remember, alphabetical is lowercase or uppercase letters only. - app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) { + app.Get("/game/{name:alphabetical}/level/{level:number}", func(ctx iris.Context) { ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level")) }) @@ -197,10 +202,9 @@ func main() { // if "/mypath/{myparam:path}" then the parameter has two names, one is the "*" and the other is the user-defined "myparam". // WARNING: - // A path parameter name should contain only alphabetical letters. Symbols like '_' and numbers are NOT allowed. + // A path parameter name should contain only alphabetical letters or digits. Symbols like '_' are NOT allowed. // Last, do not confuse `ctx.Params()` with `ctx.Values()`. - // Path parameter's values goes to `ctx.Params()` and context's local storage - // that can be used to communicate between handlers and middleware(s) goes to - // `ctx.Values()`. + // Path parameter's values can be retrieved from `ctx.Params()`, + // context's local storage that can be used to communicate between handlers and middleware(s) can be stored to `ctx.Values()`. app.Run(iris.Addr(":8080")) } diff --git a/_examples/routing/main.go b/_examples/routing/main.go index d9af5f3d34..9f4503274e 100644 --- a/_examples/routing/main.go +++ b/_examples/routing/main.go @@ -35,25 +35,25 @@ func registerGamesRoutes(app *iris.Application) { { // braces are optional of course, it's just a style of code // "GET" method - games.Get("/{gameID:int}/clans", h) - games.Get("/{gameID:int}/clans/clan/{clanPublicID:int}", h) - games.Get("/{gameID:int}/clans/search", h) + games.Get("/{gameID:uint64}/clans", h) + games.Get("/{gameID:uint64}/clans/clan/{clanPublicID:uint64}", h) + games.Get("/{gameID:uint64}/clans/search", h) // "PUT" method - games.Put("/{gameID:int}/players/{clanPublicID:int}", h) - games.Put("/{gameID:int}/clans/clan/{clanPublicID:int}", h) + games.Put("/{gameID:uint64}/players/{clanPublicID:uint64}", h) + games.Put("/{gameID:uint64}/clans/clan/{clanPublicID:uint64}", h) // remember: "clanPublicID" should not be changed to other routes with the same prefix. // "POST" method - games.Post("/{gameID:int}/clans", h) - games.Post("/{gameID:int}/players", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/leave", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/application", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/application/{action}", h) // {action} == {action:string} - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/invitation", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/invitation/{action}", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/delete", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/promote", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/demote", h) + games.Post("/{gameID:uint64}/clans", h) + games.Post("/{gameID:uint64}/players", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/leave", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/application", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/application/{action}", h) // {action} == {action:string} + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/invitation", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/invitation/{action}", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/delete", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/promote", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/demote", h) gamesCh := games.Party("/challenge") { diff --git a/_examples/routing/overview/main.go b/_examples/routing/overview/main.go index 65c15dc6d7..d2ff717843 100644 --- a/_examples/routing/overview/main.go +++ b/_examples/routing/overview/main.go @@ -68,8 +68,8 @@ func main() { // GET: http://localhost:8080/users/42 // **/users/42 and /users/help works after iris version 7.0.5** - usersRoutes.Get("/{id:int}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") + usersRoutes.Get("/{id:uint64}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") ctx.Writef("get user by id: %d", id) }) @@ -80,15 +80,15 @@ func main() { }) // PUT: http://localhost:8080/users - usersRoutes.Put("/{id:int}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") // or .Get to get its string represatantion. + usersRoutes.Put("/{id:uint64}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") // or .Get to get its string represatantion. username := ctx.PostValue("username") ctx.Writef("update user for id= %d and new username= %s", id, username) }) // DELETE: http://localhost:8080/users/42 - usersRoutes.Delete("/{id:int}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") + usersRoutes.Delete("/{id:uint64}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") ctx.Writef("delete user by id: %d", id) }) diff --git a/_examples/subdomains/www/main.go b/_examples/subdomains/www/main.go index c41e343d52..9ecfc96920 100644 --- a/_examples/subdomains/www/main.go +++ b/_examples/subdomains/www/main.go @@ -13,11 +13,11 @@ func newApp() *iris.Application { app.PartyFunc("/api/users", func(r iris.Party) { r.Get("/", info) - r.Get("/{id:int}", info) + r.Get("/{id:uint64}", info) r.Post("/", info) - r.Put("/{id:int}", info) + r.Put("/{id:uint64}", info) }) /* <- same as: usersAPI := app.Party("/api/users") { // those brackets are just syntactic-sugar things. diff --git a/context/route.go b/context/route.go index e632f11f0f..9cda8e96c3 100644 --- a/context/route.go +++ b/context/route.go @@ -25,7 +25,7 @@ type RouteReadOnly interface { // StaticPath returns the static part of the original, registered route path. // if /user/{id} it will return /user - // if /user/{id}/friend/{friendid:int} it will return /user too + // if /user/{id}/friend/{friendid:uint64} it will return /user too // if /assets/{filepath:path} it will return /assets. StaticPath() string diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 242fbadbb3..0022428a33 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -270,10 +270,10 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co // otherwise use `Party` which can handle many paths with different handlers and middlewares. // // Usage: -// app.HandleMany("GET", "/user /user/{id:int} /user/me", genericUserHandler) +// app.HandleMany("GET", "/user /user/{id:uint64} /user/me", genericUserHandler) // At the other side, with `Handle` we've had to write: // app.Handle("GET", "/user", userHandler) -// app.Handle("GET", "/user/{id:int}", userByIDHandler) +// app.Handle("GET", "/user/{id:uint64}", userByIDHandler) // app.Handle("GET", "/user/me", userMeHandler) // // This method is used behind the scenes at the `Controller` function diff --git a/core/router/api_builder_benchmark_test.go b/core/router/api_builder_benchmark_test.go index e16f798cb6..54469636cd 100644 --- a/core/router/api_builder_benchmark_test.go +++ b/core/router/api_builder_benchmark_test.go @@ -56,7 +56,7 @@ func genPaths(routesLength, minCharLength, maxCharLength int) []string { b.WriteString(randStringBytesMaskImprSrc(pathSegmentCharsLength)) b.WriteString("/{name:string}/") // sugar. b.WriteString(randStringBytesMaskImprSrc(pathSegmentCharsLength)) - b.WriteString("/{age:int}/end") + b.WriteString("/{age:number}/end") paths[i] = b.String() b.Reset() diff --git a/core/router/macro.go b/core/router/macro.go index ba6969ba30..aa2d8e8aff 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -35,8 +35,9 @@ func registerBuiltinsMacroFuncs(out *macro.Map) { // // these can be overridden by the user, later on. registerStringMacroFuncs(out.String) - registerIntMacroFuncs(out.Int) - registerIntMacroFuncs(out.Long) + registerNumberMacroFuncs(out.Number) + registerInt64MacroFuncs(out.Int64) + registerUint64MacroFuncs(out.Uint64) registerAlphabeticalMacroFuncs(out.Alphabetical) registerFileMacroFuncs(out.File) registerPathMacroFuncs(out.Path) @@ -87,9 +88,9 @@ func registerStringMacroFuncs(out *macro.Macro) { }) } -// Int -// only numbers (0-9) -func registerIntMacroFuncs(out *macro.Macro) { +// Number +// positive and negative numbers, number of digits depends on the arch. +func registerNumberMacroFuncs(out *macro.Macro) { // checks if the param value's int representation is // bigger or equal than 'min' out.RegisterFunc("min", func(min int) macro.EvaluatorFunc { @@ -131,6 +132,94 @@ func registerIntMacroFuncs(out *macro.Macro) { }) } +// Int64 +// -9223372036854775808 to 9223372036854775807. +func registerInt64MacroFuncs(out *macro.Macro) { + // checks if the param value's int64 representation is + // bigger or equal than 'min' + out.RegisterFunc("min", func(min int64) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseInt(paramValue, 10, 64) + if err != nil { + return false + } + return n >= min + } + }) + + // checks if the param value's int64 representation is + // smaller or equal than 'max' + out.RegisterFunc("max", func(max int64) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseInt(paramValue, 10, 64) + if err != nil { + return false + } + return n <= max + } + }) + + // checks if the param value's int64 representation is + // between min and max, including 'min' and 'max' + out.RegisterFunc("range", func(min, max int64) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseInt(paramValue, 10, 64) + if err != nil { + return false + } + + if n < min || n > max { + return false + } + return true + } + }) +} + +// Uint64 +// 0 to 18446744073709551615. +func registerUint64MacroFuncs(out *macro.Macro) { + // checks if the param value's uint64 representation is + // bigger or equal than 'min' + out.RegisterFunc("min", func(min uint64) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 64) + if err != nil { + return false + } + return n >= min + } + }) + + // checks if the param value's uint64 representation is + // smaller or equal than 'max' + out.RegisterFunc("max", func(max uint64) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 64) + if err != nil { + return false + } + return n <= max + } + }) + + // checks if the param value's uint64 representation is + // between min and max, including 'min' and 'max' + out.RegisterFunc("range", func(min, max uint64) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 64) + if err != nil { + return false + } + + if n < min || n > max { + return false + } + return true + } + }) +} + // Alphabetical // letters only (upper or lowercase) func registerAlphabeticalMacroFuncs(out *macro.Macro) { diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index 23e1723628..213d6b330b 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -16,20 +16,27 @@ const ( // ParamTypeString is the string type. // If parameter type is missing then it defaults to String type. // Allows anything - // Declaration: /mypath/{myparam:string} or /mypath{myparam} + // Declaration: /mypath/{myparam:string} or {myparam} ParamTypeString - // ParamTypeInt is the integer, a number type. - // Allows only positive numbers (0-9) - // Declaration: /mypath/{myparam:int} - ParamTypeInt - // ParamTypeLong is the integer, a number type. - // Allows only positive numbers (0-9) - // Declaration: /mypath/{myparam:long} - ParamTypeLong + + // ParamTypeNumber is the integer, a number type. + // Allows both positive and negative numbers, any number of digits. + // Declaration: /mypath/{myparam:number} or {myparam:int} for backwards-compatibility + ParamTypeNumber + + // ParamTypeInt64 is a number type. + // Allows only -9223372036854775808 to 9223372036854775807. + // Declaration: /mypath/{myparam:int64} or {myparam:long} + ParamTypeInt64 + // ParamTypeUint64 a number type. + // Allows only 0 to 18446744073709551615. + // Declaration: /mypath/{myparam:uint64} + ParamTypeUint64 + // ParamTypeBoolean is the bool type. // Allows only "1" or "t" or "T" or "TRUE" or "true" or "True" // or "0" or "f" or "F" or "FALSE" or "false" or "False". - // Declaration: /mypath/{myparam:boolean} + // Declaration: /mypath/{myparam:bool} or {myparam:boolean} ParamTypeBoolean // ParamTypeAlphabetical is the alphabetical/letter type type. // Allows letters only (upper or lowercase) @@ -79,10 +86,12 @@ func (pt ParamType) Kind() reflect.Kind { fallthrough case ParamTypeString: return reflect.String - case ParamTypeInt: + case ParamTypeNumber: return reflect.Int - case ParamTypeLong: + case ParamTypeInt64: return reflect.Int64 + case ParamTypeUint64: + return reflect.Uint64 case ParamTypeBoolean: return reflect.Bool } @@ -99,6 +108,8 @@ func ValidKind(k reflect.Kind) bool { fallthrough case reflect.Int64: fallthrough + case reflect.Uint64: + fallthrough case reflect.Bool: return true default: @@ -113,10 +124,17 @@ func (pt ParamType) Assignable(k reflect.Kind) bool { } var paramTypes = map[string]ParamType{ - "string": ParamTypeString, - "int": ParamTypeInt, - "long": ParamTypeLong, - "boolean": ParamTypeBoolean, + "string": ParamTypeString, + + "number": ParamTypeNumber, + "int": ParamTypeNumber, // same as number. + "long": ParamTypeInt64, + "int64": ParamTypeInt64, // same as long. + "uint64": ParamTypeUint64, + + "boolean": ParamTypeBoolean, + "bool": ParamTypeBoolean, // same as boolean. + "alphabetical": ParamTypeAlphabetical, "file": ParamTypeFile, "path": ParamTypePath, @@ -131,8 +149,10 @@ var paramTypes = map[string]ParamType{ // representation of a parameter type. // Available: // "string" -// "int" -// "long" +// "number" or "int" +// "long" or "int64" +// "uint64" +// "boolean" or "bool" // "alphabetical" // "file" // "path" @@ -149,17 +169,20 @@ func LookupParamType(ident string) ParamType { // make sure that caller resolves these types before this call. // // string matches to string -// int matches to int -// int64 matches to long -// bool matches to boolean +// int matches to int/number +// int64 matches to int64/long +// uint64 matches to uint64 +// bool matches to bool/boolean func LookupParamTypeFromStd(goType string) ParamType { switch goType { case "string": return ParamTypeString case "int": - return ParamTypeInt + return ParamTypeNumber case "int64": - return ParamTypeLong + return ParamTypeInt64 + case "uint64": + return ParamTypeUint64 case "bool": return ParamTypeBoolean default: diff --git a/core/router/macro/interpreter/lexer/lexer.go b/core/router/macro/interpreter/lexer/lexer.go index 79f7111fc0..01646361d4 100644 --- a/core/router/macro/interpreter/lexer/lexer.go +++ b/core/router/macro/interpreter/lexer/lexer.go @@ -179,7 +179,7 @@ func (l *Lexer) skipWhitespace() { func (l *Lexer) readIdentifier() string { pos := l.pos - for isLetter(l.ch) { + for isLetter(l.ch) || isDigit(l.ch) { l.readChar() } return l.input[pos:l.pos] diff --git a/core/router/macro/interpreter/lexer/lexer_test.go b/core/router/macro/interpreter/lexer/lexer_test.go index dad919f3f1..e104e8023c 100644 --- a/core/router/macro/interpreter/lexer/lexer_test.go +++ b/core/router/macro/interpreter/lexer/lexer_test.go @@ -7,27 +7,27 @@ import ( ) func TestNextToken(t *testing.T) { - input := `{id:int min(1) max(5) else 404}` + input := `{id:number min(1) max(5) else 404}` tests := []struct { expectedType token.Type expectedLiteral string }{ - {token.LBRACE, "{"}, // 0 - {token.IDENT, "id"}, // 1 - {token.COLON, ":"}, // 2 - {token.IDENT, "int"}, // 3 - {token.IDENT, "min"}, // 4 - {token.LPAREN, "("}, // 5 - {token.INT, "1"}, // 6 - {token.RPAREN, ")"}, // 7 - {token.IDENT, "max"}, // 8 - {token.LPAREN, "("}, // 9 - {token.INT, "5"}, // 10 - {token.RPAREN, ")"}, // 11 - {token.ELSE, "else"}, // 12 - {token.INT, "404"}, // 13 - {token.RBRACE, "}"}, // 14 + {token.LBRACE, "{"}, // 0 + {token.IDENT, "id"}, // 1 + {token.COLON, ":"}, // 2 + {token.IDENT, "number"}, // 3 + {token.IDENT, "min"}, // 4 + {token.LPAREN, "("}, // 5 + {token.INT, "1"}, // 6 + {token.RPAREN, ")"}, // 7 + {token.IDENT, "max"}, // 8 + {token.LPAREN, "("}, // 9 + {token.INT, "5"}, // 10 + {token.RPAREN, ")"}, // 11 + {token.ELSE, "else"}, // 12 + {token.INT, "404"}, // 13 + {token.RBRACE, "}"}, // 14 } l := New(input) diff --git a/core/router/macro/interpreter/parser/parser.go b/core/router/macro/interpreter/parser/parser.go index 8a352a73f3..84b15770f2 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/core/router/macro/interpreter/parser/parser.go @@ -120,11 +120,11 @@ func (p *ParamParser) Parse() (*ast.ParamStatement, error) { switch t.Type { case token.LBRACE: - // name, alphabetical and _, param names are not allowed to contain any number. + // can accept only letter or number only. nextTok := l.NextToken() stmt.Name = nextTok.Literal case token.COLON: - // type + // type can accept both letters and numbers but not symbols ofc. nextTok := l.NextToken() paramType := ast.LookupParamType(nextTok.Literal) if paramType == ast.ParamTypeUnExpected { diff --git a/core/router/macro/interpreter/parser/parser_test.go b/core/router/macro/interpreter/parser/parser_test.go index b1ce0ad85b..2ccd5f2deb 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/core/router/macro/interpreter/parser/parser_test.go @@ -30,7 +30,7 @@ func TestParseParamError(t *testing.T) { // // success - input2 := "{id:int range(1,5) else 404}" + input2 := "{id:uint64 range(1,5) else 404}" p.Reset(input2) _, err = p.Parse() @@ -47,9 +47,9 @@ func TestParseParam(t *testing.T) { }{ {true, ast.ParamStatement{ - Src: "{id:int min(1) max(5) else 404}", + Src: "{id:number min(1) max(5) else 404}", Name: "id", - Type: ast.ParamTypeInt, + Type: ast.ParamTypeNumber, Funcs: []ast.ParamFunc{ { Name: "min", @@ -63,9 +63,9 @@ func TestParseParam(t *testing.T) { {true, ast.ParamStatement{ - Src: "{id:int range(1,5)}", + Src: "{id:number range(1,5)}", Name: "id", - Type: ast.ParamTypeInt, + Type: ast.ParamTypeNumber, Funcs: []ast.ParamFunc{ { Name: "range", @@ -106,18 +106,18 @@ func TestParseParam(t *testing.T) { Type: ast.ParamTypeUnExpected, ErrorCode: 404, }}, // 5 - {false, // false because it will give an error of unexpeced token type with value 2 + {true, ast.ParamStatement{ Src: "{myparam2}", - Name: "myparam", // expected "myparam" because we don't allow integers to the parameter names. + Name: "myparam2", // we now allow integers to the parameter names. Type: ast.ParamTypeString, ErrorCode: 404, }}, // 6 {true, ast.ParamStatement{ - Src: "{id:int even()}", // test param funcs without any arguments (LPAREN peek for RPAREN) + Src: "{id:number even()}", // test param funcs without any arguments (LPAREN peek for RPAREN) Name: "id", - Type: ast.ParamTypeInt, + Type: ast.ParamTypeNumber, Funcs: []ast.ParamFunc{ { Name: "even"}, @@ -126,18 +126,32 @@ func TestParseParam(t *testing.T) { }}, // 7 {true, ast.ParamStatement{ - Src: "{id:long else 404}", + Src: "{id:int64 else 404}", Name: "id", - Type: ast.ParamTypeLong, + Type: ast.ParamTypeInt64, ErrorCode: 404, }}, // 8 {true, ast.ParamStatement{ - Src: "{has:boolean else 404}", + Src: "{id:long else 404}", // backwards-compatible test. + Name: "id", + Type: ast.ParamTypeInt64, + ErrorCode: 404, + }}, // 9 + {true, + ast.ParamStatement{ + Src: "{has:bool else 404}", Name: "has", Type: ast.ParamTypeBoolean, ErrorCode: 404, - }}, // 9 + }}, // 10 + {true, + ast.ParamStatement{ + Src: "{has:boolean else 404}", // backwards-compatible test. + Name: "has", + Type: ast.ParamTypeBoolean, + ErrorCode: 404, + }}, // 11 } @@ -167,11 +181,11 @@ func TestParse(t *testing.T) { valid bool expectedStatements []ast.ParamStatement }{ - {"/api/users/{id:int min(1) max(5) else 404}", true, + {"/api/users/{id:number min(1) max(5) else 404}", true, []ast.ParamStatement{{ - Src: "{id:int min(1) max(5) else 404}", + Src: "{id:number min(1) max(5) else 404}", Name: "id", - Type: ast.ParamTypeInt, + Type: ast.ParamTypeNumber, Funcs: []ast.ParamFunc{ { Name: "min", @@ -183,11 +197,11 @@ func TestParse(t *testing.T) { ErrorCode: 404, }, }}, // 0 - {"/admin/{id:int range(1,5)}", true, + {"/admin/{id:uint64 range(1,5)}", true, []ast.ParamStatement{{ - Src: "{id:int range(1,5)}", + Src: "{id:uint64 range(1,5)}", // test alternative (backwards-compatibility) "int" Name: "id", - Type: ast.ParamTypeInt, + Type: ast.ParamTypeUint64, Funcs: []ast.ParamFunc{ { Name: "range", @@ -233,10 +247,10 @@ func TestParse(t *testing.T) { ErrorCode: 404, }, }}, // 5 - {"/p2/{myparam2}", false, // false because it will give an error of unexpeced token type with value 2 + {"/p2/{myparam2}", true, []ast.ParamStatement{{ Src: "{myparam2}", - Name: "myparam", // expected "myparam" because we don't allow integers to the parameter names. + Name: "myparam2", // we now allow integers to the parameter names. Type: ast.ParamTypeString, ErrorCode: 404, }, diff --git a/core/router/macro/interpreter/token/token.go b/core/router/macro/interpreter/token/token.go index 620ad64103..f5cecbe9d1 100644 --- a/core/router/macro/interpreter/token/token.go +++ b/core/router/macro/interpreter/token/token.go @@ -13,8 +13,8 @@ type Token struct { // /about/{fullname:alphabetical} // /profile/{anySpecialName:string} -// {id:int range(1,5) else 404} -// /admin/{id:int eq(1) else 402} +// {id:uint64 range(1,5) else 404} +// /admin/{id:number eq(1) else 402} // /file/{filepath:file else 405} const ( EOF = iota // 0 diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index e4141c0789..26af655f12 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -214,14 +214,17 @@ type Map struct { // string type // anything String *Macro - // uint type - // only positive numbers (+0-9) - // it could be uint/uint32 but we keep int for simplicity - Int *Macro - // long an int64 type - // only positive numbers (+0-9) - // it could be uint64 but we keep int64 for simplicity - Long *Macro + + // int type + // both positive and negative numbers, any number of digits. + Number *Macro + // int64 as int64 type + // -9223372036854775808 to 9223372036854775807. + Int64 *Macro + // uint64 as uint64 type + // 0 to 18446744073709551615. + Uint64 *Macro + // boolean as bool type // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" // or "0" or "f" or "F" or "FALSE" or "false" or "False". @@ -247,11 +250,26 @@ type Map struct { // // Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path func NewMap() *Map { + simpleNumberEvalutator := MustNewEvaluatorFromRegexp("^-?[0-9]+$") return &Map{ // it allows everything, so no need for a regexp here. String: newMacro(func(string) bool { return true }), - Int: newMacro(MustNewEvaluatorFromRegexp("^[0-9]+$")), - Long: newMacro(MustNewEvaluatorFromRegexp("^[0-9]+$")), + Number: newMacro(simpleNumberEvalutator), //"^(-?0\\.[0-9]*[1-9]+[0-9]*$)|(^-?[1-9]+[0-9]*((\\.[0-9]*[1-9]+[0-9]*$)|(\\.[0-9]+)))|(^-?[1-9]+[0-9]*$)|(^0$){1}")), //("^-?[0-9]+$")), + Int64: newMacro(func(paramValue string) bool { + if !simpleNumberEvalutator(paramValue) { + return false + } + _, err := strconv.ParseInt(paramValue, 10, 64) + // if err == strconv.ErrRange... + return err == nil + }), //("^-[1-9]|-?[1-9][0-9]{1,14}|-?1000000000000000|-?10000000000000000|-?100000000000000000|-?[1-9]000000000000000000|-?9[0-2]00000000000000000|-?92[0-2]0000000000000000|-?922[0-3]000000000000000|-?9223[0-3]00000000000000|-?92233[0-7]0000000000000|-?922337[0-2]000000000000|-?92233720[0-3]0000000000|-?922337203[0-6]000000000|-?9223372036[0-8]00000000|-?92233720368[0-5]0000000|-?922337203685[0-4]000000|-?9223372036854[0-7]00000|-?92233720368547[0-7]0000|-?922337203685477[0-5]000|-?922337203685477[56]000|[0-9]$")), + Uint64: newMacro(func(paramValue string) bool { + if !simpleNumberEvalutator(paramValue) { + return false + } + _, err := strconv.ParseUint(paramValue, 10, 64) + return err == nil + }), //("^[0-9]|[1-9][0-9]{1,14}|1000000000000000|10000000000000000|100000000000000000|1000000000000000000|1[0-8]000000000000000000|18[0-4]00000000000000000|184[0-4]0000000000000000|1844[0-6]000000000000000|18446[0-7]00000000000000|184467[0-4]0000000000000|1844674[0-4]000000000000|184467440[0-7]0000000000|1844674407[0-3]000000000|18446744073[0-7]00000000|1844674407370000000[0-9]|18446744073709[0-5]00000|184467440737095[0-5]0000|1844674407370955[0-2]000$")), Boolean: newMacro(func(paramValue string) bool { // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$ // in this case. @@ -270,14 +288,16 @@ func NewMap() *Map { // Lookup returns the specific Macro from the map // based on the parameter type. -// i.e if ast.ParamTypeInt then it will return the m.Int. +// i.e if ast.ParamTypeNumber then it will return the m.Number. // Returns the m.String if not matched. func (m *Map) Lookup(typ ast.ParamType) *Macro { switch typ { - case ast.ParamTypeInt: - return m.Int - case ast.ParamTypeLong: - return m.Long + case ast.ParamTypeNumber: + return m.Number + case ast.ParamTypeInt64: + return m.Int64 + case ast.ParamTypeUint64: + return m.Uint64 case ast.ParamTypeBoolean: return m.Boolean case ast.ParamTypeAlphabetical: diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index d412da29e5..834c21e4e4 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -64,9 +64,9 @@ func TestGoodParamFuncName(t *testing.T) { } } -func testEvaluatorRaw(macroEvaluator *Macro, input string, pass bool, i int, t *testing.T) { +func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, pass bool, i int) { if got := macroEvaluator.Evaluator(input); pass != got { - t.Fatalf("tests[%d] - expecting %v but got %v", i, pass, got) + t.Fatalf("%s - tests[%d] - expecting %v but got %v", t.Name(), i, pass, got) } } @@ -86,26 +86,86 @@ func TestStringEvaluatorRaw(t *testing.T) { } // 0 for i, tt := range tests { - testEvaluatorRaw(f.String, tt.input, tt.pass, i, t) + testEvaluatorRaw(t, f.String, tt.input, tt.pass, i) } } -func TestIntEvaluatorRaw(t *testing.T) { +func TestNumberEvaluatorRaw(t *testing.T) { f := NewMap() tests := []struct { pass bool input string }{ - {false, "astring"}, // 0 - {false, "astringwith_numb3rS_and_symbol$"}, // 1 - {true, "32321"}, // 2 - {false, "main.css"}, // 3 - {false, "/assets/main.css"}, // 4 + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "18446744073709551615"}, // 3 + {true, "-18446744073709551615"}, // 4 + {true, "-18446744073709553213213213213213121615"}, // 5 + {false, "42 18446744073709551615"}, // 6 + {false, "--42"}, // 7 + {false, "+42"}, // 9 + {false, "main.css"}, // 9 + {false, "/assets/main.css"}, // 10 + } + + for i, tt := range tests { + testEvaluatorRaw(t, f.Number, tt.input, tt.pass, i) + } +} + +func TestInt64EvaluatorRaw(t *testing.T) { + f := NewMap() + + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {false, "18446744073709551615"}, // 2 + {false, "92233720368547758079223372036854775807"}, // 3 + {false, "9223372036854775808 9223372036854775808"}, // 4 + {false, "main.css"}, // 5 + {false, "/assets/main.css"}, // 6 + {true, "9223372036854775807"}, // 7 + {true, "-9223372036854775808"}, // 8 + {true, "-0"}, // 9 + {true, "1"}, // 10 + {true, "-042"}, // 11 + {true, "142"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, f.Int64, tt.input, tt.pass, i) + } +} + +func TestUint64EvaluatorRaw(t *testing.T) { + f := NewMap() + + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {false, "-9223372036854775808"}, // 2 + {false, "main.css"}, // 3 + {false, "/assets/main.css"}, // 4 + {false, "92233720368547758079223372036854775807"}, // 5 + {false, "9223372036854775808 9223372036854775808"}, // 6 + {false, "-1"}, // 7 + {false, "-0"}, // 8 + {false, "+1"}, // 9 + {true, "18446744073709551615"}, // 10 + {true, "9223372036854775807"}, // 11 + {true, "0"}, // 12 } for i, tt := range tests { - testEvaluatorRaw(f.Int, tt.input, tt.pass, i, t) + testEvaluatorRaw(t, f.Uint64, tt.input, tt.pass, i) } } @@ -124,7 +184,7 @@ func TestAlphabeticalEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(f.Alphabetical, tt.input, tt.pass, i, t) + testEvaluatorRaw(t, f.Alphabetical, tt.input, tt.pass, i) } } @@ -143,7 +203,7 @@ func TestFileEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(f.File, tt.input, tt.pass, i, t) + testEvaluatorRaw(t, f.File, tt.input, tt.pass, i) } } @@ -163,7 +223,7 @@ func TestPathEvaluatorRaw(t *testing.T) { } for i, tt := range pathTests { - testEvaluatorRaw(f.Path, tt.input, tt.pass, i, t) + testEvaluatorRaw(t, f.Path, tt.input, tt.pass, i) } } @@ -182,5 +242,5 @@ func TestPathEvaluatorRaw(t *testing.T) { // // p.Params = append(p.) -// testEvaluatorRaw(m.String, p.Src, false, 0, t) +// testEvaluatorRaw(t, m.String, p.Src, false, 0) // } diff --git a/core/router/party.go b/core/router/party.go index 8e8b56bd83..745d038e82 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -110,10 +110,10 @@ type Party interface { // otherwise use `Party` which can handle many paths with different handlers and middlewares. // // Usage: - // app.HandleMany(iris.MethodGet, "/user /user/{id:int} /user/me", userHandler) + // app.HandleMany(iris.MethodGet, "/user /user/{id:uint64} /user/me", userHandler) // At the other side, with `Handle` we've had to write: // app.Handle(iris.MethodGet, "/user", userHandler) - // app.Handle(iris.MethodGet, "/user/{id:int}", userHandler) + // app.Handle(iris.MethodGet, "/user/{id:uint64}", userHandler) // app.Handle(iris.MethodGet, "/user/me", userHandler) // // This method is used behind the scenes at the `Controller` function diff --git a/core/router/path_test.go b/core/router/path_test.go index 66ef283bac..103e2af9f8 100644 --- a/core/router/path_test.go +++ b/core/router/path_test.go @@ -27,8 +27,8 @@ func TestCleanPath(t *testing.T) { "/total/{year:string regexp(\\d{4})}/more/{s:string regexp(\\d{7})}"}, {"/single_no_params", "/single_no_params"}, - {"/single/{id:int}", - "/single/{id:int}"}, + {"/single/{id:uint64}", + "/single/{id:uint64}"}, } for i, tt := range tests { @@ -45,14 +45,16 @@ func TestSplitPath(t *testing.T) { }{ {"/v2/stores/{id:string format(uuid)} /v3", []string{"/v2/stores/{id:string format(uuid)}", "/v3"}}, - {"/user/{id:int} /admin/{id:int}", - []string{"/user/{id:int}", "/admin/{id:int}"}}, + {"/user/{id:uint64} /admin/{id:uint64}", + []string{"/user/{id:uint64}", "/admin/{id:uint64}"}}, + {"/users/{id:int} /admins/{id:int64}", + []string{"/users/{id:int}", "/admins/{id:int64}"}}, {"/user /admin", []string{"/user", "/admin"}}, {"/single_no_params", []string{"/single_no_params"}}, - {"/single/{id:int}", - []string{"/single/{id:int}"}}, + {"/single/{id:number}", + []string{"/single/{id:number}"}}, } equalSlice := func(s1 []string, s2 []string) bool { diff --git a/core/router/route.go b/core/router/route.go index 6c4918f157..b581b6596f 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -16,7 +16,7 @@ type Route struct { Method string `json:"method"` // "GET" methodBckp string // if Method changed to something else (which is possible at runtime as well, via RefreshRouter) then this field will be filled with the old one. Subdomain string `json:"subdomain"` // "admin." - tmpl *macro.Template // Tmpl().Src: "/api/user/{id:int}" + tmpl *macro.Template // Tmpl().Src: "/api/user/{id:uint64}" // temp storage, they're appended to the Handlers on build. // Execution happens before Handlers, can be empty. beginHandlers context.Handlers @@ -198,7 +198,7 @@ func formatPath(path string) string { // StaticPath returns the static part of the original, registered route path. // if /user/{id} it will return /user -// if /user/{id}/friend/{friendid:int} it will return /user too +// if /user/{id}/friend/{friendid:uint64} it will return /user too // if /assets/{filepath:path} it will return /assets. func (r Route) StaticPath() string { src := r.tmpl.Src diff --git a/doc.go b/doc.go index 00a781aef7..3137bead43 100644 --- a/doc.go +++ b/doc.go @@ -119,7 +119,7 @@ Example code: usersRoutes := app.Party("/users", logThisMiddleware) { // Method GET: http://localhost:8080/users/42 - usersRoutes.Get("/{id:int min(1)}", getUserByID) + usersRoutes.Get("/{id:uint64 min(1)}", getUserByID) // Method POST: http://localhost:8080/users/create usersRoutes.Post("/create", createUser) } @@ -146,7 +146,7 @@ Example code: } func getUserByID(ctx iris.Context) { - userID := ctx.Params().Get("id") // Or convert directly using: .Values().GetInt/GetInt64 etc... + userID := ctx.Params().Get("id") // Or convert directly using: .Values().GetInt/GetUint64/GetInt64 etc... // your own db fetch here instead of user :=... user := User{Username: "username" + userID} @@ -489,9 +489,9 @@ Example code: users := app.Party("/users", myAuthMiddlewareHandler) // http://myhost.com/users/42/profile - users.Get("/{id:int}/profile", userProfileHandler) + users.Get("/{id:uint64}/profile", userProfileHandler) // http://myhost.com/users/messages/1 - users.Get("/inbox/{id:int}", userMessageHandler) + users.Get("/inbox/{id:number}", userMessageHandler) Custom HTTP Errors @@ -548,12 +548,12 @@ Example code: app.Get("/donate", donateHandler, donateFinishHandler) // Pssst, don't forget dynamic-path example for more "magic"! - app.Get("/api/users/{userid:int min(1)}", func(ctx iris.Context) { - userID, err := ctx.Params().GetInt("userid") + app.Get("/api/users/{userid:uint64 min(1)}", func(ctx iris.Context) { + userID, err := ctx.Params().GetUint64("userid") if err != nil { ctx.Writef("error while trying to parse userid parameter," + - "this will never happen if :int is being used because if it's not integer it will fire Not Found automatically.") + "this will never happen if :number is being used because if it's not integer it will fire Not Found automatically.") ctx.StatusCode(iris.StatusBadRequest) return } @@ -622,8 +622,8 @@ Example code: ctx.Writef("All users") }) // http://v1.localhost:8080/api/users/42 - usersAPI.Get("/{userid:int}", func(ctx iris.Context) { - ctx.Writef("user with id: %s", ctx.Params().Get("userid")) + usersAPI.Get("/{userid:uint64}", func(ctx iris.Context) { + ctx.Writef("user with id: %s", ctx.Params().GetUint64("userid")) }) } } @@ -711,21 +711,27 @@ Standard macro types for parameters: string type anything - +------------------------+ - | {param:int} | - +------------------------+ + +-------------------------------+ + | {param:number} or {param:int} | + +-------------------------------+ int type - only numbers (0-9) + both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch) - +------------------------+ - | {param:long} | - +------------------------+ + +-------------------------------+ + | {param:long} or {param:int64} | + +-------------------------------+ int64 type - only numbers (0-9) + -9223372036854775808 to 9223372036854775807 +------------------------+ - | {param:boolean} | + | {param:uint64} | +------------------------+ + uint64 type + 0 to 18446744073709551615 + + +---------------------------------+ + | {param:bool} or {param:boolean} | + +---------------------------------+ bool type only "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" @@ -761,7 +767,7 @@ If a function not found on that type then the "string"'s types functions are bei i.e: - {param:int min(3)} + {param:number min(3)} Besides the fact that iris provides the basic types and some default "macro funcs" @@ -770,7 +776,7 @@ you are able to register your own too!. Register a named path parameter function: - app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) bool { + app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool { [...] return true/false -> true means valid. }) @@ -792,12 +798,12 @@ Example Code: ctx.Writef("Hello %s", ctx.Params().Get("name")) }) // type is missing = {name:string} - // Let's register our first macro attached to int macro type. + // Let's register our first macro attached to number macro type. // "min" = the function // "minValue" = the argument of the function // func(string) bool = the macro's path parameter evaluator, this executes in serve time when - // a user requests a path which contains the :int macro type with the min(...) macro parameter function. - app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool { + // a user requests a path which contains the number macro type with the min(...) macro parameter function. + app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool { // do anything before serve here [...] // at this case we don't need to do anything return func(paramValue string) bool { @@ -812,21 +818,21 @@ Example Code: // http://localhost:8080/profile/id>=1 // this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1 // macro parameter functions are optional of course. - app.Get("/profile/{id:int min(1)}", func(ctx iris.Context) { + app.Get("/profile/{id:uint64 min(1)}", func(ctx iris.Context) { // second parameter is the error but it will always nil because we use macros, // the validaton already happened. - id, _ := ctx.Params().GetInt("id") + id, _ := ctx.Params().GetUint64("id") ctx.Writef("Hello id: %d", id) }) // to change the error code per route's macro evaluator: - app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") - friendid, _ := ctx.Params().GetInt("friendid") + app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") + friendid, _ := ctx.Params().GetUint64("friendid") ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. - // http://localhost:8080/game/a-zA-Z/level/0-9 + // http://localhost:8080/game/a-zA-Z/level/42 // remember, alphabetical is lowercase or uppercase letters only. app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) { ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level")) @@ -855,11 +861,6 @@ Example Code: } - -A path parameter name should contain only alphabetical letters, symbols, containing '_' and numbers are NOT allowed. -If route failed to be registered, the app will panic without any warnings -if you didn't catch the second return value(error) on .Handle/.Get.... - Last, do not confuse ctx.Values() with ctx.Params(). Path parameter's values goes to ctx.Params() and context's local storage that can be used to communicate between handlers and middleware(s) goes to diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 475b79dc5c..70de2a7203 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -365,7 +365,8 @@ func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) { } func (c *testControllerRelPathFromFunc) Get() {} -func (c *testControllerRelPathFromFunc) GetBy(int64) {} +func (c *testControllerRelPathFromFunc) GetBy(uint64) {} +func (c *testControllerRelPathFromFunc) GetRatioBy(int64) {} func (c *testControllerRelPathFromFunc) GetAnythingByWildcard(string) {} func (c *testControllerRelPathFromFunc) GetLogin() {} @@ -388,8 +389,10 @@ func TestControllerRelPathFromFunc(t *testing.T) { e.GET("/").Expect().Status(iris.StatusOK). Body().Equal("GET:/") - e.GET("/42").Expect().Status(iris.StatusOK). - Body().Equal("GET:/42") + e.GET("/18446744073709551615").Expect().Status(iris.StatusOK). + Body().Equal("GET:/18446744073709551615") + e.GET("/ratio/-42").Expect().Status(iris.StatusOK). + Body().Equal("GET:/ratio/-42") e.GET("/something/true").Expect().Status(iris.StatusOK). Body().Equal("GET:/something/true") e.GET("/something/false").Expect().Status(iris.StatusOK). diff --git a/mvc/param.go b/mvc/param.go index 8b680aeef6..e261ac05da 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -37,16 +37,21 @@ func makeFuncParamGetter(paramType ast.ParamType, paramName string) reflect.Valu var fn interface{} switch paramType { - case ast.ParamTypeInt: + case ast.ParamTypeNumber: fn = func(ctx context.Context) int { v, _ := ctx.Params().GetInt(paramName) return v } - case ast.ParamTypeLong: + case ast.ParamTypeInt64: fn = func(ctx context.Context) int64 { v, _ := ctx.Params().GetInt64(paramName) return v } + case ast.ParamTypeUint64: + fn = func(ctx context.Context) uint64 { + v, _ := ctx.Params().GetUint64(paramName) + return v + } case ast.ParamTypeBoolean: fn = func(ctx context.Context) bool { v, _ := ctx.Params().GetBool(paramName) From 289b79ea921276113bd56d25c3f08e84fee553fb Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 23 Aug 2018 06:33:54 +0300 Subject: [PATCH 02/91] hero support for the new uint64 --- hero/param.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/hero/param.go b/hero/param.go index 9a9f028f33..278e507a72 100644 --- a/hero/param.go +++ b/hero/param.go @@ -44,6 +44,13 @@ func resolveParam(currentParamIndex int, typ reflect.Type) (reflect.Value, bool) entry, _ := ctx.Params().GetEntryAt(currentParamIndex) v, _ := entry.Int64Default(0) + return v + } + case reflect.Uint64: + fn = func(ctx context.Context) uint64 { + entry, _ := ctx.Params().GetEntryAt(currentParamIndex) + v, _ := entry.Uint64Default(0) + return v } case reflect.Bool: From aa317968e28e6286ec2bfc39e0fb9aaae320337a Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 23 Aug 2018 17:29:39 +0300 Subject: [PATCH 03/91] support more than string and int at macro functions route path input arguments: int,uint8,uint16,uint32,int8,int32,int64,slice of strings and string --- _examples/routing/dynamic-path/main.go | 14 ++--- core/router/macro/interpreter/ast/ast.go | 24 +------- .../router/macro/interpreter/parser/parser.go | 29 ++++----- .../macro/interpreter/parser/parser_test.go | 16 ++--- core/router/macro/macro.go | 60 +++++++++++++++++-- core/router/macro/macro_test.go | 36 ++++++++++- 6 files changed, 119 insertions(+), 60 deletions(-) diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index de0f84d1e6..42ce2f0aa2 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -112,16 +112,16 @@ func main() { ctx.Writef("Hello %s", ctx.Params().Get("name")) }) // type is missing = {name:string} - // Let's register our first macro attached to number macro type. + // Let's register our first macro attached to uint64 macro type. // "min" = the function // "minValue" = the argument of the function // func(string) bool = the macro's path parameter evaluator, this executes in serve time when - // a user requests a path which contains the :number macro type with the min(...) macro parameter function. - app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool { + // a user requests a path which contains the :uint64 macro parameter type with the min(...) macro parameter function. + app.Macros().Uint64.RegisterFunc("min", func(minValue uint64) func(string) bool { // do anything before serve here [...] // at this case we don't need to do anything return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) + n, err := strconv.ParseUint(paramValue, 10, 64) if err != nil { return false } @@ -129,10 +129,10 @@ func main() { } }) - // http://localhost:8080/profile/id>=1 + // http://localhost:8080/profile/id>=20 // this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1 // macro parameter functions are optional of course. - app.Get("/profile/{id:number min(1)}", func(ctx iris.Context) { + app.Get("/profile/{id:uint64 min(20)}", func(ctx iris.Context) { // second parameter is the error but it will always nil because we use macros, // the validaton already happened. id, _ := ctx.Params().GetInt("id") @@ -140,7 +140,7 @@ func main() { }) // to change the error code per route's macro evaluator: - app.Get("/profile/{id:number min(1)}/friends/{friendid:number min(1) else 504}", func(ctx iris.Context) { + app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { id, _ := ctx.Params().GetInt("id") // or GetUint64. friendid, _ := ctx.Params().GetInt("friendid") ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index 213d6b330b..4a4103a46b 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -1,9 +1,7 @@ package ast import ( - "fmt" "reflect" - "strconv" ) // ParamType is a specific uint8 type @@ -206,24 +204,6 @@ type ParamStatement struct { ErrorCode int // 404 } -// ParamFuncArg represents a single parameter function's argument -type ParamFuncArg interface{} - -// ParamFuncArgToInt converts and returns -// any type of "a", to an integer. -func ParamFuncArgToInt(a ParamFuncArg) (int, error) { - switch a.(type) { - case int: - return a.(int), nil - case string: - return strconv.Atoi(a.(string)) - case int64: - return int(a.(int64)), nil - default: - return -1, fmt.Errorf("unexpected function argument type: %q", a) - } -} - // ParamFunc holds the name of a parameter's function // and its arguments (values) // A param func is declared with: @@ -233,6 +213,6 @@ func ParamFuncArgToInt(a ParamFuncArg) (int, error) { // the 1 and 5 are the two param function arguments // range(1,5) type ParamFunc struct { - Name string // range - Args []ParamFuncArg // [1,5] + Name string // range + Args []string // ["1","5"] } diff --git a/core/router/macro/interpreter/parser/parser.go b/core/router/macro/interpreter/parser/parser.go index 84b15770f2..97920d5b2e 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/core/router/macro/interpreter/parser/parser.go @@ -82,10 +82,16 @@ const ( DefaultParamType = ast.ParamTypeString ) -func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) { - if t.Type == token.INT { - return ast.ParamFuncArgToInt(t.Literal) - } +// func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) { +// if t.Type == token.INT { +// return ast.ParamFuncArgToInt(t.Literal) +// } +// // act all as strings here, because of int vs int64 vs uint64 and etc. +// return t.Literal, nil +// } + +func parseParamFuncArg(t token.Token) (a string, err error) { + // act all as strings here, because of int vs int64 vs uint64 and etc. return t.Literal, nil } @@ -143,25 +149,14 @@ func (p *ParamParser) Parse() (*ast.ParamStatement, error) { argValTok := l.NextDynamicToken() // catch anything from "(" and forward, until ")", because we need to // be able to use regex expression as a macro type's func argument too. - argVal, err := parseParamFuncArg(argValTok) - if err != nil { - p.appendErr("[%d:%d] expected param func argument to be a string or number but got %s", t.Start, t.End, argValTok.Literal) - continue - } // fmt.Printf("argValTok: %#v\n", argValTok) // fmt.Printf("argVal: %#v\n", argVal) - lastParamFunc.Args = append(lastParamFunc.Args, argVal) + lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal) case token.COMMA: argValTok := l.NextToken() - argVal, err := parseParamFuncArg(argValTok) - if err != nil { - p.appendErr("[%d:%d] expected param func argument to be a string or number type but got %s", t.Start, t.End, argValTok.Literal) - continue - } - - lastParamFunc.Args = append(lastParamFunc.Args, argVal) + lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal) case token.RPAREN: stmt.Funcs = append(stmt.Funcs, lastParamFunc) lastParamFunc = ast.ParamFunc{} // reset diff --git a/core/router/macro/interpreter/parser/parser_test.go b/core/router/macro/interpreter/parser/parser_test.go index 2ccd5f2deb..25fb34003a 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/core/router/macro/interpreter/parser/parser_test.go @@ -53,10 +53,10 @@ func TestParseParam(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "min", - Args: []ast.ParamFuncArg{1}}, + Args: []string{"1"}}, { Name: "max", - Args: []ast.ParamFuncArg{5}}, + Args: []string{"5"}}, }, ErrorCode: 404, }}, // 0 @@ -69,7 +69,7 @@ func TestParseParam(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "range", - Args: []ast.ParamFuncArg{1, 5}}, + Args: []string{"1", "5"}}, }, ErrorCode: 404, }}, // 1 @@ -81,7 +81,7 @@ func TestParseParam(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "contains", - Args: []ast.ParamFuncArg{"."}}, + Args: []string{"."}}, }, ErrorCode: 404, }}, // 2 @@ -189,10 +189,10 @@ func TestParse(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "min", - Args: []ast.ParamFuncArg{1}}, + Args: []string{"1"}}, { Name: "max", - Args: []ast.ParamFuncArg{5}}, + Args: []string{"5"}}, }, ErrorCode: 404, }, @@ -205,7 +205,7 @@ func TestParse(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "range", - Args: []ast.ParamFuncArg{1, 5}}, + Args: []string{"1", "5"}}, }, ErrorCode: 404, }, @@ -218,7 +218,7 @@ func TestParse(t *testing.T) { Funcs: []ast.ParamFunc{ { Name: "contains", - Args: []ast.ParamFuncArg{"."}}, + Args: []string{"."}}, }, ErrorCode: 404, }, diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index 26af655f12..23a45d14b9 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -5,6 +5,7 @@ import ( "reflect" "regexp" "strconv" + "strings" "unicode" "github.com/kataras/iris/core/router/macro/interpreter/ast" @@ -95,7 +96,7 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { numFields := typFn.NumIn() - return func(args []ast.ParamFuncArg) EvaluatorFunc { + return func(args []string) EvaluatorFunc { if len(args) != numFields { // no variadics support, for now. panic("args should be the same len as numFields") @@ -105,11 +106,60 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { field := typFn.In(i) arg := args[i] - if field.Kind() != reflect.TypeOf(arg).Kind() { - panic("fields should have the same type") + // try to convert the string literal as we get it from the parser. + var ( + v interface{} + err error + ) + + // try to get the value based on the expected type. + switch field.Kind() { + case reflect.Int: + v, err = strconv.Atoi(arg) + case reflect.Int8: + v, err = strconv.ParseInt(arg, 10, 8) + case reflect.Int16: + v, err = strconv.ParseInt(arg, 10, 16) + case reflect.Int32: + v, err = strconv.ParseInt(arg, 10, 32) + case reflect.Int64: + v, err = strconv.ParseInt(arg, 10, 64) + case reflect.Uint8: + v, err = strconv.ParseUint(arg, 10, 8) + case reflect.Uint16: + v, err = strconv.ParseUint(arg, 10, 16) + case reflect.Uint32: + v, err = strconv.ParseUint(arg, 10, 32) + case reflect.Uint64: + v, err = strconv.ParseUint(arg, 10, 64) + case reflect.Float32: + v, err = strconv.ParseFloat(arg, 32) + case reflect.Float64: + v, err = strconv.ParseFloat(arg, 64) + case reflect.Bool: + v, err = strconv.ParseBool(arg) + case reflect.Slice: + if len(arg) > 1 { + if arg[0] == '[' && arg[len(arg)-1] == ']' { + // it is a single argument but as slice. + v = strings.Split(arg[1:len(arg)-1], ",") // only string slices. + } + } + + default: + v = arg } - argValues = append(argValues, reflect.ValueOf(arg)) + if err != nil { + panic(fmt.Sprintf("on field index: %d: %v", i, err)) + } + + argValue := reflect.ValueOf(v) + if expected, got := field.Kind(), argValue.Kind(); expected != got { + panic(fmt.Sprintf("fields should have the same type: [%d] expected %s but got %s", i, expected, got)) + } + + argValues = append(argValues, argValue) } evalFn := reflect.ValueOf(fn).Call(argValues)[0].Interface() @@ -149,7 +199,7 @@ type ( // and returns an EvaluatorFunc, its job // is to make the macros to be registered // by user at the most generic possible way. - ParamEvaluatorBuilder func([]ast.ParamFuncArg) EvaluatorFunc + ParamEvaluatorBuilder func([]string) EvaluatorFunc // ParamFunc represents the parsed // parameter function, it holds diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index 834c21e4e4..beab57f1b7 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -16,7 +16,7 @@ func TestGoodParamFunc(t *testing.T) { } } - good2 := func(min int, max int) func(string) bool { + good2 := func(min uint64, max uint64) func(string) bool { return func(paramValue string) bool { return true } @@ -244,3 +244,37 @@ func TestPathEvaluatorRaw(t *testing.T) { // testEvaluatorRaw(t, m.String, p.Src, false, 0) // } + +func TestConvertBuilderFunc(t *testing.T) { + fn := func(min uint64, slice []string) func(string) bool { + return func(paramValue string) bool { + if expected, got := "ok", paramValue; expected != got { + t.Fatalf("paramValue is not the expected one: %s vs %s", expected, got) + } + + if expected, got := uint64(1), min; expected != got { + t.Fatalf("min argument is not the expected one: %d vs %d", expected, got) + } + + if expected, got := []string{"name1", "name2"}, slice; len(expected) == len(got) { + if expected, got := "name1", slice[0]; expected != got { + t.Fatalf("slice argument[%d] does not contain the expected value: %s vs %s", 0, expected, got) + } + + if expected, got := "name2", slice[1]; expected != got { + t.Fatalf("slice argument[%d] does not contain the expected value: %s vs %s", 1, expected, got) + } + } else { + t.Fatalf("slice argument is not the expected one, the length is difference: %d vs %d", len(expected), len(got)) + } + + return true + } + } + + evalFunc := convertBuilderFunc(fn) + + if !evalFunc([]string{"1", "[name1,name2]"})("ok") { + t.Fatalf("failed, it should fail already") + } +} From 6da6c47e123279223bd45150b1db5adf9748503d Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 24 Aug 2018 00:56:54 +0300 Subject: [PATCH 04/91] add uint8 parameter type, and mvc and hero - this commit may be a point of tutorial on how to add a completely new type from scratch to the hero for future contributors --- _examples/routing/README.md | 6 +++ _examples/routing/dynamic-path/main.go | 13 ++++++ context/context.go | 9 +++- core/memstore/memstore.go | 53 ++++++++++++++++++++++++ core/router/macro.go | 46 ++++++++++++++++++++ core/router/macro/interpreter/ast/ast.go | 12 ++++++ core/router/macro/macro.go | 6 +++ core/router/macro/macro_test.go | 31 ++++++++++++++ doc.go | 6 +++ hero/param.go | 7 ++++ mvc/controller_test.go | 10 +++-- mvc/param.go | 5 +++ 12 files changed, 200 insertions(+), 4 deletions(-) diff --git a/_examples/routing/README.md b/_examples/routing/README.md index 14c3f90444..40074682dc 100644 --- a/_examples/routing/README.md +++ b/_examples/routing/README.md @@ -166,6 +166,12 @@ both positive and negative numbers, any number of digits (ctx.Params().GetInt wi int64 type -9223372036854775808 to 9223372036854775807 ++------------------------+ +| {param:uint8} | ++------------------------+ +uint8 type +0 to 255 + +------------------------+ | {param:uint64} | +------------------------+ diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 42ce2f0aa2..462634c78d 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -48,6 +48,13 @@ func main() { // -9223372036854775808 to 9223372036854775807 // // +------------------------+ + // | {param:uint8} | + // +------------------------+ + // uint8 type + // 0 to 255 + // + // + // +------------------------+ // | {param:uint64} | // +------------------------+ // uint64 type @@ -146,6 +153,12 @@ func main() { ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. + // :uint8 0 to 255. + app.Get("/ages/{age:uint8 else 400}", func(ctx iris.Context) { + age, _ := ctx.Params().GetUint8("age") + ctx.Writef("age selected: %d", age) + }) + // Another example using a custom regexp and any custom logic. latLonExpr := "^-?[0-9]{1,3}(?:\\.[0-9]{1,10})?$" latLonRegex, err := regexp.Compile(latLonExpr) diff --git a/context/context.go b/context/context.go index 46397a10f6..b558fe9a5c 100644 --- a/context/context.go +++ b/context/context.go @@ -162,7 +162,14 @@ func (r RequestParams) GetFloat64(key string) (float64, error) { return r.store.GetFloat64(key) } -// GetUint64 returns the path paramete's value as uint64, based on its key. +// GetUint8 returns the path parameter's value as uint8, based on its key. +// It checks for all available types of int, including int, string. +// It will return 0 and a non-nil error if parameter wasn't found. +func (r RequestParams) GetUint8(key string) (uint8, error) { + return r.store.GetUint8(key) +} + +// GetUint64 returns the path parameter's value as uint64, based on its key. // It checks for all available types of int, including int, uint64, int64, strings etc. // It will return 0 and a non-nil error if parameter wasn't found. func (r RequestParams) GetUint64(key string) (uint64, error) { diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 5b6bbdf2e3..489865144a 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -197,6 +197,39 @@ func (e Entry) Float32Default(key string, def float32) (float32, error) { return def, errFindParse.Format("float32", e.Key) } +// Uint8Default returns the entry's value as uint8. +// If not found returns "def" and a non-nil error. +func (e Entry) Uint8Default(def uint8) (uint8, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint8", e.Key) + } + + if vuint8, ok := v.(uint8); ok { + return vuint8, nil + } + + if vint, ok := v.(int); ok { + if vint < 0 || vint > 255 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vint), nil + } + + if vstring, sok := v.(string); sok { + vuint64, err := strconv.ParseUint(vstring, 10, 8) + if err != nil { + return def, err + } + if vuint64 > 255 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vuint64), nil + } + + return def, errFindParse.Format("uint8", e.Key) +} + // Uint64Default returns the entry's value as uint64. // If not found returns "def" and a non-nil error. func (e Entry) Uint64Default(def uint64) (uint64, error) { @@ -449,6 +482,26 @@ func (r *Store) GetIntDefault(key string, def int) int { return def } +// GetUint8 returns the entry's value as uint8, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint8(key string) (uint8, error) { + v := r.GetEntry(key) + if v == nil { + return 0, errFindParse.Format("uint8", key) + } + return v.Uint8Default(0) +} + +// GetUint8Default returns the entry's value as uint8, based on its key. +// If not found returns "def". +func (r *Store) GetUint8Default(key string, def uint8) uint8 { + if v, err := r.GetUint8(key); err == nil { + return v + } + + return def +} + // GetUint64 returns the entry's value as uint64, based on its key. // If not found returns 0 and a non-nil error. func (r *Store) GetUint64(key string) (uint64, error) { diff --git a/core/router/macro.go b/core/router/macro.go index aa2d8e8aff..e46a07c7b7 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -37,6 +37,7 @@ func registerBuiltinsMacroFuncs(out *macro.Map) { registerStringMacroFuncs(out.String) registerNumberMacroFuncs(out.Number) registerInt64MacroFuncs(out.Int64) + registerUint8MacroFuncs(out.Uint8) registerUint64MacroFuncs(out.Uint64) registerAlphabeticalMacroFuncs(out.Alphabetical) registerFileMacroFuncs(out.File) @@ -176,6 +177,51 @@ func registerInt64MacroFuncs(out *macro.Macro) { }) } +// Uint8 +// 0 to 255. +func registerUint8MacroFuncs(out *macro.Macro) { + // checks if the param value's uint8 representation is + // bigger or equal than 'min' + out.RegisterFunc("min", func(min uint8) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + + return uint8(n) >= min + } + }) + + // checks if the param value's uint8 representation is + // smaller or equal than 'max' + out.RegisterFunc("max", func(max uint8) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + return uint8(n) <= max + } + }) + + // checks if the param value's uint8 representation is + // between min and max, including 'min' and 'max' + out.RegisterFunc("range", func(min, max uint8) macro.EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + + if v := uint8(n); v < min || v > max { + return false + } + return true + } + }) +} + // Uint64 // 0 to 18446744073709551615. func registerUint64MacroFuncs(out *macro.Macro) { diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index 4a4103a46b..4299569006 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -26,6 +26,10 @@ const ( // Allows only -9223372036854775808 to 9223372036854775807. // Declaration: /mypath/{myparam:int64} or {myparam:long} ParamTypeInt64 + // ParamTypeUint8 a number type. + // Allows only 0 to 255. + // Declaration: /mypath/{myparam:uint8} + ParamTypeUint8 // ParamTypeUint64 a number type. // Allows only 0 to 18446744073709551615. // Declaration: /mypath/{myparam:uint64} @@ -88,6 +92,8 @@ func (pt ParamType) Kind() reflect.Kind { return reflect.Int case ParamTypeInt64: return reflect.Int64 + case ParamTypeUint8: + return reflect.Uint8 case ParamTypeUint64: return reflect.Uint64 case ParamTypeBoolean: @@ -106,6 +112,8 @@ func ValidKind(k reflect.Kind) bool { fallthrough case reflect.Int64: fallthrough + case reflect.Uint8: + fallthrough case reflect.Uint64: fallthrough case reflect.Bool: @@ -128,6 +136,7 @@ var paramTypes = map[string]ParamType{ "int": ParamTypeNumber, // same as number. "long": ParamTypeInt64, "int64": ParamTypeInt64, // same as long. + "uint8": ParamTypeUint8, "uint64": ParamTypeUint64, "boolean": ParamTypeBoolean, @@ -149,6 +158,7 @@ var paramTypes = map[string]ParamType{ // "string" // "number" or "int" // "long" or "int64" +// "uint8" // "uint64" // "boolean" or "bool" // "alphabetical" @@ -179,6 +189,8 @@ func LookupParamTypeFromStd(goType string) ParamType { return ParamTypeNumber case "int64": return ParamTypeInt64 + case "uint8": + return ParamTypeUint8 case "uint64": return ParamTypeUint64 case "bool": diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index 23a45d14b9..7ea4630fef 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -271,6 +271,9 @@ type Map struct { // int64 as int64 type // -9223372036854775808 to 9223372036854775807. Int64 *Macro + // uint8 as uint8 type + // 0 to 255. + Uint8 *Macro // uint64 as uint64 type // 0 to 18446744073709551615. Uint64 *Macro @@ -313,6 +316,7 @@ func NewMap() *Map { // if err == strconv.ErrRange... return err == nil }), //("^-[1-9]|-?[1-9][0-9]{1,14}|-?1000000000000000|-?10000000000000000|-?100000000000000000|-?[1-9]000000000000000000|-?9[0-2]00000000000000000|-?92[0-2]0000000000000000|-?922[0-3]000000000000000|-?9223[0-3]00000000000000|-?92233[0-7]0000000000000|-?922337[0-2]000000000000|-?92233720[0-3]0000000000|-?922337203[0-6]000000000|-?9223372036[0-8]00000000|-?92233720368[0-5]0000000|-?922337203685[0-4]000000|-?9223372036854[0-7]00000|-?92233720368547[0-7]0000|-?922337203685477[0-5]000|-?922337203685477[56]000|[0-9]$")), + Uint8: newMacro(MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")), Uint64: newMacro(func(paramValue string) bool { if !simpleNumberEvalutator(paramValue) { return false @@ -346,6 +350,8 @@ func (m *Map) Lookup(typ ast.ParamType) *Macro { return m.Number case ast.ParamTypeInt64: return m.Int64 + case ast.ParamTypeUint8: + return m.Uint8 case ast.ParamTypeUint64: return m.Uint64 case ast.ParamTypeBoolean: diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index beab57f1b7..83092939e3 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -142,6 +142,37 @@ func TestInt64EvaluatorRaw(t *testing.T) { } } +func TestUint8EvaluatorRaw(t *testing.T) { + f := NewMap() + + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {false, "-9223372036854775808"}, // 2 + {false, "main.css"}, // 3 + {false, "/assets/main.css"}, // 4 + {false, "92233720368547758079223372036854775807"}, // 5 + {false, "9223372036854775808 9223372036854775808"}, // 6 + {false, "-1"}, // 7 + {false, "-0"}, // 8 + {false, "+1"}, // 9 + {false, "18446744073709551615"}, // 10 + {false, "9223372036854775807"}, // 11 + {false, "021"}, // 12 - no leading zeroes are allowed. + {false, "300"}, // 13 + {true, "0"}, // 14 + {true, "255"}, // 15 + {true, "21"}, // 16 + } + + for i, tt := range tests { + testEvaluatorRaw(t, f.Uint8, tt.input, tt.pass, i) + } +} + func TestUint64EvaluatorRaw(t *testing.T) { f := NewMap() diff --git a/doc.go b/doc.go index 3137bead43..dc5f93b103 100644 --- a/doc.go +++ b/doc.go @@ -723,6 +723,12 @@ Standard macro types for parameters: int64 type -9223372036854775808 to 9223372036854775807 + +------------------------+ + | {param:uint8} | + +------------------------+ + uint8 type + 0 to 255 + +------------------------+ | {param:uint64} | +------------------------+ diff --git a/hero/param.go b/hero/param.go index 278e507a72..4ea62fc298 100644 --- a/hero/param.go +++ b/hero/param.go @@ -44,6 +44,13 @@ func resolveParam(currentParamIndex int, typ reflect.Type) (reflect.Value, bool) entry, _ := ctx.Params().GetEntryAt(currentParamIndex) v, _ := entry.Int64Default(0) + return v + } + case reflect.Uint8: + fn = func(ctx context.Context) uint8 { + entry, _ := ctx.Params().GetEntryAt(currentParamIndex) + v, _ := entry.Uint8Default(0) + return v } case reflect.Uint64: diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 70de2a7203..525c2103ba 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -366,7 +366,8 @@ func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) { func (c *testControllerRelPathFromFunc) Get() {} func (c *testControllerRelPathFromFunc) GetBy(uint64) {} -func (c *testControllerRelPathFromFunc) GetRatioBy(int64) {} +func (c *testControllerRelPathFromFunc) GetUint8RatioBy(uint8) {} +func (c *testControllerRelPathFromFunc) GetUint64RatioBy(int64) {} func (c *testControllerRelPathFromFunc) GetAnythingByWildcard(string) {} func (c *testControllerRelPathFromFunc) GetLogin() {} @@ -391,8 +392,11 @@ func TestControllerRelPathFromFunc(t *testing.T) { e.GET("/18446744073709551615").Expect().Status(iris.StatusOK). Body().Equal("GET:/18446744073709551615") - e.GET("/ratio/-42").Expect().Status(iris.StatusOK). - Body().Equal("GET:/ratio/-42") + e.GET("/uint8/ratio/255").Expect().Status(iris.StatusOK). + Body().Equal("GET:/uint8/ratio/255") + e.GET("/uint8/ratio/256").Expect().Status(iris.StatusNotFound) + e.GET("/uint64/ratio/-42").Expect().Status(iris.StatusOK). + Body().Equal("GET:/uint64/ratio/-42") e.GET("/something/true").Expect().Status(iris.StatusOK). Body().Equal("GET:/something/true") e.GET("/something/false").Expect().Status(iris.StatusOK). diff --git a/mvc/param.go b/mvc/param.go index e261ac05da..c2f2299022 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -47,6 +47,11 @@ func makeFuncParamGetter(paramType ast.ParamType, paramName string) reflect.Valu v, _ := ctx.Params().GetInt64(paramName) return v } + case ast.ParamTypeUint8: + fn = func(ctx context.Context) uint8 { + v, _ := ctx.Params().GetUint8(paramName) + return v + } case ast.ParamTypeUint64: fn = func(ctx context.Context) uint64 { v, _ := ctx.Params().GetUint64(paramName) From b603a47e00f71e910d58f53f65e029e6acbc1fa7 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 24 Aug 2018 14:41:53 +0300 Subject: [PATCH 05/91] add a table on the README for the param types, macro funcs and do it yourself section --- README.md | 101 +++++++++++++++++++++++++ _examples/routing/dynamic-path/main.go | 46 +++++++++-- mvc/controller_test.go | 6 +- 3 files changed, 145 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9e1c4f543d..eb800cee11 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,107 @@ func main() { ### Parameters in path +| Param Type | Go Type | Validation | Retrieve Helper | +| -----------------|------|-------------|------| +| `:string` | string | anything | `Params().Get` | +| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits | `Params().GetInt/Int64`...| +| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | +| `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | +| `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | +| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | `Params().GetBool` | +| `:alphabetical` | string | lowercase or uppercase letters | `Params().Get` | +| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | `Params().Get` | +| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | `Params().Get` | + +**Usage**: + +```go +app.Get("/users/{id:uint64}", func(ctx iris.Context){ + id, _ := ctx.Params().GetUint64("id") + // [...] +}) +``` + +| Built'n Func | Param Types | +| -----------|---------------| +| `regexp`(expr string) | :string | +| `prefix`(prefix string) | :string | +| `suffix`(suffix string) | :string | +| `contains`(s string) | :string | +| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | +| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | +| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :number, :int64, :uint8, :uint64 | + +**Usage**: + +```go +app.Get("/profile/{name:alphabetical max(255)}", func(ctx iris.Context){ + name := ctx.Params().Get("name") + // len(name) <=255 otherwise this route will fire 404 Not Found + // and this handler will not be executed at all. +}) +``` + +**Do It Yourself**: + +The `RegisterFunc` can accept any function that returns a `func(paramValue string) bool`. +Or just a `func(string) bool`. +If the validation fails then it will fire `404` or whatever status code the `else` keyword has. + +```go +latLonExpr := "^-?[0-9]{1,3}(?:\\.[0-9]{1,10})?$" +latLonRegex, _ := regexp.Compile(latLonExpr) + +// Register your custom argument-less macro function to the :string param type. +// MatchString is a type of func(string) bool, so we use it as it is. +app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) + +app.Get("/coordinates/{lat:string coordinate() else 400}/{lon:string coordinate() else 400}", func(ctx iris.Context) { + ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) +}) +``` + +Register your custom macro function which accepts two int arguments. + +```go + +app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { + return func(paramValue string) bool { + return len(paramValue) >= minLength && len(paramValue) <= maxLength + } +}) + +app.Get("/limitchar/{name:string range(1,200)}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef(`Hello %s | the name should be between 1 and 200 characters length + otherwise this handler will not be executed`, name) +}) +``` + +Register your custom macro function which accepts a slice of strings `[...,...]`. + +```go +app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { + return func(paramValue string) bool { + for _, validName := range validNames { + if validName == paramValue { + return true + } + } + + return false + } +}) + +app.Get("/static_validation/{name:string has([kataras,gerasimos,maropoulos]}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef(`Hello %s | the name should be "kataras" or "gerasimos" or "maropoulos" + otherwise this handler will not be executed`, name) +}) +``` + +**Example Code**: + ```go func main() { app := iris.Default() diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 462634c78d..926b4af14c 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -159,17 +159,17 @@ func main() { ctx.Writef("age selected: %d", age) }) - // Another example using a custom regexp and any custom logic. + // Another example using a custom regexp or any custom logic. + + // Register your custom argument-less macro function to the :string param type. latLonExpr := "^-?[0-9]{1,3}(?:\\.[0-9]{1,10})?$" latLonRegex, err := regexp.Compile(latLonExpr) if err != nil { panic(err) } - app.Macros().String.RegisterFunc("coordinate", func() func(paramName string) (ok bool) { - // MatchString is a type of func(string) bool, so we can return that as it's. - return latLonRegex.MatchString - }) + // MatchString is a type of func(string) bool, so we use it as it is. + app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) app.Get("/coordinates/{lat:string coordinate() else 502}/{lon:string coordinate() else 502}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) @@ -177,6 +177,42 @@ func main() { // + // Another one is by using a custom body. + app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { + return func(paramValue string) bool { + return len(paramValue) >= minLength && len(paramValue) <= maxLength + } + }) + + app.Get("/limitchar/{name:string range(1,200)}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef(`Hello %s | the name should be between 1 and 200 characters length + otherwise this handler will not be executed`, name) + }) + + // + + // Register your custom macro function which accepts a slice of strings `[...,...]`. + app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { + return func(paramValue string) bool { + for _, validName := range validNames { + if validName == paramValue { + return true + } + } + + return false + } + }) + + app.Get("/static_validation/{name:string has([kataras,gerasimos,maropoulos]}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef(`Hello %s | the name should be "kataras" or "gerasimos" or "maropoulos" + otherwise this handler will not be executed`, name) + }) + + // + // http://localhost:8080/game/a-zA-Z/level/42 // remember, alphabetical is lowercase or uppercase letters only. app.Get("/game/{name:alphabetical}/level/{level:number}", func(ctx iris.Context) { diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 525c2103ba..f367d0ea07 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -367,7 +367,7 @@ func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) { func (c *testControllerRelPathFromFunc) Get() {} func (c *testControllerRelPathFromFunc) GetBy(uint64) {} func (c *testControllerRelPathFromFunc) GetUint8RatioBy(uint8) {} -func (c *testControllerRelPathFromFunc) GetUint64RatioBy(int64) {} +func (c *testControllerRelPathFromFunc) GetInt64RatioBy(int64) {} func (c *testControllerRelPathFromFunc) GetAnythingByWildcard(string) {} func (c *testControllerRelPathFromFunc) GetLogin() {} @@ -395,8 +395,8 @@ func TestControllerRelPathFromFunc(t *testing.T) { e.GET("/uint8/ratio/255").Expect().Status(iris.StatusOK). Body().Equal("GET:/uint8/ratio/255") e.GET("/uint8/ratio/256").Expect().Status(iris.StatusNotFound) - e.GET("/uint64/ratio/-42").Expect().Status(iris.StatusOK). - Body().Equal("GET:/uint64/ratio/-42") + e.GET("/int64/ratio/-42").Expect().Status(iris.StatusOK). + Body().Equal("GET:/int64/ratio/-42") e.GET("/something/true").Expect().Status(iris.StatusOK). Body().Equal("GET:/something/true") e.GET("/something/false").Expect().Status(iris.StatusOK). From 5fdac7070d19e1745d1307d958736954c80266bd Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 24 Aug 2018 14:43:19 +0300 Subject: [PATCH 06/91] format the prev --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eb800cee11..e865eb43a2 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ latLonRegex, _ := regexp.Compile(latLonExpr) // MatchString is a type of func(string) bool, so we use it as it is. app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) -app.Get("/coordinates/{lat:string coordinate() else 400}/{lon:string coordinate() else 400}", func(ctx iris.Context) { +app.Get("/coordinates/{lat:string coordinate()}/{lon:string coordinate()}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) }) ``` @@ -205,7 +205,7 @@ app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(st } }) -app.Get("/limitchar/{name:string range(1,200)}", func(ctx iris.Context) { +app.Get("/limitchar/{name:string range(1,200) else 400}", func(ctx iris.Context) { name := ctx.Params().Get("name") ctx.Writef(`Hello %s | the name should be between 1 and 200 characters length otherwise this handler will not be executed`, name) From 5139db152a12c8e5a91e467b7795ed4654cc9ffb Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 24 Aug 2018 14:47:56 +0300 Subject: [PATCH 07/91] add the di section after the parameters in path section --- README.md | 49 +++++++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index e865eb43a2..b758c7c797 100644 --- a/README.md +++ b/README.md @@ -87,30 +87,6 @@ func main() { $ go run example.go ``` -## Dependency Injection - -The package [hero](hero) contains features for binding any object or functions that `handlers` can use, these are called dependencies. - -With Iris you get truly safe bindings thanks to the [hero](_examples/hero) [package](hero). It is blazing-fast, near to raw handlers performance because Iris calculates everything before even server goes online! - -Below you will see some screenshots I prepared for you in order to be easier to understand: - -### 1. Path Parameters - Built'n Dependencies - -![](https://github.com/kataras/explore/raw/master/iris/hero/hero-1-monokai.png) - -### 2. Services - Static Dependencies - -![](https://github.com/kataras/explore/raw/master/iris/hero/hero-2-monokai.png) - -### 3. Per-Request - Dynamic Dependencies - -![](https://github.com/kataras/explore/raw/master/iris/hero/hero-3-monokai.png) - -`hero funcs` are very easy to understand and when you start using them **you never go back**. - -> With Iris you also get real and [blazing-fast](_benchmarks) [MVC support](_examples/mvc) which uses "hero" under the hoods. - ## API Examples ### Using Get, Post, Put, Patch, Delete and Options @@ -270,6 +246,31 @@ func main() { > Learn more about path parameter's types by navigating [here](_examples/routing/dynamic-path/main.go#L31). + +### Dependency Injection + +The package [hero](hero) contains features for binding any object or functions that `handlers` can use, these are called dependencies. + +With Iris you get truly safe bindings thanks to the [hero](_examples/hero) [package](hero). It is blazing-fast, near to raw handlers performance because Iris calculates everything before even server goes online! + +Below you will see some screenshots I prepared for you in order to be easier to understand: + +#### 1. Path Parameters - Built'n Dependencies + +![](https://github.com/kataras/explore/raw/master/iris/hero/hero-1-monokai.png) + +#### 2. Services - Static Dependencies + +![](https://github.com/kataras/explore/raw/master/iris/hero/hero-2-monokai.png) + +#### 3. Per-Request - Dynamic Dependencies + +![](https://github.com/kataras/explore/raw/master/iris/hero/hero-3-monokai.png) + +`hero funcs` are very easy to understand and when you start using them **you never go back**. + +> With Iris you also get real and [blazing-fast](_benchmarks) [MVC support](_examples/mvc) which uses "hero" under the hoods. + ### Querystring parameters ```go From 67512a77591fa6f21152c5f61b07a5dde2e593de Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 25 Aug 2018 19:23:52 +0300 Subject: [PATCH 08/91] add the StatusMisdirectedRequest (421) added in go 1.11 net/http package as well --- iris.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/iris.go b/iris.go index f84accea30..e992938556 100644 --- a/iris.go +++ b/iris.go @@ -58,13 +58,13 @@ const ( StatusAlreadyReported = 208 // RFC 5842, 7.1 StatusIMUsed = 226 // RFC 3229, 10.4.1 - StatusMultipleChoices = 300 // RFC 7231, 6.4.1 - StatusMovedPermanently = 301 // RFC 7231, 6.4.2 - StatusFound = 302 // RFC 7231, 6.4.3 - StatusSeeOther = 303 // RFC 7231, 6.4.4 - StatusNotModified = 304 // RFC 7232, 4.1 - StatusUseProxy = 305 // RFC 7231, 6.4.5 - _ = 306 // RFC 7231, 6.4.6 (Unused) + StatusMultipleChoices = 300 // RFC 7231, 6.4.1 + StatusMovedPermanently = 301 // RFC 7231, 6.4.2 + StatusFound = 302 // RFC 7231, 6.4.3 + StatusSeeOther = 303 // RFC 7231, 6.4.4 + StatusNotModified = 304 // RFC 7232, 4.1 + StatusUseProxy = 305 // RFC 7231, 6.4.5 + StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7 StatusPermanentRedirect = 308 // RFC 7538, 3 @@ -87,6 +87,7 @@ const ( StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4 StatusExpectationFailed = 417 // RFC 7231, 6.5.14 StatusTeapot = 418 // RFC 7168, 2.3.3 + StatusMisdirectedRequest = 421 // RFC 7540, 9.1.2 StatusUnprocessableEntity = 422 // RFC 4918, 11.2 StatusLocked = 423 // RFC 4918, 11.3 StatusFailedDependency = 424 // RFC 4918, 11.4 From bd753cfe2e4c2c0474e29895ef4e6b0c6e394853 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 31 Aug 2018 02:09:48 +0300 Subject: [PATCH 09/91] remove 'WithoutVersionChecker', update and test the new versions of some of the dependencies, add a history entry with unknown release date --- Gopkg.lock | 18 + Gopkg.toml | 14 +- HISTORY.md | 65 +++ README.md | 1 - _benchmarks/iris-mvc-templates/main.go | 2 +- _benchmarks/iris-mvc/main.go | 2 +- _benchmarks/iris-sessions/main.go | 4 +- _benchmarks/iris/main.go | 2 +- _examples/hero/overview/main.go | 2 - _examples/hero/sessions/main.go | 1 - .../http_request/extract-referer/main.go | 2 +- _examples/mvc/login/main.go | 2 - _examples/mvc/overview/main.go | 2 - _examples/mvc/session-controller/main.go | 2 +- _examples/overview/main.go | 2 +- _examples/tutorial/url-shortener/main.go | 2 +- _examples/tutorial/url-shortener/store.go | 20 +- _examples/tutorial/vuejs-todo-mvc/README.md | 2 +- .../tutorial/vuejs-todo-mvc/src/web/main.go | 2 +- .../{hello_go11beta3.go => hello_go111.go} | 4 +- _examples/webassembly/basic/main.go | 2 +- configuration.go | 18 - configuration_test.go | 13 +- core/host/proxy_test.go | 2 +- core/maintenance/maintenance.go | 6 - core/maintenance/version.go | 78 --- core/maintenance/version/fetch.go | 59 --- core/maintenance/version/version.go | 64 --- core/router/macro/macro_test.go | 2 +- httptest/httptest.go | 3 +- iris.go | 7 +- .../vendor/github.com/dgraph-io/badger.txt | 4 +- .../github.com/dgraph-io/badger/backup.go | 32 +- .../github.com/dgraph-io/badger/compaction.go | 8 +- .../vendor/github.com/dgraph-io/badger/db.go | 344 +++++-------- .../github.com/dgraph-io/badger/dir_unix.go | 43 +- .../dgraph-io/badger/dir_windows.go | 6 +- .../github.com/dgraph-io/badger/errors.go | 18 +- .../github.com/dgraph-io/badger/iterator.go | 113 ++++- .../github.com/dgraph-io/badger/levels.go | 247 ++++++--- .../github.com/dgraph-io/badger/managed_db.go | 7 - .../github.com/dgraph-io/badger/manifest.go | 30 +- .../github.com/dgraph-io/badger/options.go | 38 +- .../github.com/dgraph-io/badger/skl/arena.go | 11 +- .../github.com/dgraph-io/badger/skl/skl.go | 16 +- .../dgraph-io/badger/table/README.md | 51 ++ .../dgraph-io/badger/table/builder.go | 2 +- .../dgraph-io/badger/table/iterator.go | 2 + .../dgraph-io/badger/table/table.go | 8 - .../dgraph-io/badger/transaction.go | 212 ++++---- .../github.com/dgraph-io/badger/value.go | 472 ++++++++++++------ .../golang/protobuf/proto/proto3_test.go | 151 ------ .../dgraph-io/badger/y/file_dsync.go | 6 +- .../dgraph-io/badger/y/file_nodsync.go | 2 +- .../dgraph-io/badger/y/watermark.go | 130 +++++ .../vendor/github.com/dgraph-io/badger/y/y.go | 25 +- sessions/sessiondb/boltdb/database.go | 38 +- .../{coreos => etcd-io}/bbolt/LICENSE | 0 .../{coreos => etcd-io}/bbolt/bolt_386.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_amd64.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_arm.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_arm64.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_linux.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_mips64x.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_mipsx.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_openbsd.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_ppc.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_ppc64.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_ppc64le.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_s390x.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_unix.go | 2 +- .../bbolt/bolt_unix_solaris.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_windows.go | 2 +- .../bbolt/boltsync_unix.go | 2 +- .../{coreos => etcd-io}/bbolt/bucket.go | 2 +- .../{coreos => etcd-io}/bbolt/cursor.go | 2 +- .../{coreos => etcd-io}/bbolt/db.go | 12 +- .../{coreos => etcd-io}/bbolt/errors.go | 2 +- .../{coreos => etcd-io}/bbolt/freelist.go | 2 +- .../{coreos => etcd-io}/bbolt/node.go | 2 +- .../{coreos => etcd-io}/bbolt/page.go | 2 +- .../{coreos => etcd-io}/bbolt/tx.go | 2 +- 82 files changed, 1349 insertions(+), 1130 deletions(-) rename _examples/webassembly/basic/client/{hello_go11beta3.go => hello_go111.go} (70%) delete mode 100644 core/maintenance/maintenance.go delete mode 100644 core/maintenance/version.go delete mode 100644 core/maintenance/version/fetch.go delete mode 100644 core/maintenance/version/version.go create mode 100644 sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/README.md delete mode 100644 sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/vendor/github.com/golang/protobuf/proto/proto3_test.go create mode 100644 sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/watermark.go rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/LICENSE (100%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_386.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_amd64.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_arm.go (98%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_arm64.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_linux.go (91%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_mips64x.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_mipsx.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_openbsd.go (97%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_ppc.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_ppc64.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_ppc64le.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_s390x.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_unix.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_unix_solaris.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_windows.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/boltsync_unix.go (91%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bucket.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/cursor.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/db.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/errors.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/freelist.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/node.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/page.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/tx.go (99%) diff --git a/Gopkg.lock b/Gopkg.lock index 24bd3afc71..8afa348503 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -277,6 +277,24 @@ packages = ["."] revision = "aad8439df3bf67adb025382ee2e5f46a72fc9456" +[[projects]] + branch = "master" + name = "github.com/dgraph-io/badger" + packages = ["."] + revision = "391b6d3b93e6014fe8c2971fcc0c1266e47dbbd9" + +[[projects]] + branch = "master" + name = "github.com/etcd-io/bbolt" + packages = ["."] + revision = "acbc2c426a444a65e0cbfdcbb3573857bf18b465" + +[[projects]] + branch = "master" + name = "github.com/gomodule/redigo" + packages = ["internal","redis"] + revision = "2cd21d9966bf7ff9ae091419744f0b3fb0fecace" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index a0baeae39d..eb9c030df1 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -80,4 +80,16 @@ [[constraint]] branch = "master" - name = "github.com/Shopify/goreferrer" \ No newline at end of file + name = "github.com/Shopify/goreferrer" + +[[constraint]] + name = "github.com/dgraph-io/badger" + version = "1.5.3" + +[[constraint]] + name = "github.com/etcd-io/bbolt" + version = "1.3.1-etcd.7" + +[[constraint]] + branch = "master" + name = "github.com/gomodule/redigo" diff --git a/HISTORY.md b/HISTORY.md index b042fd7cf1..2a24d77887 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -17,6 +17,71 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you. +# Whenever | v11.0.0 + +## Breaking changes + +- Remove the "Configurator" `WithoutVersionChecker` and the configuration field `DisableVersionChecker` +- `:int` (or its new alias `:number`) macro route path parameter type **can accept negative numbers now**. Recommendation: `:int` should be replaced with the more generic numeric parameter type `:number` if possible (although it will stay for backwards-compatibility) + +## Routing + +- `:number` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers and without digits limitation +- Add `:int64` parameter type and `ctx.Params().GetInt64` +- Add `:uint8` parameter type and `ctx.Params().GetUint8` +- Add `:uint64` parameter type and `ctx.Params().GetUint64` +- `:bool` parameter type as an alias for `:boolean` + +Here is the full list of the built'n parameter types that we support now, including their validations/path segment rules. + +| Param Type | Go Type | Validation | +| -----------|---------|------------| +| `:string` | string | anything | +| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits | +| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | +| `:uint8` | uint8 | 0 to 255 | +| `:uint64` | uint64 | 0 to 18446744073709551615 | +| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | +| `:alphabetical` | string | lowercase or uppercase letters | +| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | +| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | + +**Usage**: + +```go +app.Get("/users/{id:uint64}", func(ctx iris.Context){ + id, _ := ctx.Params().GetUint64("id") + // [...] +}) +``` + +| Built'n Func | Param Types | +| -----------|---------------| +| `regexp`(expr string) | :string | +| `prefix`(prefix string) | :string | +| `suffix`(suffix string) | :string | +| `contains`(s string) | :string | +| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | +| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | +| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :number, :int64, :uint8, :uint64 | + +**Usage**: + +```go +app.Get("/profile/{name:alphabetical max(255)}", func(ctx iris.Context){ + name := ctx.Params().Get("name") + // len(name) <=255 otherwise this route will fire 404 Not Found + // and this handler will not be executed at all. +}) +``` + +## Vendoring + +- Rename the vendor `sessions/sessiondb/vendor/...bbolt` from `coreos/bbolt` to `etcd-io/bbolt` and update to v1.3.1, based on [that](https://github.com/etcd-io/bbolt/releases/tag/v1.3.1-etcd.7) +- Update the vendor `sessions/sessiondb/vendor/...badger` to v1.5.3 + +> More to come, maybe it will be removed eventually. + # Sat, 11 August 2018 | v10.7.0 I am overjoyed to announce stage 1 of the the Iris Web framework **10.7 stable release is now available**. diff --git a/README.md b/README.md index b758c7c797..b8b32f5e67 100644 --- a/README.md +++ b/README.md @@ -588,7 +588,6 @@ func main() { app.Run( iris.Addr(":8080"), iris.WithoutBanner, - iris.WithoutVersionChecker, iris.WithoutServerError(iris.ErrServerClosed), ) } diff --git a/_benchmarks/iris-mvc-templates/main.go b/_benchmarks/iris-mvc-templates/main.go index 5601ad146e..cfd46558fd 100644 --- a/_benchmarks/iris-mvc-templates/main.go +++ b/_benchmarks/iris-mvc-templates/main.go @@ -24,7 +24,7 @@ func main() { mvc.New(app).Handle(new(controllers.HomeController)) - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } type err struct { diff --git a/_benchmarks/iris-mvc/main.go b/_benchmarks/iris-mvc/main.go index 6ea755de06..4b0e0a1859 100644 --- a/_benchmarks/iris-mvc/main.go +++ b/_benchmarks/iris-mvc/main.go @@ -16,7 +16,7 @@ func main() { mvc.New(app.Party("/api/values/{id}")). Handle(new(controllers.ValuesController)) - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } // +2MB/s faster than the previous implementation, 0.4MB/s difference from the raw handlers. diff --git a/_benchmarks/iris-sessions/main.go b/_benchmarks/iris-sessions/main.go index 956cad0d3b..d5a340629b 100644 --- a/_benchmarks/iris-sessions/main.go +++ b/_benchmarks/iris-sessions/main.go @@ -24,9 +24,7 @@ func main() { app.Delete("/del", delHandler) */ - // 24 August 2017: Iris has a built'n version updater but we don't need it - // when benchmarking... - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } // Set and Get diff --git a/_benchmarks/iris/main.go b/_benchmarks/iris/main.go index 7c1ee6e29d..d9885e884f 100644 --- a/_benchmarks/iris/main.go +++ b/_benchmarks/iris/main.go @@ -8,5 +8,5 @@ func main() { ctx.WriteString("value") }) - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } diff --git a/_examples/hero/overview/main.go b/_examples/hero/overview/main.go index ac7221072b..efb188847f 100644 --- a/_examples/hero/overview/main.go +++ b/_examples/hero/overview/main.go @@ -50,8 +50,6 @@ func main() { app.Run( // Start the web server at localhost:8080 iris.Addr("localhost:8080"), - // disables updates: - iris.WithoutVersionChecker, // skip err server closed when CTRL/CMD+C pressed: iris.WithoutServerError(iris.ErrServerClosed), // enables faster json serialization and more: diff --git a/_examples/hero/sessions/main.go b/_examples/hero/sessions/main.go index aa1f67b3eb..7603453253 100644 --- a/_examples/hero/sessions/main.go +++ b/_examples/hero/sessions/main.go @@ -34,7 +34,6 @@ func main() { app.Run( iris.Addr(":8080"), - iris.WithoutVersionChecker, iris.WithoutServerError(iris.ErrServerClosed), ) } diff --git a/_examples/http_request/extract-referer/main.go b/_examples/http_request/extract-referer/main.go index 8c14e54ea1..3e2436c562 100644 --- a/_examples/http_request/extract-referer/main.go +++ b/_examples/http_request/extract-referer/main.go @@ -25,5 +25,5 @@ func main() { // http://localhost:8080?referer=https://twitter.com/Xinterio/status/1023566830974251008 // http://localhost:8080?referer=https://www.google.com/search?q=Top+6+golang+web+frameworks&oq=Top+6+golang+web+frameworks - app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) } diff --git a/_examples/mvc/login/main.go b/_examples/mvc/login/main.go index 83bd350fc7..3d77e08ab2 100644 --- a/_examples/mvc/login/main.go +++ b/_examples/mvc/login/main.go @@ -79,8 +79,6 @@ func main() { app.Run( // Starts the web server at localhost:8080 iris.Addr("localhost:8080"), - // Disables the updater. - iris.WithoutVersionChecker, // Ignores err server closed log when CTRL/CMD+C pressed. iris.WithoutServerError(iris.ErrServerClosed), // Enables faster json serialization and more. diff --git a/_examples/mvc/overview/main.go b/_examples/mvc/overview/main.go index 9d7d9cf6d7..2201332524 100644 --- a/_examples/mvc/overview/main.go +++ b/_examples/mvc/overview/main.go @@ -33,8 +33,6 @@ func main() { app.Run( // Start the web server at localhost:8080 iris.Addr("localhost:8080"), - // disables updates: - iris.WithoutVersionChecker, // skip err server closed when CTRL/CMD+C pressed: iris.WithoutServerError(iris.ErrServerClosed), // enables faster json serialization and more: diff --git a/_examples/mvc/session-controller/main.go b/_examples/mvc/session-controller/main.go index 8774d448aa..aa3ea0514a 100644 --- a/_examples/mvc/session-controller/main.go +++ b/_examples/mvc/session-controller/main.go @@ -68,5 +68,5 @@ func main() { // 3. refresh the page some times // 4. close the browser // 5. re-open the browser and re-play 2. - app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080")) } diff --git a/_examples/overview/main.go b/_examples/overview/main.go index a834876e24..30714a0985 100644 --- a/_examples/overview/main.go +++ b/_examples/overview/main.go @@ -74,7 +74,7 @@ func main() { } // Listen for incoming HTTP/1.x & HTTP/2 clients on localhost port 8080. - app.Run(iris.Addr(":8080"), iris.WithCharset("UTF-8"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080"), iris.WithCharset("UTF-8")) } func logThisMiddleware(ctx iris.Context) { diff --git a/_examples/tutorial/url-shortener/main.go b/_examples/tutorial/url-shortener/main.go index 1516627c68..7fa9112316 100644 --- a/_examples/tutorial/url-shortener/main.go +++ b/_examples/tutorial/url-shortener/main.go @@ -2,7 +2,7 @@ // // Article: https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7 // -// $ go get github.com/coreos/bbolt +// $ go get github.com/etcd-io/bbolt // $ go get github.com/satori/go.uuid // $ cd $GOPATH/src/github.com/kataras/iris/_examples/tutorial/url-shortener // $ go build diff --git a/_examples/tutorial/url-shortener/store.go b/_examples/tutorial/url-shortener/store.go index 32238b0961..d76413df60 100644 --- a/_examples/tutorial/url-shortener/store.go +++ b/_examples/tutorial/url-shortener/store.go @@ -3,7 +3,7 @@ package main import ( "bytes" - "github.com/coreos/bbolt" + "github.com/etcd-io/bbolt" ) // Panic panics, change it if you don't want to panic on critical INITIALIZE-ONLY-ERRORS @@ -28,17 +28,17 @@ var ( // Only one table/bucket which contains the urls, so it's not a fully Database, // it works only with single bucket because that all we need. type DB struct { - db *bolt.DB + db *bbolt.DB } var _ Store = &DB{} // openDatabase open a new database connection // and returns its instance. -func openDatabase(stumb string) *bolt.DB { +func openDatabase(stumb string) *bbolt.DB { // Open the data(base) file in the current working directory. // It will be created if it doesn't exist. - db, err := bolt.Open(stumb, 0600, nil) + db, err := bbolt.Open(stumb, 0600, nil) if err != nil { Panic(err) } @@ -48,7 +48,7 @@ func openDatabase(stumb string) *bolt.DB { tableURLs, } - db.Update(func(tx *bolt.Tx) (err error) { + db.Update(func(tx *bbolt.Tx) (err error) { for _, table := range tables { _, err = tx.CreateBucketIfNotExists(table) if err != nil { @@ -73,7 +73,7 @@ func NewDB(stumb string) *DB { // Set sets a shorten url and its key // Note: Caller is responsible to generate a key. func (d *DB) Set(key string, value string) error { - return d.db.Update(func(tx *bolt.Tx) error { + return d.db.Update(func(tx *bbolt.Tx) error { b, err := tx.CreateBucketIfNotExists(tableURLs) // Generate ID for the url // Note: we could use that instead of a random string key @@ -106,7 +106,7 @@ func (d *DB) Set(key string, value string) error { // Clear clears all the database entries for the table urls. func (d *DB) Clear() error { - return d.db.Update(func(tx *bolt.Tx) error { + return d.db.Update(func(tx *bbolt.Tx) error { return tx.DeleteBucket(tableURLs) }) } @@ -116,7 +116,7 @@ func (d *DB) Clear() error { // Returns an empty string if not found. func (d *DB) Get(key string) (value string) { keyB := []byte(key) - d.db.Update(func(tx *bolt.Tx) error { + d.db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket(tableURLs) if b == nil { return nil @@ -138,7 +138,7 @@ func (d *DB) Get(key string) (value string) { // GetByValue returns all keys for a specific (original) url value. func (d *DB) GetByValue(value string) (keys []string) { valueB := []byte(value) - d.db.Update(func(tx *bolt.Tx) error { + d.db.Update(func(tx *bbolt.Tx) error { b := tx.Bucket(tableURLs) if b == nil { return nil @@ -159,7 +159,7 @@ func (d *DB) GetByValue(value string) (keys []string) { // Len returns all the "shorted" urls length func (d *DB) Len() (num int) { - d.db.View(func(tx *bolt.Tx) error { + d.db.View(func(tx *bbolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket(tableURLs) diff --git a/_examples/tutorial/vuejs-todo-mvc/README.md b/_examples/tutorial/vuejs-todo-mvc/README.md index 00c912fa50..666f862d23 100644 --- a/_examples/tutorial/vuejs-todo-mvc/README.md +++ b/_examples/tutorial/vuejs-todo-mvc/README.md @@ -530,7 +530,7 @@ func main() { todosApp.Handle(new(controllers.TodoController)) // start the web server at http://localhost:8080 - app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080")) } ``` diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go index c571e42630..34e5ed888d 100644 --- a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go +++ b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go @@ -51,5 +51,5 @@ func main() { todosApp.Handle(new(controllers.TodoController)) // start the web server at http://localhost:8080 - app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080")) } diff --git a/_examples/webassembly/basic/client/hello_go11beta3.go b/_examples/webassembly/basic/client/hello_go111.go similarity index 70% rename from _examples/webassembly/basic/client/hello_go11beta3.go rename to _examples/webassembly/basic/client/hello_go111.go index 9cbf5060d1..1822fc7997 100644 --- a/_examples/webassembly/basic/client/hello_go11beta3.go +++ b/_examples/webassembly/basic/client/hello_go111.go @@ -1,4 +1,4 @@ -// +build go1.11beta3 +// +build go1.11 package main @@ -9,7 +9,7 @@ import ( ) func main() { - // GOARCH=wasm GOOS=js /home/$yourusername/go1.11beta1/bin/go build -o hello.wasm hello_go11beta2.go + // GOARCH=wasm GOOS=js /home/$yourusername/go1.11/bin/go build -o hello.wasm hello_go111.go js.Global().Get("console").Call("log", "Hello WebAssemply!") message := fmt.Sprintf("Hello, the current time is: %s", time.Now().String()) js.Global().Get("document").Call("getElementById", "hello").Set("innerText", message) diff --git a/_examples/webassembly/basic/main.go b/_examples/webassembly/basic/main.go index 7dfe0ad282..4099da53a3 100644 --- a/_examples/webassembly/basic/main.go +++ b/_examples/webassembly/basic/main.go @@ -6,7 +6,7 @@ import ( /* You need to build the hello.wasm first, download the go1.11 and execute the below command: -$ cd client && GOARCH=wasm GOOS=js /home/$yourname/go1.11beta3/bin/go build -o hello.wasm hello_go11beta3.go +$ cd client && GOARCH=wasm GOOS=js /home/$yourname/go1.11/bin/go build -o hello.wasm hello_go111.go */ func main() { diff --git a/configuration.go b/configuration.go index 0d08fc3e5e..3dedfce4dd 100644 --- a/configuration.go +++ b/configuration.go @@ -222,14 +222,6 @@ var WithoutInterruptHandler = func(app *Application) { app.config.DisableInterruptHandler = true } -// WithoutVersionChecker will disable the version checker and updater. -// The Iris server will be not -// receive automatic updates if you pass this -// to the `Run` function. Use it only while you're ready for Production environment. -var WithoutVersionChecker = func(app *Application) { - app.config.DisableVersionChecker = true -} - // WithoutPathCorrection disables the PathCorrection setting. // // See `Configuration`. @@ -388,11 +380,6 @@ type Configuration struct { // Defaults to false. DisableInterruptHandler bool `json:"disableInterruptHandler,omitempty" yaml:"DisableInterruptHandler" toml:"DisableInterruptHandler"` - // DisableVersionChecker if true then process will be not be notified for any available updates. - // - // Defaults to false. - DisableVersionChecker bool `json:"disableVersionChecker,omitempty" yaml:"DisableVersionChecker" toml:"DisableVersionChecker"` - // DisablePathCorrection corrects and redirects the requested path to the registered path // for example, if /home/ path is requested but no handler for this Route found, // then the Router checks if /home handler exists, if yes, @@ -677,10 +664,6 @@ func WithConfiguration(c Configuration) Configurator { main.DisableInterruptHandler = v } - if v := c.DisableVersionChecker; v { - main.DisableVersionChecker = v - } - if v := c.DisablePathCorrection; v { main.DisablePathCorrection = v } @@ -758,7 +741,6 @@ func DefaultConfiguration() Configuration { return Configuration{ DisableStartupLog: false, DisableInterruptHandler: false, - DisableVersionChecker: false, DisablePathCorrection: false, EnablePathEscape: false, FireMethodNotAllowed: false, diff --git a/configuration_test.go b/configuration_test.go index ab770fbe08..913ebe849d 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -77,13 +77,12 @@ func TestConfigurationOptions(t *testing.T) { func TestConfigurationOptionsDeep(t *testing.T) { charset := "MYCHARSET" - app := New().Configure(WithCharset(charset), WithoutBodyConsumptionOnUnmarshal, WithoutBanner, WithoutVersionChecker) + app := New().Configure(WithCharset(charset), WithoutBodyConsumptionOnUnmarshal, WithoutBanner) expected := DefaultConfiguration() expected.Charset = charset expected.DisableBodyConsumptionOnUnmarshal = true expected.DisableStartupLog = true - expected.DisableVersionChecker = true has := *app.config @@ -142,7 +141,6 @@ func TestConfigurationYAML(t *testing.T) { }() yamlConfigurationContents := ` -DisableVersionChecker: true DisablePathCorrection: false EnablePathEscape: false FireMethodNotAllowed: true @@ -165,10 +163,6 @@ Other: c := app.config - if expected := true; c.DisableVersionChecker != expected { - t.Fatalf("error on TestConfigurationYAML: Expected DisableVersionChecker %v but got %v", expected, c.DisableVersionChecker) - } - if expected := false; c.DisablePathCorrection != expected { t.Fatalf("error on TestConfigurationYAML: Expected DisablePathCorrection %v but got %v", expected, c.DisablePathCorrection) } @@ -241,7 +235,6 @@ func TestConfigurationTOML(t *testing.T) { }() tomlConfigurationContents := ` -DisableVersionChecker = true EnablePathEscape = false FireMethodNotAllowed = true EnableOptimizations = true @@ -265,10 +258,6 @@ Charset = "UTF-8" c := app.config - if expected := true; c.DisableVersionChecker != expected { - t.Fatalf("error on TestConfigurationTOML: Expected DisableVersionChecker %v but got %v", expected, c.DisableVersionChecker) - } - if expected := false; c.DisablePathCorrection != expected { t.Fatalf("error on TestConfigurationTOML: Expected DisablePathCorrection %v but got %v", expected, c.DisablePathCorrection) } diff --git a/core/host/proxy_test.go b/core/host/proxy_test.go index b7ad878e07..76561b342f 100644 --- a/core/host/proxy_test.go +++ b/core/host/proxy_test.go @@ -60,7 +60,7 @@ func TestProxy(t *testing.T) { t.Fatalf("%v while creating tcp4 listener for new tls local test listener", err) } // main server - go app.Run(iris.Listener(httptest.NewLocalTLSListener(l)), iris.WithoutVersionChecker, iris.WithoutStartupLog) + go app.Run(iris.Listener(httptest.NewLocalTLSListener(l)), iris.WithoutStartupLog) e := httptest.NewInsecure(t, httptest.URL("http://"+listener.Addr().String())) e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedIndex) diff --git a/core/maintenance/maintenance.go b/core/maintenance/maintenance.go deleted file mode 100644 index 613b67fda6..0000000000 --- a/core/maintenance/maintenance.go +++ /dev/null @@ -1,6 +0,0 @@ -package maintenance - -// Start starts the maintenance process. -func Start() { - CheckForUpdates() -} diff --git a/core/maintenance/version.go b/core/maintenance/version.go deleted file mode 100644 index f3d3113f68..0000000000 --- a/core/maintenance/version.go +++ /dev/null @@ -1,78 +0,0 @@ -package maintenance - -import ( - "fmt" - "os" - "os/exec" - - "github.com/kataras/iris/core/maintenance/version" - - "github.com/kataras/golog" - "github.com/kataras/survey" -) - -const ( - // Version is the string representation of the current local Iris Web Framework version. - Version = "10.7.0" -) - -// CheckForUpdates checks for any available updates -// and asks for the user if want to update now or not. -func CheckForUpdates() { - v := version.Acquire() - updateAvailale := v.Compare(Version) == version.Smaller - - if updateAvailale { - if confirmUpdate(v) { - installVersion() - return - } - } -} - -func confirmUpdate(v version.Version) bool { - // on help? when asking for installing the new update. - ignoreUpdatesMsg := "Would you like to ignore future updates? Disable the version checker via:\napp.Run(..., iris.WithoutVersionChecker)" - - // if update available ask for update action. - shouldUpdateNowMsg := - fmt.Sprintf("A new version is available online[%s > %s]. Type '?' for help.\nRelease notes: %s.\nUpdate now?", - v.String(), Version, v.ChangelogURL) - - var confirmUpdate bool - survey.AskOne(&survey.Confirm{ - Message: shouldUpdateNowMsg, - Help: ignoreUpdatesMsg, - }, &confirmUpdate, nil) - return confirmUpdate // it's true only when update was available and user typed "yes". -} - -func installVersion() { - golog.Infof("Downloading...\n") - repo := "github.com/kataras/iris/..." - cmd := exec.Command("go", "get", "-u", "-v", repo) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - - if err := cmd.Run(); err != nil { - golog.Warnf("unexpected message while trying to go get,\nif you edited the original source code then you've to remove the whole $GOPATH/src/github.com/kataras folder and execute `go get -u github.com/kataras/iris/...` manually\n%v", err) - return - } - - golog.Infof("Update process finished.\nManual rebuild and restart is required to apply the changes...\n") - return -} - -/* Author's note: -We could use github webhooks to automatic notify for updates -when a new update is pushed to the repository -even when server is already started and running but this would expose -a route which dev may don't know about, so let it for now but if -they ask it then I should add an optional configuration field -to "live/realtime update" and implement the idea (which is already implemented in the iris-go server). -*/ - -/* Author's note: -The old remote endpoint for version checker is still available on the server for backwards -compatibility with older clients, it will stay there for a long period of time. -*/ diff --git a/core/maintenance/version/fetch.go b/core/maintenance/version/fetch.go deleted file mode 100644 index 728c91a4d7..0000000000 --- a/core/maintenance/version/fetch.go +++ /dev/null @@ -1,59 +0,0 @@ -package version - -import ( - "io/ioutil" - "strings" - "time" - - "github.com/hashicorp/go-version" - - "github.com/kataras/golog" - "github.com/kataras/iris/core/netutil" -) - -const ( - versionURL = "https://raw.githubusercontent.com/kataras/iris/master/VERSION" -) - -func fetch() (*version.Version, string) { - client := netutil.Client(time.Duration(30 * time.Second)) - - r, err := client.Get(versionURL) - if err != nil { - golog.Debugf("err: %v\n", err) - return nil, "" - } - defer r.Body.Close() - - if r.StatusCode >= 400 { - golog.Debugf("Internet connection is missing, updater is unable to fetch the latest Iris version\n", err) - return nil, "" - } - - b, err := ioutil.ReadAll(r.Body) - - if len(b) == 0 || err != nil { - golog.Debugf("err: %v\n", err) - return nil, "" - } - - var ( - fetchedVersion = string(b) - changelogURL string - ) - // Example output: - // Version(8.5.5) - // 8.5.5:https://github.com/kataras/iris/blob/master/HISTORY.md#tu-02-november-2017--v855 - if idx := strings.IndexByte(fetchedVersion, ':'); idx > 0 { - changelogURL = fetchedVersion[idx+1:] - fetchedVersion = fetchedVersion[0:idx] - } - - latestVersion, err := version.NewVersion(fetchedVersion) - if err != nil { - golog.Debugf("while fetching and parsing latest version from github: %v\n", err) - return nil, "" - } - - return latestVersion, changelogURL -} diff --git a/core/maintenance/version/version.go b/core/maintenance/version/version.go deleted file mode 100644 index 4a33895212..0000000000 --- a/core/maintenance/version/version.go +++ /dev/null @@ -1,64 +0,0 @@ -package version - -import ( - "time" - - "github.com/hashicorp/go-version" -) - -// Version is a version wrapper which -// contains some additional customized properties. -type Version struct { - version.Version - WrittenAt time.Time - ChangelogURL string -} - -// Result is the compare result type. -// Available types are Invalid, Smaller, Equal or Larger. -type Result int32 - -const ( - // Smaller when the compared version is smaller than the latest one. - Smaller Result = -1 - // Equal when the compared version is equal with the latest one. - Equal Result = 0 - // Larger when the compared version is larger than the latest one. - Larger Result = 1 - // Invalid means that an error occurred when comparing the versions. - Invalid Result = -2 -) - -// Compare compares the "versionStr" with the latest Iris version, -// opossite to the version package -// it returns the result of the "versionStr" not the "v" itself. -func (v *Version) Compare(versionStr string) Result { - if len(v.Version.String()) == 0 { - // if version not refreshed, by an internet connection lose, - // then return Invalid. - return Invalid - } - - other, err := version.NewVersion(versionStr) - if err != nil { - return Invalid - } - return Result(other.Compare(&v.Version)) -} - -// Acquire returns the latest version info wrapper. -// It calls the fetch. -func Acquire() (v Version) { - newVersion, changelogURL := fetch() - if newVersion == nil { // if github was down then don't panic, just set it as the smallest version. - newVersion, _ = version.NewVersion("0.0.1") - } - - v = Version{ - Version: *newVersion, - WrittenAt: time.Now(), - ChangelogURL: changelogURL, - } - - return -} diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index 83092939e3..33f37fd05a 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -105,7 +105,7 @@ func TestNumberEvaluatorRaw(t *testing.T) { {true, "-18446744073709553213213213213213121615"}, // 5 {false, "42 18446744073709551615"}, // 6 {false, "--42"}, // 7 - {false, "+42"}, // 9 + {false, "+42"}, // 8 {false, "main.css"}, // 9 {false, "/assets/main.css"}, // 10 } diff --git a/httptest/httptest.go b/httptest/httptest.go index b37f97505c..50253c5396 100644 --- a/httptest/httptest.go +++ b/httptest/httptest.go @@ -84,8 +84,7 @@ func New(t *testing.T, app *iris.Application, setters ...OptionSetter) *httpexpe setter.Set(conf) } - // set the logger or disable it (default) and disable the updater (for any case). - app.Configure(iris.WithoutVersionChecker) + // set the logger or disable it (default). app.Logger().SetLevel(conf.LogLevel) if err := app.Build(); err != nil { diff --git a/iris.go b/iris.go index e992938556..8cab499964 100644 --- a/iris.go +++ b/iris.go @@ -17,7 +17,6 @@ import ( // core packages, needed to build the application "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/host" - "github.com/kataras/iris/core/maintenance" "github.com/kataras/iris/core/netutil" "github.com/kataras/iris/core/router" // handlerconv conversions @@ -34,7 +33,7 @@ import ( var ( // Version is the current version number of the Iris Web Framework. - Version = maintenance.Version + Version = "11.0.0" ) // HTTP status codes as registered with IANA. @@ -811,10 +810,6 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error { app.Configure(withOrWithout...) app.logger.Debugf("Application: running using %d host(s)", len(app.Hosts)+1) - if !app.config.DisableVersionChecker { - go maintenance.Start() - } - // this will block until an error(unless supervisor's DeferFlow called from a Task). err := serve(app) if err != nil { diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt index 12081a43fd..a12ff0084a 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt @@ -1,3 +1,3 @@ -27 Dec 2017 +12 July 2018 -Commit: 0225b784d8dfc250b3be597fa806f0fd677a6628 \ No newline at end of file +Commit: 391b6d3b93e6014fe8c2971fcc0c1266e47dbbd9 \ No newline at end of file diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/backup.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/backup.go index 375b5a3162..4649de25b7 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/backup.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/backup.go @@ -1,9 +1,26 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package badger import ( "bufio" "encoding/binary" "io" + "log" "sync" "github.com/dgraph-io/badger/y" @@ -36,6 +53,8 @@ func (db *DB) Backup(w io.Writer, since uint64) (uint64, error) { opts := DefaultIteratorOptions opts.AllVersions = true it := txn.NewIterator(opts) + defer it.Close() + for it.Rewind(); it.Valid(); it.Next() { item := it.Item() if item.Version() < since { @@ -44,7 +63,8 @@ func (db *DB) Backup(w io.Writer, since uint64) (uint64, error) { } val, err := item.Value() if err != nil { - return err + log.Printf("Key [%x]. Error while fetching value [%v]\n", item.Key(), err) + continue } entry := &protos.KVPair{ @@ -68,7 +88,7 @@ func (db *DB) Backup(w io.Writer, since uint64) (uint64, error) { // Load reads a protobuf-encoded list of all entries from a reader and writes // them to the database. This can be used to restore the database from a backup -// made by calling DB.Dump(). +// made by calling DB.Backup(). // // DB.Load() should be called on a database that is not running any other // concurrent transactions while it is running. @@ -124,12 +144,17 @@ func (db *DB) Load(r io.Reader) error { UserMeta: e.UserMeta[0], ExpiresAt: e.ExpiresAt, }) + // Update nextCommit, memtable stores this timestamp in badger head + // when flushed. + if e.Version >= db.orc.commitTs() { + db.orc.nextCommit = e.Version + 1 + } if len(entries) == 1000 { if err := batchSetAsyncIfNoErr(entries); err != nil { return err } - entries = entries[:0] + entries = make([]*Entry, 0, 1000) } } @@ -145,6 +170,7 @@ func (db *DB) Load(r io.Reader) error { case err := <-errChan: return err default: + db.orc.curRead = db.orc.commitTs() - 1 return nil } } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/compaction.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/compaction.go index 23824ae175..00be8f6efa 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/compaction.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/compaction.go @@ -20,6 +20,7 @@ import ( "bytes" "fmt" "log" + "math" "sync" "golang.org/x/net/trace" @@ -37,7 +38,7 @@ type keyRange struct { var infRange = keyRange{inf: true} func (r keyRange) String() string { - return fmt.Sprintf("[left=%q, right=%q, inf=%v]", r.left, r.right, r.inf) + return fmt.Sprintf("[left=%x, right=%x, inf=%v]", r.left, r.right, r.inf) } func (r keyRange) equals(dst keyRange) bool { @@ -75,7 +76,10 @@ func getKeyRange(tables []*table.Table) keyRange { biggest = tables[i].Biggest() } } - return keyRange{left: smallest, right: biggest} + return keyRange{ + left: y.KeyWithTs(y.ParseKey(smallest), math.MaxUint64), + right: y.KeyWithTs(y.ParseKey(biggest), 0), + } } type levelCompactStatus struct { diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go index f03a5caaba..dc18576f3d 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go @@ -17,8 +17,6 @@ package badger import ( - "bytes" - "container/heap" "encoding/binary" "expvar" "log" @@ -43,6 +41,7 @@ var ( badgerPrefix = []byte("!badger!") // Prefix for internal keys used by badger. head = []byte("!badger!head") // For storing value offset for replay. txnKey = []byte("!badger!txn") // For indicating end of entries in txn. + badgerMove = []byte("!badger!move") // For key-value pairs which got moved during GC. ) type closers struct { @@ -101,7 +100,7 @@ func replayFunction(out *DB) func(Entry, valuePointer) error { first := true return func(e Entry, vp valuePointer) error { // Function for replaying. if first { - out.elog.Printf("First key=%s\n", e.Key) + out.elog.Printf("First key=%q\n", e.Key) } first = false @@ -168,12 +167,24 @@ func Open(opt Options) (db *DB, err error) { opt.maxBatchSize = (15 * opt.MaxTableSize) / 100 opt.maxBatchCount = opt.maxBatchSize / int64(skl.MaxNodeSize) + if opt.ValueThreshold > math.MaxUint16-16 { + return nil, ErrValueThreshold + } + + if opt.ReadOnly { + // Can't truncate if the DB is read only. + opt.Truncate = false + } + for _, path := range []string{opt.Dir, opt.ValueDir} { dirExists, err := exists(path) if err != nil { return nil, y.Wrapf(err, "Invalid Dir: %q", path) } if !dirExists { + if opt.ReadOnly { + return nil, y.Wrapf(err, "Cannot find Dir for read-only open: %q", path) + } // Try to create the directory err = os.Mkdir(path, 0700) if err != nil { @@ -189,8 +200,8 @@ func Open(opt Options) (db *DB, err error) { if err != nil { return nil, err } - - dirLockGuard, err := acquireDirectoryLock(opt.Dir, lockFile) + var dirLockGuard, valueDirLockGuard *directoryLockGuard + dirLockGuard, err = acquireDirectoryLock(opt.Dir, lockFile, opt.ReadOnly) if err != nil { return nil, err } @@ -199,9 +210,8 @@ func Open(opt Options) (db *DB, err error) { _ = dirLockGuard.release() } }() - var valueDirLockGuard *directoryLockGuard if absValueDir != absDir { - valueDirLockGuard, err = acquireDirectoryLock(opt.ValueDir, lockFile) + valueDirLockGuard, err = acquireDirectoryLock(opt.ValueDir, lockFile, opt.ReadOnly) if err != nil { return nil, err } @@ -218,7 +228,7 @@ func Open(opt Options) (db *DB, err error) { opt.ValueLogLoadingMode == options.MemoryMap) { return nil, ErrInvalidLoadingMode } - manifestFile, manifest, err := openOrCreateManifestFile(opt.Dir) + manifestFile, manifest, err := openOrCreateManifestFile(opt.Dir, opt.ReadOnly) if err != nil { return nil, err } @@ -229,12 +239,12 @@ func Open(opt Options) (db *DB, err error) { }() orc := &oracle{ - isManaged: opt.managedTxns, - nextCommit: 1, - pendingCommits: make(map[uint64]struct{}), - commits: make(map[uint64]uint64), + isManaged: opt.managedTxns, + nextCommit: 1, + commits: make(map[uint64]uint64), + readMark: y.WaterMark{}, } - heap.Init(&orc.commitMark) + orc.readMark.Init() db = &DB{ imm: make([]*skl.Skiplist, 0, opt.NumMemtables), @@ -259,11 +269,13 @@ func Open(opt Options) (db *DB, err error) { return nil, err } - db.closers.compactors = y.NewCloser(1) - db.lc.startCompact(db.closers.compactors) + if !opt.ReadOnly { + db.closers.compactors = y.NewCloser(1) + db.lc.startCompact(db.closers.compactors) - db.closers.memtable = y.NewCloser(1) - go db.flushMemtable(db.closers.memtable) // Need levels controller to be up. + db.closers.memtable = y.NewCloser(1) + go db.flushMemtable(db.closers.memtable) // Need levels controller to be up. + } if err = db.vlog.Open(db, opt); err != nil { return nil, err @@ -319,7 +331,8 @@ func Open(opt Options) (db *DB, err error) { } // Close closes a DB. It's crucial to call it to ensure all the pending updates -// make their way to disk. +// make their way to disk. Calling DB.Close() multiple times is not safe and would +// cause panic. func (db *DB) Close() (err error) { db.elog.Printf("Closing database") // Stop value GC first. @@ -366,11 +379,31 @@ func (db *DB) Close() (err error) { } db.flushChan <- flushTask{nil, valuePointer{}} // Tell flusher to quit. - db.closers.memtable.Wait() - db.elog.Printf("Memtable flushed") + if db.closers.memtable != nil { + db.closers.memtable.Wait() + db.elog.Printf("Memtable flushed") + } + if db.closers.compactors != nil { + db.closers.compactors.SignalAndWait() + db.elog.Printf("Compaction finished") + } - db.closers.compactors.SignalAndWait() - db.elog.Printf("Compaction finished") + // Force Compact L0 + // We don't need to care about cstatus since no parallel compaction is running. + cd := compactDef{ + elog: trace.New("Badger", "Compact"), + thisLevel: db.lc.levels[0], + nextLevel: db.lc.levels[1], + } + cd.elog.SetMaxEvents(100) + defer cd.elog.Finish() + if db.lc.fillTablesL0(&cd) { + if err := db.lc.runCompactDef(0, cd); err != nil { + cd.elog.LazyPrintf("\tLOG Compact FAILED with error: %+v: %+v", err, cd) + } + } else { + cd.elog.LazyPrintf("fillTables failed for level zero. No compaction required") + } if lcErr := db.lc.close(); err == nil { err = errors.Wrap(lcErr, "DB.Close") @@ -380,8 +413,10 @@ func (db *DB) Close() (err error) { db.elog.Finish() - if guardErr := db.dirLockGuard.release(); err == nil { - err = errors.Wrap(guardErr, "DB.Close") + if db.dirLockGuard != nil { + if guardErr := db.dirLockGuard.release(); err == nil { + err = errors.Wrap(guardErr, "DB.Close") + } } if db.valueDirGuard != nil { if guardErr := db.valueDirGuard.release(); err == nil { @@ -451,33 +486,25 @@ func (db *DB) getMemTables() ([]*skl.Skiplist, func()) { // get returns the value in memtable or disk for given key. // Note that value will include meta byte. +// +// IMPORTANT: We should never write an entry with an older timestamp for the same key, We need to +// maintain this invariant to search for the latest value of a key, or else we need to search in all +// tables and find the max version among them. To maintain this invariant, we also need to ensure +// that all versions of a key are always present in the same table from level 1, because compaction +// can push any table down. func (db *DB) get(key []byte) (y.ValueStruct, error) { tables, decr := db.getMemTables() // Lock should be released. defer decr() y.NumGets.Add(1) - version := y.ParseTs(key) - var maxVs y.ValueStruct - // Need to search for values in all tables, with managed db - // latest value needn't be present in the latest table. - // Even without managed db, purging can cause this constraint - // to be violated. - // Search until required version is found or iterate over all - // tables and return max version. for i := 0; i < len(tables); i++ { vs := tables[i].Get(key) y.NumMemtableGets.Add(1) - if vs.Meta == 0 && vs.Value == nil { - continue - } - if vs.Version == version { + if vs.Meta != 0 || vs.Value != nil { return vs, nil } - if maxVs.Version < vs.Version { - maxVs = vs - } } - return db.lc.get(key, maxVs) + return db.lc.get(key) } func (db *DB) updateOffset(ptrs []valuePointer) { @@ -568,8 +595,12 @@ func (db *DB) writeRequests(reqs []*request) error { continue } count += len(b.Entries) - for err := db.ensureRoomForWrite(); err != nil; err = db.ensureRoomForWrite() { - db.elog.Printf("Making room for writes") + var i uint64 + for err := db.ensureRoomForWrite(); err == errNoRoom; err = db.ensureRoomForWrite() { + i++ + if i%100 == 0 { + db.elog.Printf("Making room for writes") + } // We need to poll a bit because both hasRoomForWrite and the flusher need access to s.imm. // When flushChan is full and you are blocked there, and the flusher is trying to update s.imm, // you will get a deadlock. @@ -681,11 +712,7 @@ func (db *DB) batchSet(entries []*Entry) error { return err } - req.Wg.Wait() - req.Entries = nil - err = req.Err - requestPool.Put(req) - return err + return req.Wait() } // batchSetAsync is the asynchronous version of batchSet. It accepts a callback @@ -700,10 +727,7 @@ func (db *DB) batchSetAsync(entries []*Entry, f func(error)) error { return err } go func() { - req.Wg.Wait() - err := req.Err - req.Entries = nil - requestPool.Put(req) + err := req.Wait() // Write is complete. Let's call the callback function now. f(err) }() @@ -748,7 +772,7 @@ func arenaSize(opt Options) int64 { return opt.MaxTableSize + opt.maxBatchSize + opt.maxBatchCount*int64(skl.MaxNodeSize) } -// WriteLevel0Table flushes memtable. It drops deleteValues. +// WriteLevel0Table flushes memtable. func writeLevel0Table(s *skl.Skiplist, f *os.File) error { iter := s.NewIterator() defer iter.Close() @@ -768,6 +792,8 @@ type flushTask struct { vptr valuePointer } +// TODO: Ensure that this function doesn't return, or is handled by another wrapper function. +// Otherwise, we would have no goroutine which can flush memtables. func (db *DB) flushMemtable(lc *y.Closer) error { defer lc.Done() @@ -898,141 +924,14 @@ func (db *DB) updateSize(lc *y.Closer) { } } -// PurgeVersionsBelow will delete all versions of a key below the specified version -func (db *DB) PurgeVersionsBelow(key []byte, ts uint64) error { - txn := db.NewTransaction(false) - defer txn.Discard() - return db.purgeVersionsBelow(txn, key, ts) -} - -func (db *DB) purgeVersionsBelow(txn *Txn, key []byte, ts uint64) error { - opts := DefaultIteratorOptions - opts.AllVersions = true - opts.PrefetchValues = false - it := txn.NewIterator(opts) - defer it.Close() - - var entries []*Entry - - for it.Seek(key); it.ValidForPrefix(key); it.Next() { - item := it.Item() - if !bytes.Equal(key, item.Key()) || item.Version() >= ts { - continue - } - if isDeletedOrExpired(item.meta, item.ExpiresAt()) { - continue - } - - // Found an older version. Mark for deletion - entries = append(entries, - &Entry{ - Key: y.KeyWithTs(key, item.version), - meta: bitDelete, - }) - db.vlog.updateGCStats(item) - } - return db.batchSet(entries) -} - -// PurgeOlderVersions deletes older versions of all keys. -// -// This function could be called prior to doing garbage collection to clean up -// older versions that are no longer needed. The caller must make sure that -// there are no long-running read transactions running before this function is -// called, otherwise they will not work as expected. -func (db *DB) PurgeOlderVersions() error { - return db.View(func(txn *Txn) error { - opts := DefaultIteratorOptions - opts.AllVersions = true - opts.PrefetchValues = false - it := txn.NewIterator(opts) - defer it.Close() - - var entries []*Entry - var lastKey []byte - var count, size int - var wg sync.WaitGroup - errChan := make(chan error, 1) - - // func to check for pending error before sending off a batch for writing - batchSetAsyncIfNoErr := func(entries []*Entry) error { - select { - case err := <-errChan: - return err - default: - wg.Add(1) - return txn.db.batchSetAsync(entries, func(err error) { - defer wg.Done() - if err != nil { - select { - case errChan <- err: - default: - } - } - }) - } - } - - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - if !bytes.Equal(lastKey, item.Key()) { - lastKey = y.SafeCopy(lastKey, item.Key()) - continue - } - if isDeletedOrExpired(item.meta, item.ExpiresAt()) { - continue - } - // Found an older version. Mark for deletion - e := &Entry{ - Key: y.KeyWithTs(lastKey, item.version), - meta: bitDelete, - } - db.vlog.updateGCStats(item) - curSize := e.estimateSize(db.opt.ValueThreshold) - - // Batch up min(1000, maxBatchCount) entries at a time and write - // Ensure that total batch size doesn't exceed maxBatchSize - if count == 1000 || count+1 >= int(db.opt.maxBatchCount) || - size+curSize >= int(db.opt.maxBatchSize) { - if err := batchSetAsyncIfNoErr(entries); err != nil { - return err - } - count = 0 - size = 0 - entries = []*Entry{} - } - size += curSize - count++ - entries = append(entries, e) - } - - // Write last batch pending deletes - if count > 0 { - if err := batchSetAsyncIfNoErr(entries); err != nil { - return err - } - } - - wg.Wait() - - select { - case err := <-errChan: - return err - default: - return nil - } - }) -} - // RunValueLogGC triggers a value log garbage collection. // // It picks value log files to perform GC based on statistics that are collected -// duing the session, when DB.PurgeOlderVersions() and DB.PurgeVersions() is -// called. If no such statistics are available, then log files are picked in -// random order. The process stops as soon as the first log file is encountered -// which does not result in garbage collection. +// duing compactions. If no such statistics are available, then log files are +// picked in random order. The process stops as soon as the first log file is +// encountered which does not result in garbage collection. // -// When a log file is picked, it is first sampled If the sample shows that we +// When a log file is picked, it is first sampled. If the sample shows that we // can discard at least discardRatio space of that file, it would be rewritten. // // If a call to RunValueLogGC results in no rewrites, then an ErrNoRewrite is @@ -1060,8 +959,7 @@ func (db *DB) RunValueLogGC(discardRatio float64) error { // Find head on disk headKey := y.KeyWithTs(head, math.MaxUint64) // Need to pass with timestamp, lsm get removes the last 8 bytes and compares key - var maxVs y.ValueStruct - val, err := db.lc.get(headKey, maxVs) + val, err := db.lc.get(headKey) if err != nil { return errors.Wrap(err, "Retrieving head from on-disk LSM") } @@ -1179,14 +1077,17 @@ func (db *DB) GetSequence(key []byte, bandwidth uint64) (*Sequence, error) { return seq, err } +func (db *DB) Tables() []TableInfo { + return db.lc.getTableInfo() +} + // MergeOperator represents a Badger merge operator. type MergeOperator struct { sync.RWMutex - f MergeFunc - db *DB - key []byte - skipAtOrBelow uint64 - closer *y.Closer + f MergeFunc + db *DB + key []byte + closer *y.Closer } // MergeFunc accepts two byte slices, one representing an existing value, and @@ -1214,66 +1115,67 @@ func (db *DB) GetMergeOperator(key []byte, return op } -func (op *MergeOperator) iterateAndMerge(txn *Txn) (maxVersion uint64, val []byte, err error) { +var errNoMerge = errors.New("No need for merge") + +func (op *MergeOperator) iterateAndMerge(txn *Txn) (val []byte, err error) { opt := DefaultIteratorOptions opt.AllVersions = true it := txn.NewIterator(opt) - var first bool + defer it.Close() + + var numVersions int for it.Rewind(); it.ValidForPrefix(op.key); it.Next() { item := it.Item() - if item.Version() <= op.skipAtOrBelow { - continue - } - if item.Version() > maxVersion { - maxVersion = item.Version() - } - if !first { - first = true + numVersions++ + if numVersions == 1 { val, err = item.ValueCopy(val) if err != nil { - return 0, nil, err + return nil, err } } else { newVal, err := item.Value() if err != nil { - return 0, nil, err + return nil, err } val = op.f(val, newVal) } + if item.DiscardEarlierVersions() { + break + } } - if !first { - return 0, nil, ErrKeyNotFound + if numVersions == 0 { + return nil, ErrKeyNotFound + } else if numVersions == 1 { + return val, errNoMerge } - return maxVersion, val, nil + return val, nil } func (op *MergeOperator) compact() error { op.Lock() defer op.Unlock() - var maxVersion uint64 err := op.db.Update(func(txn *Txn) error { var ( val []byte err error ) - maxVersion, val, err = op.iterateAndMerge(txn) + val, err = op.iterateAndMerge(txn) if err != nil { return err } // Write value back to db - if maxVersion > op.skipAtOrBelow { - if err := txn.Set(op.key, val); err != nil { - return err - } + if err := txn.SetWithDiscard(op.key, val, 0); err != nil { + return err } return nil }) - if err != nil && err != ErrKeyNotFound { // Ignore ErrKeyNotFound errors during compaction + + if err == ErrKeyNotFound || err == errNoMerge { + // pass. + } else if err != nil { return err } - // Update version - op.skipAtOrBelow = maxVersion return nil } @@ -1287,16 +1189,9 @@ func (op *MergeOperator) runCompactions(dur time.Duration) { stop = true case <-ticker.C: // wait for tick } - oldSkipVersion := op.skipAtOrBelow if err := op.compact(); err != nil { log.Printf("Error while running merge operation: %s", err) } - // Purge older versions if version has updated - if op.skipAtOrBelow > oldSkipVersion { - if err := op.db.PurgeVersionsBelow(op.key, op.skipAtOrBelow+1); err != nil { - log.Printf("Error purging merged keys: %s", err) - } - } if stop { ticker.Stop() break @@ -1315,15 +1210,18 @@ func (op *MergeOperator) Add(val []byte) error { // Get returns the latest value for the merge operator, which is derived by // applying the merge function to all the values added so far. // -// If Add has not been called even once, Get will return ErrKeyNotFound +// If Add has not been called even once, Get will return ErrKeyNotFound. func (op *MergeOperator) Get() ([]byte, error) { op.RLock() defer op.RUnlock() var existing []byte err := op.db.View(func(txn *Txn) (err error) { - _, existing, err = op.iterateAndMerge(txn) + existing, err = op.iterateAndMerge(txn) return err }) + if err == errNoMerge { + return existing, nil + } return existing, err } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_unix.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_unix.go index a058f45438..a5e0fa33c5 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_unix.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_unix.go @@ -35,11 +35,14 @@ type directoryLockGuard struct { f *os.File // The absolute path to our pid file. path string + // Was this a shared lock for a read-only database? + readOnly bool } -// acquireDirectoryLock gets an exclusive lock on the directory (using flock). It writes our pid -// to dirPath/pidFileName for convenience. -func acquireDirectoryLock(dirPath string, pidFileName string) (*directoryLockGuard, error) { +// acquireDirectoryLock gets a lock on the directory (using flock). If +// this is not read-only, it will also write our pid to +// dirPath/pidFileName for convenience. +func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) { // Convert to absolute path so that Release still works even if we do an unbalanced // chdir in the meantime. absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) @@ -50,7 +53,12 @@ func acquireDirectoryLock(dirPath string, pidFileName string) (*directoryLockGua if err != nil { return nil, errors.Wrapf(err, "cannot open directory %q", dirPath) } - err = unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB) + opts := unix.LOCK_EX | unix.LOCK_NB + if readOnly { + opts = unix.LOCK_SH | unix.LOCK_NB + } + + err = unix.Flock(int(f.Fd()), opts) if err != nil { f.Close() return nil, errors.Wrapf(err, @@ -58,22 +66,27 @@ func acquireDirectoryLock(dirPath string, pidFileName string) (*directoryLockGua dirPath) } - // Yes, we happily overwrite a pre-existing pid file. We're the only badger process using this - // directory. - err = ioutil.WriteFile(absPidFilePath, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666) - if err != nil { - f.Close() - return nil, errors.Wrapf(err, - "Cannot write pid file %q", absPidFilePath) + if !readOnly { + // Yes, we happily overwrite a pre-existing pid file. We're the + // only read-write badger process using this directory. + err = ioutil.WriteFile(absPidFilePath, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666) + if err != nil { + f.Close() + return nil, errors.Wrapf(err, + "Cannot write pid file %q", absPidFilePath) + } } - - return &directoryLockGuard{f, absPidFilePath}, nil + return &directoryLockGuard{f, absPidFilePath, readOnly}, nil } // Release deletes the pid file and releases our lock on the directory. func (guard *directoryLockGuard) release() error { - // It's important that we remove the pid file first. - err := os.Remove(guard.path) + var err error + if !guard.readOnly { + // It's important that we remove the pid file first. + err = os.Remove(guard.path) + } + if closeErr := guard.f.Close(); err == nil { err = closeErr } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_windows.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_windows.go index 36d599fd03..80de84e16f 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_windows.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_windows.go @@ -57,7 +57,11 @@ type directoryLockGuard struct { } // AcquireDirectoryLock acquires exclusive access to a directory. -func acquireDirectoryLock(dirPath string, pidFileName string) (*directoryLockGuard, error) { +func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) { + if readOnly { + return nil, ErrWindowsNotSupported + } + // Convert to absolute path so that Release still works even if we do an unbalanced // chdir in the meantime. absLockFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go index b03a24f892..c567d3c5e0 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go @@ -27,6 +27,10 @@ var ( // range. ErrValueLogSize = errors.New("Invalid ValueLogFileSize, must be between 1MB and 2GB") + // ErrValueThreshold is returned when ValueThreshold is set to a value close to or greater than + // uint16. + ErrValueThreshold = errors.New("Invalid ValueThreshold, must be lower than uint16.") + // ErrKeyNotFound is returned when key isn't found on a txn.Get. ErrKeyNotFound = errors.New("Key not found") @@ -81,9 +85,21 @@ var ( // ErrInvalidLoadingMode is returned when opt.ValueLogLoadingMode option is not // within the valid range ErrInvalidLoadingMode = errors.New("Invalid ValueLogLoadingMode, must be FileIO or MemoryMap") + + // ErrReplayNeeded is returned when opt.ReadOnly is set but the + // database requires a value log replay. + ErrReplayNeeded = errors.New("Database was not properly closed, cannot open read-only") + + // ErrWindowsNotSupported is returned when opt.ReadOnly is used on Windows + ErrWindowsNotSupported = errors.New("Read-only mode is not supported on Windows") + + // ErrTruncateNeeded is returned when the value log gets corrupt, and requires truncation of + // corrupt data to allow Badger to run properly. + ErrTruncateNeeded = errors.New("Value log truncate required to run DB. This might result in data loss.") ) -const maxKeySize = 1 << 16 // Key length can't be more than uint16, as determined by table::header. +// Key length can't be more than uint16, as determined by table::header. +const maxKeySize = 1<<16 - 8 // 8 bytes are for storing timestamp func exceedsMaxKeySizeError(key []byte) error { return errors.Errorf("Key with size %d exceeded %d limit. Key:\n%s", diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go index c7ce634655..9415396290 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go @@ -20,6 +20,7 @@ import ( "bytes" "fmt" "sync" + "sync/atomic" "time" "github.com/dgraph-io/badger/options" @@ -53,20 +54,32 @@ type Item struct { txn *Txn } -// ToString returns a string representation of Item -func (item *Item) ToString() string { +// String returns a string representation of Item +func (item *Item) String() string { return fmt.Sprintf("key=%q, version=%d, meta=%x", item.Key(), item.Version(), item.meta) +} +// Deprecated +// ToString returns a string representation of Item +func (item *Item) ToString() string { + return item.String() } // Key returns the key. // // Key is only valid as long as item is valid, or transaction is valid. If you need to use it -// outside its validity, please copy it. +// outside its validity, please use KeyCopy func (item *Item) Key() []byte { return item.key } +// KeyCopy returns a copy of the key of the item, writing it to dst slice. +// If nil is passed, or capacity of dst isn't sufficient, a new slice would be allocated and +// returned. +func (item *Item) KeyCopy(dst []byte) []byte { + return y.SafeCopy(dst, item.key) +} + // Version returns the commit timestamp of the item. func (item *Item) Version() uint64 { return item.version @@ -80,7 +93,8 @@ func (item *Item) Version() uint64 { // reused. // // If you need to use a value outside a transaction, please use Item.ValueCopy -// instead, or copy it yourself. +// instead, or copy it yourself. Value might change once discard or commit is called. +// Use ValueCopy if you want to do a Set after Get. func (item *Item) Value() ([]byte, error) { item.wg.Wait() if item.status == prefetched { @@ -117,24 +131,65 @@ func (item *Item) hasValue() bool { return true } +// IsDeletedOrExpired returns true if item contains deleted or expired value. +func (item *Item) IsDeletedOrExpired() bool { + return isDeletedOrExpired(item.meta, item.expiresAt) +} + +func (item *Item) DiscardEarlierVersions() bool { + return item.meta&bitDiscardEarlierVersions > 0 +} + func (item *Item) yieldItemValue() ([]byte, func(), error) { - if !item.hasValue() { - return nil, nil, nil - } + key := item.Key() // No need to copy. + for { + if !item.hasValue() { + return nil, nil, nil + } - if item.slice == nil { - item.slice = new(y.Slice) - } + if item.slice == nil { + item.slice = new(y.Slice) + } - if (item.meta & bitValuePointer) == 0 { - val := item.slice.Resize(len(item.vptr)) - copy(val, item.vptr) - return val, nil, nil - } + if (item.meta & bitValuePointer) == 0 { + val := item.slice.Resize(len(item.vptr)) + copy(val, item.vptr) + return val, nil, nil + } - var vp valuePointer - vp.Decode(item.vptr) - return item.db.vlog.Read(vp, item.slice) + var vp valuePointer + vp.Decode(item.vptr) + result, cb, err := item.db.vlog.Read(vp, item.slice) + if err != ErrRetry || bytes.HasPrefix(key, badgerMove) { + // The error is not retry, or we have already searched the move keyspace. + return result, cb, err + } + + // The value pointer is pointing to a deleted value log. Look for the + // move key and read that instead. + runCallback(cb) + // Do not put badgerMove on the left in append. It seems to cause some sort of manipulation. + key = append([]byte{}, badgerMove...) + key = append(key, y.KeyWithTs(item.Key(), item.Version())...) + // Note that we can't set item.key to move key, because that would + // change the key user sees before and after this call. Also, this move + // logic is internal logic and should not impact the external behavior + // of the retrieval. + vs, err := item.db.get(key) + if err != nil { + return nil, nil, err + } + if vs.Version != item.Version() { + return nil, nil, nil + } + // Bug fix: Always copy the vs.Value into vptr here. Otherwise, when item is reused this + // slice gets overwritten. + item.vptr = y.SafeCopy(item.vptr, vs.Value) + item.meta &^= bitValuePointer // Clear the value pointer bit. + if vs.Meta&bitValuePointer > 0 { + item.meta |= bitValuePointer // This meta would only be about value pointer. + } + } } func runCallback(cb func()) { @@ -235,6 +290,8 @@ type IteratorOptions struct { PrefetchSize int Reverse bool // Direction of iteration. False is forward, true is backward. AllVersions bool // Fetch all valid versions of the same key. + + internalAccess bool // Used to allow internal access to badger keys. } // DefaultIteratorOptions contains default options when iterating over Badger key-value stores. @@ -264,6 +321,10 @@ type Iterator struct { // Using prefetch is highly recommended if you're doing a long running iteration. // Avoid long running iterations in update transactions. func (txn *Txn) NewIterator(opt IteratorOptions) *Iterator { + if atomic.AddInt32(&txn.numIterators, 1) > 1 { + panic("Only one iterator can be active at one time.") + } + tables, decr := txn.db.getMemTables() defer decr() txn.db.vlog.incrIteratorCount() @@ -315,8 +376,22 @@ func (it *Iterator) ValidForPrefix(prefix []byte) bool { // Close would close the iterator. It is important to call this when you're done with iteration. func (it *Iterator) Close() { it.iitr.Close() + + // It is important to wait for the fill goroutines to finish. Otherwise, we might leave zombie + // goroutines behind, which are waiting to acquire file read locks after DB has been closed. + waitFor := func(l list) { + item := l.pop() + for item != nil { + item.wg.Wait() + item = l.pop() + } + } + waitFor(it.waste) + waitFor(it.data) + // TODO: We could handle this error. _ = it.txn.db.vlog.decrIteratorCount() + atomic.AddInt32(&it.txn.numIterators, -1) } // Next would advance the iterator by one. Always check it.Valid() after a Next() @@ -367,7 +442,7 @@ func (it *Iterator) parseItem() bool { } // Skip badger keys. - if bytes.HasPrefix(key, badgerPrefix) { + if !it.opt.internalAccess && bytes.HasPrefix(key, badgerPrefix) { mi.Next() return false } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go index 55002b4fa6..e7f01d2dc3 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go @@ -18,6 +18,7 @@ package badger import ( "fmt" + "math" "math/rand" "os" "sort" @@ -104,7 +105,11 @@ func newLevelsController(kv *DB, mf *Manifest) (*levelsController, error) { var maxFileID uint64 for fileID, tableManifest := range mf.Tables { fname := table.NewFilename(fileID, kv.opt.Dir) - fd, err := y.OpenExistingSyncedFile(fname, true) + var flags uint32 = y.Sync + if kv.opt.ReadOnly { + flags |= y.ReadOnly + } + fd, err := y.OpenExistingFile(fname, flags) if err != nil { closeAllTables(tables) return nil, errors.Wrapf(err, "Opening file: %q", fname) @@ -258,6 +263,35 @@ func (s *levelsController) compactBuildTables( topTables := cd.top botTables := cd.bot + var hasOverlap bool + { + kr := getKeyRange(cd.top) + for i, lh := range s.levels { + if i <= l { // Skip upper levels. + continue + } + lh.RLock() + left, right := lh.overlappingTables(levelHandlerRLocked{}, kr) + lh.RUnlock() + if right-left > 0 { + hasOverlap = true + break + } + } + cd.elog.LazyPrintf("Key range overlaps with lower levels: %v", hasOverlap) + } + + // Try to collect stats so that we can inform value log about GC. That would help us find which + // value log file should be GCed. + discardStats := make(map[uint32]int64) + updateStats := func(vs y.ValueStruct) { + if vs.Meta&bitValuePointer > 0 { + var vp valuePointer + vp.Decode(vs.Value) + discardStats[vp.Fid] += int64(vp.Len) + } + } + // Create iterators across all the tables involved first. var iters []y.Iterator if l == 0 { @@ -274,54 +308,111 @@ func (s *levelsController) compactBuildTables( it.Rewind() + // Pick up the currently pending transactions' min readTs, so we can discard versions below this + // readTs. We should never discard any versions starting from above this timestamp, because that + // would affect the snapshot view guarantee provided by transactions. + minReadTs := s.kv.orc.readMark.MinReadTs() + // Start generating new tables. type newTableResult struct { table *table.Table err error } resultCh := make(chan newTableResult) - var i int - for ; it.Valid(); i++ { + var numBuilds, numVersions int + var lastKey, skipKey []byte + for it.Valid() { timeStart := time.Now() builder := table.NewTableBuilder() + var numKeys, numSkips uint64 for ; it.Valid(); it.Next() { - if builder.ReachedCapacity(s.kv.opt.MaxTableSize) { - break + // See if we need to skip this key. + if len(skipKey) > 0 { + if y.SameKey(it.Key(), skipKey) { + numSkips++ + updateStats(it.Value()) + continue + } else { + skipKey = skipKey[:0] + } } + + if !y.SameKey(it.Key(), lastKey) { + if builder.ReachedCapacity(s.kv.opt.MaxTableSize) { + // Only break if we are on a different key, and have reached capacity. We want + // to ensure that all versions of the key are stored in the same sstable, and + // not divided across multiple tables at the same level. + break + } + lastKey = y.SafeCopy(lastKey, it.Key()) + numVersions = 0 + } + + vs := it.Value() + version := y.ParseTs(it.Key()) + if version <= minReadTs { + // Keep track of the number of versions encountered for this key. Only consider the + // versions which are below the minReadTs, otherwise, we might end up discarding the + // only valid version for a running transaction. + numVersions++ + lastValidVersion := vs.Meta&bitDiscardEarlierVersions > 0 + if isDeletedOrExpired(vs.Meta, vs.ExpiresAt) || + numVersions > s.kv.opt.NumVersionsToKeep || + lastValidVersion { + // If this version of the key is deleted or expired, skip all the rest of the + // versions. Ensure that we're only removing versions below readTs. + skipKey = y.SafeCopy(skipKey, it.Key()) + + if lastValidVersion { + // Add this key. We have set skipKey, so the following key versions + // would be skipped. + } else if hasOverlap { + // If this key range has overlap with lower levels, then keep the deletion + // marker with the latest version, discarding the rest. We have set skipKey, + // so the following key versions would be skipped. + } else { + // If no overlap, we can skip all the versions, by continuing here. + numSkips++ + updateStats(vs) + continue // Skip adding this key. + } + } + } + numKeys++ y.Check(builder.Add(it.Key(), it.Value())) } // It was true that it.Valid() at least once in the loop above, which means we // called Add() at least once, and builder is not Empty(). - y.AssertTrue(!builder.Empty()) - - cd.elog.LazyPrintf("LOG Compact. Iteration to generate one table took: %v\n", time.Since(timeStart)) - - fileID := s.reserveFileID() - go func(builder *table.Builder) { - defer builder.Close() - - fd, err := y.CreateSyncedFile(table.NewFilename(fileID, s.kv.opt.Dir), true) - if err != nil { - resultCh <- newTableResult{nil, errors.Wrapf(err, "While opening new table: %d", fileID)} - return - } + cd.elog.LazyPrintf("Added %d keys. Skipped %d keys.", numKeys, numSkips) + cd.elog.LazyPrintf("LOG Compact. Iteration took: %v\n", time.Since(timeStart)) + if !builder.Empty() { + numBuilds++ + fileID := s.reserveFileID() + go func(builder *table.Builder) { + defer builder.Close() + + fd, err := y.CreateSyncedFile(table.NewFilename(fileID, s.kv.opt.Dir), true) + if err != nil { + resultCh <- newTableResult{nil, errors.Wrapf(err, "While opening new table: %d", fileID)} + return + } - if _, err := fd.Write(builder.Finish()); err != nil { - resultCh <- newTableResult{nil, errors.Wrapf(err, "Unable to write to file: %d", fileID)} - return - } + if _, err := fd.Write(builder.Finish()); err != nil { + resultCh <- newTableResult{nil, errors.Wrapf(err, "Unable to write to file: %d", fileID)} + return + } - tbl, err := table.OpenTable(fd, s.kv.opt.TableLoadingMode) - // decrRef is added below. - resultCh <- newTableResult{tbl, errors.Wrapf(err, "Unable to open table: %q", fd.Name())} - }(builder) + tbl, err := table.OpenTable(fd, s.kv.opt.TableLoadingMode) + // decrRef is added below. + resultCh <- newTableResult{tbl, errors.Wrapf(err, "Unable to open table: %q", fd.Name())} + }(builder) + } } newTables := make([]*table.Table, 0, 20) - // Wait for all table builders to finish. var firstErr error - for x := 0; x < i; x++ { + for x := 0; x < numBuilds; x++ { res := <-resultCh newTables = append(newTables, res.table) if firstErr == nil { @@ -339,7 +430,7 @@ func (s *levelsController) compactBuildTables( if firstErr != nil { // An error happened. Delete all the newly created table files (by calling DecrRef // -- we're the only holders of a ref). - for j := 0; j < i; j++ { + for j := 0; j < numBuilds; j++ { if newTables[j] != nil { newTables[j].DecrRef() } @@ -351,7 +442,8 @@ func (s *levelsController) compactBuildTables( sort.Slice(newTables, func(i, j int) bool { return y.CompareKeys(newTables[i].Biggest(), newTables[j].Biggest()) < 0 }) - + s.kv.vlog.updateGCStats(discardStats) + cd.elog.LazyPrintf("Discard stats: %v", discardStats) return newTables, func() error { return decrRefs(newTables) }, nil } @@ -442,8 +534,10 @@ func (s *levelsController) fillTables(cd *compactDef) bool { for _, t := range tbls { cd.thisSize = t.Size() cd.thisRange = keyRange{ - left: t.Smallest(), - right: t.Biggest(), + // We pick all the versions of the smallest and the biggest key. + left: y.KeyWithTs(y.ParseKey(t.Smallest()), math.MaxUint64), + // Note that version zero would be the rightmost key. + right: y.KeyWithTs(y.ParseKey(t.Biggest()), 0), } if s.cstatus.overlapsWith(cd.thisLevel.level, cd.thisRange) { continue @@ -482,40 +576,8 @@ func (s *levelsController) runCompactDef(l int, cd compactDef) (err error) { thisLevel := cd.thisLevel nextLevel := cd.nextLevel - if thisLevel.level >= 1 && len(cd.bot) == 0 { - y.AssertTrue(len(cd.top) == 1) - tbl := cd.top[0] - - // We write to the manifest _before_ we delete files (and after we created files). - changes := []*protos.ManifestChange{ - // The order matters here -- you can't temporarily have two copies of the same - // table id when reloading the manifest. - makeTableDeleteChange(tbl.ID()), - makeTableCreateChange(tbl.ID(), nextLevel.level), - } - if err := s.kv.manifest.addChanges(changes); err != nil { - return err - } - - // We have to add to nextLevel before we remove from thisLevel, not after. This way, we - // don't have a bug where reads would see keys missing from both levels. - - // Note: It's critical that we add tables (replace them) in nextLevel before deleting them - // in thisLevel. (We could finagle it atomically somehow.) Also, when reading we must - // read, or at least acquire s.RLock(), in increasing order by level, so that we don't skip - // a compaction. - - if err := nextLevel.replaceTables(cd.top); err != nil { - return err - } - if err := thisLevel.deleteTables(cd.top); err != nil { - return err - } - - cd.elog.LazyPrintf("\tLOG Compact-Move %d->%d smallest:%s biggest:%s took %v\n", - l, l+1, string(tbl.Smallest()), string(tbl.Biggest()), time.Since(timeStart)) - return nil - } + // Table should never be moved directly between levels, always be rewritten to allow discarding + // invalid versions. newTables, decr, err := s.compactBuildTables(l, cd) if err != nil { @@ -557,7 +619,7 @@ func (s *levelsController) doCompact(p compactionPriority) (bool, error) { y.AssertTrue(l+1 < s.kv.opt.MaxLevels) // Sanity check. cd := compactDef{ - elog: trace.New("Badger", "Compact"), + elog: trace.New(fmt.Sprintf("Badger.L%d", l), "Compact"), thisLevel: s.levels[l], nextLevel: s.levels[l+1], } @@ -580,6 +642,7 @@ func (s *levelsController) doCompact(p compactionPriority) (bool, error) { return false, nil } } + defer s.cstatus.delete(cd) // Remove the ranges from compaction status. cd.elog.LazyPrintf("Running for level: %d\n", cd.thisLevel.level) s.cstatus.toLog(cd.elog) @@ -589,8 +652,6 @@ func (s *levelsController) doCompact(p compactionPriority) (bool, error) { return false, err } - // Done with compaction. So, remove the ranges from compaction status. - s.cstatus.delete(cd) s.cstatus.toLog(cd.elog) cd.elog.LazyPrintf("Compaction for level: %d DONE", cd.thisLevel.level) return true, nil @@ -625,7 +686,7 @@ func (s *levelsController) addLevel0Table(t *table.Table) error { // Before we unstall, we need to make sure that level 0 and 1 are healthy. Otherwise, we // will very quickly fill up level 0 again and if the compaction strategy favors level 0, // then level 1 is going to super full. - for { + for i := 0; ; i++ { // Passing 0 for delSize to compactable means we're treating incomplete compactions as // not having finished -- we wait for them to finish. Also, it's crucial this behavior // replicates pickCompactLevels' behavior in computing compactability in order to @@ -634,6 +695,11 @@ func (s *levelsController) addLevel0Table(t *table.Table) error { break } time.Sleep(10 * time.Millisecond) + if i%100 == 0 { + prios := s.pickCompactLevels() + s.elog.Printf("Waiting to add level 0 table. Compaction priorities: %+v\n", prios) + i = 0 + } } { s.elog.Printf("UNSTALLED UNSTALLED UNSTALLED UNSTALLED UNSTALLED UNSTALLED: %v\n", @@ -651,14 +717,12 @@ func (s *levelsController) close() error { } // get returns the found value if any. If not found, we return nil. -func (s *levelsController) get(key []byte, maxVs y.ValueStruct) (y.ValueStruct, error) { +func (s *levelsController) get(key []byte) (y.ValueStruct, error) { // It's important that we iterate the levels from 0 on upward. The reason is, if we iterated // in opposite order, or in parallel (naively calling all the h.RLock() in some order) we could // read level L's tables post-compaction and level L+1's tables pre-compaction. (If we do // parallelize this, we will need to call the h.RLock() function by increasing order of level // number.) - - version := y.ParseTs(key) for _, h := range s.levels { vs, err := h.get(key) // Calls h.RLock() and h.RUnlock(). if err != nil { @@ -667,14 +731,9 @@ func (s *levelsController) get(key []byte, maxVs y.ValueStruct) (y.ValueStruct, if vs.Value == nil && vs.Meta == 0 { continue } - if vs.Version == version { - return vs, nil - } - if maxVs.Version < vs.Version { - maxVs = vs - } + return vs, nil } - return maxVs, nil + return y.ValueStruct{}, nil } func appendIteratorsReversed(out []y.Iterator, th []*table.Table, reversed bool) []y.Iterator { @@ -696,3 +755,31 @@ func (s *levelsController) appendIterators( } return iters } + +type TableInfo struct { + ID uint64 + Level int + Left []byte + Right []byte +} + +func (s *levelsController) getTableInfo() (result []TableInfo) { + for _, l := range s.levels { + for _, t := range l.tables { + info := TableInfo{ + ID: t.ID(), + Level: l.level, + Left: t.Smallest(), + Right: t.Biggest(), + } + result = append(result, info) + } + } + sort.Slice(result, func(i, j int) bool { + if result[i].Level != result[j].Level { + return result[i].Level < result[j].Level + } + return result[i].ID < result[j].ID + }) + return +} diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go index 3b52c1e784..adbec802b7 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go @@ -72,13 +72,6 @@ func (txn *Txn) CommitAt(commitTs uint64, callback func(error)) error { return txn.Commit(callback) } -// PurgeVersionsBelow will delete all versions of a key below the specified version -func (db *ManagedDB) PurgeVersionsBelow(key []byte, ts uint64) error { - txn := db.NewTransactionAt(ts, false) - defer txn.Discard() - return db.purgeVersionsBelow(txn, key, ts) -} - // GetSequence is not supported on ManagedDB. Calling this would result // in a panic. func (db *ManagedDB) GetSequence(_ []byte, _ uint64) (*Sequence, error) { diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/manifest.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/manifest.go index f2e57d9b06..e16bbfb467 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/manifest.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/manifest.go @@ -32,7 +32,7 @@ import ( "github.com/pkg/errors" ) -// Manifest represnts the contents of the MANIFEST file in a Badger store. +// Manifest represents the contents of the MANIFEST file in a Badger store. // // The MANIFEST file describes the startup state of the db -- all LSM files and what level they're // at. @@ -112,17 +112,24 @@ func (m *Manifest) clone() Manifest { // openOrCreateManifestFile opens a Badger manifest file if it exists, or creates on if // one doesn’t. -func openOrCreateManifestFile(dir string) (ret *manifestFile, result Manifest, err error) { - return helpOpenOrCreateManifestFile(dir, manifestDeletionsRewriteThreshold) +func openOrCreateManifestFile(dir string, readOnly bool) (ret *manifestFile, result Manifest, err error) { + return helpOpenOrCreateManifestFile(dir, readOnly, manifestDeletionsRewriteThreshold) } -func helpOpenOrCreateManifestFile(dir string, deletionsThreshold int) (ret *manifestFile, result Manifest, err error) { +func helpOpenOrCreateManifestFile(dir string, readOnly bool, deletionsThreshold int) (ret *manifestFile, result Manifest, err error) { path := filepath.Join(dir, ManifestFilename) - fp, err := y.OpenExistingSyncedFile(path, false) // We explicitly sync in addChanges, outside the lock. + var flags uint32 + if readOnly { + flags |= y.ReadOnly + } + fp, err := y.OpenExistingFile(path, flags) // We explicitly sync in addChanges, outside the lock. if err != nil { if !os.IsNotExist(err) { return nil, Manifest{}, err } + if readOnly { + return nil, Manifest{}, fmt.Errorf("no manifest found, required for read-only db") + } m := createManifest() fp, netCreations, err := helpRewrite(dir, &m) if err != nil { @@ -144,12 +151,13 @@ func helpOpenOrCreateManifestFile(dir string, deletionsThreshold int) (ret *mani return nil, Manifest{}, err } - // Truncate file so we don't have a half-written entry at the end. - if err := fp.Truncate(truncOffset); err != nil { - _ = fp.Close() - return nil, Manifest{}, err + if !readOnly { + // Truncate file so we don't have a half-written entry at the end. + if err := fp.Truncate(truncOffset); err != nil { + _ = fp.Close() + return nil, Manifest{}, err + } } - if _, err = fp.Seek(0, io.SeekEnd); err != nil { _ = fp.Close() return nil, Manifest{}, err @@ -256,7 +264,7 @@ func helpRewrite(dir string, m *Manifest) (*os.File, int, error) { if err := os.Rename(rewritePath, manifestPath); err != nil { return nil, 0, err } - fp, err = y.OpenExistingSyncedFile(manifestPath, false) + fp, err = y.OpenExistingFile(manifestPath, 0) if err != nil { return nil, 0, err } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go index 9b8bc6ee99..f6d0abb172 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go @@ -46,9 +46,12 @@ type Options struct { // How should LSM tree be accessed. TableLoadingMode options.FileLoadingMode - // How should value log be accessed + // How should value log be accessed. ValueLogLoadingMode options.FileLoadingMode + // How many versions to keep per key. + NumVersionsToKeep int + // 3. Flags that user might want to review // ---------------------------------------- // The following affect all levels of LSM tree. @@ -73,6 +76,10 @@ type Options struct { // Size of single value log file. ValueLogFileSize int64 + // Max number of entries a value log file can hold (approximately). A value log file would be + // determined by the smaller of its file size and max entries. + ValueLogMaxEntries uint32 + // Number of compaction workers to run concurrently. NumCompactors int @@ -86,6 +93,15 @@ type Options struct { maxBatchCount int64 // max entries in batch maxBatchSize int64 // max batch size in bytes + + // Open the DB as read-only. With this set, multiple processes can + // open the same Badger DB. Note: if the DB being opened had crashed + // before and has vlog data to be replayed, ReadOnly will cause Open + // to fail with an appropriate message. + ReadOnly bool + + // Truncate value log to delete corrupt data, if any. Would not truncate if ReadOnly is set. + Truncate bool } // DefaultOptions sets a list of recommended options for good performance. @@ -105,8 +121,24 @@ var DefaultOptions = Options{ NumLevelZeroTablesStall: 10, NumMemtables: 5, SyncWrites: true, + NumVersionsToKeep: 1, // Nothing to read/write value log using standard File I/O // MemoryMap to mmap() the value log files - ValueLogFileSize: 1 << 30, - ValueThreshold: 20, + ValueLogFileSize: 1 << 30, + ValueLogMaxEntries: 1000000, + ValueThreshold: 32, + Truncate: false, +} + +// LSMOnlyOptions follows from DefaultOptions, but sets a higher ValueThreshold so values would +// be colocated with the LSM tree, with value log largely acting as a write-ahead log only. These +// options would reduce the disk usage of value log, and make Badger act like a typical LSM tree. +var LSMOnlyOptions = Options{} + +func init() { + LSMOnlyOptions = DefaultOptions + + LSMOnlyOptions.ValueThreshold = 65500 // Max value length which fits in uint16. + LSMOnlyOptions.ValueLogFileSize = 64 << 20 // Allow easy space reclamation. + LSMOnlyOptions.ValueLogLoadingMode = options.FileIO } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/arena.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/arena.go index 849e5ee564..def550712f 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/arena.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/arena.go @@ -25,7 +25,12 @@ import ( const ( offsetSize = int(unsafe.Sizeof(uint32(0))) - ptrAlign = int(unsafe.Sizeof(uintptr(0))) - 1 + + // Always align nodes on 64-bit boundaries, even on 32-bit architectures, + // so that the node.value field is 64-bit aligned. This is necessary because + // node.getValueOffset uses atomic.LoadUint64, which expects its input + // pointer to be 64-bit aligned. + nodeAlign = int(unsafe.Sizeof(uint64(0))) - 1 ) // Arena should be lock-free. @@ -61,14 +66,14 @@ func (s *Arena) putNode(height int) uint32 { unusedSize := (maxHeight - height) * offsetSize // Pad the allocation with enough bytes to ensure pointer alignment. - l := uint32(MaxNodeSize - unusedSize + ptrAlign) + l := uint32(MaxNodeSize - unusedSize + nodeAlign) n := atomic.AddUint32(&s.n, l) y.AssertTruef(int(n) <= len(s.buf), "Arena too small, toWrite:%d newTotal:%d limit:%d", l, n, len(s.buf)) // Return the aligned offset. - m := (n - l + uint32(ptrAlign)) & ^uint32(ptrAlign) + m := (n - l + uint32(nodeAlign)) & ^uint32(nodeAlign) return m } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/skl.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/skl.go index 7361751a50..b465b09ecc 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/skl.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/skl.go @@ -54,8 +54,7 @@ type node struct { // can be atomically loaded and stored: // value offset: uint32 (bits 0-31) // value size : uint16 (bits 32-47) - // 12 bytes are allocated to ensure 8 byte alignment also on 32bit systems. - value [12]byte + value uint64 // A byte slice is 24 bytes. We are trying to save space here. keyOffset uint32 // Immutable. No need to lock to access key. @@ -109,7 +108,7 @@ func newNode(arena *Arena, key []byte, v y.ValueStruct, height int) *node { node.keyOffset = arena.putKey(key) node.keySize = uint16(len(key)) node.height = uint16(height) - *node.value64BitAlignedPtr() = encodeValue(arena.putVal(v), v.EncodedSize()) + node.value = encodeValue(arena.putVal(v), v.EncodedSize()) return node } @@ -135,15 +134,8 @@ func NewSkiplist(arenaSize int64) *Skiplist { } } -func (s *node) value64BitAlignedPtr() *uint64 { - if uintptr(unsafe.Pointer(&s.value))%8 == 0 { - return (*uint64)(unsafe.Pointer(&s.value)) - } - return (*uint64)(unsafe.Pointer(&s.value[4])) -} - func (s *node) getValueOffset() (uint32, uint16) { - value := atomic.LoadUint64(s.value64BitAlignedPtr()) + value := atomic.LoadUint64(&s.value) return decodeValue(value) } @@ -154,7 +146,7 @@ func (s *node) key(arena *Arena) []byte { func (s *node) setValue(arena *Arena, v y.ValueStruct) { valOffset := arena.putVal(v) value := encodeValue(valOffset, v.EncodedSize()) - atomic.StoreUint64(s.value64BitAlignedPtr(), value) + atomic.StoreUint64(&s.value, value) } func (s *node) getNextOffset(h int) uint32 { diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/README.md b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/README.md new file mode 100644 index 0000000000..5d33e96ab5 --- /dev/null +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/README.md @@ -0,0 +1,51 @@ +# BenchmarkRead + +``` +$ go test -bench Read$ -count 3 + +Size of table: 105843444 +BenchmarkRead-8 3 343846914 ns/op +BenchmarkRead-8 3 351790907 ns/op +BenchmarkRead-8 3 351762823 ns/op +``` + +Size of table is 105,843,444 bytes, which is ~101M. + +The rate is ~287M/s which matches our read speed. This is using mmap. + +To read a 64M table, this would take ~0.22s, which is negligible. + +``` +$ go test -bench BenchmarkReadAndBuild -count 3 + +BenchmarkReadAndBuild-8 1 2341034225 ns/op +BenchmarkReadAndBuild-8 1 2346349671 ns/op +BenchmarkReadAndBuild-8 1 2364064576 ns/op +``` + +The rate is ~43M/s. To build a ~64M table, this would take ~1.5s. Note that this +does NOT include the flushing of the table to disk. All we are doing above is +to read one table (mmaped) and write one table in memory. + +The table building takes 1.5-0.22 ~ 1.3s. + +If we are writing out up to 10 tables, this would take 1.5*10 ~ 15s, and ~13s +is spent building the tables. + +When running populate, building one table in memory tends to take ~1.5s to ~2.5s +on my system. Where does this overhead come from? Let's investigate the merging. + +Below, we merge 5 tables. The total size remains unchanged at ~101M. + +``` +$ go test -bench ReadMerged -count 3 +BenchmarkReadMerged-8 1 1321190264 ns/op +BenchmarkReadMerged-8 1 1296958737 ns/op +BenchmarkReadMerged-8 1 1314381178 ns/op +``` + +The rate is ~76M/s. To build a 64M table, this would take ~0.84s. The writing +takes ~1.3s as we saw above. So in total, we expect around 0.84+1.3 ~ 2.1s. +This roughly matches what we observe when running populate. There might be +some additional overhead due to the concurrent writes going on, in flushing the +table to disk. Also, the tables tend to be slightly bigger than 64M/s. \ No newline at end of file diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/builder.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/builder.go index 7ce0d61f9e..43e6562239 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/builder.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/builder.go @@ -212,7 +212,7 @@ func (b *Builder) Finish() []byte { } kl := int(binary.BigEndian.Uint16(klen[:])) if cap(key) < kl { - key = make([]byte, 2*kl) + key = make([]byte, 2*int(kl)) // 2 * uint16 will overflow } key = key[:kl] y.Check2(b.keyBuf.Read(key)) diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/iterator.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/iterator.go index 533fe2a6ed..0eb5ed01a9 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/iterator.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/iterator.go @@ -341,6 +341,7 @@ func (itr *Iterator) next() { } itr.bi = block.NewIterator() itr.bi.SeekToFirst() + itr.err = itr.bi.Error() return } @@ -368,6 +369,7 @@ func (itr *Iterator) prev() { } itr.bi = block.NewIterator() itr.bi.SeekToLast() + itr.err = itr.bi.Error() return } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/table.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/table.go index 52a17b17ab..9804fa1764 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/table.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/table.go @@ -22,7 +22,6 @@ import ( "os" "path" "path/filepath" - "sort" "strconv" "strings" "sync" @@ -102,12 +101,6 @@ func (b block) NewIterator() *blockIterator { return &blockIterator{data: b.data} } -type byKey []keyOffset - -func (b byKey) Len() int { return len(b) } -func (b byKey) Swap(i int, j int) { b[i], b[j] = b[j], b[i] } -func (b byKey) Less(i int, j int) bool { return y.CompareKeys(b[i].key, b[j].key) < 0 } - // OpenTable assumes file has only one table and opens it. Takes ownership of fd upon function // entry. Returns a table with one reference count on it (decrementing which may delete the file! // -- consider t.Close() instead). The fd has to writeable because we call Truncate on it before @@ -290,7 +283,6 @@ func (t *Table) readIndex() error { return readError } - sort.Sort(byKey(t.blockIndex)) return nil } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go index ac9d1a25b8..6ce3860525 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go @@ -18,8 +18,6 @@ package badger import ( "bytes" - "container/heap" - "fmt" "math" "sort" "strconv" @@ -32,32 +30,17 @@ import ( "github.com/pkg/errors" ) -type uint64Heap []uint64 - -func (u uint64Heap) Len() int { return len(u) } -func (u uint64Heap) Less(i int, j int) bool { return u[i] < u[j] } -func (u uint64Heap) Swap(i int, j int) { u[i], u[j] = u[j], u[i] } -func (u *uint64Heap) Push(x interface{}) { *u = append(*u, x.(uint64)) } -func (u *uint64Heap) Pop() interface{} { - old := *u - n := len(old) - x := old[n-1] - *u = old[0 : n-1] - return x -} - type oracle struct { + // curRead must be at the top for memory alignment. See issue #311. curRead uint64 // Managed by the mutex. refCount int64 isManaged bool // Does not change value, so no locking required. sync.Mutex + writeLock sync.Mutex nextCommit uint64 - // These two structures are used to figure out when a commit is done. The minimum done commit is - // used to update curRead. - commitMark uint64Heap - pendingCommits map[uint64]struct{} + readMark y.WaterMark // commits stores a key fingerprint and latest commit counter for it. // refCount is used to clear out commits map to avoid a memory blowup. @@ -70,15 +53,14 @@ func (o *oracle) addRef() { func (o *oracle) decrRef() { if count := atomic.AddInt64(&o.refCount, -1); count == 0 { - // Clear out pendingCommits maps to release memory. + // Clear out commits maps to release memory. o.Lock() - // There could be race here, so check again. - // Checking commitMark is safe since it is protected by mutex. - if len(o.commitMark) > 0 { + // Avoids the race where something new is added to commitsMap + // after we check refCount and before we take Lock. + if atomic.LoadInt64(&o.refCount) != 0 { o.Unlock() return } - y.AssertTrue(len(o.pendingCommits) == 0) if len(o.commits) >= 1000 { // If the map is still small, let it slide. o.commits = make(map[uint64]uint64) } @@ -134,15 +116,6 @@ func (o *oracle) newCommitTs(txn *Txn) uint64 { for _, w := range txn.writes { o.commits[w] = ts // Update the commitTs. } - if o.isManaged { - // No need to update the heap. - return ts - } - heap.Push(&o.commitMark, ts) - if _, has := o.pendingCommits[ts]; has { - panic(fmt.Sprintf("We shouldn't have the commit ts: %d", ts)) - } - o.pendingCommits[ts] = struct{}{} return ts } @@ -151,29 +124,14 @@ func (o *oracle) doneCommit(cts uint64) { // No need to update anything. return } - o.Lock() - defer o.Unlock() - if _, has := o.pendingCommits[cts]; !has { - panic(fmt.Sprintf("We should already have the commit ts: %d", cts)) - } - delete(o.pendingCommits, cts) - - var min uint64 - for len(o.commitMark) > 0 { - ts := o.commitMark[0] - if _, has := o.pendingCommits[ts]; has { - // Still waiting for a txn to commit. - break + for { + curRead := atomic.LoadUint64(&o.curRead) + if cts <= curRead { + return } - min = ts - heap.Pop(&o.commitMark) - } - if min == 0 { - return + atomic.CompareAndSwapUint64(&o.curRead, curRead, cts) } - atomic.StoreUint64(&o.curRead, min) - // nextCommit must never be reset. } // Txn represents a Badger transaction. @@ -191,8 +149,9 @@ type Txn struct { callbacks []func() discarded bool - size int64 - count int64 + size int64 + count int64 + numIterators int32 } type pendingWritesIterator struct { @@ -285,6 +244,9 @@ func (txn *Txn) checkSize(e *Entry) error { // // It will return ErrReadOnlyTxn if update flag was set to false when creating the // transaction. +// +// The current transaction keeps a reference to the key and val byte slice +// arguments. Users must not modify key and val until the end of the transaction. func (txn *Txn) Set(key, val []byte) error { e := &Entry{ Key: key, @@ -294,36 +256,66 @@ func (txn *Txn) Set(key, val []byte) error { } // SetWithMeta adds a key-value pair to the database, along with a metadata -// byte. This byte is stored alongside the key, and can be used as an aid to +// byte. +// +// This byte is stored alongside the key, and can be used as an aid to // interpret the value or store other contextual bits corresponding to the // key-value pair. +// +// The current transaction keeps a reference to the key and val byte slice +// arguments. Users must not modify key and val until the end of the transaction. func (txn *Txn) SetWithMeta(key, val []byte, meta byte) error { e := &Entry{Key: key, Value: val, UserMeta: meta} return txn.SetEntry(e) } +// SetWithDiscard acts like SetWithMeta, but adds a marker to discard earlier +// versions of the key. +// +// This method is only useful if you have set a higher limit for +// options.NumVersionsToKeep. The default setting is 1, in which case, this +// function doesn't add any more benefit than just calling the normal +// SetWithMeta (or Set) function. If however, you have a higher setting for +// NumVersionsToKeep (in Dgraph, we set it to infinity), you can use this method +// to indicate that all the older versions can be discarded and removed during +// compactions. +// +// The current transaction keeps a reference to the key and val byte slice +// arguments. Users must not modify key and val until the end of the +// transaction. +func (txn *Txn) SetWithDiscard(key, val []byte, meta byte) error { + e := &Entry{ + Key: key, + Value: val, + UserMeta: meta, + meta: bitDiscardEarlierVersions, + } + return txn.SetEntry(e) +} + // SetWithTTL adds a key-value pair to the database, along with a time-to-live -// (TTL) setting. A key stored with with a TTL would automatically expire after -// the time has elapsed , and be eligible for garbage collection. +// (TTL) setting. A key stored with a TTL would automatically expire after the +// time has elapsed , and be eligible for garbage collection. +// +// The current transaction keeps a reference to the key and val byte slice +// arguments. Users must not modify key and val until the end of the +// transaction. func (txn *Txn) SetWithTTL(key, val []byte, dur time.Duration) error { expire := time.Now().Add(dur).Unix() e := &Entry{Key: key, Value: val, ExpiresAt: uint64(expire)} return txn.SetEntry(e) } -// SetEntry takes an Entry struct and adds the key-value pair in the struct, along -// with other metadata to the database. -func (txn *Txn) SetEntry(e *Entry) error { - switch { - case !txn.update: +func (txn *Txn) modify(e *Entry) error { + if !txn.update { return ErrReadOnlyTxn - case txn.discarded: + } else if txn.discarded { return ErrDiscardedTxn - case len(e.Key) == 0: + } else if len(e.Key) == 0 { return ErrEmptyKey - case len(e.Key) > maxKeySize: + } else if len(e.Key) > maxKeySize { return exceedsMaxKeySizeError(e.Key) - case int64(len(e.Value)) > txn.db.opt.ValueLogFileSize: + } else if int64(len(e.Value)) > txn.db.opt.ValueLogFileSize { return exceedsMaxValueSizeError(e.Value, txn.db.opt.ValueLogFileSize) } if err := txn.checkSize(e); err != nil { @@ -336,33 +328,29 @@ func (txn *Txn) SetEntry(e *Entry) error { return nil } -// Delete deletes a key. This is done by adding a delete marker for the key at commit timestamp. -// Any reads happening before this timestamp would be unaffected. Any reads after this commit would -// see the deletion. -func (txn *Txn) Delete(key []byte) error { - if !txn.update { - return ErrReadOnlyTxn - } else if txn.discarded { - return ErrDiscardedTxn - } else if len(key) == 0 { - return ErrEmptyKey - } else if len(key) > maxKeySize { - return exceedsMaxKeySizeError(key) - } +// SetEntry takes an Entry struct and adds the key-value pair in the struct, +// along with other metadata to the database. +// +// The current transaction keeps a reference to the entry passed in argument. +// Users must not modify the entry until the end of the transaction. +func (txn *Txn) SetEntry(e *Entry) error { + return txn.modify(e) +} +// Delete deletes a key. +// +// This is done by adding a delete marker for the key at commit timestamp. Any +// reads happening before this timestamp would be unaffected. Any reads after +// this commit would see the deletion. +// +// The current transaction keeps a reference to the key byte slice argument. +// Users must not modify the key until the end of the transaction. +func (txn *Txn) Delete(key []byte) error { e := &Entry{ Key: key, meta: bitDelete, } - if err := txn.checkSize(e); err != nil { - return err - } - - fp := farm.Fingerprint64(key) // Avoid dealing with byte arrays. - txn.writes = append(txn.writes, fp) - - txn.pendingWrites[string(key)] = e - return nil + return txn.modify(e) } // Get looks for key and returns corresponding Item. @@ -387,6 +375,7 @@ func (txn *Txn) Get(key []byte) (item *Item, rerr error) { item.key = key item.status = prefetched item.version = txn.readTs + item.expiresAt = e.ExpiresAt // We probably don't need to set db on item here. return item, nil } @@ -415,9 +404,17 @@ func (txn *Txn) Get(key []byte) (item *Item, rerr error) { item.db = txn.db item.vptr = vs.Value item.txn = txn + item.expiresAt = vs.ExpiresAt return item, nil } +func (txn *Txn) runCallbacks() { + for _, cb := range txn.callbacks { + cb() + } + txn.callbacks = txn.callbacks[:0] +} + // Discard discards a created transaction. This method is very important and must be called. Commit // method calls this internally, however, calling this multiple times doesn't cause any issues. So, // this can safely be called via a defer right when transaction is created. @@ -427,11 +424,12 @@ func (txn *Txn) Discard() { if txn.discarded { // Avoid a re-run. return } - txn.discarded = true - - for _, cb := range txn.callbacks { - cb() + if atomic.LoadInt32(&txn.numIterators) > 0 { + panic("Unclosed iterator at time of Txn.Discard.") } + txn.discarded = true + txn.db.orc.readMark.Done(txn.readTs) + txn.runCallbacks() if txn.update { txn.db.orc.decrRef() } @@ -468,8 +466,10 @@ func (txn *Txn) Commit(callback func(error)) error { } state := txn.db.orc + state.writeLock.Lock() commitTs := state.newCommitTs(txn) if commitTs == 0 { + state.writeLock.Unlock() return ErrConflict } @@ -488,18 +488,30 @@ func (txn *Txn) Commit(callback func(error)) error { } entries = append(entries, e) + req, err := txn.db.sendToWriteCh(entries) + state.writeLock.Unlock() + if err != nil { + return err + } + + // Need to release all locks or writes can get deadlocked. + txn.runCallbacks() + if callback == nil { // If batchSet failed, LSM would not have been updated. So, no need to rollback anything. // TODO: What if some of the txns successfully make it to value log, but others fail. // Nothing gets updated to LSM, until a restart happens. defer state.doneCommit(commitTs) - return txn.db.batchSet(entries) + return req.Wait() } - return txn.db.batchSetAsync(entries, func(err error) { + go func() { + err := req.Wait() + // Write is complete. Let's call the callback function now. state.doneCommit(commitTs) callback(err) - }) + }() + return nil } // NewTransaction creates a new transaction. Badger supports concurrent execution of transactions, @@ -523,6 +535,11 @@ func (txn *Txn) Commit(callback func(error)) error { // defer txn.Discard() // // Call various APIs. func (db *DB) NewTransaction(update bool) *Txn { + if db.opt.ReadOnly && update { + // DB is read-only, force read-only transaction. + update = false + } + txn := &Txn{ update: update, db: db, @@ -530,6 +547,7 @@ func (db *DB) NewTransaction(update bool) *Txn { count: 1, // One extra entry for BitFin. size: int64(len(txnKey) + 10), // Some buffer for the extra entry. } + db.orc.readMark.Begin(txn.readTs) if update { txn.pendingWrites = make(map[string]*Entry) txn.db.orc.addRef() diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go index ff78f847a1..d6b9e56356 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go @@ -45,8 +45,9 @@ import ( // Values have their first byte being byteData or byteDelete. This helps us distinguish between // a key that has never been seen and a key that has been explicitly deleted. const ( - bitDelete byte = 1 << 0 // Set if the key has been deleted. - bitValuePointer byte = 1 << 1 // Set if the value is NOT stored directly next to key. + bitDelete byte = 1 << 0 // Set if the key has been deleted. + bitValuePointer byte = 1 << 1 // Set if the value is NOT stored directly next to key. + bitDiscardEarlierVersions byte = 1 << 2 // Set if earlier versions can be discarded. // The MSB 2 bits are for transactions. bitTxn byte = 1 << 6 // Set if the entry is part of a txn. @@ -175,9 +176,75 @@ func (lf *logFile) sync() error { } var errStop = errors.New("Stop iteration") +var errTruncate = errors.New("Do truncate") type logEntry func(e Entry, vp valuePointer) error +type safeRead struct { + k []byte + v []byte + + recordOffset uint32 +} + +func (r *safeRead) Entry(reader *bufio.Reader) (*Entry, error) { + var hbuf [headerBufSize]byte + var err error + + hash := crc32.New(y.CastagnoliCrcTable) + tee := io.TeeReader(reader, hash) + if _, err = io.ReadFull(tee, hbuf[:]); err != nil { + return nil, err + } + + var h header + h.Decode(hbuf[:]) + if h.klen > maxKeySize { + return nil, errTruncate + } + kl := int(h.klen) + if cap(r.k) < kl { + r.k = make([]byte, 2*kl) + } + vl := int(h.vlen) + if cap(r.v) < vl { + r.v = make([]byte, 2*vl) + } + + e := &Entry{} + e.offset = r.recordOffset + e.Key = r.k[:kl] + e.Value = r.v[:vl] + + if _, err = io.ReadFull(tee, e.Key); err != nil { + if err == io.EOF { + err = errTruncate + } + return nil, err + } + if _, err = io.ReadFull(tee, e.Value); err != nil { + if err == io.EOF { + err = errTruncate + } + return nil, err + } + var crcBuf [4]byte + if _, err = io.ReadFull(reader, crcBuf[:]); err != nil { + if err == io.EOF { + err = errTruncate + } + return nil, err + } + crc := binary.BigEndian.Uint32(crcBuf[:]) + if crc != hash.Sum32() { + return nil, errTruncate + } + e.meta = h.meta + e.UserMeta = h.userMeta + e.ExpiresAt = h.expiresAt + return e, nil +} + // iterate iterates over log file. It doesn't not allocate new memory for every kv pair. // Therefore, the kv pair is only valid for the duration of fn call. func (vlog *valueLog) iterate(lf *logFile, offset uint32, fn logEntry) error { @@ -187,87 +254,69 @@ func (vlog *valueLog) iterate(lf *logFile, offset uint32, fn logEntry) error { } reader := bufio.NewReader(lf.fd) - var hbuf [headerBufSize]byte - var h header - k := make([]byte, 1<<10) - v := make([]byte, 1<<20) + read := &safeRead{ + k: make([]byte, 10), + v: make([]byte, 10), + recordOffset: offset, + } truncate := false - recordOffset := offset + var lastCommit uint64 + var validEndOffset uint32 for { - hash := crc32.New(y.CastagnoliCrcTable) - tee := io.TeeReader(reader, hash) - - // TODO: Move this entry decode into structs.go - if _, err = io.ReadFull(tee, hbuf[:]); err != nil { - if err == io.EOF { - break - } else if err == io.ErrUnexpectedEOF { - truncate = true - break - } - return err - } - - var e Entry - e.offset = recordOffset - h.Decode(hbuf[:]) - if h.klen > maxKeySize { + e, err := read.Entry(reader) + if err == io.EOF { + break + } else if err == io.ErrUnexpectedEOF || err == errTruncate { truncate = true break - } - vl := int(h.vlen) - if cap(v) < vl { - v = make([]byte, 2*vl) + } else if err != nil { + return err + } else if e == nil { + continue } - kl := int(h.klen) - if cap(k) < kl { - k = make([]byte, 2*kl) - } - e.Key = k[:kl] - e.Value = v[:vl] + var vp valuePointer + vp.Len = uint32(headerBufSize + len(e.Key) + len(e.Value) + 4) // len(crcBuf) + read.recordOffset += vp.Len + + vp.Offset = e.offset + vp.Fid = lf.fid - if _, err = io.ReadFull(tee, e.Key); err != nil { - if err == io.EOF || err == io.ErrUnexpectedEOF { + if e.meta&bitTxn > 0 { + txnTs := y.ParseTs(e.Key) + if lastCommit == 0 { + lastCommit = txnTs + } + if lastCommit != txnTs { truncate = true break } - return err - } - if _, err = io.ReadFull(tee, e.Value); err != nil { - if err == io.EOF || err == io.ErrUnexpectedEOF { + + } else if e.meta&bitFinTxn > 0 { + txnTs, err := strconv.ParseUint(string(e.Value), 10, 64) + if err != nil || lastCommit != txnTs { truncate = true break } - return err - } + // Got the end of txn. Now we can store them. + lastCommit = 0 + validEndOffset = read.recordOffset - var crcBuf [4]byte - if _, err = io.ReadFull(reader, crcBuf[:]); err != nil { - if err == io.EOF || err == io.ErrUnexpectedEOF { + } else { + if lastCommit != 0 { + // This is most likely an entry which was moved as part of GC. + // We shouldn't get this entry in the middle of a transaction. truncate = true break } - return err - } - crc := binary.BigEndian.Uint32(crcBuf[:]) - if crc != hash.Sum32() { - truncate = true - break + validEndOffset = read.recordOffset } - e.meta = h.meta - e.UserMeta = h.userMeta - e.ExpiresAt = h.expiresAt - var vp valuePointer - vp.Len = headerBufSize + h.klen + h.vlen + uint32(len(crcBuf)) - recordOffset += vp.Len - - vp.Offset = e.offset - vp.Fid = lf.fid - - if err := fn(e, vp); err != nil { + if vlog.opt.ReadOnly { + return ErrReplayNeeded + } + if err := fn(*e, vp); err != nil { if err == errStop { break } @@ -275,33 +324,32 @@ func (vlog *valueLog) iterate(lf *logFile, offset uint32, fn logEntry) error { } } - if truncate && len(lf.fmap) == 0 { + if vlog.opt.Truncate && truncate && len(lf.fmap) == 0 { // Only truncate if the file isn't mmaped. Otherwise, Windows would puke. - if err := lf.fd.Truncate(int64(recordOffset)); err != nil { + if err := lf.fd.Truncate(int64(validEndOffset)); err != nil { return err } + } else if truncate { + return ErrTruncateNeeded } return nil } -func (vlog *valueLog) rewrite(f *logFile) error { +func (vlog *valueLog) rewrite(f *logFile, tr trace.Trace) error { maxFid := atomic.LoadUint32(&vlog.maxFid) y.AssertTruef(uint32(f.fid) < maxFid, "fid to move: %d. Current max fid: %d", f.fid, maxFid) - - elog := trace.NewEventLog("badger", "vlog-rewrite") - defer elog.Finish() - elog.Printf("Rewriting fid: %d", f.fid) + tr.LazyPrintf("Rewriting fid: %d", f.fid) wb := make([]*Entry, 0, 1000) var size int64 y.AssertTrue(vlog.kv != nil) - var count int + var count, moved int fe := func(e Entry) error { count++ - if count%10000 == 0 { - elog.Printf("Processing entry %d", count) + if count%100000 == 0 { + tr.LazyPrintf("Processing entry %d", count) } vs, err := vlog.kv.get(e.Key) @@ -326,18 +374,28 @@ func (vlog *valueLog) rewrite(f *logFile) error { return nil } if vp.Fid == f.fid && vp.Offset == e.offset { + moved++ // This new entry only contains the key, and a pointer to the value. ne := new(Entry) - ne.meta = 0 // Remove all bits. + ne.meta = 0 // Remove all bits. Different keyspace doesn't need these bits. ne.UserMeta = e.UserMeta - ne.Key = make([]byte, len(e.Key)) - copy(ne.Key, e.Key) - ne.Value = make([]byte, len(e.Value)) - copy(ne.Value, e.Value) + + // Create a new key in a separate keyspace, prefixed by moveKey. We are not + // allowed to rewrite an older version of key in the LSM tree, because then this older + // version would be at the top of the LSM tree. To work correctly, reads expect the + // latest versions to be at the top, and the older versions at the bottom. + if bytes.HasPrefix(e.Key, badgerMove) { + ne.Key = append([]byte{}, e.Key...) + } else { + ne.Key = append([]byte{}, badgerMove...) + ne.Key = append(ne.Key, e.Key...) + } + + ne.Value = append([]byte{}, e.Value...) wb = append(wb, ne) size += int64(e.estimateSize(vlog.opt.ValueThreshold)) if size >= 64*mi { - elog.Printf("request has %d entries, size %d", len(wb), size) + tr.LazyPrintf("request has %d entries, size %d", len(wb), size) if err := vlog.kv.batchSet(wb); err != nil { return err } @@ -357,7 +415,7 @@ func (vlog *valueLog) rewrite(f *logFile) error { return err } - elog.Printf("request has %d entries, size %d", len(wb), size) + tr.LazyPrintf("request has %d entries, size %d", len(wb), size) batchSize := 1024 var loops int for i := 0; i < len(wb); { @@ -374,16 +432,16 @@ func (vlog *valueLog) rewrite(f *logFile) error { if err == ErrTxnTooBig { // Decrease the batch size to half. batchSize = batchSize / 2 - elog.Printf("Dropped batch size to %d", batchSize) + tr.LazyPrintf("Dropped batch size to %d", batchSize) continue } return err } i += batchSize } - elog.Printf("Processed %d entries in %d loops", len(wb), loops) - - elog.Printf("Removing fid: %d", f.fid) + tr.LazyPrintf("Processed %d entries in %d loops", len(wb), loops) + tr.LazyPrintf("Total entries: %d. Moved: %d", count, moved) + tr.LazyPrintf("Removing fid: %d", f.fid) var deleteFileNow bool // Entries written to LSM. Remove the older file now. { @@ -409,6 +467,63 @@ func (vlog *valueLog) rewrite(f *logFile) error { return nil } +func (vlog *valueLog) deleteMoveKeysFor(fid uint32, tr trace.Trace) { + db := vlog.kv + var result []*Entry + var count, pointers uint64 + tr.LazyPrintf("Iterating over move keys to find invalids for fid: %d", fid) + err := db.View(func(txn *Txn) error { + opt := DefaultIteratorOptions + opt.internalAccess = true + opt.PrefetchValues = false + itr := txn.NewIterator(opt) + defer itr.Close() + + for itr.Seek(badgerMove); itr.ValidForPrefix(badgerMove); itr.Next() { + count++ + item := itr.Item() + if item.meta&bitValuePointer == 0 { + continue + } + pointers++ + var vp valuePointer + vp.Decode(item.vptr) + if vp.Fid == fid { + e := &Entry{Key: item.KeyCopy(nil), meta: bitDelete} + result = append(result, e) + } + } + return nil + }) + if err != nil { + tr.LazyPrintf("Got error while iterating move keys: %v", err) + tr.SetError() + return + } + tr.LazyPrintf("Num total move keys: %d. Num pointers: %d", count, pointers) + tr.LazyPrintf("Number of invalid move keys found: %d", len(result)) + batchSize := 10240 + for i := 0; i < len(result); { + end := i + batchSize + if end > len(result) { + end = len(result) + } + if err := db.batchSet(result[i:end]); err != nil { + if err == ErrTxnTooBig { + batchSize /= 2 + tr.LazyPrintf("Dropped batch size to %d", batchSize) + continue + } + tr.LazyPrintf("Error while doing batchSet: %v", err) + tr.SetError() + return + } + i += batchSize + } + tr.LazyPrintf("Move keys deletion done.") + return +} + func (vlog *valueLog) incrIteratorCount() { atomic.AddInt32(&vlog.numActiveIterators, 1) } @@ -470,6 +585,7 @@ type valueLog struct { kv *DB maxFid uint32 writableLogOffset uint32 + numEntriesWritten uint32 opt Options garbageCh chan struct{} @@ -484,7 +600,7 @@ func (vlog *valueLog) fpath(fid uint32) string { return vlogFilePath(vlog.dirPath, fid) } -func (vlog *valueLog) openOrCreateFiles() error { +func (vlog *valueLog) openOrCreateFiles(readOnly bool) error { files, err := ioutil.ReadDir(vlog.dirPath) if err != nil { return errors.Wrapf(err, "Error while opening value log") @@ -519,12 +635,18 @@ func (vlog *valueLog) openOrCreateFiles() error { vlog.maxFid = uint32(maxFid) // Open all previous log files as read only. Open the last log file - // as read write. + // as read write (unless the DB is read only). for fid, lf := range vlog.filesMap { if fid == maxFid { - if lf.fd, err = y.OpenExistingSyncedFile(vlog.fpath(fid), - vlog.opt.SyncWrites); err != nil { - return errors.Wrapf(err, "Unable to open value log file as RDWR") + var flags uint32 + if vlog.opt.SyncWrites { + flags |= y.Sync + } + if readOnly { + flags |= y.ReadOnly + } + if lf.fd, err = y.OpenExistingFile(vlog.fpath(fid), flags); err != nil { + return errors.Wrapf(err, "Unable to open value log file") } } else { if err := lf.openReadOnly(); err != nil { @@ -548,6 +670,7 @@ func (vlog *valueLog) createVlogFile(fid uint32) (*logFile, error) { path := vlog.fpath(fid) lf := &logFile{fid: fid, path: path, loadingMode: vlog.opt.ValueLogLoadingMode} vlog.writableLogOffset = 0 + vlog.numEntriesWritten = 0 var err error if lf.fd, err = y.CreateSyncedFile(path, vlog.opt.SyncWrites); err != nil { @@ -570,7 +693,7 @@ func (vlog *valueLog) Open(kv *DB, opt Options) error { vlog.opt = opt vlog.kv = kv vlog.filesMap = make(map[uint32]*logFile) - if err := vlog.openOrCreateFiles(); err != nil { + if err := vlog.openOrCreateFiles(kv.opt.ReadOnly); err != nil { return errors.Wrapf(err, "Unable to open value log") } @@ -592,7 +715,7 @@ func (vlog *valueLog) Close() error { err = munmapErr } - if id == vlog.maxFid { + if !vlog.opt.ReadOnly && id == vlog.maxFid { // truncate writable log file to correct offset. if truncErr := f.fd.Truncate( int64(vlog.writableLogOffset)); truncErr != nil && err == nil { @@ -667,6 +790,14 @@ type request struct { Err error } +func (req *request) Wait() error { + req.Wg.Wait() + req.Entries = nil + err := req.Err + requestPool.Put(req) + return err +} + // sync is thread-unsafe and should not be called concurrently with write. func (vlog *valueLog) sync() error { if vlog.opt.SyncWrites { @@ -718,7 +849,8 @@ func (vlog *valueLog) write(reqs []*request) error { atomic.AddUint32(&vlog.writableLogOffset, uint32(n)) vlog.buf.Reset() - if vlog.writableOffset() > uint32(vlog.opt.ValueLogFileSize) { + if vlog.writableOffset() > uint32(vlog.opt.ValueLogFileSize) || + vlog.numEntriesWritten > vlog.opt.ValueLogMaxEntries { var err error if err = curlf.doneWriting(vlog.writableLogOffset); err != nil { return err @@ -756,11 +888,16 @@ func (vlog *valueLog) write(reqs []*request) error { } p.Len = uint32(plen) b.Ptrs = append(b.Ptrs, p) - - if p.Offset > uint32(vlog.opt.ValueLogFileSize) { - if err := toDisk(); err != nil { - return err - } + } + vlog.numEntriesWritten += uint32(len(b.Entries)) + // We write to disk here so that all entries that are part of the same transaction are + // written to the same vlog file. + writeNow := + vlog.writableOffset()+uint32(vlog.buf.Len()) > uint32(vlog.opt.ValueLogFileSize) || + vlog.numEntriesWritten > uint32(vlog.opt.ValueLogMaxEntries) + if writeNow { + if err := toDisk(); err != nil { + return err } } } @@ -807,7 +944,7 @@ func (vlog *valueLog) Read(vp valuePointer, s *y.Slice) ([]byte, func(), error) func (vlog *valueLog) readValueBytes(vp valuePointer, s *y.Slice) ([]byte, func(), error) { lf, err := vlog.getFileRLocked(vp.Fid) if err != nil { - return nil, nil, errors.Wrapf(err, "Unable to read from value log: %+v", vp) + return nil, nil, err } buf, err := lf.read(vp, s) @@ -834,11 +971,15 @@ func valueBytesToEntry(buf []byte) (e Entry) { return } -func (vlog *valueLog) pickLog(head valuePointer) *logFile { +func (vlog *valueLog) pickLog(head valuePointer, tr trace.Trace) (files []*logFile) { vlog.filesLock.RLock() defer vlog.filesLock.RUnlock() fids := vlog.sortedFids() - if len(fids) <= 1 || head.Fid == 0 { + if len(fids) <= 1 { + tr.LazyPrintf("Only one or less value log file.") + return nil + } else if head.Fid == 0 { + tr.LazyPrintf("Head pointer is at zero.") return nil } @@ -860,7 +1001,10 @@ func (vlog *valueLog) pickLog(head valuePointer) *logFile { vlog.lfDiscardStats.Unlock() if candidate.fid != math.MaxUint32 { // Found a candidate - return vlog.filesMap[candidate.fid] + tr.LazyPrintf("Found candidate via discard stats: %v", candidate) + files = append(files, vlog.filesMap[candidate.fid]) + } else { + tr.LazyPrintf("Could not find candidate via discard stats. Randomly picking one.") } // Fallback to randomly picking a log file @@ -872,13 +1016,16 @@ func (vlog *valueLog) pickLog(head valuePointer) *logFile { } } if idxHead == 0 { // Not found or first file + tr.LazyPrintf("Could not find any file.") return nil } idx := rand.Intn(idxHead) // Don’t include head.Fid. We pick a random file before it. if idx > 0 { idx = rand.Intn(idx + 1) // Another level of rand to favor smaller fids. } - return vlog.filesMap[fids[idx]] + tr.LazyPrintf("Randomly chose fid: %d", fids[idx]) + files = append(files, vlog.filesMap[fids[idx]]) + return files } func discardEntry(e Entry, vs y.ValueStruct) bool { @@ -900,13 +1047,7 @@ func discardEntry(e Entry, vs y.ValueStruct) bool { return false } -func (vlog *valueLog) doRunGC(gcThreshold float64, head valuePointer) (err error) { - // Pick a log file for GC - lf := vlog.pickLog(head) - if lf == nil { - return ErrNoRewrite - } - +func (vlog *valueLog) doRunGC(lf *logFile, discardRatio float64, tr trace.Trace) (err error) { // Update stats before exiting defer func() { if err == nil { @@ -918,39 +1059,57 @@ func (vlog *valueLog) doRunGC(gcThreshold float64, head valuePointer) (err error type reason struct { total float64 - keep float64 discard float64 + count int } - var r reason - var window = 100.0 - count := 0 + fi, err := lf.fd.Stat() + if err != nil { + tr.LazyPrintf("Error while finding file size: %v", err) + tr.SetError() + return err + } + + // Set up the sampling window sizes. + sizeWindow := float64(fi.Size()) * 0.1 // 10% of the file as window. + countWindow := int(float64(vlog.opt.ValueLogMaxEntries) * 0.01) // 1% of num entries. + tr.LazyPrintf("Size window: %5.2f. Count window: %d.", sizeWindow, countWindow) // Pick a random start point for the log. - skipFirstM := float64(rand.Intn(int(vlog.opt.ValueLogFileSize/mi))) - window + skipFirstM := float64(rand.Int63n(fi.Size())) // Pick a random starting location. + skipFirstM -= sizeWindow // Avoid hitting EOF by moving back by window. + skipFirstM /= float64(mi) // Convert to MBs. + tr.LazyPrintf("Skip first %5.2f MB of file of size: %d MB", skipFirstM, fi.Size()/mi) var skipped float64 + var r reason start := time.Now() y.AssertTrue(vlog.kv != nil) s := new(y.Slice) + var numIterations int err = vlog.iterate(lf, 0, func(e Entry, vp valuePointer) error { - esz := float64(vp.Len) / (1 << 20) // in MBs. +4 for the CAS stuff. - skipped += esz + numIterations++ + esz := float64(vp.Len) / (1 << 20) // in MBs. if skipped < skipFirstM { + skipped += esz return nil } - count++ - if count%100 == 0 { - time.Sleep(time.Millisecond) + // Sample until we reach the window sizes or exceed 10 seconds. + if r.count > countWindow { + tr.LazyPrintf("Stopping sampling after %d entries.", countWindow) + return errStop } - r.total += esz - if r.total > window { + if r.total > sizeWindow { + tr.LazyPrintf("Stopping sampling after reaching window size.") return errStop } if time.Since(start) > 10*time.Second { + tr.LazyPrintf("Stopping sampling after 10 seconds.") return errStop } + r.total += esz + r.count++ vs, err := vlog.kv.get(e.Key) if err != nil { @@ -977,7 +1136,6 @@ func (vlog *valueLog) doRunGC(gcThreshold float64, head valuePointer) (err error } if vp.Fid == lf.fid && vp.Offset == e.offset { // This is still the active entry. This would need to be rewritten. - r.keep += esz } else { vlog.elog.Printf("Reason=%+v\n", r) @@ -998,21 +1156,23 @@ func (vlog *valueLog) doRunGC(gcThreshold float64, head valuePointer) (err error }) if err != nil { - vlog.elog.Errorf("Error while iterating for RunGC: %v", err) + tr.LazyPrintf("Error while iterating for RunGC: %v", err) + tr.SetError() return err } - vlog.elog.Printf("Fid: %d Data status=%+v\n", lf.fid, r) + tr.LazyPrintf("Fid: %d. Skipped: %5.2fMB Num iterations: %d. Data status=%+v\n", + lf.fid, skipped, numIterations, r) - if r.total < 10.0 || r.discard < gcThreshold*r.total { - vlog.elog.Printf("Skipping GC on fid: %d\n\n", lf.fid) + // If we couldn't sample at least a 1000 KV pairs or at least 75% of the window size, + // and what we can discard is below the threshold, we should skip the rewrite. + if (r.count < countWindow && r.total < sizeWindow*0.75) || r.discard < discardRatio*r.total { + tr.LazyPrintf("Skipping GC on fid: %d", lf.fid) return ErrNoRewrite } - - vlog.elog.Printf("REWRITING VLOG %d\n", lf.fid) - if err = vlog.rewrite(lf); err != nil { + if err = vlog.rewrite(lf, tr); err != nil { return err } - vlog.elog.Printf("Done rewriting.") + tr.LazyPrintf("Done rewriting.") return nil } @@ -1026,24 +1186,34 @@ func (vlog *valueLog) waitOnGC(lc *y.Closer) { vlog.garbageCh <- struct{}{} } -func (vlog *valueLog) runGC(gcThreshold float64, head valuePointer) error { +func (vlog *valueLog) runGC(discardRatio float64, head valuePointer) error { select { case vlog.garbageCh <- struct{}{}: - // Run GC - var ( - err error - count int - ) - for { - err = vlog.doRunGC(gcThreshold, head) - if err != nil { - break - } - count++ + // Pick a log file for GC. + tr := trace.New("Badger.ValueLog", "GC") + tr.SetMaxEvents(100) + defer func() { + tr.Finish() + <-vlog.garbageCh + }() + + var err error + files := vlog.pickLog(head, tr) + if len(files) == 0 { + tr.LazyPrintf("PickLog returned zero results.") + return ErrNoRewrite } - <-vlog.garbageCh - if err == ErrNoRewrite && count > 0 { - return nil + tried := make(map[uint32]bool) + for _, lf := range files { + if _, done := tried[lf.fid]; done { + continue + } + tried[lf.fid] = true + err = vlog.doRunGC(lf, discardRatio, tr) + if err == nil { + vlog.deleteMoveKeysFor(lf.fid, tr) + return nil + } } return err default: @@ -1051,12 +1221,10 @@ func (vlog *valueLog) runGC(gcThreshold float64, head valuePointer) error { } } -func (vlog *valueLog) updateGCStats(item *Item) { - if item.meta&bitValuePointer > 0 { - var vp valuePointer - vp.Decode(item.vptr) - vlog.lfDiscardStats.Lock() - vlog.lfDiscardStats.m[vp.Fid] += int64(vp.Len) - vlog.lfDiscardStats.Unlock() +func (vlog *valueLog) updateGCStats(stats map[uint32]int64) { + vlog.lfDiscardStats.Lock() + for fid, sz := range stats { + vlog.lfDiscardStats.m[fid] += sz } + vlog.lfDiscardStats.Unlock() } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/vendor/github.com/golang/protobuf/proto/proto3_test.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/vendor/github.com/golang/protobuf/proto/proto3_test.go deleted file mode 100644 index 73eed6c0b5..0000000000 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/vendor/github.com/golang/protobuf/proto/proto3_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2014 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// 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. - -package proto_test - -import ( - "bytes" - "testing" - - "github.com/golang/protobuf/proto" - pb "github.com/golang/protobuf/proto/proto3_proto" - tpb "github.com/golang/protobuf/proto/test_proto" -) - -func TestProto3ZeroValues(t *testing.T) { - tests := []struct { - desc string - m proto.Message - }{ - {"zero message", &pb.Message{}}, - {"empty bytes field", &pb.Message{Data: []byte{}}}, - } - for _, test := range tests { - b, err := proto.Marshal(test.m) - if err != nil { - t.Errorf("%s: proto.Marshal: %v", test.desc, err) - continue - } - if len(b) > 0 { - t.Errorf("%s: Encoding is non-empty: %q", test.desc, b) - } - } -} - -func TestRoundTripProto3(t *testing.T) { - m := &pb.Message{ - Name: "David", // (2 | 1<<3): 0x0a 0x05 "David" - Hilarity: pb.Message_PUNS, // (0 | 2<<3): 0x10 0x01 - HeightInCm: 178, // (0 | 3<<3): 0x18 0xb2 0x01 - Data: []byte("roboto"), // (2 | 4<<3): 0x20 0x06 "roboto" - ResultCount: 47, // (0 | 7<<3): 0x38 0x2f - TrueScotsman: true, // (0 | 8<<3): 0x40 0x01 - Score: 8.1, // (5 | 9<<3): 0x4d <8.1> - - Key: []uint64{1, 0xdeadbeef}, - Nested: &pb.Nested{ - Bunny: "Monty", - }, - } - t.Logf(" m: %v", m) - - b, err := proto.Marshal(m) - if err != nil { - t.Fatalf("proto.Marshal: %v", err) - } - t.Logf(" b: %q", b) - - m2 := new(pb.Message) - if err := proto.Unmarshal(b, m2); err != nil { - t.Fatalf("proto.Unmarshal: %v", err) - } - t.Logf("m2: %v", m2) - - if !proto.Equal(m, m2) { - t.Errorf("proto.Equal returned false:\n m: %v\nm2: %v", m, m2) - } -} - -func TestGettersForBasicTypesExist(t *testing.T) { - var m pb.Message - if got := m.GetNested().GetBunny(); got != "" { - t.Errorf("m.GetNested().GetBunny() = %q, want empty string", got) - } - if got := m.GetNested().GetCute(); got { - t.Errorf("m.GetNested().GetCute() = %t, want false", got) - } -} - -func TestProto3SetDefaults(t *testing.T) { - in := &pb.Message{ - Terrain: map[string]*pb.Nested{ - "meadow": new(pb.Nested), - }, - Proto2Field: new(tpb.SubDefaults), - Proto2Value: map[string]*tpb.SubDefaults{ - "badlands": new(tpb.SubDefaults), - }, - } - - got := proto.Clone(in).(*pb.Message) - proto.SetDefaults(got) - - // There are no defaults in proto3. Everything should be the zero value, but - // we need to remember to set defaults for nested proto2 messages. - want := &pb.Message{ - Terrain: map[string]*pb.Nested{ - "meadow": new(pb.Nested), - }, - Proto2Field: &tpb.SubDefaults{N: proto.Int64(7)}, - Proto2Value: map[string]*tpb.SubDefaults{ - "badlands": &tpb.SubDefaults{N: proto.Int64(7)}, - }, - } - - if !proto.Equal(got, want) { - t.Errorf("with in = %v\nproto.SetDefaults(in) =>\ngot %v\nwant %v", in, got, want) - } -} - -func TestUnknownFieldPreservation(t *testing.T) { - b1 := "\x0a\x05David" // Known tag 1 - b2 := "\xc2\x0c\x06Google" // Unknown tag 200 - b := []byte(b1 + b2) - - m := new(pb.Message) - if err := proto.Unmarshal(b, m); err != nil { - t.Fatalf("proto.Unmarshal: %v", err) - } - - if !bytes.Equal(m.XXX_unrecognized, []byte(b2)) { - t.Fatalf("mismatching unknown fields:\ngot %q\nwant %q", m.XXX_unrecognized, b2) - } -} diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_dsync.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_dsync.go index bd25c9e9a3..3f3445e2e9 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_dsync.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_dsync.go @@ -1,4 +1,4 @@ -// +build !dragonfly,!freebsd,!windows,!darwin +// +build !dragonfly,!freebsd,!windows /* * Copyright 2017 Dgraph Labs, Inc. and Contributors @@ -18,8 +18,8 @@ package y -import "syscall" +import "golang.org/x/sys/unix" func init() { - datasyncFileFlag = syscall.O_DSYNC + datasyncFileFlag = unix.O_DSYNC } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_nodsync.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_nodsync.go index be295f2e02..b68be7ab94 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_nodsync.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_nodsync.go @@ -1,4 +1,4 @@ -// +build dragonfly freebsd windows darwin +// +build dragonfly freebsd windows /* * Copyright 2017 Dgraph Labs, Inc. and Contributors diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/watermark.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/watermark.go new file mode 100644 index 0000000000..6fd3c89968 --- /dev/null +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/watermark.go @@ -0,0 +1,130 @@ +/* + * Copyright 2018 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package y + +import ( + "container/heap" + "sync/atomic" + + "golang.org/x/net/trace" +) + +type uint64Heap []uint64 + +func (u uint64Heap) Len() int { return len(u) } +func (u uint64Heap) Less(i int, j int) bool { return u[i] < u[j] } +func (u uint64Heap) Swap(i int, j int) { u[i], u[j] = u[j], u[i] } +func (u *uint64Heap) Push(x interface{}) { *u = append(*u, x.(uint64)) } +func (u *uint64Heap) Pop() interface{} { + old := *u + n := len(old) + x := old[n-1] + *u = old[0 : n-1] + return x +} + +type mark struct { + readTs uint64 + done bool // Set to true if the pending mutation is done. +} +type WaterMark struct { + markCh chan mark + minReadTs uint64 + elog trace.EventLog +} + +// Init initializes a WaterMark struct. MUST be called before using it. +func (w *WaterMark) Init() { + w.markCh = make(chan mark, 1000) + w.elog = trace.NewEventLog("Badger", "MinReadTs") + go w.process() +} + +func (w *WaterMark) Begin(readTs uint64) { + w.markCh <- mark{readTs: readTs, done: false} +} +func (w *WaterMark) Done(readTs uint64) { + w.markCh <- mark{readTs: readTs, done: true} +} + +// DoneUntil returns the maximum index until which all tasks are done. +func (w *WaterMark) MinReadTs() uint64 { + return atomic.LoadUint64(&w.minReadTs) +} + +// process is used to process the Mark channel. This is not thread-safe, +// so only run one goroutine for process. One is sufficient, because +// all ops in the goroutine use only memory and cpu. +func (w *WaterMark) process() { + var reads uint64Heap + // pending maps raft proposal index to the number of pending mutations for this proposal. + pending := make(map[uint64]int) + + heap.Init(&reads) + var loop uint64 + + processOne := func(readTs uint64, done bool) { + // If not already done, then set. Otherwise, don't undo a done entry. + prev, present := pending[readTs] + if !present { + heap.Push(&reads, readTs) + } + + delta := 1 + if done { + delta = -1 + } + pending[readTs] = prev + delta + + loop++ + if len(reads) > 0 && loop%1000 == 0 { + min := reads[0] + w.elog.Printf("ReadTs: %4d. Size: %4d MinReadTs: %-4d Looking for: %-4d. Value: %d\n", + readTs, len(reads), w.MinReadTs(), min, pending[min]) + } + + // Update mark by going through all reads in order; and checking if they have + // been done. Stop at the first readTs, which isn't done. + minReadTs := w.MinReadTs() + // Don't assert that minReadTs < readTs, to avoid any inconsistencies caused by managed + // transactions, or testing where we explicitly set the readTs for transactions like in + // TestTxnVersions. + until := minReadTs + loops := 0 + + for len(reads) > 0 { + min := reads[0] + if done := pending[min]; done != 0 { + break // len(reads) will be > 0. + } + heap.Pop(&reads) + delete(pending, min) + until = min + loops++ + } + if until != minReadTs { + AssertTrue(atomic.CompareAndSwapUint64(&w.minReadTs, minReadTs, until)) + w.elog.Printf("MinReadTs: %d. Loops: %d\n", until, loops) + } + } + + for mark := range w.markCh { + if mark.readTs > 0 { + processOne(mark.readTs, mark.done) + } + } +} diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/y.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/y.go index 20a8ea55e4..5927fd3836 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/y.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/y.go @@ -31,6 +31,15 @@ import ( // and encountering the end of slice. var ErrEOF = errors.New("End of mapped region") +const ( + // Sync indicates that O_DSYNC should be set on the underlying file, + // ensuring that data writes do not return until the data is flushed + // to disk. + Sync = 1 << iota + // ReadOnly opens the underlying file on a read-only basis. + ReadOnly +) + var ( // This is O_DSYNC (datasync) on platforms that support it -- see file_unix.go datasyncFileFlag = 0x0 @@ -39,13 +48,17 @@ var ( CastagnoliCrcTable = crc32.MakeTable(crc32.Castagnoli) ) -// OpenExistingSyncedFile opens an existing file, errors if it doesn't exist. -func OpenExistingSyncedFile(filename string, sync bool) (*os.File, error) { - flags := os.O_RDWR - if sync { - flags |= datasyncFileFlag +// OpenExistingFile opens an existing file, errors if it doesn't exist. +func OpenExistingFile(filename string, flags uint32) (*os.File, error) { + openFlags := os.O_RDWR + if flags&ReadOnly != 0 { + openFlags = os.O_RDONLY + } + + if flags&Sync != 0 { + openFlags |= datasyncFileFlag } - return os.OpenFile(filename, flags, 0) + return os.OpenFile(filename, openFlags, 0) } // CreateSyncedFile creates a new file (using O_EXCL), errors if it already existed. diff --git a/sessions/sessiondb/boltdb/database.go b/sessions/sessiondb/boltdb/database.go index fe1c90feb0..db0ef827ea 100644 --- a/sessions/sessiondb/boltdb/database.go +++ b/sessions/sessiondb/boltdb/database.go @@ -6,7 +6,7 @@ import ( "runtime" "time" - "github.com/coreos/bbolt" + "github.com/etcd-io/bbolt" "github.com/kataras/golog" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" @@ -25,7 +25,7 @@ type Database struct { // Service is the underline BoltDB database connection, // it's initialized at `New` or `NewFromDB`. // Can be used to get stats. - Service *bolt.DB + Service *bbolt.DB } var errPathMissing = errors.New("path is required") @@ -51,8 +51,8 @@ func New(path string, fileMode os.FileMode) (*Database, error) { return nil, err } - service, err := bolt.Open(path, fileMode, - &bolt.Options{Timeout: 20 * time.Second}, + service, err := bbolt.Open(path, fileMode, + &bbolt.Options{Timeout: 20 * time.Second}, ) if err != nil { @@ -64,10 +64,10 @@ func New(path string, fileMode os.FileMode) (*Database, error) { } // NewFromDB same as `New` but accepts an already-created custom boltdb connection instead. -func NewFromDB(service *bolt.DB, bucketName string) (*Database, error) { +func NewFromDB(service *bbolt.DB, bucketName string) (*Database, error) { bucket := []byte(bucketName) - service.Update(func(tx *bolt.Tx) (err error) { + service.Update(func(tx *bbolt.Tx) (err error) { _, err = tx.CreateBucketIfNotExists(bucket) return }) @@ -78,15 +78,15 @@ func NewFromDB(service *bolt.DB, bucketName string) (*Database, error) { return db, db.cleanup() } -func (db *Database) getBucket(tx *bolt.Tx) *bolt.Bucket { +func (db *Database) getBucket(tx *bbolt.Tx) *bbolt.Bucket { return tx.Bucket(db.table) } -func (db *Database) getBucketForSession(tx *bolt.Tx, sid string) *bolt.Bucket { +func (db *Database) getBucketForSession(tx *bbolt.Tx, sid string) *bbolt.Bucket { b := db.getBucket(tx).Bucket([]byte(sid)) if b == nil { // session does not exist, it shouldn't happen, session bucket creation happens once at `Acquire`, - // no need to accept the `bolt.bucket.CreateBucketIfNotExists`'s performance cost. + // no need to accept the `bbolt.bucket.CreateBucketIfNotExists`'s performance cost. golog.Debugf("unreachable session access for '%s'", sid) } @@ -105,7 +105,7 @@ func getExpirationBucketName(bsid []byte) []byte { // Cleanup removes any invalid(have expired) session entries on initialization. func (db *Database) cleanup() error { - return db.Service.Update(func(tx *bolt.Tx) error { + return db.Service.Update(func(tx *bbolt.Tx) error { b := db.getBucket(tx) c := b.Cursor() // loop through all buckets, find one with expiration. @@ -151,7 +151,7 @@ var expirationKey = []byte("exp") // it can be random. // if the return value is LifeTime{} then the session manager sets the life time based on the expiration duration lives in configuration. func (db *Database) Acquire(sid string, expires time.Duration) (lifetime sessions.LifeTime) { bsid := []byte(sid) - err := db.Service.Update(func(tx *bolt.Tx) (err error) { + err := db.Service.Update(func(tx *bbolt.Tx) (err error) { root := db.getBucket(tx) if expires > 0 { // should check or create the expiration bucket. @@ -218,7 +218,7 @@ func (db *Database) OnUpdateExpiration(sid string, newExpires time.Duration) err return err } - err = db.Service.Update(func(tx *bolt.Tx) error { + err = db.Service.Update(func(tx *bbolt.Tx) error { expirationName := getExpirationBucketName([]byte(sid)) root := db.getBucket(tx) b := root.Bucket(expirationName) @@ -250,7 +250,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu return } - err = db.Service.Update(func(tx *bolt.Tx) error { + err = db.Service.Update(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -270,7 +270,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu // Get retrieves a session value based on the key. func (db *Database) Get(sid string, key string) (value interface{}) { - err := db.Service.View(func(tx *bolt.Tx) error { + err := db.Service.View(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -293,7 +293,7 @@ func (db *Database) Get(sid string, key string) (value interface{}) { // Visit loops through all session keys and values. func (db *Database) Visit(sid string, cb func(key string, value interface{})) { - db.Service.View(func(tx *bolt.Tx) error { + db.Service.View(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -314,7 +314,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) { // Len returns the length of the session's entries (keys). func (db *Database) Len(sid string) (n int) { - db.Service.View(func(tx *bolt.Tx) error { + db.Service.View(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -329,7 +329,7 @@ func (db *Database) Len(sid string) (n int) { // Delete removes a session key value based on its key. func (db *Database) Delete(sid string, key string) (deleted bool) { - err := db.Service.Update(func(tx *bolt.Tx) error { + err := db.Service.Update(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return sessions.ErrNotFound @@ -343,7 +343,7 @@ func (db *Database) Delete(sid string, key string) (deleted bool) { // Clear removes all session key values but it keeps the session entry. func (db *Database) Clear(sid string) { - db.Service.Update(func(tx *bolt.Tx) error { + db.Service.Update(func(tx *bbolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -358,7 +358,7 @@ func (db *Database) Clear(sid string) { // Release destroys the session, it clears and removes the session entry, // session manager will create a new session ID on the next request after this call. func (db *Database) Release(sid string) { - db.Service.Update(func(tx *bolt.Tx) error { + db.Service.Update(func(tx *bbolt.Tx) error { // delete the session bucket. b := db.getBucket(tx) bsid := []byte(sid) diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/LICENSE b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/LICENSE similarity index 100% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/LICENSE rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/LICENSE diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_386.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_386.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_386.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_386.go index 820d533c15..4d35ee7cf3 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_386.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_386.go @@ -1,4 +1,4 @@ -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0x7FFFFFFF // 2GB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_amd64.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_amd64.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_amd64.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_amd64.go index 98fafdb47d..60a52dad56 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_amd64.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_amd64.go @@ -1,4 +1,4 @@ -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm.go similarity index 98% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm.go index 7e5cb4b941..105d27ddb7 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import "unsafe" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm64.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm64.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm64.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm64.go index b26d84f91b..f5aa2a5ee2 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm64.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm64.go @@ -1,6 +1,6 @@ // +build arm64 -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_linux.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_linux.go similarity index 91% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_linux.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_linux.go index 2b67666140..7707bcacf0 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_linux.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_linux.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "syscall" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mips64x.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mips64x.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mips64x.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mips64x.go index 134b578bd4..baeb289fd9 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mips64x.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mips64x.go @@ -1,6 +1,6 @@ // +build mips64 mips64le -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0x8000000000 // 512GB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mipsx.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mipsx.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mipsx.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mipsx.go index d5ecb0597e..2d9b1a91f3 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mipsx.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mipsx.go @@ -1,6 +1,6 @@ // +build mips mipsle -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0x40000000 // 1GB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_openbsd.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_openbsd.go similarity index 97% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_openbsd.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_openbsd.go index 7058c3d734..d7f50358ef 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_openbsd.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_openbsd.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "syscall" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc.go index 55cb8a72cc..69804714aa 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc.go @@ -1,6 +1,6 @@ // +build ppc -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0x7FFFFFFF // 2GB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64.go index 9331d9771e..3565908576 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64.go @@ -1,6 +1,6 @@ // +build ppc64 -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64le.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64le.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64le.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64le.go index 8c143bc5d1..422c7c69d6 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64le.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64le.go @@ -1,6 +1,6 @@ // +build ppc64le -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_s390x.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_s390x.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_s390x.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_s390x.go index d7c39af925..6d3fcb825d 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_s390x.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_s390x.go @@ -1,6 +1,6 @@ // +build s390x -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix.go index add3bd8823..38e11d4c4d 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix.go @@ -1,6 +1,6 @@ // +build !windows,!plan9,!solaris -package bolt +package bbolt import ( "fmt" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix_solaris.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix_solaris.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix_solaris.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix_solaris.go index fd8335ecc9..492eaf302c 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix_solaris.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix_solaris.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "fmt" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_windows.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_windows.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_windows.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_windows.go index ca6f9a11c2..4e3f90fb43 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_windows.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_windows.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "fmt" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/boltsync_unix.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/boltsync_unix.go similarity index 91% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/boltsync_unix.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/boltsync_unix.go index f50442523c..9587afefee 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/boltsync_unix.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/boltsync_unix.go @@ -1,6 +1,6 @@ // +build !windows,!plan9,!linux,!openbsd -package bolt +package bbolt // fdatasync flushes written data to a file descriptor. func fdatasync(db *DB) error { diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bucket.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bucket.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bucket.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bucket.go index 44db88b8ab..84bfd4d6a2 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bucket.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bucket.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "bytes" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/cursor.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/cursor.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/cursor.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/cursor.go index 1bdda63a2f..3000aced6c 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/cursor.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/cursor.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "bytes" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/db.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/db.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/db.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/db.go index ab97c6014a..575054032f 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/db.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/db.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "errors" @@ -147,7 +147,7 @@ func (db *DB) Path() string { // GoString returns the Go string representation of the database. func (db *DB) GoString() string { - return fmt.Sprintf("bolt.DB{path:%q}", db.path) + return fmt.Sprintf("bbolt.DB{path:%q}", db.path) } // String returns the string representation of the database. @@ -454,8 +454,8 @@ func (db *DB) Close() error { db.metalock.Lock() defer db.metalock.Unlock() - db.mmaplock.RLock() - defer db.mmaplock.RUnlock() + db.mmaplock.Lock() + defer db.mmaplock.Unlock() return db.close() } @@ -483,7 +483,7 @@ func (db *DB) close() error { if !db.readOnly { // Unlock the file. if err := funlock(db); err != nil { - log.Printf("bolt.Close(): funlock error: %s", err) + log.Printf("bbolt.Close(): funlock error: %s", err) } } @@ -890,7 +890,7 @@ func (db *DB) meta() *meta { // This should never be reached, because both meta1 and meta0 were validated // on mmap() and we do fsync() on every write. - panic("bolt.DB.meta(): invalid meta pages") + panic("bbolt.DB.meta(): invalid meta pages") } // allocate returns a contiguous block of memory starting at a given page. diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/errors.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/errors.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/errors.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/errors.go index a3620a3ebb..48758ca577 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/errors.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/errors.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import "errors" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/freelist.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/freelist.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/freelist.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/freelist.go index 266f154294..e4bcb2dcf9 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/freelist.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/freelist.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "fmt" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/node.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/node.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/node.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/node.go index f4ce240edd..6c3fa553ea 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/node.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/node.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "bytes" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/page.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/page.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/page.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/page.go index cde403ae86..bca9615f0f 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/page.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/page.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "fmt" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/tx.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/tx.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/tx.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/tx.go index 41a9bc619a..f508641427 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/tx.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/tx.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "fmt" From 9acfe30d3385dea09575b3aca16d6603a01990b1 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 31 Aug 2018 02:16:23 +0300 Subject: [PATCH 10/91] remove unnecessary deps --- Gopkg.lock | 6 - Gopkg.toml | 4 - vendor/github.com/kataras/survey/LICENSE | 22 - vendor/github.com/kataras/survey/confirm.go | 138 --- .../kataras/survey/core/renderer.go | 62 -- .../kataras/survey/core/template.go | 83 -- .../github.com/kataras/survey/core/write.go | 244 ----- .../kataras/survey/core/write_test.go | 543 ---------- vendor/github.com/kataras/survey/editor.go | 168 --- vendor/github.com/kataras/survey/input.go | 98 -- .../github.com/kataras/survey/multiselect.go | 203 ---- vendor/github.com/kataras/survey/password.go | 84 -- vendor/github.com/kataras/survey/select.go | 209 ---- vendor/github.com/kataras/survey/survey.go | 198 ---- .../kataras/survey/terminal/cursor.go | 134 --- .../kataras/survey/terminal/cursor_windows.go | 101 -- .../kataras/survey/terminal/display.go | 9 - .../kataras/survey/terminal/display_posix.go | 11 - .../survey/terminal/display_windows.go | 28 - .../kataras/survey/terminal/error.go | 9 - .../kataras/survey/terminal/output.go | 20 - .../kataras/survey/terminal/output_windows.go | 228 ----- .../kataras/survey/terminal/print.go | 25 - .../kataras/survey/terminal/runereader.go | 183 ---- .../kataras/survey/terminal/runereader_bsd.go | 13 - .../survey/terminal/runereader_linux.go | 12 - .../survey/terminal/runereader_posix.go | 84 -- .../survey/terminal/runereader_windows.go | 130 --- .../kataras/survey/terminal/sequences.go | 18 - .../survey/terminal/syscall_windows.go | 39 - .../kataras/survey/terminal/terminal.go | 8 - vendor/github.com/kataras/survey/transform.go | 76 -- vendor/github.com/kataras/survey/validate.go | 92 -- .../github.com/mattn/go-colorable/LICENSE | 21 - .../mattn/go-colorable/colorable_appengine.go | 29 - .../mattn/go-colorable/colorable_others.go | 30 - .../mattn/go-colorable/colorable_windows.go | 968 ------------------ .../mattn/go-colorable/noncolorable.go | 55 - .../vendor/github.com/mattn/go-isatty/LICENSE | 9 - .../vendor/github.com/mattn/go-isatty/doc.go | 2 - .../mattn/go-isatty/isatty_appengine.go | 15 - .../github.com/mattn/go-isatty/isatty_bsd.go | 18 - .../mattn/go-isatty/isatty_linux.go | 18 - .../mattn/go-isatty/isatty_linux_ppc64x.go | 19 - .../mattn/go-isatty/isatty_others.go | 10 - .../mattn/go-isatty/isatty_solaris.go | 16 - .../mattn/go-isatty/isatty_windows.go | 94 -- .../vendor/github.com/mgutz/ansi/LICENSE | 9 - .../vendor/github.com/mgutz/ansi/ansi.go | 285 ------ .../vendor/github.com/mgutz/ansi/doc.go | 65 -- .../vendor/github.com/mgutz/ansi/print.go | 57 -- vendor/github.com/satori/go.uuid/LICENSE | 20 - vendor/github.com/satori/go.uuid/codec.go | 206 ---- vendor/github.com/satori/go.uuid/generator.go | 265 ----- vendor/github.com/satori/go.uuid/sql.go | 78 -- vendor/github.com/satori/go.uuid/uuid.go | 161 --- 56 files changed, 5732 deletions(-) delete mode 100644 vendor/github.com/kataras/survey/LICENSE delete mode 100644 vendor/github.com/kataras/survey/confirm.go delete mode 100644 vendor/github.com/kataras/survey/core/renderer.go delete mode 100644 vendor/github.com/kataras/survey/core/template.go delete mode 100644 vendor/github.com/kataras/survey/core/write.go delete mode 100644 vendor/github.com/kataras/survey/core/write_test.go delete mode 100644 vendor/github.com/kataras/survey/editor.go delete mode 100644 vendor/github.com/kataras/survey/input.go delete mode 100644 vendor/github.com/kataras/survey/multiselect.go delete mode 100644 vendor/github.com/kataras/survey/password.go delete mode 100644 vendor/github.com/kataras/survey/select.go delete mode 100644 vendor/github.com/kataras/survey/survey.go delete mode 100644 vendor/github.com/kataras/survey/terminal/cursor.go delete mode 100644 vendor/github.com/kataras/survey/terminal/cursor_windows.go delete mode 100644 vendor/github.com/kataras/survey/terminal/display.go delete mode 100644 vendor/github.com/kataras/survey/terminal/display_posix.go delete mode 100644 vendor/github.com/kataras/survey/terminal/display_windows.go delete mode 100644 vendor/github.com/kataras/survey/terminal/error.go delete mode 100644 vendor/github.com/kataras/survey/terminal/output.go delete mode 100644 vendor/github.com/kataras/survey/terminal/output_windows.go delete mode 100644 vendor/github.com/kataras/survey/terminal/print.go delete mode 100644 vendor/github.com/kataras/survey/terminal/runereader.go delete mode 100644 vendor/github.com/kataras/survey/terminal/runereader_bsd.go delete mode 100644 vendor/github.com/kataras/survey/terminal/runereader_linux.go delete mode 100644 vendor/github.com/kataras/survey/terminal/runereader_posix.go delete mode 100644 vendor/github.com/kataras/survey/terminal/runereader_windows.go delete mode 100644 vendor/github.com/kataras/survey/terminal/sequences.go delete mode 100644 vendor/github.com/kataras/survey/terminal/syscall_windows.go delete mode 100644 vendor/github.com/kataras/survey/terminal/terminal.go delete mode 100644 vendor/github.com/kataras/survey/transform.go delete mode 100644 vendor/github.com/kataras/survey/validate.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/LICENSE delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_appengine.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_others.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_windows.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/noncolorable.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/LICENSE delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/doc.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_appengine.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_bsd.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_others.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_solaris.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_windows.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/LICENSE delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/ansi.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/doc.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/print.go delete mode 100644 vendor/github.com/satori/go.uuid/LICENSE delete mode 100644 vendor/github.com/satori/go.uuid/codec.go delete mode 100644 vendor/github.com/satori/go.uuid/generator.go delete mode 100644 vendor/github.com/satori/go.uuid/sql.go delete mode 100644 vendor/github.com/satori/go.uuid/uuid.go diff --git a/Gopkg.lock b/Gopkg.lock index 8afa348503..d392261b51 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -115,12 +115,6 @@ packages = [".","terminal"] revision = "825e39f34365e7db2c9fbc3692c16220e3bd7418" -[[projects]] - branch = "v2" - name = "github.com/kataras/survey" - packages = [".","terminal","core"] - revision = "00934ae069eda15df26fa427ac393e67e239380c" - [[projects]] name = "github.com/klauspost/compress" packages = ["flate","gzip"] diff --git a/Gopkg.toml b/Gopkg.toml index eb9c030df1..b0efe84658 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -42,10 +42,6 @@ branch = "master" name = "github.com/kataras/golog" -[[constraint]] - branch = "v2" - name = "github.com/kataras/survey" - [[constraint]] name = "github.com/klauspost/compress" version = "1.2.1" diff --git a/vendor/github.com/kataras/survey/LICENSE b/vendor/github.com/kataras/survey/LICENSE deleted file mode 100644 index f8ef08748a..0000000000 --- a/vendor/github.com/kataras/survey/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2016 Alec Aivazis -Copyright (c) 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. diff --git a/vendor/github.com/kataras/survey/confirm.go b/vendor/github.com/kataras/survey/confirm.go deleted file mode 100644 index d1ef6f3a25..0000000000 --- a/vendor/github.com/kataras/survey/confirm.go +++ /dev/null @@ -1,138 +0,0 @@ -package survey - -import ( - "fmt" - "os" - "regexp" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -// Confirm is a regular text input that accept yes/no answers. Response type is a bool. -type Confirm struct { - core.Renderer - Message string - Default bool - Help string -} - -// data available to the templates when processing -type ConfirmTemplateData struct { - Confirm - Answer string - ShowHelp bool -} - -// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format -var ConfirmQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if .Answer}} - {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}} - {{- color "white"}}{{if .Default}}(Y/n) {{else}}(y/N) {{end}}{{color "reset"}} -{{- end}}` - -// the regex for answers -var ( - yesRx = regexp.MustCompile("^(?i:y(?:es)?)$") - noRx = regexp.MustCompile("^(?i:n(?:o)?)$") -) - -func yesNo(t bool) string { - if t { - return "Yes" - } - return "No" -} - -func (c *Confirm) getBool(showHelp bool) (bool, error) { - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - // start waiting for input - for { - line, err := rr.ReadLine(0) - if err != nil { - return false, err - } - // move back up a line to compensate for the \n echoed from terminal - terminal.CursorPreviousLine(1) - val := string(line) - - // get the answer that matches the - var answer bool - switch { - case yesRx.Match([]byte(val)): - answer = true - case noRx.Match([]byte(val)): - answer = false - case val == "": - answer = c.Default - case val == string(core.HelpInputRune) && c.Help != "": - err := c.Render( - ConfirmQuestionTemplate, - ConfirmTemplateData{Confirm: *c, ShowHelp: true}, - ) - if err != nil { - // use the default value and bubble up - return c.Default, err - } - showHelp = true - continue - default: - // we didnt get a valid answer, so print error and prompt again - if err := c.Error(fmt.Errorf("%q is not a valid answer, please try again.", val)); err != nil { - return c.Default, err - } - err := c.Render( - ConfirmQuestionTemplate, - ConfirmTemplateData{Confirm: *c, ShowHelp: showHelp}, - ) - if err != nil { - // use the default value and bubble up - return c.Default, err - } - continue - } - return answer, nil - } - // should not get here - return c.Default, nil -} - -/* -Prompt prompts the user with a simple text field and expects a reply followed -by a carriage return. - - likesPie := false - prompt := &survey.Confirm{ Message: "What is your name?" } - survey.AskOne(prompt, &likesPie, nil) -*/ -func (c *Confirm) Prompt() (interface{}, error) { - // render the question template - err := c.Render( - ConfirmQuestionTemplate, - ConfirmTemplateData{Confirm: *c}, - ) - if err != nil { - return "", err - } - - // get input and return - return c.getBool(false) -} - -// Cleanup overwrite the line with the finalized formatted version -func (c *Confirm) Cleanup(val interface{}) error { - // if the value was previously true - ans := yesNo(val.(bool)) - // render the template - return c.Render( - ConfirmQuestionTemplate, - ConfirmTemplateData{Confirm: *c, Answer: ans}, - ) -} diff --git a/vendor/github.com/kataras/survey/core/renderer.go b/vendor/github.com/kataras/survey/core/renderer.go deleted file mode 100644 index 53b457871b..0000000000 --- a/vendor/github.com/kataras/survey/core/renderer.go +++ /dev/null @@ -1,62 +0,0 @@ -package core - -import ( - "strings" - - "github.com/kataras/survey/terminal" -) - -type Renderer struct { - lineCount int - errorLineCount int -} - -var ErrorTemplate = `{{color "red"}}{{ ErrorIcon }} Sorry, your reply was invalid: {{.Error}}{{color "reset"}} -` - -func (r *Renderer) Error(invalid error) error { - // since errors are printed on top we need to reset the prompt - // as well as any previous error print - r.resetPrompt(r.lineCount + r.errorLineCount) - // we just cleared the prompt lines - r.lineCount = 0 - out, err := RunTemplate(ErrorTemplate, invalid) - if err != nil { - return err - } - // keep track of how many lines are printed so we can clean up later - r.errorLineCount = strings.Count(out, "\n") - - // send the message to the user - terminal.Print(out) - return nil -} - -func (r *Renderer) resetPrompt(lines int) { - // clean out current line in case tmpl didnt end in newline - terminal.CursorHorizontalAbsolute(0) - terminal.EraseLine(terminal.ERASE_LINE_ALL) - // clean up what we left behind last time - for i := 0; i < lines; i++ { - terminal.CursorPreviousLine(1) - terminal.EraseLine(terminal.ERASE_LINE_ALL) - } -} - -func (r *Renderer) Render(tmpl string, data interface{}) error { - r.resetPrompt(r.lineCount) - // render the template summarizing the current state - out, err := RunTemplate(tmpl, data) - if err != nil { - return err - } - - // keep track of how many lines are printed so we can clean up later - r.lineCount = strings.Count(out, "\n") - - // print the summary - terminal.Print(out) - - // nothing went wrong - return nil -} diff --git a/vendor/github.com/kataras/survey/core/template.go b/vendor/github.com/kataras/survey/core/template.go deleted file mode 100644 index 9c31cc3069..0000000000 --- a/vendor/github.com/kataras/survey/core/template.go +++ /dev/null @@ -1,83 +0,0 @@ -package core - -import ( - "bytes" - "text/template" - - "github.com/mgutz/ansi" -) - -var DisableColor = false - -var ( - HelpInputRune = '?' - - ErrorIcon = "✘" - HelpIcon = "ⓘ" - QuestionIcon = "?" - - MarkedOptionIcon = "◉" - UnmarkedOptionIcon = "◯" - - SelectFocusIcon = "❯" -) - -var TemplateFuncs = map[string]interface{}{ - // Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format - "color": func(color string) string { - if DisableColor { - return "" - } - return ansi.ColorCode(color) - }, - "HelpInputRune": func() string { - return string(HelpInputRune) - }, - "ErrorIcon": func() string { - return ErrorIcon - }, - "HelpIcon": func() string { - return HelpIcon - }, - "QuestionIcon": func() string { - return QuestionIcon - }, - "MarkedOptionIcon": func() string { - return MarkedOptionIcon - }, - "UnmarkedOptionIcon": func() string { - return UnmarkedOptionIcon - }, - "SelectFocusIcon": func() string { - return SelectFocusIcon - }, -} - -var memoizedGetTemplate = map[string]*template.Template{} - -func getTemplate(tmpl string) (*template.Template, error) { - if t, ok := memoizedGetTemplate[tmpl]; ok { - return t, nil - } - - t, err := template.New("prompt").Funcs(TemplateFuncs).Parse(tmpl) - if err != nil { - return nil, err - } - - memoizedGetTemplate[tmpl] = t - return t, nil -} - -func RunTemplate(tmpl string, data interface{}) (string, error) { - t, err := getTemplate(tmpl) - if err != nil { - return "", err - } - buf := bytes.NewBufferString("") - err = t.Execute(buf, data) - if err != nil { - return "", err - } - return buf.String(), err -} diff --git a/vendor/github.com/kataras/survey/core/write.go b/vendor/github.com/kataras/survey/core/write.go deleted file mode 100644 index becc7b4870..0000000000 --- a/vendor/github.com/kataras/survey/core/write.go +++ /dev/null @@ -1,244 +0,0 @@ -package core - -import ( - "errors" - "fmt" - "reflect" - "strconv" - "strings" -) - -// the tag used to denote the name of the question -const tagName = "survey" - -// add a few interfaces so users can configure how the prompt values are set -type settable interface { - WriteAnswer(field string, value interface{}) error -} - -func WriteAnswer(t interface{}, name string, v interface{}) (err error) { - // if the field is a custom type - if s, ok := t.(settable); ok { - // use the interface method - return s.WriteAnswer(name, v) - } - - // the target to write to - target := reflect.ValueOf(t) - // the value to write from - value := reflect.ValueOf(v) - - // make sure we are writing to a pointer - if target.Kind() != reflect.Ptr { - return errors.New("you must pass a pointer as the target of a Write operation") - } - // the object "inside" of the target pointer - elem := target.Elem() - - // handle the special types - switch elem.Kind() { - // if we are writing to a struct - case reflect.Struct: - // get the name of the field that matches the string we were given - fieldIndex, err := findFieldIndex(elem, name) - // if something went wrong - if err != nil { - // bubble up - return err - } - field := elem.Field(fieldIndex) - // handle references to the settable interface aswell - if s, ok := field.Interface().(settable); ok { - // use the interface method - return s.WriteAnswer(name, v) - } - if field.CanAddr() { - if s, ok := field.Addr().Interface().(settable); ok { - // use the interface method - return s.WriteAnswer(name, v) - } - } - - // copy the value over to the normal struct - return copy(field, value) - case reflect.Map: - mapType := reflect.TypeOf(t).Elem() - if mapType.Key().Kind() != reflect.String || mapType.Elem().Kind() != reflect.Interface { - return errors.New("answer maps must be of type map[string]interface") - } - mt := *t.(*map[string]interface{}) - mt[name] = value.Interface() - return nil - } - // otherwise just copy the value to the target - return copy(elem, value) -} - -func findFieldIndex(s reflect.Value, name string) (int, error) { - // the type of the value - sType := s.Type() - - // first look for matching tags so we can overwrite matching field names - for i := 0; i < sType.NumField(); i++ { - // the field we are current scanning - field := sType.Field(i) - - // the value of the survey tag - tag := field.Tag.Get(tagName) - // if the tag matches the name we are looking for - if tag != "" && tag == name { - // then we found our index - return i, nil - } - } - - // then look for matching names - for i := 0; i < sType.NumField(); i++ { - // the field we are current scanning - field := sType.Field(i) - - // if the name of the field matches what we're looking for - if strings.ToLower(field.Name) == strings.ToLower(name) { - return i, nil - } - } - - // we didn't find the field - return -1, fmt.Errorf("could not find field matching %v", name) -} - -// isList returns true if the element is something we can Len() -func isList(v reflect.Value) bool { - switch v.Type().Kind() { - case reflect.Array, reflect.Slice: - return true - default: - return false - } -} - -// Write takes a value and copies it to the target -func copy(t reflect.Value, v reflect.Value) (err error) { - // if something ends up panicing we need to catch it in a deferred func - defer func() { - if r := recover(); r != nil { - // if we paniced with an error - if _, ok := r.(error); ok { - // cast the result to an error object - err = r.(error) - } else if _, ok := r.(string); ok { - // otherwise we could have paniced with a string so wrap it in an error - err = errors.New(r.(string)) - } - } - }() - - // if we are copying from a string result to something else - if v.Kind() == reflect.String && v.Type() != t.Type() { - var castVal interface{} - var casterr error - vString := v.Interface().(string) - - switch t.Kind() { - case reflect.Bool: - castVal, casterr = strconv.ParseBool(vString) - case reflect.Int: - castVal, casterr = strconv.Atoi(vString) - case reflect.Int8: - var val64 int64 - val64, casterr = strconv.ParseInt(vString, 10, 8) - if casterr == nil { - castVal = int8(val64) - } - case reflect.Int16: - var val64 int64 - val64, casterr = strconv.ParseInt(vString, 10, 16) - if casterr == nil { - castVal = int16(val64) - } - case reflect.Int32: - var val64 int64 - val64, casterr = strconv.ParseInt(vString, 10, 32) - if casterr == nil { - castVal = int32(val64) - } - case reflect.Int64: - castVal, casterr = strconv.ParseInt(vString, 10, 64) - case reflect.Uint: - var val64 uint64 - val64, casterr = strconv.ParseUint(vString, 10, 8) - if casterr == nil { - castVal = uint(val64) - } - case reflect.Uint8: - var val64 uint64 - val64, casterr = strconv.ParseUint(vString, 10, 8) - if casterr == nil { - castVal = uint8(val64) - } - case reflect.Uint16: - var val64 uint64 - val64, casterr = strconv.ParseUint(vString, 10, 16) - if casterr == nil { - castVal = uint16(val64) - } - case reflect.Uint32: - var val64 uint64 - val64, casterr = strconv.ParseUint(vString, 10, 32) - if casterr == nil { - castVal = uint32(val64) - } - case reflect.Uint64: - castVal, casterr = strconv.ParseUint(vString, 10, 64) - case reflect.Float32: - var val64 float64 - val64, casterr = strconv.ParseFloat(vString, 32) - if casterr == nil { - castVal = float32(val64) - } - case reflect.Float64: - castVal, casterr = strconv.ParseFloat(vString, 64) - default: - return fmt.Errorf("Unable to convert from string to type %s", t.Kind()) - } - - if casterr != nil { - return casterr - } - - t.Set(reflect.ValueOf(castVal)) - return - } - - // if we are copying from one slice or array to another - if isList(v) && isList(t) { - // loop over every item in the desired value - for i := 0; i < v.Len(); i++ { - // write to the target given its kind - switch t.Kind() { - // if its a slice - case reflect.Slice: - // an object of the correct type - obj := reflect.Indirect(reflect.New(t.Type().Elem())) - - // write the appropriate value to the obj and catch any errors - if err := copy(obj, v.Index(i)); err != nil { - return err - } - - // just append the value to the end - t.Set(reflect.Append(t, obj)) - // otherwise it could be an array - case reflect.Array: - // set the index to the appropriate value - copy(t.Slice(i, i+1).Index(0), v.Index(i)) - } - } - } else { - // set the value to the target - t.Set(v) - } - - // we're done - return -} diff --git a/vendor/github.com/kataras/survey/core/write_test.go b/vendor/github.com/kataras/survey/core/write_test.go deleted file mode 100644 index 815c4f540e..0000000000 --- a/vendor/github.com/kataras/survey/core/write_test.go +++ /dev/null @@ -1,543 +0,0 @@ -package core - -import ( - "fmt" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestWrite_returnsErrorIfTargetNotPtr(t *testing.T) { - // try to copy a value to a non-pointer - err := WriteAnswer(true, "hello", true) - // make sure there was an error - if err == nil { - t.Error("Did not encounter error when writing to non-pointer.") - } -} - -func TestWrite_canWriteToBool(t *testing.T) { - // a pointer to hold the boolean value - ptr := true - - // try to copy a false value to the pointer - WriteAnswer(&ptr, "", false) - - // if the value is true - if ptr { - // the test failed - t.Error("Could not write a false bool to a pointer") - } -} - -func TestWrite_canWriteString(t *testing.T) { - // a pointer to hold the boolean value - ptr := "" - - // try to copy a false value to the pointer - err := WriteAnswer(&ptr, "", "hello") - if err != nil { - t.Error(err) - } - - // if the value is not what we wrote - if ptr != "hello" { - t.Error("Could not write a string value to a pointer") - } -} - -func TestWrite_canWriteSlice(t *testing.T) { - // a pointer to hold the value - ptr := []string{} - - // copy in a value - WriteAnswer(&ptr, "", []string{"hello", "world"}) - - // make sure there are two entries - assert.Equal(t, []string{"hello", "world"}, ptr) -} - -func TestWrite_recoversInvalidReflection(t *testing.T) { - // a variable to mutate - ptr := false - - // write a boolean value to the string - err := WriteAnswer(&ptr, "", "hello") - - // if there was no error - if err == nil { - // the test failed - t.Error("Did not encounter error when forced invalid write.") - } -} - -func TestWriteAnswer_handlesNonStructValues(t *testing.T) { - // the value to write to - ptr := "" - - // write a value to the pointer - WriteAnswer(&ptr, "", "world") - - // if we didn't change the value appropriate - if ptr != "world" { - // the test failed - t.Error("Did not write value to primitive pointer") - } -} - -func TestWriteAnswer_canMutateStruct(t *testing.T) { - // the struct to hold the answer - ptr := struct{ Name string }{} - - // write a value to an existing field - err := WriteAnswer(&ptr, "name", "world") - if err != nil { - // the test failed - t.Errorf("Encountered error while writing answer: %v", err.Error()) - // we're done here - return - } - - // make sure we changed the field - if ptr.Name != "world" { - // the test failed - t.Error("Did not mutate struct field when writing answer.") - } -} - -func TestWriteAnswer_canMutateMap(t *testing.T) { - // the map to hold the answer - ptr := make(map[string]interface{}) - - // write a value to an existing field - err := WriteAnswer(&ptr, "name", "world") - if err != nil { - // the test failed - t.Errorf("Encountered error while writing answer: %v", err.Error()) - // we're done here - return - } - - // make sure we changed the field - if ptr["name"] != "world" { - // the test failed - t.Error("Did not mutate map when writing answer.") - } -} - -func TestWrite_returnsErrorIfInvalidMapType(t *testing.T) { - // try to copy a value to a non map[string]interface{} - ptr := make(map[int]string) - - err := WriteAnswer(&ptr, "name", "world") - // make sure there was an error - if err == nil { - t.Error("Did not encounter error when writing to invalid map.") - } -} - -func TestWrite_writesStringSliceToIntSlice(t *testing.T) { - // make a slice of int to write to - target := []int{} - - // write the answer - err := WriteAnswer(&target, "name", []string{"1", "2", "3"}) - - // make sure there was no error - assert.Nil(t, err, "WriteSlice to Int Slice") - // and we got what we wanted - assert.Equal(t, []int{1, 2, 3}, target) -} - -func TestWrite_writesStringArrayToIntArray(t *testing.T) { - // make an array of int to write to - target := [3]int{} - - // write the answer - err := WriteAnswer(&target, "name", [3]string{"1", "2", "3"}) - - // make sure there was no error - assert.Nil(t, err, "WriteArray to Int Array") - // and we got what we wanted - assert.Equal(t, [3]int{1, 2, 3}, target) -} - -func TestWriteAnswer_returnsErrWhenFieldNotFound(t *testing.T) { - // the struct to hold the answer - ptr := struct{ Name string }{} - - // write a value to an existing field - err := WriteAnswer(&ptr, "", "world") - - if err == nil { - // the test failed - t.Error("Did not encountered error while writing answer to non-existing field.") - } -} - -func TestFindFieldIndex_canFindExportedField(t *testing.T) { - // create a reflective wrapper over the struct to look through - val := reflect.ValueOf(struct{ Name string }{}) - - // find the field matching "name" - fieldIndex, err := findFieldIndex(val, "name") - // if something went wrong - if err != nil { - // the test failed - t.Error(err.Error()) - return - } - - // make sure we got the right value - if val.Type().Field(fieldIndex).Name != "Name" { - // the test failed - t.Errorf("Did not find the correct field name. Expected 'Name' found %v.", val.Type().Field(fieldIndex).Name) - } -} - -func TestFindFieldIndex_canFindTaggedField(t *testing.T) { - // the struct to look through - val := reflect.ValueOf(struct { - Username string `survey:"name"` - }{}) - - // find the field matching "name" - fieldIndex, err := findFieldIndex(val, "name") - // if something went wrong - if err != nil { - // the test failed - t.Error(err.Error()) - return - } - - // make sure we got the right value - if val.Type().Field(fieldIndex).Name != "Username" { - // the test failed - t.Errorf("Did not find the correct field name. Expected 'Username' found %v.", val.Type().Field(fieldIndex).Name) - } -} - -func TestFindFieldIndex_canHandleCapitalAnswerNames(t *testing.T) { - // create a reflective wrapper over the struct to look through - val := reflect.ValueOf(struct{ Name string }{}) - - // find the field matching "name" - fieldIndex, err := findFieldIndex(val, "Name") - // if something went wrong - if err != nil { - // the test failed - t.Error(err.Error()) - return - } - - // make sure we got the right value - if val.Type().Field(fieldIndex).Name != "Name" { - // the test failed - t.Errorf("Did not find the correct field name. Expected 'Name' found %v.", val.Type().Field(fieldIndex).Name) - } -} - -func TestFindFieldIndex_tagOverwriteFieldName(t *testing.T) { - // the struct to look through - val := reflect.ValueOf(struct { - Name string - Username string `survey:"name"` - }{}) - - // find the field matching "name" - fieldIndex, err := findFieldIndex(val, "name") - // if something went wrong - if err != nil { - // the test failed - t.Error(err.Error()) - return - } - - // make sure we got the right value - if val.Type().Field(fieldIndex).Name != "Username" { - // the test failed - t.Errorf("Did not find the correct field name. Expected 'Username' found %v.", val.Type().Field(fieldIndex).Name) - } -} - -type testFieldSettable struct { - Values map[string]string -} - -type testStringSettable struct { - Value string `survey:"string"` -} - -type testTaggedStruct struct { - TaggedValue testStringSettable `survey:"tagged"` -} - -type testPtrTaggedStruct struct { - TaggedValue *testStringSettable `survey:"tagged"` -} - -func (t *testFieldSettable) WriteAnswer(name string, value interface{}) error { - if t.Values == nil { - t.Values = map[string]string{} - } - if v, ok := value.(string); ok { - t.Values[name] = v - return nil - } - return fmt.Errorf("Incompatible type %T", value) -} - -func (t *testStringSettable) WriteAnswer(_ string, value interface{}) error { - t.Value = value.(string) - return nil -} - -func TestWriteWithFieldSettable(t *testing.T) { - testSet1 := testFieldSettable{} - err := WriteAnswer(&testSet1, "values", "stringVal") - assert.Nil(t, err) - assert.Equal(t, map[string]string{"values": "stringVal"}, testSet1.Values) - - testSet2 := testFieldSettable{} - err = WriteAnswer(&testSet2, "values", 123) - assert.Error(t, fmt.Errorf("Incompatible type int64"), err) - assert.Equal(t, map[string]string{}, testSet2.Values) - - testString1 := testStringSettable{} - err = WriteAnswer(&testString1, "", "value1") - assert.Nil(t, err) - assert.Equal(t, testStringSettable{"value1"}, testString1) - - testSetStruct := testTaggedStruct{} - err = WriteAnswer(&testSetStruct, "tagged", "stringVal1") - assert.Nil(t, err) - assert.Equal(t, testTaggedStruct{TaggedValue: testStringSettable{"stringVal1"}}, testSetStruct) - - testPtrSetStruct := testPtrTaggedStruct{&testStringSettable{}} - err = WriteAnswer(&testPtrSetStruct, "tagged", "stringVal1") - assert.Nil(t, err) - assert.Equal(t, testPtrTaggedStruct{TaggedValue: &testStringSettable{"stringVal1"}}, testPtrSetStruct) -} - -// CONVERSION TESTS -func TestWrite_canStringToBool(t *testing.T) { - // a pointer to hold the boolean value - ptr := true - - // try to copy a false value to the pointer - WriteAnswer(&ptr, "", "false") - - // if the value is true - if ptr { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToInt(t *testing.T) { - // a pointer to hold the value - var ptr int = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToInt8(t *testing.T) { - // a pointer to hold the value - var ptr int8 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToInt16(t *testing.T) { - // a pointer to hold the value - var ptr int16 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToInt32(t *testing.T) { - // a pointer to hold the value - var ptr int32 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToInt64(t *testing.T) { - // a pointer to hold the value - var ptr int64 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToUint(t *testing.T) { - // a pointer to hold the value - var ptr uint = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToUint8(t *testing.T) { - // a pointer to hold the value - var ptr uint8 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToUint16(t *testing.T) { - // a pointer to hold the value - var ptr uint16 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToUint32(t *testing.T) { - // a pointer to hold the value - var ptr uint32 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToUint64(t *testing.T) { - // a pointer to hold the value - var ptr uint64 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToFloat32(t *testing.T) { - // a pointer to hold the value - var ptr float32 = 1.0 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2.5") - - // if the value is true - if ptr != 2.5 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToFloat64(t *testing.T) { - // a pointer to hold the value - var ptr float64 = 1.0 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2.5") - - // if the value is true - if ptr != 2.5 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canConvertStructFieldTypes(t *testing.T) { - // the struct to hold the answer - ptr := struct { - Name string - Age uint - Male bool - Height float64 - }{} - - // write the values as strings - check(t, WriteAnswer(&ptr, "name", "Bob")) - check(t, WriteAnswer(&ptr, "age", "22")) - check(t, WriteAnswer(&ptr, "male", "true")) - check(t, WriteAnswer(&ptr, "height", "6.2")) - - // make sure we changed the fields - if ptr.Name != "Bob" { - t.Error("Did not mutate Name when writing answer.") - } - - if ptr.Age != 22 { - t.Error("Did not mutate Age when writing answer.") - } - - if !ptr.Male { - t.Error("Did not mutate Male when writing answer.") - } - - if ptr.Height != 6.2 { - t.Error("Did not mutate Height when writing answer.") - } -} - -func check(t *testing.T, err error) { - if err != nil { - t.Fatalf("Encountered error while writing answer: %v", err.Error()) - } -} diff --git a/vendor/github.com/kataras/survey/editor.go b/vendor/github.com/kataras/survey/editor.go deleted file mode 100644 index 2241dd3435..0000000000 --- a/vendor/github.com/kataras/survey/editor.go +++ /dev/null @@ -1,168 +0,0 @@ -package survey - -import ( - "bytes" - "io/ioutil" - "os" - "os/exec" - "runtime" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -/* -Editor launches an instance of the users preferred editor on a temporary file. -The editor to use is determined by reading the $VISUAL or $EDITOR environment -variables. If neither of those are present, notepad (on Windows) or vim -(others) is used. -The launch of the editor is triggered by the enter key. Since the response may -be long, it will not be echoed as Input does, instead, it print . -Response type is a string. - - message := "" - prompt := &survey.Editor{ Message: "What is your commit message?" } - survey.AskOne(prompt, &message, nil) -*/ -type Editor struct { - core.Renderer - Message string - Default string - Help string -} - -// data available to the templates when processing -type EditorTemplateData struct { - Editor - Answer string - ShowAnswer bool - ShowHelp bool -} - -// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format -var EditorQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if .ShowAnswer}} - {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}} - {{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}} - {{- color "cyan"}}[Enter to launch editor] {{color "reset"}} -{{- end}}` - -var ( - bom = []byte{0xef, 0xbb, 0xbf} - editor = "vim" -) - -func init() { - if runtime.GOOS == "windows" { - editor = "notepad" - } - if v := os.Getenv("VISUAL"); v != "" { - editor = v - } else if e := os.Getenv("EDITOR"); e != "" { - editor = e - } -} - -func (e *Editor) Prompt() (interface{}, error) { - // render the template - err := e.Render( - EditorQuestionTemplate, - EditorTemplateData{Editor: *e}, - ) - if err != nil { - return "", err - } - - // start reading runes from the standard in - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - - terminal.CursorHide() - defer terminal.CursorShow() - - for { - r, _, err := rr.ReadRune() - if err != nil { - return "", err - } - if r == '\r' || r == '\n' { - break - } - if r == terminal.KeyInterrupt { - return "", terminal.InterruptErr - } - if r == terminal.KeyEndTransmission { - break - } - if r == core.HelpInputRune && e.Help != "" { - err = e.Render( - EditorQuestionTemplate, - EditorTemplateData{Editor: *e, ShowHelp: true}, - ) - if err != nil { - return "", err - } - } - continue - } - - // prepare the temp file - f, err := ioutil.TempFile("", "survey") - if err != nil { - return "", err - } - defer os.Remove(f.Name()) - - // write utf8 BOM header - // The reason why we do this is because notepad.exe on Windows determines the - // encoding of an "empty" text file by the locale, for example, GBK in China, - // while golang string only handles utf8 well. However, a text file with utf8 - // BOM header is not considered "empty" on Windows, and the encoding will then - // be determined utf8 by notepad.exe, instead of GBK or other encodings. - if _, err := f.Write(bom); err != nil { - return "", err - } - // close the fd to prevent the editor unable to save file - if err := f.Close(); err != nil { - return "", err - } - - // open the editor - cmd := exec.Command(editor, f.Name()) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - terminal.CursorShow() - if err := cmd.Run(); err != nil { - return "", err - } - - // raw is a BOM-unstripped UTF8 byte slice - raw, err := ioutil.ReadFile(f.Name()) - if err != nil { - return "", err - } - - // strip BOM header - text := string(bytes.TrimPrefix(raw, bom)) - - // check length, return default value on empty - if len(text) == 0 { - return e.Default, nil - } - - return text, nil -} - -func (e *Editor) Cleanup(val interface{}) error { - return e.Render( - EditorQuestionTemplate, - EditorTemplateData{Editor: *e, Answer: "", ShowAnswer: true}, - ) -} diff --git a/vendor/github.com/kataras/survey/input.go b/vendor/github.com/kataras/survey/input.go deleted file mode 100644 index a774751326..0000000000 --- a/vendor/github.com/kataras/survey/input.go +++ /dev/null @@ -1,98 +0,0 @@ -package survey - -import ( - "os" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -/* -Input is a regular text input that prints each character the user types on the screen -and accepts the input with the enter key. Response type is a string. - - name := "" - prompt := &survey.Input{ Message: "What is your name?" } - survey.AskOne(prompt, &name, nil) -*/ -type Input struct { - core.Renderer - Message string - Default string - Help string -} - -// data available to the templates when processing -type InputTemplateData struct { - Input - Answer string - ShowAnswer bool - ShowHelp bool -} - -// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format -var InputQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if .ShowAnswer}} - {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}} - {{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}} -{{- end}}` - -func (i *Input) Prompt() (interface{}, error) { - // render the template - err := i.Render( - InputQuestionTemplate, - InputTemplateData{Input: *i}, - ) - if err != nil { - return "", err - } - - // start reading runes from the standard in - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - - line := []rune{} - // get the next line - for { - line, err = rr.ReadLine(0) - if err != nil { - return string(line), err - } - // terminal will echo the \n so we need to jump back up one row - terminal.CursorPreviousLine(1) - - if string(line) == string(core.HelpInputRune) && i.Help != "" { - err = i.Render( - InputQuestionTemplate, - InputTemplateData{Input: *i, ShowHelp: true}, - ) - if err != nil { - return "", err - } - continue - } - break - } - - // if the line is empty - if line == nil || len(line) == 0 { - // use the default value - return i.Default, err - } - - // we're done - return string(line), err -} - -func (i *Input) Cleanup(val interface{}) error { - return i.Render( - InputQuestionTemplate, - InputTemplateData{Input: *i, Answer: val.(string), ShowAnswer: true}, - ) -} diff --git a/vendor/github.com/kataras/survey/multiselect.go b/vendor/github.com/kataras/survey/multiselect.go deleted file mode 100644 index 1d72b49ce5..0000000000 --- a/vendor/github.com/kataras/survey/multiselect.go +++ /dev/null @@ -1,203 +0,0 @@ -package survey - -import ( - "errors" - "os" - "strings" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -/* -MultiSelect is a prompt that presents a list of various options to the user -for them to select using the arrow keys and enter. Response type is a slice of strings. - - days := []string{} - prompt := &survey.MultiSelect{ - Message: "What days do you prefer:", - Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}, - } - survey.AskOne(prompt, &days, nil) -*/ -type MultiSelect struct { - core.Renderer - Message string - Options []string - Default []string - Help string - PageSize int - selectedIndex int - checked map[string]bool - showingHelp bool -} - -// data available to the templates when processing -type MultiSelectTemplateData struct { - MultiSelect - Answer string - ShowAnswer bool - Checked map[string]bool - SelectedIndex int - ShowHelp bool - PageEntries []string -} - -var MultiSelectQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }}{{color "reset"}} -{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- if and .Help (not .ShowHelp)}} {{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}}{{end}} - {{- "\n"}} - {{- range $ix, $option := .PageEntries}} - {{- if eq $ix $.SelectedIndex}}{{color "cyan"}}{{ SelectFocusIcon }}{{color "reset"}}{{else}} {{end}} - {{- if index $.Checked $option}}{{color "green"}} {{ MarkedOptionIcon }} {{else}}{{color "default+hb"}} {{ UnmarkedOptionIcon }} {{end}} - {{- color "reset"}} - {{- " "}}{{$option}}{{"\n"}} - {{- end}} -{{- end}}` - -// OnChange is called on every keypress. -func (m *MultiSelect) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { - if key == terminal.KeyArrowUp { - // if we are at the top of the list - if m.selectedIndex == 0 { - // go to the bottom - m.selectedIndex = len(m.Options) - 1 - } else { - // decrement the selected index - m.selectedIndex-- - } - } else if key == terminal.KeyArrowDown { - // if we are at the bottom of the list - if m.selectedIndex == len(m.Options)-1 { - // start at the top - m.selectedIndex = 0 - } else { - // increment the selected index - m.selectedIndex++ - } - // if the user pressed down and there is room to move - } else if key == terminal.KeySpace { - if old, ok := m.checked[m.Options[m.selectedIndex]]; !ok { - // otherwise just invert the current value - m.checked[m.Options[m.selectedIndex]] = true - } else { - // otherwise just invert the current value - m.checked[m.Options[m.selectedIndex]] = !old - } - // only show the help message if we have one to show - } else if key == core.HelpInputRune && m.Help != "" { - m.showingHelp = true - } - - // paginate the options - opts, idx := paginate(m.PageSize, m.Options, m.selectedIndex) - - // render the options - m.Render( - MultiSelectQuestionTemplate, - MultiSelectTemplateData{ - MultiSelect: *m, - SelectedIndex: idx, - Checked: m.checked, - ShowHelp: m.showingHelp, - PageEntries: opts, - }, - ) - - // if we are not pressing ent - return line, 0, true -} - -func (m *MultiSelect) Prompt() (interface{}, error) { - // compute the default state - m.checked = make(map[string]bool) - // if there is a default - if len(m.Default) > 0 { - for _, dflt := range m.Default { - for _, opt := range m.Options { - // if the option correponds to the default - if opt == dflt { - // we found our initial value - m.checked[opt] = true - // stop looking - break - } - } - } - } - - // if there are no options to render - if len(m.Options) == 0 { - // we failed - return "", errors.New("please provide options to select from") - } - - // hide the cursor - terminal.CursorHide() - // show the cursor when we're done - defer terminal.CursorShow() - - // paginate the options - opts, idx := paginate(m.PageSize, m.Options, m.selectedIndex) - - // ask the question - err := m.Render( - MultiSelectQuestionTemplate, - MultiSelectTemplateData{ - MultiSelect: *m, - SelectedIndex: idx, - Checked: m.checked, - PageEntries: opts, - }, - ) - if err != nil { - return "", err - } - - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - - // start waiting for input - for { - r, _, _ := rr.ReadRune() - if r == '\r' || r == '\n' { - break - } - if r == terminal.KeyInterrupt { - return "", terminal.InterruptErr - } - if r == terminal.KeyEndTransmission { - break - } - m.OnChange(nil, 0, r) - } - - answers := []string{} - for _, option := range m.Options { - if val, ok := m.checked[option]; ok && val { - answers = append(answers, option) - } - } - - return answers, nil -} - -// Cleanup removes the options section, and renders the ask like a normal question. -func (m *MultiSelect) Cleanup(val interface{}) error { - // execute the output summary template with the answer - return m.Render( - MultiSelectQuestionTemplate, - MultiSelectTemplateData{ - MultiSelect: *m, - SelectedIndex: m.selectedIndex, - Checked: m.checked, - Answer: strings.Join(val.([]string), ", "), - ShowAnswer: true, - }, - ) -} diff --git a/vendor/github.com/kataras/survey/password.go b/vendor/github.com/kataras/survey/password.go deleted file mode 100644 index 9065fec588..0000000000 --- a/vendor/github.com/kataras/survey/password.go +++ /dev/null @@ -1,84 +0,0 @@ -package survey - -import ( - "os" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -/* -Password is like a normal Input but the text shows up as *'s and there is no default. Response -type is a string. - - password := "" - prompt := &survey.Password{ Message: "Please type your password" } - survey.AskOne(prompt, &password, nil) -*/ -type Password struct { - core.Renderer - Message string - Help string -} - -type PasswordTemplateData struct { - Password - ShowHelp bool -} - -// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format -var PasswordQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}` - -func (p *Password) Prompt() (line interface{}, err error) { - // render the question template - out, err := core.RunTemplate( - PasswordQuestionTemplate, - PasswordTemplateData{Password: *p}, - ) - terminal.Print(out) - if err != nil { - return "", err - } - - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - - // no help msg? Just return any response - if p.Help == "" { - line, err := rr.ReadLine('*') - return string(line), err - } - - // process answers looking for help prompt answer - for { - line, err := rr.ReadLine('*') - if err != nil { - return string(line), err - } - - if string(line) == string(core.HelpInputRune) { - // terminal will echo the \n so we need to jump back up one row - terminal.CursorPreviousLine(1) - - err = p.Render( - PasswordQuestionTemplate, - PasswordTemplateData{Password: *p, ShowHelp: true}, - ) - if err != nil { - return "", err - } - continue - } - return string(line), err - } -} - -// Cleanup hides the string with a fixed number of characters. -func (prompt *Password) Cleanup(val interface{}) error { - return nil -} diff --git a/vendor/github.com/kataras/survey/select.go b/vendor/github.com/kataras/survey/select.go deleted file mode 100644 index 12820428a4..0000000000 --- a/vendor/github.com/kataras/survey/select.go +++ /dev/null @@ -1,209 +0,0 @@ -package survey - -import ( - "errors" - "os" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -/* -Select is a prompt that presents a list of various options to the user -for them to select using the arrow keys and enter. Response type is a string. - - color := "" - prompt := &survey.Select{ - Message: "Choose a color:", - Options: []string{"red", "blue", "green"}, - } - survey.AskOne(prompt, &color, nil) -*/ -type Select struct { - core.Renderer - Message string - Options []string - Default string - Help string - PageSize int - selectedIndex int - useDefault bool - showingHelp bool -} - -// the data available to the templates when processing -type SelectTemplateData struct { - Select - PageEntries []string - SelectedIndex int - Answer string - ShowAnswer bool - ShowHelp bool -} - -var SelectQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }}{{color "reset"}} -{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}} -{{- else}} - {{- if and .Help (not .ShowHelp)}} {{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}}{{end}} - {{- "\n"}} - {{- range $ix, $choice := .PageEntries}} - {{- if eq $ix $.SelectedIndex}}{{color "cyan+b"}}{{ SelectFocusIcon }} {{else}}{{color "default+hb"}} {{end}} - {{- $choice}} - {{- color "reset"}}{{"\n"}} - {{- end}} -{{- end}}` - -// OnChange is called on every keypress. -func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { - // if the user pressed the enter key - if key == terminal.KeyEnter { - return []rune(s.Options[s.selectedIndex]), 0, true - // if the user pressed the up arrow - } else if key == terminal.KeyArrowUp { - s.useDefault = false - - // if we are at the top of the list - if s.selectedIndex == 0 { - // start from the button - s.selectedIndex = len(s.Options) - 1 - } else { - // otherwise we are not at the top of the list so decrement the selected index - s.selectedIndex-- - } - // if the user pressed down and there is room to move - } else if key == terminal.KeyArrowDown { - s.useDefault = false - // if we are at the bottom of the list - if s.selectedIndex == len(s.Options)-1 { - // start from the top - s.selectedIndex = 0 - } else { - // increment the selected index - s.selectedIndex++ - } - // only show the help message if we have one - } else if key == core.HelpInputRune && s.Help != "" { - s.showingHelp = true - } - - // figure out the options and index to render - opts, idx := paginate(s.PageSize, s.Options, s.selectedIndex) - - // render the options - s.Render( - SelectQuestionTemplate, - SelectTemplateData{ - Select: *s, - SelectedIndex: idx, - ShowHelp: s.showingHelp, - PageEntries: opts, - }, - ) - - // if we are not pressing ent - return []rune(s.Options[s.selectedIndex]), 0, true -} - -func (s *Select) Prompt() (interface{}, error) { - // if there are no options to render - if len(s.Options) == 0 { - // we failed - return "", errors.New("please provide options to select from") - } - - // start off with the first option selected - sel := 0 - // if there is a default - if s.Default != "" { - // find the choice - for i, opt := range s.Options { - // if the option correponds to the default - if opt == s.Default { - // we found our initial value - sel = i - // stop looking - break - } - } - } - // save the selected index - s.selectedIndex = sel - - // figure out the options and index to render - opts, idx := paginate(s.PageSize, s.Options, sel) - - // ask the question - err := s.Render( - SelectQuestionTemplate, - SelectTemplateData{ - Select: *s, - PageEntries: opts, - SelectedIndex: idx, - }, - ) - if err != nil { - return "", err - } - - // hide the cursor - terminal.CursorHide() - // show the cursor when we're done - defer terminal.CursorShow() - - // by default, use the default value - s.useDefault = true - - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - // start waiting for input - for { - r, _, err := rr.ReadRune() - if err != nil { - return "", err - } - if r == '\r' || r == '\n' { - break - } - if r == terminal.KeyInterrupt { - return "", terminal.InterruptErr - } - if r == terminal.KeyEndTransmission { - break - } - s.OnChange(nil, 0, r) - } - - var val string - // if we are supposed to use the default value - if s.useDefault { - // if there is a default value - if s.Default != "" { - // use the default value - val = s.Default - } else { - // there is no default value so use the first - val = s.Options[0] - } - // otherwise the selected index points to the value - } else { - // the - val = s.Options[s.selectedIndex] - } - - return val, err -} - -func (s *Select) Cleanup(val interface{}) error { - return s.Render( - SelectQuestionTemplate, - SelectTemplateData{ - Select: *s, - Answer: val.(string), - ShowAnswer: true, - }, - ) -} diff --git a/vendor/github.com/kataras/survey/survey.go b/vendor/github.com/kataras/survey/survey.go deleted file mode 100644 index 2e5fd40e61..0000000000 --- a/vendor/github.com/kataras/survey/survey.go +++ /dev/null @@ -1,198 +0,0 @@ -package survey - -import ( - "errors" - - "github.com/kataras/survey/core" -) - -// PageSize is the default maximum number of items to show in select/multiselect prompts -var PageSize = 7 - -// Validator is a function passed to a Question after a user has provided a response. -// If the function returns an error, then the user will be prompted again for another -// response. -type Validator func(ans interface{}) error - -// Transformer is a function passed to a Question after a user has provided a response. -// The function can be used to implement a custom logic that will result to return -// a different representation of the given answer. -// -// Look `TransformString`, `ToLower` `Title` and `ComposeTransformers` for more. -type Transformer func(ans interface{}) (newAns interface{}) - -// Question is the core data structure for a survey questionnaire. -type Question struct { - Name string - Prompt Prompt - Validate Validator - Transform Transformer -} - -// Prompt is the primary interface for the objects that can take user input -// and return a response. -type Prompt interface { - Prompt() (interface{}, error) - Cleanup(interface{}) error - Error(error) error -} - -/* -AskOne performs the prompt for a single prompt and asks for validation if required. -Response types should be something that can be casted from the response type designated -in the documentation. For example: - - name := "" - prompt := &survey.Input{ - Message: "name", - } - - survey.AskOne(prompt, &name, nil) - -*/ -func AskOne(p Prompt, response interface{}, v Validator) error { - err := Ask([]*Question{{Prompt: p, Validate: v}}, response) - if err != nil { - return err - } - - return nil -} - -/* -Ask performs the prompt loop, asking for validation when appropriate. The response -type can be one of two options. If a struct is passed, the answer will be written to -the field whose name matches the Name field on the corresponding question. Field types -should be something that can be casted from the response type designated in the -documentation. Note, a survey tag can also be used to identify a Otherwise, a -map[string]interface{} can be passed, responses will be written to the key with the -matching name. For example: - - qs := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "What is your name?"}, - Validate: survey.Required, - Transform: survey.Title, - }, - } - - answers := struct{ Name string }{} - - - err := survey.Ask(qs, &answers) -*/ -func Ask(qs []*Question, response interface{}) error { - - // if we weren't passed a place to record the answers - if response == nil { - // we can't go any further - return errors.New("cannot call Ask() with a nil reference to record the answers") - } - - // go over every question - for _, q := range qs { - // grab the user input and save it - ans, err := q.Prompt.Prompt() - // if there was a problem - if err != nil { - return err - } - - // if there is a validate handler for this question - if q.Validate != nil { - // wait for a valid response - for invalid := q.Validate(ans); invalid != nil; invalid = q.Validate(ans) { - err := q.Prompt.Error(invalid) - // if there was a problem - if err != nil { - return err - } - - // ask for more input - ans, err = q.Prompt.Prompt() - // if there was a problem - if err != nil { - return err - } - } - } - - if q.Transform != nil { - // check if we have a transformer available, if so - // then try to acquire the new representation of the - // answer, if the resulting answer is not nil. - if newAns := q.Transform(ans); newAns != nil { - ans = newAns - } - } - - // tell the prompt to cleanup with the validated value - q.Prompt.Cleanup(ans) - - // if something went wrong - if err != nil { - // stop listening - return err - } - - // add it to the map - err = core.WriteAnswer(response, q.Name, ans) - // if something went wrong - if err != nil { - return err - } - - } - // return the response - return nil -} - -// paginate returns a single page of choices given the page size, the total list of -// possible choices, and the current selected index in the total list. -func paginate(page int, choices []string, sel int) ([]string, int) { - // the number of elements to show in a single page - var pageSize int - // if the select has a specific page size - if page != 0 { - // use the specified one - pageSize = page - // otherwise the select does not have a page size - } else { - // use the package default - pageSize = PageSize - } - - var start, end, cursor int - - if len(choices) < pageSize { - // if we dont have enough options to fill a page - start = 0 - end = len(choices) - cursor = sel - - } else if sel < pageSize/2 { - // if we are in the first half page - start = 0 - end = pageSize - cursor = sel - - } else if len(choices)-sel-1 < pageSize/2 { - // if we are in the last half page - start = len(choices) - pageSize - end = len(choices) - cursor = sel - start - - } else { - // somewhere in the middle - above := pageSize / 2 - below := pageSize - above - - cursor = pageSize / 2 - start = sel - above - end = sel + below - } - - // return the subset we care about and the index - return choices[start:end], cursor -} diff --git a/vendor/github.com/kataras/survey/terminal/cursor.go b/vendor/github.com/kataras/survey/terminal/cursor.go deleted file mode 100644 index 533b75a644..0000000000 --- a/vendor/github.com/kataras/survey/terminal/cursor.go +++ /dev/null @@ -1,134 +0,0 @@ -// +build !windows - -package terminal - -import ( - "bufio" - "fmt" - "os" - "regexp" - "strconv" - "strings" -) - -// CursorUp moves the cursor n cells to up. -func CursorUp(n int) { - fmt.Printf("\x1b[%dA", n) -} - -// CursorDown moves the cursor n cells to down. -func CursorDown(n int) { - fmt.Printf("\x1b[%dB", n) -} - -// CursorForward moves the cursor n cells to right. -func CursorForward(n int) { - fmt.Printf("\x1b[%dC", n) -} - -// CursorBack moves the cursor n cells to left. -func CursorBack(n int) { - fmt.Printf("\x1b[%dD", n) -} - -// CursorNextLine moves cursor to beginning of the line n lines down. -func CursorNextLine(n int) { - fmt.Printf("\x1b[%dE", n) -} - -// CursorPreviousLine moves cursor to beginning of the line n lines up. -func CursorPreviousLine(n int) { - fmt.Printf("\x1b[%dF", n) -} - -// CursorHorizontalAbsolute moves cursor horizontally to x. -func CursorHorizontalAbsolute(x int) { - fmt.Printf("\x1b[%dG", x) -} - -// CursorShow shows the cursor. -func CursorShow() { - fmt.Print("\x1b[?25h") -} - -// CursorHide hide the cursor. -func CursorHide() { - fmt.Print("\x1b[?25l") -} - -// CursorMove moves the cursor to a specific x,y location. -func CursorMove(x int, y int) { - fmt.Printf("\x1b[%d;%df", x, y) -} - -// CursorLocation returns the current location of the cursor in the terminal -func CursorLocation() (*Coord, error) { - // print the escape sequence to receive the position in our stdin - fmt.Print("\x1b[6n") - - // read from stdin to get the response - reader := bufio.NewReader(os.Stdin) - // spec says we read 'til R, so do that - text, err := reader.ReadSlice('R') - if err != nil { - return nil, err - } - - // spec also says they're split by ;, so do that too - if strings.Contains(string(text), ";") { - // a regex to parse the output of the ansi code - re := regexp.MustCompile(`\d+;\d+`) - line := re.FindString(string(text)) - - // find the column and rows embedded in the string - coords := strings.Split(line, ";") - - // try to cast the col number to an int - col, err := strconv.Atoi(coords[1]) - if err != nil { - return nil, err - } - - // try to cast the row number to an int - row, err := strconv.Atoi(coords[0]) - if err != nil { - return nil, err - } - - // return the coordinate object with the col and row we calculated - return &Coord{Short(col), Short(row)}, nil - } - - // it didn't work so return an error - return nil, fmt.Errorf("could not compute the cursor position using ascii escape sequences") -} - -// Size returns the height and width of the terminal. -func Size() (*Coord, error) { - // the general approach here is to move the cursor to the very bottom - // of the terminal, ask for the current location and then move the - // cursor back where we started - - // save the current location of the cursor - origin, err := CursorLocation() - if err != nil { - return nil, err - } - - // move the cursor to the very bottom of the terminal - CursorMove(999, 999) - - // ask for the current location - bottom, err := CursorLocation() - if err != nil { - return nil, err - } - - // move back where we began - CursorUp(int(bottom.Y - origin.Y)) - CursorHorizontalAbsolute(int(origin.X)) - - // sice the bottom was calcuated in the lower right corner, it - // is the dimensions we are looking for - return bottom, nil -} diff --git a/vendor/github.com/kataras/survey/terminal/cursor_windows.go b/vendor/github.com/kataras/survey/terminal/cursor_windows.go deleted file mode 100644 index 9a7d5b4721..0000000000 --- a/vendor/github.com/kataras/survey/terminal/cursor_windows.go +++ /dev/null @@ -1,101 +0,0 @@ -package terminal - -import ( - "os" - "syscall" - "unsafe" -) - -func CursorUp(n int) { - cursorMove(0, n) -} - -func CursorDown(n int) { - cursorMove(0, -1*n) -} - -func CursorForward(n int) { - cursorMove(n, 0) -} - -func CursorBack(n int) { - cursorMove(-1*n, 0) -} - -func cursorMove(x int, y int) { - handle := syscall.Handle(os.Stdout.Fd()) - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - - var cursor Coord - cursor.X = csbi.cursorPosition.X + Short(x) - cursor.Y = csbi.cursorPosition.Y + Short(y) - - procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor)))) -} - -func CursorNextLine(n int) { - CursorUp(n) - CursorHorizontalAbsolute(0) -} - -func CursorPreviousLine(n int) { - CursorDown(n) - CursorHorizontalAbsolute(0) -} - -func CursorHorizontalAbsolute(x int) { - handle := syscall.Handle(os.Stdout.Fd()) - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - - var cursor Coord - cursor.X = Short(x) - cursor.Y = csbi.cursorPosition.Y - - if csbi.size.X < cursor.X { - cursor.X = csbi.size.X - } - - procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor)))) -} - -func CursorShow() { - handle := syscall.Handle(os.Stdout.Fd()) - - var cci consoleCursorInfo - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) - cci.visible = 1 - - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) -} - -func CursorHide() { - handle := syscall.Handle(os.Stdout.Fd()) - - var cci consoleCursorInfo - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) - cci.visible = 0 - - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) -} - -func CursorLocation() (Coord, error) { - handle := syscall.Handle(os.Stdout.Fd()) - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - - return csbi.cursorPosition, nil -} - -func Size() (Coord, error) { - handle := syscall.Handle(os.Stdout.Fd()) - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - - return csbi.size, nil -} diff --git a/vendor/github.com/kataras/survey/terminal/display.go b/vendor/github.com/kataras/survey/terminal/display.go deleted file mode 100644 index 0f014b1353..0000000000 --- a/vendor/github.com/kataras/survey/terminal/display.go +++ /dev/null @@ -1,9 +0,0 @@ -package terminal - -type EraseLineMode int - -const ( - ERASE_LINE_END EraseLineMode = iota - ERASE_LINE_START - ERASE_LINE_ALL -) diff --git a/vendor/github.com/kataras/survey/terminal/display_posix.go b/vendor/github.com/kataras/survey/terminal/display_posix.go deleted file mode 100644 index 47a765c794..0000000000 --- a/vendor/github.com/kataras/survey/terminal/display_posix.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build !windows - -package terminal - -import ( - "fmt" -) - -func EraseLine(mode EraseLineMode) { - fmt.Printf("\x1b[%dK", mode) -} diff --git a/vendor/github.com/kataras/survey/terminal/display_windows.go b/vendor/github.com/kataras/survey/terminal/display_windows.go deleted file mode 100644 index bb1ef45d7b..0000000000 --- a/vendor/github.com/kataras/survey/terminal/display_windows.go +++ /dev/null @@ -1,28 +0,0 @@ -package terminal - -import ( - "os" - "syscall" - "unsafe" -) - -func EraseLine(mode EraseLineMode) { - handle := syscall.Handle(os.Stdout.Fd()) - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - - var w uint32 - var x Short - cursor := csbi.cursorPosition - switch mode { - case ERASE_LINE_END: - x = csbi.size.X - case ERASE_LINE_START: - x = 0 - case ERASE_LINE_ALL: - cursor.X = 0 - x = csbi.size.X - } - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w))) -} diff --git a/vendor/github.com/kataras/survey/terminal/error.go b/vendor/github.com/kataras/survey/terminal/error.go deleted file mode 100644 index 710c361406..0000000000 --- a/vendor/github.com/kataras/survey/terminal/error.go +++ /dev/null @@ -1,9 +0,0 @@ -package terminal - -import ( - "errors" -) - -var ( - InterruptErr = errors.New("interrupt") -) diff --git a/vendor/github.com/kataras/survey/terminal/output.go b/vendor/github.com/kataras/survey/terminal/output.go deleted file mode 100644 index fabbb9fcde..0000000000 --- a/vendor/github.com/kataras/survey/terminal/output.go +++ /dev/null @@ -1,20 +0,0 @@ -// +build !windows - -package terminal - -import ( - "io" - "os" -) - -// Returns special stdout, which converts escape sequences to Windows API calls -// on Windows environment. -func NewAnsiStdout() io.Writer { - return os.Stdout -} - -// Returns special stderr, which converts escape sequences to Windows API calls -// on Windows environment. -func NewAnsiStderr() io.Writer { - return os.Stderr -} diff --git a/vendor/github.com/kataras/survey/terminal/output_windows.go b/vendor/github.com/kataras/survey/terminal/output_windows.go deleted file mode 100644 index 7d7f47fb14..0000000000 --- a/vendor/github.com/kataras/survey/terminal/output_windows.go +++ /dev/null @@ -1,228 +0,0 @@ -package terminal - -import ( - "bytes" - "fmt" - "io" - "os" - "strconv" - "strings" - "syscall" - "unsafe" - - "github.com/mattn/go-isatty" -) - -var ( - singleArgFunctions = map[rune]func(int){ - 'A': CursorUp, - 'B': CursorDown, - 'C': CursorForward, - 'D': CursorBack, - 'E': CursorNextLine, - 'F': CursorPreviousLine, - 'G': CursorHorizontalAbsolute, - } -) - -const ( - foregroundBlue = 0x1 - foregroundGreen = 0x2 - foregroundRed = 0x4 - foregroundIntensity = 0x8 - foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) - backgroundBlue = 0x10 - backgroundGreen = 0x20 - backgroundRed = 0x40 - backgroundIntensity = 0x80 - backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) -) - -type Writer struct { - out io.Writer - handle syscall.Handle - orgAttr word -} - -func NewAnsiStdout() io.Writer { - var csbi consoleScreenBufferInfo - out := os.Stdout - if !isatty.IsTerminal(out.Fd()) { - return out - } - handle := syscall.Handle(out.Fd()) - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - return &Writer{out: out, handle: handle, orgAttr: csbi.attributes} -} - -func NewAnsiStderr() io.Writer { - var csbi consoleScreenBufferInfo - out := os.Stderr - if !isatty.IsTerminal(out.Fd()) { - return out - } - handle := syscall.Handle(out.Fd()) - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - return &Writer{out: out, handle: handle, orgAttr: csbi.attributes} -} - -func (w *Writer) Write(data []byte) (n int, err error) { - r := bytes.NewReader(data) - - for { - ch, size, err := r.ReadRune() - if err != nil { - break - } - n += size - - switch ch { - case '\x1b': - size, err = w.handleEscape(r) - n += size - if err != nil { - break - } - default: - fmt.Fprint(w.out, string(ch)) - } - } - return -} - -func (w *Writer) handleEscape(r *bytes.Reader) (n int, err error) { - buf := make([]byte, 0, 10) - buf = append(buf, "\x1b"...) - - // Check '[' continues after \x1b - ch, size, err := r.ReadRune() - if err != nil { - fmt.Fprint(w.out, string(buf)) - return - } - n += size - if ch != '[' { - fmt.Fprint(w.out, string(buf)) - return - } - - // Parse escape code - var code rune - argBuf := make([]byte, 0, 10) - for { - ch, size, err = r.ReadRune() - if err != nil { - fmt.Fprint(w.out, string(buf)) - return - } - n += size - if ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') { - code = ch - break - } - argBuf = append(argBuf, string(ch)...) - } - - w.applyEscapeCode(buf, string(argBuf), code) - return -} - -func (w *Writer) applyEscapeCode(buf []byte, arg string, code rune) { - switch arg + string(code) { - case "?25h": - CursorShow() - return - case "?25l": - CursorHide() - return - } - - if f, ok := singleArgFunctions[code]; ok { - if n, err := strconv.Atoi(arg); err == nil { - f(n) - return - } - } - - switch code { - case 'm': - w.applySelectGraphicRendition(arg) - default: - buf = append(buf, string(code)...) - fmt.Fprint(w.out, string(buf)) - } -} - -// Original implementation: https://github.com/mattn/go-colorable -func (w *Writer) applySelectGraphicRendition(arg string) { - if arg == "" { - procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.orgAttr)) - return - } - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) - attr := csbi.attributes - - for _, param := range strings.Split(arg, ";") { - n, err := strconv.Atoi(param) - if err != nil { - continue - } - - switch { - case n == 0 || n == 100: - attr = w.orgAttr - case 1 <= n && n <= 5: - attr |= foregroundIntensity - case 30 <= n && n <= 37: - attr = (attr & backgroundMask) - if (n-30)&1 != 0 { - attr |= foregroundRed - } - if (n-30)&2 != 0 { - attr |= foregroundGreen - } - if (n-30)&4 != 0 { - attr |= foregroundBlue - } - case 40 <= n && n <= 47: - attr = (attr & foregroundMask) - if (n-40)&1 != 0 { - attr |= backgroundRed - } - if (n-40)&2 != 0 { - attr |= backgroundGreen - } - if (n-40)&4 != 0 { - attr |= backgroundBlue - } - case 90 <= n && n <= 97: - attr = (attr & backgroundMask) - attr |= foregroundIntensity - if (n-90)&1 != 0 { - attr |= foregroundRed - } - if (n-90)&2 != 0 { - attr |= foregroundGreen - } - if (n-90)&4 != 0 { - attr |= foregroundBlue - } - case 100 <= n && n <= 107: - attr = (attr & foregroundMask) - attr |= backgroundIntensity - if (n-100)&1 != 0 { - attr |= backgroundRed - } - if (n-100)&2 != 0 { - attr |= backgroundGreen - } - if (n-100)&4 != 0 { - attr |= backgroundBlue - } - } - } - - procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) -} diff --git a/vendor/github.com/kataras/survey/terminal/print.go b/vendor/github.com/kataras/survey/terminal/print.go deleted file mode 100644 index d7b6f612fc..0000000000 --- a/vendor/github.com/kataras/survey/terminal/print.go +++ /dev/null @@ -1,25 +0,0 @@ -package terminal - -import ( - "fmt" -) - -var ( - Stdout = NewAnsiStdout() -) - -// Print prints given arguments with escape sequence conversion for windows. -func Print(a ...interface{}) (n int, err error) { - return fmt.Fprint(Stdout, a...) -} - -// Printf prints a given format with escape sequence conversion for windows. -func Printf(format string, a ...interface{}) (n int, err error) { - return fmt.Fprintf(Stdout, format, a...) -} - -// Println prints given arguments with newline and escape sequence conversion -// for windows. -func Println(a ...interface{}) (n int, err error) { - return fmt.Fprintln(Stdout, a...) -} diff --git a/vendor/github.com/kataras/survey/terminal/runereader.go b/vendor/github.com/kataras/survey/terminal/runereader.go deleted file mode 100644 index 96226425d5..0000000000 --- a/vendor/github.com/kataras/survey/terminal/runereader.go +++ /dev/null @@ -1,183 +0,0 @@ -package terminal - -import ( - "os" - "unicode" -) - -type RuneReader struct { - Input *os.File - - state runeReaderState -} - -func NewRuneReader(input *os.File) *RuneReader { - return &RuneReader{ - Input: input, - state: newRuneReaderState(input), - } -} - -func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) { - line := []rune{} - - // we only care about horizontal displacements from the origin so start counting at 0 - index := 0 - - for { - // wait for some input - r, _, err := rr.ReadRune() - if err != nil { - return line, err - } - - // if the user pressed enter or some other newline/termination like ctrl+d - if r == '\r' || r == '\n' || r == KeyEndTransmission { - // go to the beginning of the next line - Print("\r\n") - - // we're done processing the input - return line, nil - } - - // if the user interrupts (ie with ctrl+c) - if r == KeyInterrupt { - // go to the beginning of the next line - Print("\r\n") - - // we're done processing the input, and treat interrupt like an error - return line, InterruptErr - } - - // allow for backspace/delete editing of inputs - if r == KeyBackspace || r == KeyDelete { - // and we're not at the beginning of the line - if index > 0 && len(line) > 0 { - // if we are at the end of the word - if index == len(line) { - // just remove the last letter from the internal representation - line = line[:len(line)-1] - - // go back one - CursorBack(1) - - // clear the rest of the line - EraseLine(ERASE_LINE_END) - } else { - // we need to remove a character from the middle of the word - - // remove the current index from the list - line = append(line[:index-1], line[index:]...) - - // go back one space so we can clear the rest - CursorBack(1) - - // clear the rest of the line - EraseLine(ERASE_LINE_END) - - // print what comes after - Print(string(line[index-1:])) - - // leave the cursor where the user left it - CursorBack(len(line) - index + 1) - } - - // decrement the index - index-- - } else { - // otherwise the user pressed backspace while at the beginning of the line - soundBell() - } - - // we're done processing this key - continue - } - - // if the left arrow is pressed - if r == KeyArrowLeft { - // and we have space to the left - if index > 0 { - // move the cursor to the left - CursorBack(1) - // decrement the index - index-- - - } else { - // otherwise we are at the beginning of where we started reading lines - // sound the bell - soundBell() - } - - // we're done processing this key press - continue - } - - // if the right arrow is pressed - if r == KeyArrowRight { - // and we have space to the right of the word - if index < len(line) { - // move the cursor to the right - CursorForward(1) - // increment the index - index++ - - } else { - // otherwise we are at the end of the word and can't go past - // sound the bell - soundBell() - } - - // we're done processing this key press - continue - } - - // if the letter is another escape sequence - if unicode.IsControl(r) { - // ignore it - continue - } - - // the user pressed a regular key - - // if we are at the end of the line - if index == len(line) { - // just append the character at the end of the line - line = append(line, r) - // increment the location counter - index++ - - // if we don't need to mask the input - if mask == 0 { - // just print the character the user pressed - Printf("%c", r) - } else { - // otherwise print the mask we were given - Printf("%c", mask) - } - } else { - // we are in the middle of the word so we need to insert the character the user pressed - line = append(line[:index], append([]rune{r}, line[index:]...)...) - - // visually insert the character by deleting the rest of the line - EraseLine(ERASE_LINE_END) - - // print the rest of the word after - for _, char := range line[index:] { - // if we don't need to mask the input - if mask == 0 { - // just print the character the user pressed - Printf("%c", char) - } else { - // otherwise print the mask we were given - Printf("%c", mask) - } - } - - // leave the cursor where the user left it - CursorBack(len(line) - index - 1) - - // accommodate the new letter in our counter - index++ - } - } -} diff --git a/vendor/github.com/kataras/survey/terminal/runereader_bsd.go b/vendor/github.com/kataras/survey/terminal/runereader_bsd.go deleted file mode 100644 index 6ea340923a..0000000000 --- a/vendor/github.com/kataras/survey/terminal/runereader_bsd.go +++ /dev/null @@ -1,13 +0,0 @@ -// copied from: https://github.com/golang/crypto/blob/master/ssh/terminal/util_bsd.go -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin dragonfly freebsd netbsd openbsd - -package terminal - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA -const ioctlWriteTermios = syscall.TIOCSETA diff --git a/vendor/github.com/kataras/survey/terminal/runereader_linux.go b/vendor/github.com/kataras/survey/terminal/runereader_linux.go deleted file mode 100644 index 74e1b697ff..0000000000 --- a/vendor/github.com/kataras/survey/terminal/runereader_linux.go +++ /dev/null @@ -1,12 +0,0 @@ -// copied from https://github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package terminal - -// These constants are declared here, rather than importing -// them from the syscall package as some syscall packages, even -// on linux, for example gccgo, do not declare them. -const ioctlReadTermios = 0x5401 // syscall.TCGETS -const ioctlWriteTermios = 0x5402 // syscall.TCSETS diff --git a/vendor/github.com/kataras/survey/terminal/runereader_posix.go b/vendor/github.com/kataras/survey/terminal/runereader_posix.go deleted file mode 100644 index 1f463fb8cc..0000000000 --- a/vendor/github.com/kataras/survey/terminal/runereader_posix.go +++ /dev/null @@ -1,84 +0,0 @@ -// +build !windows - -// The terminal mode manipulation code is derived heavily from: -// https://github.com/golang/crypto/blob/master/ssh/terminal/util.go: -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package terminal - -import ( - "bufio" - "fmt" - "os" - "syscall" - "unsafe" -) - -type runeReaderState struct { - term syscall.Termios - buf *bufio.Reader -} - -func newRuneReaderState(input *os.File) runeReaderState { - return runeReaderState{ - buf: bufio.NewReader(input), - } -} - -// For reading runes we just want to disable echo. -func (rr *RuneReader) SetTermMode() error { - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.Input.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 { - return err - } - - newState := rr.state.term - newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG - - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.Input.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { - return err - } - - return nil -} - -func (rr *RuneReader) RestoreTermMode() error { - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.Input.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 { - return err - } - return nil -} - -func (rr *RuneReader) ReadRune() (rune, int, error) { - r, size, err := rr.state.buf.ReadRune() - if err != nil { - return r, size, err - } - // parse ^[ sequences to look for arrow keys - if r == '\033' { - r, size, err = rr.state.buf.ReadRune() - if err != nil { - return r, size, err - } - if r != '[' { - return r, size, fmt.Errorf("Unexpected Escape Sequence: %q", []rune{'\033', r}) - } - r, size, err = rr.state.buf.ReadRune() - if err != nil { - return r, size, err - } - switch r { - case 'D': - return KeyArrowLeft, 1, nil - case 'C': - return KeyArrowRight, 1, nil - case 'A': - return KeyArrowUp, 1, nil - case 'B': - return KeyArrowDown, 1, nil - } - return r, size, fmt.Errorf("Unknown Escape Sequence: %q", []rune{'\033', '[', r}) - } - return r, size, err -} diff --git a/vendor/github.com/kataras/survey/terminal/runereader_windows.go b/vendor/github.com/kataras/survey/terminal/runereader_windows.go deleted file mode 100644 index a0cea826ac..0000000000 --- a/vendor/github.com/kataras/survey/terminal/runereader_windows.go +++ /dev/null @@ -1,130 +0,0 @@ -package terminal - -import ( - "os" - "syscall" - "unsafe" -) - -var ( - dll = syscall.NewLazyDLL("kernel32.dll") - setConsoleMode = dll.NewProc("SetConsoleMode") - getConsoleMode = dll.NewProc("GetConsoleMode") - readConsoleInput = dll.NewProc("ReadConsoleInputW") -) - -const ( - EVENT_KEY = 0x0001 - - // key codes for arrow keys - // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx - VK_LEFT = 0x25 - VK_UP = 0x26 - VK_RIGHT = 0x27 - VK_DOWN = 0x28 - - RIGHT_CTRL_PRESSED = 0x0004 - LEFT_CTRL_PRESSED = 0x0008 - - ENABLE_ECHO_INPUT uint32 = 0x0004 - ENABLE_LINE_INPUT uint32 = 0x0002 - ENABLE_PROCESSED_INPUT uint32 = 0x0001 -) - -type inputRecord struct { - eventType uint16 - padding uint16 - event [16]byte -} - -type keyEventRecord struct { - bKeyDown int32 - wRepeatCount uint16 - wVirtualKeyCode uint16 - wVirtualScanCode uint16 - unicodeChar uint16 - wdControlKeyState uint32 -} - -type runeReaderState struct { - term uint32 -} - -func newRuneReaderState(input *os.File) runeReaderState { - return runeReaderState{} -} - -func (rr *RuneReader) SetTermMode() error { - r, _, err := getConsoleMode.Call(uintptr(rr.Input.Fd()), uintptr(unsafe.Pointer(&rr.state.term))) - // windows return 0 on error - if r == 0 { - return err - } - - newState := rr.state.term - newState &^= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT - r, _, err = setConsoleMode.Call(uintptr(rr.Input.Fd()), uintptr(newState)) - // windows return 0 on error - if r == 0 { - return err - } - return nil -} - -func (rr *RuneReader) RestoreTermMode() error { - r, _, err := setConsoleMode.Call(uintptr(rr.Input.Fd()), uintptr(rr.state.term)) - // windows return 0 on error - if r == 0 { - return err - } - return nil -} - -func (rr *RuneReader) ReadRune() (rune, int, error) { - ir := &inputRecord{} - bytesRead := 0 - for { - rv, _, e := readConsoleInput.Call(rr.Input.Fd(), uintptr(unsafe.Pointer(ir)), 1, uintptr(unsafe.Pointer(&bytesRead))) - // windows returns non-zero to indicate success - if rv == 0 && e != nil { - return 0, 0, e - } - - if ir.eventType != EVENT_KEY { - continue - } - - // the event data is really a c struct union, so here we have to do an usafe - // cast to put the data into the keyEventRecord (since we have already verified - // above that this event does correspond to a key event - key := (*keyEventRecord)(unsafe.Pointer(&ir.event[0])) - // we only care about key down events - if key.bKeyDown == 0 { - continue - } - if key.wdControlKeyState&(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED) != 0 && key.unicodeChar == 'C' { - return KeyInterrupt, bytesRead, nil - } - - // not a normal character so look up the input sequence from the - // virtual key code mappings (VK_*) - if key.unicodeChar == 0 { - switch key.wVirtualKeyCode { - case VK_DOWN: - return KeyArrowDown, bytesRead, nil - case VK_LEFT: - return KeyArrowLeft, bytesRead, nil - case VK_RIGHT: - return KeyArrowRight, bytesRead, nil - case VK_UP: - return KeyArrowUp, bytesRead, nil - default: - // not a virtual key that we care about so just continue on to - // the next input key - continue - } - } - r := rune(key.unicodeChar) - return r, bytesRead, nil - } -} diff --git a/vendor/github.com/kataras/survey/terminal/sequences.go b/vendor/github.com/kataras/survey/terminal/sequences.go deleted file mode 100644 index 0fc1396944..0000000000 --- a/vendor/github.com/kataras/survey/terminal/sequences.go +++ /dev/null @@ -1,18 +0,0 @@ -package terminal - -const ( - KeyArrowLeft = '\x02' - KeyArrowRight = '\x06' - KeyArrowUp = '\x10' - KeyArrowDown = '\x0e' - KeySpace = ' ' - KeyEnter = '\r' - KeyBackspace = '\b' - KeyDelete = '\x7f' - KeyInterrupt = '\x03' - KeyEndTransmission = '\x04' -) - -func soundBell() { - Print("\a") -} diff --git a/vendor/github.com/kataras/survey/terminal/syscall_windows.go b/vendor/github.com/kataras/survey/terminal/syscall_windows.go deleted file mode 100644 index 63b85d4c39..0000000000 --- a/vendor/github.com/kataras/survey/terminal/syscall_windows.go +++ /dev/null @@ -1,39 +0,0 @@ -package terminal - -import ( - "syscall" -) - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") - procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") - procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") - procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") - procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") - procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") -) - -type wchar uint16 -type dword uint32 -type word uint16 - -type smallRect struct { - left Short - top Short - right Short - bottom Short -} - -type consoleScreenBufferInfo struct { - size Coord - cursorPosition Coord - attributes word - window smallRect - maximumWindowSize Coord -} - -type consoleCursorInfo struct { - size dword - visible int32 -} diff --git a/vendor/github.com/kataras/survey/terminal/terminal.go b/vendor/github.com/kataras/survey/terminal/terminal.go deleted file mode 100644 index 4b59062c7d..0000000000 --- a/vendor/github.com/kataras/survey/terminal/terminal.go +++ /dev/null @@ -1,8 +0,0 @@ -package terminal - -type Short int16 - -type Coord struct { - X Short - Y Short -} diff --git a/vendor/github.com/kataras/survey/transform.go b/vendor/github.com/kataras/survey/transform.go deleted file mode 100644 index ccc75e0843..0000000000 --- a/vendor/github.com/kataras/survey/transform.go +++ /dev/null @@ -1,76 +0,0 @@ -package survey - -import ( - "reflect" - "strings" -) - -// TransformString returns a `Transformer` based on the "f" -// function which accepts a string representation of the answer -// and returns a new one, transformed, answer. -// Take for example the functions inside the std `strings` package, -// they can be converted to a compatible `Transformer` by using this function, -// i.e: `TransformString(strings.Title)`, `TransformString(strings.ToUpper)`. -// -// Note that `TransformString` is just a helper, `Transformer` can be used -// to transform any type of answer. -func TransformString(f func(s string) string) Transformer { - return func(ans interface{}) interface{} { - // if the answer value passed in is the zero value of the appropriate type - if isZero(reflect.ValueOf(ans)) { - // skip this `Transformer` by returning a nil value. - // The original answer will be not affected, - // see survey.go#L125. - return nil - } - - // "ans" is never nil here, so we don't have to check that - // see survey.go#L97 for more. - // Make sure that the the answer's value was a typeof string. - s, ok := ans.(string) - if !ok { - return nil - } - - return f(s) - } -} - -// ToLower is a `Transformer`. -// It receives an answer value -// and returns a copy of the "ans" -// with all Unicode letters mapped to their lower case. -// -// Note that if "ans" is not a string then it will -// return a nil value, meaning that the above answer -// will not be affected by this call at all. -func ToLower(ans interface{}) interface{} { - transformer := TransformString(strings.ToLower) - return transformer(ans) -} - -// Title is a `Transformer`. -// It receives an answer value -// and returns a copy of the "ans" -// with all Unicode letters that begin words -// mapped to their title case. -// -// Note that if "ans" is not a string then it will -// return a nil value, meaning that the above answer -// will not be affected by this call at all. -func Title(ans interface{}) interface{} { - transformer := TransformString(strings.Title) - return transformer(ans) -} - -// ComposeTransformers is a variadic function used to create one transformer from many. -func ComposeTransformers(transformers ...Transformer) Transformer { - // return a transformer that calls each one sequentially - return func(ans interface{}) interface{} { - // execute each transformer - for _, t := range transformers { - ans = t(ans) - } - return ans - } -} diff --git a/vendor/github.com/kataras/survey/validate.go b/vendor/github.com/kataras/survey/validate.go deleted file mode 100644 index 50162c2582..0000000000 --- a/vendor/github.com/kataras/survey/validate.go +++ /dev/null @@ -1,92 +0,0 @@ -package survey - -import ( - "errors" - "fmt" - "reflect" -) - -// Required does not allow an empty value -func Required(val interface{}) error { - // if the value passed in is the zero value of the appropriate type - if isZero(reflect.ValueOf(val)) { - return errors.New("Value is required") - } - return nil -} - -// MaxLength requires that the string is no longer than the specified value -func MaxLength(length int) Validator { - // return a validator that checks the length of the string - return func(val interface{}) error { - if str, ok := val.(string); ok { - // if the string is longer than the given value - if len(str) > length { - // yell loudly - return fmt.Errorf("value is too long. Max length is %v", length) - } - } else { - // otherwise we cannot convert the value into a string and cannot enforce length - return fmt.Errorf("cannot enforce length on response of type %v", reflect.TypeOf(val).Name()) - } - - // the input is fine - return nil - } -} - -// MinLength requires that the string is longer or equal in length to the specified value -func MinLength(length int) Validator { - // return a validator that checks the length of the string - return func(val interface{}) error { - if str, ok := val.(string); ok { - // if the string is shorter than the given value - if len(str) < length { - // yell loudly - return fmt.Errorf("value is too short. Min length is %v", length) - } - } else { - // otherwise we cannot convert the value into a string and cannot enforce length - return fmt.Errorf("cannot enforce length on response of type %v", reflect.TypeOf(val).Name()) - } - - // the input is fine - return nil - } -} - -// ComposeValidators is a variadic function used to create one validator from many. -func ComposeValidators(validators ...Validator) Validator { - // return a validator that calls each one sequentially - return func(val interface{}) error { - // execute each validator - for _, validator := range validators { - // if the answer's value is not valid - if err := validator(val); err != nil { - // return the error - return err - } - } - // we passed all validators, the answer is valid - return nil - } -} - -// isZero returns true if the passed value is the zero object -func isZero(v reflect.Value) bool { - switch v.Kind() { - case reflect.Slice, reflect.Map: - return v.Len() == 0 - // fixes: - // if confirm and `Validate: survey.Required` is used - // and answer is "No" (== false) - // then it shows "Sorry, your reply was invalid: Value is required" - // and it stucks there. - // This happens because 'false' is the zero value of a "bool" type. - case reflect.Bool: - return false - } - - // compare the types directly with more general coverage - return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/LICENSE b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/LICENSE deleted file mode 100644 index 91b5cef30e..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Yasuhiro Matsumoto - -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. diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_appengine.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_appengine.go deleted file mode 100644 index 1f28d773d7..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_appengine.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build appengine - -package colorable - -import ( - "io" - "os" - - _ "github.com/mattn/go-isatty" -) - -// NewColorable return new instance of Writer which handle escape sequence. -func NewColorable(file *os.File) io.Writer { - if file == nil { - panic("nil passed instead of *os.File to NewColorable()") - } - - return file -} - -// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. -func NewColorableStdout() io.Writer { - return os.Stdout -} - -// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. -func NewColorableStderr() io.Writer { - return os.Stderr -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_others.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_others.go deleted file mode 100644 index 887f203dc7..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_others.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build !windows -// +build !appengine - -package colorable - -import ( - "io" - "os" - - _ "github.com/mattn/go-isatty" -) - -// NewColorable return new instance of Writer which handle escape sequence. -func NewColorable(file *os.File) io.Writer { - if file == nil { - panic("nil passed instead of *os.File to NewColorable()") - } - - return file -} - -// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. -func NewColorableStdout() io.Writer { - return os.Stdout -} - -// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. -func NewColorableStderr() io.Writer { - return os.Stderr -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_windows.go deleted file mode 100644 index 15a014fd30..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_windows.go +++ /dev/null @@ -1,968 +0,0 @@ -// +build windows -// +build !appengine - -package colorable - -import ( - "bytes" - "io" - "math" - "os" - "strconv" - "strings" - "syscall" - "unsafe" - - "github.com/mattn/go-isatty" -) - -const ( - foregroundBlue = 0x1 - foregroundGreen = 0x2 - foregroundRed = 0x4 - foregroundIntensity = 0x8 - foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) - backgroundBlue = 0x10 - backgroundGreen = 0x20 - backgroundRed = 0x40 - backgroundIntensity = 0x80 - backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) -) - -const ( - genericRead = 0x80000000 - genericWrite = 0x40000000 -) - -const ( - consoleTextmodeBuffer = 0x1 -) - -type wchar uint16 -type short int16 -type dword uint32 -type word uint16 - -type coord struct { - x short - y short -} - -type smallRect struct { - left short - top short - right short - bottom short -} - -type consoleScreenBufferInfo struct { - size coord - cursorPosition coord - attributes word - window smallRect - maximumWindowSize coord -} - -type consoleCursorInfo struct { - size dword - visible int32 -} - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") - procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") - procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") - procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") - procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") - procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") - procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") - procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW") - procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer") -) - -// Writer provide colorable Writer to the console -type Writer struct { - out io.Writer - handle syscall.Handle - althandle syscall.Handle - oldattr word - oldpos coord - rest bytes.Buffer -} - -// NewColorable return new instance of Writer which handle escape sequence from File. -func NewColorable(file *os.File) io.Writer { - if file == nil { - panic("nil passed instead of *os.File to NewColorable()") - } - - if isatty.IsTerminal(file.Fd()) { - var csbi consoleScreenBufferInfo - handle := syscall.Handle(file.Fd()) - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}} - } - return file -} - -// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. -func NewColorableStdout() io.Writer { - return NewColorable(os.Stdout) -} - -// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. -func NewColorableStderr() io.Writer { - return NewColorable(os.Stderr) -} - -var color256 = map[int]int{ - 0: 0x000000, - 1: 0x800000, - 2: 0x008000, - 3: 0x808000, - 4: 0x000080, - 5: 0x800080, - 6: 0x008080, - 7: 0xc0c0c0, - 8: 0x808080, - 9: 0xff0000, - 10: 0x00ff00, - 11: 0xffff00, - 12: 0x0000ff, - 13: 0xff00ff, - 14: 0x00ffff, - 15: 0xffffff, - 16: 0x000000, - 17: 0x00005f, - 18: 0x000087, - 19: 0x0000af, - 20: 0x0000d7, - 21: 0x0000ff, - 22: 0x005f00, - 23: 0x005f5f, - 24: 0x005f87, - 25: 0x005faf, - 26: 0x005fd7, - 27: 0x005fff, - 28: 0x008700, - 29: 0x00875f, - 30: 0x008787, - 31: 0x0087af, - 32: 0x0087d7, - 33: 0x0087ff, - 34: 0x00af00, - 35: 0x00af5f, - 36: 0x00af87, - 37: 0x00afaf, - 38: 0x00afd7, - 39: 0x00afff, - 40: 0x00d700, - 41: 0x00d75f, - 42: 0x00d787, - 43: 0x00d7af, - 44: 0x00d7d7, - 45: 0x00d7ff, - 46: 0x00ff00, - 47: 0x00ff5f, - 48: 0x00ff87, - 49: 0x00ffaf, - 50: 0x00ffd7, - 51: 0x00ffff, - 52: 0x5f0000, - 53: 0x5f005f, - 54: 0x5f0087, - 55: 0x5f00af, - 56: 0x5f00d7, - 57: 0x5f00ff, - 58: 0x5f5f00, - 59: 0x5f5f5f, - 60: 0x5f5f87, - 61: 0x5f5faf, - 62: 0x5f5fd7, - 63: 0x5f5fff, - 64: 0x5f8700, - 65: 0x5f875f, - 66: 0x5f8787, - 67: 0x5f87af, - 68: 0x5f87d7, - 69: 0x5f87ff, - 70: 0x5faf00, - 71: 0x5faf5f, - 72: 0x5faf87, - 73: 0x5fafaf, - 74: 0x5fafd7, - 75: 0x5fafff, - 76: 0x5fd700, - 77: 0x5fd75f, - 78: 0x5fd787, - 79: 0x5fd7af, - 80: 0x5fd7d7, - 81: 0x5fd7ff, - 82: 0x5fff00, - 83: 0x5fff5f, - 84: 0x5fff87, - 85: 0x5fffaf, - 86: 0x5fffd7, - 87: 0x5fffff, - 88: 0x870000, - 89: 0x87005f, - 90: 0x870087, - 91: 0x8700af, - 92: 0x8700d7, - 93: 0x8700ff, - 94: 0x875f00, - 95: 0x875f5f, - 96: 0x875f87, - 97: 0x875faf, - 98: 0x875fd7, - 99: 0x875fff, - 100: 0x878700, - 101: 0x87875f, - 102: 0x878787, - 103: 0x8787af, - 104: 0x8787d7, - 105: 0x8787ff, - 106: 0x87af00, - 107: 0x87af5f, - 108: 0x87af87, - 109: 0x87afaf, - 110: 0x87afd7, - 111: 0x87afff, - 112: 0x87d700, - 113: 0x87d75f, - 114: 0x87d787, - 115: 0x87d7af, - 116: 0x87d7d7, - 117: 0x87d7ff, - 118: 0x87ff00, - 119: 0x87ff5f, - 120: 0x87ff87, - 121: 0x87ffaf, - 122: 0x87ffd7, - 123: 0x87ffff, - 124: 0xaf0000, - 125: 0xaf005f, - 126: 0xaf0087, - 127: 0xaf00af, - 128: 0xaf00d7, - 129: 0xaf00ff, - 130: 0xaf5f00, - 131: 0xaf5f5f, - 132: 0xaf5f87, - 133: 0xaf5faf, - 134: 0xaf5fd7, - 135: 0xaf5fff, - 136: 0xaf8700, - 137: 0xaf875f, - 138: 0xaf8787, - 139: 0xaf87af, - 140: 0xaf87d7, - 141: 0xaf87ff, - 142: 0xafaf00, - 143: 0xafaf5f, - 144: 0xafaf87, - 145: 0xafafaf, - 146: 0xafafd7, - 147: 0xafafff, - 148: 0xafd700, - 149: 0xafd75f, - 150: 0xafd787, - 151: 0xafd7af, - 152: 0xafd7d7, - 153: 0xafd7ff, - 154: 0xafff00, - 155: 0xafff5f, - 156: 0xafff87, - 157: 0xafffaf, - 158: 0xafffd7, - 159: 0xafffff, - 160: 0xd70000, - 161: 0xd7005f, - 162: 0xd70087, - 163: 0xd700af, - 164: 0xd700d7, - 165: 0xd700ff, - 166: 0xd75f00, - 167: 0xd75f5f, - 168: 0xd75f87, - 169: 0xd75faf, - 170: 0xd75fd7, - 171: 0xd75fff, - 172: 0xd78700, - 173: 0xd7875f, - 174: 0xd78787, - 175: 0xd787af, - 176: 0xd787d7, - 177: 0xd787ff, - 178: 0xd7af00, - 179: 0xd7af5f, - 180: 0xd7af87, - 181: 0xd7afaf, - 182: 0xd7afd7, - 183: 0xd7afff, - 184: 0xd7d700, - 185: 0xd7d75f, - 186: 0xd7d787, - 187: 0xd7d7af, - 188: 0xd7d7d7, - 189: 0xd7d7ff, - 190: 0xd7ff00, - 191: 0xd7ff5f, - 192: 0xd7ff87, - 193: 0xd7ffaf, - 194: 0xd7ffd7, - 195: 0xd7ffff, - 196: 0xff0000, - 197: 0xff005f, - 198: 0xff0087, - 199: 0xff00af, - 200: 0xff00d7, - 201: 0xff00ff, - 202: 0xff5f00, - 203: 0xff5f5f, - 204: 0xff5f87, - 205: 0xff5faf, - 206: 0xff5fd7, - 207: 0xff5fff, - 208: 0xff8700, - 209: 0xff875f, - 210: 0xff8787, - 211: 0xff87af, - 212: 0xff87d7, - 213: 0xff87ff, - 214: 0xffaf00, - 215: 0xffaf5f, - 216: 0xffaf87, - 217: 0xffafaf, - 218: 0xffafd7, - 219: 0xffafff, - 220: 0xffd700, - 221: 0xffd75f, - 222: 0xffd787, - 223: 0xffd7af, - 224: 0xffd7d7, - 225: 0xffd7ff, - 226: 0xffff00, - 227: 0xffff5f, - 228: 0xffff87, - 229: 0xffffaf, - 230: 0xffffd7, - 231: 0xffffff, - 232: 0x080808, - 233: 0x121212, - 234: 0x1c1c1c, - 235: 0x262626, - 236: 0x303030, - 237: 0x3a3a3a, - 238: 0x444444, - 239: 0x4e4e4e, - 240: 0x585858, - 241: 0x626262, - 242: 0x6c6c6c, - 243: 0x767676, - 244: 0x808080, - 245: 0x8a8a8a, - 246: 0x949494, - 247: 0x9e9e9e, - 248: 0xa8a8a8, - 249: 0xb2b2b2, - 250: 0xbcbcbc, - 251: 0xc6c6c6, - 252: 0xd0d0d0, - 253: 0xdadada, - 254: 0xe4e4e4, - 255: 0xeeeeee, -} - -// `\033]0;TITLESTR\007` -func doTitleSequence(er *bytes.Reader) error { - var c byte - var err error - - c, err = er.ReadByte() - if err != nil { - return err - } - if c != '0' && c != '2' { - return nil - } - c, err = er.ReadByte() - if err != nil { - return err - } - if c != ';' { - return nil - } - title := make([]byte, 0, 80) - for { - c, err = er.ReadByte() - if err != nil { - return err - } - if c == 0x07 || c == '\n' { - break - } - title = append(title, c) - } - if len(title) > 0 { - title8, err := syscall.UTF16PtrFromString(string(title)) - if err == nil { - procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8))) - } - } - return nil -} - -// Write write data on console -func (w *Writer) Write(data []byte) (n int, err error) { - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) - - handle := w.handle - - var er *bytes.Reader - if w.rest.Len() > 0 { - var rest bytes.Buffer - w.rest.WriteTo(&rest) - w.rest.Reset() - rest.Write(data) - er = bytes.NewReader(rest.Bytes()) - } else { - er = bytes.NewReader(data) - } - var bw [1]byte -loop: - for { - c1, err := er.ReadByte() - if err != nil { - break loop - } - if c1 != 0x1b { - bw[0] = c1 - w.out.Write(bw[:]) - continue - } - c2, err := er.ReadByte() - if err != nil { - break loop - } - - if c2 == ']' { - w.rest.WriteByte(c1) - w.rest.WriteByte(c2) - er.WriteTo(&w.rest) - if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 { - break loop - } - er = bytes.NewReader(w.rest.Bytes()[2:]) - err := doTitleSequence(er) - if err != nil { - break loop - } - w.rest.Reset() - continue - } - if c2 != 0x5b { - continue - } - - w.rest.WriteByte(c1) - w.rest.WriteByte(c2) - er.WriteTo(&w.rest) - - var buf bytes.Buffer - var m byte - for i, c := range w.rest.Bytes()[2:] { - if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { - m = c - er = bytes.NewReader(w.rest.Bytes()[2+i+1:]) - w.rest.Reset() - break - } - buf.Write([]byte(string(c))) - } - if m == 0 { - break loop - } - - switch m { - case 'A': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.y -= short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'B': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.y += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'C': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'D': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x -= short(n) - if csbi.cursorPosition.x < 0 { - csbi.cursorPosition.x = 0 - } - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'E': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x = 0 - csbi.cursorPosition.y += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'F': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x = 0 - csbi.cursorPosition.y -= short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'G': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x = short(n - 1) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'H', 'f': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - if buf.Len() > 0 { - token := strings.Split(buf.String(), ";") - switch len(token) { - case 1: - n1, err := strconv.Atoi(token[0]) - if err != nil { - continue - } - csbi.cursorPosition.y = short(n1 - 1) - case 2: - n1, err := strconv.Atoi(token[0]) - if err != nil { - continue - } - n2, err := strconv.Atoi(token[1]) - if err != nil { - continue - } - csbi.cursorPosition.x = short(n2 - 1) - csbi.cursorPosition.y = short(n1 - 1) - } - } else { - csbi.cursorPosition.y = 0 - } - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'J': - n := 0 - if buf.Len() > 0 { - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - } - var count, written dword - var cursor coord - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - switch n { - case 0: - cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} - count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) - case 1: - cursor = coord{x: csbi.window.left, y: csbi.window.top} - count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.window.top-csbi.cursorPosition.y)*csbi.size.x) - case 2: - cursor = coord{x: csbi.window.left, y: csbi.window.top} - count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) - } - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - case 'K': - n := 0 - if buf.Len() > 0 { - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - var cursor coord - var count, written dword - switch n { - case 0: - cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} - count = dword(csbi.size.x - csbi.cursorPosition.x) - case 1: - cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} - count = dword(csbi.size.x - csbi.cursorPosition.x) - case 2: - cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} - count = dword(csbi.size.x) - } - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - case 'm': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - attr := csbi.attributes - cs := buf.String() - if cs == "" { - procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr)) - continue - } - token := strings.Split(cs, ";") - for i := 0; i < len(token); i++ { - ns := token[i] - if n, err = strconv.Atoi(ns); err == nil { - switch { - case n == 0 || n == 100: - attr = w.oldattr - case 1 <= n && n <= 5: - attr |= foregroundIntensity - case n == 7: - attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) - case n == 22 || n == 25: - attr |= foregroundIntensity - case n == 27: - attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) - case 30 <= n && n <= 37: - attr &= backgroundMask - if (n-30)&1 != 0 { - attr |= foregroundRed - } - if (n-30)&2 != 0 { - attr |= foregroundGreen - } - if (n-30)&4 != 0 { - attr |= foregroundBlue - } - case n == 38: // set foreground color. - if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { - if n256, err := strconv.Atoi(token[i+2]); err == nil { - if n256foreAttr == nil { - n256setup() - } - attr &= backgroundMask - attr |= n256foreAttr[n256] - i += 2 - } - } else if len(token) == 5 && token[i+1] == "2" { - var r, g, b int - r, _ = strconv.Atoi(token[i+2]) - g, _ = strconv.Atoi(token[i+3]) - b, _ = strconv.Atoi(token[i+4]) - i += 4 - if r > 127 { - attr |= foregroundRed - } - if g > 127 { - attr |= foregroundGreen - } - if b > 127 { - attr |= foregroundBlue - } - } else { - attr = attr & (w.oldattr & backgroundMask) - } - case n == 39: // reset foreground color. - attr &= backgroundMask - attr |= w.oldattr & foregroundMask - case 40 <= n && n <= 47: - attr &= foregroundMask - if (n-40)&1 != 0 { - attr |= backgroundRed - } - if (n-40)&2 != 0 { - attr |= backgroundGreen - } - if (n-40)&4 != 0 { - attr |= backgroundBlue - } - case n == 48: // set background color. - if i < len(token)-2 && token[i+1] == "5" { - if n256, err := strconv.Atoi(token[i+2]); err == nil { - if n256backAttr == nil { - n256setup() - } - attr &= foregroundMask - attr |= n256backAttr[n256] - i += 2 - } - } else if len(token) == 5 && token[i+1] == "2" { - var r, g, b int - r, _ = strconv.Atoi(token[i+2]) - g, _ = strconv.Atoi(token[i+3]) - b, _ = strconv.Atoi(token[i+4]) - i += 4 - if r > 127 { - attr |= backgroundRed - } - if g > 127 { - attr |= backgroundGreen - } - if b > 127 { - attr |= backgroundBlue - } - } else { - attr = attr & (w.oldattr & foregroundMask) - } - case n == 49: // reset foreground color. - attr &= foregroundMask - attr |= w.oldattr & backgroundMask - case 90 <= n && n <= 97: - attr = (attr & backgroundMask) - attr |= foregroundIntensity - if (n-90)&1 != 0 { - attr |= foregroundRed - } - if (n-90)&2 != 0 { - attr |= foregroundGreen - } - if (n-90)&4 != 0 { - attr |= foregroundBlue - } - case 100 <= n && n <= 107: - attr = (attr & foregroundMask) - attr |= backgroundIntensity - if (n-100)&1 != 0 { - attr |= backgroundRed - } - if (n-100)&2 != 0 { - attr |= backgroundGreen - } - if (n-100)&4 != 0 { - attr |= backgroundBlue - } - } - procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr)) - } - } - case 'h': - var ci consoleCursorInfo - cs := buf.String() - if cs == "5>" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 0 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?25" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 1 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?1049" { - if w.althandle == 0 { - h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0) - w.althandle = syscall.Handle(h) - if w.althandle != 0 { - handle = w.althandle - } - } - } - case 'l': - var ci consoleCursorInfo - cs := buf.String() - if cs == "5>" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 1 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?25" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 0 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?1049" { - if w.althandle != 0 { - syscall.CloseHandle(w.althandle) - w.althandle = 0 - handle = w.handle - } - } - case 's': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - w.oldpos = csbi.cursorPosition - case 'u': - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) - } - } - - return len(data), nil -} - -type consoleColor struct { - rgb int - red bool - green bool - blue bool - intensity bool -} - -func (c consoleColor) foregroundAttr() (attr word) { - if c.red { - attr |= foregroundRed - } - if c.green { - attr |= foregroundGreen - } - if c.blue { - attr |= foregroundBlue - } - if c.intensity { - attr |= foregroundIntensity - } - return -} - -func (c consoleColor) backgroundAttr() (attr word) { - if c.red { - attr |= backgroundRed - } - if c.green { - attr |= backgroundGreen - } - if c.blue { - attr |= backgroundBlue - } - if c.intensity { - attr |= backgroundIntensity - } - return -} - -var color16 = []consoleColor{ - {0x000000, false, false, false, false}, - {0x000080, false, false, true, false}, - {0x008000, false, true, false, false}, - {0x008080, false, true, true, false}, - {0x800000, true, false, false, false}, - {0x800080, true, false, true, false}, - {0x808000, true, true, false, false}, - {0xc0c0c0, true, true, true, false}, - {0x808080, false, false, false, true}, - {0x0000ff, false, false, true, true}, - {0x00ff00, false, true, false, true}, - {0x00ffff, false, true, true, true}, - {0xff0000, true, false, false, true}, - {0xff00ff, true, false, true, true}, - {0xffff00, true, true, false, true}, - {0xffffff, true, true, true, true}, -} - -type hsv struct { - h, s, v float32 -} - -func (a hsv) dist(b hsv) float32 { - dh := a.h - b.h - switch { - case dh > 0.5: - dh = 1 - dh - case dh < -0.5: - dh = -1 - dh - } - ds := a.s - b.s - dv := a.v - b.v - return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) -} - -func toHSV(rgb int) hsv { - r, g, b := float32((rgb&0xFF0000)>>16)/256.0, - float32((rgb&0x00FF00)>>8)/256.0, - float32(rgb&0x0000FF)/256.0 - min, max := minmax3f(r, g, b) - h := max - min - if h > 0 { - if max == r { - h = (g - b) / h - if h < 0 { - h += 6 - } - } else if max == g { - h = 2 + (b-r)/h - } else { - h = 4 + (r-g)/h - } - } - h /= 6.0 - s := max - min - if max != 0 { - s /= max - } - v := max - return hsv{h: h, s: s, v: v} -} - -type hsvTable []hsv - -func toHSVTable(rgbTable []consoleColor) hsvTable { - t := make(hsvTable, len(rgbTable)) - for i, c := range rgbTable { - t[i] = toHSV(c.rgb) - } - return t -} - -func (t hsvTable) find(rgb int) consoleColor { - hsv := toHSV(rgb) - n := 7 - l := float32(5.0) - for i, p := range t { - d := hsv.dist(p) - if d < l { - l, n = d, i - } - } - return color16[n] -} - -func minmax3f(a, b, c float32) (min, max float32) { - if a < b { - if b < c { - return a, c - } else if a < c { - return a, b - } else { - return c, b - } - } else { - if a < c { - return b, c - } else if b < c { - return b, a - } else { - return c, a - } - } -} - -var n256foreAttr []word -var n256backAttr []word - -func n256setup() { - n256foreAttr = make([]word, 256) - n256backAttr = make([]word, 256) - t := toHSVTable(color16) - for i, rgb := range color256 { - c := t.find(rgb) - n256foreAttr[i] = c.foregroundAttr() - n256backAttr[i] = c.backgroundAttr() - } -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/noncolorable.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/noncolorable.go deleted file mode 100644 index 9721e16f4b..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/noncolorable.go +++ /dev/null @@ -1,55 +0,0 @@ -package colorable - -import ( - "bytes" - "io" -) - -// NonColorable hold writer but remove escape sequence. -type NonColorable struct { - out io.Writer -} - -// NewNonColorable return new instance of Writer which remove escape sequence from Writer. -func NewNonColorable(w io.Writer) io.Writer { - return &NonColorable{out: w} -} - -// Write write data on console -func (w *NonColorable) Write(data []byte) (n int, err error) { - er := bytes.NewReader(data) - var bw [1]byte -loop: - for { - c1, err := er.ReadByte() - if err != nil { - break loop - } - if c1 != 0x1b { - bw[0] = c1 - w.out.Write(bw[:]) - continue - } - c2, err := er.ReadByte() - if err != nil { - break loop - } - if c2 != 0x5b { - continue - } - - var buf bytes.Buffer - for { - c, err := er.ReadByte() - if err != nil { - break loop - } - if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { - break - } - buf.Write([]byte(string(c))) - } - } - - return len(data), nil -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/LICENSE deleted file mode 100644 index 65dc692b6b..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -Copyright (c) Yasuhiro MATSUMOTO - -MIT License (Expat) - -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. diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/doc.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/doc.go deleted file mode 100644 index 17d4f90ebc..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package isatty implements interface to isatty -package isatty diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_appengine.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_appengine.go deleted file mode 100644 index 9584a98842..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_appengine.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build appengine - -package isatty - -// IsTerminal returns true if the file descriptor is terminal which -// is always false on on appengine classic which is a sandboxed PaaS. -func IsTerminal(fd uintptr) bool { - return false -} - -// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 -// terminal. This is also always false on this environment. -func IsCygwinTerminal(fd uintptr) bool { - return false -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_bsd.go deleted file mode 100644 index 42f2514d13..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_bsd.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build darwin freebsd openbsd netbsd dragonfly -// +build !appengine - -package isatty - -import ( - "syscall" - "unsafe" -) - -const ioctlReadTermios = syscall.TIOCGETA - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - var termios syscall.Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux.go deleted file mode 100644 index 7384cf9916..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build linux -// +build !appengine,!ppc64,!ppc64le - -package isatty - -import ( - "syscall" - "unsafe" -) - -const ioctlReadTermios = syscall.TCGETS - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - var termios syscall.Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go deleted file mode 100644 index 44e5d21302..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go +++ /dev/null @@ -1,19 +0,0 @@ -// +build linux -// +build ppc64 ppc64le - -package isatty - -import ( - "unsafe" - - syscall "golang.org/x/sys/unix" -) - -const ioctlReadTermios = syscall.TCGETS - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - var termios syscall.Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_others.go deleted file mode 100644 index ff4de3d9a5..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_others.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build !windows -// +build !appengine - -package isatty - -// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 -// terminal. This is also always false on this environment. -func IsCygwinTerminal(fd uintptr) bool { - return false -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_solaris.go deleted file mode 100644 index 1f0c6bf53d..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_solaris.go +++ /dev/null @@ -1,16 +0,0 @@ -// +build solaris -// +build !appengine - -package isatty - -import ( - "golang.org/x/sys/unix" -) - -// IsTerminal returns true if the given file descriptor is a terminal. -// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c -func IsTerminal(fd uintptr) bool { - var termio unix.Termio - err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) - return err == nil -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_windows.go deleted file mode 100644 index af51cbcaa4..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_windows.go +++ /dev/null @@ -1,94 +0,0 @@ -// +build windows -// +build !appengine - -package isatty - -import ( - "strings" - "syscall" - "unicode/utf16" - "unsafe" -) - -const ( - fileNameInfo uintptr = 2 - fileTypePipe = 3 -) - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - procGetConsoleMode = kernel32.NewProc("GetConsoleMode") - procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") - procGetFileType = kernel32.NewProc("GetFileType") -) - -func init() { - // Check if GetFileInformationByHandleEx is available. - if procGetFileInformationByHandleEx.Find() != nil { - procGetFileInformationByHandleEx = nil - } -} - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - var st uint32 - r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) - return r != 0 && e == 0 -} - -// Check pipe name is used for cygwin/msys2 pty. -// Cygwin/MSYS2 PTY has a name like: -// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master -func isCygwinPipeName(name string) bool { - token := strings.Split(name, "-") - if len(token) < 5 { - return false - } - - if token[0] != `\msys` && token[0] != `\cygwin` { - return false - } - - if token[1] == "" { - return false - } - - if !strings.HasPrefix(token[2], "pty") { - return false - } - - if token[3] != `from` && token[3] != `to` { - return false - } - - if token[4] != "master" { - return false - } - - return true -} - -// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 -// terminal. -func IsCygwinTerminal(fd uintptr) bool { - if procGetFileInformationByHandleEx == nil { - return false - } - - // Cygwin/msys's pty is a pipe. - ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) - if ft != fileTypePipe || e != 0 { - return false - } - - var buf [2 + syscall.MAX_PATH]uint16 - r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), - 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), - uintptr(len(buf)*2), 0, 0) - if r == 0 || e != 0 { - return false - } - - l := *(*uint32)(unsafe.Pointer(&buf)) - return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/LICENSE b/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/LICENSE deleted file mode 100644 index 06ce0c3b51..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -The MIT License (MIT) -Copyright (c) 2013 Mario L. Gutierrez - -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. - diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/ansi.go b/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/ansi.go deleted file mode 100644 index dc0413649e..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/ansi.go +++ /dev/null @@ -1,285 +0,0 @@ -package ansi - -import ( - "bytes" - "fmt" - "strconv" - "strings" -) - -const ( - black = iota - red - green - yellow - blue - magenta - cyan - white - defaultt = 9 - - normalIntensityFG = 30 - highIntensityFG = 90 - normalIntensityBG = 40 - highIntensityBG = 100 - - start = "\033[" - bold = "1;" - blink = "5;" - underline = "4;" - inverse = "7;" - strikethrough = "9;" - - // Reset is the ANSI reset escape sequence - Reset = "\033[0m" - // DefaultBG is the default background - DefaultBG = "\033[49m" - // DefaultFG is the default foreground - DefaultFG = "\033[39m" -) - -// Black FG -var Black string - -// Red FG -var Red string - -// Green FG -var Green string - -// Yellow FG -var Yellow string - -// Blue FG -var Blue string - -// Magenta FG -var Magenta string - -// Cyan FG -var Cyan string - -// White FG -var White string - -// LightBlack FG -var LightBlack string - -// LightRed FG -var LightRed string - -// LightGreen FG -var LightGreen string - -// LightYellow FG -var LightYellow string - -// LightBlue FG -var LightBlue string - -// LightMagenta FG -var LightMagenta string - -// LightCyan FG -var LightCyan string - -// LightWhite FG -var LightWhite string - -var ( - plain = false - // Colors maps common color names to their ANSI color code. - Colors = map[string]int{ - "black": black, - "red": red, - "green": green, - "yellow": yellow, - "blue": blue, - "magenta": magenta, - "cyan": cyan, - "white": white, - "default": defaultt, - } -) - -func init() { - for i := 0; i < 256; i++ { - Colors[strconv.Itoa(i)] = i - } - - Black = ColorCode("black") - Red = ColorCode("red") - Green = ColorCode("green") - Yellow = ColorCode("yellow") - Blue = ColorCode("blue") - Magenta = ColorCode("magenta") - Cyan = ColorCode("cyan") - White = ColorCode("white") - LightBlack = ColorCode("black+h") - LightRed = ColorCode("red+h") - LightGreen = ColorCode("green+h") - LightYellow = ColorCode("yellow+h") - LightBlue = ColorCode("blue+h") - LightMagenta = ColorCode("magenta+h") - LightCyan = ColorCode("cyan+h") - LightWhite = ColorCode("white+h") -} - -// ColorCode returns the ANSI color color code for style. -func ColorCode(style string) string { - return colorCode(style).String() -} - -// Gets the ANSI color code for a style. -func colorCode(style string) *bytes.Buffer { - buf := bytes.NewBufferString("") - if plain || style == "" { - return buf - } - if style == "reset" { - buf.WriteString(Reset) - return buf - } else if style == "off" { - return buf - } - - foregroundBackground := strings.Split(style, ":") - foreground := strings.Split(foregroundBackground[0], "+") - fgKey := foreground[0] - fg := Colors[fgKey] - fgStyle := "" - if len(foreground) > 1 { - fgStyle = foreground[1] - } - - bg, bgStyle := "", "" - - if len(foregroundBackground) > 1 { - background := strings.Split(foregroundBackground[1], "+") - bg = background[0] - if len(background) > 1 { - bgStyle = background[1] - } - } - - buf.WriteString(start) - base := normalIntensityFG - if len(fgStyle) > 0 { - if strings.Contains(fgStyle, "b") { - buf.WriteString(bold) - } - if strings.Contains(fgStyle, "B") { - buf.WriteString(blink) - } - if strings.Contains(fgStyle, "u") { - buf.WriteString(underline) - } - if strings.Contains(fgStyle, "i") { - buf.WriteString(inverse) - } - if strings.Contains(fgStyle, "s") { - buf.WriteString(strikethrough) - } - if strings.Contains(fgStyle, "h") { - base = highIntensityFG - } - } - - // if 256-color - n, err := strconv.Atoi(fgKey) - if err == nil { - fmt.Fprintf(buf, "38;5;%d;", n) - } else { - fmt.Fprintf(buf, "%d;", base+fg) - } - - base = normalIntensityBG - if len(bg) > 0 { - if strings.Contains(bgStyle, "h") { - base = highIntensityBG - } - // if 256-color - n, err := strconv.Atoi(bg) - if err == nil { - fmt.Fprintf(buf, "48;5;%d;", n) - } else { - fmt.Fprintf(buf, "%d;", base+Colors[bg]) - } - } - - // remove last ";" - buf.Truncate(buf.Len() - 1) - buf.WriteRune('m') - return buf -} - -// Color colors a string based on the ANSI color code for style. -func Color(s, style string) string { - if plain || len(style) < 1 { - return s - } - buf := colorCode(style) - buf.WriteString(s) - buf.WriteString(Reset) - return buf.String() -} - -// ColorFunc creates a closure to avoid computation ANSI color code. -func ColorFunc(style string) func(string) string { - if style == "" { - return func(s string) string { - return s - } - } - color := ColorCode(style) - return func(s string) string { - if plain || s == "" { - return s - } - buf := bytes.NewBufferString(color) - buf.WriteString(s) - buf.WriteString(Reset) - result := buf.String() - return result - } -} - -// DisableColors disables ANSI color codes. The default is false (colors are on). -func DisableColors(disable bool) { - plain = disable - if plain { - Black = "" - Red = "" - Green = "" - Yellow = "" - Blue = "" - Magenta = "" - Cyan = "" - White = "" - LightBlack = "" - LightRed = "" - LightGreen = "" - LightYellow = "" - LightBlue = "" - LightMagenta = "" - LightCyan = "" - LightWhite = "" - } else { - Black = ColorCode("black") - Red = ColorCode("red") - Green = ColorCode("green") - Yellow = ColorCode("yellow") - Blue = ColorCode("blue") - Magenta = ColorCode("magenta") - Cyan = ColorCode("cyan") - White = ColorCode("white") - LightBlack = ColorCode("black+h") - LightRed = ColorCode("red+h") - LightGreen = ColorCode("green+h") - LightYellow = ColorCode("yellow+h") - LightBlue = ColorCode("blue+h") - LightMagenta = ColorCode("magenta+h") - LightCyan = ColorCode("cyan+h") - LightWhite = ColorCode("white+h") - } -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/doc.go b/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/doc.go deleted file mode 100644 index 43c217e11d..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/doc.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Package ansi is a small, fast library to create ANSI colored strings and codes. - -Installation - - # this installs the color viewer and the package - go get -u github.com/mgutz/ansi/cmd/ansi-mgutz - -Example - - // colorize a string, SLOW - msg := ansi.Color("foo", "red+b:white") - - // create a closure to avoid recalculating ANSI code compilation - phosphorize := ansi.ColorFunc("green+h:black") - msg = phosphorize("Bring back the 80s!") - msg2 := phospohorize("Look, I'm a CRT!") - - // cache escape codes and build strings manually - lime := ansi.ColorCode("green+h:black") - reset := ansi.ColorCode("reset") - - fmt.Println(lime, "Bring back the 80s!", reset) - -Other examples - - Color(s, "red") // red - Color(s, "red+b") // red bold - Color(s, "red+B") // red blinking - Color(s, "red+u") // red underline - Color(s, "red+bh") // red bold bright - Color(s, "red:white") // red on white - Color(s, "red+b:white+h") // red bold on white bright - Color(s, "red+B:white+h") // red blink on white bright - -To view color combinations, from terminal - - ansi-mgutz - -Style format - - "foregroundColor+attributes:backgroundColor+attributes" - -Colors - - black - red - green - yellow - blue - magenta - cyan - white - -Attributes - - b = bold foreground - B = Blink foreground - u = underline foreground - h = high intensity (bright) foreground, background - i = inverse - -Wikipedia ANSI escape codes [Colors](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) -*/ -package ansi diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/print.go b/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/print.go deleted file mode 100644 index 806f436bb3..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/print.go +++ /dev/null @@ -1,57 +0,0 @@ -package ansi - -import ( - "fmt" - "sort" - - colorable "github.com/mattn/go-colorable" -) - -// PrintStyles prints all style combinations to the terminal. -func PrintStyles() { - // for compatibility with Windows, not needed for *nix - stdout := colorable.NewColorableStdout() - - bgColors := []string{ - "", - ":black", - ":red", - ":green", - ":yellow", - ":blue", - ":magenta", - ":cyan", - ":white", - } - - keys := make([]string, 0, len(Colors)) - for k := range Colors { - keys = append(keys, k) - } - - sort.Sort(sort.StringSlice(keys)) - - for _, fg := range keys { - for _, bg := range bgColors { - fmt.Fprintln(stdout, padColor(fg, []string{"" + bg, "+b" + bg, "+bh" + bg, "+u" + bg})) - fmt.Fprintln(stdout, padColor(fg, []string{"+s" + bg, "+i" + bg})) - fmt.Fprintln(stdout, padColor(fg, []string{"+uh" + bg, "+B" + bg, "+Bb" + bg /* backgrounds */, "" + bg + "+h"})) - fmt.Fprintln(stdout, padColor(fg, []string{"+b" + bg + "+h", "+bh" + bg + "+h", "+u" + bg + "+h", "+uh" + bg + "+h"})) - } - } -} - -func pad(s string, length int) string { - for len(s) < length { - s += " " - } - return s -} - -func padColor(color string, styles []string) string { - buffer := "" - for _, style := range styles { - buffer += Color(pad(color+style, 20), color+style) - } - return buffer -} diff --git a/vendor/github.com/satori/go.uuid/LICENSE b/vendor/github.com/satori/go.uuid/LICENSE deleted file mode 100644 index 926d549870..0000000000 --- a/vendor/github.com/satori/go.uuid/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (C) 2013-2018 by Maxim Bublis - -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. diff --git a/vendor/github.com/satori/go.uuid/codec.go b/vendor/github.com/satori/go.uuid/codec.go deleted file mode 100644 index 656892c53e..0000000000 --- a/vendor/github.com/satori/go.uuid/codec.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (C) 2013-2018 by Maxim Bublis -// -// 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. - -package uuid - -import ( - "bytes" - "encoding/hex" - "fmt" -) - -// FromBytes returns UUID converted from raw byte slice input. -// It will return error if the slice isn't 16 bytes long. -func FromBytes(input []byte) (u UUID, err error) { - err = u.UnmarshalBinary(input) - return -} - -// FromBytesOrNil returns UUID converted from raw byte slice input. -// Same behavior as FromBytes, but returns a Nil UUID on error. -func FromBytesOrNil(input []byte) UUID { - uuid, err := FromBytes(input) - if err != nil { - return Nil - } - return uuid -} - -// FromString returns UUID parsed from string input. -// Input is expected in a form accepted by UnmarshalText. -func FromString(input string) (u UUID, err error) { - err = u.UnmarshalText([]byte(input)) - return -} - -// FromStringOrNil returns UUID parsed from string input. -// Same behavior as FromString, but returns a Nil UUID on error. -func FromStringOrNil(input string) UUID { - uuid, err := FromString(input) - if err != nil { - return Nil - } - return uuid -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The encoding is the same as returned by String. -func (u UUID) MarshalText() (text []byte, err error) { - text = []byte(u.String()) - return -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// Following formats are supported: -// "6ba7b810-9dad-11d1-80b4-00c04fd430c8", -// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", -// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" -// "6ba7b8109dad11d180b400c04fd430c8" -// ABNF for supported UUID text representation follows: -// uuid := canonical | hashlike | braced | urn -// plain := canonical | hashlike -// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct -// hashlike := 12hexoct -// braced := '{' plain '}' -// urn := URN ':' UUID-NID ':' plain -// URN := 'urn' -// UUID-NID := 'uuid' -// 12hexoct := 6hexoct 6hexoct -// 6hexoct := 4hexoct 2hexoct -// 4hexoct := 2hexoct 2hexoct -// 2hexoct := hexoct hexoct -// hexoct := hexdig hexdig -// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | -// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | -// 'A' | 'B' | 'C' | 'D' | 'E' | 'F' -func (u *UUID) UnmarshalText(text []byte) (err error) { - switch len(text) { - case 32: - return u.decodeHashLike(text) - case 36: - return u.decodeCanonical(text) - case 38: - return u.decodeBraced(text) - case 41: - fallthrough - case 45: - return u.decodeURN(text) - default: - return fmt.Errorf("uuid: incorrect UUID length: %s", text) - } -} - -// decodeCanonical decodes UUID string in format -// "6ba7b810-9dad-11d1-80b4-00c04fd430c8". -func (u *UUID) decodeCanonical(t []byte) (err error) { - if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' { - return fmt.Errorf("uuid: incorrect UUID format %s", t) - } - - src := t[:] - dst := u[:] - - for i, byteGroup := range byteGroups { - if i > 0 { - src = src[1:] // skip dash - } - _, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup]) - if err != nil { - return - } - src = src[byteGroup:] - dst = dst[byteGroup/2:] - } - - return -} - -// decodeHashLike decodes UUID string in format -// "6ba7b8109dad11d180b400c04fd430c8". -func (u *UUID) decodeHashLike(t []byte) (err error) { - src := t[:] - dst := u[:] - - if _, err = hex.Decode(dst, src); err != nil { - return err - } - return -} - -// decodeBraced decodes UUID string in format -// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format -// "{6ba7b8109dad11d180b400c04fd430c8}". -func (u *UUID) decodeBraced(t []byte) (err error) { - l := len(t) - - if t[0] != '{' || t[l-1] != '}' { - return fmt.Errorf("uuid: incorrect UUID format %s", t) - } - - return u.decodePlain(t[1 : l-1]) -} - -// decodeURN decodes UUID string in format -// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format -// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8". -func (u *UUID) decodeURN(t []byte) (err error) { - total := len(t) - - urn_uuid_prefix := t[:9] - - if !bytes.Equal(urn_uuid_prefix, urnPrefix) { - return fmt.Errorf("uuid: incorrect UUID format: %s", t) - } - - return u.decodePlain(t[9:total]) -} - -// decodePlain decodes UUID string in canonical format -// "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format -// "6ba7b8109dad11d180b400c04fd430c8". -func (u *UUID) decodePlain(t []byte) (err error) { - switch len(t) { - case 32: - return u.decodeHashLike(t) - case 36: - return u.decodeCanonical(t) - default: - return fmt.Errorf("uuid: incorrrect UUID length: %s", t) - } -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface. -func (u UUID) MarshalBinary() (data []byte, err error) { - data = u.Bytes() - return -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. -// It will return error if the slice isn't 16 bytes long. -func (u *UUID) UnmarshalBinary(data []byte) (err error) { - if len(data) != Size { - err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) - return - } - copy(u[:], data) - - return -} diff --git a/vendor/github.com/satori/go.uuid/generator.go b/vendor/github.com/satori/go.uuid/generator.go deleted file mode 100644 index 499dc35fb7..0000000000 --- a/vendor/github.com/satori/go.uuid/generator.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright (C) 2013-2018 by Maxim Bublis -// -// 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. - -package uuid - -import ( - "crypto/md5" - "crypto/rand" - "crypto/sha1" - "encoding/binary" - "fmt" - "hash" - "io" - "net" - "os" - "sync" - "time" -) - -// Difference in 100-nanosecond intervals between -// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). -const epochStart = 122192928000000000 - -type epochFunc func() time.Time -type hwAddrFunc func() (net.HardwareAddr, error) - -var ( - global = newRFC4122Generator() - - posixUID = uint32(os.Getuid()) - posixGID = uint32(os.Getgid()) -) - -// NewV1 returns UUID based on current timestamp and MAC address. -func NewV1() (UUID, error) { - return global.NewV1() -} - -// NewV2 returns DCE Security UUID based on POSIX UID/GID. -func NewV2(domain byte) (UUID, error) { - return global.NewV2(domain) -} - -// NewV3 returns UUID based on MD5 hash of namespace UUID and name. -func NewV3(ns UUID, name string) UUID { - return global.NewV3(ns, name) -} - -// NewV4 returns random generated UUID. -func NewV4() (UUID, error) { - return global.NewV4() -} - -// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. -func NewV5(ns UUID, name string) UUID { - return global.NewV5(ns, name) -} - -// Generator provides interface for generating UUIDs. -type Generator interface { - NewV1() (UUID, error) - NewV2(domain byte) (UUID, error) - NewV3(ns UUID, name string) UUID - NewV4() (UUID, error) - NewV5(ns UUID, name string) UUID -} - -// Default generator implementation. -type rfc4122Generator struct { - clockSequenceOnce sync.Once - hardwareAddrOnce sync.Once - storageMutex sync.Mutex - - rand io.Reader - - epochFunc epochFunc - hwAddrFunc hwAddrFunc - lastTime uint64 - clockSequence uint16 - hardwareAddr [6]byte -} - -func newRFC4122Generator() Generator { - return &rfc4122Generator{ - epochFunc: time.Now, - hwAddrFunc: defaultHWAddrFunc, - rand: rand.Reader, - } -} - -// NewV1 returns UUID based on current timestamp and MAC address. -func (g *rfc4122Generator) NewV1() (UUID, error) { - u := UUID{} - - timeNow, clockSeq, err := g.getClockSequence() - if err != nil { - return Nil, err - } - binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) - binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) - binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) - binary.BigEndian.PutUint16(u[8:], clockSeq) - - hardwareAddr, err := g.getHardwareAddr() - if err != nil { - return Nil, err - } - copy(u[10:], hardwareAddr) - - u.SetVersion(V1) - u.SetVariant(VariantRFC4122) - - return u, nil -} - -// NewV2 returns DCE Security UUID based on POSIX UID/GID. -func (g *rfc4122Generator) NewV2(domain byte) (UUID, error) { - u, err := g.NewV1() - if err != nil { - return Nil, err - } - - switch domain { - case DomainPerson: - binary.BigEndian.PutUint32(u[:], posixUID) - case DomainGroup: - binary.BigEndian.PutUint32(u[:], posixGID) - } - - u[9] = domain - - u.SetVersion(V2) - u.SetVariant(VariantRFC4122) - - return u, nil -} - -// NewV3 returns UUID based on MD5 hash of namespace UUID and name. -func (g *rfc4122Generator) NewV3(ns UUID, name string) UUID { - u := newFromHash(md5.New(), ns, name) - u.SetVersion(V3) - u.SetVariant(VariantRFC4122) - - return u -} - -// NewV4 returns random generated UUID. -func (g *rfc4122Generator) NewV4() (UUID, error) { - u := UUID{} - if _, err := g.rand.Read(u[:]); err != nil { - return Nil, err - } - u.SetVersion(V4) - u.SetVariant(VariantRFC4122) - - return u, nil -} - -// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. -func (g *rfc4122Generator) NewV5(ns UUID, name string) UUID { - u := newFromHash(sha1.New(), ns, name) - u.SetVersion(V5) - u.SetVariant(VariantRFC4122) - - return u -} - -// Returns epoch and clock sequence. -func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) { - var err error - g.clockSequenceOnce.Do(func() { - buf := make([]byte, 2) - if _, err = g.rand.Read(buf); err != nil { - return - } - g.clockSequence = binary.BigEndian.Uint16(buf) - }) - if err != nil { - return 0, 0, err - } - - g.storageMutex.Lock() - defer g.storageMutex.Unlock() - - timeNow := g.getEpoch() - // Clock didn't change since last UUID generation. - // Should increase clock sequence. - if timeNow <= g.lastTime { - g.clockSequence++ - } - g.lastTime = timeNow - - return timeNow, g.clockSequence, nil -} - -// Returns hardware address. -func (g *rfc4122Generator) getHardwareAddr() ([]byte, error) { - var err error - g.hardwareAddrOnce.Do(func() { - if hwAddr, err := g.hwAddrFunc(); err == nil { - copy(g.hardwareAddr[:], hwAddr) - return - } - - // Initialize hardwareAddr randomly in case - // of real network interfaces absence. - if _, err = g.rand.Read(g.hardwareAddr[:]); err != nil { - return - } - // Set multicast bit as recommended by RFC 4122 - g.hardwareAddr[0] |= 0x01 - }) - if err != nil { - return []byte{}, err - } - return g.hardwareAddr[:], nil -} - -// Returns difference in 100-nanosecond intervals between -// UUID epoch (October 15, 1582) and current time. -func (g *rfc4122Generator) getEpoch() uint64 { - return epochStart + uint64(g.epochFunc().UnixNano()/100) -} - -// Returns UUID based on hashing of namespace UUID and name. -func newFromHash(h hash.Hash, ns UUID, name string) UUID { - u := UUID{} - h.Write(ns[:]) - h.Write([]byte(name)) - copy(u[:], h.Sum(nil)) - - return u -} - -// Returns hardware address. -func defaultHWAddrFunc() (net.HardwareAddr, error) { - ifaces, err := net.Interfaces() - if err != nil { - return []byte{}, err - } - for _, iface := range ifaces { - if len(iface.HardwareAddr) >= 6 { - return iface.HardwareAddr, nil - } - } - return []byte{}, fmt.Errorf("uuid: no HW address found") -} diff --git a/vendor/github.com/satori/go.uuid/sql.go b/vendor/github.com/satori/go.uuid/sql.go deleted file mode 100644 index 56759d3905..0000000000 --- a/vendor/github.com/satori/go.uuid/sql.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2013-2018 by Maxim Bublis -// -// 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. - -package uuid - -import ( - "database/sql/driver" - "fmt" -) - -// Value implements the driver.Valuer interface. -func (u UUID) Value() (driver.Value, error) { - return u.String(), nil -} - -// Scan implements the sql.Scanner interface. -// A 16-byte slice is handled by UnmarshalBinary, while -// a longer byte slice or a string is handled by UnmarshalText. -func (u *UUID) Scan(src interface{}) error { - switch src := src.(type) { - case []byte: - if len(src) == Size { - return u.UnmarshalBinary(src) - } - return u.UnmarshalText(src) - - case string: - return u.UnmarshalText([]byte(src)) - } - - return fmt.Errorf("uuid: cannot convert %T to UUID", src) -} - -// NullUUID can be used with the standard sql package to represent a -// UUID value that can be NULL in the database -type NullUUID struct { - UUID UUID - Valid bool -} - -// Value implements the driver.Valuer interface. -func (u NullUUID) Value() (driver.Value, error) { - if !u.Valid { - return nil, nil - } - // Delegate to UUID Value function - return u.UUID.Value() -} - -// Scan implements the sql.Scanner interface. -func (u *NullUUID) Scan(src interface{}) error { - if src == nil { - u.UUID, u.Valid = Nil, false - return nil - } - - // Delegate to UUID Scan function - u.Valid = true - return u.UUID.Scan(src) -} diff --git a/vendor/github.com/satori/go.uuid/uuid.go b/vendor/github.com/satori/go.uuid/uuid.go deleted file mode 100644 index a2b8e2ca2a..0000000000 --- a/vendor/github.com/satori/go.uuid/uuid.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (C) 2013-2018 by Maxim Bublis -// -// 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. - -// Package uuid provides implementation of Universally Unique Identifier (UUID). -// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and -// version 2 (as specified in DCE 1.1). -package uuid - -import ( - "bytes" - "encoding/hex" -) - -// Size of a UUID in bytes. -const Size = 16 - -// UUID representation compliant with specification -// described in RFC 4122. -type UUID [Size]byte - -// UUID versions -const ( - _ byte = iota - V1 - V2 - V3 - V4 - V5 -) - -// UUID layout variants. -const ( - VariantNCS byte = iota - VariantRFC4122 - VariantMicrosoft - VariantFuture -) - -// UUID DCE domains. -const ( - DomainPerson = iota - DomainGroup - DomainOrg -) - -// String parse helpers. -var ( - urnPrefix = []byte("urn:uuid:") - byteGroups = []int{8, 4, 4, 4, 12} -) - -// Nil is special form of UUID that is specified to have all -// 128 bits set to zero. -var Nil = UUID{} - -// Predefined namespace UUIDs. -var ( - NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) - NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) - NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) - NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) -) - -// Equal returns true if u1 and u2 equals, otherwise returns false. -func Equal(u1 UUID, u2 UUID) bool { - return bytes.Equal(u1[:], u2[:]) -} - -// Version returns algorithm version used to generate UUID. -func (u UUID) Version() byte { - return u[6] >> 4 -} - -// Variant returns UUID layout variant. -func (u UUID) Variant() byte { - switch { - case (u[8] >> 7) == 0x00: - return VariantNCS - case (u[8] >> 6) == 0x02: - return VariantRFC4122 - case (u[8] >> 5) == 0x06: - return VariantMicrosoft - case (u[8] >> 5) == 0x07: - fallthrough - default: - return VariantFuture - } -} - -// Bytes returns bytes slice representation of UUID. -func (u UUID) Bytes() []byte { - return u[:] -} - -// Returns canonical string representation of UUID: -// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. -func (u UUID) String() string { - buf := make([]byte, 36) - - hex.Encode(buf[0:8], u[0:4]) - buf[8] = '-' - hex.Encode(buf[9:13], u[4:6]) - buf[13] = '-' - hex.Encode(buf[14:18], u[6:8]) - buf[18] = '-' - hex.Encode(buf[19:23], u[8:10]) - buf[23] = '-' - hex.Encode(buf[24:], u[10:]) - - return string(buf) -} - -// SetVersion sets version bits. -func (u *UUID) SetVersion(v byte) { - u[6] = (u[6] & 0x0f) | (v << 4) -} - -// SetVariant sets variant bits. -func (u *UUID) SetVariant(v byte) { - switch v { - case VariantNCS: - u[8] = (u[8]&(0xff>>1) | (0x00 << 7)) - case VariantRFC4122: - u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) - case VariantMicrosoft: - u[8] = (u[8]&(0xff>>3) | (0x06 << 5)) - case VariantFuture: - fallthrough - default: - u[8] = (u[8]&(0xff>>3) | (0x07 << 5)) - } -} - -// Must is a helper that wraps a call to a function returning (UUID, error) -// and panics if the error is non-nil. It is intended for use in variable -// initializations such as -// var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")); -func Must(u UUID, err error) UUID { - if err != nil { - panic(err) - } - return u -} From 02568492429dbce11707ec86e35c7fd0fe0506f3 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 1 Sep 2018 02:06:56 +0300 Subject: [PATCH 11/91] go modules and vendoring section explain why not yet, in my opinion let's stick with the current system until gophers get acquainted with the new go modules and how it works - I putted a link to the TOC in order to help them --- .travis.yml | 11 ++--- HISTORY.md | 3 +- _examples/tutorial/url-shortener/store.go | 20 ++++----- sessions/sessiondb/boltdb/database.go | 41 ++++++++++--------- .../vendor/github.com/etcd-io/bbolt/db.go | 6 +-- 5 files changed, 42 insertions(+), 39 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6e6ada55f5..a1912a5077 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,14 @@ os: - linux - osx go: - - "go1.9" - - "go1.10" + - 1.9.x + - 1.10.x + - 1.11.x go_import_path: github.com/kataras/iris # we disable test caching via GOCACHE=off -env: - global: - - GOCACHE=off +# env: +# global: +# - GOCACHE=off install: - go get ./... # for iris-contrib/httpexpect, kataras/golog script: diff --git a/HISTORY.md b/HISTORY.md index 2a24d77887..d30424b1de 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -80,7 +80,8 @@ app.Get("/profile/{name:alphabetical max(255)}", func(ctx iris.Context){ - Rename the vendor `sessions/sessiondb/vendor/...bbolt` from `coreos/bbolt` to `etcd-io/bbolt` and update to v1.3.1, based on [that](https://github.com/etcd-io/bbolt/releases/tag/v1.3.1-etcd.7) - Update the vendor `sessions/sessiondb/vendor/...badger` to v1.5.3 -> More to come, maybe it will be removed eventually. +I believe it is soon to adapt the new [go modules](https://github.com/golang/go/wiki/Modules#table-of-contents) inside Iris, the new `go mod` command may change until go 1.12, it is still an experimental feature. +The [vendor](https://github.com/kataras/iris/tree/v11/vendor) folder will be kept until most the majority of Go developers get acquainted with the new `go modules`. The `go.mod` and `go.sum` files will come at `iris v12` (or `go 1.12`), we could do that on this version as well but I don't want to have half-things, versioning should be passed on import path as well and that is a large breaking change to go with it right now, so it will probably have a new path such as `github.com/kataras/iris/v12` based on a `git tag` like every Iris release (we are lucky here because we used semantic versioning from day zero). No folder re-structure inside the root git repository to split versions will ever happen, so backwards-compatibility will be not enabled by-default although it's easy for anyone to grab any version from older [releases](https://github.com/kataras/iris/releases) or branch and target that. # Sat, 11 August 2018 | v10.7.0 diff --git a/_examples/tutorial/url-shortener/store.go b/_examples/tutorial/url-shortener/store.go index d76413df60..f2b78891f9 100644 --- a/_examples/tutorial/url-shortener/store.go +++ b/_examples/tutorial/url-shortener/store.go @@ -3,7 +3,7 @@ package main import ( "bytes" - "github.com/etcd-io/bbolt" + bolt "github.com/etcd-io/bbolt" ) // Panic panics, change it if you don't want to panic on critical INITIALIZE-ONLY-ERRORS @@ -28,17 +28,17 @@ var ( // Only one table/bucket which contains the urls, so it's not a fully Database, // it works only with single bucket because that all we need. type DB struct { - db *bbolt.DB + db *bolt.DB } var _ Store = &DB{} // openDatabase open a new database connection // and returns its instance. -func openDatabase(stumb string) *bbolt.DB { +func openDatabase(stumb string) *bolt.DB { // Open the data(base) file in the current working directory. // It will be created if it doesn't exist. - db, err := bbolt.Open(stumb, 0600, nil) + db, err := bolt.Open(stumb, 0600, nil) if err != nil { Panic(err) } @@ -48,7 +48,7 @@ func openDatabase(stumb string) *bbolt.DB { tableURLs, } - db.Update(func(tx *bbolt.Tx) (err error) { + db.Update(func(tx *bolt.Tx) (err error) { for _, table := range tables { _, err = tx.CreateBucketIfNotExists(table) if err != nil { @@ -73,7 +73,7 @@ func NewDB(stumb string) *DB { // Set sets a shorten url and its key // Note: Caller is responsible to generate a key. func (d *DB) Set(key string, value string) error { - return d.db.Update(func(tx *bbolt.Tx) error { + return d.db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists(tableURLs) // Generate ID for the url // Note: we could use that instead of a random string key @@ -106,7 +106,7 @@ func (d *DB) Set(key string, value string) error { // Clear clears all the database entries for the table urls. func (d *DB) Clear() error { - return d.db.Update(func(tx *bbolt.Tx) error { + return d.db.Update(func(tx *bolt.Tx) error { return tx.DeleteBucket(tableURLs) }) } @@ -116,7 +116,7 @@ func (d *DB) Clear() error { // Returns an empty string if not found. func (d *DB) Get(key string) (value string) { keyB := []byte(key) - d.db.Update(func(tx *bbolt.Tx) error { + d.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(tableURLs) if b == nil { return nil @@ -138,7 +138,7 @@ func (d *DB) Get(key string) (value string) { // GetByValue returns all keys for a specific (original) url value. func (d *DB) GetByValue(value string) (keys []string) { valueB := []byte(value) - d.db.Update(func(tx *bbolt.Tx) error { + d.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket(tableURLs) if b == nil { return nil @@ -159,7 +159,7 @@ func (d *DB) GetByValue(value string) (keys []string) { // Len returns all the "shorted" urls length func (d *DB) Len() (num int) { - d.db.View(func(tx *bbolt.Tx) error { + d.db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket(tableURLs) diff --git a/sessions/sessiondb/boltdb/database.go b/sessions/sessiondb/boltdb/database.go index db0ef827ea..7a27d5cc0e 100644 --- a/sessions/sessiondb/boltdb/database.go +++ b/sessions/sessiondb/boltdb/database.go @@ -6,10 +6,11 @@ import ( "runtime" "time" - "github.com/etcd-io/bbolt" - "github.com/kataras/golog" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" + + bolt "github.com/etcd-io/bbolt" + "github.com/kataras/golog" ) // DefaultFileMode used as the default database's "fileMode" @@ -25,7 +26,7 @@ type Database struct { // Service is the underline BoltDB database connection, // it's initialized at `New` or `NewFromDB`. // Can be used to get stats. - Service *bbolt.DB + Service *bolt.DB } var errPathMissing = errors.New("path is required") @@ -51,8 +52,8 @@ func New(path string, fileMode os.FileMode) (*Database, error) { return nil, err } - service, err := bbolt.Open(path, fileMode, - &bbolt.Options{Timeout: 20 * time.Second}, + service, err := bolt.Open(path, fileMode, + &bolt.Options{Timeout: 20 * time.Second}, ) if err != nil { @@ -64,10 +65,10 @@ func New(path string, fileMode os.FileMode) (*Database, error) { } // NewFromDB same as `New` but accepts an already-created custom boltdb connection instead. -func NewFromDB(service *bbolt.DB, bucketName string) (*Database, error) { +func NewFromDB(service *bolt.DB, bucketName string) (*Database, error) { bucket := []byte(bucketName) - service.Update(func(tx *bbolt.Tx) (err error) { + service.Update(func(tx *bolt.Tx) (err error) { _, err = tx.CreateBucketIfNotExists(bucket) return }) @@ -78,15 +79,15 @@ func NewFromDB(service *bbolt.DB, bucketName string) (*Database, error) { return db, db.cleanup() } -func (db *Database) getBucket(tx *bbolt.Tx) *bbolt.Bucket { +func (db *Database) getBucket(tx *bolt.Tx) *bolt.Bucket { return tx.Bucket(db.table) } -func (db *Database) getBucketForSession(tx *bbolt.Tx, sid string) *bbolt.Bucket { +func (db *Database) getBucketForSession(tx *bolt.Tx, sid string) *bolt.Bucket { b := db.getBucket(tx).Bucket([]byte(sid)) if b == nil { // session does not exist, it shouldn't happen, session bucket creation happens once at `Acquire`, - // no need to accept the `bbolt.bucket.CreateBucketIfNotExists`'s performance cost. + // no need to accept the `bolt.bucket.CreateBucketIfNotExists`'s performance cost. golog.Debugf("unreachable session access for '%s'", sid) } @@ -105,7 +106,7 @@ func getExpirationBucketName(bsid []byte) []byte { // Cleanup removes any invalid(have expired) session entries on initialization. func (db *Database) cleanup() error { - return db.Service.Update(func(tx *bbolt.Tx) error { + return db.Service.Update(func(tx *bolt.Tx) error { b := db.getBucket(tx) c := b.Cursor() // loop through all buckets, find one with expiration. @@ -151,7 +152,7 @@ var expirationKey = []byte("exp") // it can be random. // if the return value is LifeTime{} then the session manager sets the life time based on the expiration duration lives in configuration. func (db *Database) Acquire(sid string, expires time.Duration) (lifetime sessions.LifeTime) { bsid := []byte(sid) - err := db.Service.Update(func(tx *bbolt.Tx) (err error) { + err := db.Service.Update(func(tx *bolt.Tx) (err error) { root := db.getBucket(tx) if expires > 0 { // should check or create the expiration bucket. @@ -218,7 +219,7 @@ func (db *Database) OnUpdateExpiration(sid string, newExpires time.Duration) err return err } - err = db.Service.Update(func(tx *bbolt.Tx) error { + err = db.Service.Update(func(tx *bolt.Tx) error { expirationName := getExpirationBucketName([]byte(sid)) root := db.getBucket(tx) b := root.Bucket(expirationName) @@ -250,7 +251,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu return } - err = db.Service.Update(func(tx *bbolt.Tx) error { + err = db.Service.Update(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -270,7 +271,7 @@ func (db *Database) Set(sid string, lifetime sessions.LifeTime, key string, valu // Get retrieves a session value based on the key. func (db *Database) Get(sid string, key string) (value interface{}) { - err := db.Service.View(func(tx *bbolt.Tx) error { + err := db.Service.View(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -293,7 +294,7 @@ func (db *Database) Get(sid string, key string) (value interface{}) { // Visit loops through all session keys and values. func (db *Database) Visit(sid string, cb func(key string, value interface{})) { - db.Service.View(func(tx *bbolt.Tx) error { + db.Service.View(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -314,7 +315,7 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) { // Len returns the length of the session's entries (keys). func (db *Database) Len(sid string) (n int) { - db.Service.View(func(tx *bbolt.Tx) error { + db.Service.View(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -329,7 +330,7 @@ func (db *Database) Len(sid string) (n int) { // Delete removes a session key value based on its key. func (db *Database) Delete(sid string, key string) (deleted bool) { - err := db.Service.Update(func(tx *bbolt.Tx) error { + err := db.Service.Update(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return sessions.ErrNotFound @@ -343,7 +344,7 @@ func (db *Database) Delete(sid string, key string) (deleted bool) { // Clear removes all session key values but it keeps the session entry. func (db *Database) Clear(sid string) { - db.Service.Update(func(tx *bbolt.Tx) error { + db.Service.Update(func(tx *bolt.Tx) error { b := db.getBucketForSession(tx, sid) if b == nil { return nil @@ -358,7 +359,7 @@ func (db *Database) Clear(sid string) { // Release destroys the session, it clears and removes the session entry, // session manager will create a new session ID on the next request after this call. func (db *Database) Release(sid string) { - db.Service.Update(func(tx *bbolt.Tx) error { + db.Service.Update(func(tx *bolt.Tx) error { // delete the session bucket. b := db.getBucket(tx) bsid := []byte(sid) diff --git a/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/db.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/db.go index 575054032f..4e38ab8c62 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/db.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/db.go @@ -147,7 +147,7 @@ func (db *DB) Path() string { // GoString returns the Go string representation of the database. func (db *DB) GoString() string { - return fmt.Sprintf("bbolt.DB{path:%q}", db.path) + return fmt.Sprintf("bolt.DB{path:%q}", db.path) } // String returns the string representation of the database. @@ -483,7 +483,7 @@ func (db *DB) close() error { if !db.readOnly { // Unlock the file. if err := funlock(db); err != nil { - log.Printf("bbolt.Close(): funlock error: %s", err) + log.Printf("bolt.Close(): funlock error: %s", err) } } @@ -890,7 +890,7 @@ func (db *DB) meta() *meta { // This should never be reached, because both meta1 and meta0 were validated // on mmap() and we do fsync() on every write. - panic("bbolt.DB.meta(): invalid meta pages") + panic("bolt.DB.meta(): invalid meta pages") } // allocate returns a contiguous block of memory starting at a given page. From 2d87444a6c2315e4809846f141d93a0c5a85c2fa Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 1 Sep 2018 02:10:22 +0300 Subject: [PATCH 12/91] more details Signed-off-by: Gerasimos (Makis) Maropoulos --- HISTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index d30424b1de..5f774844d0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -81,7 +81,7 @@ app.Get("/profile/{name:alphabetical max(255)}", func(ctx iris.Context){ - Update the vendor `sessions/sessiondb/vendor/...badger` to v1.5.3 I believe it is soon to adapt the new [go modules](https://github.com/golang/go/wiki/Modules#table-of-contents) inside Iris, the new `go mod` command may change until go 1.12, it is still an experimental feature. -The [vendor](https://github.com/kataras/iris/tree/v11/vendor) folder will be kept until most the majority of Go developers get acquainted with the new `go modules`. The `go.mod` and `go.sum` files will come at `iris v12` (or `go 1.12`), we could do that on this version as well but I don't want to have half-things, versioning should be passed on import path as well and that is a large breaking change to go with it right now, so it will probably have a new path such as `github.com/kataras/iris/v12` based on a `git tag` like every Iris release (we are lucky here because we used semantic versioning from day zero). No folder re-structure inside the root git repository to split versions will ever happen, so backwards-compatibility will be not enabled by-default although it's easy for anyone to grab any version from older [releases](https://github.com/kataras/iris/releases) or branch and target that. +The [vendor](https://github.com/kataras/iris/tree/v11/vendor) folder will be kept until the majority of Go developers get acquainted with the new `go modules`. The `go.mod` and `go.sum` files will come at `iris v12` (or `go 1.12`), we could do that on this version as well but I don't want to have half-things, versioning should be passed on import path as well and that is a large breaking change to go with it right now, so it will probably have a new path such as `github.com/kataras/iris/v12` based on a `git tag` like every Iris release (we are lucky here because we used semantic versioning from day zero). No folder re-structure inside the root git repository to split versions will ever happen, so backwards-compatibility for older go versions(before go 1.9.3) and iris versions will be not enabled by-default although it's easy for anyone to grab any version from older [releases](https://github.com/kataras/iris/releases) or branch and target that. # Sat, 11 August 2018 | v10.7.0 From b4186045efc2b9ac2b4d789788835d7962737970 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 1 Sep 2018 18:53:42 +0300 Subject: [PATCH 13/91] dynamic param types part 1 --- core/router/macro/interpreter/ast/ast.go | 210 +++++------------- .../router/macro/interpreter/parser/parser.go | 81 ++++++- .../macro/interpreter/parser/parser_test.go | 63 ++++-- deprecated.go | 1 - 4 files changed, 169 insertions(+), 186 deletions(-) delete mode 100644 deprecated.go diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index 4299569006..fa695226b6 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -2,154 +2,63 @@ package ast import ( "reflect" + "strings" ) -// ParamType is a specific uint8 type -// which holds the parameter types' type. -type ParamType uint8 - -const ( - // ParamTypeUnExpected is an unexpected parameter type. - ParamTypeUnExpected ParamType = iota - // ParamTypeString is the string type. - // If parameter type is missing then it defaults to String type. - // Allows anything - // Declaration: /mypath/{myparam:string} or {myparam} - ParamTypeString - - // ParamTypeNumber is the integer, a number type. - // Allows both positive and negative numbers, any number of digits. - // Declaration: /mypath/{myparam:number} or {myparam:int} for backwards-compatibility - ParamTypeNumber - - // ParamTypeInt64 is a number type. - // Allows only -9223372036854775808 to 9223372036854775807. - // Declaration: /mypath/{myparam:int64} or {myparam:long} - ParamTypeInt64 - // ParamTypeUint8 a number type. - // Allows only 0 to 255. - // Declaration: /mypath/{myparam:uint8} - ParamTypeUint8 - // ParamTypeUint64 a number type. - // Allows only 0 to 18446744073709551615. - // Declaration: /mypath/{myparam:uint64} - ParamTypeUint64 - - // ParamTypeBoolean is the bool type. - // Allows only "1" or "t" or "T" or "TRUE" or "true" or "True" - // or "0" or "f" or "F" or "FALSE" or "false" or "False". - // Declaration: /mypath/{myparam:bool} or {myparam:boolean} - ParamTypeBoolean - // ParamTypeAlphabetical is the alphabetical/letter type type. - // Allows letters only (upper or lowercase) - // Declaration: /mypath/{myparam:alphabetical} - ParamTypeAlphabetical - // ParamTypeFile is the file single path type. - // Allows: - // letters (upper or lowercase) - // numbers (0-9) - // underscore (_) - // dash (-) - // point (.) - // no spaces! or other character - // Declaration: /mypath/{myparam:file} - ParamTypeFile - // ParamTypePath is the multi path (or wildcard) type. - // Allows anything, should be the last part - // Declaration: /mypath/{myparam:path} - ParamTypePath -) +// ParamType holds the necessary information about a parameter type. +type ParamType struct { + Indent string // the name of the parameter type. + Aliases []string // any aliases, can be empty. -func (pt ParamType) String() string { - for k, v := range paramTypes { - if v == pt { - return k - } - } + GoType reflect.Kind // the go type useful for "mvc" and "hero" bindings. - return "unexpected" -} + Default bool // if true then empty type param will target this and its functions will be available to the rest of the param type's funcs. + End bool // if true then it should be declared at the end of a route path and can accept any trailing path segment as one parameter. -// Not because for a single reason -// a string may be a -// ParamTypeString or a ParamTypeFile -// or a ParamTypePath or ParamTypeAlphabetical. -// -// func ParamTypeFromStd(k reflect.Kind) ParamType { - -// Kind returns the std kind of this param type. -func (pt ParamType) Kind() reflect.Kind { - switch pt { - case ParamTypeAlphabetical: - fallthrough - case ParamTypeFile: - fallthrough - case ParamTypePath: - fallthrough - case ParamTypeString: - return reflect.String - case ParamTypeNumber: - return reflect.Int - case ParamTypeInt64: - return reflect.Int64 - case ParamTypeUint8: - return reflect.Uint8 - case ParamTypeUint64: - return reflect.Uint64 - case ParamTypeBoolean: - return reflect.Bool - } - return reflect.Invalid // 0 + invalid bool // only true if returned by the parser via `LookupParamType`. } -// ValidKind will return true if at least one param type is supported -// for this std kind. -func ValidKind(k reflect.Kind) bool { - switch k { - case reflect.String: - fallthrough - case reflect.Int: - fallthrough - case reflect.Int64: - fallthrough - case reflect.Uint8: - fallthrough - case reflect.Uint64: - fallthrough - case reflect.Bool: - return true - default: - return false - } +// ParamTypeUnExpected is the unexpected parameter type. +var ParamTypeUnExpected = ParamType{invalid: true} + +func (pt ParamType) String() string { + return pt.Indent } // Assignable returns true if the "k" standard type // is assignabled to this ParamType. func (pt ParamType) Assignable(k reflect.Kind) bool { - return pt.Kind() == k + return pt.GoType == k } -var paramTypes = map[string]ParamType{ - "string": ParamTypeString, +// GetDefaultParamType accepts a list of ParamType and returns its default. +// If no `Default` specified: +// and len(paramTypes) > 0 then it will return the first one, +// otherwise it returns a "string" parameter type. +func GetDefaultParamType(paramTypes ...ParamType) ParamType { + for _, pt := range paramTypes { + if pt.Default == true { + return pt + } + } - "number": ParamTypeNumber, - "int": ParamTypeNumber, // same as number. - "long": ParamTypeInt64, - "int64": ParamTypeInt64, // same as long. - "uint8": ParamTypeUint8, - "uint64": ParamTypeUint64, + if len(paramTypes) > 0 { + return paramTypes[0] + } - "boolean": ParamTypeBoolean, - "bool": ParamTypeBoolean, // same as boolean. + return ParamType{Indent: "string", GoType: reflect.String, Default: true} +} - "alphabetical": ParamTypeAlphabetical, - "file": ParamTypeFile, - "path": ParamTypePath, - // could be named also: - // "tail": - // "wild" - // "wildcard" +// ValidKind will return true if at least one param type is supported +// for this std kind. +func ValidKind(k reflect.Kind, paramTypes ...ParamType) bool { + for _, pt := range paramTypes { + if pt.GoType == k { + return true + } + } + return false } // LookupParamType accepts the string @@ -164,11 +73,20 @@ var paramTypes = map[string]ParamType{ // "alphabetical" // "file" // "path" -func LookupParamType(ident string) ParamType { - if typ, ok := paramTypes[ident]; ok { - return typ +func LookupParamType(indent string, paramTypes ...ParamType) (ParamType, bool) { + for _, pt := range paramTypes { + if pt.Indent == indent { + return pt, true + } + + for _, alias := range pt.Aliases { + if alias == indent { + return pt, true + } + } } - return ParamTypeUnExpected + + return ParamTypeUnExpected, false } // LookupParamTypeFromStd accepts the string representation of a standard go type. @@ -181,23 +99,15 @@ func LookupParamType(ident string) ParamType { // int64 matches to int64/long // uint64 matches to uint64 // bool matches to bool/boolean -func LookupParamTypeFromStd(goType string) ParamType { - switch goType { - case "string": - return ParamTypeString - case "int": - return ParamTypeNumber - case "int64": - return ParamTypeInt64 - case "uint8": - return ParamTypeUint8 - case "uint64": - return ParamTypeUint64 - case "bool": - return ParamTypeBoolean - default: - return ParamTypeUnExpected +func LookupParamTypeFromStd(goType string, paramTypes ...ParamType) (ParamType, bool) { + goType = strings.ToLower(goType) + for _, pt := range paramTypes { + if strings.ToLower(pt.GoType.String()) == goType { + return pt, true + } } + + return ParamTypeUnExpected, false } // ParamStatement is a struct diff --git a/core/router/macro/interpreter/parser/parser.go b/core/router/macro/interpreter/parser/parser.go index 97920d5b2e..9ce125c91e 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/core/router/macro/interpreter/parser/parser.go @@ -2,6 +2,7 @@ package parser import ( "fmt" + "reflect" "strconv" "strings" @@ -10,10 +11,69 @@ import ( "github.com/kataras/iris/core/router/macro/interpreter/token" ) +var ( + // paramTypeString is the string type. + // If parameter type is missing then it defaults to String type. + // Allows anything + // Declaration: /mypath/{myparam:string} or {myparam} + paramTypeString = ast.ParamType{Indent: "string", GoType: reflect.String, Default: true} + // ParamTypeNumber is the integer, a number type. + // Allows both positive and negative numbers, any number of digits. + // Declaration: /mypath/{myparam:number} or {myparam:int} for backwards-compatibility + paramTypeNumber = ast.ParamType{Indent: "number", Aliases: []string{"int"}, GoType: reflect.Int} + // ParamTypeInt64 is a number type. + // Allows only -9223372036854775808 to 9223372036854775807. + // Declaration: /mypath/{myparam:int64} or {myparam:long} + paramTypeInt64 = ast.ParamType{Indent: "int64", Aliases: []string{"long"}, GoType: reflect.Int64} + // ParamTypeUint8 a number type. + // Allows only 0 to 255. + // Declaration: /mypath/{myparam:uint8} + paramTypeUint8 = ast.ParamType{Indent: "uint8", GoType: reflect.Uint8} + // ParamTypeUint64 a number type. + // Allows only 0 to 18446744073709551615. + // Declaration: /mypath/{myparam:uint64} + paramTypeUint64 = ast.ParamType{Indent: "uint64", GoType: reflect.Uint64} + // ParamTypeBool is the bool type. + // Allows only "1" or "t" or "T" or "TRUE" or "true" or "True" + // or "0" or "f" or "F" or "FALSE" or "false" or "False". + // Declaration: /mypath/{myparam:bool} or {myparam:boolean} + paramTypeBool = ast.ParamType{Indent: "bool", Aliases: []string{"boolean"}, GoType: reflect.Bool} + // ParamTypeAlphabetical is the alphabetical/letter type type. + // Allows letters only (upper or lowercase) + // Declaration: /mypath/{myparam:alphabetical} + paramTypeAlphabetical = ast.ParamType{Indent: "alphabetical", GoType: reflect.String} + // ParamTypeFile is the file single path type. + // Allows: + // letters (upper or lowercase) + // numbers (0-9) + // underscore (_) + // dash (-) + // point (.) + // no spaces! or other character + // Declaration: /mypath/{myparam:file} + paramTypeFile = ast.ParamType{Indent: "file", GoType: reflect.String} + // ParamTypePath is the multi path (or wildcard) type. + // Allows anything, should be the last part + // Declaration: /mypath/{myparam:path} + paramTypePath = ast.ParamType{Indent: "path", GoType: reflect.String, End: true} +) + +// DefaultParamTypes are the built'n parameter types. +var DefaultParamTypes = []ast.ParamType{ + paramTypeString, + paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64, + paramTypeBool, + paramTypeAlphabetical, paramTypeFile, paramTypePath, +} + // Parse takes a route "fullpath" // and returns its param statements // and an error on failure. -func Parse(fullpath string) ([]*ast.ParamStatement, error) { +func Parse(fullpath string, paramTypes ...ast.ParamType) ([]*ast.ParamStatement, error) { + if len(paramTypes) == 0 { + paramTypes = DefaultParamTypes + } + pathParts := strings.SplitN(fullpath, "/", -1) p := new(ParamParser) statements := make([]*ast.ParamStatement, 0) @@ -28,14 +88,14 @@ func Parse(fullpath string) ([]*ast.ParamStatement, error) { } p.Reset(s) - stmt, err := p.Parse() + stmt, err := p.Parse(paramTypes) if err != nil { // exit on first error return nil, err } // if we have param type path but it's not the last path part - if stmt.Type == ast.ParamTypePath && i < len(pathParts)-1 { - return nil, fmt.Errorf("param type 'path' should be lived only inside the last path segment, but was inside: %s", s) + if stmt.Type.End && i < len(pathParts)-1 { + return nil, fmt.Errorf("param type '%s' should be lived only inside the last path segment, but was inside: %s", stmt.Type, s) } statements = append(statements, stmt) @@ -77,9 +137,6 @@ const ( // per-parameter. An error code can be setted via // the "else" keyword inside a route's path. DefaultParamErrorCode = 404 - // DefaultParamType when parameter type is missing use this param type, defaults to string - // and it should be remains unless earth split in two. - DefaultParamType = ast.ParamTypeString ) // func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) { @@ -102,14 +159,14 @@ func (p ParamParser) Error() error { return nil } -// Parse parses the p.src and returns its param statement +// Parse parses the p.src based on the given param types and returns its param statement // and an error on failure. -func (p *ParamParser) Parse() (*ast.ParamStatement, error) { +func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, error) { l := lexer.New(p.src) stmt := &ast.ParamStatement{ ErrorCode: DefaultParamErrorCode, - Type: DefaultParamType, + Type: ast.GetDefaultParamType(paramTypes...), Src: p.src, } @@ -132,8 +189,8 @@ func (p *ParamParser) Parse() (*ast.ParamStatement, error) { case token.COLON: // type can accept both letters and numbers but not symbols ofc. nextTok := l.NextToken() - paramType := ast.LookupParamType(nextTok.Literal) - if paramType == ast.ParamTypeUnExpected { + paramType, found := ast.LookupParamType(nextTok.Literal, paramTypes...) + if !found { p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal) } stmt.Type = paramType diff --git a/core/router/macro/interpreter/parser/parser_test.go b/core/router/macro/interpreter/parser/parser_test.go index 25fb34003a..ffdd533551 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/core/router/macro/interpreter/parser/parser_test.go @@ -16,7 +16,7 @@ func TestParseParamError(t *testing.T) { input := "{id" + string(illegalChar) + "int range(1,5) else 404}" p := NewParamParser(input) - _, err := p.Parse() + _, err := p.Parse(DefaultParamTypes) if err == nil { t.Fatalf("expecting not empty error on input '%s'", input) @@ -32,7 +32,7 @@ func TestParseParamError(t *testing.T) { // success input2 := "{id:uint64 range(1,5) else 404}" p.Reset(input2) - _, err = p.Parse() + _, err = p.Parse(DefaultParamTypes) if err != nil { t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error()) @@ -40,6 +40,16 @@ func TestParseParamError(t *testing.T) { // } +// mustLookupParamType same as `ast.LookupParamType` but it panics if "indent" does not match with a valid Param Type. +func mustLookupParamType(indent string) ast.ParamType { + pt, found := ast.LookupParamType(indent, DefaultParamTypes...) + if !found { + panic("param type '" + indent + "' is not part of the provided param types") + } + + return pt +} + func TestParseParam(t *testing.T) { tests := []struct { valid bool @@ -49,7 +59,7 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{id:number min(1) max(5) else 404}", Name: "id", - Type: ast.ParamTypeNumber, + Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "min", @@ -65,7 +75,7 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{id:number range(1,5)}", Name: "id", - Type: ast.ParamTypeNumber, + Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "range", @@ -77,7 +87,7 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{file:path contains(.)}", Name: "file", - Type: ast.ParamTypePath, + Type: mustLookupParamType("path"), Funcs: []ast.ParamFunc{ { Name: "contains", @@ -89,14 +99,14 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{username:alphabetical}", Name: "username", - Type: ast.ParamTypeAlphabetical, + Type: mustLookupParamType("alphabetical"), ErrorCode: 404, }}, // 3 {true, ast.ParamStatement{ Src: "{myparam}", Name: "myparam", - Type: ast.ParamTypeString, + Type: mustLookupParamType("string"), ErrorCode: 404, }}, // 4 {false, @@ -110,14 +120,14 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{myparam2}", Name: "myparam2", // we now allow integers to the parameter names. - Type: ast.ParamTypeString, + Type: ast.GetDefaultParamType(DefaultParamTypes...), ErrorCode: 404, }}, // 6 {true, ast.ParamStatement{ Src: "{id:number even()}", // test param funcs without any arguments (LPAREN peek for RPAREN) Name: "id", - Type: ast.ParamTypeNumber, + Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "even"}, @@ -128,37 +138,44 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{id:int64 else 404}", Name: "id", - Type: ast.ParamTypeInt64, + Type: mustLookupParamType("int64"), ErrorCode: 404, }}, // 8 {true, ast.ParamStatement{ Src: "{id:long else 404}", // backwards-compatible test. Name: "id", - Type: ast.ParamTypeInt64, + Type: mustLookupParamType("int64"), ErrorCode: 404, }}, // 9 + {true, + ast.ParamStatement{ + Src: "{id:long else 404}", + Name: "id", + Type: mustLookupParamType("long"), // backwards-compatible test of LookupParamType. + ErrorCode: 404, + }}, // 10 {true, ast.ParamStatement{ Src: "{has:bool else 404}", Name: "has", - Type: ast.ParamTypeBoolean, + Type: mustLookupParamType("bool"), ErrorCode: 404, - }}, // 10 + }}, // 11 {true, ast.ParamStatement{ Src: "{has:boolean else 404}", // backwards-compatible test. Name: "has", - Type: ast.ParamTypeBoolean, + Type: mustLookupParamType("bool"), ErrorCode: 404, - }}, // 11 + }}, // 12 } p := new(ParamParser) for i, tt := range tests { p.Reset(tt.expectedStatement.Src) - resultStmt, err := p.Parse() + resultStmt, err := p.Parse(DefaultParamTypes) if tt.valid && err != nil { t.Fatalf("tests[%d] - error %s", i, err.Error()) @@ -185,7 +202,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{id:number min(1) max(5) else 404}", Name: "id", - Type: ast.ParamTypeNumber, + Type: paramTypeNumber, Funcs: []ast.ParamFunc{ { Name: "min", @@ -201,7 +218,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{id:uint64 range(1,5)}", // test alternative (backwards-compatibility) "int" Name: "id", - Type: ast.ParamTypeUint64, + Type: paramTypeUint64, Funcs: []ast.ParamFunc{ { Name: "range", @@ -214,7 +231,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{file:path contains(.)}", Name: "file", - Type: ast.ParamTypePath, + Type: paramTypePath, Funcs: []ast.ParamFunc{ { Name: "contains", @@ -227,7 +244,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{username:alphabetical}", Name: "username", - Type: ast.ParamTypeAlphabetical, + Type: paramTypeAlphabetical, ErrorCode: 404, }, }}, // 3 @@ -235,7 +252,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{myparam}", Name: "myparam", - Type: ast.ParamTypeString, + Type: paramTypeString, ErrorCode: 404, }, }}, // 4 @@ -251,7 +268,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{myparam2}", Name: "myparam2", // we now allow integers to the parameter names. - Type: ast.ParamTypeString, + Type: paramTypeString, ErrorCode: 404, }, }}, // 6 @@ -259,7 +276,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{file:path}", Name: "file", - Type: ast.ParamTypePath, + Type: paramTypePath, ErrorCode: 404, }, }}, // 7 diff --git a/deprecated.go b/deprecated.go deleted file mode 100644 index d44707b25f..0000000000 --- a/deprecated.go +++ /dev/null @@ -1 +0,0 @@ -package iris From 17c9bd26677db80bf6e7c0d3379a165e7fc228f3 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 13 Sep 2018 02:53:13 +0300 Subject: [PATCH 14/91] upstream fixes --- Gopkg.lock | 4 +- Gopkg.toml | 2 +- README.md | 3 +- configuration.go | 4 +- context/context.go | 2 +- vendor/gopkg.in/yaml.v2/LICENSE | 214 +++++++++++++++++++-- vendor/gopkg.in/yaml.v2/LICENSE.libyaml | 62 +++--- vendor/gopkg.in/yaml.v2/NOTICE | 13 ++ vendor/gopkg.in/yaml.v2/apic.go | 55 +++--- vendor/gopkg.in/yaml.v2/decode.go | 245 ++++++++++++++++-------- vendor/gopkg.in/yaml.v2/emitterc.go | 5 +- vendor/gopkg.in/yaml.v2/encode.go | 136 +++++++++---- vendor/gopkg.in/yaml.v2/readerc.go | 20 +- vendor/gopkg.in/yaml.v2/resolve.go | 80 ++++++-- vendor/gopkg.in/yaml.v2/scannerc.go | 38 ++-- vendor/gopkg.in/yaml.v2/sorter.go | 9 + vendor/gopkg.in/yaml.v2/writerc.go | 65 +------ vendor/gopkg.in/yaml.v2/yaml.go | 136 ++++++++++++- vendor/gopkg.in/yaml.v2/yamlh.go | 30 ++- 19 files changed, 807 insertions(+), 316 deletions(-) create mode 100644 vendor/gopkg.in/yaml.v2/NOTICE diff --git a/Gopkg.lock b/Gopkg.lock index d392261b51..1f3d818643 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -260,10 +260,10 @@ version = "v1.28.2" [[projects]] - branch = "v2" name = "gopkg.in/yaml.v2" packages = ["."] - revision = "eb3733d160e74a9c7e442f435eb3bea458e1d19f" + revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" + version = "2.2.1" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index b0efe84658..8274cd489c 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -71,8 +71,8 @@ name = "golang.org/x/crypto" [[constraint]] - branch = "v2" name = "gopkg.in/yaml.v2" + version = "2.2.1" [[constraint]] branch = "master" diff --git a/README.md b/README.md index b8b32f5e67..3ae3103a9f 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,8 @@ func main() { ctx.Writef("User with ID: %d", id) }) - // However, this one will match /user/john/ and also /user/john/send. + // However, this one will match /user/john/send and also /user/john/everything/else/here + // but will not match /user/john neither /user/john/. app.Post("/user/{name:string}/{action:path}", func(ctx iris.Context) { name := ctx.Params().Get("name") action := ctx.Params().Get("action") diff --git a/configuration.go b/configuration.go index 3dedfce4dd..6ee8071f20 100644 --- a/configuration.go +++ b/configuration.go @@ -118,8 +118,8 @@ func YAML(filename string) Configuration { // see `WithGlobalConfiguration` for more information. // // Usage: -// app.Configure(iris.WithConfiguration(iris.YAML("myconfig.tml"))) or -// app.Run([iris.Runner], iris.WithConfiguration(iris.YAML("myconfig.tml"))). +// app.Configure(iris.WithConfiguration(iris.TOML("myconfig.tml"))) or +// app.Run([iris.Runner], iris.WithConfiguration(iris.TOML("myconfig.tml"))). func TOML(filename string) Configuration { c := DefaultConfiguration() diff --git a/context/context.go b/context/context.go index b558fe9a5c..1ca344d0e5 100644 --- a/context/context.go +++ b/context/context.go @@ -1724,7 +1724,7 @@ type ( Subdomain string `json:"subdomain" form:"referrer_subdomain" xml:"Subdomain" yaml:"Subdomain" toml:"Subdomain"` Domain string `json:"domain" form:"referrer_domain" xml:"Domain" yaml:"Domain" toml:"Domain"` Tld string `json:"tld" form:"referrer_tld" xml:"Tld" yaml:"Tld" toml:"Tld"` - Path string `jsonn:"path" form:"referrer_path" xml:"Path" yaml:"Path" toml:"Path"` + Path string `json:"path" form:"referrer_path" xml:"Path" yaml:"Path" toml:"Path"` Query string `json:"query" form:"referrer_query" xml:"Query" yaml:"Query" toml:"GoogleType"` GoogleType ReferrerGoogleSearchType `json:"googleType" form:"referrer_google_type" xml:"GoogleType" yaml:"GoogleType" toml:"GoogleType"` } diff --git a/vendor/gopkg.in/yaml.v2/LICENSE b/vendor/gopkg.in/yaml.v2/LICENSE index 2544dd0d82..8dada3edaf 100644 --- a/vendor/gopkg.in/yaml.v2/LICENSE +++ b/vendor/gopkg.in/yaml.v2/LICENSE @@ -1,13 +1,201 @@ -Copyright 2011-2016 Canonical Ltd. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml index a01337d471..8da58fbf6f 100644 --- a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml +++ b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml @@ -1,31 +1,31 @@ -The following files were ported to Go from C files of libyaml, and thus -are still covered by their original copyright and license: - - apic.go - emitterc.go - parserc.go - readerc.go - scannerc.go - writerc.go - yamlh.go - yamlprivateh.go - -Copyright (c) 2006 Kirill Simonov - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The following files were ported to Go from C files of libyaml, and thus +are still covered by their original copyright and license: + + apic.go + emitterc.go + parserc.go + readerc.go + scannerc.go + writerc.go + yamlh.go + yamlprivateh.go + +Copyright (c) 2006 Kirill Simonov + +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. diff --git a/vendor/gopkg.in/yaml.v2/NOTICE b/vendor/gopkg.in/yaml.v2/NOTICE new file mode 100644 index 0000000000..866d74a7ad --- /dev/null +++ b/vendor/gopkg.in/yaml.v2/NOTICE @@ -0,0 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v2/apic.go index 95ec014e8c..1f7e87e672 100644 --- a/vendor/gopkg.in/yaml.v2/apic.go +++ b/vendor/gopkg.in/yaml.v2/apic.go @@ -2,7 +2,6 @@ package yaml import ( "io" - "os" ) func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) { @@ -48,9 +47,9 @@ func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err return n, nil } -// File read handler. -func yaml_file_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { - return parser.input_file.Read(buffer) +// Reader read handler. +func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { + return parser.input_reader.Read(buffer) } // Set a string input. @@ -64,12 +63,12 @@ func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) { } // Set a file input. -func yaml_parser_set_input_file(parser *yaml_parser_t, file *os.File) { +func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) { if parser.read_handler != nil { panic("must set the input source only once") } - parser.read_handler = yaml_file_read_handler - parser.input_file = file + parser.read_handler = yaml_reader_read_handler + parser.input_reader = r } // Set the source encoding. @@ -81,14 +80,13 @@ func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) { } // Create a new emitter object. -func yaml_emitter_initialize(emitter *yaml_emitter_t) bool { +func yaml_emitter_initialize(emitter *yaml_emitter_t) { *emitter = yaml_emitter_t{ buffer: make([]byte, output_buffer_size), raw_buffer: make([]byte, 0, output_raw_buffer_size), states: make([]yaml_emitter_state_t, 0, initial_stack_size), events: make([]yaml_event_t, 0, initial_queue_size), } - return true } // Destroy an emitter object. @@ -102,9 +100,10 @@ func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error { return nil } -// File write handler. -func yaml_file_write_handler(emitter *yaml_emitter_t, buffer []byte) error { - _, err := emitter.output_file.Write(buffer) +// yaml_writer_write_handler uses emitter.output_writer to write the +// emitted text. +func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error { + _, err := emitter.output_writer.Write(buffer) return err } @@ -118,12 +117,12 @@ func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]by } // Set a file output. -func yaml_emitter_set_output_file(emitter *yaml_emitter_t, file io.Writer) { +func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) { if emitter.write_handler != nil { panic("must set the output target only once") } - emitter.write_handler = yaml_file_write_handler - emitter.output_file = file + emitter.write_handler = yaml_writer_write_handler + emitter.output_writer = w } // Set the output encoding. @@ -252,41 +251,41 @@ func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) { // // Create STREAM-START. -func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) bool { +func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) { *event = yaml_event_t{ typ: yaml_STREAM_START_EVENT, encoding: encoding, } - return true } // Create STREAM-END. -func yaml_stream_end_event_initialize(event *yaml_event_t) bool { +func yaml_stream_end_event_initialize(event *yaml_event_t) { *event = yaml_event_t{ typ: yaml_STREAM_END_EVENT, } - return true } // Create DOCUMENT-START. -func yaml_document_start_event_initialize(event *yaml_event_t, version_directive *yaml_version_directive_t, - tag_directives []yaml_tag_directive_t, implicit bool) bool { +func yaml_document_start_event_initialize( + event *yaml_event_t, + version_directive *yaml_version_directive_t, + tag_directives []yaml_tag_directive_t, + implicit bool, +) { *event = yaml_event_t{ typ: yaml_DOCUMENT_START_EVENT, version_directive: version_directive, tag_directives: tag_directives, implicit: implicit, } - return true } // Create DOCUMENT-END. -func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) bool { +func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) { *event = yaml_event_t{ typ: yaml_DOCUMENT_END_EVENT, implicit: implicit, } - return true } ///* @@ -348,7 +347,7 @@ func yaml_sequence_end_event_initialize(event *yaml_event_t) bool { } // Create MAPPING-START. -func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) bool { +func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) { *event = yaml_event_t{ typ: yaml_MAPPING_START_EVENT, anchor: anchor, @@ -356,15 +355,13 @@ func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte implicit: implicit, style: yaml_style_t(style), } - return true } // Create MAPPING-END. -func yaml_mapping_end_event_initialize(event *yaml_event_t) bool { +func yaml_mapping_end_event_initialize(event *yaml_event_t) { *event = yaml_event_t{ typ: yaml_MAPPING_END_EVENT, } - return true } // Destroy an event object. @@ -471,7 +468,7 @@ func yaml_event_delete(event *yaml_event_t) { // } context // tag_directive *yaml_tag_directive_t // -// context.error = YAML_NO_ERROR // Eliminate a compliler warning. +// context.error = YAML_NO_ERROR // Eliminate a compiler warning. // // assert(document) // Non-NULL document object is expected. // diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go index 052ecfcd19..e4e56e28e0 100644 --- a/vendor/gopkg.in/yaml.v2/decode.go +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -4,6 +4,7 @@ import ( "encoding" "encoding/base64" "fmt" + "io" "math" "reflect" "strconv" @@ -22,19 +23,22 @@ type node struct { kind int line, column int tag string - value string - implicit bool - children []*node - anchors map[string]*node + // For an alias node, alias holds the resolved alias. + alias *node + value string + implicit bool + children []*node + anchors map[string]*node } // ---------------------------------------------------------------------------- // Parser, produces a node tree out of a libyaml event stream. type parser struct { - parser yaml_parser_t - event yaml_event_t - doc *node + parser yaml_parser_t + event yaml_event_t + doc *node + doneInit bool } func newParser(b []byte) *parser { @@ -42,21 +46,30 @@ func newParser(b []byte) *parser { if !yaml_parser_initialize(&p.parser) { panic("failed to initialize YAML emitter") } - if len(b) == 0 { b = []byte{'\n'} } - yaml_parser_set_input_string(&p.parser, b) + return &p +} - p.skip() - if p.event.typ != yaml_STREAM_START_EVENT { - panic("expected stream start event, got " + strconv.Itoa(int(p.event.typ))) +func newParserFromReader(r io.Reader) *parser { + p := parser{} + if !yaml_parser_initialize(&p.parser) { + panic("failed to initialize YAML emitter") } - p.skip() + yaml_parser_set_input_reader(&p.parser, r) return &p } +func (p *parser) init() { + if p.doneInit { + return + } + p.expect(yaml_STREAM_START_EVENT) + p.doneInit = true +} + func (p *parser) destroy() { if p.event.typ != yaml_NO_EVENT { yaml_event_delete(&p.event) @@ -64,16 +77,35 @@ func (p *parser) destroy() { yaml_parser_delete(&p.parser) } -func (p *parser) skip() { - if p.event.typ != yaml_NO_EVENT { - if p.event.typ == yaml_STREAM_END_EVENT { - failf("attempted to go past the end of stream; corrupted value?") +// expect consumes an event from the event stream and +// checks that it's of the expected type. +func (p *parser) expect(e yaml_event_type_t) { + if p.event.typ == yaml_NO_EVENT { + if !yaml_parser_parse(&p.parser, &p.event) { + p.fail() } - yaml_event_delete(&p.event) + } + if p.event.typ == yaml_STREAM_END_EVENT { + failf("attempted to go past the end of stream; corrupted value?") + } + if p.event.typ != e { + p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ) + p.fail() + } + yaml_event_delete(&p.event) + p.event.typ = yaml_NO_EVENT +} + +// peek peeks at the next event in the event stream, +// puts the results into p.event and returns the event type. +func (p *parser) peek() yaml_event_type_t { + if p.event.typ != yaml_NO_EVENT { + return p.event.typ } if !yaml_parser_parse(&p.parser, &p.event) { p.fail() } + return p.event.typ } func (p *parser) fail() { @@ -81,6 +113,10 @@ func (p *parser) fail() { var line int if p.parser.problem_mark.line != 0 { line = p.parser.problem_mark.line + // Scanner errors don't iterate line before returning error + if p.parser.error == yaml_SCANNER_ERROR { + line++ + } } else if p.parser.context_mark.line != 0 { line = p.parser.context_mark.line } @@ -103,7 +139,8 @@ func (p *parser) anchor(n *node, anchor []byte) { } func (p *parser) parse() *node { - switch p.event.typ { + p.init() + switch p.peek() { case yaml_SCALAR_EVENT: return p.scalar() case yaml_ALIAS_EVENT: @@ -118,7 +155,7 @@ func (p *parser) parse() *node { // Happens when attempting to decode an empty buffer. return nil default: - panic("attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ))) + panic("attempted to parse unknown event: " + p.event.typ.String()) } } @@ -134,19 +171,20 @@ func (p *parser) document() *node { n := p.node(documentNode) n.anchors = make(map[string]*node) p.doc = n - p.skip() + p.expect(yaml_DOCUMENT_START_EVENT) n.children = append(n.children, p.parse()) - if p.event.typ != yaml_DOCUMENT_END_EVENT { - panic("expected end of document event but got " + strconv.Itoa(int(p.event.typ))) - } - p.skip() + p.expect(yaml_DOCUMENT_END_EVENT) return n } func (p *parser) alias() *node { n := p.node(aliasNode) n.value = string(p.event.anchor) - p.skip() + n.alias = p.doc.anchors[n.value] + if n.alias == nil { + failf("unknown anchor '%s' referenced", n.value) + } + p.expect(yaml_ALIAS_EVENT) return n } @@ -156,29 +194,29 @@ func (p *parser) scalar() *node { n.tag = string(p.event.tag) n.implicit = p.event.implicit p.anchor(n, p.event.anchor) - p.skip() + p.expect(yaml_SCALAR_EVENT) return n } func (p *parser) sequence() *node { n := p.node(sequenceNode) p.anchor(n, p.event.anchor) - p.skip() - for p.event.typ != yaml_SEQUENCE_END_EVENT { + p.expect(yaml_SEQUENCE_START_EVENT) + for p.peek() != yaml_SEQUENCE_END_EVENT { n.children = append(n.children, p.parse()) } - p.skip() + p.expect(yaml_SEQUENCE_END_EVENT) return n } func (p *parser) mapping() *node { n := p.node(mappingNode) p.anchor(n, p.event.anchor) - p.skip() - for p.event.typ != yaml_MAPPING_END_EVENT { + p.expect(yaml_MAPPING_START_EVENT) + for p.peek() != yaml_MAPPING_END_EVENT { n.children = append(n.children, p.parse(), p.parse()) } - p.skip() + p.expect(yaml_MAPPING_END_EVENT) return n } @@ -187,9 +225,10 @@ func (p *parser) mapping() *node { type decoder struct { doc *node - aliases map[string]bool + aliases map[*node]bool mapType reflect.Type terrors []string + strict bool } var ( @@ -197,11 +236,13 @@ var ( durationType = reflect.TypeOf(time.Duration(0)) defaultMapType = reflect.TypeOf(map[interface{}]interface{}{}) ifaceType = defaultMapType.Elem() + timeType = reflect.TypeOf(time.Time{}) + ptrTimeType = reflect.TypeOf(&time.Time{}) ) -func newDecoder() *decoder { - d := &decoder{mapType: defaultMapType} - d.aliases = make(map[string]bool) +func newDecoder(strict bool) *decoder { + d := &decoder{mapType: defaultMapType, strict: strict} + d.aliases = make(map[*node]bool) return d } @@ -250,7 +291,7 @@ func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) { // // If n holds a null value, prepare returns before doing anything. func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) { - if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "" && n.implicit) { + if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) { return out, false, false } again := true @@ -307,16 +348,13 @@ func (d *decoder) document(n *node, out reflect.Value) (good bool) { } func (d *decoder) alias(n *node, out reflect.Value) (good bool) { - an, ok := d.doc.anchors[n.value] - if !ok { - failf("unknown anchor '%s' referenced", n.value) - } - if d.aliases[n.value] { + if d.aliases[n] { + // TODO this could actually be allowed in some circumstances. failf("anchor '%s' value contains itself", n.value) } - d.aliases[n.value] = true - good = d.unmarshal(an, out) - delete(d.aliases, n.value) + d.aliases[n] = true + good = d.unmarshal(n.alias, out) + delete(d.aliases, n) return good } @@ -328,7 +366,7 @@ func resetMap(out reflect.Value) { } } -func (d *decoder) scalar(n *node, out reflect.Value) (good bool) { +func (d *decoder) scalar(n *node, out reflect.Value) bool { var tag string var resolved interface{} if n.tag == "" && !n.implicit { @@ -352,9 +390,26 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) { } return true } - if s, ok := resolved.(string); ok && out.CanAddr() { - if u, ok := out.Addr().Interface().(encoding.TextUnmarshaler); ok { - err := u.UnmarshalText([]byte(s)) + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + // We've resolved to exactly the type we want, so use that. + out.Set(resolvedv) + return true + } + // Perhaps we can use the value as a TextUnmarshaler to + // set its value. + if out.CanAddr() { + u, ok := out.Addr().Interface().(encoding.TextUnmarshaler) + if ok { + var text []byte + if tag == yaml_BINARY_TAG { + text = []byte(resolved.(string)) + } else { + // We let any value be unmarshaled into TextUnmarshaler. + // That might be more lax than we'd like, but the + // TextUnmarshaler itself should bowl out any dubious values. + text = []byte(n.value) + } + err := u.UnmarshalText(text) if err != nil { fail(err) } @@ -365,46 +420,54 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) { case reflect.String: if tag == yaml_BINARY_TAG { out.SetString(resolved.(string)) - good = true - } else if resolved != nil { + return true + } + if resolved != nil { out.SetString(n.value) - good = true + return true } case reflect.Interface: if resolved == nil { out.Set(reflect.Zero(out.Type())) + } else if tag == yaml_TIMESTAMP_TAG { + // It looks like a timestamp but for backward compatibility + // reasons we set it as a string, so that code that unmarshals + // timestamp-like values into interface{} will continue to + // see a string and not a time.Time. + // TODO(v3) Drop this. + out.Set(reflect.ValueOf(n.value)) } else { out.Set(reflect.ValueOf(resolved)) } - good = true + return true case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: switch resolved := resolved.(type) { case int: if !out.OverflowInt(int64(resolved)) { out.SetInt(int64(resolved)) - good = true + return true } case int64: if !out.OverflowInt(resolved) { out.SetInt(resolved) - good = true + return true } case uint64: if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { out.SetInt(int64(resolved)) - good = true + return true } case float64: if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { out.SetInt(int64(resolved)) - good = true + return true } case string: if out.Type() == durationType { d, err := time.ParseDuration(resolved) if err == nil { out.SetInt(int64(d)) - good = true + return true } } } @@ -413,44 +476,49 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) { case int: if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { out.SetUint(uint64(resolved)) - good = true + return true } case int64: if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { out.SetUint(uint64(resolved)) - good = true + return true } case uint64: if !out.OverflowUint(uint64(resolved)) { out.SetUint(uint64(resolved)) - good = true + return true } case float64: if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) { out.SetUint(uint64(resolved)) - good = true + return true } } case reflect.Bool: switch resolved := resolved.(type) { case bool: out.SetBool(resolved) - good = true + return true } case reflect.Float32, reflect.Float64: switch resolved := resolved.(type) { case int: out.SetFloat(float64(resolved)) - good = true + return true case int64: out.SetFloat(float64(resolved)) - good = true + return true case uint64: out.SetFloat(float64(resolved)) - good = true + return true case float64: out.SetFloat(resolved) - good = true + return true + } + case reflect.Struct: + if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() { + out.Set(resolvedv) + return true } case reflect.Ptr: if out.Type().Elem() == reflect.TypeOf(resolved) { @@ -458,13 +526,11 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) { elem := reflect.New(out.Type().Elem()) elem.Elem().Set(reflect.ValueOf(resolved)) out.Set(elem) - good = true + return true } } - if !good { - d.terror(n, tag, out) - } - return good + d.terror(n, tag, out) + return false } func settableValueOf(i interface{}) reflect.Value { @@ -481,6 +547,10 @@ func (d *decoder) sequence(n *node, out reflect.Value) (good bool) { switch out.Kind() { case reflect.Slice: out.Set(reflect.MakeSlice(out.Type(), l, l)) + case reflect.Array: + if l != out.Len() { + failf("invalid array: want %d elements but got %d", out.Len(), l) + } case reflect.Interface: // No type hints. Will have to use a generic sequence. iface = out @@ -499,7 +569,9 @@ func (d *decoder) sequence(n *node, out reflect.Value) (good bool) { j++ } } - out.Set(out.Slice(0, j)) + if out.Kind() != reflect.Array { + out.Set(out.Slice(0, j)) + } if iface.IsValid() { iface.Set(out) } @@ -560,7 +632,7 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) { } e := reflect.New(et).Elem() if d.unmarshal(n.children[i+1], e) { - out.SetMapIndex(k, e) + d.setMapIndex(n.children[i+1], out, k, e) } } } @@ -568,6 +640,14 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) { return true } +func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) { + if d.strict && out.MapIndex(k) != zeroValue { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface())) + return + } + out.SetMapIndex(k, v) +} + func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) { outt := out.Type() if outt.Elem() != mapItemType { @@ -615,6 +695,10 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { elemType = inlineMap.Type().Elem() } + var doneFields []bool + if d.strict { + doneFields = make([]bool, len(sinfo.FieldsList)) + } for i := 0; i < l; i += 2 { ni := n.children[i] if isMerge(ni) { @@ -625,6 +709,13 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { continue } if info, ok := sinfo.FieldsMap[name.String()]; ok { + if d.strict { + if doneFields[info.Id] { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type())) + continue + } + doneFields[info.Id] = true + } var field reflect.Value if info.Inline == nil { field = out.Field(info.Num) @@ -638,7 +729,9 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) { } value := reflect.New(elemType).Elem() d.unmarshal(n.children[i+1], value) - inlineMap.SetMapIndex(name, value) + d.setMapIndex(n.children[i+1], inlineMap, name, value) + } else if d.strict { + d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type())) } } return true diff --git a/vendor/gopkg.in/yaml.v2/emitterc.go b/vendor/gopkg.in/yaml.v2/emitterc.go index dcaf502f0e..a1c2cc5262 100644 --- a/vendor/gopkg.in/yaml.v2/emitterc.go +++ b/vendor/gopkg.in/yaml.v2/emitterc.go @@ -2,6 +2,7 @@ package yaml import ( "bytes" + "fmt" ) // Flush the buffer if needed. @@ -664,7 +665,7 @@ func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t, return yaml_emitter_emit_mapping_start(emitter, event) default: return yaml_emitter_set_emitter_error(emitter, - "expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS") + fmt.Sprintf("expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, but got %v", event.typ)) } } @@ -842,7 +843,7 @@ func yaml_emitter_select_scalar_style(emitter *yaml_emitter_t, event *yaml_event return true } -// Write an achor. +// Write an anchor. func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool { if emitter.anchor_data.anchor == nil { return true diff --git a/vendor/gopkg.in/yaml.v2/encode.go b/vendor/gopkg.in/yaml.v2/encode.go index 84f8499551..a14435e82f 100644 --- a/vendor/gopkg.in/yaml.v2/encode.go +++ b/vendor/gopkg.in/yaml.v2/encode.go @@ -3,12 +3,14 @@ package yaml import ( "encoding" "fmt" + "io" "reflect" "regexp" "sort" "strconv" "strings" "time" + "unicode/utf8" ) type encoder struct { @@ -16,25 +18,39 @@ type encoder struct { event yaml_event_t out []byte flow bool + // doneInit holds whether the initial stream_start_event has been + // emitted. + doneInit bool } -func newEncoder() (e *encoder) { - e = &encoder{} - e.must(yaml_emitter_initialize(&e.emitter)) +func newEncoder() *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) yaml_emitter_set_output_string(&e.emitter, &e.out) yaml_emitter_set_unicode(&e.emitter, true) - e.must(yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)) - e.emit() - e.must(yaml_document_start_event_initialize(&e.event, nil, nil, true)) - e.emit() return e } -func (e *encoder) finish() { - e.must(yaml_document_end_event_initialize(&e.event, true)) +func newEncoderWithWriter(w io.Writer) *encoder { + e := &encoder{} + yaml_emitter_initialize(&e.emitter) + yaml_emitter_set_output_writer(&e.emitter, w) + yaml_emitter_set_unicode(&e.emitter, true) + return e +} + +func (e *encoder) init() { + if e.doneInit { + return + } + yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING) e.emit() + e.doneInit = true +} + +func (e *encoder) finish() { e.emitter.open_ended = false - e.must(yaml_stream_end_event_initialize(&e.event)) + yaml_stream_end_event_initialize(&e.event) e.emit() } @@ -44,9 +60,7 @@ func (e *encoder) destroy() { func (e *encoder) emit() { // This will internally delete the e.event value. - if !yaml_emitter_emit(&e.emitter, &e.event) && e.event.typ != yaml_DOCUMENT_END_EVENT && e.event.typ != yaml_STREAM_END_EVENT { - e.must(false) - } + e.must(yaml_emitter_emit(&e.emitter, &e.event)) } func (e *encoder) must(ok bool) { @@ -59,13 +73,28 @@ func (e *encoder) must(ok bool) { } } +func (e *encoder) marshalDoc(tag string, in reflect.Value) { + e.init() + yaml_document_start_event_initialize(&e.event, nil, nil, true) + e.emit() + e.marshal(tag, in) + yaml_document_end_event_initialize(&e.event, true) + e.emit() +} + func (e *encoder) marshal(tag string, in reflect.Value) { - if !in.IsValid() { + if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() { e.nilv() return } iface := in.Interface() - if m, ok := iface.(Marshaler); ok { + switch m := iface.(type) { + case time.Time, *time.Time: + // Although time.Time implements TextMarshaler, + // we don't want to treat it as a string for YAML + // purposes because YAML has special support for + // timestamps. + case Marshaler: v, err := m.MarshalYAML() if err != nil { fail(err) @@ -75,31 +104,34 @@ func (e *encoder) marshal(tag string, in reflect.Value) { return } in = reflect.ValueOf(v) - } else if m, ok := iface.(encoding.TextMarshaler); ok { + case encoding.TextMarshaler: text, err := m.MarshalText() if err != nil { fail(err) } in = reflect.ValueOf(string(text)) + case nil: + e.nilv() + return } switch in.Kind() { case reflect.Interface: - if in.IsNil() { - e.nilv() - } else { - e.marshal(tag, in.Elem()) - } + e.marshal(tag, in.Elem()) case reflect.Map: e.mapv(tag, in) case reflect.Ptr: - if in.IsNil() { - e.nilv() + if in.Type() == ptrTimeType { + e.timev(tag, in.Elem()) } else { e.marshal(tag, in.Elem()) } case reflect.Struct: - e.structv(tag, in) - case reflect.Slice: + if in.Type() == timeType { + e.timev(tag, in) + } else { + e.structv(tag, in) + } + case reflect.Slice, reflect.Array: if in.Type().Elem() == mapItemType { e.itemsv(tag, in) } else { @@ -191,10 +223,10 @@ func (e *encoder) mappingv(tag string, f func()) { e.flow = false style = yaml_FLOW_MAPPING_STYLE } - e.must(yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) + yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style) e.emit() f() - e.must(yaml_mapping_end_event_initialize(&e.event)) + yaml_mapping_end_event_initialize(&e.event) e.emit() } @@ -240,23 +272,36 @@ var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0 func (e *encoder) stringv(tag string, in reflect.Value) { var style yaml_scalar_style_t s := in.String() - rtag, rs := resolve("", s) - if rtag == yaml_BINARY_TAG { - if tag == "" || tag == yaml_STR_TAG { - tag = rtag - s = rs.(string) - } else if tag == yaml_BINARY_TAG { + canUsePlain := true + switch { + case !utf8.ValidString(s): + if tag == yaml_BINARY_TAG { failf("explicitly tagged !!binary data must be base64-encoded") - } else { + } + if tag != "" { failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) } + // It can't be encoded directly as YAML so use a binary tag + // and encode it as base64. + tag = yaml_BINARY_TAG + s = encodeBase64(s) + case tag == "": + // Check to see if it would resolve to a specific + // tag when encoded unquoted. If it doesn't, + // there's no need to quote it. + rtag, _ := resolve("", s) + canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s) } - if tag == "" && (rtag != yaml_STR_TAG || isBase60Float(s)) { - style = yaml_DOUBLE_QUOTED_SCALAR_STYLE - } else if strings.Contains(s, "\n") { + // Note: it's possible for user code to emit invalid YAML + // if they explicitly specify a tag and a string containing + // text that's incompatible with that tag. + switch { + case strings.Contains(s, "\n"): style = yaml_LITERAL_SCALAR_STYLE - } else { + case canUsePlain: style = yaml_PLAIN_SCALAR_STYLE + default: + style = yaml_DOUBLE_QUOTED_SCALAR_STYLE } e.emitScalar(s, "", tag, style) } @@ -281,9 +326,20 @@ func (e *encoder) uintv(tag string, in reflect.Value) { e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) } +func (e *encoder) timev(tag string, in reflect.Value) { + t := in.Interface().(time.Time) + s := t.Format(time.RFC3339Nano) + e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) +} + func (e *encoder) floatv(tag string, in reflect.Value) { - // FIXME: Handle 64 bits here. - s := strconv.FormatFloat(float64(in.Float()), 'g', -1, 32) + // Issue #352: When formatting, use the precision of the underlying value + precision := 64 + if in.Kind() == reflect.Float32 { + precision = 32 + } + + s := strconv.FormatFloat(in.Float(), 'g', -1, precision) switch s { case "+Inf": s = ".inf" diff --git a/vendor/gopkg.in/yaml.v2/readerc.go b/vendor/gopkg.in/yaml.v2/readerc.go index f450791717..7c1f5fac3d 100644 --- a/vendor/gopkg.in/yaml.v2/readerc.go +++ b/vendor/gopkg.in/yaml.v2/readerc.go @@ -93,9 +93,18 @@ func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { panic("read handler must be set") } + // [Go] This function was changed to guarantee the requested length size at EOF. + // The fact we need to do this is pretty awful, but the description above implies + // for that to be the case, and there are tests + // If the EOF flag is set and the raw buffer is empty, do nothing. if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { - return true + // [Go] ACTUALLY! Read the documentation of this function above. + // This is just broken. To return true, we need to have the + // given length in the buffer. Not doing that means every single + // check that calls this function to make sure the buffer has a + // given length is Go) panicking; or C) accessing invalid memory. + //return true } // Return if the buffer contains enough characters. @@ -389,6 +398,15 @@ func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool { break } } + // [Go] Read the documentation of this function above. To return true, + // we need to have the given length in the buffer. Not doing that means + // every single check that calls this function to make sure the buffer + // has a given length is Go) panicking; or C) accessing invalid memory. + // This happens here due to the EOF above breaking early. + for buffer_len < length { + parser.buffer[buffer_len] = 0 + buffer_len++ + } parser.buffer = parser.buffer[:buffer_len] return true } diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go index 232313cc08..6c151db6fb 100644 --- a/vendor/gopkg.in/yaml.v2/resolve.go +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -6,7 +6,7 @@ import ( "regexp" "strconv" "strings" - "unicode/utf8" + "time" ) type resolveMapItem struct { @@ -75,7 +75,7 @@ func longTag(tag string) string { func resolvableTag(tag string) bool { switch tag { - case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG: + case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG: return true } return false @@ -92,6 +92,19 @@ func resolve(tag string, in string) (rtag string, out interface{}) { switch tag { case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG: return + case yaml_FLOAT_TAG: + if rtag == yaml_INT_TAG { + switch v := out.(type) { + case int64: + rtag = yaml_FLOAT_TAG + out = float64(v) + return + case int: + rtag = yaml_FLOAT_TAG + out = float64(v) + return + } + } } failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)) }() @@ -125,6 +138,15 @@ func resolve(tag string, in string) (rtag string, out interface{}) { case 'D', 'S': // Int, float, or timestamp. + // Only try values as a timestamp if the value is unquoted or there's an explicit + // !!timestamp tag. + if tag == "" || tag == yaml_TIMESTAMP_TAG { + t, ok := parseTimestamp(in) + if ok { + return yaml_TIMESTAMP_TAG, t + } + } + plain := strings.Replace(in, "_", "", -1) intv, err := strconv.ParseInt(plain, 0, 64) if err == nil { @@ -158,28 +180,20 @@ func resolve(tag string, in string) (rtag string, out interface{}) { return yaml_INT_TAG, uintv } } else if strings.HasPrefix(plain, "-0b") { - intv, err := strconv.ParseInt(plain[3:], 2, 64) + intv, err := strconv.ParseInt("-" + plain[3:], 2, 64) if err == nil { - if intv == int64(int(intv)) { - return yaml_INT_TAG, -int(intv) + if true || intv == int64(int(intv)) { + return yaml_INT_TAG, int(intv) } else { - return yaml_INT_TAG, -intv + return yaml_INT_TAG, intv } } } - // XXX Handle timestamps here. - default: panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")") } } - if tag == yaml_BINARY_TAG { - return yaml_BINARY_TAG, in - } - if utf8.ValidString(in) { - return yaml_STR_TAG, in - } - return yaml_BINARY_TAG, encodeBase64(in) + return yaml_STR_TAG, in } // encodeBase64 encodes s as base64 that is broken up into multiple lines @@ -206,3 +220,39 @@ func encodeBase64(s string) string { } return string(out[:k]) } + +// This is a subset of the formats allowed by the regular expression +// defined at http://yaml.org/type/timestamp.html. +var allowedTimestampFormats = []string{ + "2006-1-2T15:4:5.999999999Z07:00", // RCF3339Nano with short date fields. + "2006-1-2t15:4:5.999999999Z07:00", // RFC3339Nano with short date fields and lower-case "t". + "2006-1-2 15:4:5.999999999", // space separated with no time zone + "2006-1-2", // date only + // Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5" + // from the set of examples. +} + +// parseTimestamp parses s as a timestamp string and +// returns the timestamp and reports whether it succeeded. +// Timestamp formats are defined at http://yaml.org/type/timestamp.html +func parseTimestamp(s string) (time.Time, bool) { + // TODO write code to check all the formats supported by + // http://yaml.org/type/timestamp.html instead of using time.Parse. + + // Quick check: all date formats start with YYYY-. + i := 0 + for ; i < len(s); i++ { + if c := s[i]; c < '0' || c > '9' { + break + } + } + if i != 4 || i == len(s) || s[i] != '-' { + return time.Time{}, false + } + for _, format := range allowedTimestampFormats { + if t, err := time.Parse(format, s); err == nil { + return t, true + } + } + return time.Time{}, false +} diff --git a/vendor/gopkg.in/yaml.v2/scannerc.go b/vendor/gopkg.in/yaml.v2/scannerc.go index 2c9d5111f9..077fd1dd2d 100644 --- a/vendor/gopkg.in/yaml.v2/scannerc.go +++ b/vendor/gopkg.in/yaml.v2/scannerc.go @@ -611,7 +611,7 @@ func yaml_parser_set_scanner_tag_error(parser *yaml_parser_t, directive bool, co if directive { context = "while parsing a %TAG directive" } - return yaml_parser_set_scanner_error(parser, context, context_mark, "did not find URI escaped octet") + return yaml_parser_set_scanner_error(parser, context, context_mark, problem) } func trace(args ...interface{}) func() { @@ -871,12 +871,6 @@ func yaml_parser_save_simple_key(parser *yaml_parser_t) bool { required := parser.flow_level == 0 && parser.indent == parser.mark.column - // A simple key is required only when it is the first token in the current - // line. Therefore it is always allowed. But we add a check anyway. - if required && !parser.simple_key_allowed { - panic("should not happen") - } - // // If the current position may start a simple key, save it. // @@ -1944,7 +1938,7 @@ func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_ma } else { // It's either the '!' tag or not really a tag handle. If it's a %TAG // directive, it's an error. If it's a tag token, it must be a part of URI. - if directive && !(s[0] == '!' && s[1] == 0) { + if directive && string(s) != "!" { yaml_parser_set_scanner_tag_error(parser, directive, start_mark, "did not find expected '!'") return false @@ -1959,6 +1953,7 @@ func yaml_parser_scan_tag_handle(parser *yaml_parser_t, directive bool, start_ma func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte, start_mark yaml_mark_t, uri *[]byte) bool { //size_t length = head ? strlen((char *)head) : 0 var s []byte + hasTag := len(head) > 0 // Copy the head if needed. // @@ -2000,10 +1995,10 @@ func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { return false } + hasTag = true } - // Check if the tag is non-empty. - if len(s) == 0 { + if !hasTag { yaml_parser_set_scanner_tag_error(parser, directive, start_mark, "did not find expected tag URI") return false @@ -2474,6 +2469,10 @@ func yaml_parser_scan_flow_scalar(parser *yaml_parser_t, token *yaml_token_t, si } } + if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { + return false + } + // Check if we are at the end of the scalar. if single { if parser.buffer[parser.buffer_pos] == '\'' { @@ -2486,10 +2485,6 @@ func yaml_parser_scan_flow_scalar(parser *yaml_parser_t, token *yaml_token_t, si } // Consume blank characters. - if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) { - return false - } - for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { if is_blank(parser.buffer, parser.buffer_pos) { // Consume a space or a tab character. @@ -2591,19 +2586,10 @@ func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) b // Consume non-blank characters. for !is_blankz(parser.buffer, parser.buffer_pos) { - // Check for 'x:x' in the flow context. TODO: Fix the test "spec-08-13". - if parser.flow_level > 0 && - parser.buffer[parser.buffer_pos] == ':' && - !is_blankz(parser.buffer, parser.buffer_pos+1) { - yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", - start_mark, "found unexpected ':'") - return false - } - // Check for indicators that may end a plain scalar. if (parser.buffer[parser.buffer_pos] == ':' && is_blankz(parser.buffer, parser.buffer_pos+1)) || (parser.flow_level > 0 && - (parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == ':' || + (parser.buffer[parser.buffer_pos] == ',' || parser.buffer[parser.buffer_pos] == '?' || parser.buffer[parser.buffer_pos] == '[' || parser.buffer[parser.buffer_pos] == ']' || parser.buffer[parser.buffer_pos] == '{' || parser.buffer[parser.buffer_pos] == '}')) { @@ -2655,10 +2641,10 @@ func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) b for is_blank(parser.buffer, parser.buffer_pos) || is_break(parser.buffer, parser.buffer_pos) { if is_blank(parser.buffer, parser.buffer_pos) { - // Check for tab character that abuse indentation. + // Check for tab characters that abuse indentation. if leading_blanks && parser.mark.column < indent && is_tab(parser.buffer, parser.buffer_pos) { yaml_parser_set_scanner_error(parser, "while scanning a plain scalar", - start_mark, "found a tab character that violate indentation") + start_mark, "found a tab character that violates indentation") return false } diff --git a/vendor/gopkg.in/yaml.v2/sorter.go b/vendor/gopkg.in/yaml.v2/sorter.go index 5958822f9c..4c45e660a8 100644 --- a/vendor/gopkg.in/yaml.v2/sorter.go +++ b/vendor/gopkg.in/yaml.v2/sorter.go @@ -51,6 +51,15 @@ func (l keyList) Less(i, j int) bool { } var ai, bi int var an, bn int64 + if ar[i] == '0' || br[i] == '0' { + for j := i-1; j >= 0 && unicode.IsDigit(ar[j]); j-- { + if ar[j] != '0' { + an = 1 + bn = 1 + break + } + } + } for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ { an = an*10 + int64(ar[ai]-'0') } diff --git a/vendor/gopkg.in/yaml.v2/writerc.go b/vendor/gopkg.in/yaml.v2/writerc.go index 190362f25d..a2dde608cb 100644 --- a/vendor/gopkg.in/yaml.v2/writerc.go +++ b/vendor/gopkg.in/yaml.v2/writerc.go @@ -18,72 +18,9 @@ func yaml_emitter_flush(emitter *yaml_emitter_t) bool { return true } - // If the output encoding is UTF-8, we don't need to recode the buffer. - if emitter.encoding == yaml_UTF8_ENCODING { - if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { - return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) - } - emitter.buffer_pos = 0 - return true - } - - // Recode the buffer into the raw buffer. - var low, high int - if emitter.encoding == yaml_UTF16LE_ENCODING { - low, high = 0, 1 - } else { - high, low = 1, 0 - } - - pos := 0 - for pos < emitter.buffer_pos { - // See the "reader.c" code for more details on UTF-8 encoding. Note - // that we assume that the buffer contains a valid UTF-8 sequence. - - // Read the next UTF-8 character. - octet := emitter.buffer[pos] - - var w int - var value rune - switch { - case octet&0x80 == 0x00: - w, value = 1, rune(octet&0x7F) - case octet&0xE0 == 0xC0: - w, value = 2, rune(octet&0x1F) - case octet&0xF0 == 0xE0: - w, value = 3, rune(octet&0x0F) - case octet&0xF8 == 0xF0: - w, value = 4, rune(octet&0x07) - } - for k := 1; k < w; k++ { - octet = emitter.buffer[pos+k] - value = (value << 6) + (rune(octet) & 0x3F) - } - pos += w - - // Write the character. - if value < 0x10000 { - var b [2]byte - b[high] = byte(value >> 8) - b[low] = byte(value & 0xFF) - emitter.raw_buffer = append(emitter.raw_buffer, b[0], b[1]) - } else { - // Write the character using a surrogate pair (check "reader.c"). - var b [4]byte - value -= 0x10000 - b[high] = byte(0xD8 + (value >> 18)) - b[low] = byte((value >> 10) & 0xFF) - b[high+2] = byte(0xDC + ((value >> 8) & 0xFF)) - b[low+2] = byte(value & 0xFF) - emitter.raw_buffer = append(emitter.raw_buffer, b[0], b[1], b[2], b[3]) - } - } - - // Write the raw buffer. - if err := emitter.write_handler(emitter, emitter.raw_buffer); err != nil { + if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil { return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error()) } emitter.buffer_pos = 0 - emitter.raw_buffer = emitter.raw_buffer[:0] return true } diff --git a/vendor/gopkg.in/yaml.v2/yaml.go b/vendor/gopkg.in/yaml.v2/yaml.go index 36d6b883a6..de85aa4cdb 100644 --- a/vendor/gopkg.in/yaml.v2/yaml.go +++ b/vendor/gopkg.in/yaml.v2/yaml.go @@ -9,6 +9,7 @@ package yaml import ( "errors" "fmt" + "io" "reflect" "strings" "sync" @@ -77,8 +78,65 @@ type Marshaler interface { // supported tag options. // func Unmarshal(in []byte, out interface{}) (err error) { + return unmarshal(in, out, false) +} + +// UnmarshalStrict is like Unmarshal except that any fields that are found +// in the data that do not have corresponding struct members, or mapping +// keys that are duplicates, will result in +// an error. +func UnmarshalStrict(in []byte, out interface{}) (err error) { + return unmarshal(in, out, true) +} + +// A Decorder reads and decodes YAML values from an input stream. +type Decoder struct { + strict bool + parser *parser +} + +// NewDecoder returns a new decoder that reads from r. +// +// The decoder introduces its own buffering and may read +// data from r beyond the YAML values requested. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{ + parser: newParserFromReader(r), + } +} + +// SetStrict sets whether strict decoding behaviour is enabled when +// decoding items in the data (see UnmarshalStrict). By default, decoding is not strict. +func (dec *Decoder) SetStrict(strict bool) { + dec.strict = strict +} + +// Decode reads the next YAML-encoded value from its input +// and stores it in the value pointed to by v. +// +// See the documentation for Unmarshal for details about the +// conversion of YAML into a Go value. +func (dec *Decoder) Decode(v interface{}) (err error) { + d := newDecoder(dec.strict) + defer handleErr(&err) + node := dec.parser.parse() + if node == nil { + return io.EOF + } + out := reflect.ValueOf(v) + if out.Kind() == reflect.Ptr && !out.IsNil() { + out = out.Elem() + } + d.unmarshal(node, out) + if len(d.terrors) > 0 { + return &TypeError{d.terrors} + } + return nil +} + +func unmarshal(in []byte, out interface{}, strict bool) (err error) { defer handleErr(&err) - d := newDecoder() + d := newDecoder(strict) p := newParser(in) defer p.destroy() node := p.parse() @@ -99,8 +157,8 @@ func Unmarshal(in []byte, out interface{}) (err error) { // of the generated document will reflect the structure of the value itself. // Maps and pointers (to struct, string, int, etc) are accepted as the in value. // -// Struct fields are only unmarshalled if they are exported (have an upper case -// first letter), and are unmarshalled using the field name lowercased as the +// Struct fields are only marshalled if they are exported (have an upper case +// first letter), and are marshalled using the field name lowercased as the // default key. Custom keys may be defined via the "yaml" name in the field // tag: the content preceding the first comma is used as the key, and the // following comma-separated options are used to tweak the marshalling process. @@ -114,7 +172,10 @@ func Unmarshal(in []byte, out interface{}) (err error) { // // omitempty Only include the field if it's not set to the zero // value for the type or to empty slices or maps. -// Does not apply to zero valued structs. +// Zero valued structs will be omitted if all their public +// fields are zero, unless they implement an IsZero +// method (see the IsZeroer interface type), in which +// case the field will be included if that method returns true. // // flow Marshal using a flow style (useful for structs, // sequences and maps). @@ -129,7 +190,7 @@ func Unmarshal(in []byte, out interface{}) (err error) { // For example: // // type T struct { -// F int "a,omitempty" +// F int `yaml:"a,omitempty"` // B int // } // yaml.Marshal(&T{B: 2}) // Returns "b: 2\n" @@ -139,12 +200,47 @@ func Marshal(in interface{}) (out []byte, err error) { defer handleErr(&err) e := newEncoder() defer e.destroy() - e.marshal("", reflect.ValueOf(in)) + e.marshalDoc("", reflect.ValueOf(in)) e.finish() out = e.out return } +// An Encoder writes YAML values to an output stream. +type Encoder struct { + encoder *encoder +} + +// NewEncoder returns a new encoder that writes to w. +// The Encoder should be closed after use to flush all data +// to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + encoder: newEncoderWithWriter(w), + } +} + +// Encode writes the YAML encoding of v to the stream. +// If multiple items are encoded to the stream, the +// second and subsequent document will be preceded +// with a "---" document separator, but the first will not. +// +// See the documentation for Marshal for details about the conversion of Go +// values to YAML. +func (e *Encoder) Encode(v interface{}) (err error) { + defer handleErr(&err) + e.encoder.marshalDoc("", reflect.ValueOf(v)) + return nil +} + +// Close closes the encoder by writing any remaining data. +// It does not write a stream terminating string "...". +func (e *Encoder) Close() (err error) { + defer handleErr(&err) + e.encoder.finish() + return nil +} + func handleErr(err *error) { if v := recover(); v != nil { if e, ok := v.(yamlError); ok { @@ -200,6 +296,9 @@ type fieldInfo struct { Num int OmitEmpty bool Flow bool + // Id holds the unique field identifier, so we can cheaply + // check for field duplicates without maintaining an extra map. + Id int // Inline holds the field index if the field is part of an inlined struct. Inline []int @@ -279,6 +378,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) { } else { finfo.Inline = append([]int{i}, finfo.Inline...) } + finfo.Id = len(fieldsList) fieldsMap[finfo.Key] = finfo fieldsList = append(fieldsList, finfo) } @@ -300,11 +400,16 @@ func getStructInfo(st reflect.Type) (*structInfo, error) { return nil, errors.New(msg) } + info.Id = len(fieldsList) fieldsList = append(fieldsList, info) fieldsMap[info.Key] = info } - sinfo = &structInfo{fieldsMap, fieldsList, inlineMap} + sinfo = &structInfo{ + FieldsMap: fieldsMap, + FieldsList: fieldsList, + InlineMap: inlineMap, + } fieldMapMutex.Lock() structMap[st] = sinfo @@ -312,8 +417,23 @@ func getStructInfo(st reflect.Type) (*structInfo, error) { return sinfo, nil } +// IsZeroer is used to check whether an object is zero to +// determine whether it should be omitted when marshaling +// with the omitempty flag. One notable implementation +// is time.Time. +type IsZeroer interface { + IsZero() bool +} + func isZero(v reflect.Value) bool { - switch v.Kind() { + kind := v.Kind() + if z, ok := v.Interface().(IsZeroer); ok { + if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() { + return true + } + return z.IsZero() + } + switch kind { case reflect.String: return len(v.String()) == 0 case reflect.Interface, reflect.Ptr: diff --git a/vendor/gopkg.in/yaml.v2/yamlh.go b/vendor/gopkg.in/yaml.v2/yamlh.go index 3caeca0491..e25cee563b 100644 --- a/vendor/gopkg.in/yaml.v2/yamlh.go +++ b/vendor/gopkg.in/yaml.v2/yamlh.go @@ -1,6 +1,7 @@ package yaml import ( + "fmt" "io" ) @@ -239,6 +240,27 @@ const ( yaml_MAPPING_END_EVENT // A MAPPING-END event. ) +var eventStrings = []string{ + yaml_NO_EVENT: "none", + yaml_STREAM_START_EVENT: "stream start", + yaml_STREAM_END_EVENT: "stream end", + yaml_DOCUMENT_START_EVENT: "document start", + yaml_DOCUMENT_END_EVENT: "document end", + yaml_ALIAS_EVENT: "alias", + yaml_SCALAR_EVENT: "scalar", + yaml_SEQUENCE_START_EVENT: "sequence start", + yaml_SEQUENCE_END_EVENT: "sequence end", + yaml_MAPPING_START_EVENT: "mapping start", + yaml_MAPPING_END_EVENT: "mapping end", +} + +func (e yaml_event_type_t) String() string { + if e < 0 || int(e) >= len(eventStrings) { + return fmt.Sprintf("unknown event %d", e) + } + return eventStrings[e] +} + // The event structure. type yaml_event_t struct { @@ -521,9 +543,9 @@ type yaml_parser_t struct { read_handler yaml_read_handler_t // Read handler. - input_file io.Reader // File input data. - input []byte // String input data. - input_pos int + input_reader io.Reader // File input data. + input []byte // String input data. + input_pos int eof bool // EOF flag @@ -632,7 +654,7 @@ type yaml_emitter_t struct { write_handler yaml_write_handler_t // Write handler. output_buffer *[]byte // String output data. - output_file io.Writer // File output data. + output_writer io.Writer // File output data. buffer []byte // The working buffer. buffer_pos int // The current position of the buffer. From a99b64de379f4424d68c5847810439e24e7784a3 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 26 Sep 2018 11:37:11 +0300 Subject: [PATCH 15/91] add the ability to add custom parameter types to the interpreter and mapped macros with any number of macro functions - example added - although it's working it is not ready yet - I have to do some cleanup, doc comments and a TODO --- README.md | 6 +- _examples/routing/macros/main.go | 64 +++ context/context.go | 134 +------ context/request_params.go | 159 ++++++++ core/memstore/memstore.go | 208 +++++----- core/router/api_builder.go | 12 +- core/router/macro.go | 290 +------------- core/router/macro/interpreter/ast/ast.go | 124 +++--- .../router/macro/interpreter/parser/parser.go | 67 +--- .../macro/interpreter/parser/parser_test.go | 58 ++- core/router/macro/macro.go | 222 ++++------- core/router/macro/macro_test.go | 50 +-- core/router/macro/macros.go | 375 ++++++++++++++++++ core/router/macro/template.go | 24 +- core/router/party.go | 6 +- core/router/route.go | 2 +- hero/di.go | 11 + hero/di/func.go | 7 +- hero/di/object.go | 5 + hero/di/reflect.go | 12 +- hero/handler.go | 18 +- hero/param.go | 58 +-- mvc/controller.go | 10 +- mvc/controller_handle_test.go | 8 +- mvc/controller_method_parser.go | 84 ++-- mvc/param.go | 86 ++-- 26 files changed, 1067 insertions(+), 1033 deletions(-) create mode 100644 _examples/routing/macros/main.go create mode 100644 context/request_params.go create mode 100644 core/router/macro/macros.go diff --git a/README.md b/README.md index 3ae3103a9f..9981c0b2f3 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ latLonRegex, _ := regexp.Compile(latLonExpr) // Register your custom argument-less macro function to the :string param type. // MatchString is a type of func(string) bool, so we use it as it is. -app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) +app.Macros().Get("string").RegisterFunc("coordinate", latLonRegex.MatchString) app.Get("/coordinates/{lat:string coordinate()}/{lon:string coordinate()}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) @@ -175,7 +175,7 @@ Register your custom macro function which accepts two int arguments. ```go -app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { +app.Macros().Get("string").RegisterFunc("range", func(minLength, maxLength int) func(string) bool { return func(paramValue string) bool { return len(paramValue) >= minLength && len(paramValue) <= maxLength } @@ -191,7 +191,7 @@ app.Get("/limitchar/{name:string range(1,200) else 400}", func(ctx iris.Context) Register your custom macro function which accepts a slice of strings `[...,...]`. ```go -app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { +app.Macros().Get("string").RegisterFunc("has", func(validNames []string) func(string) bool { return func(paramValue string) bool { for _, validName := range validNames { if validName == paramValue { diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go new file mode 100644 index 0000000000..f45d4031e9 --- /dev/null +++ b/_examples/routing/macros/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "reflect" + "strconv" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + // "github.com/kataras/iris/core/memstore" + "github.com/kataras/iris/hero" +) + +func main() { + app := iris.New() + app.Logger().SetLevel("debug") + + // Let's see how we can register a custom macro such as ":uint32" or ":small" for its alias (optionally) for Uint32 types. + app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool { + _, err := strconv.ParseUint(paramValue, 10, 32) + return err == nil + }). + RegisterFunc("min", func(min uint32) func(string) bool { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 32) + if err != nil { + return false + } + + return uint32(n) >= min + } + }) + + /* TODO: + somehow define one-time how the parameter should be parsed to a particular type (go std or custom) + tip: we can change the original value from string to X using the entry's.ValueRaw + */ + + context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { + // return func(store memstore.Store) uint32 { + // param, _ := store.GetEntryAt(paramIndex) + // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) + // return uint32(paramValueAsUint32) + // } + return func(ctx context.Context) uint32 { + param := ctx.Params().GetEntryAt(paramIndex) + paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) + return uint32(paramValueAsUint32) + } + } + // + + app.Get("/test_uint32/{myparam:uint32 min(10)}", hero.Handler(func(paramValue uint32) string { + return fmt.Sprintf("Value of the parameter is: %d\n", paramValue) + })) + + app.Get("test_uint64/{myparam:uint64}", handler) + + app.Run(iris.Addr(":8080")) +} + +func handler(ctx context.Context) { + ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) +} diff --git a/context/context.go b/context/context.go index 1ca344d0e5..a213b3b76f 100644 --- a/context/context.go +++ b/context/context.go @@ -76,138 +76,6 @@ func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error { return u(data, v) } -// RequestParams is a key string - value string storage which -// context's request dynamic path params are being kept. -// Empty if the route is static. -type RequestParams struct { - store memstore.Store -} - -// Set adds a key-value pair to the path parameters values -// it's being called internally so it shouldn't be used as a local storage by the user, use `ctx.Values()` instead. -func (r *RequestParams) Set(key, value string) { - r.store.Set(key, value) -} - -// Visit accepts a visitor which will be filled -// by the key-value params. -func (r *RequestParams) Visit(visitor func(key string, value string)) { - r.store.Visit(func(k string, v interface{}) { - visitor(k, v.(string)) // always string here. - }) -} - -var emptyEntry memstore.Entry - -// GetEntryAt returns the internal Entry of the memstore based on its index, -// the stored index by the router. -// If not found then it returns a zero Entry and false. -func (r RequestParams) GetEntryAt(index int) (memstore.Entry, bool) { - if len(r.store) > index { - return r.store[index], true - } - return emptyEntry, false -} - -// GetEntry returns the internal Entry of the memstore based on its "key". -// If not found then it returns a zero Entry and false. -func (r RequestParams) GetEntry(key string) (memstore.Entry, bool) { - // we don't return the pointer here, we don't want to give the end-developer - // the strength to change the entry that way. - if e := r.store.GetEntry(key); e != nil { - return *e, true - } - return emptyEntry, false -} - -// Get returns a path parameter's value based on its route's dynamic path key. -func (r RequestParams) Get(key string) string { - return r.store.GetString(key) -} - -// GetTrim returns a path parameter's value without trailing spaces based on its route's dynamic path key. -func (r RequestParams) GetTrim(key string) string { - return strings.TrimSpace(r.Get(key)) -} - -// GetEscape returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. -func (r RequestParams) GetEscape(key string) string { - return DecodeQuery(DecodeQuery(r.Get(key))) -} - -// GetDecoded returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. -// same as `GetEscape`. -func (r RequestParams) GetDecoded(key string) string { - return r.GetEscape(key) -} - -// GetInt returns the path parameter's value as int, based on its key. -// It checks for all available types of int, including int64, strings etc. -// It will return -1 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetInt(key string) (int, error) { - return r.store.GetInt(key) -} - -// GetInt64 returns the path paramete's value as int64, based on its key. -// It checks for all available types of int, including int, strings etc. -// It will return -1 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetInt64(key string) (int64, error) { - return r.store.GetInt64(key) -} - -// GetFloat64 returns a path parameter's value based as float64 on its route's dynamic path key. -// It checks for all available types of int, including float64, int, strings etc. -// It will return -1 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetFloat64(key string) (float64, error) { - return r.store.GetFloat64(key) -} - -// GetUint8 returns the path parameter's value as uint8, based on its key. -// It checks for all available types of int, including int, string. -// It will return 0 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetUint8(key string) (uint8, error) { - return r.store.GetUint8(key) -} - -// GetUint64 returns the path parameter's value as uint64, based on its key. -// It checks for all available types of int, including int, uint64, int64, strings etc. -// It will return 0 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetUint64(key string) (uint64, error) { - return r.store.GetUint64(key) -} - -// GetBool returns the path parameter's value as bool, based on its key. -// a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" -// or "0" or "f" or "F" or "FALSE" or "false" or "False". -// Any other value returns an error. -func (r RequestParams) GetBool(key string) (bool, error) { - return r.store.GetBool(key) -} - -// GetIntUnslashed same as Get but it removes the first slash if found. -// Usage: Get an id from a wildcard path. -// -// Returns -1 with an error if the parameter couldn't be found. -func (r RequestParams) GetIntUnslashed(key string) (int, error) { - v := r.Get(key) - if v != "" { - if len(v) > 1 { - if v[0] == '/' { - v = v[1:] - } - } - return strconv.Atoi(v) - - } - - return -1, fmt.Errorf("unable to find int for '%s'", key) -} - -// Len returns the full length of the parameters. -func (r RequestParams) Len() int { - return r.store.Len() -} - // Context is the midle-man server's "object" for the clients. // // A New context is being acquired from a sync.Pool on each connection. @@ -1123,7 +991,7 @@ func NewContext(app Application) Context { func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) { ctx.handlers = nil // will be filled by router.Serve/HTTP ctx.values = ctx.values[0:0] // >> >> by context.Values().Set - ctx.params.store = ctx.params.store[0:0] + ctx.params.Store = ctx.params.Store[0:0] ctx.request = r ctx.currentHandlerIndex = 0 ctx.writer = AcquireResponseWriter() diff --git a/context/request_params.go b/context/request_params.go new file mode 100644 index 0000000000..ebb20afb85 --- /dev/null +++ b/context/request_params.go @@ -0,0 +1,159 @@ +package context + +import ( + "reflect" + "strconv" + "strings" + + "github.com/kataras/iris/core/memstore" +) + +// RequestParams is a key string - value string storage which +// context's request dynamic path params are being kept. +// Empty if the route is static. +type RequestParams struct { + memstore.Store +} + +// GetEntryAt will return the parameter's internal store's `Entry` based on the index. +// If not found it will return an emptry `Entry`. +func (r *RequestParams) GetEntryAt(index int) memstore.Entry { + entry, _ := r.Store.GetEntryAt(index) + return entry +} + +// GetEntry will return the parameter's internal store's `Entry` based on its name/key. +// If not found it will return an emptry `Entry`. +func (r *RequestParams) GetEntry(key string) memstore.Entry { + entry, _ := r.Store.GetEntry(key) + return entry +} + +// Visit accepts a visitor which will be filled +// by the key-value params. +func (r *RequestParams) Visit(visitor func(key string, value string)) { + r.Store.Visit(func(k string, v interface{}) { + visitor(k, v.(string)) // always string here. + }) +} + +// Get returns a path parameter's value based on its route's dynamic path key. +func (r RequestParams) Get(key string) string { + return r.GetString(key) +} + +// GetTrim returns a path parameter's value without trailing spaces based on its route's dynamic path key. +func (r RequestParams) GetTrim(key string) string { + return strings.TrimSpace(r.Get(key)) +} + +// GetEscape returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. +func (r RequestParams) GetEscape(key string) string { + return DecodeQuery(DecodeQuery(r.Get(key))) +} + +// GetDecoded returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. +// same as `GetEscape`. +func (r RequestParams) GetDecoded(key string) string { + return r.GetEscape(key) +} + +// GetIntUnslashed same as Get but it removes the first slash if found. +// Usage: Get an id from a wildcard path. +// +// Returns -1 and false if not path parameter with that "key" found. +func (r RequestParams) GetIntUnslashed(key string) (int, bool) { + v := r.Get(key) + if v != "" { + if len(v) > 1 { + if v[0] == '/' { + v = v[1:] + } + } + + vInt, err := strconv.Atoi(v) + if err != nil { + return -1, false + } + return vInt, true + } + + return -1, false +} + +var ( + ParamResolvers = map[reflect.Kind]func(paramIndex int) interface{}{ + reflect.String: func(paramIndex int) interface{} { + return func(ctx Context) string { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(string) + } + }, + reflect.Int: func(paramIndex int) interface{} { + return func(ctx Context) int { + v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0) + return v + } + }, + reflect.Int64: func(paramIndex int) interface{} { + return func(ctx Context) int64 { + v, _ := ctx.Params().GetEntryAt(paramIndex).Int64Default(0) + return v + } + }, + reflect.Uint8: func(paramIndex int) interface{} { + return func(ctx Context) uint8 { + v, _ := ctx.Params().GetEntryAt(paramIndex).Uint8Default(0) + return v + } + }, + reflect.Uint64: func(paramIndex int) interface{} { + return func(ctx Context) uint64 { + v, _ := ctx.Params().GetEntryAt(paramIndex).Uint64Default(0) + return v + } + }, + reflect.Bool: func(paramIndex int) interface{} { + return func(ctx Context) bool { + v, _ := ctx.Params().GetEntryAt(paramIndex).BoolDefault(false) + return v + } + }, + } +) + +// ParamResolverByKindAndIndex will return a function that can be used to bind path parameter's exact value by its Go std type +// and the parameter's index based on the registered path. +// Usage: nameResolver := ParamResolverByKindAndKey(reflect.String, 0) +// Inside a Handler: nameResolver.Call(ctx)[0] +// it will return the reflect.Value Of the exact type of the parameter(based on the path parameters and macros). +// It is only useful for dynamic binding of the parameter, it is used on "hero" package and it should be modified +// only when Macros are modified in such way that the default selections for the available go std types are not enough. +// +// Returns empty value and false if "k" does not match any valid parameter resolver. +func ParamResolverByKindAndIndex(k reflect.Kind, paramIndex int) (reflect.Value, bool) { + /* NO: + // This could work but its result is not exact type, so direct binding is not possible. + resolver := m.ParamResolver + fn := func(ctx context.Context) interface{} { + entry, _ := ctx.Params().GetEntry(paramName) + return resolver(entry) + } + // + + // This works but it is slower on serve-time. + paramNameValue := []reflect.Value{reflect.ValueOf(paramName)} + var fnSignature func(context.Context) string + return reflect.MakeFunc(reflect.ValueOf(&fnSignature).Elem().Type(), func(in []reflect.Value) []reflect.Value { + return in[0].MethodByName("Params").Call(emptyIn)[0].MethodByName("Get").Call(paramNameValue) + // return []reflect.Value{reflect.ValueOf(in[0].Interface().(context.Context).Params().Get(paramName))} + }) + // + */ + + r, ok := ParamResolvers[k] + if !ok || r == nil { + return reflect.Value{}, false + } + + return reflect.ValueOf(r(paramIndex)), true +} diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 489865144a..52c3b3407c 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -94,15 +94,17 @@ func (e Entry) IntDefault(def int) (int, error) { if v == nil { return def, errFindParse.Format("int", e.Key) } - if vint, ok := v.(int); ok { - return vint, nil - } else if vstring, sok := v.(string); sok && vstring != "" { - vint, err := strconv.Atoi(vstring) + + switch vv := v.(type) { + case string: + val, err := strconv.Atoi(vv) if err != nil { return def, err } - return vint, nil + return val, nil + case int: + return vv, nil } return def, errFindParse.Format("int", e.Key) @@ -116,16 +118,13 @@ func (e Entry) Int64Default(def int64) (int64, error) { return def, errFindParse.Format("int64", e.Key) } - if vint64, ok := v.(int64); ok { - return vint64, nil - } - - if vint, ok := v.(int); ok { - return int64(vint), nil - } - - if vstring, sok := v.(string); sok { - return strconv.ParseInt(vstring, 10, 64) + switch vv := v.(type) { + case string: + return strconv.ParseInt(vv, 10, 64) + case int64: + return vv, nil + case int: + return int64(vv), nil } return def, errFindParse.Format("int64", e.Key) @@ -135,30 +134,23 @@ func (e Entry) Int64Default(def int64) (int64, error) { // If not found returns "def" and a non-nil error. func (e Entry) Float64Default(def float64) (float64, error) { v := e.ValueRaw - if v == nil { return def, errFindParse.Format("float64", e.Key) } - if vfloat32, ok := v.(float32); ok { - return float64(vfloat32), nil - } - - if vfloat64, ok := v.(float64); ok { - return vfloat64, nil - } - - if vint, ok := v.(int); ok { - return float64(vint), nil - } - - if vstring, sok := v.(string); sok { - vfloat64, err := strconv.ParseFloat(vstring, 64) + switch vv := v.(type) { + case string: + val, err := strconv.ParseFloat(vv, 64) if err != nil { return def, err } - - return vfloat64, nil + return val, nil + case float32: + return float64(vv), nil + case float64: + return vv, nil + case int: + return float64(vv), nil } return def, errFindParse.Format("float64", e.Key) @@ -168,30 +160,24 @@ func (e Entry) Float64Default(def float64) (float64, error) { // If not found returns "def" and a non-nil error. func (e Entry) Float32Default(key string, def float32) (float32, error) { v := e.ValueRaw - if v == nil { return def, errFindParse.Format("float32", e.Key) } - if vfloat32, ok := v.(float32); ok { - return vfloat32, nil - } - - if vfloat64, ok := v.(float64); ok { - return float32(vfloat64), nil - } - - if vint, ok := v.(int); ok { - return float32(vint), nil - } - - if vstring, sok := v.(string); sok { - vfloat32, err := strconv.ParseFloat(vstring, 32) + switch vv := v.(type) { + case string: + val, err := strconv.ParseFloat(vv, 32) if err != nil { return def, err } - return float32(vfloat32), nil + return float32(val), nil + case float32: + return vv, nil + case float64: + return float32(vv), nil + case int: + return float32(vv), nil } return def, errFindParse.Format("float32", e.Key) @@ -205,26 +191,23 @@ func (e Entry) Uint8Default(def uint8) (uint8, error) { return def, errFindParse.Format("uint8", e.Key) } - if vuint8, ok := v.(uint8); ok { - return vuint8, nil - } - - if vint, ok := v.(int); ok { - if vint < 0 || vint > 255 { - return def, errFindParse.Format("uint8", e.Key) - } - return uint8(vint), nil - } - - if vstring, sok := v.(string); sok { - vuint64, err := strconv.ParseUint(vstring, 10, 8) + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 8) if err != nil { return def, err } - if vuint64 > 255 { + if val > 255 { return def, errFindParse.Format("uint8", e.Key) } - return uint8(vuint64), nil + return uint8(val), nil + case uint8: + return vv, nil + case int: + if vv < 0 || vv > 255 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil } return def, errFindParse.Format("uint8", e.Key) @@ -238,20 +221,15 @@ func (e Entry) Uint64Default(def uint64) (uint64, error) { return def, errFindParse.Format("uint64", e.Key) } - if vuint64, ok := v.(uint64); ok { - return vuint64, nil - } - - if vint64, ok := v.(int64); ok { - return uint64(vint64), nil - } - - if vint, ok := v.(int); ok { - return uint64(vint), nil - } - - if vstring, sok := v.(string); sok { - return strconv.ParseUint(vstring, 10, 64) + switch vv := v.(type) { + case string: + return strconv.ParseUint(vv, 10, 64) + case uint64: + return vv, nil + case int64: + return uint64(vv), nil + case int: + return uint64(vv), nil } return def, errFindParse.Format("uint64", e.Key) @@ -269,20 +247,17 @@ func (e Entry) BoolDefault(def bool) (bool, error) { return def, errFindParse.Format("bool", e.Key) } - if vBoolean, ok := v.(bool); ok { - return vBoolean, nil - } - - if vString, ok := v.(string); ok { - b, err := strconv.ParseBool(vString) + switch vv := v.(type) { + case string: + val, err := strconv.ParseBool(vv) if err != nil { return def, err } - return b, nil - } - - if vInt, ok := v.(int); ok { - if vInt == 1 { + return val, nil + case bool: + return vv, nil + case int: + if vv == 1 { return true, nil } return false, nil @@ -394,28 +369,39 @@ func (r *Store) SetImmutable(key string, value interface{}) (Entry, bool) { return r.Save(key, value, true) } +var emptyEntry Entry + // GetEntry returns a pointer to the "Entry" found with the given "key" -// if nothing found then it returns nil, so be careful with that, -// it's not supposed to be used by end-developers. -func (r *Store) GetEntry(key string) *Entry { +// if nothing found then it returns an empty Entry and false. +func (r *Store) GetEntry(key string) (Entry, bool) { args := *r n := len(args) for i := 0; i < n; i++ { - kv := &args[i] - if kv.Key == key { - return kv + if kv := args[i]; kv.Key == key { + return kv, true } } - return nil + return emptyEntry, false +} + +// GetEntryAt returns the internal Entry of the memstore based on its index, +// the stored index by the router. +// If not found then it returns a zero Entry and false. +func (r *Store) GetEntryAt(index int) (Entry, bool) { + args := *r + if len(args) > index { + return args[index], true + } + return emptyEntry, false } // GetDefault returns the entry's value based on its key. // If not found returns "def". // This function checks for immutability as well, the rest don't. func (r *Store) GetDefault(key string, def interface{}) interface{} { - v := r.GetEntry(key) - if v == nil || v.ValueRaw == nil { + v, ok := r.GetEntry(key) + if !ok || v.ValueRaw == nil { return def } vv := v.Value() @@ -444,8 +430,8 @@ func (r *Store) Visit(visitor func(key string, value interface{})) { // GetStringDefault returns the entry's value as string, based on its key. // If not found returns "def". func (r *Store) GetStringDefault(key string, def string) string { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return def } @@ -465,8 +451,8 @@ func (r *Store) GetStringTrim(name string) string { // GetInt returns the entry's value as int, based on its key. // If not found returns -1 and a non-nil error. func (r *Store) GetInt(key string) (int, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return 0, errFindParse.Format("int", key) } return v.IntDefault(-1) @@ -485,8 +471,8 @@ func (r *Store) GetIntDefault(key string, def int) int { // GetUint8 returns the entry's value as uint8, based on its key. // If not found returns 0 and a non-nil error. func (r *Store) GetUint8(key string) (uint8, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return 0, errFindParse.Format("uint8", key) } return v.Uint8Default(0) @@ -505,8 +491,8 @@ func (r *Store) GetUint8Default(key string, def uint8) uint8 { // GetUint64 returns the entry's value as uint64, based on its key. // If not found returns 0 and a non-nil error. func (r *Store) GetUint64(key string) (uint64, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return 0, errFindParse.Format("uint64", key) } return v.Uint64Default(0) @@ -525,8 +511,8 @@ func (r *Store) GetUint64Default(key string, def uint64) uint64 { // GetInt64 returns the entry's value as int64, based on its key. // If not found returns -1 and a non-nil error. func (r *Store) GetInt64(key string) (int64, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return -1, errFindParse.Format("int64", key) } return v.Int64Default(-1) @@ -545,8 +531,8 @@ func (r *Store) GetInt64Default(key string, def int64) int64 { // GetFloat64 returns the entry's value as float64, based on its key. // If not found returns -1 and a non nil error. func (r *Store) GetFloat64(key string) (float64, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return -1, errFindParse.Format("float64", key) } return v.Float64Default(-1) @@ -569,8 +555,8 @@ func (r *Store) GetFloat64Default(key string, def float64) float64 { // // If not found returns false and a non-nil error. func (r *Store) GetBool(key string) (bool, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return false, errFindParse.Format("bool", key) } diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 0022428a33..614bddacf3 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -68,7 +68,7 @@ func (r *repository) getAll() []*Route { // and child routers. type APIBuilder struct { // the api builder global macros registry - macros *macro.Map + macros *macro.Macros // the api builder global handlers per status code registry (used for custom http errors) errorCodeHandlers *ErrorCodeHandlers // the api builder global routes repository @@ -116,7 +116,7 @@ var _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handl // which is responsible to build the API and the router handler. func NewAPIBuilder() *APIBuilder { api := &APIBuilder{ - macros: defaultMacros(), + macros: macro.Defaults, errorCodeHandlers: defaultErrorCodeHandlers(), reporter: errors.NewReporter(), relativePath: "/", @@ -246,7 +246,7 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co ) for _, m := range methods { - route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, api.macros) + route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros) if err != nil { // template path parser errors: api.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path) return nil // fail on first error. @@ -411,11 +411,11 @@ func (api *APIBuilder) WildcardSubdomain(middleware ...context.Handler) Party { return api.Subdomain(SubdomainWildcardIndicator, middleware...) } -// Macros returns the macro map which is responsible -// to register custom macro functions for all routes. +// Macros returns the macro collection that is responsible +// to register custom macros with their own parameter types and their macro functions for all routes. // // Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path -func (api *APIBuilder) Macros() *macro.Map { +func (api *APIBuilder) Macros() *macro.Macros { return api.macros } diff --git a/core/router/macro.go b/core/router/macro.go index e46a07c7b7..02c0ea952e 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -1,295 +1,15 @@ package router import ( + "fmt" "net/http" - "strconv" "strings" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/router/macro" "github.com/kataras/iris/core/router/macro/interpreter/ast" ) -// defaultMacros returns a new macro map which -// contains the default router's named param types functions. -func defaultMacros() *macro.Map { - macros := macro.NewMap() - // registers the String and Int default macro funcs - // user can add or override of his own funcs later on - // i.e: - // app.Macro.String.RegisterFunc("equal", func(eqWith string) func(string) bool { - // return func(paramValue string) bool { - // return eqWith == paramValue - // }}) - registerBuiltinsMacroFuncs(macros) - - return macros -} - -func registerBuiltinsMacroFuncs(out *macro.Map) { - // register the String which is the default type if not - // parameter type is specified or - // if a given parameter into path given but the func doesn't exist on the - // parameter type's function list. - // - // these can be overridden by the user, later on. - registerStringMacroFuncs(out.String) - registerNumberMacroFuncs(out.Number) - registerInt64MacroFuncs(out.Int64) - registerUint8MacroFuncs(out.Uint8) - registerUint64MacroFuncs(out.Uint64) - registerAlphabeticalMacroFuncs(out.Alphabetical) - registerFileMacroFuncs(out.File) - registerPathMacroFuncs(out.Path) -} - -// String -// anything one part -func registerStringMacroFuncs(out *macro.Macro) { - // this can be used everywhere, it's to help users to define custom regexp expressions - // on all macros - out.RegisterFunc("regexp", func(expr string) macro.EvaluatorFunc { - regexpEvaluator := macro.MustNewEvaluatorFromRegexp(expr) - return regexpEvaluator - }) - - // checks if param value starts with the 'prefix' arg - out.RegisterFunc("prefix", func(prefix string) macro.EvaluatorFunc { - return func(paramValue string) bool { - return strings.HasPrefix(paramValue, prefix) - } - }) - - // checks if param value ends with the 'suffix' arg - out.RegisterFunc("suffix", func(suffix string) macro.EvaluatorFunc { - return func(paramValue string) bool { - return strings.HasSuffix(paramValue, suffix) - } - }) - - // checks if param value contains the 's' arg - out.RegisterFunc("contains", func(s string) macro.EvaluatorFunc { - return func(paramValue string) bool { - return strings.Contains(paramValue, s) - } - }) - - // checks if param value's length is at least 'min' - out.RegisterFunc("min", func(min int) macro.EvaluatorFunc { - return func(paramValue string) bool { - return len(paramValue) >= min - } - }) - // checks if param value's length is not bigger than 'max' - out.RegisterFunc("max", func(max int) macro.EvaluatorFunc { - return func(paramValue string) bool { - return max >= len(paramValue) - } - }) -} - -// Number -// positive and negative numbers, number of digits depends on the arch. -func registerNumberMacroFuncs(out *macro.Macro) { - // checks if the param value's int representation is - // bigger or equal than 'min' - out.RegisterFunc("min", func(min int) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= min - } - }) - - // checks if the param value's int representation is - // smaller or equal than 'max' - out.RegisterFunc("max", func(max int) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n <= max - } - }) - - // checks if the param value's int representation is - // between min and max, including 'min' and 'max' - out.RegisterFunc("range", func(min, max int) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true - } - }) -} - -// Int64 -// -9223372036854775808 to 9223372036854775807. -func registerInt64MacroFuncs(out *macro.Macro) { - // checks if the param value's int64 representation is - // bigger or equal than 'min' - out.RegisterFunc("min", func(min int64) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - return n >= min - } - }) - - // checks if the param value's int64 representation is - // smaller or equal than 'max' - out.RegisterFunc("max", func(max int64) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - return n <= max - } - }) - - // checks if the param value's int64 representation is - // between min and max, including 'min' and 'max' - out.RegisterFunc("range", func(min, max int64) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true - } - }) -} - -// Uint8 -// 0 to 255. -func registerUint8MacroFuncs(out *macro.Macro) { - // checks if the param value's uint8 representation is - // bigger or equal than 'min' - out.RegisterFunc("min", func(min uint8) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - - return uint8(n) >= min - } - }) - - // checks if the param value's uint8 representation is - // smaller or equal than 'max' - out.RegisterFunc("max", func(max uint8) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - return uint8(n) <= max - } - }) - - // checks if the param value's uint8 representation is - // between min and max, including 'min' and 'max' - out.RegisterFunc("range", func(min, max uint8) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - - if v := uint8(n); v < min || v > max { - return false - } - return true - } - }) -} - -// Uint64 -// 0 to 18446744073709551615. -func registerUint64MacroFuncs(out *macro.Macro) { - // checks if the param value's uint64 representation is - // bigger or equal than 'min' - out.RegisterFunc("min", func(min uint64) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n >= min - } - }) - - // checks if the param value's uint64 representation is - // smaller or equal than 'max' - out.RegisterFunc("max", func(max uint64) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n <= max - } - }) - - // checks if the param value's uint64 representation is - // between min and max, including 'min' and 'max' - out.RegisterFunc("range", func(min, max uint64) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true - } - }) -} - -// Alphabetical -// letters only (upper or lowercase) -func registerAlphabeticalMacroFuncs(out *macro.Macro) { - -} - -// File -// letters (upper or lowercase) -// numbers (0-9) -// underscore (_) -// dash (-) -// point (.) -// no spaces! or other character -func registerFileMacroFuncs(out *macro.Macro) { - -} - -// Path -// File+slashes(anywhere) -// should be the latest param, it's the wildcard -func registerPathMacroFuncs(out *macro.Macro) { - -} - // compileRoutePathAndHandlers receives a route info and returns its parsed/"compiled" path // and the new handlers (prepend all the macro's handler, if any). // @@ -325,9 +45,9 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) { // then the tmpl.Params will be filled, // so no any further check needed for i, p := range tmpl.Params { - if p.Type == ast.ParamTypePath { + if ast.IsTrailing(p.Type) { if i != len(tmpl.Params)-1 { - return "", errors.New("parameter type \"ParamTypePath\" should be putted to the very last of a path") + return "", fmt.Errorf("parameter type \"%s\" should be putted to the very last of a path", p.Type.Indent()) } routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1) } else { @@ -338,7 +58,7 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) { return routePath, nil } -// note: returns nil if not needed, the caller(router) should be check for that before adding that on route's Middleware +// Note: returns nil if not needed, the caller(router) should check for that before adding that on route's Middleware. func convertTmplToHandler(tmpl *macro.Template) context.Handler { needMacroHandler := false @@ -347,7 +67,7 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) // 2. if we don't have any named params then we don't need a handler too. for _, p := range tmpl.Params { - if len(p.Funcs) == 0 && (p.Type == ast.ParamTypeUnExpected || p.Type == ast.ParamTypeString || p.Type == ast.ParamTypePath) && p.ErrCode == http.StatusNotFound { + if len(p.Funcs) == 0 && (ast.IsMaster(p.Type) || ast.IsTrailing(p.Type)) && p.ErrCode == http.StatusNotFound { } else { // println("we need handler for: " + tmpl.Src) needMacroHandler = true diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go index fa695226b6..a3508373ac 100644 --- a/core/router/macro/interpreter/ast/ast.go +++ b/core/router/macro/interpreter/ast/ast.go @@ -1,43 +1,68 @@ package ast -import ( - "reflect" - "strings" -) +type ( + // ParamType holds the necessary information about a parameter type for the parser to lookup for. + ParamType interface { + // The name of the parameter type. + // Indent should contain the characters for the parser. + Indent() string + } -// ParamType holds the necessary information about a parameter type. -type ParamType struct { - Indent string // the name of the parameter type. - Aliases []string // any aliases, can be empty. + // MasterParamType if implemented and its `Master()` returns true then empty type param will be translated to this param type. + // Also its functions will be available to the rest of the macro param type's funcs. + // + // Only one Master is allowed. + MasterParamType interface { + ParamType + Master() bool + } - GoType reflect.Kind // the go type useful for "mvc" and "hero" bindings. + // TrailingParamType if implemented and its `Trailing()` returns true + // then it should be declared at the end of a route path and can accept any trailing path segment as one parameter. + TrailingParamType interface { + ParamType + Trailing() bool + } - Default bool // if true then empty type param will target this and its functions will be available to the rest of the param type's funcs. - End bool // if true then it should be declared at the end of a route path and can accept any trailing path segment as one parameter. + // AliasParamType if implemeneted nad its `Alias()` returns a non-empty string + // then the param type can be written with that string literal too. + AliasParamType interface { + ParamType + Alias() string + } +) - invalid bool // only true if returned by the parser via `LookupParamType`. +// IsMaster returns true if the "pt" param type is a master one. +func IsMaster(pt ParamType) bool { + p, ok := pt.(MasterParamType) + return ok && p.Master() } -// ParamTypeUnExpected is the unexpected parameter type. -var ParamTypeUnExpected = ParamType{invalid: true} - -func (pt ParamType) String() string { - return pt.Indent +// IsTrailing returns true if the "pt" param type is a marked as trailing, +// which should accept more than one path segment when in the end. +func IsTrailing(pt ParamType) bool { + p, ok := pt.(TrailingParamType) + return ok && p.Trailing() } -// Assignable returns true if the "k" standard type -// is assignabled to this ParamType. -func (pt ParamType) Assignable(k reflect.Kind) bool { - return pt.GoType == k +// HasAlias returns any alias of the "pt" param type. +// If alias is empty or not found then it returns false as its second output argument. +func HasAlias(pt ParamType) (string, bool) { + if p, ok := pt.(AliasParamType); ok { + alias := p.Alias() + return alias, len(alias) > 0 + } + + return "", false } -// GetDefaultParamType accepts a list of ParamType and returns its default. -// If no `Default` specified: +// GetMasterParamType accepts a list of ParamType and returns its master. +// If no `Master` specified: // and len(paramTypes) > 0 then it will return the first one, -// otherwise it returns a "string" parameter type. -func GetDefaultParamType(paramTypes ...ParamType) ParamType { +// otherwise it returns nil. +func GetMasterParamType(paramTypes ...ParamType) ParamType { for _, pt := range paramTypes { - if pt.Default == true { + if IsMaster(pt) { return pt } } @@ -46,24 +71,12 @@ func GetDefaultParamType(paramTypes ...ParamType) ParamType { return paramTypes[0] } - return ParamType{Indent: "string", GoType: reflect.String, Default: true} -} - -// ValidKind will return true if at least one param type is supported -// for this std kind. -func ValidKind(k reflect.Kind, paramTypes ...ParamType) bool { - for _, pt := range paramTypes { - if pt.GoType == k { - return true - } - } - - return false + return nil } // LookupParamType accepts the string // representation of a parameter type. -// Available: +// Example: // "string" // "number" or "int" // "long" or "int64" @@ -73,41 +86,20 @@ func ValidKind(k reflect.Kind, paramTypes ...ParamType) bool { // "alphabetical" // "file" // "path" -func LookupParamType(indent string, paramTypes ...ParamType) (ParamType, bool) { +func LookupParamType(indentOrAlias string, paramTypes ...ParamType) (ParamType, bool) { for _, pt := range paramTypes { - if pt.Indent == indent { + if pt.Indent() == indentOrAlias { return pt, true } - for _, alias := range pt.Aliases { - if alias == indent { + if alias, has := HasAlias(pt); has { + if alias == indentOrAlias { return pt, true } } } - return ParamTypeUnExpected, false -} - -// LookupParamTypeFromStd accepts the string representation of a standard go type. -// It returns a ParamType, but it may differs for example -// the alphabetical, file, path and string are all string go types, so -// make sure that caller resolves these types before this call. -// -// string matches to string -// int matches to int/number -// int64 matches to int64/long -// uint64 matches to uint64 -// bool matches to bool/boolean -func LookupParamTypeFromStd(goType string, paramTypes ...ParamType) (ParamType, bool) { - goType = strings.ToLower(goType) - for _, pt := range paramTypes { - if strings.ToLower(pt.GoType.String()) == goType { - return pt, true - } - } - - return ParamTypeUnExpected, false + return nil, false } // ParamStatement is a struct diff --git a/core/router/macro/interpreter/parser/parser.go b/core/router/macro/interpreter/parser/parser.go index 9ce125c91e..b4bb0293d8 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/core/router/macro/interpreter/parser/parser.go @@ -2,7 +2,6 @@ package parser import ( "fmt" - "reflect" "strconv" "strings" @@ -11,67 +10,12 @@ import ( "github.com/kataras/iris/core/router/macro/interpreter/token" ) -var ( - // paramTypeString is the string type. - // If parameter type is missing then it defaults to String type. - // Allows anything - // Declaration: /mypath/{myparam:string} or {myparam} - paramTypeString = ast.ParamType{Indent: "string", GoType: reflect.String, Default: true} - // ParamTypeNumber is the integer, a number type. - // Allows both positive and negative numbers, any number of digits. - // Declaration: /mypath/{myparam:number} or {myparam:int} for backwards-compatibility - paramTypeNumber = ast.ParamType{Indent: "number", Aliases: []string{"int"}, GoType: reflect.Int} - // ParamTypeInt64 is a number type. - // Allows only -9223372036854775808 to 9223372036854775807. - // Declaration: /mypath/{myparam:int64} or {myparam:long} - paramTypeInt64 = ast.ParamType{Indent: "int64", Aliases: []string{"long"}, GoType: reflect.Int64} - // ParamTypeUint8 a number type. - // Allows only 0 to 255. - // Declaration: /mypath/{myparam:uint8} - paramTypeUint8 = ast.ParamType{Indent: "uint8", GoType: reflect.Uint8} - // ParamTypeUint64 a number type. - // Allows only 0 to 18446744073709551615. - // Declaration: /mypath/{myparam:uint64} - paramTypeUint64 = ast.ParamType{Indent: "uint64", GoType: reflect.Uint64} - // ParamTypeBool is the bool type. - // Allows only "1" or "t" or "T" or "TRUE" or "true" or "True" - // or "0" or "f" or "F" or "FALSE" or "false" or "False". - // Declaration: /mypath/{myparam:bool} or {myparam:boolean} - paramTypeBool = ast.ParamType{Indent: "bool", Aliases: []string{"boolean"}, GoType: reflect.Bool} - // ParamTypeAlphabetical is the alphabetical/letter type type. - // Allows letters only (upper or lowercase) - // Declaration: /mypath/{myparam:alphabetical} - paramTypeAlphabetical = ast.ParamType{Indent: "alphabetical", GoType: reflect.String} - // ParamTypeFile is the file single path type. - // Allows: - // letters (upper or lowercase) - // numbers (0-9) - // underscore (_) - // dash (-) - // point (.) - // no spaces! or other character - // Declaration: /mypath/{myparam:file} - paramTypeFile = ast.ParamType{Indent: "file", GoType: reflect.String} - // ParamTypePath is the multi path (or wildcard) type. - // Allows anything, should be the last part - // Declaration: /mypath/{myparam:path} - paramTypePath = ast.ParamType{Indent: "path", GoType: reflect.String, End: true} -) - -// DefaultParamTypes are the built'n parameter types. -var DefaultParamTypes = []ast.ParamType{ - paramTypeString, - paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64, - paramTypeBool, - paramTypeAlphabetical, paramTypeFile, paramTypePath, -} - // Parse takes a route "fullpath" // and returns its param statements -// and an error on failure. -func Parse(fullpath string, paramTypes ...ast.ParamType) ([]*ast.ParamStatement, error) { +// or an error if failed. +func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement, error) { if len(paramTypes) == 0 { - paramTypes = DefaultParamTypes + return nil, fmt.Errorf("empty parameter types") } pathParts := strings.SplitN(fullpath, "/", -1) @@ -94,7 +38,7 @@ func Parse(fullpath string, paramTypes ...ast.ParamType) ([]*ast.ParamStatement, return nil, err } // if we have param type path but it's not the last path part - if stmt.Type.End && i < len(pathParts)-1 { + if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 { return nil, fmt.Errorf("param type '%s' should be lived only inside the last path segment, but was inside: %s", stmt.Type, s) } @@ -166,7 +110,7 @@ func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, er stmt := &ast.ParamStatement{ ErrorCode: DefaultParamErrorCode, - Type: ast.GetDefaultParamType(paramTypes...), + Type: ast.GetMasterParamType(paramTypes...), Src: p.src, } @@ -190,6 +134,7 @@ func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, er // type can accept both letters and numbers but not symbols ofc. nextTok := l.NextToken() paramType, found := ast.LookupParamType(nextTok.Literal, paramTypes...) + if !found { p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal) } diff --git a/core/router/macro/interpreter/parser/parser_test.go b/core/router/macro/interpreter/parser/parser_test.go index ffdd533551..b492b361d5 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/core/router/macro/interpreter/parser/parser_test.go @@ -9,6 +9,44 @@ import ( "github.com/kataras/iris/core/router/macro/interpreter/ast" ) +type simpleParamType string + +func (pt simpleParamType) Indent() string { return string(pt) } + +type masterParamType simpleParamType + +func (pt masterParamType) Indent() string { return string(pt) } +func (pt masterParamType) Master() bool { return true } + +type wildcardParamType string + +func (pt wildcardParamType) Indent() string { return string(pt) } +func (pt wildcardParamType) Trailing() bool { return true } + +type aliasedParamType []string + +func (pt aliasedParamType) Indent() string { return string(pt[0]) } +func (pt aliasedParamType) Alias() string { return pt[1] } + +var ( + paramTypeString = masterParamType("string") + paramTypeNumber = aliasedParamType{"number", "int"} + paramTypeInt64 = aliasedParamType{"int64", "long"} + paramTypeUint8 = simpleParamType("uint8") + paramTypeUint64 = simpleParamType("uint64") + paramTypeBool = aliasedParamType{"bool", "boolean"} + paramTypeAlphabetical = simpleParamType("alphabetical") + paramTypeFile = simpleParamType("file") + paramTypePath = wildcardParamType("path") +) + +var testParamTypes = []ast.ParamType{ + paramTypeString, + paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64, + paramTypeBool, + paramTypeAlphabetical, paramTypeFile, paramTypePath, +} + func TestParseParamError(t *testing.T) { // fail illegalChar := '$' @@ -16,7 +54,7 @@ func TestParseParamError(t *testing.T) { input := "{id" + string(illegalChar) + "int range(1,5) else 404}" p := NewParamParser(input) - _, err := p.Parse(DefaultParamTypes) + _, err := p.Parse(testParamTypes) if err == nil { t.Fatalf("expecting not empty error on input '%s'", input) @@ -32,7 +70,7 @@ func TestParseParamError(t *testing.T) { // success input2 := "{id:uint64 range(1,5) else 404}" p.Reset(input2) - _, err = p.Parse(DefaultParamTypes) + _, err = p.Parse(testParamTypes) if err != nil { t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error()) @@ -42,7 +80,7 @@ func TestParseParamError(t *testing.T) { // mustLookupParamType same as `ast.LookupParamType` but it panics if "indent" does not match with a valid Param Type. func mustLookupParamType(indent string) ast.ParamType { - pt, found := ast.LookupParamType(indent, DefaultParamTypes...) + pt, found := ast.LookupParamType(indent, testParamTypes...) if !found { panic("param type '" + indent + "' is not part of the provided param types") } @@ -113,14 +151,14 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{myparam_:thisianunexpected}", Name: "myparam_", - Type: ast.ParamTypeUnExpected, + Type: nil, ErrorCode: 404, }}, // 5 {true, ast.ParamStatement{ Src: "{myparam2}", Name: "myparam2", // we now allow integers to the parameter names. - Type: ast.GetDefaultParamType(DefaultParamTypes...), + Type: ast.GetMasterParamType(testParamTypes...), ErrorCode: 404, }}, // 6 {true, @@ -152,7 +190,7 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{id:long else 404}", Name: "id", - Type: mustLookupParamType("long"), // backwards-compatible test of LookupParamType. + Type: mustLookupParamType("int64"), // backwards-compatible test of LookupParamType. ErrorCode: 404, }}, // 10 {true, @@ -175,7 +213,7 @@ func TestParseParam(t *testing.T) { p := new(ParamParser) for i, tt := range tests { p.Reset(tt.expectedStatement.Src) - resultStmt, err := p.Parse(DefaultParamTypes) + resultStmt, err := p.Parse(testParamTypes) if tt.valid && err != nil { t.Fatalf("tests[%d] - error %s", i, err.Error()) @@ -216,7 +254,7 @@ func TestParse(t *testing.T) { }}, // 0 {"/admin/{id:uint64 range(1,5)}", true, []ast.ParamStatement{{ - Src: "{id:uint64 range(1,5)}", // test alternative (backwards-compatibility) "int" + Src: "{id:uint64 range(1,5)}", Name: "id", Type: paramTypeUint64, Funcs: []ast.ParamFunc{ @@ -260,7 +298,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{myparam_:thisianunexpected}", Name: "myparam_", - Type: ast.ParamTypeUnExpected, + Type: nil, ErrorCode: 404, }, }}, // 5 @@ -282,7 +320,7 @@ func TestParse(t *testing.T) { }}, // 7 } for i, tt := range tests { - statements, err := Parse(tt.path) + statements, err := Parse(tt.path, testParamTypes) if tt.valid && err != nil { t.Fatalf("tests[%d] - error %s", i, err.Error()) diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index 7ea4630fef..5cb304e06b 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -7,8 +7,6 @@ import ( "strconv" "strings" "unicode" - - "github.com/kataras/iris/core/router/macro/interpreter/ast" ) // EvaluatorFunc is the signature for both param types and param funcs. @@ -108,53 +106,78 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { // try to convert the string literal as we get it from the parser. var ( - v interface{} - err error + val interface{} + + panicIfErr = func(err error) { + if err != nil { + panic(fmt.Sprintf("on field index: %d: %v", i, err)) + } + } ) // try to get the value based on the expected type. switch field.Kind() { case reflect.Int: - v, err = strconv.Atoi(arg) + v, err := strconv.Atoi(arg) + panicIfErr(err) + val = v case reflect.Int8: - v, err = strconv.ParseInt(arg, 10, 8) + v, err := strconv.ParseInt(arg, 10, 8) + panicIfErr(err) + val = int8(v) case reflect.Int16: - v, err = strconv.ParseInt(arg, 10, 16) + v, err := strconv.ParseInt(arg, 10, 16) + panicIfErr(err) + val = int16(v) case reflect.Int32: - v, err = strconv.ParseInt(arg, 10, 32) + v, err := strconv.ParseInt(arg, 10, 32) + panicIfErr(err) + val = int32(v) case reflect.Int64: - v, err = strconv.ParseInt(arg, 10, 64) + v, err := strconv.ParseInt(arg, 10, 64) + panicIfErr(err) + val = v case reflect.Uint8: - v, err = strconv.ParseUint(arg, 10, 8) + v, err := strconv.ParseUint(arg, 10, 8) + panicIfErr(err) + val = uint8(v) case reflect.Uint16: - v, err = strconv.ParseUint(arg, 10, 16) + v, err := strconv.ParseUint(arg, 10, 16) + panicIfErr(err) + val = uint16(v) case reflect.Uint32: - v, err = strconv.ParseUint(arg, 10, 32) + v, err := strconv.ParseUint(arg, 10, 32) + panicIfErr(err) + val = uint32(v) case reflect.Uint64: - v, err = strconv.ParseUint(arg, 10, 64) + v, err := strconv.ParseUint(arg, 10, 64) + panicIfErr(err) + val = v case reflect.Float32: - v, err = strconv.ParseFloat(arg, 32) + v, err := strconv.ParseFloat(arg, 32) + panicIfErr(err) + val = float32(v) case reflect.Float64: - v, err = strconv.ParseFloat(arg, 64) + v, err := strconv.ParseFloat(arg, 64) + panicIfErr(err) + val = v case reflect.Bool: - v, err = strconv.ParseBool(arg) + v, err := strconv.ParseBool(arg) + panicIfErr(err) + val = v case reflect.Slice: if len(arg) > 1 { if arg[0] == '[' && arg[len(arg)-1] == ']' { // it is a single argument but as slice. - v = strings.Split(arg[1:len(arg)-1], ",") // only string slices. + val = strings.Split(arg[1:len(arg)-1], ",") // only string slices. } } default: - v = arg + val = arg } - if err != nil { - panic(fmt.Sprintf("on field index: %d: %v", i, err)) - } - - argValue := reflect.ValueOf(v) + argValue := reflect.ValueOf(val) if expected, got := field.Kind(), argValue.Kind(); expected != got { panic(fmt.Sprintf("fields should have the same type: [%d] expected %s but got %s", i, expected, got)) } @@ -190,6 +213,11 @@ type ( // and it can register param functions // to that macro which maps to a parameter type. Macro struct { + indent string + alias string + master bool + trailing bool + Evaluator EvaluatorFunc funcs []ParamFunc } @@ -212,19 +240,51 @@ type ( } ) -func newMacro(evaluator EvaluatorFunc) *Macro { - return &Macro{Evaluator: evaluator} +// NewMacro creates and returns a Macro that can be used as a registry for +// a new customized parameter type and its functions. +func NewMacro(indent, alias string, master, trailing bool, evaluator EvaluatorFunc) *Macro { + return &Macro{ + indent: indent, + alias: alias, + master: master, + trailing: trailing, + + Evaluator: evaluator, + } +} + +func (m *Macro) Indent() string { + return m.indent +} + +func (m *Macro) Alias() string { + return m.alias +} + +func (m *Macro) Master() bool { + return m.master } +func (m *Macro) Trailing() bool { + return m.trailing +} + +// func (m *Macro) SetParamResolver(fn func(memstore.Entry) interface{}) *Macro { +// m.ParamResolver = fn +// return m +// } + // RegisterFunc registers a parameter function // to that macro. // Accepts the func name ("range") // and the function body, which should return an EvaluatorFunc // a bool (it will be converted to EvaluatorFunc later on), // i.e RegisterFunc("min", func(minValue int) func(paramValue string) bool){}) -func (m *Macro) RegisterFunc(funcName string, fn interface{}) { +func (m *Macro) RegisterFunc(funcName string, fn interface{}) *Macro { fullFn := convertBuilderFunc(fn) m.registerFunc(funcName, fullFn) + + return m } func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { @@ -256,113 +316,3 @@ func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder { } return nil } - -// Map contains the default macros mapped to their types. -// This is the manager which is used by the caller to register custom -// parameter functions per param-type (String, Int, Long, Boolean, Alphabetical, File, Path). -type Map struct { - // string type - // anything - String *Macro - - // int type - // both positive and negative numbers, any number of digits. - Number *Macro - // int64 as int64 type - // -9223372036854775808 to 9223372036854775807. - Int64 *Macro - // uint8 as uint8 type - // 0 to 255. - Uint8 *Macro - // uint64 as uint64 type - // 0 to 18446744073709551615. - Uint64 *Macro - - // boolean as bool type - // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" - // or "0" or "f" or "F" or "FALSE" or "false" or "False". - Boolean *Macro - // alphabetical/letter type - // letters only (upper or lowercase) - Alphabetical *Macro - // file type - // letters (upper or lowercase) - // numbers (0-9) - // underscore (_) - // dash (-) - // point (.) - // no spaces! or other character - File *Macro - // path type - // anything, should be the last part - Path *Macro -} - -// NewMap returns a new macro Map with default -// type evaluators. -// -// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path -func NewMap() *Map { - simpleNumberEvalutator := MustNewEvaluatorFromRegexp("^-?[0-9]+$") - return &Map{ - // it allows everything, so no need for a regexp here. - String: newMacro(func(string) bool { return true }), - Number: newMacro(simpleNumberEvalutator), //"^(-?0\\.[0-9]*[1-9]+[0-9]*$)|(^-?[1-9]+[0-9]*((\\.[0-9]*[1-9]+[0-9]*$)|(\\.[0-9]+)))|(^-?[1-9]+[0-9]*$)|(^0$){1}")), //("^-?[0-9]+$")), - Int64: newMacro(func(paramValue string) bool { - if !simpleNumberEvalutator(paramValue) { - return false - } - _, err := strconv.ParseInt(paramValue, 10, 64) - // if err == strconv.ErrRange... - return err == nil - }), //("^-[1-9]|-?[1-9][0-9]{1,14}|-?1000000000000000|-?10000000000000000|-?100000000000000000|-?[1-9]000000000000000000|-?9[0-2]00000000000000000|-?92[0-2]0000000000000000|-?922[0-3]000000000000000|-?9223[0-3]00000000000000|-?92233[0-7]0000000000000|-?922337[0-2]000000000000|-?92233720[0-3]0000000000|-?922337203[0-6]000000000|-?9223372036[0-8]00000000|-?92233720368[0-5]0000000|-?922337203685[0-4]000000|-?9223372036854[0-7]00000|-?92233720368547[0-7]0000|-?922337203685477[0-5]000|-?922337203685477[56]000|[0-9]$")), - Uint8: newMacro(MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")), - Uint64: newMacro(func(paramValue string) bool { - if !simpleNumberEvalutator(paramValue) { - return false - } - _, err := strconv.ParseUint(paramValue, 10, 64) - return err == nil - }), //("^[0-9]|[1-9][0-9]{1,14}|1000000000000000|10000000000000000|100000000000000000|1000000000000000000|1[0-8]000000000000000000|18[0-4]00000000000000000|184[0-4]0000000000000000|1844[0-6]000000000000000|18446[0-7]00000000000000|184467[0-4]0000000000000|1844674[0-4]000000000000|184467440[0-7]0000000000|1844674407[0-3]000000000|18446744073[0-7]00000000|1844674407370000000[0-9]|18446744073709[0-5]00000|184467440737095[0-5]0000|1844674407370955[0-2]000$")), - Boolean: newMacro(func(paramValue string) bool { - // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$ - // in this case. - _, err := strconv.ParseBool(paramValue) - return err == nil - }), - Alphabetical: newMacro(MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")), - File: newMacro(MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")), - // it allows everything, we have String and Path as different - // types because I want to give the opportunity to the user - // to organise the macro functions based on wildcard or single dynamic named path parameter. - // Should be the last. - Path: newMacro(func(string) bool { return true }), - } -} - -// Lookup returns the specific Macro from the map -// based on the parameter type. -// i.e if ast.ParamTypeNumber then it will return the m.Number. -// Returns the m.String if not matched. -func (m *Map) Lookup(typ ast.ParamType) *Macro { - switch typ { - case ast.ParamTypeNumber: - return m.Number - case ast.ParamTypeInt64: - return m.Int64 - case ast.ParamTypeUint8: - return m.Uint8 - case ast.ParamTypeUint64: - return m.Uint64 - case ast.ParamTypeBoolean: - return m.Boolean - case ast.ParamTypeAlphabetical: - return m.Alphabetical - case ast.ParamTypeFile: - return m.File - case ast.ParamTypePath: - return m.Path - default: - return m.String - } -} diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index 33f37fd05a..20a2973215 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -71,8 +71,6 @@ func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, pass bo } func TestStringEvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -86,13 +84,11 @@ func TestStringEvaluatorRaw(t *testing.T) { } // 0 for i, tt := range tests { - testEvaluatorRaw(t, f.String, tt.input, tt.pass, i) + testEvaluatorRaw(t, String, tt.input, tt.pass, i) } } func TestNumberEvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -111,13 +107,11 @@ func TestNumberEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.Number, tt.input, tt.pass, i) + testEvaluatorRaw(t, Number, tt.input, tt.pass, i) } } func TestInt64EvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -138,13 +132,11 @@ func TestInt64EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.Int64, tt.input, tt.pass, i) + testEvaluatorRaw(t, Int64, tt.input, tt.pass, i) } } func TestUint8EvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -169,13 +161,11 @@ func TestUint8EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.Uint8, tt.input, tt.pass, i) + testEvaluatorRaw(t, Uint8, tt.input, tt.pass, i) } } func TestUint64EvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -196,13 +186,11 @@ func TestUint64EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.Uint64, tt.input, tt.pass, i) + testEvaluatorRaw(t, Uint64, tt.input, tt.pass, i) } } func TestAlphabeticalEvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -215,13 +203,11 @@ func TestAlphabeticalEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.Alphabetical, tt.input, tt.pass, i) + testEvaluatorRaw(t, Alphabetical, tt.input, tt.pass, i) } } func TestFileEvaluatorRaw(t *testing.T) { - f := NewMap() - tests := []struct { pass bool input string @@ -234,13 +220,11 @@ func TestFileEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, f.File, tt.input, tt.pass, i) + testEvaluatorRaw(t, File, tt.input, tt.pass, i) } } func TestPathEvaluatorRaw(t *testing.T) { - f := NewMap() - pathTests := []struct { pass bool input string @@ -254,28 +238,10 @@ func TestPathEvaluatorRaw(t *testing.T) { } for i, tt := range pathTests { - testEvaluatorRaw(t, f.Path, tt.input, tt.pass, i) + testEvaluatorRaw(t, Path, tt.input, tt.pass, i) } } -// func TestMapRegisterFunc(t *testing.T) { -// m := NewMap() -// m.String.RegisterFunc("prefix", func(prefix string) EvaluatorFunc { -// return func(paramValue string) bool { -// return strings.HasPrefix(paramValue, prefix) -// } -// }) - -// p, err := Parse("/user/@iris") -// if err != nil { -// t.Fatalf(err) -// } - -// // p.Params = append(p.) - -// testEvaluatorRaw(t, m.String, p.Src, false, 0) -// } - func TestConvertBuilderFunc(t *testing.T) { fn := func(min uint64, slice []string) func(string) bool { return func(paramValue string) bool { diff --git a/core/router/macro/macros.go b/core/router/macro/macros.go new file mode 100644 index 0000000000..1a520af2b6 --- /dev/null +++ b/core/router/macro/macros.go @@ -0,0 +1,375 @@ +package macro + +import ( + "strconv" + "strings" + + "github.com/kataras/iris/core/router/macro/interpreter/ast" +) + +var ( + // String type + // Allows anything (single path segment, as everything except the `Path`). + String = NewMacro("string", "", true, false, func(string) bool { return true }). + RegisterFunc("regexp", func(expr string) EvaluatorFunc { + return MustNewEvaluatorFromRegexp(expr) + }). + // checks if param value starts with the 'prefix' arg + RegisterFunc("prefix", func(prefix string) EvaluatorFunc { + return func(paramValue string) bool { + return strings.HasPrefix(paramValue, prefix) + } + }). + // checks if param value ends with the 'suffix' arg + RegisterFunc("suffix", func(suffix string) EvaluatorFunc { + return func(paramValue string) bool { + return strings.HasSuffix(paramValue, suffix) + } + }). + // checks if param value contains the 's' arg + RegisterFunc("contains", func(s string) EvaluatorFunc { + return func(paramValue string) bool { + return strings.Contains(paramValue, s) + } + }). + // checks if param value's length is at least 'min' + RegisterFunc("min", func(min int) EvaluatorFunc { + return func(paramValue string) bool { + return len(paramValue) >= min + } + }). + // checks if param value's length is not bigger than 'max' + RegisterFunc("max", func(max int) EvaluatorFunc { + return func(paramValue string) bool { + return max >= len(paramValue) + } + }) + + simpleNumberEvalutator = MustNewEvaluatorFromRegexp("^-?[0-9]+$") + // Number or int type + // both positive and negative numbers, any number of digits. + Number = NewMacro("number", "int", false, false, simpleNumberEvalutator). + // checks if the param value's int representation is + // bigger or equal than 'min' + RegisterFunc("min", func(min int) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.Atoi(paramValue) + if err != nil { + return false + } + return n >= min + } + }). + // checks if the param value's int representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max int) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.Atoi(paramValue) + if err != nil { + return false + } + return n <= max + } + }). + // checks if the param value's int representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max int) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.Atoi(paramValue) + if err != nil { + return false + } + + if n < min || n > max { + return false + } + return true + } + }) + + // Int64 as int64 type + // -9223372036854775808 to 9223372036854775807. + Int64 = NewMacro("int64", "long", false, false, func(paramValue string) bool { + if !simpleNumberEvalutator(paramValue) { + return false + } + _, err := strconv.ParseInt(paramValue, 10, 64) + // if err == strconv.ErrRange... + return err == nil + }). + // checks if the param value's int64 representation is + // bigger or equal than 'min'. + RegisterFunc("min", func(min int64) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseInt(paramValue, 10, 64) + if err != nil { + return false + } + return n >= min + } + }). + // checks if the param value's int64 representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max int64) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseInt(paramValue, 10, 64) + if err != nil { + return false + } + return n <= max + } + }). + // checks if the param value's int64 representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max int64) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseInt(paramValue, 10, 64) + if err != nil { + return false + } + + if n < min || n > max { + return false + } + return true + } + }) + + // Uint8 as uint8 type + // 0 to 255. + Uint8 = NewMacro("uint8", "", false, false, MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")). + // checks if the param value's uint8 representation is + // bigger or equal than 'min'. + RegisterFunc("min", func(min uint8) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + + return uint8(n) >= min + } + }). + // checks if the param value's uint8 representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max uint8) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + return uint8(n) <= max + } + }). + // checks if the param value's uint8 representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max uint8) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return false + } + + if v := uint8(n); v < min || v > max { + return false + } + return true + } + }) + + // Uint64 as uint64 type + // 0 to 18446744073709551615. + Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) bool { + if !simpleNumberEvalutator(paramValue) { + return false + } + _, err := strconv.ParseUint(paramValue, 10, 64) + return err == nil + }). + // checks if the param value's uint64 representation is + // bigger or equal than 'min'. + RegisterFunc("min", func(min uint64) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 64) + if err != nil { + return false + } + return n >= min + } + }). + // checks if the param value's uint64 representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max uint64) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 64) + if err != nil { + return false + } + return n <= max + } + }). + // checks if the param value's uint64 representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max uint64) EvaluatorFunc { + return func(paramValue string) bool { + n, err := strconv.ParseUint(paramValue, 10, 64) + if err != nil { + return false + } + + if n < min || n > max { + return false + } + return true + } + }) + + // Bool or boolean as bool type + // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" + // or "0" or "f" or "F" or "FALSE" or "false" or "False". + Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) bool { + // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$ + // in this case. + _, err := strconv.ParseBool(paramValue) + return err == nil + }) + + // Alphabetical letter type + // letters only (upper or lowercase) + Alphabetical = NewMacro("alphabetical", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")) + // File type + // letters (upper or lowercase) + // numbers (0-9) + // underscore (_) + // dash (-) + // point (.) + // no spaces! or other character + File = NewMacro("file", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")) + // Path type + // anything, should be the last part + // + // It allows everything, we have String and Path as different + // types because I want to give the opportunity to the user + // to organise the macro functions based on wildcard or single dynamic named path parameter. + // Should be living in the latest path segment of a route path. + Path = NewMacro("path", "", false, true, func(string) bool { return true }) + + Defaults = &Macros{ + String, + Number, + Int64, + Uint8, + Uint64, + Bool, + Alphabetical, + Path, + } +) + +type Macros []*Macro + +func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator EvaluatorFunc) *Macro { + macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator) + if ms.register(macro) { + return macro + } + return nil +} + +func (ms *Macros) register(macro *Macro) bool { + if macro.Indent() == "" || macro.Evaluator == nil { + return false + } + + cp := *ms + + for _, m := range cp { + + // can't add more than one with the same ast characteristics. + if macro.Indent() == m.Indent() { + return false + } + + if macro.Alias() == m.Alias() || macro.Alias() == m.Indent() { + return false + } + + if macro.Master() && m.Master() { + return false + } + } + + cp = append(cp, macro) + + *ms = cp + return true +} + +func (ms *Macros) Unregister(indent string) bool { + cp := *ms + + for i, m := range cp { + if m.Indent() == indent { + copy(cp[i:], cp[i+1:]) + cp[len(cp)-1] = nil + cp = cp[:len(cp)-1] + + *ms = cp + return true + } + } + + return false +} + +func (ms *Macros) Lookup(pt ast.ParamType) *Macro { + if m := ms.Get(pt.Indent()); m != nil { + return m + } + + if alias, has := ast.HasAlias(pt); has { + if m := ms.Get(alias); m != nil { + return m + } + } + + return nil +} + +func (ms *Macros) Get(indentOrAlias string) *Macro { + if indentOrAlias == "" { + return nil + } + + for _, m := range *ms { + if m.Indent() == indentOrAlias { + return m + } + + if m.Alias() == indentOrAlias { + return m + } + } + + return nil +} + +func (ms *Macros) GetMaster() *Macro { + for _, m := range *ms { + if m.Master() { + return m + } + } + + return nil +} + +func (ms *Macros) GetTrailings() (macros []*Macro) { + for _, m := range *ms { + if m.Trailing() { + macros = append(macros, m) + } + } + + return +} diff --git a/core/router/macro/template.go b/core/router/macro/template.go index f26c1d0619..3b30d2f79e 100644 --- a/core/router/macro/template.go +++ b/core/router/macro/template.go @@ -25,6 +25,7 @@ type TemplateParam struct { // it's useful on host to decide how to convert the path template to specific router's syntax Type ast.ParamType `json:"type"` Name string `json:"name"` + Index int `json:"index"` ErrCode int `json:"errCode"` TypeEvaluator EvaluatorFunc `json:"-"` Funcs []EvaluatorFunc `json:"-"` @@ -34,15 +35,20 @@ type TemplateParam struct { // and returns a new Template. // It builds all the parameter functions for that template // and their evaluators, it's the api call that makes use the interpeter's parser -> lexer. -func Parse(src string, macros *Map) (*Template, error) { - params, err := parser.Parse(src) +func Parse(src string, macros Macros) (*Template, error) { + types := make([]ast.ParamType, len(macros)) + for i, m := range macros { + types[i] = m + } + + params, err := parser.Parse(src, types) if err != nil { return nil, err } t := new(Template) t.Src = src - for _, p := range params { + for idx, p := range params { funcMap := macros.Lookup(p.Type) typEval := funcMap.Evaluator @@ -50,17 +56,23 @@ func Parse(src string, macros *Map) (*Template, error) { Src: p.Src, Type: p.Type, Name: p.Name, + Index: idx, ErrCode: p.ErrorCode, TypeEvaluator: typEval, } + for _, paramfn := range p.Funcs { tmplFn := funcMap.getFunc(paramfn.Name) - if tmplFn == nil { // if not find on this type, check for String's which is for global funcs too - tmplFn = macros.String.getFunc(paramfn.Name) - if tmplFn == nil { // if not found then just skip this param + if tmplFn == nil { // if not find on this type, check for Master's which is for global funcs too. + if m := macros.GetMaster(); m != nil { + tmplFn = m.getFunc(paramfn.Name) + } + + if tmplFn == nil { // if not found then just skip this param. continue } } + evalFn := tmplFn(paramfn.Args) if evalFn == nil { continue diff --git a/core/router/party.go b/core/router/party.go index 745d038e82..99e482e10a 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -18,11 +18,11 @@ type Party interface { GetRelPath() string // GetReporter returns the reporter for adding errors GetReporter() *errors.Reporter - // Macros returns the macro map which is responsible - // to register custom macro functions for all routes. + // Macros returns the macro collection that is responsible + // to register custom macros with their own parameter types and their macro functions for all routes. // // Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path - Macros() *macro.Map + Macros() *macro.Macros // Party groups routes which may have the same prefix and share same handlers, // returns that new rich subrouter. diff --git a/core/router/route.go b/core/router/route.go index b581b6596f..11a69d6ef1 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -39,7 +39,7 @@ type Route struct { // It parses the path based on the "macros", // handlers are being changed to validate the macros at serve time, if needed. func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, - handlers context.Handlers, macros *macro.Map) (*Route, error) { + handlers context.Handlers, macros macro.Macros) (*Route, error) { tmpl, err := macro.Parse(unparsedPath, macros) if err != nil { diff --git a/hero/di.go b/hero/di.go index 4244cfa0a9..d50665145b 100644 --- a/hero/di.go +++ b/hero/di.go @@ -8,6 +8,17 @@ import ( func init() { di.DefaultHijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) { + // if IsExpectingStore(fieldOrFuncInput) { + // return &di.BindObject{ + // Type: memstoreTyp, + // BindType: di.Dynamic, + // ReturnValue: func(ctxValue []reflect.Value) reflect.Value { + // // return ctxValue[0].MethodByName("Params").Call(di.EmptyIn)[0] + // return ctxValue[0].MethodByName("Params").Call(di.EmptyIn)[0].Field(0) // the Params' memstore.Store. + // }, + // }, true + // } + if !IsContext(fieldOrFuncInput) { return nil, false } diff --git a/hero/di/func.go b/hero/di/func.go index da81d6f320..3c417d8f0c 100644 --- a/hero/di/func.go +++ b/hero/di/func.go @@ -132,9 +132,8 @@ func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool { } if b.IsAssignable(inTyp) { - // println(inTyp.String() + " is assignable to " + val.Type().String()) // fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n", - // i, b.Type.String(), value.String(), val.Pointer()) + // i, b.Type.String(), inTyp.String(), inTyp.Pointer()) s.inputs = append(s.inputs, &targetFuncInput{ InputIndex: inputIndex, Object: &b, @@ -194,8 +193,8 @@ func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) { args := *in for _, input := range s.inputs { input.Object.Assign(ctx, func(v reflect.Value) { - // fmt.Printf("assign input index: %d for value: %v\n", - // input.InputIndex, v.String()) + // fmt.Printf("assign input index: %d for value: %v of type: %s\n", + // input.InputIndex, v.String(), v.Type().Name()) args[input.InputIndex] = v }) diff --git a/hero/di/object.go b/hero/di/object.go index 392abcc587..385162bd87 100644 --- a/hero/di/object.go +++ b/hero/di/object.go @@ -101,6 +101,11 @@ func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Val if !v.IsValid() { return zeroOutVal } + // if v.String() == "" { + // println("di/object.go: " + v.String()) + // // println("di/object.go: because it's interface{} it should be returned as: " + v.Elem().Type().String() + " and its value: " + v.Elem().Interface().(string)) + // return v.Elem() + // } return v } diff --git a/hero/di/reflect.go b/hero/di/reflect.go index 3b91ebf8d5..08fc7f2a7a 100644 --- a/hero/di/reflect.go +++ b/hero/di/reflect.go @@ -54,6 +54,7 @@ func IsZero(v reflect.Value) bool { // if can't interface, i.e return value from unexported field or method then return false return false } + zero := reflect.Zero(v.Type()) return v.Interface() == zero.Interface() } @@ -62,7 +63,10 @@ func IsZero(v reflect.Value) bool { // If "v" is a nil pointer, Indirect returns a zero Value. // If "v" is not a pointer, Indirect returns v. func IndirectValue(v reflect.Value) reflect.Value { - return reflect.Indirect(v) + if k := v.Kind(); k == reflect.Ptr { //|| k == reflect.Interface { + return v.Elem() + } + return v } // ValueOf returns the reflect.Value of "o". @@ -123,6 +127,11 @@ func equalTypes(got reflect.Type, expected reflect.Type) bool { // fmt.Printf("expected interface = %s and got to set on the arg is: %s\n", expected.String(), got.String()) return got.Implements(expected) } + + // if got.String() == "interface {}" { + // return true + // } + return false } @@ -161,7 +170,6 @@ func lookupFields(elemTyp reflect.Type, skipUnexported bool, parentIndex []int) for i, n := 0, elemTyp.NumField(); i < n; i++ { f := elemTyp.Field(i) - if IndirectType(f.Type).Kind() == reflect.Struct && !structFieldIgnored(f) { fields = append(fields, lookupFields(f.Type, skipUnexported, append(parentIndex, i))...) diff --git a/hero/handler.go b/hero/handler.go index bb427d772c..6644dc83cb 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -5,19 +5,31 @@ import ( "reflect" "runtime" + "github.com/kataras/iris/context" + "github.com/kataras/iris/core/memstore" "github.com/kataras/iris/hero/di" "github.com/kataras/golog" - "github.com/kataras/iris/context" ) -var contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() +var ( + contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() + memstoreTyp = reflect.TypeOf(memstore.Store{}) +) // IsContext returns true if the "inTyp" is a type of Context. func IsContext(inTyp reflect.Type) bool { return inTyp.Implements(contextTyp) } +// IsExpectingStore returns true if the "inTyp" is a type of memstore.Store. +func IsExpectingStore(inTyp reflect.Type) bool { + print("di/handler.go: " + inTyp.String() + " vs " + memstoreTyp.String() + " : ") + println(inTyp == memstoreTyp) + + return inTyp == memstoreTyp +} + // checks if "handler" is context.Handler: func(context.Context). func isContextHandler(handler interface{}) (context.Handler, bool) { h, is := handler.(context.Handler) @@ -70,7 +82,7 @@ func makeHandler(handler interface{}, values ...reflect.Value) (context.Handler, // is invalid when input len and values are not match // or their types are not match, we will take look at the // second statement, here we will re-try it - // using binders for path parameters: string, int, int64, bool. + // using binders for path parameters: string, int, int64, uint8, uint64, bool and so on. // We don't have access to the path, so neither to the macros here, // but in mvc. So we have to do it here. if valid = funcInjector.Retry(new(params).resolve); !valid { diff --git a/hero/param.go b/hero/param.go index 4ea62fc298..dc34b10e61 100644 --- a/hero/param.go +++ b/hero/param.go @@ -19,64 +19,8 @@ type params struct { func (p *params) resolve(index int, typ reflect.Type) (reflect.Value, bool) { currentParamIndex := p.next - v, ok := resolveParam(currentParamIndex, typ) + v, ok := context.ParamResolverByKindAndIndex(typ.Kind(), currentParamIndex) p.next = p.next + 1 return v, ok } - -func resolveParam(currentParamIndex int, typ reflect.Type) (reflect.Value, bool) { - var fn interface{} - - switch typ.Kind() { - case reflect.Int: - fn = func(ctx context.Context) int { - // the second "ok/found" check is not necessary, - // because even if the entry didn't found on that "index" - // it will return an empty entry which will return the - // default value passed from the xDefault(def) because its `ValueRaw` is nil. - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.IntDefault(0) - return v - } - case reflect.Int64: - fn = func(ctx context.Context) int64 { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.Int64Default(0) - - return v - } - case reflect.Uint8: - fn = func(ctx context.Context) uint8 { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.Uint8Default(0) - - return v - } - case reflect.Uint64: - fn = func(ctx context.Context) uint64 { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.Uint64Default(0) - - return v - } - case reflect.Bool: - fn = func(ctx context.Context) bool { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.BoolDefault(false) - return v - } - case reflect.String: - fn = func(ctx context.Context) string { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - // print(entry.Key + " with index of: ") - // print(currentParamIndex) - // println(" and value: " + entry.String()) - return entry.String() - } - default: - return reflect.Value{}, false - } - - return reflect.ValueOf(fn), true -} diff --git a/mvc/controller.go b/mvc/controller.go index 41f37ac200..85675ec25a 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -247,7 +247,7 @@ func (c *ControllerActivator) parseMethods() { } func (c *ControllerActivator) parseMethod(m reflect.Method) { - httpMethod, httpPath, err := parseMethod(m, c.isReservedMethod) + httpMethod, httpPath, err := parseMethod(*c.router.Macros(), m, c.isReservedMethod) if err != nil { if err != errSkip { c.addErr(fmt.Errorf("MVC: fail to parse the route path and HTTP method for '%s.%s': %v", c.fullName, m.Name, err)) @@ -283,7 +283,7 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . } // parse a route template which contains the parameters organised. - tmpl, err := macro.Parse(path, c.router.Macros()) + tmpl, err := macro.Parse(path, *c.router.Macros()) if err != nil { c.addErr(fmt.Errorf("MVC: fail to parse the path for '%s.%s': %v", c.fullName, funcName, err)) return nil @@ -338,6 +338,7 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref } // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) + funcInjector := di.Func(m.Func, funcDependencies...) // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) if funcInjector.Has { @@ -396,6 +397,11 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref in := make([]reflect.Value, n, n) in[0] = ctrl funcInjector.Inject(&in, ctxValue) + + // for idxx, inn := range in { + // println("controller.go: execution: in.Value = "+inn.String()+" and in.Type = "+inn.Type().Kind().String()+" of index: ", idxx) + // } + hero.DispatchFuncResult(ctx, call(in)) return } diff --git a/mvc/controller_handle_test.go b/mvc/controller_handle_test.go index 55e200d4c4..9864b3ad6e 100644 --- a/mvc/controller_handle_test.go +++ b/mvc/controller_handle_test.go @@ -37,6 +37,7 @@ type testControllerHandle struct { func (c *testControllerHandle) BeforeActivation(b BeforeActivation) { b.Handle("GET", "/histatic", "HiStatic") b.Handle("GET", "/hiservice", "HiService") + b.Handle("GET", "/hiservice/{ps:string}", "HiServiceBy") b.Handle("GET", "/hiparam/{ps:string}", "HiParamBy") b.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy") } @@ -84,6 +85,10 @@ func (c *testControllerHandle) HiService() string { return c.Service.Say("hi") } +func (c *testControllerHandle) HiServiceBy(v string) string { + return c.Service.Say("hi with param: " + v) +} + func (c *testControllerHandle) HiParamBy(v string) string { return v } @@ -116,7 +121,8 @@ func TestControllerHandle(t *testing.T) { // and can be used in a user-defined, dynamic "mvc handler". e.GET("/hiservice").Expect().Status(httptest.StatusOK). Body().Equal("service: hi") - + e.GET("/hiservice/value").Expect().Status(httptest.StatusOK). + Body().Equal("service: hi with param: value") // this worked with a temporary variadic on the resolvemethodfunc which is not // correct design, I should split the path and params with the rest of implementation // in order a simple template.Src can be given. diff --git a/mvc/controller_method_parser.go b/mvc/controller_method_parser.go index 97c1282edb..e5477e9eb8 100644 --- a/mvc/controller_method_parser.go +++ b/mvc/controller_method_parser.go @@ -4,11 +4,12 @@ import ( "errors" "fmt" "reflect" + "strconv" "strings" "unicode" "github.com/kataras/iris/core/router" - "github.com/kataras/iris/core/router/macro/interpreter/ast" + "github.com/kataras/iris/core/router/macro" ) const ( @@ -95,47 +96,25 @@ func (l *methodLexer) peekPrev() (w string) { return w } -var posWords = map[int]string{ - 0: "", - 1: "first", - 2: "second", - 3: "third", - 4: "forth", - 5: "five", - 6: "sixth", - 7: "seventh", - 8: "eighth", - 9: "ninth", - 10: "tenth", - 11: "eleventh", - 12: "twelfth", - 13: "thirteenth", - 14: "fourteenth", - 15: "fifteenth", - 16: "sixteenth", - 17: "seventeenth", - 18: "eighteenth", - 19: "nineteenth", - 20: "twentieth", -} - func genParamKey(argIdx int) string { - return "arg" + posWords[argIdx] // argfirst, argsecond... + return "param" + strconv.Itoa(argIdx) // param0, param1, param2... } type methodParser struct { - lexer *methodLexer - fn reflect.Method + lexer *methodLexer + fn reflect.Method + macros macro.Macros } -func parseMethod(fn reflect.Method, skipper func(string) bool) (method, path string, err error) { +func parseMethod(macros macro.Macros, fn reflect.Method, skipper func(string) bool) (method, path string, err error) { if skipper(fn.Name) { return "", "", errSkip } p := &methodParser{ - fn: fn, - lexer: newMethodLexer(fn.Name), + fn: fn, + lexer: newMethodLexer(fn.Name), + macros: macros, } return p.parse() } @@ -211,34 +190,45 @@ func (p *methodParser) parsePathParam(path string, w string, funcArgPos int) (st var ( paramKey = genParamKey(funcArgPos) // argfirst, argsecond... - paramType = ast.ParamTypeString // default string + m = p.macros.GetMaster() // default (String by-default) + trailings = p.macros.GetTrailings() ) // string, int... - goType := typ.In(funcArgPos).Name() + goType := typ.In(funcArgPos).Kind() nextWord := p.lexer.peekNext() if nextWord == tokenWildcard { p.lexer.skip() // skip the Wildcard word. - paramType = ast.ParamTypePath - } else if pType := ast.LookupParamTypeFromStd(goType); pType != ast.ParamTypeUnExpected { - // it's not wildcard, so check base on our available macro types. - paramType = pType + if len(trailings) == 0 { + return "", 0, errors.New("no trailing path parameter found") + } + m = trailings[0] } else { - if typ.NumIn() > funcArgPos { - // has more input arguments but we are not in the correct - // index now, maybe the first argument was an `iris/context.Context` - // so retry with the "funcArgPos" incremented. - // - // the "funcArgPos" will be updated to the caller as well - // because we return it among the path and the error. - return p.parsePathParam(path, w, funcArgPos+1) + // validMacros := p.macros.LookupForGoType(goType) + + // instead of mapping with a reflect.Kind which has its limitation, + // we map the param types with a go type as a string, + // so custom structs such as "user" can be mapped to a macro with indent || alias == "user". + m = p.macros.Get(strings.ToLower(goType.String())) + + if m == nil { + if typ.NumIn() > funcArgPos { + // has more input arguments but we are not in the correct + // index now, maybe the first argument was an `iris/context.Context` + // so retry with the "funcArgPos" incremented. + // + // the "funcArgPos" will be updated to the caller as well + // because we return it among the path and the error. + return p.parsePathParam(path, w, funcArgPos+1) + } + + return "", 0, fmt.Errorf("invalid syntax: the standard go type: %s found in controller's function: %s at position: %d does not match any valid macro", goType, p.fn.Name, funcArgPos) } - return "", 0, errors.New("invalid syntax for " + p.fn.Name) } // /{argfirst:path}, /{argfirst:long}... - path += fmt.Sprintf("/{%s:%s}", paramKey, paramType.String()) + path += fmt.Sprintf("/{%s:%s}", paramKey, m.Indent()) if nextWord == "" && typ.NumIn() > funcArgPos+1 { // By is the latest word but func is expected diff --git a/mvc/param.go b/mvc/param.go index c2f2299022..a81d191c44 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -5,7 +5,6 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/router/macro" - "github.com/kataras/iris/core/router/macro/interpreter/ast" ) func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) { @@ -13,61 +12,40 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) return } - consumedParams := make(map[int]bool, 0) - for _, in := range funcIn { - for j, p := range params { - if _, consumed := consumedParams[j]; consumed { - continue - } - paramType := p.Type - paramName := p.Name - // fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String()) - if paramType.Assignable(in.Kind()) { - consumedParams[j] = true - // fmt.Printf("param.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String()) - values = append(values, makeFuncParamGetter(paramType, paramName)) - } + // consumedParams := make(map[int]bool, 0) + // for _, in := range funcIn { + // for j, p := range params { + // if _, consumed := consumedParams[j]; consumed { + // continue + // } + + // // fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String()) + // if m := macros.Lookup(p.Type); m != nil && m.GoType == in.Kind() { + // consumedParams[j] = true + // // fmt.Printf("param.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String()) + // funcDep, ok := context.ParamResolverByKindAndIndex(m.GoType, p.Index) + // // funcDep, ok := context.ParamResolverByKindAndKey(in.Kind(), paramName) + // if !ok { + // // here we can add a logger about invalid parameter type although it should never happen here + // // unless the end-developer modified the macro/macros with a special type but not the context/ParamResolvers. + // continue + // } + // values = append(values, funcDep) + // } + // } + // } + + for i, param := range params { + if len(funcIn) <= i { + return } - } - - return -} - -func makeFuncParamGetter(paramType ast.ParamType, paramName string) reflect.Value { - var fn interface{} - - switch paramType { - case ast.ParamTypeNumber: - fn = func(ctx context.Context) int { - v, _ := ctx.Params().GetInt(paramName) - return v - } - case ast.ParamTypeInt64: - fn = func(ctx context.Context) int64 { - v, _ := ctx.Params().GetInt64(paramName) - return v - } - case ast.ParamTypeUint8: - fn = func(ctx context.Context) uint8 { - v, _ := ctx.Params().GetUint8(paramName) - return v - } - case ast.ParamTypeUint64: - fn = func(ctx context.Context) uint64 { - v, _ := ctx.Params().GetUint64(paramName) - return v - } - case ast.ParamTypeBoolean: - fn = func(ctx context.Context) bool { - v, _ := ctx.Params().GetBool(paramName) - return v - } - default: - // string, path... - fn = func(ctx context.Context) string { - return ctx.Params().Get(paramName) + funcDep, ok := context.ParamResolverByKindAndIndex(funcIn[i].Kind(), param.Index) + if !ok { + continue } + + values = append(values, funcDep) } - return reflect.ValueOf(fn) + return } From 13803a0bfd663a522f95d3f5c449dfff50a308d4 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 27 Sep 2018 03:17:45 +0300 Subject: [PATCH 16/91] Conversion once at macros and their functions, internal changes required --- README.md | 6 +- _examples/routing/dynamic-path/main.go | 28 ++- _examples/routing/macros/main.go | 66 ++++--- context/request_params.go | 3 +- core/memstore/memstore.go | 33 ++++ core/router/macro.go | 32 +++- core/router/macro/macro.go | 120 ++++++------ core/router/macro/macro_test.go | 68 ++++--- core/router/macro/macros.go | 243 +++++++++++-------------- core/router/macro/template.go | 8 +- hero/handler.go | 12 +- 11 files changed, 339 insertions(+), 280 deletions(-) diff --git a/README.md b/README.md index 9981c0b2f3..49125deb51 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ func main() { | Param Type | Go Type | Validation | Retrieve Helper | | -----------------|------|-------------|------| | `:string` | string | anything | `Params().Get` | -| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits | `Params().GetInt/Int64`...| +| `:int` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, depends on the arch | `Params().GetInt`...| | `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | | `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | | `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | @@ -127,7 +127,7 @@ func main() { ```go app.Get("/users/{id:uint64}", func(ctx iris.Context){ - id, _ := ctx.Params().GetUint64("id") + id := ctx.Params().GetUint64Default("id", 0) // [...] }) ``` @@ -226,7 +226,7 @@ func main() { // but will not match /users/-1 because uint should be bigger than zero // neither /users or /users/. app.Get("/users/{id:uint64}", func(ctx iris.Context) { - id, _ := ctx.Params().GetUint64("id") + id := ctx.Params().GetUint64Default("id", 0) ctx.Writef("User with ID: %d", id) }) diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 926b4af14c..6d11a878a1 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -2,7 +2,6 @@ package main import ( "regexp" - "strconv" "github.com/kataras/iris" ) @@ -122,17 +121,12 @@ func main() { // Let's register our first macro attached to uint64 macro type. // "min" = the function // "minValue" = the argument of the function - // func(string) bool = the macro's path parameter evaluator, this executes in serve time when + // func(uint64) bool = our func's evaluator, this executes in serve time when // a user requests a path which contains the :uint64 macro parameter type with the min(...) macro parameter function. - app.Macros().Uint64.RegisterFunc("min", func(minValue uint64) func(string) bool { - // do anything before serve here [...] - // at this case we don't need to do anything - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n >= minValue + app.Macros().Get("uint64").RegisterFunc("min", func(minValue uint64) func(uint64) bool { + // type of "paramValue" should match the type of the internal macro's evaluator function, which in this case is "uint64". + return func(paramValue uint64) bool { + return paramValue >= minValue } }) @@ -142,14 +136,14 @@ func main() { app.Get("/profile/{id:uint64 min(20)}", func(ctx iris.Context) { // second parameter is the error but it will always nil because we use macros, // the validaton already happened. - id, _ := ctx.Params().GetInt("id") + id := ctx.Params().GetUint64Default("id", 0) ctx.Writef("Hello id: %d", id) }) // to change the error code per route's macro evaluator: app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") // or GetUint64. - friendid, _ := ctx.Params().GetInt("friendid") + id := ctx.Params().GetUint64Default("id", 0) + friendid := ctx.Params().GetUint64Default("friendid", 0) ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. @@ -169,7 +163,7 @@ func main() { } // MatchString is a type of func(string) bool, so we use it as it is. - app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) + app.Macros().Get("string").RegisterFunc("coordinate", latLonRegex.MatchString) app.Get("/coordinates/{lat:string coordinate() else 502}/{lon:string coordinate() else 502}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) @@ -178,7 +172,7 @@ func main() { // // Another one is by using a custom body. - app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { + app.Macros().Get("string").RegisterFunc("range", func(minLength, maxLength int) func(string) bool { return func(paramValue string) bool { return len(paramValue) >= minLength && len(paramValue) <= maxLength } @@ -193,7 +187,7 @@ func main() { // // Register your custom macro function which accepts a slice of strings `[...,...]`. - app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { + app.Macros().Get("string").RegisterFunc("has", func(validNames []string) func(string) bool { return func(paramValue string) bool { for _, validName := range validNames { if validName == paramValue { diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index f45d4031e9..8548da88ec 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -16,37 +16,49 @@ func main() { app.Logger().SetLevel("debug") // Let's see how we can register a custom macro such as ":uint32" or ":small" for its alias (optionally) for Uint32 types. - app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool { - _, err := strconv.ParseUint(paramValue, 10, 32) - return err == nil - }). - RegisterFunc("min", func(min uint32) func(string) bool { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 32) - if err != nil { - return false - } + // app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool { + // _, err := strconv.ParseUint(paramValue, 10, 32) + // return err == nil + // }). + // RegisterFunc("min", func(min uint32) func(string) bool { + // return func(paramValue string) bool { + // n, err := strconv.ParseUint(paramValue, 10, 32) + // if err != nil { + // return false + // } + + // return uint32(n) >= min + // } + // }) - return uint32(n) >= min + /* TODO: + somehow define one-time how the parameter should be parsed to a particular type (go std or custom) + tip: we can change the original value from string to X using the entry's.ValueRaw + ^ Done 27 sep 2018. + */ + + app.Macros().Register("uint32", "small", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, 32) + return uint32(v), err == nil + }). + RegisterFunc("min", func(min uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return paramValue >= min } }) - /* TODO: - somehow define one-time how the parameter should be parsed to a particular type (go std or custom) - tip: we can change the original value from string to X using the entry's.ValueRaw - */ - + // optionally, only when mvc or hero features are used for this custom macro/parameter type. context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { - // return func(store memstore.Store) uint32 { - // param, _ := store.GetEntryAt(paramIndex) + /* both works but second is faster, we omit the duplication of the type conversion over and over as of 27 Sep of 2018 (this patch)*/ + // return func(ctx context.Context) uint32 { + // param := ctx.Params().GetEntryAt(paramIndex) // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) // return uint32(paramValueAsUint32) // } return func(ctx context.Context) uint32 { - param := ctx.Params().GetEntryAt(paramIndex) - paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) - return uint32(paramValueAsUint32) - } + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) + } /* TODO: find a way to automative it based on the macro's first return value type, if thats the case then we must not return nil even if not found, + we must return a value i.e 0 for int for its interface{} */ } // @@ -54,11 +66,11 @@ func main() { return fmt.Sprintf("Value of the parameter is: %d\n", paramValue) })) - app.Get("test_uint64/{myparam:uint64}", handler) + app.Get("test_uint64/{myparam:uint64 min(5)}", func(ctx context.Context) { + // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) + // but better and faster because the macro converts the string to uint64 automatically: + ctx.Writef("Value of the parameter is: %d\n", ctx.Params().GetUint64Default("myparam", 0)) + }) app.Run(iris.Addr(":8080")) } - -func handler(ctx context.Context) { - ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) -} diff --git a/context/request_params.go b/context/request_params.go index ebb20afb85..b8426856e2 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -1,6 +1,7 @@ package context import ( + "fmt" "reflect" "strconv" "strings" @@ -33,7 +34,7 @@ func (r *RequestParams) GetEntry(key string) memstore.Entry { // by the key-value params. func (r *RequestParams) Visit(visitor func(key string, value string)) { r.Store.Visit(func(k string, v interface{}) { - visitor(k, v.(string)) // always string here. + visitor(k, fmt.Sprintf("%v", v)) // always string here. }) } diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 52c3b3407c..4eda5113f8 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -6,6 +6,7 @@ package memstore import ( + "fmt" "reflect" "strconv" "strings" @@ -67,11 +68,19 @@ func (e Entry) GetByKindOrNil(k reflect.Kind) interface{} { // If not found returns "def". func (e Entry) StringDefault(def string) string { v := e.ValueRaw + if v == nil { + return def + } if vString, ok := v.(string); ok { return vString } + val := fmt.Sprintf("%v", v) + if val != "" { + return val + } + return def } @@ -105,6 +114,20 @@ func (e Entry) IntDefault(def int) (int, error) { return val, nil case int: return vv, nil + case int8: + return int(vv), nil + case int32: + return int(vv), nil + case int64: + return int(vv), nil + case uint: + return int(vv), nil + case uint8: + return int(vv), nil + case uint32: + return int(vv), nil + case uint64: + return int(vv), nil } return def, errFindParse.Format("int", e.Key) @@ -123,6 +146,10 @@ func (e Entry) Int64Default(def int64) (int64, error) { return strconv.ParseInt(vv, 10, 64) case int64: return vv, nil + case int32: + return int64(vv), nil + case int8: + return int64(vv), nil case int: return int64(vv), nil } @@ -151,6 +178,12 @@ func (e Entry) Float64Default(def float64) (float64, error) { return vv, nil case int: return float64(vv), nil + case int64: + return float64(vv), nil + case uint: + return float64(vv), nil + case uint64: + return float64(vv), nil } return def, errFindParse.Format("float64", e.Key) diff --git a/core/router/macro.go b/core/router/macro.go index 02c0ea952e..2af58d3425 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -3,6 +3,7 @@ package router import ( "fmt" "net/http" + "reflect" "strings" "github.com/kataras/iris/context" @@ -83,24 +84,37 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { return func(ctx context.Context) { for _, p := range tmpl.Params { paramValue := ctx.Params().Get(p.Name) - // first, check for type evaluator - if !p.TypeEvaluator(paramValue) { + if p.TypeEvaluator == nil { + // allow. + ctx.Next() + return + } + + // first, check for type evaluator. + newValue, passed := p.TypeEvaluator(paramValue) + if !passed { ctx.StatusCode(p.ErrCode) ctx.StopExecution() return } - // then check for all of its functions - for _, evalFunc := range p.Funcs { - if !evalFunc(paramValue) { - ctx.StatusCode(p.ErrCode) - ctx.StopExecution() - return + if len(p.Funcs) > 0 { + paramIn := []reflect.Value{reflect.ValueOf(newValue)} + // then check for all of its functions + for _, evalFunc := range p.Funcs { + // or make it as func(interface{}) bool and pass directly the "newValue" + // but that would not be as easy for end-developer, so keep that "slower": + if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool + ctx.StatusCode(p.ErrCode) + ctx.StopExecution() + return + } } } + ctx.Params().Store.Set(p.Name, newValue) } - // if all passed, just continue + // if all passed, just continue. ctx.Next() } }(*tmpl) diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index 5cb304e06b..a589e60a2a 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -12,14 +12,51 @@ import ( // EvaluatorFunc is the signature for both param types and param funcs. // It should accepts the param's value as string // and return true if validated otherwise false. -type EvaluatorFunc func(paramValue string) bool +// type EvaluatorFunc func(paramValue string) bool +// type BinderFunc func(paramValue string) interface{} -// NewEvaluatorFromRegexp accepts a regexp "expr" expression -// and returns an EvaluatorFunc based on that regexp. -// the regexp is compiled before return. +type ( + ParamEvaluator func(paramValue string) (interface{}, bool) + // FuncEvaluator interface{} // i.e func(paramValue int) bool +) + +var goodEvaluatorFuncs = []reflect.Type{ + reflect.TypeOf(func(string) (interface{}, bool) { return nil, false }), + reflect.TypeOf(ParamEvaluator(func(string) (interface{}, bool) { return nil, false })), +} + +func goodParamFunc(typ reflect.Type) bool { + if typ.Kind() == reflect.Func { // it should be a func which returns a func (see below check). + if typ.NumOut() == 1 { + typOut := typ.Out(0) + if typOut.Kind() != reflect.Func { + return false + } + + if typOut.NumOut() == 2 { // if it's a type of EvaluatorFunc, used for param evaluator. + for _, fType := range goodEvaluatorFuncs { + if typOut == fType { + return true + } + } + return false + } + + if typOut.NumIn() == 1 && typOut.NumOut() == 1 { // if it's a type of func(paramValue [int,string...]) bool, used for param funcs. + return typOut.Out(0).Kind() == reflect.Bool + } + } + } + + return false +} + +// Regexp accepts a regexp "expr" expression +// and returns its MatchString. +// The regexp is compiled before return. // // Returns a not-nil error on regexp compile failure. -func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) { +func Regexp(expr string) (func(string) bool, error) { if expr == "" { return nil, fmt.Errorf("empty regex expression") } @@ -37,36 +74,16 @@ func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) { return r.MatchString, nil } -// MustNewEvaluatorFromRegexp same as NewEvaluatorFromRegexp +// MustRegexp same as Regexp // but it panics on the "expr" parse failure. -func MustNewEvaluatorFromRegexp(expr string) EvaluatorFunc { - r, err := NewEvaluatorFromRegexp(expr) +func MustRegexp(expr string) func(string) bool { + r, err := Regexp(expr) if err != nil { panic(err) } return r } -var ( - goodParamFuncReturnType = reflect.TypeOf(func(string) bool { return false }) - goodParamFuncReturnType2 = reflect.TypeOf(EvaluatorFunc(func(string) bool { return false })) -) - -func goodParamFunc(typ reflect.Type) bool { - // should be a func - // which returns a func(string) bool - if typ.Kind() == reflect.Func { - if typ.NumOut() == 1 { - typOut := typ.Out(0) - if typOut == goodParamFuncReturnType || typOut == goodParamFuncReturnType2 { - return true - } - } - } - - return false -} - // goodParamFuncName reports whether the function name is a valid identifier. func goodParamFuncName(name string) bool { if name == "" { @@ -85,7 +102,7 @@ func goodParamFuncName(name string) bool { // the convertBuilderFunc return value is generating at boot time. // convertFunc converts an interface to a valid full param function. -func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { +func convertBuilderFunc(fn interface{}) ParamFuncBuilder { typFn := reflect.TypeOf(fn) if !goodParamFunc(typFn) { @@ -94,7 +111,7 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { numFields := typFn.NumIn() - return func(args []string) EvaluatorFunc { + return func(args []string) reflect.Value { if len(args) != numFields { // no variadics support, for now. panic("args should be the same len as numFields") @@ -179,24 +196,25 @@ func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { argValue := reflect.ValueOf(val) if expected, got := field.Kind(), argValue.Kind(); expected != got { - panic(fmt.Sprintf("fields should have the same type: [%d] expected %s but got %s", i, expected, got)) + panic(fmt.Sprintf("func's input arguments should have the same type: [%d] expected %s but got %s", i, expected, got)) } argValues = append(argValues, argValue) } - evalFn := reflect.ValueOf(fn).Call(argValues)[0].Interface() - - var evaluator EvaluatorFunc - // check for typed and not typed - if _v, ok := evalFn.(EvaluatorFunc); ok { - evaluator = _v - } else if _v, ok = evalFn.(func(string) bool); ok { - evaluator = _v - } - return func(paramValue string) bool { - return evaluator(paramValue) - } + evalFn := reflect.ValueOf(fn).Call(argValues)[0] + + // var evaluator EvaluatorFunc + // // check for typed and not typed + // if _v, ok := evalFn.(EvaluatorFunc); ok { + // evaluator = _v + // } else if _v, ok = evalFn.(func(string) bool); ok { + // evaluator = _v + // } + // return func(paramValue interface{}) bool { + // return evaluator(paramValue) + // } + return evalFn } } @@ -218,16 +236,16 @@ type ( master bool trailing bool - Evaluator EvaluatorFunc + Evaluator ParamEvaluator funcs []ParamFunc } - // ParamEvaluatorBuilder is a func + // ParamFuncBuilder is a func // which accepts a param function's arguments (values) - // and returns an EvaluatorFunc, its job + // and returns a function as value, its job // is to make the macros to be registered // by user at the most generic possible way. - ParamEvaluatorBuilder func([]string) EvaluatorFunc + ParamFuncBuilder func([]string) reflect.Value // the func // ParamFunc represents the parsed // parameter function, it holds @@ -236,13 +254,13 @@ type ( // the evaluator func. ParamFunc struct { Name string - Func ParamEvaluatorBuilder + Func ParamFuncBuilder } ) // NewMacro creates and returns a Macro that can be used as a registry for // a new customized parameter type and its functions. -func NewMacro(indent, alias string, master, trailing bool, evaluator EvaluatorFunc) *Macro { +func NewMacro(indent, alias string, master, trailing bool, evaluator ParamEvaluator) *Macro { return &Macro{ indent: indent, alias: alias, @@ -287,7 +305,7 @@ func (m *Macro) RegisterFunc(funcName string, fn interface{}) *Macro { return m } -func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { +func (m *Macro) registerFunc(funcName string, fullFn ParamFuncBuilder) { if !goodParamFuncName(funcName) { return } @@ -305,7 +323,7 @@ func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { }) } -func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder { +func (m *Macro) getFunc(funcName string) ParamFuncBuilder { for _, fn := range m.funcs { if fn.Name == funcName { if fn.Func == nil { diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go index 20a2973215..020f4d7485 100644 --- a/core/router/macro/macro_test.go +++ b/core/router/macro/macro_test.go @@ -2,6 +2,7 @@ package macro import ( "reflect" + "strconv" "testing" ) @@ -64,9 +65,25 @@ func TestGoodParamFuncName(t *testing.T) { } } -func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, pass bool, i int) { - if got := macroEvaluator.Evaluator(input); pass != got { - t.Fatalf("%s - tests[%d] - expecting %v but got %v", t.Name(), i, pass, got) +func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, expectedType reflect.Kind, pass bool, i int) { + if macroEvaluator.Evaluator == nil && pass { + return // if not evaluator defined then it should allow everything. + } + value, passed := macroEvaluator.Evaluator(input) + if pass != passed { + t.Fatalf("%s - tests[%d] - expecting[pass] %v but got %v", t.Name(), i, pass, passed) + } + + if !passed { + return + } + + if value == nil && expectedType != reflect.Invalid { + t.Fatalf("%s - tests[%d] - expecting[value] to not be nil", t.Name(), i) + } + + if v := reflect.ValueOf(value); v.Kind() != expectedType { + t.Fatalf("%s - tests[%d] - expecting[value.Kind] %v but got %v", t.Name(), i, expectedType, v.Kind()) } } @@ -84,30 +101,32 @@ func TestStringEvaluatorRaw(t *testing.T) { } // 0 for i, tt := range tests { - testEvaluatorRaw(t, String, tt.input, tt.pass, i) + testEvaluatorRaw(t, String, tt.input, reflect.String, tt.pass, i) } } -func TestNumberEvaluatorRaw(t *testing.T) { +func TestIntEvaluatorRaw(t *testing.T) { + x64 := strconv.IntSize == 64 + tests := []struct { pass bool input string }{ - {false, "astring"}, // 0 - {false, "astringwith_numb3rS_and_symbol$"}, // 1 - {true, "32321"}, // 2 - {true, "18446744073709551615"}, // 3 - {true, "-18446744073709551615"}, // 4 - {true, "-18446744073709553213213213213213121615"}, // 5 - {false, "42 18446744073709551615"}, // 6 - {false, "--42"}, // 7 - {false, "+42"}, // 8 - {false, "main.css"}, // 9 - {false, "/assets/main.css"}, // 10 + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {x64, "9223372036854775807" /*max int64*/}, // 3 + {x64, "-9223372036854775808" /*min int64 */}, // 4 + {false, "-18446744073709553213213213213213121615"}, // 5 + {false, "42 18446744073709551615"}, // 6 + {false, "--42"}, // 7 + {false, "+42"}, // 8 + {false, "main.css"}, // 9 + {false, "/assets/main.css"}, // 10 } for i, tt := range tests { - testEvaluatorRaw(t, Number, tt.input, tt.pass, i) + testEvaluatorRaw(t, Int, tt.input, reflect.Int, tt.pass, i) } } @@ -132,7 +151,7 @@ func TestInt64EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Int64, tt.input, tt.pass, i) + testEvaluatorRaw(t, Int64, tt.input, reflect.Int64, tt.pass, i) } } @@ -161,7 +180,7 @@ func TestUint8EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Uint8, tt.input, tt.pass, i) + testEvaluatorRaw(t, Uint8, tt.input, reflect.Uint8, tt.pass, i) } } @@ -186,7 +205,7 @@ func TestUint64EvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Uint64, tt.input, tt.pass, i) + testEvaluatorRaw(t, Uint64, tt.input, reflect.Uint64, tt.pass, i) } } @@ -203,7 +222,7 @@ func TestAlphabeticalEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, Alphabetical, tt.input, tt.pass, i) + testEvaluatorRaw(t, Alphabetical, tt.input, reflect.String, tt.pass, i) } } @@ -220,7 +239,7 @@ func TestFileEvaluatorRaw(t *testing.T) { } for i, tt := range tests { - testEvaluatorRaw(t, File, tt.input, tt.pass, i) + testEvaluatorRaw(t, File, tt.input, reflect.String, tt.pass, i) } } @@ -238,7 +257,7 @@ func TestPathEvaluatorRaw(t *testing.T) { } for i, tt := range pathTests { - testEvaluatorRaw(t, Path, tt.input, tt.pass, i) + testEvaluatorRaw(t, Path, tt.input, reflect.String, tt.pass, i) } } @@ -270,8 +289,7 @@ func TestConvertBuilderFunc(t *testing.T) { } evalFunc := convertBuilderFunc(fn) - - if !evalFunc([]string{"1", "[name1,name2]"})("ok") { + if !evalFunc([]string{"1", "[name1,name2]"}).Call([]reflect.Value{reflect.ValueOf("ok")})[0].Interface().(bool) { t.Fatalf("failed, it should fail already") } } diff --git a/core/router/macro/macros.go b/core/router/macro/macros.go index 1a520af2b6..756ef9dd11 100644 --- a/core/router/macro/macros.go +++ b/core/router/macro/macros.go @@ -10,233 +10,205 @@ import ( var ( // String type // Allows anything (single path segment, as everything except the `Path`). - String = NewMacro("string", "", true, false, func(string) bool { return true }). - RegisterFunc("regexp", func(expr string) EvaluatorFunc { - return MustNewEvaluatorFromRegexp(expr) + String = NewMacro("string", "", true, false, nil). // if nil allows everything. + RegisterFunc("regexp", func(expr string) func(string) bool { + return MustRegexp(expr) }). // checks if param value starts with the 'prefix' arg - RegisterFunc("prefix", func(prefix string) EvaluatorFunc { + RegisterFunc("prefix", func(prefix string) func(string) bool { return func(paramValue string) bool { return strings.HasPrefix(paramValue, prefix) } }). // checks if param value ends with the 'suffix' arg - RegisterFunc("suffix", func(suffix string) EvaluatorFunc { + RegisterFunc("suffix", func(suffix string) func(string) bool { return func(paramValue string) bool { return strings.HasSuffix(paramValue, suffix) } }). // checks if param value contains the 's' arg - RegisterFunc("contains", func(s string) EvaluatorFunc { + RegisterFunc("contains", func(s string) func(string) bool { return func(paramValue string) bool { return strings.Contains(paramValue, s) } }). // checks if param value's length is at least 'min' - RegisterFunc("min", func(min int) EvaluatorFunc { + RegisterFunc("min", func(min int) func(string) bool { return func(paramValue string) bool { return len(paramValue) >= min } }). // checks if param value's length is not bigger than 'max' - RegisterFunc("max", func(max int) EvaluatorFunc { + RegisterFunc("max", func(max int) func(string) bool { return func(paramValue string) bool { return max >= len(paramValue) } }) - simpleNumberEvalutator = MustNewEvaluatorFromRegexp("^-?[0-9]+$") - // Number or int type - // both positive and negative numbers, any number of digits. - Number = NewMacro("number", "int", false, false, simpleNumberEvalutator). + simpleNumberEval = MustRegexp("^-?[0-9]+$") + // Int or int type + // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch. + Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + v, err := strconv.Atoi(paramValue) + if err != nil { + return nil, false + } + + return v, true + }). // checks if the param value's int representation is // bigger or equal than 'min' - RegisterFunc("min", func(min int) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= min + RegisterFunc("min", func(min int) func(int) bool { + return func(paramValue int) bool { + return paramValue >= min } }). // checks if the param value's int representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max int) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n <= max + RegisterFunc("max", func(max int) func(int) bool { + return func(paramValue int) bool { + return paramValue <= max } }). // checks if the param value's int representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max int) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true + RegisterFunc("range", func(min, max int) func(int) bool { + return func(paramValue int) bool { + return !(paramValue < min || paramValue > max) } }) // Int64 as int64 type // -9223372036854775808 to 9223372036854775807. - Int64 = NewMacro("int64", "long", false, false, func(paramValue string) bool { - if !simpleNumberEvalutator(paramValue) { - return false + Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false } - _, err := strconv.ParseInt(paramValue, 10, 64) - // if err == strconv.ErrRange... - return err == nil + v, err := strconv.ParseInt(paramValue, 10, 64) + if err != nil { // if err == strconv.ErrRange... + return nil, false + } + return v, true }). // checks if the param value's int64 representation is // bigger or equal than 'min'. - RegisterFunc("min", func(min int64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - return n >= min + RegisterFunc("min", func(min int64) func(int64) bool { + return func(paramValue int64) bool { + return paramValue >= min } }). // checks if the param value's int64 representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max int64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - return n <= max + RegisterFunc("max", func(max int64) func(int64) bool { + return func(paramValue int64) bool { + return paramValue <= max } }). // checks if the param value's int64 representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max int64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseInt(paramValue, 10, 64) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true + RegisterFunc("range", func(min, max int64) func(int64) bool { + return func(paramValue int64) bool { + return !(paramValue < min || paramValue > max) } }) + uint8Eval = MustRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") // Uint8 as uint8 type // 0 to 255. - Uint8 = NewMacro("uint8", "", false, false, MustNewEvaluatorFromRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")). + Uint8 = NewMacro("uint8", "", false, false, func(paramValue string) (interface{}, bool) { + if !uint8Eval(paramValue) { + return nil, false + } + + v, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return nil, false + } + return uint8(v), true + }). // checks if the param value's uint8 representation is // bigger or equal than 'min'. - RegisterFunc("min", func(min uint8) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - - return uint8(n) >= min + RegisterFunc("min", func(min uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return paramValue >= min } }). // checks if the param value's uint8 representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max uint8) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - return uint8(n) <= max + RegisterFunc("max", func(max uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return paramValue <= max } }). // checks if the param value's uint8 representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max uint8) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 8) - if err != nil { - return false - } - - if v := uint8(n); v < min || v > max { - return false - } - return true + RegisterFunc("range", func(min, max uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return !(paramValue < min || paramValue > max) } }) // Uint64 as uint64 type // 0 to 18446744073709551615. - Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) bool { - if !simpleNumberEvalutator(paramValue) { - return false + Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + v, err := strconv.ParseUint(paramValue, 10, 64) + if err != nil { + return nil, false } - _, err := strconv.ParseUint(paramValue, 10, 64) - return err == nil + return v, true }). // checks if the param value's uint64 representation is // bigger or equal than 'min'. - RegisterFunc("min", func(min uint64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n >= min + RegisterFunc("min", func(min uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return paramValue >= min } }). // checks if the param value's uint64 representation is // smaller or equal than 'max'. - RegisterFunc("max", func(max uint64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - return n <= max + RegisterFunc("max", func(max uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return paramValue <= max } }). // checks if the param value's uint64 representation is // between min and max, including 'min' and 'max'. - RegisterFunc("range", func(min, max uint64) EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.ParseUint(paramValue, 10, 64) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true + RegisterFunc("range", func(min, max uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return !(paramValue < min || paramValue > max) } }) // Bool or boolean as bool type // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" // or "0" or "f" or "F" or "FALSE" or "false" or "False". - Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) bool { + Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) (interface{}, bool) { // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$ // in this case. - _, err := strconv.ParseBool(paramValue) - return err == nil + v, err := strconv.ParseBool(paramValue) + if err != nil { + return nil, false + } + return v, true }) + alphabeticalEval = MustRegexp("^[a-zA-Z ]+$") // Alphabetical letter type // letters only (upper or lowercase) - Alphabetical = NewMacro("alphabetical", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")) + Alphabetical = NewMacro("alphabetical", "", false, false, func(paramValue string) (interface{}, bool) { + if !alphabeticalEval(paramValue) { + return nil, false + } + return paramValue, true + }) + + fileEval = MustRegexp("^[a-zA-Z0-9_.-]*$") // File type // letters (upper or lowercase) // numbers (0-9) @@ -244,7 +216,12 @@ var ( // dash (-) // point (.) // no spaces! or other character - File = NewMacro("file", "", false, false, MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")) + File = NewMacro("file", "", false, false, func(paramValue string) (interface{}, bool) { + if !fileEval(paramValue) { + return nil, false + } + return paramValue, true + }) // Path type // anything, should be the last part // @@ -252,11 +229,11 @@ var ( // types because I want to give the opportunity to the user // to organise the macro functions based on wildcard or single dynamic named path parameter. // Should be living in the latest path segment of a route path. - Path = NewMacro("path", "", false, true, func(string) bool { return true }) + Path = NewMacro("path", "", false, true, nil) Defaults = &Macros{ String, - Number, + Int, Int64, Uint8, Uint64, @@ -268,7 +245,7 @@ var ( type Macros []*Macro -func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator EvaluatorFunc) *Macro { +func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator ParamEvaluator) *Macro { macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator) if ms.register(macro) { return macro diff --git a/core/router/macro/template.go b/core/router/macro/template.go index 3b30d2f79e..9491b6df80 100644 --- a/core/router/macro/template.go +++ b/core/router/macro/template.go @@ -1,6 +1,8 @@ package macro import ( + "reflect" + "github.com/kataras/iris/core/router/macro/interpreter/ast" "github.com/kataras/iris/core/router/macro/interpreter/parser" ) @@ -27,8 +29,8 @@ type TemplateParam struct { Name string `json:"name"` Index int `json:"index"` ErrCode int `json:"errCode"` - TypeEvaluator EvaluatorFunc `json:"-"` - Funcs []EvaluatorFunc `json:"-"` + TypeEvaluator ParamEvaluator `json:"-"` + Funcs []reflect.Value `json:"-"` } // Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions) @@ -74,7 +76,7 @@ func Parse(src string, macros Macros) (*Template, error) { } evalFn := tmplFn(paramfn.Args) - if evalFn == nil { + if evalFn.IsNil() || !evalFn.IsValid() || evalFn.Kind() != reflect.Func { continue } tmplParam.Funcs = append(tmplParam.Funcs, evalFn) diff --git a/hero/handler.go b/hero/handler.go index 6644dc83cb..f0b4704408 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -6,15 +6,13 @@ import ( "runtime" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/memstore" "github.com/kataras/iris/hero/di" "github.com/kataras/golog" ) var ( - contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() - memstoreTyp = reflect.TypeOf(memstore.Store{}) + contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() ) // IsContext returns true if the "inTyp" is a type of Context. @@ -22,14 +20,6 @@ func IsContext(inTyp reflect.Type) bool { return inTyp.Implements(contextTyp) } -// IsExpectingStore returns true if the "inTyp" is a type of memstore.Store. -func IsExpectingStore(inTyp reflect.Type) bool { - print("di/handler.go: " + inTyp.String() + " vs " + memstoreTyp.String() + " : ") - println(inTyp == memstoreTyp) - - return inTyp == memstoreTyp -} - // checks if "handler" is context.Handler: func(context.Context). func isContextHandler(handler interface{}) (context.Handler, bool) { h, is := handler.(context.Handler) From e4010bac180aed9685f10f8045a106eb0d87ea69 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 27 Sep 2018 06:20:03 +0300 Subject: [PATCH 17/91] add int8, int16, int32, uint, uint16 and uint32 default-builtn parameter types and macros - no doc update - no live tests yet - time for sleep --- context/request_params.go | 47 +++++++-- core/router/macro.go | 3 +- core/router/macro/macro.go | 6 +- core/router/macro/macros.go | 183 +++++++++++++++++++++++++++++++++++- 4 files changed, 222 insertions(+), 17 deletions(-) diff --git a/context/request_params.go b/context/request_params.go index b8426856e2..9e45c3635e 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -91,32 +91,59 @@ var ( }, reflect.Int: func(paramIndex int) interface{} { return func(ctx Context) int { - v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0) - return v + // v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0) + // return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int) + } + }, + reflect.Int8: func(paramIndex int) interface{} { + return func(ctx Context) int8 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int8) + } + }, + reflect.Int16: func(paramIndex int) interface{} { + return func(ctx Context) int16 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int16) + } + }, + reflect.Int32: func(paramIndex int) interface{} { + return func(ctx Context) int32 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int32) } }, reflect.Int64: func(paramIndex int) interface{} { return func(ctx Context) int64 { - v, _ := ctx.Params().GetEntryAt(paramIndex).Int64Default(0) - return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int64) + } + }, + reflect.Uint: func(paramIndex int) interface{} { + return func(ctx Context) uint { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint) } }, reflect.Uint8: func(paramIndex int) interface{} { return func(ctx Context) uint8 { - v, _ := ctx.Params().GetEntryAt(paramIndex).Uint8Default(0) - return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint8) + } + }, + reflect.Uint16: func(paramIndex int) interface{} { + return func(ctx Context) uint16 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint16) + } + }, + reflect.Uint32: func(paramIndex int) interface{} { + return func(ctx Context) uint32 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) } }, reflect.Uint64: func(paramIndex int) interface{} { return func(ctx Context) uint64 { - v, _ := ctx.Params().GetEntryAt(paramIndex).Uint64Default(0) - return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint64) } }, reflect.Bool: func(paramIndex int) interface{} { return func(ctx Context) bool { - v, _ := ctx.Params().GetEntryAt(paramIndex).BoolDefault(false) - return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(bool) } }, } diff --git a/core/router/macro.go b/core/router/macro.go index 2af58d3425..b17941a6d9 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -83,7 +83,6 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { return func(tmpl macro.Template) context.Handler { return func(ctx context.Context) { for _, p := range tmpl.Params { - paramValue := ctx.Params().Get(p.Name) if p.TypeEvaluator == nil { // allow. ctx.Next() @@ -91,7 +90,7 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { } // first, check for type evaluator. - newValue, passed := p.TypeEvaluator(paramValue) + newValue, passed := p.TypeEvaluator(ctx.Parms().Get(p.Name)) if !passed { ctx.StatusCode(p.ErrCode) ctx.StopExecution() diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go index a589e60a2a..2e461ad2c9 100644 --- a/core/router/macro/macro.go +++ b/core/router/macro/macro.go @@ -154,6 +154,10 @@ func convertBuilderFunc(fn interface{}) ParamFuncBuilder { v, err := strconv.ParseInt(arg, 10, 64) panicIfErr(err) val = v + case reflect.Uint: + v, err := strconv.ParseUint(arg, 10, strconv.IntSize) + panicIfErr(err) + val = uint(v) case reflect.Uint8: v, err := strconv.ParseUint(arg, 10, 8) panicIfErr(err) @@ -245,7 +249,7 @@ type ( // and returns a function as value, its job // is to make the macros to be registered // by user at the most generic possible way. - ParamFuncBuilder func([]string) reflect.Value // the func + ParamFuncBuilder func([]string) reflect.Value // the func() bool // ParamFunc represents the parsed // parameter function, it holds diff --git a/core/router/macro/macros.go b/core/router/macro/macros.go index 756ef9dd11..a629755325 100644 --- a/core/router/macro/macros.go +++ b/core/router/macro/macros.go @@ -46,12 +46,13 @@ var ( }) simpleNumberEval = MustRegexp("^-?[0-9]+$") - // Int or int type + // Int or number type // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch. Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) { if !simpleNumberEval(paramValue) { return nil, false } + v, err := strconv.Atoi(paramValue) if err != nil { return nil, false @@ -81,12 +82,100 @@ var ( } }) + // Int8 type + // -128 to 127. + Int8 = NewMacro("int8", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.ParseInt(paramValue, 10, 8) + if err != nil { + return nil, false + } + return int8(v), true + }). + RegisterFunc("min", func(min int8) func(int8) bool { + return func(paramValue int8) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max int8) func(int8) bool { + return func(paramValue int8) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max int8) func(int8) bool { + return func(paramValue int8) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Int16 type + // -32768 to 32767. + Int16 = NewMacro("int16", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.ParseInt(paramValue, 10, 16) + if err != nil { + return nil, false + } + return int16(v), true + }). + RegisterFunc("min", func(min int16) func(int16) bool { + return func(paramValue int16) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max int16) func(int16) bool { + return func(paramValue int16) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max int16) func(int16) bool { + return func(paramValue int16) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Int32 type + // -2147483648 to 2147483647. + Int32 = NewMacro("int32", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.ParseInt(paramValue, 10, 32) + if err != nil { + return nil, false + } + return int32(v), true + }). + RegisterFunc("min", func(min int32) func(int32) bool { + return func(paramValue int32) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max int32) func(int32) bool { + return func(paramValue int32) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max int32) func(int32) bool { + return func(paramValue int32) bool { + return !(paramValue < min || paramValue > max) + } + }) + // Int64 as int64 type // -9223372036854775808 to 9223372036854775807. Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) { if !simpleNumberEval(paramValue) { return nil, false } + v, err := strconv.ParseInt(paramValue, 10, 64) if err != nil { // if err == strconv.ErrRange... return nil, false @@ -115,6 +204,39 @@ var ( } }) + // Uint as uint type + // actual value can be min-max uint64 or min-max uint32 depends on the arch. + // if x64: 0 to 18446744073709551615 + // if x32: 0 to 4294967295 and etc. + Uint = NewMacro("uint", "", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, strconv.IntSize) // 32,64... + if err != nil { + return nil, false + } + return uint(v), true + }). + // checks if the param value's int representation is + // bigger or equal than 'min' + RegisterFunc("min", func(min uint) func(uint) bool { + return func(paramValue uint) bool { + return paramValue >= min + } + }). + // checks if the param value's int representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max uint) func(uint) bool { + return func(paramValue uint) bool { + return paramValue <= max + } + }). + // checks if the param value's int representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max uint) func(uint) bool { + return func(paramValue uint) bool { + return !(paramValue < min || paramValue > max) + } + }) + uint8Eval = MustRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") // Uint8 as uint8 type // 0 to 255. @@ -151,12 +273,59 @@ var ( } }) + // Uint16 as uint16 type + // 0 to 65535. + Uint16 = NewMacro("uint16", "", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, 16) + if err != nil { + return nil, false + } + return uint16(v), true + }). + RegisterFunc("min", func(min uint16) func(uint16) bool { + return func(paramValue uint16) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max uint16) func(uint16) bool { + return func(paramValue uint16) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max uint16) func(uint16) bool { + return func(paramValue uint16) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Uint32 as uint32 type + // 0 to 4294967295. + Uint32 = NewMacro("uint32", "", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, 32) + if err != nil { + return nil, false + } + return uint32(v), true + }). + RegisterFunc("min", func(min uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return !(paramValue < min || paramValue > max) + } + }) + // Uint64 as uint64 type // 0 to 18446744073709551615. Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) { - if !simpleNumberEval(paramValue) { - return nil, false - } v, err := strconv.ParseUint(paramValue, 10, 64) if err != nil { return nil, false @@ -234,8 +403,14 @@ var ( Defaults = &Macros{ String, Int, + Int8, + Int16, + Int32, Int64, + Uint, Uint8, + Uint16, + Uint32, Uint64, Bool, Alphabetical, From fc9090d9162787fd831926922054ebf4ccbc4d04 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 27 Sep 2018 06:28:47 +0300 Subject: [PATCH 18/91] see previous commit for more details --- core/router/macro.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router/macro.go b/core/router/macro.go index b17941a6d9..0a84f67f0b 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -90,7 +90,7 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { } // first, check for type evaluator. - newValue, passed := p.TypeEvaluator(ctx.Parms().Get(p.Name)) + newValue, passed := p.TypeEvaluator(ctx.Params().Get(p.Name)) if !passed { ctx.StatusCode(p.ErrCode) ctx.StopExecution() From 7273623dff784422d863d201fbb3f68afcb8a312 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 27 Sep 2018 21:31:07 +0300 Subject: [PATCH 19/91] easy fix of macro handler caused tests to fail by before prev commit --- _examples/routing/macros/main.go | 56 ++++++++++++++++---------------- core/router/macro.go | 3 +- mvc/controller_test.go | 6 ++-- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index 8548da88ec..1afaf005b2 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -3,7 +3,6 @@ package main import ( "fmt" "reflect" - "strconv" "github.com/kataras/iris" "github.com/kataras/iris/context" @@ -37,39 +36,40 @@ func main() { ^ Done 27 sep 2018. */ - app.Macros().Register("uint32", "small", false, false, func(paramValue string) (interface{}, bool) { - v, err := strconv.ParseUint(paramValue, 10, 32) - return uint32(v), err == nil - }). - RegisterFunc("min", func(min uint32) func(uint32) bool { - return func(paramValue uint32) bool { - return paramValue >= min - } - }) + // app.Macros().Register("uint32", "small", false, false, func(paramValue string) (interface{}, bool) { + // v, err := strconv.ParseUint(paramValue, 10, 32) + // return uint32(v), err == nil + // }). + // RegisterFunc("min", func(min uint32) func(uint32) bool { + // return func(paramValue uint32) bool { + // return paramValue >= min + // } + // }) - // optionally, only when mvc or hero features are used for this custom macro/parameter type. - context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { - /* both works but second is faster, we omit the duplication of the type conversion over and over as of 27 Sep of 2018 (this patch)*/ - // return func(ctx context.Context) uint32 { - // param := ctx.Params().GetEntryAt(paramIndex) - // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) - // return uint32(paramValueAsUint32) - // } - return func(ctx context.Context) uint32 { - return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) - } /* TODO: find a way to automative it based on the macro's first return value type, if thats the case then we must not return nil even if not found, - we must return a value i.e 0 for int for its interface{} */ - } - // + // // optionally, only when mvc or hero features are used for this custom macro/parameter type. + // context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { + // /* both works but second is faster, we omit the duplication of the type conversion over and over as of 27 Sep of 2018 (this patch)*/ + // // return func(ctx context.Context) uint32 { + // // param := ctx.Params().GetEntryAt(paramIndex) + // // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) + // // return uint32(paramValueAsUint32) + // // } + // return func(ctx context.Context) uint32 { + // return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) + // } /* TODO: find a way to automative it based on the macro's first return value type, if thats the case then we must not return nil even if not found, + // we must return a value i.e 0 for int for its interface{} */ + // } + // // - app.Get("/test_uint32/{myparam:uint32 min(10)}", hero.Handler(func(paramValue uint32) string { - return fmt.Sprintf("Value of the parameter is: %d\n", paramValue) + app.Get("/test_uint32/{myparam1:string}/{myparam2:uint32 min(10)}", hero.Handler(func(myparam1 string, myparam2 uint32) string { + return fmt.Sprintf("Value of the parameters are: %s:%d\n", myparam1, myparam2) })) - app.Get("test_uint64/{myparam:uint64 min(5)}", func(ctx context.Context) { + app.Get("test_uint64/{myparam1:string}/{myparam2:uint64}", func(ctx context.Context) { // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) // but better and faster because the macro converts the string to uint64 automatically: - ctx.Writef("Value of the parameter is: %d\n", ctx.Params().GetUint64Default("myparam", 0)) + println("type of myparam2 (should be uint64) is: " + reflect.ValueOf(ctx.Params().GetEntry("myparam2").ValueRaw).Kind().String()) + ctx.Writef("Value of the parameters are: %s:%d\n", ctx.Params().Get("myparam1"), ctx.Params().GetUint64Default("myparam2", 0)) }) app.Run(iris.Addr(":8080")) diff --git a/core/router/macro.go b/core/router/macro.go index 0a84f67f0b..dfbf39be48 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -85,8 +85,7 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { for _, p := range tmpl.Params { if p.TypeEvaluator == nil { // allow. - ctx.Next() - return + continue } // first, check for type evaluator. diff --git a/mvc/controller_test.go b/mvc/controller_test.go index f367d0ea07..7103c9e944 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -377,8 +377,10 @@ func (c *testControllerRelPathFromFunc) GetAdminLogin() {} func (c *testControllerRelPathFromFunc) PutSomethingIntoThis() {} -func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {} -func (c *testControllerRelPathFromFunc) GetSomethingByBy(string, int) {} +func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {} + +func (c *testControllerRelPathFromFunc) GetSomethingByBy(string, int) {} + func (c *testControllerRelPathFromFunc) GetSomethingNewBy(string, int) {} // two input arguments, one By which is the latest word. func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} // two input arguments From 7becb521cc3dd1fc8483eaab11d7d4f4774b4199 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Fri, 28 Sep 2018 05:34:35 +0300 Subject: [PATCH 20/91] make macros even faster and smart catch common :string and do not execute anything at all if not really needed, more clean code as well --- _examples/routing/macros/main.go | 18 ++++++++++ core/router/macro.go | 44 ++++++------------------ core/router/macro/macros.go | 2 +- core/router/macro/template.go | 58 +++++++++++++++++++++++++++++++- 4 files changed, 87 insertions(+), 35 deletions(-) diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index 1afaf005b2..a1dd0d77dc 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -65,6 +65,24 @@ func main() { return fmt.Sprintf("Value of the parameters are: %s:%d\n", myparam1, myparam2) })) + app.Get("/test_string/{myparam1}/{myparam2 prefix(a)}", func(ctx context.Context) { + var ( + myparam1 = ctx.Params().Get("myparam1") + myparam2 = ctx.Params().Get("myparam2") + ) + + ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) + }) + + app.Get("/test_string2/{myparam1}/{myparam2}", func(ctx context.Context) { + var ( + myparam1 = ctx.Params().Get("myparam1") + myparam2 = ctx.Params().Get("myparam2") + ) + + ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) + }) + app.Get("test_uint64/{myparam1:string}/{myparam2:uint64}", func(ctx context.Context) { // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) // but better and faster because the macro converts the string to uint64 automatically: diff --git a/core/router/macro.go b/core/router/macro.go index dfbf39be48..075496400b 100644 --- a/core/router/macro.go +++ b/core/router/macro.go @@ -2,8 +2,6 @@ package router import ( "fmt" - "net/http" - "reflect" "strings" "github.com/kataras/iris/context" @@ -61,21 +59,20 @@ func convertTmplToNodePath(tmpl *macro.Template) (string, error) { // Note: returns nil if not needed, the caller(router) should check for that before adding that on route's Middleware. func convertTmplToHandler(tmpl *macro.Template) context.Handler { - - needMacroHandler := false - // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) // 2. if we don't have any named params then we don't need a handler too. + + needsMacroHandler := false for _, p := range tmpl.Params { - if len(p.Funcs) == 0 && (ast.IsMaster(p.Type) || ast.IsTrailing(p.Type)) && p.ErrCode == http.StatusNotFound { - } else { - // println("we need handler for: " + tmpl.Src) - needMacroHandler = true + if p.CanEval() { + // if at least one needs it, then create the handler. + needsMacroHandler = true + break } } - if !needMacroHandler { + if !needsMacroHandler { // println("we don't need handler for: " + tmpl.Src) return nil } @@ -83,38 +80,19 @@ func convertTmplToHandler(tmpl *macro.Template) context.Handler { return func(tmpl macro.Template) context.Handler { return func(ctx context.Context) { for _, p := range tmpl.Params { - if p.TypeEvaluator == nil { - // allow. - continue + if !p.CanEval() { + // println(p.Src + " no need to evaluate anything") + continue // allow. } - // first, check for type evaluator. - newValue, passed := p.TypeEvaluator(ctx.Params().Get(p.Name)) - if !passed { + if !p.Eval(ctx.Params().Get(p.Name), ctx.Params().Set) { ctx.StatusCode(p.ErrCode) ctx.StopExecution() return } - - if len(p.Funcs) > 0 { - paramIn := []reflect.Value{reflect.ValueOf(newValue)} - // then check for all of its functions - for _, evalFunc := range p.Funcs { - // or make it as func(interface{}) bool and pass directly the "newValue" - // but that would not be as easy for end-developer, so keep that "slower": - if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool - ctx.StatusCode(p.ErrCode) - ctx.StopExecution() - return - } - } - } - - ctx.Params().Store.Set(p.Name, newValue) } // if all passed, just continue. ctx.Next() } }(*tmpl) - } diff --git a/core/router/macro/macros.go b/core/router/macro/macros.go index a629755325..6832ee92c0 100644 --- a/core/router/macro/macros.go +++ b/core/router/macro/macros.go @@ -10,7 +10,7 @@ import ( var ( // String type // Allows anything (single path segment, as everything except the `Path`). - String = NewMacro("string", "", true, false, nil). // if nil allows everything. + String = NewMacro("string", "", true, false, nil). RegisterFunc("regexp", func(expr string) func(string) bool { return MustRegexp(expr) }). diff --git a/core/router/macro/template.go b/core/router/macro/template.go index 9491b6df80..983427a813 100644 --- a/core/router/macro/template.go +++ b/core/router/macro/template.go @@ -1,6 +1,7 @@ package macro import ( + "github.com/kataras/iris/core/memstore" "reflect" "github.com/kataras/iris/core/router/macro/interpreter/ast" @@ -31,6 +32,61 @@ type TemplateParam struct { ErrCode int `json:"errCode"` TypeEvaluator ParamEvaluator `json:"-"` Funcs []reflect.Value `json:"-"` + + stringInFuncs []func(string) bool + canEval bool +} + +func (p TemplateParam) preComputed() TemplateParam { + for _, pfn := range p.Funcs { + if fn, ok := pfn.Interface().(func(string) bool); ok { + p.stringInFuncs = append(p.stringInFuncs, fn) + } + } + + // if true then it should be execute the type parameter or its functions + // else it can be ignored, + // i.e {myparam} or {myparam:string} or {myparam:path} -> + // their type evaluator is nil because they don't do any checks and they don't change + // the default parameter value's type (string) so no need for any work). + p.canEval = p.TypeEvaluator != nil || len(p.Funcs) > 0 || p.ErrCode != parser.DefaultParamErrorCode + + return p +} + +func (p *TemplateParam) CanEval() bool { + return p.canEval +} + +// paramChanger is the same form of context's Params().Set +func (p *TemplateParam) Eval(paramValue string, paramChanger func(key string, newValue interface{}) (memstore.Entry, bool)) bool { + if p.TypeEvaluator == nil { + for _, fn := range p.stringInFuncs { + if !fn(paramValue) { + return false + } + } + return true + } + + newValue, passed := p.TypeEvaluator(paramValue) + if !passed { + return false + } + + if len(p.Funcs) > 0 { + paramIn := []reflect.Value{reflect.ValueOf(newValue)} + for _, evalFunc := range p.Funcs { + // or make it as func(interface{}) bool and pass directly the "newValue" + // but that would not be as easy for end-developer, so keep that "slower": + if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool + return false + } + } + } + + paramChanger(p.Name, newValue) + return true } // Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions) @@ -82,7 +138,7 @@ func Parse(src string, macros Macros) (*Template, error) { tmplParam.Funcs = append(tmplParam.Funcs, evalFn) } - t.Params = append(t.Params, tmplParam) + t.Params = append(t.Params, tmplParam.preComputed()) } return t, nil From fa88cdeff27e1047b1e009fff3d708a067372dc2 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 29 Sep 2018 02:41:51 +0300 Subject: [PATCH 21/91] partial cleanup of the macro pkg and move it from /core/router to the root because it may be used by the end-developers now to ammend the available macros per application --- HISTORY.md | 2 +- HISTORY_ID.md | 2 +- HISTORY_ZH.md | 2 +- _examples/routing/macros/main.go | 2 +- context/route.go | 2 +- core/memstore/memstore.go | 7 ++ core/router/api_builder.go | 2 +- core/router/macro.go | 98 ------------------- core/router/party.go | 2 +- core/router/path.go | 26 ++++- core/router/route.go | 11 ++- macro/handler/handler.go | 52 ++++++++++ .../macro => macro}/interpreter/ast/ast.go | 0 .../interpreter/lexer/lexer.go | 2 +- .../interpreter/lexer/lexer_test.go | 2 +- .../interpreter/parser/parser.go | 8 +- .../interpreter/parser/parser_test.go | 2 +- .../interpreter/token/token.go | 0 {core/router/macro => macro}/macro.go | 0 {core/router/macro => macro}/macro_test.go | 0 {core/router/macro => macro}/macros.go | 2 +- {core/router/macro => macro}/template.go | 18 ++-- mvc/controller.go | 2 +- mvc/controller_method_parser.go | 2 +- mvc/param.go | 2 +- 25 files changed, 119 insertions(+), 129 deletions(-) delete mode 100644 core/router/macro.go create mode 100644 macro/handler/handler.go rename {core/router/macro => macro}/interpreter/ast/ast.go (100%) rename {core/router/macro => macro}/interpreter/lexer/lexer.go (98%) rename {core/router/macro => macro}/interpreter/lexer/lexer_test.go (95%) rename {core/router/macro => macro}/interpreter/parser/parser.go (94%) rename {core/router/macro => macro}/interpreter/parser/parser_test.go (99%) rename {core/router/macro => macro}/interpreter/token/token.go (100%) rename {core/router/macro => macro}/macro.go (100%) rename {core/router/macro => macro}/macro_test.go (100%) rename {core/router/macro => macro}/macros.go (99%) rename {core/router/macro => macro}/template.go (88%) diff --git a/HISTORY.md b/HISTORY.md index 5f774844d0..f1c56f5dd7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -294,7 +294,7 @@ For example: at [_examples/mvc/basic/main.go line 100](_examples/mvc/basic/main. - fix `APIBuilder, Party#StaticWeb` and `APIBuilder, Party#StaticEmbedded` wrong strip prefix inside children parties - keep the `iris, core/router#StaticEmbeddedHandler` and remove the `core/router/APIBuilder#StaticEmbeddedHandler`, (note the `Handler` suffix) it's global and has nothing to do with the `Party` or the `APIBuilder` -- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](core/router/macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) +- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) - sync the `golang.org/x/sys/unix` vendor ## The most important diff --git a/HISTORY_ID.md b/HISTORY_ID.md index 001bdab1d0..12a588bc78 100644 --- a/HISTORY_ID.md +++ b/HISTORY_ID.md @@ -83,7 +83,7 @@ For example: at [_examples/mvc/basic/main.go line 100](_examples/mvc/basic/main. - fix `APIBuilder, Party#StaticWeb` and `APIBuilder, Party#StaticEmbedded` wrong strip prefix inside children parties - keep the `iris, core/router#StaticEmbeddedHandler` and remove the `core/router/APIBuilder#StaticEmbeddedHandler`, (note the `Handler` suffix) it's global and has nothing to do with the `Party` or the `APIBuilder` -- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](core/router/macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) +- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) - sync the `golang.org/x/sys/unix` vendor ## The most important diff --git a/HISTORY_ZH.md b/HISTORY_ZH.md index 674ede1a38..653623f6cf 100644 --- a/HISTORY_ZH.md +++ b/HISTORY_ZH.md @@ -79,7 +79,7 @@ This history entry is not translated yet to the Chinese language yet, please ref - 修正 `APIBuilder, Party#StaticWeb` 和 `APIBuilder, Party#StaticEmbedded` 子分组内的前缀错误 - 保留 `iris, core/router#StaticEmbeddedHandler` 并移除 `core/router/APIBuilder#StaticEmbeddedHandler`, (`Handler` 后缀) 这是全局性的,与 `Party` `APIBuilder` 无关。 -- 修正 路径 `{}` 中的路径清理 (我们已经在 [解释器](core/router/macro/interpreter) 级别转义了这些字符, 但是一些符号仍然被更高级别的API构建器删除) , 例如 `\\` 字符串的宏函数正则表达式内容 [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) +- 修正 路径 `{}` 中的路径清理 (我们已经在 [解释器](macro/interpreter) 级别转义了这些字符, 但是一些符号仍然被更高级别的API构建器删除) , 例如 `\\` 字符串的宏函数正则表达式内容 [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) - 同步 `golang.org/x/sys/unix` ## 重要变更 diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index a1dd0d77dc..fe68c5984f 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -83,7 +83,7 @@ func main() { ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) }) - app.Get("test_uint64/{myparam1:string}/{myparam2:uint64}", func(ctx context.Context) { + app.Get("/test_uint64/{myparam1:string}/{myparam2:uint64}", func(ctx context.Context) { // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) // but better and faster because the macro converts the string to uint64 automatically: println("type of myparam2 (should be uint64) is: " + reflect.ValueOf(ctx.Params().GetEntry("myparam2").ValueRaw).Kind().String()) diff --git a/context/route.go b/context/route.go index 9cda8e96c3..7c680e7d95 100644 --- a/context/route.go +++ b/context/route.go @@ -1,6 +1,6 @@ package context -import "github.com/kataras/iris/core/router/macro" +import "github.com/kataras/iris/macro" // RouteReadOnly allows decoupled access to the current route // inside the context. diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 4eda5113f8..c36272d86e 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -15,6 +15,11 @@ import ( ) type ( + // ValueSetter is the interface which can be accepted as a generic solution of RequestParams or memstore when Set is the only requirement, + // i.e internally on macro/template/TemplateParam#Eval:paramChanger. + ValueSetter interface { + Set(key string, newValue interface{}) (Entry, bool) + } // Entry is the entry of the context storage Store - .Values() Entry struct { Key string @@ -26,6 +31,8 @@ type ( Store []Entry ) +var _ ValueSetter = (*Store)(nil) + // GetByKindOrNil will try to get this entry's value of "k" kind, // if value is not that kind it will NOT try to convert it the "k", instead // it will return nil, except if boolean; then it will return false diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 614bddacf3..7f1f363514 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -9,7 +9,7 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" ) const ( diff --git a/core/router/macro.go b/core/router/macro.go deleted file mode 100644 index 075496400b..0000000000 --- a/core/router/macro.go +++ /dev/null @@ -1,98 +0,0 @@ -package router - -import ( - "fmt" - "strings" - - "github.com/kataras/iris/context" - "github.com/kataras/iris/core/router/macro" - "github.com/kataras/iris/core/router/macro/interpreter/ast" -) - -// compileRoutePathAndHandlers receives a route info and returns its parsed/"compiled" path -// and the new handlers (prepend all the macro's handler, if any). -// -// It's not exported for direct use. -func compileRoutePathAndHandlers(handlers context.Handlers, tmpl *macro.Template) (string, context.Handlers, error) { - // parse the path to node's path, now. - path, err := convertTmplToNodePath(tmpl) - if err != nil { - return tmpl.Src, handlers, err - } - // prepend the macro handler to the route, now, - // right before the register to the tree, so routerbuilder.UseGlobal will work as expected. - if len(tmpl.Params) > 0 { - macroEvaluatorHandler := convertTmplToHandler(tmpl) - // may return nil if no really need a macro handler evaluator - if macroEvaluatorHandler != nil { - handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...) - } - } - - return path, handlers, nil -} - -func convertTmplToNodePath(tmpl *macro.Template) (string, error) { - routePath := tmpl.Src - if len(tmpl.Params) > 0 { - if routePath[len(routePath)-1] == '/' { - routePath = routePath[0 : len(routePath)-2] // remove the last "/" if macro syntax instead of underline's - } - } - - // if it has started with {} and it's valid - // then the tmpl.Params will be filled, - // so no any further check needed - for i, p := range tmpl.Params { - if ast.IsTrailing(p.Type) { - if i != len(tmpl.Params)-1 { - return "", fmt.Errorf("parameter type \"%s\" should be putted to the very last of a path", p.Type.Indent()) - } - routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1) - } else { - routePath = strings.Replace(routePath, p.Src, Param(p.Name), 1) - } - } - - return routePath, nil -} - -// Note: returns nil if not needed, the caller(router) should check for that before adding that on route's Middleware. -func convertTmplToHandler(tmpl *macro.Template) context.Handler { - // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. - // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) - // 2. if we don't have any named params then we don't need a handler too. - - needsMacroHandler := false - for _, p := range tmpl.Params { - if p.CanEval() { - // if at least one needs it, then create the handler. - needsMacroHandler = true - break - } - } - - if !needsMacroHandler { - // println("we don't need handler for: " + tmpl.Src) - return nil - } - - return func(tmpl macro.Template) context.Handler { - return func(ctx context.Context) { - for _, p := range tmpl.Params { - if !p.CanEval() { - // println(p.Src + " no need to evaluate anything") - continue // allow. - } - - if !p.Eval(ctx.Params().Get(p.Name), ctx.Params().Set) { - ctx.StatusCode(p.ErrCode) - ctx.StopExecution() - return - } - } - // if all passed, just continue. - ctx.Next() - } - }(*tmpl) -} diff --git a/core/router/party.go b/core/router/party.go index 99e482e10a..5d462392f6 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -3,7 +3,7 @@ package router import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" ) // Party is just a group joiner of routes which have the same prefix and share same middleware(s) also. diff --git a/core/router/path.go b/core/router/path.go index 3a471f9023..9b9d12a9fb 100644 --- a/core/router/path.go +++ b/core/router/path.go @@ -7,7 +7,9 @@ import ( "strings" "github.com/kataras/iris/core/netutil" - "github.com/kataras/iris/core/router/macro/interpreter/lexer" + "github.com/kataras/iris/macro" + "github.com/kataras/iris/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/lexer" ) const ( @@ -31,6 +33,28 @@ func WildcardParam(name string) string { return prefix(name, WildcardParamStart) } +func convertTmplToNodePath(tmpl *macro.Template) string { + routePath := tmpl.Src + if len(tmpl.Params) > 0 { + if routePath[len(routePath)-1] == '/' { + routePath = routePath[0 : len(routePath)-2] // remove the last "/" if macro syntax instead of underline's. + } + } + + // if it has started with {} and it's valid + // then the tmpl.Params will be filled, + // so no any further check needed. + for _, p := range tmpl.Params { + if ast.IsTrailing(p.Type) { + routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1) + } else { + routePath = strings.Replace(routePath, p.Src, Param(p.Name), 1) + } + } + + return routePath +} + func prefix(s string, prefix string) string { if !strings.HasPrefix(s, prefix) { return prefix + s diff --git a/core/router/route.go b/core/router/route.go index 11a69d6ef1..c5c273e1c6 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -5,7 +5,8 @@ import ( "strings" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" + "github.com/kataras/iris/macro/handler" ) // Route contains the information about a registered Route. @@ -46,9 +47,11 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, return nil, err } - path, handlers, err := compileRoutePathAndHandlers(handlers, tmpl) - if err != nil { - return nil, err + path := convertTmplToNodePath(tmpl) + // prepend the macro handler to the route, now, + // right before the register to the tree, so APIBuilder#UseGlobal will work as expected. + if macroEvaluatorHandler, ok := handler.MakeHandler(tmpl); ok { + handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...) } path = cleanPath(path) // maybe unnecessary here but who cares in this moment diff --git a/macro/handler/handler.go b/macro/handler/handler.go new file mode 100644 index 0000000000..24b6367aa7 --- /dev/null +++ b/macro/handler/handler.go @@ -0,0 +1,52 @@ +// Package handler is the highest level module of the macro package which makes use the rest of the macro package, +// it is mainly used, internally, by the router package. +package handler + +import ( + "github.com/kataras/iris/context" + "github.com/kataras/iris/macro" +) + +// MakeHandler creates and returns a handler from a macro template, the handler evaluates each of the parameters if necessary at all. +// If the template does not contain any dynamic attributes and a special handler is NOT required +// then it returns a nil handler and false as its second output value, +// the caller should check those two values before any further action. +func MakeHandler(tmpl *macro.Template) (context.Handler, bool) { + needsMacroHandler := len(tmpl.Params) > 0 + if !needsMacroHandler { + return nil, false + } + + // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. + // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) + // 2. if we don't have any named params then we don't need a handler too. + for _, p := range tmpl.Params { + if p.CanEval() { + // if at least one needs it, then create the handler. + needsMacroHandler = true + break + } + } + + if !needsMacroHandler { + return nil, false + } + + handler := func(ctx context.Context) { + for _, p := range tmpl.Params { + if !p.CanEval() { + continue // allow. + } + + if !p.Eval(ctx.Params().Get(p.Name), ctx.Params()) { + ctx.StatusCode(p.ErrCode) + ctx.StopExecution() + return + } + } + // if all passed, just continue. + ctx.Next() + } + + return handler, true +} diff --git a/core/router/macro/interpreter/ast/ast.go b/macro/interpreter/ast/ast.go similarity index 100% rename from core/router/macro/interpreter/ast/ast.go rename to macro/interpreter/ast/ast.go diff --git a/core/router/macro/interpreter/lexer/lexer.go b/macro/interpreter/lexer/lexer.go similarity index 98% rename from core/router/macro/interpreter/lexer/lexer.go rename to macro/interpreter/lexer/lexer.go index 01646361d4..6a77260252 100644 --- a/core/router/macro/interpreter/lexer/lexer.go +++ b/macro/interpreter/lexer/lexer.go @@ -1,7 +1,7 @@ package lexer import ( - "github.com/kataras/iris/core/router/macro/interpreter/token" + "github.com/kataras/iris/macro/interpreter/token" ) // Lexer helps us to read/scan characters of a source and resolve their token types. diff --git a/core/router/macro/interpreter/lexer/lexer_test.go b/macro/interpreter/lexer/lexer_test.go similarity index 95% rename from core/router/macro/interpreter/lexer/lexer_test.go rename to macro/interpreter/lexer/lexer_test.go index e104e8023c..4ed056a270 100644 --- a/core/router/macro/interpreter/lexer/lexer_test.go +++ b/macro/interpreter/lexer/lexer_test.go @@ -3,7 +3,7 @@ package lexer import ( "testing" - "github.com/kataras/iris/core/router/macro/interpreter/token" + "github.com/kataras/iris/macro/interpreter/token" ) func TestNextToken(t *testing.T) { diff --git a/core/router/macro/interpreter/parser/parser.go b/macro/interpreter/parser/parser.go similarity index 94% rename from core/router/macro/interpreter/parser/parser.go rename to macro/interpreter/parser/parser.go index b4bb0293d8..1a0b86086e 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/macro/interpreter/parser/parser.go @@ -5,9 +5,9 @@ import ( "strconv" "strings" - "github.com/kataras/iris/core/router/macro/interpreter/ast" - "github.com/kataras/iris/core/router/macro/interpreter/lexer" - "github.com/kataras/iris/core/router/macro/interpreter/token" + "github.com/kataras/iris/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/lexer" + "github.com/kataras/iris/macro/interpreter/token" ) // Parse takes a route "fullpath" @@ -39,7 +39,7 @@ func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement, } // if we have param type path but it's not the last path part if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 { - return nil, fmt.Errorf("param type '%s' should be lived only inside the last path segment, but was inside: %s", stmt.Type, s) + return nil, fmt.Errorf("%s: parameter type \"%s\" should be registered to the very last of a path", s, stmt.Type.Indent()) } statements = append(statements, stmt) diff --git a/core/router/macro/interpreter/parser/parser_test.go b/macro/interpreter/parser/parser_test.go similarity index 99% rename from core/router/macro/interpreter/parser/parser_test.go rename to macro/interpreter/parser/parser_test.go index b492b361d5..1695e6f4de 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/macro/interpreter/parser/parser_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/kataras/iris/core/router/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/ast" ) type simpleParamType string diff --git a/core/router/macro/interpreter/token/token.go b/macro/interpreter/token/token.go similarity index 100% rename from core/router/macro/interpreter/token/token.go rename to macro/interpreter/token/token.go diff --git a/core/router/macro/macro.go b/macro/macro.go similarity index 100% rename from core/router/macro/macro.go rename to macro/macro.go diff --git a/core/router/macro/macro_test.go b/macro/macro_test.go similarity index 100% rename from core/router/macro/macro_test.go rename to macro/macro_test.go diff --git a/core/router/macro/macros.go b/macro/macros.go similarity index 99% rename from core/router/macro/macros.go rename to macro/macros.go index 6832ee92c0..b1d9a96b93 100644 --- a/core/router/macro/macros.go +++ b/macro/macros.go @@ -4,7 +4,7 @@ import ( "strconv" "strings" - "github.com/kataras/iris/core/router/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/ast" ) var ( diff --git a/core/router/macro/template.go b/macro/template.go similarity index 88% rename from core/router/macro/template.go rename to macro/template.go index 983427a813..33b147341e 100644 --- a/core/router/macro/template.go +++ b/macro/template.go @@ -1,11 +1,11 @@ package macro import ( - "github.com/kataras/iris/core/memstore" "reflect" - "github.com/kataras/iris/core/router/macro/interpreter/ast" - "github.com/kataras/iris/core/router/macro/interpreter/parser" + "github.com/kataras/iris/core/memstore" + "github.com/kataras/iris/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/parser" ) // Template contains a route's path full parsed template. @@ -59,7 +59,9 @@ func (p *TemplateParam) CanEval() bool { } // paramChanger is the same form of context's Params().Set -func (p *TemplateParam) Eval(paramValue string, paramChanger func(key string, newValue interface{}) (memstore.Entry, bool)) bool { +// we could accept a memstore.Store or even context.RequestParams +// but this form has been chosed in order to test easier and fully decoupled from a request when necessary. +func (p *TemplateParam) Eval(paramValue string, paramChanger memstore.ValueSetter) bool { if p.TypeEvaluator == nil { for _, fn := range p.stringInFuncs { if !fn(paramValue) { @@ -85,7 +87,7 @@ func (p *TemplateParam) Eval(paramValue string, paramChanger func(key string, ne } } - paramChanger(p.Name, newValue) + paramChanger.Set(p.Name, newValue) return true } @@ -107,8 +109,8 @@ func Parse(src string, macros Macros) (*Template, error) { t.Src = src for idx, p := range params { - funcMap := macros.Lookup(p.Type) - typEval := funcMap.Evaluator + m := macros.Lookup(p.Type) + typEval := m.Evaluator tmplParam := TemplateParam{ Src: p.Src, @@ -120,7 +122,7 @@ func Parse(src string, macros Macros) (*Template, error) { } for _, paramfn := range p.Funcs { - tmplFn := funcMap.getFunc(paramfn.Name) + tmplFn := m.getFunc(paramfn.Name) if tmplFn == nil { // if not find on this type, check for Master's which is for global funcs too. if m := macros.GetMaster(); m != nil { tmplFn = m.getFunc(paramfn.Name) diff --git a/mvc/controller.go b/mvc/controller.go index 85675ec25a..f7c75eeffc 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -7,9 +7,9 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/router" - "github.com/kataras/iris/core/router/macro" "github.com/kataras/iris/hero" "github.com/kataras/iris/hero/di" + "github.com/kataras/iris/macro" "github.com/kataras/golog" ) diff --git a/mvc/controller_method_parser.go b/mvc/controller_method_parser.go index e5477e9eb8..1deb40ef46 100644 --- a/mvc/controller_method_parser.go +++ b/mvc/controller_method_parser.go @@ -9,7 +9,7 @@ import ( "unicode" "github.com/kataras/iris/core/router" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" ) const ( diff --git a/mvc/param.go b/mvc/param.go index a81d191c44..723017875e 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -4,7 +4,7 @@ import ( "reflect" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" ) func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) { From d9bda6e1972428aa3f855bffdce4b8a0bab2d762 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 29 Sep 2018 04:35:09 +0300 Subject: [PATCH 22/91] add tests for the new types (int8, int16, int32, uint, uint8, uint16, uint32, uint64) --- README.md | 14 ++-- core/router/path.go | 2 +- core/router/route.go | 2 +- macro/macro_test.go | 162 ++++++++++++++++++++++++++++++++++++++++++- macro/macros.go | 6 +- 5 files changed, 176 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 49125deb51..23b860e9ad 100644 --- a/README.md +++ b/README.md @@ -114,9 +114,15 @@ func main() { | Param Type | Go Type | Validation | Retrieve Helper | | -----------------|------|-------------|------| | `:string` | string | anything | `Params().Get` | -| `:int` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, depends on the arch | `Params().GetInt`...| +| `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` | +| `:int8` | int8 | -128 to 127 | `Params().GetInt8` | +| `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` | +| `:int32` | int32 | -2147483648 to 2147483647 | `Params().GetInt32` | | `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | +| `:uint` | uint | 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32), depends on the host arch | `Params().GetUint` | | `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | +| `:uint16` | uint16 | 0 to 65535 | `Params().GetUint16` | +| `:uint32` | uint32 | 0 to 4294967295 | `Params().GetUint32` | | `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | | `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | `Params().GetBool` | | `:alphabetical` | string | lowercase or uppercase letters | `Params().Get` | @@ -138,9 +144,9 @@ app.Get("/users/{id:uint64}", func(ctx iris.Context){ | `prefix`(prefix string) | :string | | `suffix`(suffix string) | :string | | `contains`(s string) | :string | -| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | -| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | -| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :number, :int64, :uint8, :uint64 | +| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | **Usage**: diff --git a/core/router/path.go b/core/router/path.go index 9b9d12a9fb..05ba11c319 100644 --- a/core/router/path.go +++ b/core/router/path.go @@ -33,7 +33,7 @@ func WildcardParam(name string) string { return prefix(name, WildcardParamStart) } -func convertTmplToNodePath(tmpl *macro.Template) string { +func convertMacroTmplToNodePath(tmpl *macro.Template) string { routePath := tmpl.Src if len(tmpl.Params) > 0 { if routePath[len(routePath)-1] == '/' { diff --git a/core/router/route.go b/core/router/route.go index c5c273e1c6..5e0e6ef0cf 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -47,7 +47,7 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, return nil, err } - path := convertTmplToNodePath(tmpl) + path := convertMacroTmplToNodePath(tmpl) // prepend the macro handler to the route, now, // right before the register to the tree, so APIBuilder#UseGlobal will work as expected. if macroEvaluatorHandler, ok := handler.MakeHandler(tmpl); ok { diff --git a/macro/macro_test.go b/macro/macro_test.go index 020f4d7485..ff25c817db 100644 --- a/macro/macro_test.go +++ b/macro/macro_test.go @@ -115,8 +115,8 @@ func TestIntEvaluatorRaw(t *testing.T) { {false, "astring"}, // 0 {false, "astringwith_numb3rS_and_symbol$"}, // 1 {true, "32321"}, // 2 - {x64, "9223372036854775807" /*max int64*/}, // 3 - {x64, "-9223372036854775808" /*min int64 */}, // 4 + {x64, "9223372036854775807" /* max int64 */}, // 3 + {x64, "-9223372036854775808" /* min int64 */}, // 4 {false, "-18446744073709553213213213213213121615"}, // 5 {false, "42 18446744073709551615"}, // 6 {false, "--42"}, // 7 @@ -130,6 +130,83 @@ func TestIntEvaluatorRaw(t *testing.T) { } } +func TestInt8EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {false, "32321"}, // 2 + {true, "127" /* max int8 */}, // 3 + {true, "-128" /* min int8 */}, // 4 + {false, "128"}, // 5 + {false, "-129"}, // 6 + {false, "-18446744073709553213213213213213121615"}, // 7 + {false, "42 18446744073709551615"}, // 8 + {false, "--42"}, // 9 + {false, "+42"}, // 10 + {false, "main.css"}, // 11 + {false, "/assets/main.css"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int8, tt.input, reflect.Int8, tt.pass, i) + } +} + +func TestInt16EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "32767" /* max int16 */}, // 3 + {true, "-32768" /* min int16 */}, // 4 + {false, "-32769"}, // 5 + {false, "32768"}, // 6 + {false, "-18446744073709553213213213213213121615"}, // 7 + {false, "42 18446744073709551615"}, // 8 + {false, "--42"}, // 9 + {false, "+42"}, // 10 + {false, "main.css"}, // 11 + {false, "/assets/main.css"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int16, tt.input, reflect.Int16, tt.pass, i) + } +} + +func TestInt32EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "1"}, // 3 + {true, "42"}, // 4 + {true, "2147483647" /* max int32 */}, // 5 + {true, "-2147483648" /* min int32 */}, // 6 + {false, "-2147483649"}, // 7 + {false, "2147483648"}, // 8 + {false, "-18446744073709553213213213213213121615"}, // 9 + {false, "42 18446744073709551615"}, // 10 + {false, "--42"}, // 11 + {false, "+42"}, // 12 + {false, "main.css"}, // 13 + {false, "/assets/main.css"}, // 14 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int32, tt.input, reflect.Int32, tt.pass, i) + } +} + func TestInt64EvaluatorRaw(t *testing.T) { tests := []struct { pass bool @@ -155,6 +232,35 @@ func TestInt64EvaluatorRaw(t *testing.T) { } } +func TestUintEvaluatorRaw(t *testing.T) { + x64 := strconv.IntSize == 64 + + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "1"}, // 3 + {true, "42"}, // 4 + {x64, "18446744073709551615" /* max uint64 */}, // 5 + {true, "4294967295" /* max uint32 */}, // 6 + {false, "-2147483649"}, // 7 + {true, "2147483648"}, // 8 + {false, "-18446744073709553213213213213213121615"}, // 9 + {false, "42 18446744073709551615"}, // 10 + {false, "--42"}, // 11 + {false, "+42"}, // 12 + {false, "main.css"}, // 13 + {false, "/assets/main.css"}, // 14 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint, tt.input, reflect.Uint, tt.pass, i) + } +} + func TestUint8EvaluatorRaw(t *testing.T) { tests := []struct { pass bool @@ -184,6 +290,58 @@ func TestUint8EvaluatorRaw(t *testing.T) { } } +func TestUint16EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "65535" /* max uint16 */}, // 3 + {true, "0" /* min uint16 */}, // 4 + {false, "-32769"}, // 5 + {true, "32768"}, // 6 + {false, "-18446744073709553213213213213213121615"}, // 7 + {false, "42 18446744073709551615"}, // 8 + {false, "--42"}, // 9 + {false, "+42"}, // 10 + {false, "main.css"}, // 11 + {false, "/assets/main.css"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint16, tt.input, reflect.Uint16, tt.pass, i) + } +} + +func TestUint32EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "1"}, // 3 + {true, "42"}, // 4 + {true, "4294967295" /* max uint32*/}, // 5 + {true, "0" /* min uint32 */}, // 6 + {false, "-2147483649"}, // 7 + {true, "2147483648"}, // 8 + {false, "-18446744073709553213213213213213121615"}, // 9 + {false, "42 18446744073709551615"}, // 10 + {false, "--42"}, // 11 + {false, "+42"}, // 12 + {false, "main.css"}, // 13 + {false, "/assets/main.css"}, // 14 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint32, tt.input, reflect.Uint32, tt.pass, i) + } +} + func TestUint64EvaluatorRaw(t *testing.T) { tests := []struct { pass bool diff --git a/macro/macros.go b/macro/macros.go index b1d9a96b93..c92c020227 100644 --- a/macro/macros.go +++ b/macro/macros.go @@ -48,6 +48,8 @@ var ( simpleNumberEval = MustRegexp("^-?[0-9]+$") // Int or number type // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch. + // If x64: -9223372036854775808 to 9223372036854775807. + // If x32: -2147483648 to 2147483647 and etc.. Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) { if !simpleNumberEval(paramValue) { return nil, false @@ -206,8 +208,8 @@ var ( // Uint as uint type // actual value can be min-max uint64 or min-max uint32 depends on the arch. - // if x64: 0 to 18446744073709551615 - // if x32: 0 to 4294967295 and etc. + // If x64: 0 to 18446744073709551615. + // If x32: 0 to 4294967295 and etc. Uint = NewMacro("uint", "", false, false, func(paramValue string) (interface{}, bool) { v, err := strconv.ParseUint(paramValue, 10, strconv.IntSize) // 32,64... if err != nil { From 20460ca9e22b08eef99bfff0fa22880b6a5b4849 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 29 Sep 2018 19:32:32 +0300 Subject: [PATCH 23/91] add a test and fix a small issue on the macro/handler#MakeHandler --- macro/handler/handler.go | 13 +++++------ macro/handler/handler_test.go | 41 +++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 macro/handler/handler_test.go diff --git a/macro/handler/handler.go b/macro/handler/handler.go index 24b6367aa7..e3f108590c 100644 --- a/macro/handler/handler.go +++ b/macro/handler/handler.go @@ -11,10 +11,9 @@ import ( // If the template does not contain any dynamic attributes and a special handler is NOT required // then it returns a nil handler and false as its second output value, // the caller should check those two values before any further action. -func MakeHandler(tmpl *macro.Template) (context.Handler, bool) { - needsMacroHandler := len(tmpl.Params) > 0 - if !needsMacroHandler { - return nil, false +func MakeHandler(tmpl *macro.Template) (handler context.Handler, needsMacroHandler bool) { + if len(tmpl.Params) == 0 { + return } // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. @@ -29,10 +28,10 @@ func MakeHandler(tmpl *macro.Template) (context.Handler, bool) { } if !needsMacroHandler { - return nil, false + return } - handler := func(ctx context.Context) { + handler = func(ctx context.Context) { for _, p := range tmpl.Params { if !p.CanEval() { continue // allow. @@ -48,5 +47,5 @@ func MakeHandler(tmpl *macro.Template) (context.Handler, bool) { ctx.Next() } - return handler, true + return } diff --git a/macro/handler/handler_test.go b/macro/handler/handler_test.go new file mode 100644 index 0000000000..c22ed663be --- /dev/null +++ b/macro/handler/handler_test.go @@ -0,0 +1,41 @@ +package handler + +import ( + "testing" + + "github.com/kataras/iris/macro" +) + +func TestMakeHandlerNeeds(t *testing.T) { + tests := []struct { + src string + needsHandler bool + }{ + {"/static/static", false}, + {"/{myparam}", false}, + {"/{myparam min(1)}", true}, + {"/{myparam else 500}", true}, + {"/{myparam else 404}", false}, + {"/{myparam:string}/static", false}, + {"/{myparam:int}", true}, + {"/static/{myparam:int}/static", true}, + {"/{myparam:path}", false}, + {"/{myparam:path min(1) else 404}", true}, + } + + availableMacros := *macro.Defaults + for i, tt := range tests { + tmpl, err := macro.Parse(tt.src, availableMacros) + if err != nil { + t.Fatalf("[%d] '%s' failed to be parsed: %v", i, tt.src, err) + } + if _, got := MakeHandler(tmpl); got != tt.needsHandler { + if tt.needsHandler { + t.Fatalf("[%d] '%s' expected to be able to generate an evaluator handler instead of a nil one", i, tt.src) + } else { + t.Fatalf("[%d] '%s' should not need an evaluator handler", i, tt.src) + } + + } + } +} From e73091a2658c5f0f24ae4c2831f623ae5d928175 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sat, 29 Sep 2018 19:59:39 +0300 Subject: [PATCH 24/91] make the macro#Parse to return a value of a Template instead of its ptr and debug logs for handlers length ignores the internal generated macro evaluator handler if it is there, so end-dev cannot be confused about the debug logs at that point --- _examples/routing/macros/main.go | 2 +- core/router/path.go | 2 +- core/router/route.go | 29 +++++++++++++++++++++-------- macro/handler/handler.go | 23 ++++++++++++++--------- macro/handler/handler_test.go | 6 +++--- macro/template.go | 11 +++++------ 6 files changed, 45 insertions(+), 28 deletions(-) diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index fe68c5984f..9361ff5fbb 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -72,7 +72,7 @@ func main() { ) ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) - }) + }, func(ctx context.Context) {}) app.Get("/test_string2/{myparam1}/{myparam2}", func(ctx context.Context) { var ( diff --git a/core/router/path.go b/core/router/path.go index 05ba11c319..11d4ffd080 100644 --- a/core/router/path.go +++ b/core/router/path.go @@ -33,7 +33,7 @@ func WildcardParam(name string) string { return prefix(name, WildcardParamStart) } -func convertMacroTmplToNodePath(tmpl *macro.Template) string { +func convertMacroTmplToNodePath(tmpl macro.Template) string { routePath := tmpl.Src if len(tmpl.Params) > 0 { if routePath[len(routePath)-1] == '/' { diff --git a/core/router/route.go b/core/router/route.go index 5e0e6ef0cf..132eb7c728 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -13,11 +13,11 @@ import ( // If any of the following fields are changed then the // caller should Refresh the router. type Route struct { - Name string `json:"name"` // "userRoute" - Method string `json:"method"` // "GET" - methodBckp string // if Method changed to something else (which is possible at runtime as well, via RefreshRouter) then this field will be filled with the old one. - Subdomain string `json:"subdomain"` // "admin." - tmpl *macro.Template // Tmpl().Src: "/api/user/{id:uint64}" + Name string `json:"name"` // "userRoute" + Method string `json:"method"` // "GET" + methodBckp string // if Method changed to something else (which is possible at runtime as well, via RefreshRouter) then this field will be filled with the old one. + Subdomain string `json:"subdomain"` // "admin." + tmpl macro.Template // Tmpl().Src: "/api/user/{id:uint64}" // temp storage, they're appended to the Handlers on build. // Execution happens before Handlers, can be empty. beginHandlers context.Handlers @@ -50,7 +50,8 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, path := convertMacroTmplToNodePath(tmpl) // prepend the macro handler to the route, now, // right before the register to the tree, so APIBuilder#UseGlobal will work as expected. - if macroEvaluatorHandler, ok := handler.MakeHandler(tmpl); ok { + if handler.CanMakeHandler(tmpl) { + macroEvaluatorHandler := handler.MakeHandler(tmpl) handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...) } @@ -155,7 +156,18 @@ func (r Route) String() string { // via Tmpl().Src, Route.Path is the path // converted to match the underline router's specs. func (r Route) Tmpl() macro.Template { - return *r.tmpl + return r.tmpl +} + +// RegisteredHandlersLen returns the end-developer's registered handlers, all except the macro evaluator handler +// if was required by the build process. +func (r Route) RegisteredHandlersLen() int { + n := len(r.Handlers) + if handler.CanMakeHandler(r.tmpl) { + n-- + } + + return n } // IsOnline returns true if the route is marked as "online" (state). @@ -245,7 +257,8 @@ func (r Route) Trace() string { printfmt += fmt.Sprintf(" %s", r.Subdomain) } printfmt += fmt.Sprintf(" %s ", r.Tmpl().Src) - if l := len(r.Handlers); l > 1 { + + if l := r.RegisteredHandlersLen(); l > 1 { printfmt += fmt.Sprintf("-> %s() and %d more", r.MainHandlerName, l-1) } else { printfmt += fmt.Sprintf("-> %s()", r.MainHandlerName) diff --git a/macro/handler/handler.go b/macro/handler/handler.go index e3f108590c..16f466c194 100644 --- a/macro/handler/handler.go +++ b/macro/handler/handler.go @@ -7,11 +7,11 @@ import ( "github.com/kataras/iris/macro" ) -// MakeHandler creates and returns a handler from a macro template, the handler evaluates each of the parameters if necessary at all. +// CanMakeHandler reports whether a macro template needs a special macro's evaluator handler to be validated +// before procceed to the next handler(s). // If the template does not contain any dynamic attributes and a special handler is NOT required -// then it returns a nil handler and false as its second output value, -// the caller should check those two values before any further action. -func MakeHandler(tmpl *macro.Template) (handler context.Handler, needsMacroHandler bool) { +// then it returns false. +func CanMakeHandler(tmpl macro.Template) (needsMacroHandler bool) { if len(tmpl.Params) == 0 { return } @@ -27,11 +27,18 @@ func MakeHandler(tmpl *macro.Template) (handler context.Handler, needsMacroHandl } } - if !needsMacroHandler { - return + return +} + +// MakeHandler creates and returns a handler from a macro template, the handler evaluates each of the parameters if necessary at all. +// If the template does not contain any dynamic attributes and a special handler is NOT required +// then it returns a nil handler. +func MakeHandler(tmpl macro.Template) context.Handler { + if !CanMakeHandler(tmpl) { + return nil } - handler = func(ctx context.Context) { + return func(ctx context.Context) { for _, p := range tmpl.Params { if !p.CanEval() { continue // allow. @@ -46,6 +53,4 @@ func MakeHandler(tmpl *macro.Template) (handler context.Handler, needsMacroHandl // if all passed, just continue. ctx.Next() } - - return } diff --git a/macro/handler/handler_test.go b/macro/handler/handler_test.go index c22ed663be..0acc1e830a 100644 --- a/macro/handler/handler_test.go +++ b/macro/handler/handler_test.go @@ -6,7 +6,7 @@ import ( "github.com/kataras/iris/macro" ) -func TestMakeHandlerNeeds(t *testing.T) { +func TestCanMakeHandler(t *testing.T) { tests := []struct { src string needsHandler bool @@ -29,13 +29,13 @@ func TestMakeHandlerNeeds(t *testing.T) { if err != nil { t.Fatalf("[%d] '%s' failed to be parsed: %v", i, tt.src, err) } - if _, got := MakeHandler(tmpl); got != tt.needsHandler { + + if got := CanMakeHandler(tmpl); got != tt.needsHandler { if tt.needsHandler { t.Fatalf("[%d] '%s' expected to be able to generate an evaluator handler instead of a nil one", i, tt.src) } else { t.Fatalf("[%d] '%s' should not need an evaluator handler", i, tt.src) } - } } } diff --git a/macro/template.go b/macro/template.go index 33b147341e..cd83d9c7de 100644 --- a/macro/template.go +++ b/macro/template.go @@ -95,18 +95,17 @@ func (p *TemplateParam) Eval(paramValue string, paramChanger memstore.ValueSette // and returns a new Template. // It builds all the parameter functions for that template // and their evaluators, it's the api call that makes use the interpeter's parser -> lexer. -func Parse(src string, macros Macros) (*Template, error) { +func Parse(src string, macros Macros) (Template, error) { types := make([]ast.ParamType, len(macros)) for i, m := range macros { types[i] = m } + tmpl := Template{Src: src} params, err := parser.Parse(src, types) if err != nil { - return nil, err + return tmpl, err } - t := new(Template) - t.Src = src for idx, p := range params { m := macros.Lookup(p.Type) @@ -140,8 +139,8 @@ func Parse(src string, macros Macros) (*Template, error) { tmplParam.Funcs = append(tmplParam.Funcs, evalFn) } - t.Params = append(t.Params, tmplParam.preComputed()) + tmpl.Params = append(tmpl.Params, tmplParam.preComputed()) } - return t, nil + return tmpl, nil } From d42aea589e9a1ce7d29feafa7cee33147e195ca9 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 30 Sep 2018 16:46:10 +0300 Subject: [PATCH 25/91] fix https://github.com/kataras/iris/issues/1087 --- websocket/connection.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/websocket/connection.go b/websocket/connection.go index 748f8067af..4d1ec8abc8 100644 --- a/websocket/connection.go +++ b/websocket/connection.go @@ -2,6 +2,7 @@ package websocket import ( "bytes" + "errors" "io" "net" "strconv" @@ -580,7 +581,14 @@ func (c *connection) Wait() { c.startReader() } +// ErrAlreadyDisconnected can be reported on the `Connection#Disconnect` function whenever the caller tries to close the +// connection when it is already closed by the client or the caller previously. +var ErrAlreadyDisconnected = errors.New("already disconnected") + func (c *connection) Disconnect() error { + if c == nil || c.disconnected { + return ErrAlreadyDisconnected + } return c.server.Disconnect(c.ID()) } From d7cc93fcb7676114f72bfb28931e9aa4dc1f261d Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 30 Sep 2018 17:26:40 +0300 Subject: [PATCH 26/91] fix macro registration issue and match by kind for MVC and hero instead of its kind, so custom types like structs can be used without any issues. Add an example on how to register a custom macro it is just few lines and all in one place in this version. --- _examples/routing/macros/main.go | 118 +++++++++++++------------------ context/request_params.go | 34 ++++----- hero/param.go | 2 +- macro/AUTHORS | 4 ++ macro/LICENSE | 27 +++++++ macro/macros.go | 15 ++-- mvc/param.go | 2 +- 7 files changed, 108 insertions(+), 94 deletions(-) create mode 100644 macro/AUTHORS create mode 100644 macro/LICENSE diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go index 9361ff5fbb..3de4e29a80 100644 --- a/_examples/routing/macros/main.go +++ b/_examples/routing/macros/main.go @@ -1,12 +1,14 @@ +// Package main shows how you can register a custom parameter type and macro functions that belongs to it. package main import ( "fmt" "reflect" + "sort" + "strings" "github.com/kataras/iris" "github.com/kataras/iris/context" - // "github.com/kataras/iris/core/memstore" "github.com/kataras/iris/hero" ) @@ -14,80 +16,60 @@ func main() { app := iris.New() app.Logger().SetLevel("debug") - // Let's see how we can register a custom macro such as ":uint32" or ":small" for its alias (optionally) for Uint32 types. - // app.Macros().Register("uint32", "small", false, false, func(paramValue string) bool { - // _, err := strconv.ParseUint(paramValue, 10, 32) - // return err == nil - // }). - // RegisterFunc("min", func(min uint32) func(string) bool { - // return func(paramValue string) bool { - // n, err := strconv.ParseUint(paramValue, 10, 32) - // if err != nil { - // return false - // } + app.Macros().Register("slice", "", false, true, func(paramValue string) (interface{}, bool) { + return strings.Split(paramValue, "/"), true + }).RegisterFunc("contains", func(expectedItems []string) func(paramValue []string) bool { + sort.Strings(expectedItems) + return func(paramValue []string) bool { + if len(paramValue) != len(expectedItems) { + return false + } - // return uint32(n) >= min - // } - // }) + sort.Strings(paramValue) + for i := 0; i < len(paramValue); i++ { + if paramValue[i] != expectedItems[i] { + return false + } + } - /* TODO: - somehow define one-time how the parameter should be parsed to a particular type (go std or custom) - tip: we can change the original value from string to X using the entry's.ValueRaw - ^ Done 27 sep 2018. - */ - - // app.Macros().Register("uint32", "small", false, false, func(paramValue string) (interface{}, bool) { - // v, err := strconv.ParseUint(paramValue, 10, 32) - // return uint32(v), err == nil - // }). - // RegisterFunc("min", func(min uint32) func(uint32) bool { - // return func(paramValue uint32) bool { - // return paramValue >= min - // } - // }) + return true + } + }) - // // optionally, only when mvc or hero features are used for this custom macro/parameter type. - // context.ParamResolvers[reflect.Uint32] = func(paramIndex int) interface{} { - // /* both works but second is faster, we omit the duplication of the type conversion over and over as of 27 Sep of 2018 (this patch)*/ - // // return func(ctx context.Context) uint32 { - // // param := ctx.Params().GetEntryAt(paramIndex) - // // paramValueAsUint32, _ := strconv.ParseUint(param.String(), 10, 32) - // // return uint32(paramValueAsUint32) - // // } - // return func(ctx context.Context) uint32 { - // return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) - // } /* TODO: find a way to automative it based on the macro's first return value type, if thats the case then we must not return nil even if not found, - // we must return a value i.e 0 for int for its interface{} */ - // } - // // + // In order to use your new param type inside MVC controller's function input argument or a hero function input argument + // you have to tell the Iris what type it is, the `ValueRaw` of the parameter is the same type + // as you defined it above with the func(paramValue string) (interface{}, bool). + // The new value and its type(from string to your new custom type) it is stored only once now, + // you don't have to do any conversions for simple cases like this. + context.ParamResolvers[reflect.TypeOf([]string{})] = func(paramIndex int) interface{} { + return func(ctx context.Context) []string { + // When you want to retrieve a parameter with a value type that it is not supported by-default, such as ctx.Params().GetInt + // then you can use the `GetEntry` or `GetEntryAt` and cast its underline `ValueRaw` to the desired type. + // The type should be the same as the macro's evaluator function (last argument on the Macros#Register) return value. + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.([]string) + } + } - app.Get("/test_uint32/{myparam1:string}/{myparam2:uint32 min(10)}", hero.Handler(func(myparam1 string, myparam2 uint32) string { - return fmt.Sprintf("Value of the parameters are: %s:%d\n", myparam1, myparam2) + /* + http://localhost:8080/test_slice_hero/myvaluei1/myavlue2 -> + myparam's value (a trailing path parameter type) is: []string{"myvaluei1", "myavlue2"} + */ + app.Get("/test_slice_hero/{myparam:slice}", hero.Handler(func(myparam []string) string { + return fmt.Sprintf("myparam's value (a trailing path parameter type) is: %#v\n", myparam) })) - app.Get("/test_string/{myparam1}/{myparam2 prefix(a)}", func(ctx context.Context) { - var ( - myparam1 = ctx.Params().Get("myparam1") - myparam2 = ctx.Params().Get("myparam2") - ) - - ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) - }, func(ctx context.Context) {}) + /* + http://localhost:8080/test_slice_contains/notcontains1/value2 -> + (404) Not Found - app.Get("/test_string2/{myparam1}/{myparam2}", func(ctx context.Context) { - var ( - myparam1 = ctx.Params().Get("myparam1") - myparam2 = ctx.Params().Get("myparam2") - ) - - ctx.Writef("myparam1: %s | myparam2: %s", myparam1, myparam2) - }) - - app.Get("/test_uint64/{myparam1:string}/{myparam2:uint64}", func(ctx context.Context) { - // works: ctx.Writef("Value of the parameter is: %s\n", ctx.Params().Get("myparam")) - // but better and faster because the macro converts the string to uint64 automatically: - println("type of myparam2 (should be uint64) is: " + reflect.ValueOf(ctx.Params().GetEntry("myparam2").ValueRaw).Kind().String()) - ctx.Writef("Value of the parameters are: %s:%d\n", ctx.Params().Get("myparam1"), ctx.Params().GetUint64Default("myparam2", 0)) + http://localhost:8080/test_slice_contains/value1/value2 -> + myparam's value (a trailing path parameter type) is: []string{"value1", "value2"} + */ + app.Get("/test_slice_contains/{myparam:slice contains([value1,value2])}", func(ctx context.Context) { + // When it is not a built'n function available to retrieve your value with the type you want, such as ctx.Params().GetInt + // then you can use the `GetEntry.ValueRaw` to get the real value, which is set-ed by your macro above. + myparam := ctx.Params().GetEntry("myparam").ValueRaw.([]string) + ctx.Writef("myparam's value (a trailing path parameter type) is: %#v\n", myparam) }) app.Run(iris.Addr(":8080")) diff --git a/context/request_params.go b/context/request_params.go index 9e45c3635e..860ccf901a 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -83,65 +83,65 @@ func (r RequestParams) GetIntUnslashed(key string) (int, bool) { } var ( - ParamResolvers = map[reflect.Kind]func(paramIndex int) interface{}{ - reflect.String: func(paramIndex int) interface{} { + ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ + reflect.TypeOf(""): func(paramIndex int) interface{} { return func(ctx Context) string { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(string) } }, - reflect.Int: func(paramIndex int) interface{} { + reflect.TypeOf(int(1)): func(paramIndex int) interface{} { return func(ctx Context) int { // v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0) // return v return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int) } }, - reflect.Int8: func(paramIndex int) interface{} { + reflect.TypeOf(int8(1)): func(paramIndex int) interface{} { return func(ctx Context) int8 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int8) } }, - reflect.Int16: func(paramIndex int) interface{} { + reflect.TypeOf(int16(1)): func(paramIndex int) interface{} { return func(ctx Context) int16 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int16) } }, - reflect.Int32: func(paramIndex int) interface{} { + reflect.TypeOf(int32(1)): func(paramIndex int) interface{} { return func(ctx Context) int32 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int32) } }, - reflect.Int64: func(paramIndex int) interface{} { + reflect.TypeOf(int64(1)): func(paramIndex int) interface{} { return func(ctx Context) int64 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int64) } }, - reflect.Uint: func(paramIndex int) interface{} { + reflect.TypeOf(uint(1)): func(paramIndex int) interface{} { return func(ctx Context) uint { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint) } }, - reflect.Uint8: func(paramIndex int) interface{} { + reflect.TypeOf(uint8(1)): func(paramIndex int) interface{} { return func(ctx Context) uint8 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint8) } }, - reflect.Uint16: func(paramIndex int) interface{} { + reflect.TypeOf(uint16(1)): func(paramIndex int) interface{} { return func(ctx Context) uint16 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint16) } }, - reflect.Uint32: func(paramIndex int) interface{} { + reflect.TypeOf(uint32(1)): func(paramIndex int) interface{} { return func(ctx Context) uint32 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) } }, - reflect.Uint64: func(paramIndex int) interface{} { + reflect.TypeOf(uint64(1)): func(paramIndex int) interface{} { return func(ctx Context) uint64 { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint64) } }, - reflect.Bool: func(paramIndex int) interface{} { + reflect.TypeOf(true): func(paramIndex int) interface{} { return func(ctx Context) bool { return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(bool) } @@ -149,16 +149,16 @@ var ( } ) -// ParamResolverByKindAndIndex will return a function that can be used to bind path parameter's exact value by its Go std type +// ParamResolverByTypeAndIndex will return a function that can be used to bind path parameter's exact value by its Go std type // and the parameter's index based on the registered path. -// Usage: nameResolver := ParamResolverByKindAndKey(reflect.String, 0) +// Usage: nameResolver := ParamResolverByKindAndKey(reflect.TypeOf(""), 0) // Inside a Handler: nameResolver.Call(ctx)[0] // it will return the reflect.Value Of the exact type of the parameter(based on the path parameters and macros). // It is only useful for dynamic binding of the parameter, it is used on "hero" package and it should be modified // only when Macros are modified in such way that the default selections for the available go std types are not enough. // // Returns empty value and false if "k" does not match any valid parameter resolver. -func ParamResolverByKindAndIndex(k reflect.Kind, paramIndex int) (reflect.Value, bool) { +func ParamResolverByTypeAndIndex(typ reflect.Type, paramIndex int) (reflect.Value, bool) { /* NO: // This could work but its result is not exact type, so direct binding is not possible. resolver := m.ParamResolver @@ -178,7 +178,7 @@ func ParamResolverByKindAndIndex(k reflect.Kind, paramIndex int) (reflect.Value, // */ - r, ok := ParamResolvers[k] + r, ok := ParamResolvers[typ] if !ok || r == nil { return reflect.Value{}, false } diff --git a/hero/param.go b/hero/param.go index dc34b10e61..6941d5543c 100644 --- a/hero/param.go +++ b/hero/param.go @@ -19,7 +19,7 @@ type params struct { func (p *params) resolve(index int, typ reflect.Type) (reflect.Value, bool) { currentParamIndex := p.next - v, ok := context.ParamResolverByKindAndIndex(typ.Kind(), currentParamIndex) + v, ok := context.ParamResolverByTypeAndIndex(typ, currentParamIndex) p.next = p.next + 1 return v, ok diff --git a/macro/AUTHORS b/macro/AUTHORS new file mode 100644 index 0000000000..0476475022 --- /dev/null +++ b/macro/AUTHORS @@ -0,0 +1,4 @@ +# This is the official list of Iris Macro and Route path interpreter authors for copyright +# purposes. + +Gerasimos Maropoulos diff --git a/macro/LICENSE b/macro/LICENSE new file mode 100644 index 0000000000..c73df4cefa --- /dev/null +++ b/macro/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2017-2018 The Iris Macro and Route path interpreter. 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 Iris 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. \ No newline at end of file diff --git a/macro/macros.go b/macro/macros.go index c92c020227..12a5cf21f5 100644 --- a/macro/macros.go +++ b/macro/macros.go @@ -10,10 +10,10 @@ import ( var ( // String type // Allows anything (single path segment, as everything except the `Path`). + // Its functions can be used by the rest of the macros and param types whenever not available function by name is used. + // Because of its "master" boolean value to true (third parameter). String = NewMacro("string", "", true, false, nil). - RegisterFunc("regexp", func(expr string) func(string) bool { - return MustRegexp(expr) - }). + RegisterFunc("regexp", MustRegexp). // checks if param value starts with the 'prefix' arg RegisterFunc("prefix", func(prefix string) func(string) bool { return func(paramValue string) bool { @@ -431,21 +431,22 @@ func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, eval } func (ms *Macros) register(macro *Macro) bool { - if macro.Indent() == "" || macro.Evaluator == nil { + if macro.Indent() == "" { return false } cp := *ms for _, m := range cp { - // can't add more than one with the same ast characteristics. if macro.Indent() == m.Indent() { return false } - if macro.Alias() == m.Alias() || macro.Alias() == m.Indent() { - return false + if alias := macro.Alias(); alias != "" { + if alias == m.Alias() || alias == m.Indent() { + return false + } } if macro.Master() && m.Master() { diff --git a/mvc/param.go b/mvc/param.go index 723017875e..faa6839626 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -39,7 +39,7 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) if len(funcIn) <= i { return } - funcDep, ok := context.ParamResolverByKindAndIndex(funcIn[i].Kind(), param.Index) + funcDep, ok := context.ParamResolverByTypeAndIndex(funcIn[i], param.Index) if !ok { continue } From 9802c25183104ff42baa0a26db2329514eb17a49 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 1 Oct 2018 15:27:45 +0300 Subject: [PATCH 27/91] add some more helpers for the parameters and the memstore for num types --- core/memstore/memstore.go | 501 ++++++++++++++++++++++++++++++++++---- 1 file changed, 448 insertions(+), 53 deletions(-) diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index c36272d86e..001762e0e8 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -7,6 +7,7 @@ package memstore import ( "fmt" + "math" "reflect" "strconv" "strings" @@ -117,12 +118,13 @@ func (e Entry) IntDefault(def int) (int, error) { if err != nil { return def, err } - return val, nil case int: return vv, nil case int8: return int(vv), nil + case int16: + return int(vv), nil case int32: return int(vv), nil case int64: @@ -131,6 +133,8 @@ func (e Entry) IntDefault(def int) (int, error) { return int(vv), nil case uint8: return int(vv), nil + case uint16: + return int(vv), nil case uint32: return int(vv), nil case uint64: @@ -140,6 +144,96 @@ func (e Entry) IntDefault(def int) (int, error) { return def, errFindParse.Format("int", e.Key) } +// Int8Default returns the entry's value as int8. +// If not found returns "def" and a non-nil error. +func (e Entry) Int8Default(def int8) (int8, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("int8", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseInt(vv, 10, 8) + if err != nil { + return def, err + } + return int8(val), nil + case int: + return int8(vv), nil + case int8: + return vv, nil + case int16: + return int8(vv), nil + case int32: + return int8(vv), nil + case int64: + return int8(vv), nil + } + + return def, errFindParse.Format("int8", e.Key) +} + +// Int16Default returns the entry's value as int16. +// If not found returns "def" and a non-nil error. +func (e Entry) Int16Default(def int16) (int16, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("int16", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseInt(vv, 10, 16) + if err != nil { + return def, err + } + return int16(val), nil + case int: + return int16(vv), nil + case int8: + return int16(vv), nil + case int16: + return vv, nil + case int32: + return int16(vv), nil + case int64: + return int16(vv), nil + } + + return def, errFindParse.Format("int16", e.Key) +} + +// Int32Default returns the entry's value as int32. +// If not found returns "def" and a non-nil error. +func (e Entry) Int32Default(def int32) (int32, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("int32", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseInt(vv, 10, 32) + if err != nil { + return def, err + } + return int32(val), nil + case int: + return int32(vv), nil + case int8: + return int32(vv), nil + case int16: + return int32(vv), nil + case int32: + return vv, nil + case int64: + return int32(vv), nil + } + + return def, errFindParse.Format("int32", e.Key) +} + // Int64Default returns the entry's value as int64. // If not found returns "def" and a non-nil error. func (e Entry) Int64Default(def int64) (int64, error) { @@ -164,93 +258,192 @@ func (e Entry) Int64Default(def int64) (int64, error) { return def, errFindParse.Format("int64", e.Key) } -// Float64Default returns the entry's value as float64. +// UintDefault returns the entry's value as uint. // If not found returns "def" and a non-nil error. -func (e Entry) Float64Default(def float64) (float64, error) { +func (e Entry) UintDefault(def uint) (uint, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("float64", e.Key) + return def, errFindParse.Format("uint", e.Key) + } + + x64 := strconv.IntSize == 64 + var maxValue uint = math.MaxUint32 + if x64 { + maxValue = math.MaxUint64 } switch vv := v.(type) { case string: - val, err := strconv.ParseFloat(vv, 64) + val, err := strconv.ParseUint(vv, 10, strconv.IntSize) if err != nil { return def, err } - return val, nil - case float32: - return float64(vv), nil - case float64: + if val > uint64(maxValue) { + return def, errFindParse.Format("uint", e.Key) + } + return uint(val), nil + case uint: return vv, nil + case uint8: + return uint(vv), nil + case uint16: + return uint(vv), nil + case uint32: + return uint(vv), nil + case uint64: + if vv > uint64(maxValue) { + return def, errFindParse.Format("uint", e.Key) + } + return uint(vv), nil case int: - return float64(vv), nil - case int64: - return float64(vv), nil + if vv < 0 || vv > int(maxValue) { + return def, errFindParse.Format("uint", e.Key) + } + return uint(vv), nil + } + + return def, errFindParse.Format("uint", e.Key) +} + +// Uint8Default returns the entry's value as uint8. +// If not found returns "def" and a non-nil error. +func (e Entry) Uint8Default(def uint8) (uint8, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint8", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 8) + if err != nil { + return def, err + } + if val > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(val), nil case uint: - return float64(vv), nil + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case uint8: + return vv, nil + case uint16: + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case uint32: + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil case uint64: - return float64(vv), nil + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case int: + if vv < 0 || vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil } - return def, errFindParse.Format("float64", e.Key) + return def, errFindParse.Format("uint8", e.Key) } -// Float32Default returns the entry's value as float32. +// Uint16Default returns the entry's value as uint16. // If not found returns "def" and a non-nil error. -func (e Entry) Float32Default(key string, def float32) (float32, error) { +func (e Entry) Uint16Default(def uint16) (uint16, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("float32", e.Key) + return def, errFindParse.Format("uint16", e.Key) } switch vv := v.(type) { case string: - val, err := strconv.ParseFloat(vv, 32) + val, err := strconv.ParseUint(vv, 10, 16) if err != nil { return def, err } - - return float32(val), nil - case float32: + if val > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(val), nil + case uint: + if vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil + case uint8: + return uint16(vv), nil + case uint16: return vv, nil - case float64: - return float32(vv), nil + case uint32: + if vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil + case uint64: + if vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil case int: - return float32(vv), nil + if vv < 0 || vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil } - return def, errFindParse.Format("float32", e.Key) + return def, errFindParse.Format("uint16", e.Key) } -// Uint8Default returns the entry's value as uint8. +// Uint32Default returns the entry's value as uint32. // If not found returns "def" and a non-nil error. -func (e Entry) Uint8Default(def uint8) (uint8, error) { +func (e Entry) Uint32Default(def uint32) (uint32, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("uint8", e.Key) + return def, errFindParse.Format("uint32", e.Key) } switch vv := v.(type) { case string: - val, err := strconv.ParseUint(vv, 10, 8) + val, err := strconv.ParseUint(vv, 10, 32) if err != nil { return def, err } - if val > 255 { - return def, errFindParse.Format("uint8", e.Key) + if val > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) } - return uint8(val), nil + return uint32(val), nil + case uint: + if vv > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) + } + return uint32(vv), nil case uint8: + return uint32(vv), nil + case uint16: + return uint32(vv), nil + case uint32: return vv, nil + case uint64: + if vv > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) + } + return uint32(vv), nil case int: - if vv < 0 || vv > 255 { - return def, errFindParse.Format("uint8", e.Key) + if vv < 0 || vv > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) } - return uint8(vv), nil + return uint32(vv), nil } - return def, errFindParse.Format("uint8", e.Key) + return def, errFindParse.Format("uint32", e.Key) } // Uint64Default returns the entry's value as uint64. @@ -263,7 +456,25 @@ func (e Entry) Uint64Default(def uint64) (uint64, error) { switch vv := v.(type) { case string: - return strconv.ParseUint(vv, 10, 64) + val, err := strconv.ParseUint(vv, 10, 64) + if err != nil { + return def, err + } + if val > math.MaxUint64 { + return def, errFindParse.Format("uint64", e.Key) + } + return uint64(val), nil + case uint: + if vv > math.MaxUint64 { + return def, errFindParse.Format("uint64", e.Key) + } + return uint64(vv), nil + case uint8: + return uint64(vv), nil + case uint16: + return uint64(vv), nil + case uint32: + return uint64(vv), nil case uint64: return vv, nil case int64: @@ -275,6 +486,70 @@ func (e Entry) Uint64Default(def uint64) (uint64, error) { return def, errFindParse.Format("uint64", e.Key) } +// Float32Default returns the entry's value as float32. +// If not found returns "def" and a non-nil error. +func (e Entry) Float32Default(key string, def float32) (float32, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("float32", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseFloat(vv, 32) + if err != nil { + return def, err + } + if val > math.MaxFloat32 { + return def, errFindParse.Format("float32", e.Key) + } + return float32(val), nil + case float32: + return vv, nil + case float64: + if vv > math.MaxFloat32 { + return def, errFindParse.Format("float32", e.Key) + } + return float32(vv), nil + case int: + return float32(vv), nil + } + + return def, errFindParse.Format("float32", e.Key) +} + +// Float64Default returns the entry's value as float64. +// If not found returns "def" and a non-nil error. +func (e Entry) Float64Default(def float64) (float64, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("float64", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseFloat(vv, 64) + if err != nil { + return def, err + } + return val, nil + case float32: + return float64(vv), nil + case float64: + return vv, nil + case int: + return float64(vv), nil + case int64: + return float64(vv), nil + case uint: + return float64(vv), nil + case uint64: + return float64(vv), nil + } + + return def, errFindParse.Format("float64", e.Key) +} + // BoolDefault returns the user's value as bool. // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" // or "0" or "f" or "F" or "FALSE" or "false" or "False". @@ -508,6 +783,106 @@ func (r *Store) GetIntDefault(key string, def int) int { return def } +// GetInt8 returns the entry's value as int8, based on its key. +// If not found returns -1 and a non-nil error. +func (r *Store) GetInt8(key string) (int8, error) { + v, ok := r.GetEntry(key) + if !ok { + return -1, errFindParse.Format("int8", key) + } + return v.Int8Default(-1) +} + +// GetInt8Default returns the entry's value as int8, based on its key. +// If not found returns "def". +func (r *Store) GetInt8Default(key string, def int8) int8 { + if v, err := r.GetInt8(key); err == nil { + return v + } + + return def +} + +// GetInt16 returns the entry's value as int16, based on its key. +// If not found returns -1 and a non-nil error. +func (r *Store) GetInt16(key string) (int16, error) { + v, ok := r.GetEntry(key) + if !ok { + return -1, errFindParse.Format("int16", key) + } + return v.Int16Default(-1) +} + +// GetInt16Default returns the entry's value as int16, based on its key. +// If not found returns "def". +func (r *Store) GetInt16Default(key string, def int16) int16 { + if v, err := r.GetInt16(key); err == nil { + return v + } + + return def +} + +// GetInt32 returns the entry's value as int32, based on its key. +// If not found returns -1 and a non-nil error. +func (r *Store) GetInt32(key string) (int32, error) { + v, ok := r.GetEntry(key) + if !ok { + return -1, errFindParse.Format("int32", key) + } + return v.Int32Default(-1) +} + +// GetInt32Default returns the entry's value as int32, based on its key. +// If not found returns "def". +func (r *Store) GetInt32Default(key string, def int32) int32 { + if v, err := r.GetInt32(key); err == nil { + return v + } + + return def +} + +// GetInt64 returns the entry's value as int64, based on its key. +// If not found returns -1 and a non-nil error. +func (r *Store) GetInt64(key string) (int64, error) { + v, ok := r.GetEntry(key) + if !ok { + return -1, errFindParse.Format("int64", key) + } + return v.Int64Default(-1) +} + +// GetInt64Default returns the entry's value as int64, based on its key. +// If not found returns "def". +func (r *Store) GetInt64Default(key string, def int64) int64 { + if v, err := r.GetInt64(key); err == nil { + return v + } + + return def +} + +// GetUint returns the entry's value as uint, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint(key string) (uint, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint", key) + } + return v.UintDefault(0) +} + +// GetUintDefault returns the entry's value as uint, based on its key. +// If not found returns "def". +func (r *Store) GetUintDefault(key string, def uint) uint { + if v, err := r.GetUint(key); err == nil { + return v + } + + return def +} + // GetUint8 returns the entry's value as uint8, based on its key. // If not found returns 0 and a non-nil error. func (r *Store) GetUint8(key string) (uint8, error) { @@ -528,40 +903,60 @@ func (r *Store) GetUint8Default(key string, def uint8) uint8 { return def } -// GetUint64 returns the entry's value as uint64, based on its key. +// GetUint16 returns the entry's value as uint16, based on its key. // If not found returns 0 and a non-nil error. -func (r *Store) GetUint64(key string) (uint64, error) { +func (r *Store) GetUint16(key string) (uint16, error) { v, ok := r.GetEntry(key) if !ok { - return 0, errFindParse.Format("uint64", key) + return 0, errFindParse.Format("uint16", key) } - return v.Uint64Default(0) + return v.Uint16Default(0) } -// GetUint64Default returns the entry's value as uint64, based on its key. +// GetUint16Default returns the entry's value as uint16, based on its key. // If not found returns "def". -func (r *Store) GetUint64Default(key string, def uint64) uint64 { - if v, err := r.GetUint64(key); err == nil { +func (r *Store) GetUint16Default(key string, def uint16) uint16 { + if v, err := r.GetUint16(key); err == nil { return v } return def } -// GetInt64 returns the entry's value as int64, based on its key. -// If not found returns -1 and a non-nil error. -func (r *Store) GetInt64(key string) (int64, error) { +// GetUint32 returns the entry's value as uint32, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint32(key string) (uint32, error) { v, ok := r.GetEntry(key) if !ok { - return -1, errFindParse.Format("int64", key) + return 0, errFindParse.Format("uint32", key) } - return v.Int64Default(-1) + return v.Uint32Default(0) } -// GetInt64Default returns the entry's value as int64, based on its key. +// GetUint32Default returns the entry's value as uint32, based on its key. // If not found returns "def". -func (r *Store) GetInt64Default(key string, def int64) int64 { - if v, err := r.GetInt64(key); err == nil { +func (r *Store) GetUint32Default(key string, def uint32) uint32 { + if v, err := r.GetUint32(key); err == nil { + return v + } + + return def +} + +// GetUint64 returns the entry's value as uint64, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint64(key string) (uint64, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint64", key) + } + return v.Uint64Default(0) +} + +// GetUint64Default returns the entry's value as uint64, based on its key. +// If not found returns "def". +func (r *Store) GetUint64Default(key string, def uint64) uint64 { + if v, err := r.GetUint64(key); err == nil { return v } From 9d9728bfd16796709ed88e892d595efa718511ea Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 2 Oct 2018 04:05:39 +0300 Subject: [PATCH 28/91] init go modules but keep the dep files and vendor folder and update badger sessionsdb's dependency to 1.5.4 from 1.5.3 --- Gopkg.lock | 2 +- Gopkg.toml | 2 +- .../README.md | 2 - .../counter/configurator.go | 30 ---- .../main.go | 22 ++- .../http-listening/notify-on-shutdown/main.go | 16 +- go.mod | 68 ++++++++ go.sum | 148 ++++++++++++++++++ sessions/sessiondb/badger/database.go | 24 ++- sessions/sessiondb/badger/vendor/badger.txt | 5 + .../vendor/github.com/dgraph-io/badger.txt | 3 - .../vendor/github.com/dgraph-io/badger/db.go | 24 ++- .../vendor/github.com/dgraph-io/badger/doc.go | 28 ---- .../github.com/dgraph-io/badger/errors.go | 4 + .../github.com/dgraph-io/badger/iterator.go | 9 +- .../github.com/dgraph-io/badger/levels.go | 66 +++++++- .../github.com/dgraph-io/badger/managed_db.go | 114 ++++++++++++++ .../github.com/dgraph-io/badger/options.go | 5 +- .../dgraph-io/badger/table/README.md | 51 ------ .../dgraph-io/badger/transaction.go | 22 ++- .../github.com/dgraph-io/badger/value.go | 9 +- 21 files changed, 512 insertions(+), 142 deletions(-) delete mode 100644 _examples/http-listening/iris-configurator-and-host-configurator/README.md delete mode 100644 _examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 sessions/sessiondb/badger/vendor/badger.txt delete mode 100644 sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt delete mode 100644 sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/doc.go delete mode 100644 sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/README.md diff --git a/Gopkg.lock b/Gopkg.lock index 1f3d818643..641072082a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -275,7 +275,7 @@ branch = "master" name = "github.com/dgraph-io/badger" packages = ["."] - revision = "391b6d3b93e6014fe8c2971fcc0c1266e47dbbd9" + revision = "99233d725dbdd26d156c61b2f42ae1671b794656" [[projects]] branch = "master" diff --git a/Gopkg.toml b/Gopkg.toml index 8274cd489c..b03d869347 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -80,7 +80,7 @@ [[constraint]] name = "github.com/dgraph-io/badger" - version = "1.5.3" + version = "1.5.4" [[constraint]] name = "github.com/etcd-io/bbolt" diff --git a/_examples/http-listening/iris-configurator-and-host-configurator/README.md b/_examples/http-listening/iris-configurator-and-host-configurator/README.md deleted file mode 100644 index 89ecb89c15..0000000000 --- a/_examples/http-listening/iris-configurator-and-host-configurator/README.md +++ /dev/null @@ -1,2 +0,0 @@ -A silly example for this issue: https://github.com/kataras/iris/issues/688#issuecomment-318828259. -However it seems useful and therefore is being included in the examples for everyone else. \ No newline at end of file diff --git a/_examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go b/_examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go deleted file mode 100644 index 7298830347..0000000000 --- a/_examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go +++ /dev/null @@ -1,30 +0,0 @@ -package counter - -import ( - "time" - - "github.com/kataras/iris" - "github.com/kataras/iris/core/host" -) - -func Configurator(app *iris.Application) { - counterValue := 0 - - go func() { - ticker := time.NewTicker(time.Second) - - for range ticker.C { - counterValue++ - } - - app.ConfigureHost(func(h *host.Supervisor) { // <- HERE: IMPORTANT - h.RegisterOnShutdown(func() { - ticker.Stop() - }) - }) // or put the ticker outside of the gofunc and put the configurator before or after the app.Get, outside of this gofunc - }() - - app.Get("/counter", func(ctx iris.Context) { - ctx.Writef("Counter value = %d", counterValue) - }) -} diff --git a/_examples/http-listening/iris-configurator-and-host-configurator/main.go b/_examples/http-listening/iris-configurator-and-host-configurator/main.go index a82d974f9e..f1d7ad85b9 100644 --- a/_examples/http-listening/iris-configurator-and-host-configurator/main.go +++ b/_examples/http-listening/iris-configurator-and-host-configurator/main.go @@ -1,14 +1,28 @@ package main import ( - "github.com/kataras/iris/_examples/http-listening/iris-configurator-and-host-configurator/counter" - "github.com/kataras/iris" ) func main() { app := iris.New() - app.Configure(counter.Configurator) - app.Run(iris.Addr(":8080")) + app.ConfigureHost(func(host *iris.Supervisor) { // <- HERE: IMPORTANT + // You can control the flow or defer something using some of the host's methods: + // host.RegisterOnError + // host.RegisterOnServe + host.RegisterOnShutdown(func() { + app.Logger().Infof("Application shutdown on signal") + }) + }) + + app.Get("/", func(ctx iris.Context) { + ctx.HTML("

Hello

\n") + }) + + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) + + /* There are more easy ways to notify for global shutdown using the `iris.RegisterOnInterrupt` for default signal interrupt events. + You can even go it even further by looking at the: "graceful-shutdown" example. + */ } diff --git a/_examples/http-listening/notify-on-shutdown/main.go b/_examples/http-listening/notify-on-shutdown/main.go index 67400aa864..9a6e37091a 100644 --- a/_examples/http-listening/notify-on-shutdown/main.go +++ b/_examples/http-listening/notify-on-shutdown/main.go @@ -1,11 +1,10 @@ package main import ( - stdContext "context" + "context" "time" "github.com/kataras/iris" - "github.com/kataras/iris/core/host" ) func main() { @@ -15,20 +14,20 @@ func main() { ctx.HTML("

Hello, try to refresh the page after ~10 secs

") }) - // app.ConfigureHost(configureHost) -> or pass "configureHost" as `app.Addr` argument, same result. - app.Logger().Info("Wait 10 seconds and check your terminal again") // simulate a shutdown action here... go func() { <-time.After(10 * time.Second) timeout := 5 * time.Second - ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout) + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // close all hosts, this will notify the callback we had register // inside the `configureHost` func. app.Shutdown(ctx) }() + // app.ConfigureHost(configureHost) -> or pass "configureHost" as `app.Addr` argument, same result. + // start the server as usual, the only difference is that // we're adding a second (optional) function // to configure the just-created host supervisor. @@ -37,9 +36,14 @@ func main() { // wait 10 seconds and check your terminal. app.Run(iris.Addr(":8080", configureHost), iris.WithoutServerError(iris.ErrServerClosed)) + /* + Or for simple cases you can just use the: + iris.RegisterOnInterrupt for global catch of the CTRL/CMD+C and OS events. + Look at the "graceful-shutdown" example for more. + */ } -func configureHost(su *host.Supervisor) { +func configureHost(su *iris.Supervisor) { // here we have full access to the host that will be created // inside the `app.Run` function or `NewHost`. // diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000..8711dc1227 --- /dev/null +++ b/go.mod @@ -0,0 +1,68 @@ +module github.com/kataras/iris + +require ( + github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 // indirect + github.com/BurntSushi/toml v0.3.1 + github.com/Joker/jade v0.7.0 + github.com/Shopify/goreferrer v0.0.0-20180807163728-b9777dc9f9cc + github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f // indirect + github.com/aymerick/raymond v2.0.2+incompatible + github.com/boltdb/bolt v1.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgraph-io/badger v1.5.4 + github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 // indirect + github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 + github.com/etcd-io/bbolt v1.3.0 + github.com/fatih/structs v1.0.0 + github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0 + github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d // indirect + github.com/gomodule/redigo v2.0.0+incompatible + github.com/google/go-querystring v1.0.0 // indirect + github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c // indirect + github.com/gorilla/websocket v1.4.0 + github.com/imkira/go-interpol v1.1.0 // indirect + github.com/iris-contrib/formBinder v0.0.0-20171010160137-ad9fb86c356f + github.com/iris-contrib/go.uuid v2.0.0+incompatible + github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce + github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0 + github.com/json-iterator/go v1.1.5 + github.com/jtolds/gls v4.2.1+incompatible // indirect + github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 // indirect + github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect + github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect + github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect + github.com/kataras/golog v0.0.0-20180321173939-03be10146386 + github.com/kataras/pio v0.0.0-20180511174041-a9733b5b6b83 // indirect + github.com/klauspost/compress v1.4.0 + github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e // indirect + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + github.com/microcosm-cc/bluemonday v1.0.1 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b // indirect + github.com/onsi/gomega v1.4.2 // indirect + github.com/pkg/errors v0.8.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/ryanuber/columnize v2.1.0+incompatible + github.com/sergi/go-diff v1.0.0 // indirect + github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect + github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect + github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect + github.com/stretchr/testify v1.2.2 // indirect + github.com/valyala/bytebufferpool v1.0.0 + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 // indirect + github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect + github.com/yudai/gojsondiff v1.0.0 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yudai/pp v2.0.1+incompatible // indirect + golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 + golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 // indirect + golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 // indirect + gopkg.in/ini.v1 v1.38.3 // indirect + gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect + gopkg.in/russross/blackfriday.v2 v2.0.0+incompatible + gopkg.in/yaml.v2 v2.2.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000..40e4805455 --- /dev/null +++ b/go.sum @@ -0,0 +1,148 @@ +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 h1:PqzgE6kAMi81xWQA2QIVxjWkFHptGgC547vchpUbtFo= +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Joker/jade v0.7.0 h1:9sEpF/GAfkn0D0n+x8/SVGpxvdjIStsyPaTvtXW6z/U= +github.com/Joker/jade v0.7.0/go.mod h1:R1kvvouJogE6SnKqO5Qw3j2rCE2T9HjIWaFeSET/qMQ= +github.com/Shopify/goreferrer v0.0.0-20180807163728-b9777dc9f9cc h1:zZYkIbeMNcH1lhztdVxy4+Ykk8NoMhqUfSigsrT/x7Y= +github.com/Shopify/goreferrer v0.0.0-20180807163728-b9777dc9f9cc/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= +github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.5.4 h1:gVTrpUTbbr/T24uvoCaqY2KSHfNLVGm0w+hbee2HMeg= +github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 h1:afESQBXJEnj3fu+34X//E8Wg3nEbMJxJkwSc0tPePK0= +github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/etcd-io/bbolt v1.3.0 h1:ec0U3x11Mk69A8YwQyZEhNaUqHkQSv2gDR3Bioz5DfU= +github.com/etcd-io/bbolt v1.3.0/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0 h1:ZHx2BEERvWkuwuE7qWN9TuRxucHDH2JrsvneZjVJfo0= +github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0/go.mod h1:rE0ErqqBaMcp9pzj8JxV1GcfDBpuypXYxlR1c37AUwg= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d h1:oYXrtNhqNKL1dVtKdv8XUq5zqdGVFNQ0/4tvccXZOLM= +github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/iris-contrib/formBinder v0.0.0-20171010160137-ad9fb86c356f h1:WgD6cqCSncBgkftw34mSPlMKU5JgoruAomW/SJtRrGU= +github.com/iris-contrib/formBinder v0.0.0-20171010160137-ad9fb86c356f/go.mod h1:i8kTYUOEstd/S8TG0ChTXQdf4ermA/e8vJX0+QruD9w= +github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce h1:q8Ka/exfHNgK7izJE+aUOZd7KZXJ7oQbnJWiZakEiMo= +github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce/go.mod h1:VER17o2JZqquOx41avolD/wMGQSFEFBKWmhag9/RQRY= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0 h1:Kyp9KiXwsyZRTeoNjgVCrWks7D8ht9+kg6yCjh8K97o= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= +github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 h1:wnhMXidtb70kDZCeLt/EfsVtkXS5c8zLnE9y/6DIRAU= +github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 h1:WQM1NildKThwdP7qWrNAFGzp4ijNLw8RlgENkaI4MJs= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kataras/golog v0.0.0-20180321173939-03be10146386 h1:VT6AeCHO/mc+VedKBMhoqb5eAK8B1i9F6nZl7EGlHvA= +github.com/kataras/golog v0.0.0-20180321173939-03be10146386/go.mod h1:PcaEvfvhGsqwXZ6S3CgCbmjcp+4UDUh2MIfF2ZEul8M= +github.com/kataras/pio v0.0.0-20180511174041-a9733b5b6b83 h1:NoJ+fI58ptwrPc1blX116i+5xWGAY/2TJww37AN8X54= +github.com/kataras/pio v0.0.0-20180511174041-a9733b5b6b83/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= +github.com/klauspost/compress v1.4.0 h1:8nsMz3tWa9SWWPL60G1V6CUsf4lLjWLTNEtibhe8gh8= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e h1:+lIPJOWl+jSiJOc70QXJ07+2eg2Jy2EC7Mi11BWujeM= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b h1:Pip12xNtMvEFUBF4f8/b5yRXj94LLrNdLWELfOr2KcY= +github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 h1:BcMExZAULPkihVZ7UJXK7t8rwGqisXFw75tILnafhBY= +github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 h1:dgd4x4kJt7G4k4m93AYLzM8Ni6h2qLTfh9n9vXJT3/0= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.38.3 h1:ourkRZgR6qjJYoec9lYhX4+nuN1tEbV34dQEQ3IRk9U= +gopkg.in/ini.v1 v1.38.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/russross/blackfriday.v2 v2.0.0+incompatible h1:l1Mna0cVh8WlpyB8uFtc2c+5cdvrI5CDyuwTgIChojI= +gopkg.in/russross/blackfriday.v2 v2.0.0+incompatible/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/sessions/sessiondb/badger/database.go b/sessions/sessiondb/badger/database.go index 223deafeea..5c990e064b 100644 --- a/sessions/sessiondb/badger/database.go +++ b/sessions/sessiondb/badger/database.go @@ -7,11 +7,11 @@ import ( "sync/atomic" "time" - "github.com/kataras/golog" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" "github.com/dgraph-io/badger" + "github.com/kataras/golog" ) // DefaultFileMode used as the default database's "fileMode" @@ -144,7 +144,13 @@ func (db *Database) Get(sid string, key string) (value interface{}) { if err != nil { return err } - // item.ValueCopy + + // return item.Value(func(valueBytes []byte) { + // if err := sessions.DefaultTranscoder.Unmarshal(valueBytes, &value); err != nil { + // golog.Error(err) + // } + // }) + valueBytes, err := item.Value() if err != nil { return err @@ -173,13 +179,25 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) { for iter.Rewind(); iter.ValidForPrefix(prefix); iter.Next() { item := iter.Item() + var value interface{} + + // err := item.Value(func(valueBytes []byte) { + // if err := sessions.DefaultTranscoder.Unmarshal(valueBytes, &value); err != nil { + // golog.Error(err) + // } + // }) + + // if err != nil { + // golog.Error(err) + // continue + // } + valueBytes, err := item.Value() if err != nil { golog.Error(err) continue } - var value interface{} if err = sessions.DefaultTranscoder.Unmarshal(valueBytes, &value); err != nil { golog.Error(err) continue diff --git a/sessions/sessiondb/badger/vendor/badger.txt b/sessions/sessiondb/badger/vendor/badger.txt new file mode 100644 index 0000000000..f890a03872 --- /dev/null +++ b/sessions/sessiondb/badger/vendor/badger.txt @@ -0,0 +1,5 @@ +02 Oct 2018 +----------- +Keep this vendor because go modules uses the v1.5.4 which is the latest published release(~19 Sep 2018) +while the master repo will be used if host's golang version is lower than 1.11 or inside $GOPATH, which +has a breaking change on the `item.Value` input & output values, see the comments inside this `database.go`. \ No newline at end of file diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt deleted file mode 100644 index a12ff0084a..0000000000 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt +++ /dev/null @@ -1,3 +0,0 @@ -12 July 2018 - -Commit: 391b6d3b93e6014fe8c2971fcc0c1266e47dbbd9 \ No newline at end of file diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go index dc18576f3d..1bd59e031f 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go @@ -25,6 +25,7 @@ import ( "path/filepath" "strconv" "sync" + "sync/atomic" "time" "github.com/dgraph-io/badger/options" @@ -73,6 +74,8 @@ type DB struct { writeCh chan *request flushChan chan flushTask // For flushing memtables. + blockWrites int32 + orc *oracle } @@ -622,6 +625,9 @@ func (db *DB) writeRequests(reqs []*request) error { } func (db *DB) sendToWriteCh(entries []*Entry) (*request, error) { + if atomic.LoadInt32(&db.blockWrites) == 1 { + return nil, ErrBlockedWrites + } var count, size int64 for _, e := range entries { size += int64(e.estimateSize(db.opt.ValueThreshold)) @@ -850,7 +856,12 @@ func (db *DB) flushMemtable(lc *y.Closer) error { // Update s.imm. Need a lock. db.Lock() - y.AssertTrue(ft.mt == db.imm[0]) //For now, single threaded. + // This is a single-threaded operation. ft.mt corresponds to the head of + // db.imm list. Once we flush it, we advance db.imm. The next ft.mt + // which would arrive here would match db.imm[0], because we acquire a + // lock over DB when pushing to flushChan. + // TODO: This logic is dirty AF. Any change and this could easily break. + y.AssertTrue(ft.mt == db.imm[0]) db.imm = db.imm[1:] ft.mt.DecrRef() // Return memory. db.Unlock() @@ -905,7 +916,6 @@ func (db *DB) calculateSize() { _, vlogSize = totalSize(db.opt.ValueDir) } y.VlogSize.Set(db.opt.Dir, newInt(vlogSize)) - } func (db *DB) updateSize(lc *y.Closer) { @@ -1081,6 +1091,16 @@ func (db *DB) Tables() []TableInfo { return db.lc.getTableInfo() } +// MaxBatchCount returns max possible entries in batch +func (db *DB) MaxBatchCount() int64 { + return db.opt.maxBatchCount +} + +// MaxBatchCount returns max possible batch size +func (db *DB) MaxBatchSize() int64 { + return db.opt.maxBatchSize +} + // MergeOperator represents a Badger merge operator. type MergeOperator struct { sync.RWMutex diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/doc.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/doc.go deleted file mode 100644 index 83dc9a28ac..0000000000 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/doc.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -Package badger implements an embeddable, simple and fast key-value database, -written in pure Go. It is designed to be highly performant for both reads and -writes simultaneously. Badger uses Multi-Version Concurrency Control (MVCC), and -supports transactions. It runs transactions concurrently, with serializable -snapshot isolation guarantees. - -Badger uses an LSM tree along with a value log to separate keys from values, -hence reducing both write amplification and the size of the LSM tree. This -allows LSM tree to be served entirely from RAM, while the values are served -from SSD. - - -Usage - -Badger has the following main types: DB, Txn, Item and Iterator. DB contains -keys that are associated with values. It must be opened with the appropriate -options before it can be accessed. - -All operations happen inside a Txn. Txn represents a transaction, which can -be read-only or read-write. Read-only transactions can read values for a -given key (which are returned inside an Item), or iterate over a set of -key-value pairs using an Iterator (which are returned as Item type values as -well). Read-write transactions can also update and delete keys from the DB. - -See the examples for more usage details. -*/ -package badger diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go index c567d3c5e0..1de35826e9 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go @@ -96,6 +96,10 @@ var ( // ErrTruncateNeeded is returned when the value log gets corrupt, and requires truncation of // corrupt data to allow Badger to run properly. ErrTruncateNeeded = errors.New("Value log truncate required to run DB. This might result in data loss.") + + // ErrBlockedWrites is returned if the user called DropAll. During the process of dropping all + // data from Badger, we stop accepting new writes, by returning this error. + ErrBlockedWrites = errors.New("Writes are blocked possibly due to DropAll") ) // Key length can't be more than uint16, as determined by table::header. diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go index 9415396290..3a6bec9819 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go @@ -160,10 +160,15 @@ func (item *Item) yieldItemValue() ([]byte, func(), error) { var vp valuePointer vp.Decode(item.vptr) result, cb, err := item.db.vlog.Read(vp, item.slice) - if err != ErrRetry || bytes.HasPrefix(key, badgerMove) { - // The error is not retry, or we have already searched the move keyspace. + if err != ErrRetry { return result, cb, err } + if bytes.HasPrefix(key, badgerMove) { + // err == ErrRetry + // Error is retry even after checking the move keyspace. So, let's + // just assume that value is not present. + return nil, cb, nil + } // The value pointer is pointing to a deleted value log. Look for the // move key and read that instead. diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go index e7f01d2dc3..31b7fe6bfd 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go @@ -170,6 +170,62 @@ func (s *levelsController) cleanupLevels() error { return firstErr } +// This function picks all tables from all levels, creates a manifest changeset, +// applies it, and then decrements the refs of these tables, which would result +// in their deletion. It spares one table from L0, to keep the badgerHead key +// persisted, so we don't lose where we are w.r.t. value log. +// NOTE: This function in itself isn't sufficient to completely delete all the +// data. After this, one would still need to iterate over the KV pairs and mark +// them as deleted. +func (s *levelsController) deleteLSMTree() (int, error) { + var all []*table.Table + var keepOne *table.Table + for _, l := range s.levels { + l.RLock() + if l.level == 0 && len(l.tables) > 1 { + // Skip the last table. We do this to keep the badgerMove key persisted. + lastIdx := len(l.tables) - 1 + keepOne = l.tables[lastIdx] + all = append(all, l.tables[:lastIdx]...) + } else { + all = append(all, l.tables...) + } + l.RUnlock() + } + if len(all) == 0 { + return 0, nil + } + + // Generate the manifest changes. + changes := []*protos.ManifestChange{} + for _, table := range all { + changes = append(changes, makeTableDeleteChange(table.ID())) + } + changeSet := protos.ManifestChangeSet{Changes: changes} + if err := s.kv.manifest.addChanges(changeSet.Changes); err != nil { + return 0, err + } + + for _, l := range s.levels { + l.Lock() + l.totalSize = 0 + if l.level == 0 && len(l.tables) > 1 { + l.tables = []*table.Table{keepOne} + l.totalSize += keepOne.Size() + } else { + l.tables = l.tables[:0] + } + l.Unlock() + } + // Now allow deletion of tables. + for _, table := range all { + if err := table.DecrRef(); err != nil { + return 0, err + } + } + return len(all), nil +} + func (s *levelsController) startCompact(lc *y.Closer) { n := s.kv.opt.NumCompactors lc.AddRunning(n - 1) @@ -308,10 +364,10 @@ func (s *levelsController) compactBuildTables( it.Rewind() - // Pick up the currently pending transactions' min readTs, so we can discard versions below this - // readTs. We should never discard any versions starting from above this timestamp, because that - // would affect the snapshot view guarantee provided by transactions. - minReadTs := s.kv.orc.readMark.MinReadTs() + // Pick a discard ts, so we can discard versions below this ts. We should + // never discard any versions starting from above this timestamp, because + // that would affect the snapshot view guarantee provided by transactions. + discardTs := s.kv.orc.discardAtOrBelow() // Start generating new tables. type newTableResult struct { @@ -350,7 +406,7 @@ func (s *levelsController) compactBuildTables( vs := it.Value() version := y.ParseTs(it.Key()) - if version <= minReadTs { + if version <= discardTs { // Keep track of the number of versions encountered for this key. Only consider the // versions which are below the minReadTs, otherwise, we might end up discarding the // only valid version for a running transaction. diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go index adbec802b7..c3898130e9 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go @@ -16,6 +16,16 @@ package badger +import ( + "math" + "sync" + "sync/atomic" + "time" + + "github.com/dgraph-io/badger/y" + "github.com/pkg/errors" +) + // ManagedDB allows end users to manage the transactions themselves. Transaction // start and commit timestamps are set by end-user. // @@ -77,3 +87,107 @@ func (txn *Txn) CommitAt(commitTs uint64, callback func(error)) error { func (db *ManagedDB) GetSequence(_ []byte, _ uint64) (*Sequence, error) { panic("Cannot use GetSequence for ManagedDB.") } + +// SetDiscardTs sets a timestamp at or below which, any invalid or deleted +// versions can be discarded from the LSM tree, and thence from the value log to +// reclaim disk space. +func (db *ManagedDB) SetDiscardTs(ts uint64) { + db.orc.setDiscardTs(ts) +} + +var errDone = errors.New("Done deleting keys") + +// DropAll would drop all the data stored in Badger. It does this in the following way. +// - Stop accepting new writes. +// - Pause the compactions. +// - Pick all tables from all levels, create a changeset to delete all these +// tables and apply it to manifest. DO not pick up the latest table from level +// 0, to preserve the (persistent) badgerHead key. +// - Iterate over the KVs in Level 0, and run deletes on them via transactions. +// - The deletions are done at the same timestamp as the latest version of the +// key. Thus, we could write the keys back at the same timestamp as before. +func (db *ManagedDB) DropAll() error { + // Stop accepting new writes. + atomic.StoreInt32(&db.blockWrites, 1) + + // Wait for writeCh to reach size of zero. This is not ideal, but a very + // simple way to allow writeCh to flush out, before we proceed. + tick := time.NewTicker(100 * time.Millisecond) + for range tick.C { + if len(db.writeCh) == 0 { + break + } + } + tick.Stop() + + // Stop the compactions. + if db.closers.compactors != nil { + db.closers.compactors.SignalAndWait() + } + + _, err := db.lc.deleteLSMTree() + // Allow writes so that we can run transactions. Ideally, the user must ensure that they're not + // doing more writes concurrently while this operation is happening. + atomic.StoreInt32(&db.blockWrites, 0) + // Need compactions to happen so deletes below can be flushed out. + if db.closers.compactors != nil { + db.closers.compactors = y.NewCloser(1) + db.lc.startCompact(db.closers.compactors) + } + if err != nil { + return err + } + + type KV struct { + key []byte + version uint64 + } + + var kvs []KV + getKeys := func() error { + txn := db.NewTransactionAt(math.MaxUint64, false) + defer txn.Discard() + + opts := DefaultIteratorOptions + opts.PrefetchValues = false + itr := txn.NewIterator(opts) + defer itr.Close() + + for itr.Rewind(); itr.Valid(); itr.Next() { + item := itr.Item() + kvs = append(kvs, KV{item.KeyCopy(nil), item.Version()}) + } + return nil + } + if err := getKeys(); err != nil { + return err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + for _, kv := range kvs { + wg.Add(1) + txn := db.NewTransactionAt(math.MaxUint64, true) + if err := txn.Delete(kv.key); err != nil { + return err + } + if err := txn.CommitAt(kv.version, func(rerr error) { + if rerr != nil { + select { + case errCh <- rerr: + default: + } + } + wg.Done() + }); err != nil { + return err + } + } + wg.Wait() + select { + case err := <-errCh: + return err + default: + return nil + } +} diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go index f6d0abb172..21fc2a5a3a 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go @@ -124,7 +124,10 @@ var DefaultOptions = Options{ NumVersionsToKeep: 1, // Nothing to read/write value log using standard File I/O // MemoryMap to mmap() the value log files - ValueLogFileSize: 1 << 30, + // (2^30 - 1)*2 when mmapping < 2^31 - 1, max int32. + // -1 so 2*ValueLogFileSize won't overflow on 32-bit systems. + ValueLogFileSize: 1<<30 - 1, + ValueLogMaxEntries: 1000000, ValueThreshold: 32, Truncate: false, diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/README.md b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/README.md deleted file mode 100644 index 5d33e96ab5..0000000000 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# BenchmarkRead - -``` -$ go test -bench Read$ -count 3 - -Size of table: 105843444 -BenchmarkRead-8 3 343846914 ns/op -BenchmarkRead-8 3 351790907 ns/op -BenchmarkRead-8 3 351762823 ns/op -``` - -Size of table is 105,843,444 bytes, which is ~101M. - -The rate is ~287M/s which matches our read speed. This is using mmap. - -To read a 64M table, this would take ~0.22s, which is negligible. - -``` -$ go test -bench BenchmarkReadAndBuild -count 3 - -BenchmarkReadAndBuild-8 1 2341034225 ns/op -BenchmarkReadAndBuild-8 1 2346349671 ns/op -BenchmarkReadAndBuild-8 1 2364064576 ns/op -``` - -The rate is ~43M/s. To build a ~64M table, this would take ~1.5s. Note that this -does NOT include the flushing of the table to disk. All we are doing above is -to read one table (mmaped) and write one table in memory. - -The table building takes 1.5-0.22 ~ 1.3s. - -If we are writing out up to 10 tables, this would take 1.5*10 ~ 15s, and ~13s -is spent building the tables. - -When running populate, building one table in memory tends to take ~1.5s to ~2.5s -on my system. Where does this overhead come from? Let's investigate the merging. - -Below, we merge 5 tables. The total size remains unchanged at ~101M. - -``` -$ go test -bench ReadMerged -count 3 -BenchmarkReadMerged-8 1 1321190264 ns/op -BenchmarkReadMerged-8 1 1296958737 ns/op -BenchmarkReadMerged-8 1 1314381178 ns/op -``` - -The rate is ~76M/s. To build a 64M table, this would take ~0.84s. The writing -takes ~1.3s as we saw above. So in total, we expect around 0.84+1.3 ~ 2.1s. -This roughly matches what we observe when running populate. There might be -some additional overhead due to the concurrent writes going on, in flushing the -table to disk. Also, the tables tend to be slightly bigger than 64M/s. \ No newline at end of file diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go index 6ce3860525..32a6a96aec 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go @@ -40,7 +40,10 @@ type oracle struct { writeLock sync.Mutex nextCommit uint64 - readMark y.WaterMark + // Either of these is used to determine which versions can be permanently + // discarded during compaction. + discardTs uint64 // Used by ManagedDB. + readMark y.WaterMark // Used by DB. // commits stores a key fingerprint and latest commit counter for it. // refCount is used to clear out commits map to avoid a memory blowup. @@ -81,6 +84,23 @@ func (o *oracle) commitTs() uint64 { return o.nextCommit } +// Any deleted or invalid versions at or below ts would be discarded during +// compaction to reclaim disk space in LSM tree and thence value log. +func (o *oracle) setDiscardTs(ts uint64) { + o.Lock() + defer o.Unlock() + o.discardTs = ts +} + +func (o *oracle) discardAtOrBelow() uint64 { + if o.isManaged { + o.Lock() + defer o.Unlock() + return o.discardTs + } + return o.readMark.MinReadTs() +} + // hasConflict must be called while having a lock. func (o *oracle) hasConflict(txn *Txn) bool { if len(txn.reads) == 0 { diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go index d6b9e56356..6b8de919e1 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go @@ -754,7 +754,8 @@ func (vlog *valueLog) sortedFids() []uint32 { func (vlog *valueLog) Replay(ptr valuePointer, fn logEntry) error { fid := ptr.Fid offset := ptr.Offset + ptr.Len - vlog.elog.Printf("Seeking at value pointer: %+v\n", ptr) + // @kataras removed: vlog.elog.Printf("Seeking at value pointer: %+v\n", ptr) + // @kataras removed: log.Printf("Replaying from value pointer: %+v\n", ptr) fids := vlog.sortedFids() @@ -767,7 +768,11 @@ func (vlog *valueLog) Replay(ptr valuePointer, fn logEntry) error { of = 0 } f := vlog.filesMap[id] + // @kataras removed: + // log.Printf("Iterating file id: %d", id) + // now := time.Now() err := vlog.iterate(f, of, fn) + // @kataras removed: log.Printf("Iteration took: %s\n", time.Since(now)) if err != nil { return errors.Wrapf(err, "Unable to replay value log: %q", f.path) } @@ -857,7 +862,7 @@ func (vlog *valueLog) write(reqs []*request) error { } newid := atomic.AddUint32(&vlog.maxFid, 1) - y.AssertTruef(newid <= math.MaxUint32, "newid will overflow uint32: %v", newid) + y.AssertTruef(newid > 0, "newid has overflown uint32: %v", newid) newlf, err := vlog.createVlogFile(newid) if err != nil { return err From 4266698faa280164be247f3c069cdc85cdb504cb Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 2 Oct 2018 06:36:51 +0300 Subject: [PATCH 29/91] example: write our own customized router using the high-level API which gives access to the correct context and routes --- Dockerfile | 5 - Dockerfile.build | 12 -- _examples/README.md | 5 +- _examples/README_ZH.md | 3 + .../routing/custom-high-level-router/main.go | 103 ++++++++++++++++++ .../routing/custom-low-level-router/main.go | 6 + core/router/handler.go | 5 +- core/router/router.go | 24 +++- iris.go | 2 +- 9 files changed, 138 insertions(+), 27 deletions(-) delete mode 100644 Dockerfile delete mode 100644 Dockerfile.build create mode 100644 _examples/routing/custom-high-level-router/main.go create mode 100644 _examples/routing/custom-low-level-router/main.go diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 54e3725a70..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM irisgo/cloud-native-go:latest - -ENV APPSOURCES /go/src/github.com/iris-contrib/cloud-native-go - -RUN ${APPSOURCES}/cloud-native-go \ No newline at end of file diff --git a/Dockerfile.build b/Dockerfile.build deleted file mode 100644 index 6c127b0e21..0000000000 --- a/Dockerfile.build +++ /dev/null @@ -1,12 +0,0 @@ -FROM golang:1.9.3-alpine - -RUN apk update && apk upgrade && apk add --no-cache bash git -RUN go get github.com/iris-contrib/cloud-native-go - -ENV SOURCES /go/src/github.com/iris-contrib/cloud-native-go -# COPY . ${SOURCES} - -RUN cd ${SOURCES} $$ CGO_ENABLED=0 go build - -ENTRYPOINT cloud-native-go -EXPOSE 8080 \ No newline at end of file diff --git a/_examples/README.md b/_examples/README.md index 4caf6d3db4..e0b00d673e 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -146,8 +146,11 @@ Navigate through examples for a better understanding. - [Custom HTTP Errors](routing/http-errors/main.go) - [Dynamic Path](routing/dynamic-path/main.go) * [root level wildcard path](routing/dynamic-path/root-wildcard/main.go) +- [Write your own custom parameter types](routing/macros/main.go) **NEW** - [Reverse routing](routing/reverse/main.go) -- [Custom wrapper](routing/custom-wrapper/main.go) +- [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW** +- [Custom Router (low-level)](routing/custom-low-level-router/main.go) **NEW** +- [Custom Wrapper](routing/custom-wrapper/main.go) - Custom Context * [method overriding](routing/custom-context/method-overriding/main.go) * [new implementation](routing/custom-context/new-implementation/main.go) diff --git a/_examples/README_ZH.md b/_examples/README_ZH.md index a10ca6ada7..82b5648e07 100644 --- a/_examples/README_ZH.md +++ b/_examples/README_ZH.md @@ -105,7 +105,10 @@ app.Get("{root:path}", rootWildcardHandler) - [自定义 HTTP 错误](routing/http-errors/main.go) - [动态路径](routing/dynamic-path/main.go) * [根级通配符路径](routing/dynamic-path/root-wildcard/main.go) +- [Write your own custom parameter types](routing/macros/main.go) **NEW** - [反向路由](routing/reverse/main.go) +- [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW** +- [Custom Router (low-level)](routing/custom-low-level-router/main.go) **NEW** - [自定义包装](routing/custom-wrapper/main.go) - 自定义上下文    * [方法重写](routing/custom-context/method-overriding/main.go) diff --git a/_examples/routing/custom-high-level-router/main.go b/_examples/routing/custom-high-level-router/main.go new file mode 100644 index 0000000000..1557c31105 --- /dev/null +++ b/_examples/routing/custom-high-level-router/main.go @@ -0,0 +1,103 @@ +package main + +import ( + "strings" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + "github.com/kataras/iris/core/router" +) + +/* A Router should contain all three of the following methods: + - HandleRequest should handle the request based on the Context. + HandleRequest(ctx context.Context) + - Build should builds the handler, it's being called on router's BuildRouter. + Build(provider router.RoutesProvider) error + - RouteExists reports whether a particular route exists. + RouteExists(ctx context.Context, method, path string) bool + +For a more detailed, complete and useful example +you can take a look at the iris' router itself which is located at: +https://github.com/kataras/iris/tree/master/core/router/handler.go +which completes this exact interface, the `router#RequestHandler`. +*/ +type customRouter struct { + // a copy of routes (safer because you will not be able to alter a route on serve-time without a `app.RefreshRouter` call): + // []router.Route + // or just expect the whole routes provider: + provider router.RoutesProvider +} + +// HandleRequest a silly example which finds routes based only on the first part of the requested path +// which must be a static one as well, the rest goes to fill the parameters. +func (r *customRouter) HandleRequest(ctx context.Context) { + path := ctx.Path() + ctx.Application().Logger().Infof("Requested resource path: %s", path) + + parts := strings.Split(path, "/")[1:] + staticPath := "/" + parts[0] + for _, route := range r.provider.GetRoutes() { + if strings.HasPrefix(route.Path, staticPath) { + paramParts := parts[1:] + for _, paramValue := range paramParts { + for _, p := range route.Tmpl().Params { + ctx.Params().Set(p.Name, paramValue) + } + } + + ctx.SetCurrentRouteName(route.Name) + ctx.Do(route.Handlers) + return + } + } + + // if nothing found... + ctx.StatusCode(iris.StatusNotFound) +} + +func (r *customRouter) Build(provider router.RoutesProvider) error { + for _, route := range provider.GetRoutes() { + // do any necessary validation or conversations based on your custom logic here + // but always run the "BuildHandlers" for each registered route. + route.BuildHandlers() + // [...] r.routes = append(r.routes, *route) + } + + r.provider = provider + return nil +} + +func (r *customRouter) RouteExists(ctx context.Context, method, path string) bool { + // [...] + return false +} + +func main() { + app := iris.New() + + // In case you are wondering, the parameter types and macros like "{param:string $func()}" still work inside + // your custom router if you fetch by the Route's Handler + // because they are middlewares under the hood, so you don't have to implement the logic of handling them manually, + // though you have to match what requested path is what route and fill the ctx.Params(), this is the work of your custom router. + app.Get("/hello/{name}", func(ctx context.Context) { + name := ctx.Params().Get("name") + ctx.Writef("Hello %s\n", name) + }) + + app.Get("/cs/{num:uint64 min(10) else 400}", func(ctx context.Context) { + num := ctx.Params().GetUint64Default("num", 0) + ctx.Writef("num is: %d\n", num) + }) + + // To replace the existing router with a customized one by using the iris/context.Context + // you have to use the `app.BuildRouter` method before `app.Run` and after the routes registered. + // You should pass your custom router's instance as the second input arg, which must completes the `router#RequestHandler` + // interface as shown above. + // + // To see how you can build something even more low-level without direct iris' context support (you can do that manually as well) + // navigate to the "custom-wrapper" example instead. + myCustomRouter := new(customRouter) + app.BuildRouter(app.ContextPool, myCustomRouter, app.APIBuilder, true) + + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) +} diff --git a/_examples/routing/custom-low-level-router/main.go b/_examples/routing/custom-low-level-router/main.go new file mode 100644 index 0000000000..d1aac6ed77 --- /dev/null +++ b/_examples/routing/custom-low-level-router/main.go @@ -0,0 +1,6 @@ +/// TODO: showcase the `app.Downgrade` feature tomorrow if not already existing elsewhere. +package main + +func main() { + panic("TODO") +} diff --git a/core/router/handler.go b/core/router/handler.go index 24ecfc5d74..f16e1df019 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -17,10 +17,9 @@ import ( // RequestHandler the middle man between acquiring a context and releasing it. // By-default is the router algorithm. type RequestHandler interface { - // HandleRequest is same as context.Handler but its usage is only about routing, - // separate the concept here. + // HandleRequest should handle the request based on the Context. HandleRequest(context.Context) - // Build should builds the handler, it's being called on router's BuildRouter. + // Build should builds the handler, it's being called on router's BuildRouter. Build(provider RoutesProvider) error // RouteExists reports whether a particular route exists. RouteExists(ctx context.Context, method, path string) bool diff --git a/core/router/router.go b/core/router/router.go index 50526395b5..f4e9840d76 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -31,7 +31,7 @@ func NewRouter() *Router { return &Router{} } // RefreshRouter re-builds the router. Should be called when a route's state // changed (i.e Method changed at serve-time). func (router *Router) RefreshRouter() error { - return router.BuildRouter(router.cPool, router.requestHandler, router.routesProvider) + return router.BuildRouter(router.cPool, router.requestHandler, router.routesProvider, true) } // BuildRouter builds the router based on @@ -41,7 +41,7 @@ func (router *Router) RefreshRouter() error { // its wrapper. // // Use of RefreshRouter to re-build the router if needed. -func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHandler, routesProvider RoutesProvider) error { +func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHandler, routesProvider RoutesProvider, force bool) error { if requestHandler == nil { return errors.New("router: request handler is nil") @@ -60,9 +60,23 @@ func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHan defer router.mu.Unlock() // store these for RefreshRouter's needs. - router.cPool = cPool - router.requestHandler = requestHandler - router.routesProvider = routesProvider + if force { + router.cPool = cPool + router.requestHandler = requestHandler + router.routesProvider = routesProvider + } else { + if router.cPool == nil { + router.cPool = cPool + } + + if router.requestHandler == nil { + router.requestHandler = requestHandler + } + + if router.routesProvider == nil && routesProvider != nil { + router.routesProvider = routesProvider + } + } // the important router.mainHandler = func(w http.ResponseWriter, r *http.Request) { diff --git a/iris.go b/iris.go index 8cab499964..5660847ba7 100644 --- a/iris.go +++ b/iris.go @@ -761,7 +761,7 @@ func (app *Application) Build() error { // create the request handler, the default routing handler routerHandler := router.NewDefaultHandler() - rp.Describe("router: %v", app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder)) + rp.Describe("router: %v", app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder, false)) // re-build of the router from outside can be done with; // app.RefreshRouter() } From 5e79a86c5a0f528bc12020ffbcb0904f12832313 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 3 Oct 2018 20:39:38 +0300 Subject: [PATCH 30/91] context#ErrEmptyForm --- context/context.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/context/context.go b/context/context.go index b70624284b..8f2d40c679 100644 --- a/context/context.go +++ b/context/context.go @@ -2414,6 +2414,9 @@ var ( errReadBody = errors.New("while trying to read %s from the request body. Trace %s") ) +// ErrEmptyForm will be thrown from `context#ReadForm` when empty request data. +var ErrEmptyForm = errors.New("form data: empty") + // ReadForm binds the formObject with the form data // it supports any kind of struct. // @@ -2421,7 +2424,7 @@ var ( func (ctx *context) ReadForm(formObject interface{}) error { values := ctx.FormValues() if values == nil { - return errors.New("An empty form passed on ReadForm") + return ErrEmptyForm } // or dec := formbinder.NewDecoder(&formbinder.DecoderOptions{TagName: "form"}) From 617b9e72495702fa77b456b68a3e5a80c91a9479 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 3 Oct 2018 20:49:49 +0300 Subject: [PATCH 31/91] context#ErrEmptyForm --- context/context.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/context/context.go b/context/context.go index a213b3b76f..e39d29fd4e 100644 --- a/context/context.go +++ b/context/context.go @@ -2289,6 +2289,9 @@ var ( errReadBody = errors.New("while trying to read %s from the request body. Trace %s") ) +// ErrEmptyForm will be thrown from `context#ReadForm` when empty request data. +var ErrEmptyForm = errors.New("form data: empty") + // ReadForm binds the formObject with the form data // it supports any kind of struct. // @@ -2296,7 +2299,7 @@ var ( func (ctx *context) ReadForm(formObject interface{}) error { values := ctx.FormValues() if values == nil { - return errors.New("An empty form passed on ReadForm") + return ErrEmptyForm } // or dec := formbinder.NewDecoder(&formbinder.DecoderOptions{TagName: "form"}) From 082ead79171b2ed7526bc6ab71981a5fca280275 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 4 Oct 2018 12:05:55 +0300 Subject: [PATCH 32/91] `context#ReadForm`: do not return an error if request data are not there, instead return nil without touching the ptr value. Requested at: https://github.com/kataras/iris/issues/1095 --- context/context.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/context/context.go b/context/context.go index e39d29fd4e..a7ad80b183 100644 --- a/context/context.go +++ b/context/context.go @@ -560,7 +560,8 @@ type Context interface { // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go ReadXML(xmlObjectPtr interface{}) error // ReadForm binds the formObject with the form data - // it supports any kind of struct. + // it supports any kind of type, including custom structs. + // It will return nothing if request data are empty. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go ReadForm(formObjectPtr interface{}) error @@ -2289,17 +2290,15 @@ var ( errReadBody = errors.New("while trying to read %s from the request body. Trace %s") ) -// ErrEmptyForm will be thrown from `context#ReadForm` when empty request data. -var ErrEmptyForm = errors.New("form data: empty") - // ReadForm binds the formObject with the form data -// it supports any kind of struct. +// it supports any kind of type, including custom structs. +// It will return nothing if request data are empty. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go func (ctx *context) ReadForm(formObject interface{}) error { values := ctx.FormValues() - if values == nil { - return ErrEmptyForm + if len(values) == 0 { + return nil } // or dec := formbinder.NewDecoder(&formbinder.DecoderOptions{TagName: "form"}) From abfc29403a1debc1488014e8f5a2d54f91fe042b Mon Sep 17 00:00:00 2001 From: Fedir RYKHTIK Date: Sun, 14 Oct 2018 20:50:17 +0200 Subject: [PATCH 33/91] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f801ffee8b..b16ef2808c 100644 --- a/README.md +++ b/README.md @@ -636,7 +636,7 @@ func main() { // Register validation for 'User' // NOTE: only have to register a non-pointer type for 'User', validator - // interanlly dereferences during it's type checks. + // internally dereferences during it's type checks. validate.RegisterStructValidation(UserStructLevelValidation, User{}) app := iris.New() From 17da20763f3ada965e85e51d8c5b72f258d08d67 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 15 Oct 2018 10:49:09 +0300 Subject: [PATCH 34/91] add my new trie data structure implementation written from scratch and specifically designed for HTTP (and Iris) - see https://github.com/kataras/muxie for the net/http version of it --- HISTORY.md | 46 +- README.md | 2 +- _examples/README.md | 7 +- _examples/README_ZH.md | 7 +- _examples/overview/main.go | 2 +- _examples/routing/README.md | 129 +++-- _examples/routing/basic/main.go | 2 +- .../routing/custom-high-level-router/main.go | 2 +- .../routing/custom-low-level-router/main.go | 6 - _examples/routing/dynamic-path/main.go | 4 +- .../webassembly/basic/client/hello_go111.go | 2 +- context/request_params.go | 7 + core/router/api_builder_benchmark_test.go | 2 +- core/router/handler.go | 128 +++-- core/router/node/node.go | 448 ------------------ core/router/path.go | 14 +- core/router/path_test.go | 4 +- core/router/router_wildcard_root_test.go | 13 +- core/router/trie.go | 267 +++++++++++ doc.go | 84 +++- macro/handler/handler.go | 2 +- macro/interpreter/lexer/lexer_test.go | 32 +- macro/interpreter/parser/parser_test.go | 9 +- macro/interpreter/token/token.go | 2 +- 24 files changed, 574 insertions(+), 647 deletions(-) delete mode 100644 _examples/routing/custom-low-level-router/main.go delete mode 100644 core/router/node/node.go create mode 100644 core/router/trie.go diff --git a/HISTORY.md b/HISTORY.md index f1c56f5dd7..34a6e675e3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -22,29 +22,41 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene ## Breaking changes - Remove the "Configurator" `WithoutVersionChecker` and the configuration field `DisableVersionChecker` -- `:int` (or its new alias `:number`) macro route path parameter type **can accept negative numbers now**. Recommendation: `:int` should be replaced with the more generic numeric parameter type `:number` if possible (although it will stay for backwards-compatibility) +- `:int` (or its new alias `:number`) macro route path parameter type **can accept negative numbers now**. Recommendation: `:int` should be replaced with the more generic numeric parameter type `:int` if possible (although it will stay for backwards-compatibility) ## Routing -- `:number` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers and without digits limitation +- `:int` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers +- Add `:int8` parameter type and `ctx.Params().GetInt8` +- Add `:int16` parameter type and `ctx.Params().GetInt16` +- Add `:int32` parameter type and `ctx.Params().GetInt32` - Add `:int64` parameter type and `ctx.Params().GetInt64` +- Add `:uint` parameter type and `ctx.Params().GetUint` - Add `:uint8` parameter type and `ctx.Params().GetUint8` +- Add `:uint16` parameter type and `ctx.Params().GetUint16` +- Add `:uint32` parameter type and `ctx.Params().GetUint32` - Add `:uint64` parameter type and `ctx.Params().GetUint64` -- `:bool` parameter type as an alias for `:boolean` +- Add alias `:bool` for the `:boolean` parameter type Here is the full list of the built'n parameter types that we support now, including their validations/path segment rules. -| Param Type | Go Type | Validation | -| -----------|---------|------------| -| `:string` | string | anything | -| `:number` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive or negative number, any number of digits | -| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | -| `:uint8` | uint8 | 0 to 255 | -| `:uint64` | uint64 | 0 to 18446744073709551615 | -| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | -| `:alphabetical` | string | lowercase or uppercase letters | -| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | -| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | +| Param Type | Go Type | Validation | Retrieve Helper | +| -----------------|------|-------------|------| +| `:string` | string | anything (single path segment) | `Params().Get` | +| `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` | +| `:int8` | int8 | -128 to 127 | `Params().GetInt8` | +| `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` | +| `:int32` | int32 | -2147483648 to 2147483647 | `Params().GetInt32` | +| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | +| `:uint` | uint | 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32), depends on the host arch | `Params().GetUint` | +| `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | +| `:uint16` | uint16 | 0 to 65535 | `Params().GetUint16` | +| `:uint32` | uint32 | 0 to 4294967295 | `Params().GetUint32` | +| `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | +| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | `Params().GetBool` | +| `:alphabetical` | string | lowercase or uppercase letters | `Params().Get` | +| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | `Params().Get` | +| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | `Params().Get` | **Usage**: @@ -61,9 +73,9 @@ app.Get("/users/{id:uint64}", func(ctx iris.Context){ | `prefix`(prefix string) | :string | | `suffix`(suffix string) | :string | | `contains`(s string) | :string | -| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | -| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :number, :int64, :uint8, :uint64 | -| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :number, :int64, :uint8, :uint64 | +| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | **Usage**: diff --git a/README.md b/README.md index 23b860e9ad..1183f7e153 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ func main() { | Param Type | Go Type | Validation | Retrieve Helper | | -----------------|------|-------------|------| -| `:string` | string | anything | `Params().Get` | +| `:string` | string | anything (single path segment) | `Params().Get` | | `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` | | `:int8` | int8 | -128 to 127 | `Params().GetInt8` | | `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` | diff --git a/_examples/README.md b/_examples/README.md index e0b00d673e..fd0f4f7ce5 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -104,7 +104,7 @@ Structuring depends on your own needs. We can't tell you how to design your own ### Routing, Grouping, Dynamic Path Parameters, "Macros" and Custom Context -* `app.Get("{userid:number min(1)}", myHandler)` +* `app.Get("{userid:int min(1)}", myHandler)` * `app.Post("{asset:path}", myHandler)` * `app.Put("{custom:string regexp([a-z]+)}", myHandler)` @@ -128,10 +128,10 @@ app.Get("/profile/me", userHandler) // Matches all GET requests prefixed with /users/ // and followed by a number which should be equal or bigger than 1 -app.Get("/user/{userid:number min(1)}", getUserHandler) +app.Get("/user/{userid:int min(1)}", getUserHandler) // Matches all requests DELETE prefixed with /users/ // and following by a number which should be equal or bigger than 1 -app.Delete("/user/{userid:number min(1)}", deleteUserHandler) +app.Delete("/user/{userid:int min(1)}", deleteUserHandler) // Matches all GET requests except "/", "/about", anything starts with "/assets/" etc... // because it does not conflict with the rest of the routes. @@ -149,7 +149,6 @@ Navigate through examples for a better understanding. - [Write your own custom parameter types](routing/macros/main.go) **NEW** - [Reverse routing](routing/reverse/main.go) - [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW** -- [Custom Router (low-level)](routing/custom-low-level-router/main.go) **NEW** - [Custom Wrapper](routing/custom-wrapper/main.go) - Custom Context * [method overriding](routing/custom-context/method-overriding/main.go) diff --git a/_examples/README_ZH.md b/_examples/README_ZH.md index 82b5648e07..1fd80a8664 100644 --- a/_examples/README_ZH.md +++ b/_examples/README_ZH.md @@ -63,7 +63,7 @@ Iris 是个底层框架, 对 MVC 模式有很好的支持,但不限制文件 ### 路由、路由分组、路径动态参数、路由参数处理宏 、 自定义上下文 -* `app.Get("{userid:number min(1)}", myHandler)` +* `app.Get("{userid:int min(1)}", myHandler)` * `app.Post("{asset:path}", myHandler)` * `app.Put("{custom:string regexp([a-z]+)}", myHandler)` @@ -87,10 +87,10 @@ app.Get("/profile/me", userHandler) // 匹配所有前缀为 /users/ 的 GET 请求 // 参数为数字,且 >= 1 -app.Get("/user/{userid:number min(1)}", getUserHandler) +app.Get("/user/{userid:int min(1)}", getUserHandler) // 匹配所有前缀为 /users/ 的 DELETE 请求 // 参数为数字,且 >= 1 -app.Delete("/user/{userid:number min(1)}", deleteUserHandler) +app.Delete("/user/{userid:int min(1)}", deleteUserHandler) // 匹配所有 GET 请求,除了 "/", "/about", 或其他以 "/assets/" 开头 // 因为它不会与其他路线冲突。 @@ -108,7 +108,6 @@ app.Get("{root:path}", rootWildcardHandler) - [Write your own custom parameter types](routing/macros/main.go) **NEW** - [反向路由](routing/reverse/main.go) - [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW** -- [Custom Router (low-level)](routing/custom-low-level-router/main.go) **NEW** - [自定义包装](routing/custom-wrapper/main.go) - 自定义上下文    * [方法重写](routing/custom-context/method-overriding/main.go) diff --git a/_examples/overview/main.go b/_examples/overview/main.go index 30714a0985..d416f29ee3 100644 --- a/_examples/overview/main.go +++ b/_examples/overview/main.go @@ -68,7 +68,7 @@ func main() { usersRoutes := app.Party("/users", logThisMiddleware) { // Method GET: http://localhost:8080/users/42 - usersRoutes.Get("/{id:number min(1)}", getUserByID) + usersRoutes.Get("/{id:int min(1)}", getUserByID) // Method POST: http://localhost:8080/users/create usersRoutes.Post("/create", createUser) } diff --git a/_examples/routing/README.md b/_examples/routing/README.md index 40074682dc..88c40db999 100644 --- a/_examples/routing/README.md +++ b/_examples/routing/README.md @@ -152,26 +152,62 @@ Standard macro types for route path parameters | {param:string} | +------------------------+ string type -anything +anything (single path segmnent) +-------------------------------+ -| {param:number} or {param:int} | +| {param:int} | +-------------------------------+ int type -both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch) +-9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch -+-------------------------------+ -| {param:long} or {param:int64} | -+-------------------------------+ ++------------------------+ +| {param:int8} | ++------------------------+ +int8 type +-128 to 127 + ++------------------------+ +| {param:int16} | ++------------------------+ +int16 type +-32768 to 32767 + ++------------------------+ +| {param:int32} | ++------------------------+ +int32 type +-2147483648 to 2147483647 + ++------------------------+ +| {param:int64} | ++------------------------+ int64 type -9223372036854775808 to 9223372036854775807 ++------------------------+ +| {param:uint} | ++------------------------+ +uint type +0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32) + +------------------------+ | {param:uint8} | +------------------------+ uint8 type 0 to 255 ++------------------------+ +| {param:uint16} | ++------------------------+ +uint16 type +0 to 65535 + ++------------------------+ +| {param:uint32} | ++------------------------+ +uint32 type +0 to 4294967295 + +------------------------+ | {param:uint64} | +------------------------+ @@ -206,8 +242,8 @@ no spaces ! or other character | {param:path} | +------------------------+ path type -anything, should be the last part, more than one path segment, -i.e: /path1/path2/path3 , ctx.Params().Get("param") == "/path1/path2/path3" +anything, should be the last part, can be more than one path segment, +i.e: "/test/*param" and request: "/test/path1/path2/path3" , ctx.Params().Get("param") == "path1/path2/path3" ``` If type is missing then parameter's type is defaulted to string, so @@ -221,21 +257,24 @@ you are able to register your own too!. Register a named path parameter function ```go -app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool { +app.Macros().Get("int").RegisterFunc("min", func(argument int) func(paramValue int) bool { // [...] - return true - // -> true means valid, false means invalid fire 404 or if "else 500" is appended to the macro syntax then internal server error. + return func(paramValue int) bool { + // -> true means valid, false means invalid fire 404 + // or if "else 500" is appended to the macro syntax then internal server error. + return true + } }) ``` -At the `func(argument ...)` you can have any standard type, it will be validated before the server starts so don't care about any performance cost there, the only thing it runs at serve time is the returning `func(paramValue string) bool`. +At the `func(argument ...)` you can have any standard type, it will be validated before the server starts so don't care about any performance cost there, the only thing it runs at serve time is the returning `func(paramValue ) bool`. ```go {param:string equal(iris)} ``` The "iris" will be the argument here: ```go -app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool { +app.Macros().Get("string").RegisterFunc("equal", func(argument string) func(paramValue string) bool { return func(paramValue string){ return argument == paramValue } }) ``` @@ -249,20 +288,16 @@ app.Get("/username/{name}", func(ctx iris.Context) { ctx.Writef("Hello %s", ctx.Params().Get("name")) }) // type is missing = {name:string} -// Let's register our first macro attached to number macro type. +// Let's register our first macro attached to int macro type. // "min" = the function // "minValue" = the argument of the function -// func(string) bool = the macro's path parameter evaluator, this executes in serve time when -// a user requests a path which contains the :number macro type with the min(...) macro parameter function. -app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool { +// func() bool = the macro's path parameter evaluator, this executes in serve time when +// a user requests a path which contains the :int macro type with the min(...) macro parameter function. +app.Macros().Get("int").RegisterFunc("min", func(minValue int) func(int) bool { // do anything before serve here [...] // at this case we don't need to do anything - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= minValue + return func(paramValue int) bool { + return paramValue >= minValue } }) @@ -789,6 +824,32 @@ type Context interface { // IsStopped checks and returns true if the current position of the Context is 255, // means that the StopExecution() was called. IsStopped() bool + // OnConnectionClose registers the "cb" function which will fire (on its own goroutine, no need to be registered goroutine by the end-dev) + // when the underlying connection has gone away. + // + // This mechanism can be used to cancel long operations on the server + // if the client has disconnected before the response is ready. + // + // It depends on the `http#CloseNotify`. + // CloseNotify may wait to notify until Request.Body has been + // fully read. + // + // After the main Handler has returned, there is no guarantee + // that the channel receives a value. + // + // Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported). + // The "cb" will not fire for sure if the output value is false. + // + // Note that you can register only one callback for the entire request handler chain/per route. + // + // Look the `ResponseWriter#CloseNotifier` for more. + OnConnectionClose(fnGoroutine func()) bool + // OnClose registers the callback function "cb" to the underline connection closing event using the `Context#OnConnectionClose` + // and also in the end of the request handler using the `ResponseWriter#SetBeforeFlush`. + // Note that you can register only one callback for the entire request handler chain/per route. + // + // Look the `Context#OnConnectionClose` and `ResponseWriter#SetBeforeFlush` for more. + OnClose(cb func()) // +------------------------------------------------------------+ // | Current "user/request" storage | @@ -868,8 +929,12 @@ type Context interface { // // Keep note that this checks the "User-Agent" request header. IsMobile() bool + // GetReferrer extracts and returns the information from the "Referer" header as specified + // in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + // or by the URL query parameter "referer". + GetReferrer() Referrer // +------------------------------------------------------------+ - // | Response Headers helpers | + // | Headers helpers | // +------------------------------------------------------------+ // Header adds a header to the response writer. @@ -880,16 +945,18 @@ type Context interface { // GetContentType returns the response writer's header value of "Content-Type" // which may, setted before with the 'ContentType'. GetContentType() string + // GetContentType returns the request's header value of "Content-Type". + GetContentTypeRequested() string // GetContentLength returns the request's header value of "Content-Length". // Returns 0 if header was unable to be found or its value was not a valid number. GetContentLength() int64 // StatusCode sets the status code header to the response. - // Look .GetStatusCode too. + // Look .`GetStatusCode` too. StatusCode(statusCode int) // GetStatusCode returns the current status code of the response. - // Look StatusCode too. + // Look `StatusCode` too. GetStatusCode() int // Redirect sends a redirect response to the client @@ -924,6 +991,9 @@ type Context interface { // URLParamIntDefault returns the url query parameter as int value from a request, // if not found or parse failed then "def" is returned. URLParamIntDefault(name string, def int) int + // URLParamInt32Default returns the url query parameter as int32 value from a request, + // if not found or parse failed then "def" is returned. + URLParamInt32Default(name string, def int32) int32 // URLParamInt64 returns the url query parameter as int64 value from a request, // returns -1 and an error if parse failed. URLParamInt64(name string) (int64, error) @@ -1071,6 +1141,10 @@ type Context interface { // Examples of usage: context.ReadJSON, context.ReadXML. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go + // + // UnmarshalBody does not check about gzipped data. + // Do not rely on compressed data incoming to your server. The main reason is: https://en.wikipedia.org/wiki/Zip_bomb + // However you are still free to read the `ctx.Request().Body io.Reader` manually. UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error // ReadJSON reads JSON from request's body and binds it to a pointer of a value of any json-valid type. // @@ -1081,7 +1155,8 @@ type Context interface { // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go ReadXML(xmlObjectPtr interface{}) error // ReadForm binds the formObject with the form data - // it supports any kind of struct. + // it supports any kind of type, including custom structs. + // It will return nothing if request data are empty. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go ReadForm(formObjectPtr interface{}) error diff --git a/_examples/routing/basic/main.go b/_examples/routing/basic/main.go index 9a62bdef92..d03ea46c0d 100644 --- a/_examples/routing/basic/main.go +++ b/_examples/routing/basic/main.go @@ -103,7 +103,7 @@ func main() { ctx.Writef("All users") }) // http://v1.localhost:8080/api/users/42 - usersAPI.Get("/{userid:number}", func(ctx iris.Context) { + usersAPI.Get("/{userid:int}", func(ctx iris.Context) { ctx.Writef("user with id: %s", ctx.Params().Get("userid")) }) } diff --git a/_examples/routing/custom-high-level-router/main.go b/_examples/routing/custom-high-level-router/main.go index 1557c31105..77830024c5 100644 --- a/_examples/routing/custom-high-level-router/main.go +++ b/_examples/routing/custom-high-level-router/main.go @@ -37,7 +37,7 @@ func (r *customRouter) HandleRequest(ctx context.Context) { parts := strings.Split(path, "/")[1:] staticPath := "/" + parts[0] for _, route := range r.provider.GetRoutes() { - if strings.HasPrefix(route.Path, staticPath) { + if strings.HasPrefix(route.Path, staticPath) && route.Method == ctx.Method() { paramParts := parts[1:] for _, paramValue := range paramParts { for _, p := range route.Tmpl().Params { diff --git a/_examples/routing/custom-low-level-router/main.go b/_examples/routing/custom-low-level-router/main.go deleted file mode 100644 index d1aac6ed77..0000000000 --- a/_examples/routing/custom-low-level-router/main.go +++ /dev/null @@ -1,6 +0,0 @@ -/// TODO: showcase the `app.Downgrade` feature tomorrow if not already existing elsewhere. -package main - -func main() { - panic("TODO") -} diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 6d11a878a1..d94b0c1840 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -35,7 +35,7 @@ func main() { // anything // // +-------------------------------+ - // | {param:int} or {param:number} | + // | {param:int} or {param:int} | // +-------------------------------+ // int type // both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch) @@ -209,7 +209,7 @@ func main() { // http://localhost:8080/game/a-zA-Z/level/42 // remember, alphabetical is lowercase or uppercase letters only. - app.Get("/game/{name:alphabetical}/level/{level:number}", func(ctx iris.Context) { + app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) { ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level")) }) diff --git a/_examples/webassembly/basic/client/hello_go111.go b/_examples/webassembly/basic/client/hello_go111.go index 1822fc7997..97dda1f577 100644 --- a/_examples/webassembly/basic/client/hello_go111.go +++ b/_examples/webassembly/basic/client/hello_go111.go @@ -1,4 +1,4 @@ -// +build go1.11 +// +build js package main diff --git a/context/request_params.go b/context/request_params.go index 860ccf901a..fc1b47c819 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -16,6 +16,13 @@ type RequestParams struct { memstore.Store } +// Set inserts a value to the key-value storage. +// +// See `SetImmutable` and `Get` too. +func (r *RequestParams) Set(key, value string) { + r.Store.Set(key, value) +} + // GetEntryAt will return the parameter's internal store's `Entry` based on the index. // If not found it will return an emptry `Entry`. func (r *RequestParams) GetEntryAt(index int) memstore.Entry { diff --git a/core/router/api_builder_benchmark_test.go b/core/router/api_builder_benchmark_test.go index 54469636cd..e16f798cb6 100644 --- a/core/router/api_builder_benchmark_test.go +++ b/core/router/api_builder_benchmark_test.go @@ -56,7 +56,7 @@ func genPaths(routesLength, minCharLength, maxCharLength int) []string { b.WriteString(randStringBytesMaskImprSrc(pathSegmentCharsLength)) b.WriteString("/{name:string}/") // sugar. b.WriteString(randStringBytesMaskImprSrc(pathSegmentCharsLength)) - b.WriteString("/{age:number}/end") + b.WriteString("/{age:int}/end") paths[i] = b.String() b.Reset() diff --git a/core/router/handler.go b/core/router/handler.go index f16e1df019..cc91819f19 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -11,7 +11,6 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/netutil" - "github.com/kataras/iris/core/router/node" ) // RequestHandler the middle man between acquiring a context and releasing it. @@ -25,25 +24,17 @@ type RequestHandler interface { RouteExists(ctx context.Context, method, path string) bool } -type tree struct { - Method string - // subdomain is empty for default-hostname routes, - // ex: mysubdomain. - Subdomain string - Nodes *node.Nodes -} - type routerHandler struct { - trees []*tree + trees []*trie hosts bool // true if at least one route contains a Subdomain. } var _ RequestHandler = &routerHandler{} -func (h *routerHandler) getTree(method, subdomain string) *tree { +func (h *routerHandler) getTree(method, subdomain string) *trie { for i := range h.trees { t := h.trees[i] - if t.Method == method && t.Subdomain == subdomain { + if t.method == method && t.subdomain == subdomain { return t } } @@ -63,12 +54,14 @@ func (h *routerHandler) addRoute(r *Route) error { t := h.getTree(method, subdomain) if t == nil { - n := node.Nodes{} + n := newTrieNode() // first time we register a route to this method with this subdomain - t = &tree{Method: method, Subdomain: subdomain, Nodes: &n} + t = &trie{method: method, subdomain: subdomain, root: n} h.trees = append(h.trees, t) } - return t.Nodes.Add(routeName, path, handlers) + + t.insert(path, routeName, handlers) + return nil } // NewDefaultHandler returns the handler which is responsible @@ -188,11 +181,11 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { for i := range h.trees { t := h.trees[i] - if method != t.Method { + if method != t.method { continue } - if h.hosts && t.Subdomain != "" { + if h.hosts && t.subdomain != "" { requestHost := ctx.Host() if netutil.IsLoopbackSubdomain(requestHost) { // this fixes a bug when listening on @@ -201,7 +194,7 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { continue // it's not a subdomain, it's something like 127.0.0.1 probably } // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty - if t.Subdomain == SubdomainWildcardIndicator { + if t.subdomain == SubdomainWildcardIndicator { // mydomain.com -> invalid // localhost -> invalid // sub.mydomain.com -> valid @@ -219,14 +212,14 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { continue } // continue to that, any subdomain is valid. - } else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot. + } else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot. continue } } - routeName, handlers := t.Nodes.Find(path, ctx.Params()) - if len(handlers) > 0 { - ctx.SetCurrentRouteName(routeName) - ctx.Do(handlers) + n := t.search(path, ctx.Params()) + if n != nil { + ctx.SetCurrentRouteName(n.RouteName) + ctx.Do(n.Handlers) // found return } @@ -237,15 +230,12 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { if ctx.Application().ConfigurationReadOnly().GetFireMethodNotAllowed() { for i := range h.trees { t := h.trees[i] - // a bit slower than previous implementation but @kataras let me to apply this change - // because it's more reliable. - // // if `Configuration#FireMethodNotAllowed` is kept as defaulted(false) then this function will not // run, therefore performance kept as before. - if t.Nodes.Exists(path) { + if h.subdomainAndPathAndMethodExists(ctx, t, "", path) { // RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html // The response MUST include an Allow header containing a list of valid methods for the requested resource. - ctx.Header("Allow", t.Method) + ctx.Header("Allow", t.method) ctx.StatusCode(http.StatusMethodNotAllowed) return } @@ -255,55 +245,55 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { ctx.StatusCode(http.StatusNotFound) } -// RouteExists reports whether a particular route exists -// It will search from the current subdomain of context's host, if not inside the root domain. -func (h *routerHandler) RouteExists(ctx context.Context, method, path string) bool { - for i := range h.trees { - t := h.trees[i] - if method != t.Method { - continue - } +func (h *routerHandler) subdomainAndPathAndMethodExists(ctx context.Context, t *trie, method, path string) bool { + if method != "" && method != t.method { + return false + } - if h.hosts && t.Subdomain != "" { - requestHost := ctx.Host() - if netutil.IsLoopbackSubdomain(requestHost) { - // this fixes a bug when listening on - // 127.0.0.1:8080 for example - // and have a wildcard subdomain and a route registered to root domain. - continue // it's not a subdomain, it's something like 127.0.0.1 probably + if h.hosts && t.subdomain != "" { + requestHost := ctx.Host() + if netutil.IsLoopbackSubdomain(requestHost) { + // this fixes a bug when listening on + // 127.0.0.1:8080 for example + // and have a wildcard subdomain and a route registered to root domain. + return false // it's not a subdomain, it's something like 127.0.0.1 probably + } + // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty + if t.subdomain == SubdomainWildcardIndicator { + // mydomain.com -> invalid + // localhost -> invalid + // sub.mydomain.com -> valid + // sub.localhost -> valid + serverHost := ctx.Application().ConfigurationReadOnly().GetVHost() + if serverHost == requestHost { + return false // it's not a subdomain, it's a full domain (with .com...) } - // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty - if t.Subdomain == SubdomainWildcardIndicator { - // mydomain.com -> invalid - // localhost -> invalid - // sub.mydomain.com -> valid - // sub.localhost -> valid - serverHost := ctx.Application().ConfigurationReadOnly().GetVHost() - if serverHost == requestHost { - continue // it's not a subdomain, it's a full domain (with .com...) - } - dotIdx := strings.IndexByte(requestHost, '.') - slashIdx := strings.IndexByte(requestHost, '/') - if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) { - // if "." was found anywhere but not at the first path segment (host). - } else { - continue - } - // continue to that, any subdomain is valid. - } else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot. - continue + dotIdx := strings.IndexByte(requestHost, '.') + slashIdx := strings.IndexByte(requestHost, '/') + if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) { + // if "." was found anywhere but not at the first path segment (host). + } else { + return false } + // continue to that, any subdomain is valid. + } else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot. + return false } + } - _, handlers := t.Nodes.Find(path, ctx.Params()) - if len(handlers) > 0 { - // found + n := t.search(path, ctx.Params()) + return n != nil +} + +// RouteExists reports whether a particular route exists +// It will search from the current subdomain of context's host, if not inside the root domain. +func (h *routerHandler) RouteExists(ctx context.Context, method, path string) bool { + for i := range h.trees { + t := h.trees[i] + if h.subdomainAndPathAndMethodExists(ctx, t, method, path) { return true } - - // not found or method not allowed. - break } return false diff --git a/core/router/node/node.go b/core/router/node/node.go deleted file mode 100644 index 4a4adb05eb..0000000000 --- a/core/router/node/node.go +++ /dev/null @@ -1,448 +0,0 @@ -package node - -import ( - "sort" - "strings" - - "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" -) - -// Nodes a conversion type for []*node. -type Nodes []*node - -type node struct { - s string - routeName string - wildcardParamName string // name of the wildcard parameter, only one per whole Node is allowed - paramNames []string // only-names - childrenNodes Nodes - handlers context.Handlers - root bool - rootWildcard bool // if it's a wildcard {path} type on root, it should allow everything but it is not conflicts with - // any other static or dynamic or wildcard paths if exists on other nodes. -} - -// ErrDublicate returnned from `Add` when two or more routes have the same registered path. -var ErrDublicate = errors.New("two or more routes have the same registered path") - -/// TODO: clean up needed until v8.5 - -// Add adds a node to the tree, returns an ErrDublicate error on failure. -func (nodes *Nodes) Add(routeName string, path string, handlers context.Handlers) error { - // println("[Add] adding path: " + path) - // resolve params and if that node should be added as root - var params []string - var paramStart, paramEnd int - for { - paramStart = strings.IndexByte(path[paramEnd:], ':') - if paramStart == -1 { - break - } - paramStart += paramEnd - paramStart++ - paramEnd = strings.IndexByte(path[paramStart:], '/') - - if paramEnd == -1 { - params = append(params, path[paramStart:]) - path = path[:paramStart] - break - } - paramEnd += paramStart - params = append(params, path[paramStart:paramEnd]) - path = path[:paramStart] + path[paramEnd:] - paramEnd -= paramEnd - paramStart - } - - var p []int - for i := 0; i < len(path); i++ { - idx := strings.IndexByte(path[i:], ':') - if idx == -1 { - break - } - p = append(p, idx+i) - i = idx + i - } - - for _, idx := range p { - // print("-2 nodes.Add: path: " + path + " params len: ") - // println(len(params)) - if err := nodes.add(routeName, path[:idx], nil, nil, true); err != nil { - return err - } - // print("-1 nodes.Add: path: " + path + " params len: ") - // println(len(params)) - if nidx := idx + 1; len(path) > nidx { - if err := nodes.add(routeName, path[:nidx], nil, nil, true); err != nil { - return err - } - } - } - - // print("nodes.Add: path: " + path + " params len: ") - // println(len(params)) - if err := nodes.add(routeName, path, params, handlers, true); err != nil { - return err - } - - // prioritize by static path remember, they were already sorted by subdomains too. - nodes.prioritize() - return nil -} - -func (nodes *Nodes) add(routeName, path string, paramNames []string, handlers context.Handlers, root bool) (err error) { - // println("[add] route name: " + routeName) - // println("[add] adding path: " + path) - - // wraia etsi doulevei ara - // na to kanw na exei to node to diko tou wildcard parameter name - // kai sto telos na pernei auto, me vasi to *paramname - // alla edw mesa 9a ginete register vasi tou last / - - // set the wildcard param name to the root and its children. - wildcardIdx := strings.IndexByte(path, '*') - wildcardParamName := "" - if wildcardIdx > 0 && len(paramNames) == 0 { // 27 Oct comment: && len(paramNames) == 0 { - wildcardParamName = path[wildcardIdx+1:] - path = path[0:wildcardIdx-1] + "/" // replace *paramName with single slash - - // if path[len(path)-1] == '/' { - // if root wildcard, then add it as it's and return - rootWildcard := path == "/" - if rootWildcard { - path += "/" // if root wildcard, then do it like "//" instead of simple "/" - } - - n := &node{ - rootWildcard: rootWildcard, - s: path, - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - root: root, - } - *nodes = append(*nodes, n) - // println("1. nodes.Add path: " + path) - return - - } - -loop: - for _, n := range *nodes { - if n.rootWildcard { - continue - } - - if len(n.paramNames) == 0 && n.wildcardParamName != "" { - continue - } - - minlen := len(n.s) - if len(path) < minlen { - minlen = len(path) - } - - for i := 0; i < minlen; i++ { - if n.s[i] == path[i] { - continue - } - if i == 0 { - continue loop - } - - *n = node{ - s: n.s[:i], - childrenNodes: Nodes{ - { - s: n.s[i:], - routeName: n.routeName, - wildcardParamName: n.wildcardParamName, // wildcardParamName - paramNames: n.paramNames, - childrenNodes: n.childrenNodes, - handlers: n.handlers, - }, - { - s: path[i:], - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - }, - }, - root: n.root, - } - - // println("2. change n and return " + n.s[:i] + " and " + path[i:]) - return - } - - if len(path) < len(n.s) { - // println("3. change n and return | n.s[:len(path)] = " + n.s[:len(path)-1] + " and child: " + n.s[len(path)-1:]) - - *n = node{ - s: n.s[:len(path)], - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - childrenNodes: Nodes{ - { - s: n.s[len(path):], - routeName: n.routeName, - wildcardParamName: n.wildcardParamName, // wildcardParamName - paramNames: n.paramNames, - childrenNodes: n.childrenNodes, - handlers: n.handlers, - }, - }, - handlers: handlers, - root: n.root, - } - - return - } - - if len(path) > len(n.s) { - if n.wildcardParamName != "" { - n := &node{ - s: path, - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - root: root, - } - // println("3.5. nodes.Add path: " + n.s) - *nodes = append(*nodes, n) - return - } - - pathToAdd := path[len(n.s):] - // println("4. nodes.Add route name: " + routeName) - // println("4. nodes.Add path: " + pathToAdd) - err = n.childrenNodes.add(routeName, pathToAdd, paramNames, handlers, false) - return err - } - - if len(handlers) == 0 { // missing handlers - return nil - } - - if len(n.handlers) > 0 { // n.handlers already setted - return ErrDublicate - } - n.paramNames = paramNames - n.handlers = handlers - n.routeName = routeName - return - } - - // START - // Author's note: - // 27 Oct 2017; fixes s|i|l+static+p - // without breaking the current tests. - if wildcardIdx > 0 { - wildcardParamName = path[wildcardIdx+1:] - path = path[0:wildcardIdx-1] + "/" - } - // END - - n := &node{ - s: path, - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - root: root, - } - *nodes = append(*nodes, n) - - // println("5. node add on path: " + path + " n.s: " + n.s + " wildcard param: " + n.wildcardParamName) - return -} - -// Find resolves the path, fills its params -// and returns the registered to the resolved node's handlers. -func (nodes Nodes) Find(path string, params *context.RequestParams) (string, context.Handlers) { - n, paramValues := nodes.findChild(path, nil) - if n != nil { - // map the params, - // n.params are the param names - if len(paramValues) > 0 { - // println("-----------") - // print("param values returned len: ") - // println(len(paramValues)) - // println("first value is: " + paramValues[0]) - // print("n.paramNames len: ") - // println(len(n.paramNames)) - for i, name := range n.paramNames { - // println("setting param name: " + name + " = " + paramValues[i]) - params.Set(name, paramValues[i]) - } - // last is the wildcard, - // if paramValues are exceed from the registered param names. - // Note that n.wildcardParamName can be not empty but that doesn't meaning - // that it contains a wildcard path, so the check is required. - if len(paramValues) > len(n.paramNames) { - // println("len(paramValues) > len(n.paramNames)") - lastWildcardVal := paramValues[len(paramValues)-1] - // println("setting wildcard param name: " + n.wildcardParamName + " = " + lastWildcardVal) - params.Set(n.wildcardParamName, lastWildcardVal) - } - } - - return n.routeName, n.handlers - } - - return "", nil -} - -// Exists returns true if a node with that "path" exists, -// otherise false. -// -// We don't care about parameters here. -func (nodes Nodes) Exists(path string) bool { - n, _ := nodes.findChild(path, nil) - return n != nil && len(n.handlers) > 0 -} - -func (nodes Nodes) findChild(path string, params []string) (*node, []string) { - - for _, n := range nodes { - if n.s == ":" { - paramEnd := strings.IndexByte(path, '/') - if paramEnd == -1 { - if len(n.handlers) == 0 { - return nil, nil - } - return n, append(params, path) - } - return n.childrenNodes.findChild(path[paramEnd:], append(params, path[:paramEnd])) - } - - // println("n.s: " + n.s) - // print("n.childrenNodes len: ") - // println(len(n.childrenNodes)) - // print("n.root: ") - // println(n.root) - - // by runtime check of:, - // if n.s == "//" && n.root && n.wildcardParamName != "" { - // but this will slow down, so we have a static field on the node itself: - if n.rootWildcard { - // println("return from n.rootWildcard") - // single root wildcard - if len(path) < 2 { - // do not remove that, it seems useless but it's not, - // we had an error while production, this fixes that. - path = "/" + path - } - return n, append(params, path[1:]) - } - - // second conditional may be unnecessary - // because of the n.rootWildcard before, but do it. - if n.wildcardParamName != "" && len(path) > 2 { - // println("n has wildcard n.s: " + n.s + " on path: " + path) - // n.s = static/, path = static - - // println(n.s + " vs path: " + path) - - // we could have /other/ as n.s so - // we must do this check, remember: - // now wildcards live on their own nodes - if len(path) == len(n.s)-1 { - // then it's like: - // path = /other2 - // ns = /other2/ - if path == n.s[0:len(n.s)-1] { - return n, params - } - } - - // othwerwise path = /other2/dsadas - // ns= /other2/ - if strings.HasPrefix(path, n.s) { - if len(path) > len(n.s)+1 { - return n, append(params, path[len(n.s):]) // without slash - } - } - - } - - if !strings.HasPrefix(path, n.s) { - // fmt.Printf("---here root: %v, n.s: "+n.s+" and path: "+path+" is dynamic: %v , wildcardParamName: %s, children len: %v \n", n.root, n.isDynamic(), n.wildcardParamName, len(n.childrenNodes)) - // println(path + " n.s: " + n.s + " continue...") - continue - } - - if len(path) == len(n.s) { - if len(n.handlers) == 0 { - return nil, nil - } - return n, params - } - - child, childParamNames := n.childrenNodes.findChild(path[len(n.s):], params) - - // print("childParamNames len: ") - // println(len(childParamNames)) - - // if len(childParamNames) > 0 { - // println("childParamsNames[0] = " + childParamNames[0]) - // } - - if child == nil || len(child.handlers) == 0 { - if n.s[len(n.s)-1] == '/' && !(n.root && (n.s == "/" || len(n.childrenNodes) > 0)) { - if len(n.handlers) == 0 { - return nil, nil - } - - // println("if child == nil.... | n.s = " + n.s) - // print("n.paramNames len: ") - // println(n.paramNames) - // print("n.wildcardParamName is: ") - // println(n.wildcardParamName) - // print("return n, append(params, path[len(n.s) | params: ") - // println(path[len(n.s):]) - return n, append(params, path[len(n.s):]) - } - - continue - } - - return child, childParamNames - } - return nil, nil -} - -// childLen returns all the children's and their children's length. -func (n *node) childLen() (i int) { - for _, n := range n.childrenNodes { - i++ - i += n.childLen() - } - return -} - -func (n *node) isDynamic() bool { - return n.s == ":" || n.wildcardParamName != "" || n.rootWildcard -} - -// prioritize sets the static paths first. -func (nodes Nodes) prioritize() { - sort.Slice(nodes, func(i, j int) bool { - if nodes[i].isDynamic() { - return false - } - if nodes[j].isDynamic() { - return true - } - - return nodes[i].childLen() > nodes[j].childLen() - }) - - for _, n := range nodes { - n.childrenNodes.prioritize() - } -} diff --git a/core/router/path.go b/core/router/path.go index 11d4ffd080..0a8b4014b7 100644 --- a/core/router/path.go +++ b/core/router/path.go @@ -12,14 +12,6 @@ import ( "github.com/kataras/iris/macro/interpreter/lexer" ) -const ( - // ParamStart the character in string representation where the underline router starts its dynamic named parameter. - ParamStart = ":" - // WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard - // path parameter. - WildcardParamStart = "*" -) - // Param receives a parameter name prefixed with the ParamStart symbol. func Param(name string) string { return prefix(name, ParamStart) @@ -35,10 +27,8 @@ func WildcardParam(name string) string { func convertMacroTmplToNodePath(tmpl macro.Template) string { routePath := tmpl.Src - if len(tmpl.Params) > 0 { - if routePath[len(routePath)-1] == '/' { - routePath = routePath[0 : len(routePath)-2] // remove the last "/" if macro syntax instead of underline's. - } + if len(routePath) > 1 && routePath[len(routePath)-1] == '/' { + routePath = routePath[0 : len(routePath)-1] // remove any last "/" } // if it has started with {} and it's valid diff --git a/core/router/path_test.go b/core/router/path_test.go index 103e2af9f8..2c26c6da54 100644 --- a/core/router/path_test.go +++ b/core/router/path_test.go @@ -53,8 +53,8 @@ func TestSplitPath(t *testing.T) { []string{"/user", "/admin"}}, {"/single_no_params", []string{"/single_no_params"}}, - {"/single/{id:number}", - []string{"/single/{id:number}"}}, + {"/single/{id:int}", + []string{"/single/{id:int}"}}, } equalSlice := func(s1 []string, s2 []string) bool { diff --git a/core/router/router_wildcard_root_test.go b/core/router/router_wildcard_root_test.go index 110192ebeb..c3190ce5a2 100644 --- a/core/router/router_wildcard_root_test.go +++ b/core/router/router_wildcard_root_test.go @@ -122,20 +122,25 @@ func TestRouterWildcardRootMany(t *testing.T) { func TestRouterWildcardRootManyAndRootStatic(t *testing.T) { var tt = []testRoute{ - // all routes will be handlded by "h" because we added wildcard to root, + // routes that may return 404 will be handled by the below route ("h" handler) because we added wildcard to root, // this feature is very important and can remove noumerous of previous hacks on our apps. + // + // Static paths and parameters have priority over wildcard, all three types can be registered in the same path prefix. + // + // Remember, all of those routes are registered don't be tricked by the visual appearance of the below test blocks. {"GET", "/{p:path}", h, []testRouteRequest{ {"GET", "", "/other2almost/some", iris.StatusOK, same_as_request_path}, }}, {"GET", "/static/{p:path}", h, []testRouteRequest{ - {"GET", "", "/static", iris.StatusOK, same_as_request_path}, + {"GET", "", "/static", iris.StatusOK, same_as_request_path}, // HERE<- IF NOT FOUND THEN BACKWARDS TO WILDCARD IF THERE IS ONE, HMM. {"GET", "", "/static/something/here", iris.StatusOK, same_as_request_path}, }}, {"GET", "/", h, []testRouteRequest{ {"GET", "", "/", iris.StatusOK, same_as_request_path}, }}, {"GET", "/other/{paramother:path}", h2, []testRouteRequest{ - {"GET", "", "/other", iris.StatusForbidden, same_as_request_path}, + // OK and not h2 because of the root wildcard. + {"GET", "", "/other", iris.StatusOK, same_as_request_path}, {"GET", "", "/other/wildcard", iris.StatusForbidden, same_as_request_path}, {"GET", "", "/other/wildcard/here", iris.StatusForbidden, same_as_request_path}, }}, @@ -145,6 +150,7 @@ func TestRouterWildcardRootManyAndRootStatic(t *testing.T) { }}, {"GET", "/other2/static", h3, []testRouteRequest{ {"GET", "", "/other2/static", iris.StatusOK, prefix_static_path_following_by_request_path}, + // h2(Forbiddenn) instead of h3 OK because it will be handled by the /other2/{paramothersecond:path}'s handler which gives 403. {"GET", "", "/other2/staticed", iris.StatusForbidden, same_as_request_path}, }}, } @@ -165,6 +171,7 @@ func testTheRoutes(t *testing.T, tests []testRoute, debug bool) { // run the tests for _, tt := range tests { for _, req := range tt.requests { + // t.Logf("req: %s:%s\n", tt.method, tt.path) method := req.method if method == "" { method = tt.method diff --git a/core/router/trie.go b/core/router/trie.go new file mode 100644 index 0000000000..2d11e25776 --- /dev/null +++ b/core/router/trie.go @@ -0,0 +1,267 @@ +package router + +import ( + "strings" + + "github.com/kataras/iris/context" +) + +const ( + // ParamStart the character in string representation where the underline router starts its dynamic named parameter. + ParamStart = ":" + // WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard + // path parameter. + WildcardParamStart = "*" +) + +// An iris-specific identical version of the https://github.com/kataras/muxie version 1.0.0 released at 15 Oct 2018 +type trieNode struct { + parent *trieNode + + children map[string]*trieNode + hasDynamicChild bool // does one of the children contains a parameter or wildcard? + childNamedParameter bool // is the child a named parameter (single segmnet) + childWildcardParameter bool // or it is a wildcard (can be more than one path segments) ? + paramKeys []string // the param keys without : or *. + end bool // it is a complete node, here we stop and we can say that the node is valid. + key string // if end == true then key is filled with the original value of the insertion's key. + // if key != "" && its parent has childWildcardParameter == true, + // we need it to track the static part for the closest-wildcard's parameter storage. + staticKey string + + // insert data. + Handlers context.Handlers + RouteName string +} + +func newTrieNode() *trieNode { + n := new(trieNode) + return n +} + +func (tn *trieNode) hasChild(s string) bool { + return tn.getChild(s) != nil +} + +func (tn *trieNode) getChild(s string) *trieNode { + if tn.children == nil { + tn.children = make(map[string]*trieNode) + } + + return tn.children[s] +} + +func (tn *trieNode) addChild(s string, n *trieNode) { + if tn.children == nil { + tn.children = make(map[string]*trieNode) + } + + if _, exists := tn.children[s]; exists { + return + } + + n.parent = tn + tn.children[s] = n +} + +func (tn *trieNode) findClosestParentWildcardNode() *trieNode { + tn = tn.parent + for tn != nil { + if tn.childWildcardParameter { + return tn.getChild(WildcardParamStart) + } + + tn = tn.parent + } + + return nil +} + +func (tn *trieNode) String() string { + return tn.key +} + +type trie struct { + root *trieNode + + // if true then it will handle any path if not other parent wildcard exists, + // so even 404 (on http services) is up to it, see trie#insert. + hasRootWildcard bool + + method string + // subdomain is empty for default-hostname routes, + // ex: mysubdomain. + subdomain string +} + +func newTrie() *trie { + return &trie{ + root: newTrieNode(), + } +} + +const ( + pathSep = "/" + pathSepB = '/' +) + +func slowPathSplit(path string) []string { + if path == "/" { + return []string{"/"} + } + + return strings.Split(path, pathSep)[1:] +} + +func (tr *trie) insert(path, routeName string, handlers context.Handlers) { + input := slowPathSplit(path) + + n := tr.root + var paramKeys []string + + for _, s := range input { + c := s[0] + + if isParam, isWildcard := c == ParamStart[0], c == WildcardParamStart[0]; isParam || isWildcard { + n.hasDynamicChild = true + paramKeys = append(paramKeys, s[1:]) // without : or *. + + // if node has already a wildcard, don't force a value, check for true only. + if isParam { + n.childNamedParameter = true + s = ParamStart + } + + if isWildcard { + n.childWildcardParameter = true + s = WildcardParamStart + if tr.root == n { + tr.hasRootWildcard = true + } + } + } + + if !n.hasChild(s) { + child := newTrieNode() + n.addChild(s, child) + } + + n = n.getChild(s) + } + + n.RouteName = routeName + n.Handlers = handlers + n.paramKeys = paramKeys + n.key = path + n.end = true + + i := strings.Index(path, ParamStart) + if i == -1 { + i = strings.Index(path, WildcardParamStart) + } + if i == -1 { + i = len(n.key) + } + + n.staticKey = path[:i] +} + +func (tr *trie) search(q string, params *context.RequestParams) *trieNode { + end := len(q) + n := tr.root + if end == 1 && q[0] == pathSepB { + return n.getChild(pathSep) + } + + start := 1 + i := 1 + var paramValues []string + + for { + if i == end || q[i] == pathSepB { + if child := n.getChild(q[start:i]); child != nil { + n = child + } else if n.childNamedParameter { + n = n.getChild(ParamStart) + if ln := len(paramValues); cap(paramValues) > ln { + paramValues = paramValues[:ln+1] + paramValues[ln] = q[start:i] + } else { + paramValues = append(paramValues, q[start:i]) + } + } else if n.childWildcardParameter { + n = n.getChild(WildcardParamStart) + if ln := len(paramValues); cap(paramValues) > ln { + paramValues = paramValues[:ln+1] + paramValues[ln] = q[start:] + } else { + paramValues = append(paramValues, q[start:]) + } + break + } else { + n = n.findClosestParentWildcardNode() + if n != nil { + // means that it has :param/static and *wildcard, we go trhough the :param + // but the next path segment is not the /static, so go back to *wildcard + // instead of not found. + // + // Fixes: + // /hello/*p + // /hello/:p1/static/:p2 + // req: http://localhost:8080/hello/dsadsa/static/dsadsa => found + // req: http://localhost:8080/hello/dsadsa => but not found! + // and + // /second/wild/*p + // /second/wild/static/otherstatic/ + // req: /second/wild/static/otherstatic/random => but not found! + params.Set(n.paramKeys[0], q[len(n.staticKey):]) + return n + } + + return nil + } + + if i == end { + break + } + + i++ + start = i + continue + } + + i++ + } + + if n == nil || !n.end { + if n != nil { // we need it on both places, on last segment (below) or on the first unnknown (above). + if n = n.findClosestParentWildcardNode(); n != nil { + params.Set(n.paramKeys[0], q[len(n.staticKey):]) + return n + } + } + + if tr.hasRootWildcard { + // that's the case for root wildcard, tests are passing + // even without it but stick with it for reference. + // Note ote that something like: + // Routes: /other2/*myparam and /other2/static + // Reqs: /other2/staticed will be handled + // the /other2/*myparam and not the root wildcard, which is what we want. + // + n = tr.root.getChild(WildcardParamStart) + params.Set(n.paramKeys[0], q[1:]) + return n + } + + return nil + } + + for i, paramValue := range paramValues { + if len(n.paramKeys) > i { + params.Set(n.paramKeys[i], paramValue) + } + } + + return n +} diff --git a/doc.go b/doc.go index dc5f93b103..88b67c40ce 100644 --- a/doc.go +++ b/doc.go @@ -491,7 +491,7 @@ Example code: // http://myhost.com/users/42/profile users.Get("/{id:uint64}/profile", userProfileHandler) // http://myhost.com/users/messages/1 - users.Get("/inbox/{id:number}", userMessageHandler) + users.Get("/inbox/{id:int}", userMessageHandler) Custom HTTP Errors @@ -553,7 +553,7 @@ Example code: if err != nil { ctx.Writef("error while trying to parse userid parameter," + - "this will never happen if :number is being used because if it's not integer it will fire Not Found automatically.") + "this will never happen if :int is being used because if it's not integer it will fire Not Found automatically.") ctx.StatusCode(iris.StatusBadRequest) return } @@ -709,26 +709,62 @@ Standard macro types for parameters: | {param:string} | +------------------------+ string type - anything + anything (single path segmnent) +-------------------------------+ - | {param:number} or {param:int} | + | {param:int} | +-------------------------------+ int type - both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch) + -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch - +-------------------------------+ - | {param:long} or {param:int64} | - +-------------------------------+ + +------------------------+ + | {param:int8} | + +------------------------+ + int8 type + -128 to 127 + + +------------------------+ + | {param:int16} | + +------------------------+ + int16 type + -32768 to 32767 + + +------------------------+ + | {param:int32} | + +------------------------+ + int32 type + -2147483648 to 2147483647 + + +------------------------+ + | {param:int64} | + +------------------------+ int64 type -9223372036854775808 to 9223372036854775807 + +------------------------+ + | {param:uint} | + +------------------------+ + uint type + 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32) + +------------------------+ | {param:uint8} | +------------------------+ uint8 type 0 to 255 + +------------------------+ + | {param:uint16} | + +------------------------+ + uint16 type + 0 to 65535 + + +------------------------+ + | {param:uint32} | + +------------------------+ + uint32 type + 0 to 4294967295 + +------------------------+ | {param:uint64} | +------------------------+ @@ -763,8 +799,8 @@ Standard macro types for parameters: | {param:path} | +------------------------+ path type - anything, should be the last part, more than one path segment, - i.e: /path1/path2/path3 , ctx.Params().Get("param") == "/path1/path2/path3" + anything, should be the last part, can be more than one path segment, + i.e: "/test/*param" and request: "/test/path1/path2/path3" , ctx.Params().Get("param") == "path1/path2/path3" if type is missing then parameter's type is defaulted to string, so {param} == {param:string}. @@ -773,7 +809,7 @@ If a function not found on that type then the "string"'s types functions are bei i.e: - {param:number min(3)} + {param:int min(3)} Besides the fact that iris provides the basic types and some default "macro funcs" @@ -782,16 +818,18 @@ you are able to register your own too!. Register a named path parameter function: - app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool { - [...] - return true/false -> true means valid. + app.Macros().Get("int").RegisterFunc("min", func(argument int) func(paramValue int) bool { + return func(paramValue int) bool { + [...] + return true/false -> true means valid. + } }) at the func(argument ...) you can have any standard type, it will be validated before the server starts so don't care about performance here, the only thing it runs at serve time is the returning func(paramValue string) bool. {param:string equal(iris)} , "iris" will be the argument here: - app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool { + app.Macros().Get("string").RegisterFunc("equal", func(argument string) func(paramValue string) bool { return func(paramValue string){ return argument == paramValue } }) @@ -804,20 +842,16 @@ Example Code: ctx.Writef("Hello %s", ctx.Params().Get("name")) }) // type is missing = {name:string} - // Let's register our first macro attached to number macro type. + // Let's register our first macro attached to int macro type. // "min" = the function // "minValue" = the argument of the function - // func(string) bool = the macro's path parameter evaluator, this executes in serve time when - // a user requests a path which contains the number macro type with the min(...) macro parameter function. - app.Macros().Number.RegisterFunc("min", func(minValue int) func(string) bool { + // func() bool = the macro's path parameter evaluator, this executes in serve time when + // a user requests a path which contains the int macro type with the min(...) macro parameter function. + app.Macros().Get("int").RegisterFunc("min", func(minValue int) func(int) bool { // do anything before serve here [...] // at this case we don't need to do anything - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= minValue + return func(paramValue int) bool { + return paramValue >= minValue } }) diff --git a/macro/handler/handler.go b/macro/handler/handler.go index 16f466c194..ea36c7d566 100644 --- a/macro/handler/handler.go +++ b/macro/handler/handler.go @@ -44,7 +44,7 @@ func MakeHandler(tmpl macro.Template) context.Handler { continue // allow. } - if !p.Eval(ctx.Params().Get(p.Name), ctx.Params()) { + if !p.Eval(ctx.Params().Get(p.Name), &ctx.Params().Store) { ctx.StatusCode(p.ErrCode) ctx.StopExecution() return diff --git a/macro/interpreter/lexer/lexer_test.go b/macro/interpreter/lexer/lexer_test.go index 4ed056a270..848731e0f2 100644 --- a/macro/interpreter/lexer/lexer_test.go +++ b/macro/interpreter/lexer/lexer_test.go @@ -7,27 +7,27 @@ import ( ) func TestNextToken(t *testing.T) { - input := `{id:number min(1) max(5) else 404}` + input := `{id:int min(1) max(5) else 404}` tests := []struct { expectedType token.Type expectedLiteral string }{ - {token.LBRACE, "{"}, // 0 - {token.IDENT, "id"}, // 1 - {token.COLON, ":"}, // 2 - {token.IDENT, "number"}, // 3 - {token.IDENT, "min"}, // 4 - {token.LPAREN, "("}, // 5 - {token.INT, "1"}, // 6 - {token.RPAREN, ")"}, // 7 - {token.IDENT, "max"}, // 8 - {token.LPAREN, "("}, // 9 - {token.INT, "5"}, // 10 - {token.RPAREN, ")"}, // 11 - {token.ELSE, "else"}, // 12 - {token.INT, "404"}, // 13 - {token.RBRACE, "}"}, // 14 + {token.LBRACE, "{"}, // 0 + {token.IDENT, "id"}, // 1 + {token.COLON, ":"}, // 2 + {token.IDENT, "int"}, // 3 + {token.IDENT, "min"}, // 4 + {token.LPAREN, "("}, // 5 + {token.INT, "1"}, // 6 + {token.RPAREN, ")"}, // 7 + {token.IDENT, "max"}, // 8 + {token.LPAREN, "("}, // 9 + {token.INT, "5"}, // 10 + {token.RPAREN, ")"}, // 11 + {token.ELSE, "else"}, // 12 + {token.INT, "404"}, // 13 + {token.RBRACE, "}"}, // 14 } l := New(input) diff --git a/macro/interpreter/parser/parser_test.go b/macro/interpreter/parser/parser_test.go index 1695e6f4de..5424f67565 100644 --- a/macro/interpreter/parser/parser_test.go +++ b/macro/interpreter/parser/parser_test.go @@ -95,7 +95,7 @@ func TestParseParam(t *testing.T) { }{ {true, ast.ParamStatement{ - Src: "{id:number min(1) max(5) else 404}", + Src: "{id:int min(1) max(5) else 404}", Name: "id", Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ @@ -111,6 +111,7 @@ func TestParseParam(t *testing.T) { {true, ast.ParamStatement{ + // test alias of int. Src: "{id:number range(1,5)}", Name: "id", Type: mustLookupParamType("number"), @@ -163,7 +164,7 @@ func TestParseParam(t *testing.T) { }}, // 6 {true, ast.ParamStatement{ - Src: "{id:number even()}", // test param funcs without any arguments (LPAREN peek for RPAREN) + Src: "{id:int even()}", // test param funcs without any arguments (LPAREN peek for RPAREN) Name: "id", Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ @@ -236,9 +237,9 @@ func TestParse(t *testing.T) { valid bool expectedStatements []ast.ParamStatement }{ - {"/api/users/{id:number min(1) max(5) else 404}", true, + {"/api/users/{id:int min(1) max(5) else 404}", true, []ast.ParamStatement{{ - Src: "{id:number min(1) max(5) else 404}", + Src: "{id:int min(1) max(5) else 404}", Name: "id", Type: paramTypeNumber, Funcs: []ast.ParamFunc{ diff --git a/macro/interpreter/token/token.go b/macro/interpreter/token/token.go index f5cecbe9d1..b964db1d70 100644 --- a/macro/interpreter/token/token.go +++ b/macro/interpreter/token/token.go @@ -14,7 +14,7 @@ type Token struct { // /about/{fullname:alphabetical} // /profile/{anySpecialName:string} // {id:uint64 range(1,5) else 404} -// /admin/{id:number eq(1) else 402} +// /admin/{id:int eq(1) else 402} // /file/{filepath:file else 405} const ( EOF = iota // 0 From 85efa86d91e1fe4d09bb4ea932772392bbee4490 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 15 Oct 2018 11:58:57 +0300 Subject: [PATCH 35/91] typo fix: internally --- _examples/http_request/read-json-struct-validation/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_examples/http_request/read-json-struct-validation/main.go b/_examples/http_request/read-json-struct-validation/main.go index f7beedc0c4..16dab51166 100644 --- a/_examples/http_request/read-json-struct-validation/main.go +++ b/_examples/http_request/read-json-struct-validation/main.go @@ -36,7 +36,7 @@ func main() { // Register validation for 'User' // NOTE: only have to register a non-pointer type for 'User', validator - // interanlly dereferences during it's type checks. + // internally dereferences during it's type checks. validate.RegisterStructValidation(UserStructLevelValidation, User{}) app := iris.New() From 486b25a64a14fb679717b727b3d595851036dde6 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 16 Oct 2018 01:39:27 +0300 Subject: [PATCH 36/91] add godoc comments for the updated macro package --- README.md | 2 +- .../read-json-struct-validation/main.go | 2 +- context/request_params.go | 12 +++++++++++ macro/macro.go | 18 ++++++++++------- macro/macros.go | 20 +++++++++++++++++++ macro/template.go | 15 +++++++++++--- 6 files changed, 57 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1183f7e153..a6f6972d39 100644 --- a/README.md +++ b/README.md @@ -644,7 +644,7 @@ func main() { // Register validation for 'User' // NOTE: only have to register a non-pointer type for 'User', validator - // interanlly dereferences during it's type checks. + // internally dereferences during it's type checks. validate.RegisterStructValidation(UserStructLevelValidation, User{}) app := iris.New() diff --git a/_examples/http_request/read-json-struct-validation/main.go b/_examples/http_request/read-json-struct-validation/main.go index f7beedc0c4..16dab51166 100644 --- a/_examples/http_request/read-json-struct-validation/main.go +++ b/_examples/http_request/read-json-struct-validation/main.go @@ -36,7 +36,7 @@ func main() { // Register validation for 'User' // NOTE: only have to register a non-pointer type for 'User', validator - // interanlly dereferences during it's type checks. + // internally dereferences during it's type checks. validate.RegisterStructValidation(UserStructLevelValidation, User{}) app := iris.New() diff --git a/context/request_params.go b/context/request_params.go index fc1b47c819..7757308b97 100644 --- a/context/request_params.go +++ b/context/request_params.go @@ -90,6 +90,18 @@ func (r RequestParams) GetIntUnslashed(key string) (int, bool) { } var ( + // ParamResolvers is the global param resolution for a parameter type for a specific go std or custom type. + // + // Key is the specific type, which should be unique. + // The value is a function which accepts the parameter index + // and it should return the value as the parameter type evaluator expects it. + // i.e [reflect.TypeOf("string")] = func(paramIndex int) interface{} { + // return func(ctx Context) { + // return ctx.Params().GetEntryAt(paramIndex).ValueRaw.() + // } + // } + // + // Read https://github.com/kataras/iris/tree/master/_examples/routing/macros for more details. ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ reflect.TypeOf(""): func(paramIndex int) interface{} { return func(ctx Context) string { diff --git a/macro/macro.go b/macro/macro.go index 2e461ad2c9..8a656e2cc7 100644 --- a/macro/macro.go +++ b/macro/macro.go @@ -9,15 +9,12 @@ import ( "unicode" ) -// EvaluatorFunc is the signature for both param types and param funcs. -// It should accepts the param's value as string -// and return true if validated otherwise false. -// type EvaluatorFunc func(paramValue string) bool -// type BinderFunc func(paramValue string) interface{} - type ( + // ParamEvaluator is the signature for param type evaluator. + // It accepts the param's value as string and returns + // the value (which its type is used for the input argument of the parameter functions, if any) + // and a true value for passed, otherwise nil and false shoudl be returned. ParamEvaluator func(paramValue string) (interface{}, bool) - // FuncEvaluator interface{} // i.e func(paramValue int) bool ) var goodEvaluatorFuncs = []reflect.Type{ @@ -275,18 +272,25 @@ func NewMacro(indent, alias string, master, trailing bool, evaluator ParamEvalua } } +// Indent returns the name of the parameter type. func (m *Macro) Indent() string { return m.indent } +// Alias returns the alias of the parameter type, if any. func (m *Macro) Alias() string { return m.alias } +// Master returns true if that macro's parameter type is the +// default one if not :type is followed by a parameter type inside the route path. func (m *Macro) Master() bool { return m.master } +// Trailing returns true if that macro's parameter type +// is wildcard and can accept one or more path segments as one parameter value. +// A wildcard should be registered in the last path segment only. func (m *Macro) Trailing() bool { return m.trailing } diff --git a/macro/macros.go b/macro/macros.go index 12a5cf21f5..4c6f641617 100644 --- a/macro/macros.go +++ b/macro/macros.go @@ -402,6 +402,9 @@ var ( // Should be living in the latest path segment of a route path. Path = NewMacro("path", "", false, true, nil) + // Defaults contains the defaults macro and parameters types for the router. + // + // Read https://github.com/kataras/iris/tree/master/_examples/routing/macros for more details. Defaults = &Macros{ String, Int, @@ -420,8 +423,18 @@ var ( } ) +// Macros is just a type of a slice of *Macro +// which is responsible to register and search for macros based on the indent(parameter type). type Macros []*Macro +// Register registers a custom Macro. +// The "indent" should not be empty and should be unique, it is the parameter type's name, i.e "string". +// The "alias" is optionally and it should be unique, it is the alias of the parameter type. +// "isMaster" and "isTrailing" is for default parameter type and wildcard respectfully. +// The "evaluator" is the function that is converted to an Iris handler which is executed every time +// before the main chain of a route's handlers that contains this macro of the specific parameter type. +// +// Read https://github.com/kataras/iris/tree/master/_examples/routing/macros for more details. func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator ParamEvaluator) *Macro { macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator) if ms.register(macro) { @@ -460,6 +473,7 @@ func (ms *Macros) register(macro *Macro) bool { return true } +// Unregister removes a macro and its parameter type from the list. func (ms *Macros) Unregister(indent string) bool { cp := *ms @@ -477,6 +491,7 @@ func (ms *Macros) Unregister(indent string) bool { return false } +// Lookup returns the responsible macro for a parameter type, it can return nil. func (ms *Macros) Lookup(pt ast.ParamType) *Macro { if m := ms.Get(pt.Indent()); m != nil { return m @@ -491,6 +506,7 @@ func (ms *Macros) Lookup(pt ast.ParamType) *Macro { return nil } +// Get returns the responsible macro for a parameter type, it can return nil. func (ms *Macros) Get(indentOrAlias string) *Macro { if indentOrAlias == "" { return nil @@ -509,6 +525,8 @@ func (ms *Macros) Get(indentOrAlias string) *Macro { return nil } +// GetMaster returns the default macro and its parameter type, +// by default it will return the `String` macro which is responsible for the "string" parameter type. func (ms *Macros) GetMaster() *Macro { for _, m := range *ms { if m.Master() { @@ -519,6 +537,8 @@ func (ms *Macros) GetMaster() *Macro { return nil } +// GetTrailings returns the macros that have support for wildcards parameter types. +// By default it will return the `Path` macro which is responsible for the "path" parameter type. func (ms *Macros) GetTrailings() (macros []*Macro) { for _, m := range *ms { if m.Trailing() { diff --git a/macro/template.go b/macro/template.go index cd83d9c7de..8e766cb5dd 100644 --- a/macro/template.go +++ b/macro/template.go @@ -54,14 +54,23 @@ func (p TemplateParam) preComputed() TemplateParam { return p } +// CanEval returns true if this "p" TemplateParam should be evaluated in serve time. +// It is computed before server ran and it is used to determinate if a route needs to build a macro handler (middleware). func (p *TemplateParam) CanEval() bool { return p.canEval } -// paramChanger is the same form of context's Params().Set +// Eval is the most critical part of the TEmplateParam. +// It is responsible to return "passed:true" or "not passed:false" +// if the "paramValue" is the correct type of the registered parameter type +// and all functions, if any, are passed. +// "paramChanger" is the same form of context's Params().Set // we could accept a memstore.Store or even context.RequestParams // but this form has been chosed in order to test easier and fully decoupled from a request when necessary. -func (p *TemplateParam) Eval(paramValue string, paramChanger memstore.ValueSetter) bool { +// +// It is called from the converted macro handler (middleware) +// from the higher-level component of "kataras/iris/macro/handler#MakeHandler". +func (p *TemplateParam) Eval(paramValue string, paramSetter memstore.ValueSetter) bool { if p.TypeEvaluator == nil { for _, fn := range p.stringInFuncs { if !fn(paramValue) { @@ -87,7 +96,7 @@ func (p *TemplateParam) Eval(paramValue string, paramChanger memstore.ValueSette } } - paramChanger.Set(p.Name, newValue) + paramSetter.Set(p.Name, newValue) return true } From 625c7bf22ba10197e10ae56cfdf0781e3fc48315 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 16 Oct 2018 01:40:17 +0300 Subject: [PATCH 37/91] typo fix --- macro/macro.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macro/macro.go b/macro/macro.go index 8a656e2cc7..c4c557043d 100644 --- a/macro/macro.go +++ b/macro/macro.go @@ -13,7 +13,7 @@ type ( // ParamEvaluator is the signature for param type evaluator. // It accepts the param's value as string and returns // the value (which its type is used for the input argument of the parameter functions, if any) - // and a true value for passed, otherwise nil and false shoudl be returned. + // and a true value for passed, otherwise nil and false should be returned. ParamEvaluator func(paramValue string) (interface{}, bool) ) From 34e7cb2a5c3c362881aa99cc4b433025833039c3 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 16 Oct 2018 01:46:00 +0300 Subject: [PATCH 38/91] version preparation, it should be ready before novemember. --- README.md | 2 +- VERSION | 2 +- doc.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a6f6972d39..de91acb807 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/routing%20by-example-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/v11/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris is a fast, simple yet fully featured and very efficient web framework for Go. diff --git a/VERSION b/VERSION index 729c3531e6..8274a13e99 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.7.0:https://github.com/kataras/iris/blob/master/HISTORY.md#sat-11-august-2018--v1070 \ No newline at end of file +11.0.0:https://github.com/kataras/iris/blob/master/HISTORY.md#day??-dayN??-october-2018--v1100 \ No newline at end of file diff --git a/doc.go b/doc.go index 88b67c40ce..6c8bb56794 100644 --- a/doc.go +++ b/doc.go @@ -35,11 +35,11 @@ Source code and other details for the project are available at GitHub: Current Version -10.7.0 +11.0.0 Installation -The only requirement is the Go Programming Language, at least version 1.8 but 1.10 and above is highly recommended. +The only requirement is the Go Programming Language, at least version 1.8 but 1.11.1 and above is highly recommended. $ go get -u github.com/kataras/iris From 68a98c3f3fa97577f292cdc2bea3164936a55792 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Tue, 16 Oct 2018 05:37:21 +0300 Subject: [PATCH 39/91] make the core/router.AllMethods a variable that can be changed by end-devs as requested at: https://github.com/kataras/iris/issues/1102 --- HISTORY.md | 8 +++++++- core/router/api_builder.go | 10 ++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 34a6e675e3..bd6bfee89f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -22,10 +22,16 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene ## Breaking changes - Remove the "Configurator" `WithoutVersionChecker` and the configuration field `DisableVersionChecker` -- `:int` (or its new alias `:number`) macro route path parameter type **can accept negative numbers now**. Recommendation: `:int` should be replaced with the more generic numeric parameter type `:int` if possible (although it will stay for backwards-compatibility) +- `:int` parameter type **can accept negative numbers now**. +- `app.Macros().String/Int/Uint64/Path...RegisterFunc` should be replaced to: `app.Macros().Get("string" or "int" or "uint64" or "path" when "path" is the ":path" parameter type).RegisterFunc`, because you can now add custom macros and parameter types as well, see [here](_examples/routing/macros). +- `RegisterFunc("min", func(paramValue string) bool {...})` should be replaced to `RegisterFunc("min", func(paramValue ) bool {...})`, the `paramValue` argument is now stored in the exact type the macro's type evaluator inits it, i.e `uint64` or `int` and so on, therefore you don't have to convert the parameter value each time (this should make your handlers with macro functions activated even faster now) ## Routing +The `github.com/kataras/iris/core/router.AllMethods` is now a variable that can be altered by end-developers, so things like `app.Any` can register to custom methods as well, as requested at: https://github.com/kataras/iris/issues/1102. For example, import that package and do `router.AllMethods = append(router.AllMethods, "LINK")` in your `main` or `init` function. + +The old `github.com/kataras/iris/core/router/macro` package was moved to `guthub.com/kataras/iris/macro` to allow end-developers to add custom parameter types and macros, it supports all go standard types by default as you will see below. + - `:int` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers - Add `:int8` parameter type and `ctx.Params().GetInt8` - Add `:int16` parameter type and `ctx.Params().GetInt16` diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 7f1f363514..9aa9a3386e 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -12,17 +12,15 @@ import ( "github.com/kataras/iris/macro" ) -const ( - // MethodNone is a Virtual method - // to store the "offline" routes. - MethodNone = "NONE" -) +// MethodNone is a Virtual method +// to store the "offline" routes. +const MethodNone = "NONE" var ( // AllMethods contains the valid http methods: // "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", // "PATCH", "OPTIONS", "TRACE". - AllMethods = [...]string{ + AllMethods = []string{ "GET", "POST", "PUT", From 4004ebea1b4a42dc73d21501baaebdb8819c794a Mon Sep 17 00:00:00 2001 From: Eryx Date: Tue, 16 Oct 2018 13:05:14 +0800 Subject: [PATCH 40/91] Fix panic error in concurrent calling with websocket.Connection.Emit() --- websocket/server.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/websocket/server.go b/websocket/server.go index f704a05ed1..4055d0d5e4 100644 --- a/websocket/server.go +++ b/websocket/server.go @@ -52,9 +52,9 @@ type ( func New(cfg Config) *Server { cfg = cfg.Validate() return &Server{ - config: cfg, - connections: sync.Map{}, // ready-to-use, this is not necessary. - rooms: make(map[string][]string), + config: cfg, + connections: sync.Map{}, // ready-to-use, this is not necessary. + rooms: make(map[string][]string), onConnectionListeners: make([]ConnectionFunc, 0), upgrader: websocket.Upgrader{ HandshakeTimeout: cfg.HandshakeTimeout, @@ -352,7 +352,10 @@ func (s *Server) GetConnectionsByRoom(roomName string) []Connection { // let's keep it unexported for the best. func (s *Server) emitMessage(from, to string, data []byte) { if to != All && to != Broadcast { - if s.rooms[to] != nil { + s.mu.RLock() + room := s.rooms[to] + s.mu.RUnlock() + if room != nil { // it suppose to send the message to a specific room/or a user inside its own room for _, connectionIDInsideRoom := range s.rooms[to] { if c, ok := s.getConnection(connectionIDInsideRoom); ok { From 2f94c0c236a936a6adc79a4673c1aef625364934 Mon Sep 17 00:00:00 2001 From: Eryx Date: Tue, 16 Oct 2018 13:25:28 +0800 Subject: [PATCH 41/91] Fix panic error in concurrent calling with websocket.Connection.Emit() --- websocket/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/websocket/server.go b/websocket/server.go index 4055d0d5e4..a88f1806e6 100644 --- a/websocket/server.go +++ b/websocket/server.go @@ -357,7 +357,7 @@ func (s *Server) emitMessage(from, to string, data []byte) { s.mu.RUnlock() if room != nil { // it suppose to send the message to a specific room/or a user inside its own room - for _, connectionIDInsideRoom := range s.rooms[to] { + for _, connectionIDInsideRoom := range room { if c, ok := s.getConnection(connectionIDInsideRoom); ok { c.writeDefault(data) //send the message to the client(s) } else { From 715889b77f4c9370284be3a3f598ec6a1daf8558 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 17 Oct 2018 07:13:34 +0300 Subject: [PATCH 42/91] sync with master fixes and add more details in the HISTORY.md for the upcoming release --- HISTORY.md | 83 ++++++++++++++++++++++++++++++++++++++++++++- websocket/server.go | 13 ++++--- 2 files changed, 90 insertions(+), 6 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index bd6bfee89f..b3d5ab5384 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -28,6 +28,87 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene ## Routing +I wrote a [new router implementation](https://github.com/kataras/muxie#philosophy) for our Iris internal(low-level) routing mechanism, it is good to know that this was the second time we have updated the router internals without a single breaking change after the v6, thanks to the very well-writen and designed-first code we have for the high-level path syntax component called [macro interpreter](macro/interpreter). + +The new router supports things like **closest wildcard resolution**. + +> If the name doesn't sound good to you it is because I named that feature myself, I don't know any other framework or router that supports a thing like that so be gentle:) + +Previously you couldn't register routes like: `/{myparam:path}` and `/static` and `/{myparam:string}` and `/{myparam:string}/static` and `/static/{myparam:string}` all in one path prefix without a "decision handler". And generally if you had a wildcard it was possible to add (a single) static part and (a single) named parameter but not without performance cost and limits, why only one? (one is better than nothing: look the Iris' alternatives) We struggle to overcome our own selves, now you **can definitely do it without a bit of performance cost**, and surely we hand't imagine the wildcard to **catch all if nothing else found** without huge routing performance cost, the wildcard(`:path`) meant ONLY: "accept one or more path segments and put them into the declared parameter" so if you had register a dynamic single-path-segment named parameter like `:string, :int, :uint, :alphabetical...` in between those path segments it wouldn't work. The **closest wildcard resolution** offers you the opportunity to design your APIs even better via custom handlers and error handlers like `404 not found` to path prefixes for your API's groups, now you can do it without any custom code for path resolution inside a "decision handler" or a middleware. + +Code worths 1000 words, now it is possible to define your routes like this without any issues: + +```go +package main + +import ( + "github.com/kataras/iris" + "github.com/kataras/iris/context" +) + +func main() { + app := iris.New() + + // matches everyhing if nothing else found, + // so you can use it for custom 404 root-level/main pages! + app.Get("/{p:path}", func(ctx context.Context) { + path := ctx.Params().Get("p") + // gives the path without the first "/". + ctx.Writef("Site Custom 404 Error Message\nPage of: '%s' not found", path) + }) + + app.Get("/", indexHandler) + + // request: http://localhost:8080/profile + // response: "Profile Index" + app.Get("/profile", func(ctx context.Context) { + ctx.Writef("Profile Index") + }) + + // request: http://localhost:8080/profile/kataras + // response: "Profile of username: 'kataras'" + app.Get("/profile/{username}", func(ctx context.Context) { + username := ctx.Params().Get("username") + ctx.Writef("Profile of username: '%s'", username) + }) + + // request: http://localhost:8080/profile/settings + // response: "Profile personal settings" + app.Get("/profile/settings", func(ctx context.Context) { + ctx.Writef("Profile personal settings") + }) + + // request: http://localhost:8080/profile/settings/security + // response: "Profile personal security settings" + app.Get("/profile/settings/security", func(ctx context.Context) { + ctx.Writef("Profile personal security settings") + }) + + // matches everyhing /profile/*somethng_here* + // if no other route matches the path semgnet after the + // /profile or /profile/ + // + // So, you can use it for custom 404 profile pages + // side-by-side to your root wildcard without issues! + // For example: + // request: http://localhost:8080/profile/kataras/what + // response: + // Profile Page Custom 404 Error Message + // Profile Page of: 'kataras/what' was unable to be found + app.Get("/profile/{p:path}", func(ctx context.Context) { + path := ctx.Params().Get("p") + ctx.Writef("Profile Page Custom 404 Error Message\nProfile Page of: '%s' not found", path) + }) + + app.Run(iris.Addr(":8080")) +} + +func indexHandler(ctx context.Context) { + ctx.HTML("This is the index page") +} + +``` + The `github.com/kataras/iris/core/router.AllMethods` is now a variable that can be altered by end-developers, so things like `app.Any` can register to custom methods as well, as requested at: https://github.com/kataras/iris/issues/1102. For example, import that package and do `router.AllMethods = append(router.AllMethods, "LINK")` in your `main` or `init` function. The old `github.com/kataras/iris/core/router/macro` package was moved to `guthub.com/kataras/iris/macro` to allow end-developers to add custom parameter types and macros, it supports all go standard types by default as you will see below. @@ -48,7 +129,7 @@ Here is the full list of the built'n parameter types that we support now, includ | Param Type | Go Type | Validation | Retrieve Helper | | -----------------|------|-------------|------| -| `:string` | string | anything (single path segment) | `Params().Get` | +| `:string` | string | the default if param type is missing, anything (single path segment) | `Params().Get` | | `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` | | `:int8` | int8 | -128 to 127 | `Params().GetInt8` | | `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` | diff --git a/websocket/server.go b/websocket/server.go index f704a05ed1..a88f1806e6 100644 --- a/websocket/server.go +++ b/websocket/server.go @@ -52,9 +52,9 @@ type ( func New(cfg Config) *Server { cfg = cfg.Validate() return &Server{ - config: cfg, - connections: sync.Map{}, // ready-to-use, this is not necessary. - rooms: make(map[string][]string), + config: cfg, + connections: sync.Map{}, // ready-to-use, this is not necessary. + rooms: make(map[string][]string), onConnectionListeners: make([]ConnectionFunc, 0), upgrader: websocket.Upgrader{ HandshakeTimeout: cfg.HandshakeTimeout, @@ -352,9 +352,12 @@ func (s *Server) GetConnectionsByRoom(roomName string) []Connection { // let's keep it unexported for the best. func (s *Server) emitMessage(from, to string, data []byte) { if to != All && to != Broadcast { - if s.rooms[to] != nil { + s.mu.RLock() + room := s.rooms[to] + s.mu.RUnlock() + if room != nil { // it suppose to send the message to a specific room/or a user inside its own room - for _, connectionIDInsideRoom := range s.rooms[to] { + for _, connectionIDInsideRoom := range room { if c, ok := s.getConnection(connectionIDInsideRoom); ok { c.writeDefault(data) //send the message to the client(s) } else { From d570a906cc3272141d82960540aae570d53aacd4 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 17 Oct 2018 07:27:50 +0300 Subject: [PATCH 43/91] A note for the upcominng release in the main README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index b16ef2808c..c5b94a083a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +# ⚡️ The upcoming release is in-progress + +Please stay tuned by following its changelog before its official release. Click [here](https://github.com/kataras/iris/blob/v11/HISTORY.md#whenever--v1100) to read more about the upcoming release, version 11. + +> And, for the craziest of us, click [here](https://github.com/kataras/iris/compare/v10.7.0...v11) 🔥 to find out the commits and the code changes since current v10 + # Iris Web Framework From af7ad7b03f33b3580a754b22c401de731505926b Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Thu, 18 Oct 2018 20:56:41 +0300 Subject: [PATCH 44/91] add a HISTORY note about the Context#ReadForm return error --- HISTORY.md | 115 +++++++++++++++++++++++---------------------- context/context.go | 6 +-- 2 files changed, 60 insertions(+), 61 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index b3d5ab5384..67c65700bd 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -25,6 +25,7 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene - `:int` parameter type **can accept negative numbers now**. - `app.Macros().String/Int/Uint64/Path...RegisterFunc` should be replaced to: `app.Macros().Get("string" or "int" or "uint64" or "path" when "path" is the ":path" parameter type).RegisterFunc`, because you can now add custom macros and parameter types as well, see [here](_examples/routing/macros). - `RegisterFunc("min", func(paramValue string) bool {...})` should be replaced to `RegisterFunc("min", func(paramValue ) bool {...})`, the `paramValue` argument is now stored in the exact type the macro's type evaluator inits it, i.e `uint64` or `int` and so on, therefore you don't have to convert the parameter value each time (this should make your handlers with macro functions activated even faster now) +- The `Context#ReadForm` will no longer return an error if it has no value to read from the request, we let those checks to the caller and validators as requested at: https://github.com/kataras/iris/issues/1095 by [@haritsfahreza](https://github.com/haritsfahreza) ## Routing @@ -42,69 +43,69 @@ Code worths 1000 words, now it is possible to define your routes like this witho package main import ( - "github.com/kataras/iris" - "github.com/kataras/iris/context" + "github.com/kataras/iris" + "github.com/kataras/iris/context" ) func main() { - app := iris.New() - - // matches everyhing if nothing else found, - // so you can use it for custom 404 root-level/main pages! - app.Get("/{p:path}", func(ctx context.Context) { - path := ctx.Params().Get("p") - // gives the path without the first "/". - ctx.Writef("Site Custom 404 Error Message\nPage of: '%s' not found", path) - }) - - app.Get("/", indexHandler) - - // request: http://localhost:8080/profile - // response: "Profile Index" - app.Get("/profile", func(ctx context.Context) { - ctx.Writef("Profile Index") - }) - - // request: http://localhost:8080/profile/kataras - // response: "Profile of username: 'kataras'" - app.Get("/profile/{username}", func(ctx context.Context) { - username := ctx.Params().Get("username") - ctx.Writef("Profile of username: '%s'", username) - }) - - // request: http://localhost:8080/profile/settings - // response: "Profile personal settings" - app.Get("/profile/settings", func(ctx context.Context) { - ctx.Writef("Profile personal settings") - }) - - // request: http://localhost:8080/profile/settings/security - // response: "Profile personal security settings" - app.Get("/profile/settings/security", func(ctx context.Context) { - ctx.Writef("Profile personal security settings") - }) - - // matches everyhing /profile/*somethng_here* - // if no other route matches the path semgnet after the - // /profile or /profile/ - // - // So, you can use it for custom 404 profile pages - // side-by-side to your root wildcard without issues! - // For example: - // request: http://localhost:8080/profile/kataras/what - // response: - // Profile Page Custom 404 Error Message - // Profile Page of: 'kataras/what' was unable to be found - app.Get("/profile/{p:path}", func(ctx context.Context) { - path := ctx.Params().Get("p") - ctx.Writef("Profile Page Custom 404 Error Message\nProfile Page of: '%s' not found", path) - }) - - app.Run(iris.Addr(":8080")) + app := iris.New() + + // matches everyhing if nothing else found, + // so you can use it for custom 404 root-level/main pages! + app.Get("/{p:path}", func(ctx context.Context) { + path := ctx.Params().Get("p") + // gives the path without the first "/". + ctx.Writef("Site Custom 404 Error Message\nPage of: '%s' not found", path) + }) + + app.Get("/", indexHandler) + + // request: http://localhost:8080/profile + // response: "Profile Index" + app.Get("/profile", func(ctx context.Context) { + ctx.Writef("Profile Index") + }) + + // request: http://localhost:8080/profile/kataras + // response: "Profile of username: 'kataras'" + app.Get("/profile/{username}", func(ctx context.Context) { + username := ctx.Params().Get("username") + ctx.Writef("Profile of username: '%s'", username) + }) + + // request: http://localhost:8080/profile/settings + // response: "Profile personal settings" + app.Get("/profile/settings", func(ctx context.Context) { + ctx.Writef("Profile personal settings") + }) + + // request: http://localhost:8080/profile/settings/security + // response: "Profile personal security settings" + app.Get("/profile/settings/security", func(ctx context.Context) { + ctx.Writef("Profile personal security settings") + }) + + // matches everyhing /profile/*somethng_here* + // if no other route matches the path semgnet after the + // /profile or /profile/ + // + // So, you can use it for custom 404 profile pages + // side-by-side to your root wildcard without issues! + // For example: + // request: http://localhost:8080/profile/kataras/what + // response: + // Profile Page Custom 404 Error Message + // Profile Page of: 'kataras/what' was unable to be found + app.Get("/profile/{p:path}", func(ctx context.Context) { + path := ctx.Params().Get("p") + ctx.Writef("Profile Page Custom 404 Error Message\nProfile Page of: '%s' not found", path) + }) + + app.Run(iris.Addr(":8080")) } func indexHandler(ctx context.Context) { - ctx.HTML("This is the index page") + ctx.HTML("This is the index page") } ``` diff --git a/context/context.go b/context/context.go index a7ad80b183..7ea7822921 100644 --- a/context/context.go +++ b/context/context.go @@ -2864,12 +2864,10 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { options = opts[0] } - optimize := ctx.shouldOptimize() - ctx.ContentType(ContentJSONHeaderValue) if options.StreamingJSON { - if optimize { + if ctx.shouldOptimize() { var jsoniterConfig = jsoniter.Config{ EscapeHTML: !options.UnescapeHTML, IndentionStep: 4, @@ -2890,7 +2888,7 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { return ctx.writer.Written(), err } - n, err = WriteJSON(ctx.writer, v, options, optimize) + n, err = WriteJSON(ctx.writer, v, options, ctx.shouldOptimize()) if err != nil { ctx.StatusCode(http.StatusInternalServerError) return 0, err From 9dac15a758c9c1e42d955175bb8e9d31f565fe51 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 21 Oct 2018 18:46:10 +0300 Subject: [PATCH 45/91] version 11 release --- FAQ.md | 2 +- HISTORY.md | 2 +- HISTORY_GR.md | 6 +++++- HISTORY_ID.md | 4 ++++ HISTORY_ZH.md | 4 ++++ README.md | 4 ++-- README_GR.md | 4 ++-- README_ID.md | 4 ++-- README_JPN.md | 4 ++-- README_PT_BR.md | 4 ++-- README_RU.md | 4 ++-- README_ZH.md | 2 +- VERSION | 2 +- _examples/tutorial/vuejs-todo-mvc/README.md | 2 +- 14 files changed, 30 insertions(+), 18 deletions(-) diff --git a/FAQ.md b/FAQ.md index f5c8110d6d..1f5daf7f44 100644 --- a/FAQ.md +++ b/FAQ.md @@ -30,7 +30,7 @@ More than 100 practical examples, tutorials and articles at: - https://github.com/kataras/iris/tree/master/_examples - https://github.com/iris-contrib/examples -- https://iris-go.com/v10/recipe +- https://iris-go.com/v11/recipe - https://docs.iris-go.com (in-progress) - https://godoc.org/github.com/kataras/iris diff --git a/HISTORY.md b/HISTORY.md index 67c65700bd..2e985b3775 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -17,7 +17,7 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you. -# Whenever | v11.0.0 +# Su, 21 October 2018 | v11.0.0 ## Breaking changes diff --git a/HISTORY_GR.md b/HISTORY_GR.md index 30f1674517..98a1c416ae 100644 --- a/HISTORY_GR.md +++ b/HISTORY_GR.md @@ -17,9 +17,13 @@ **Πώς να αναβαθμίσετε**: Ανοίξτε την γραμμή εντολών σας και εκτελέστε αυτήν την εντολή: `go get -u github.com/kataras/iris` ή αφήστε το αυτόματο updater να το κάνει αυτό για σας. +# Su, 21 October 2018 | v11.0.0 + +Πατήστε [εδώ](https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v11) για να διαβάσετε στα αγγλικά τις αλλαγές και τα νέα features που φέρνει νεότερη έκδοση του Iris, version 11. + # Sat, 11 August 2018 | v10.7.0 -Είμαι στην πραγματικά ευχάριστη θέση να σας ανακοινώσω το πρώτο στάδιο της σταθερής κυκλοφορίας της έκδοσης **10.7** του Iris Web Framework. +Είμαι στην, πραγματικά, ευχάριστη θέση να σας ανακοινώσω το πρώτο στάδιο της σταθερής κυκλοφορίας της έκδοσης **10.7** του Iris Web Framework. Η έκδοση 10.7.0 είναι μέρος των [επίσημων διανομών μας](https://github.com/kataras/iris/releases). diff --git a/HISTORY_ID.md b/HISTORY_ID.md index 12a588bc78..36e3088578 100644 --- a/HISTORY_ID.md +++ b/HISTORY_ID.md @@ -17,6 +17,10 @@ Developers tidak diwajibkan untuk melakukan upgrade apabila mereka tidak membutu **Cara Upgrade**: Bukan command-line anda dan eksekuis perintah ini: `go get -u github.com/kataras/iris` atau biarkan updater otomatis melakukannya untuk anda. +# Su, 21 October 2018 | v11.0.0 + +This history entry is not translated yet to the Indonesian language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v11) instead. + # Sat, 11 August 2018 | v10.7.0 This history entry is not translated yet to the Indonesian language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#sat-11-august-2018--v1070) instead. diff --git a/HISTORY_ZH.md b/HISTORY_ZH.md index 653623f6cf..7ee04145f3 100644 --- a/HISTORY_ZH.md +++ b/HISTORY_ZH.md @@ -17,6 +17,10 @@ **如何升级**: 打开命令行执行以下命令: `go get -u github.com/kataras/iris` 或者等待自动更新。 +# Su, 21 October 2018 | v11.0.0 + +This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v11) instead. + # Sat, 11 August 2018 | v10.7.0 This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#sat-11-august-2018--v1070) instead. diff --git a/README.md b/README.md index de91acb807..e565563947 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/routing%20by-example-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/v11/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/routing%20by-example-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris is a fast, simple yet fully featured and very efficient web framework for Go. @@ -1006,7 +1006,7 @@ Iris, unlike others, is 100% compatible with the standards and that's why the ma ## Support -- [HISTORY](HISTORY.md#sat-11-august-2018--v1070) file is your best friend, it contains information about the latest features and changes +- [HISTORY](HISTORY.md#su-21-october-2018--v1100) file is your best friend, it contains information about the latest features and changes - Did you happen to find a bug? Post it at [github issues](https://github.com/kataras/iris/issues) - Do you have any questions or need to speak with someone experienced to solve a problem at real-time? Join us to the [community chat](https://chat.iris-go.com) - Complete our form-based user experience report by clicking [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_GR.md b/README_GR.md index 8fb8f43acd..7cc0640a67 100644 --- a/README_GR.md +++ b/README_GR.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Το Iris είναι ένα γρήγορο, απλό αλλά και πλήρως λειτουργικό και πολύ αποδοτικό web framework για τη Go. @@ -108,7 +108,7 @@ _Η τελευταία ενημέρωση έγινε την [Τρίτη, 21 Νο ## Υποστήριξη -- To [HISTORY](HISTORY_GR.md#sat-11-august-2018--v1070) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές +- To [HISTORY](HISTORY_GR.md#su-21-october-2018--v1100) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές - Μήπως τυχαίνει να βρήκατε κάποιο bug; Δημοσιεύστε το στα [github issues](https://github.com/kataras/iris/issues) - Έχετε οποιεσδήποτε ερωτήσεις ή πρέπει να μιλήσετε με κάποιον έμπειρο για την επίλυση ενός προβλήματος σε πραγματικό χρόνο; Ελάτε μαζί μας στην [συνομιλία κοινότητας](https://chat.iris-go.com) - Συμπληρώστε την αναφορά εμπειρίας χρήστη κάνοντας κλικ [εδώ](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_ID.md b/README_ID.md index 1839c15c94..1c0f442e97 100644 --- a/README_ID.md +++ b/README_ID.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris adalah web framework yang cepat, sederhana namun berfitur lengkap dan sangat efisien untuk Go. @@ -106,7 +106,7 @@ _Diperbarui pada: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_ ## Dukungan -- File [HISTORY](HISTORY_ID.md#sat-11-august-2018--v1070) adalah sahabat anda, file tersebut memiliki informasi terkait fitur dan perubahan terbaru +- File [HISTORY](HISTORY_ID.md#su-21-october-2018--v1100) adalah sahabat anda, file tersebut memiliki informasi terkait fitur dan perubahan terbaru - Apakah anda menemukan bug? Laporkan itu melalui [github issues](https://github.com/kataras/iris/issues) - Apakah anda memiliki pertanyaan atau butuh untuk bicara kepada seseorang yang sudah berpengalaman untuk menyelesaikan masalah secara langsung? Gabung bersama kami di [community chat](https://chat.iris-go.com) - Lengkapi laporan user-experience berbasis formulir kami dengan tekan [disini](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_JPN.md b/README_JPN.md index e2da517d16..ac0603be5f 100644 --- a/README_JPN.md +++ b/README_JPN.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Irisはシンプルで高速、それにも関わらず充実した機能を有する効率的なGo言語のウェブフレームワークです。 @@ -106,7 +106,7 @@ _Updated at: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_ ## 支援 -- [HISTORY](HISTORY.md#sat-11-august-2018--v1070)ファイルはあなたの友人です。このファイルには、機能に関する最新の情報や変更点が記載されています。 +- [HISTORY](HISTORY.md#su-21-october-2018--v1100)ファイルはあなたの友人です。このファイルには、機能に関する最新の情報や変更点が記載されています。 - バグを発見しましたか?[github issues](https://github.com/kataras/iris/issues)に投稿をお願い致します。 - 質問がありますか?または問題を即時に解決するため、熟練者に相談する必要がありますか?[community chat](https://chat.iris-go.com)に参加しましょう。 - [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)をクリックしてユーザーとしての体験を報告しましょう。 diff --git a/README_PT_BR.md b/README_PT_BR.md index cdaaf03c1c..22f0c4f2cc 100644 --- a/README_PT_BR.md +++ b/README_PT_BR.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris é um framework rápido, simples porém completo e muito eficiente para a linguagem Go. @@ -106,7 +106,7 @@ _Atualizado em : [Terça, 21 de Novembro de 2017](_benchmarks/README_UNIX.md)_ ## Apoie -- [HISTORY](HISTORY.md#sat-11-august-2018--v1070) o arquivo HISTORY é o seu melhor amigo, ele contém informações sobre as últimas features e mudanças. +- [HISTORY](HISTORY.md#su-21-october-2018--v1100) o arquivo HISTORY é o seu melhor amigo, ele contém informações sobre as últimas features e mudanças. - Econtrou algum bug ? Poste-o nas [issues](https://github.com/kataras/iris/issues) - Possui alguma dúvida ou gostaria de falar com alguém experiente para resolver seu problema em tempo real ? Junte-se ao [chat da nossa comunidade](https://chat.iris-go.com). - Complete nosso formulário de experiência do usuário clicando [aqui](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_RU.md b/README_RU.md index 201e2d9824..99317d9dbb 100644 --- a/README_RU.md +++ b/README_RU.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris - это быстрая, простая, но полнофункциональная и очень эффективная веб-платформа для Go. @@ -106,7 +106,7 @@ _Обновлено: [Вторник, 21 ноября 2017 г.](_benchmarks/READ ## Поддержка -- Файл [HISTORY](HISTORY.md#sat-11-august-2018--v1070) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях +- Файл [HISTORY](HISTORY.md#su-21-october-2018--v1100) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях - Вы случайно обнаружили ошибку? Опубликуйте ее на [Github вопросы](https://github.com/kataras/iris/issues) - У Вас есть какие-либо вопросы или Вам нужно поговорить с кем-то, кто бы смог решить Вашу проблему в режиме реального времени? Присоединяйтесь к нам в [чате сообщества](https://chat.iris-go.com) - Заполните наш отчет о пользовательском опыте на основе формы, нажав [здесь](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_ZH.md b/README_ZH.md index 95721dd592..e6873a34ea 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris 是一款超快、简洁高效的 Go 语言 Web开发框架。 diff --git a/VERSION b/VERSION index 8274a13e99..236610e098 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -11.0.0:https://github.com/kataras/iris/blob/master/HISTORY.md#day??-dayN??-october-2018--v1100 \ No newline at end of file +11.0.0:https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v1100 \ No newline at end of file diff --git a/_examples/tutorial/vuejs-todo-mvc/README.md b/_examples/tutorial/vuejs-todo-mvc/README.md index 666f862d23..b856373bcc 100644 --- a/_examples/tutorial/vuejs-todo-mvc/README.md +++ b/_examples/tutorial/vuejs-todo-mvc/README.md @@ -27,7 +27,7 @@ Many articles have been written, in the past, that lead developers not to use a You’ll need two dependencies: 1. Vue.js, for our client-side requirements. Download it from [here](https://vuejs.org/), latest v2. -2. The Iris Web Framework, for our server-side requirements. Can be found [here](https://github.com/kataras/iris), latest v10. +2. The Iris Web Framework, for our server-side requirements. Can be found [here](https://github.com/kataras/iris), latest v11. > If you have Go already installed then just execute `go get -u github.com/kataras/iris` to install the Iris Web Framework. From 93e6467081ecd0451a068891f70357aa5a5a90d6 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 21 Oct 2018 19:01:56 +0300 Subject: [PATCH 46/91] add a HISTORY.md note about the new commits --- HISTORY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index 2e985b3775..c8a79237f7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -19,6 +19,8 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene # Su, 21 October 2018 | v11.0.0 +For the craziest of us, click [here](https://github.com/kataras/iris/compare/v10.7.0...v11) 🔥 to find out the commits and the code changes since our previous release. + ## Breaking changes - Remove the "Configurator" `WithoutVersionChecker` and the configuration field `DisableVersionChecker` From 978d1ede8475e0f4d0f64d440f75c96912a6bc21 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 21 Oct 2018 19:20:05 +0300 Subject: [PATCH 47/91] Version 11 released. Read https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v1100 --- .travis.yml | 11 +- Dockerfile | 5 - Dockerfile.build | 12 - FAQ.md | 2 +- Gopkg.lock | 24 +- Gopkg.toml | 18 +- HISTORY.md | 170 ++- HISTORY_GR.md | 6 +- HISTORY_ID.md | 6 +- HISTORY_ZH.md | 6 +- README.md | 94 +- README_GR.md | 4 +- README_ID.md | 4 +- README_JPN.md | 4 +- README_PT_BR.md | 4 +- README_RU.md | 4 +- README_ZH.md | 2 +- VERSION | 2 +- _benchmarks/iris-mvc-templates/main.go | 2 +- _benchmarks/iris-mvc/main.go | 2 +- _benchmarks/iris-sessions/main.go | 4 +- _benchmarks/iris/main.go | 2 +- _examples/README.md | 4 +- _examples/README_ZH.md | 2 + _examples/hero/overview/main.go | 2 - _examples/hero/sessions/main.go | 1 - .../README.md | 2 - .../counter/configurator.go | 30 - .../main.go | 22 +- .../http-listening/notify-on-shutdown/main.go | 16 +- .../http_request/extract-referer/main.go | 2 +- _examples/mvc/login/main.go | 2 - _examples/mvc/overview/main.go | 2 - _examples/mvc/session-controller/main.go | 2 +- _examples/overview/main.go | 2 +- _examples/routing/README.md | 174 +++- _examples/routing/basic/main.go | 6 +- .../routing/custom-high-level-router/main.go | 103 ++ _examples/routing/dynamic-path/main.go | 131 ++- _examples/routing/macros/main.go | 76 ++ _examples/routing/main.go | 30 +- _examples/routing/overview/main.go | 12 +- _examples/subdomains/www/main.go | 4 +- _examples/tutorial/url-shortener/main.go | 2 +- _examples/tutorial/url-shortener/store.go | 2 +- _examples/tutorial/vuejs-todo-mvc/README.md | 4 +- .../tutorial/vuejs-todo-mvc/src/web/main.go | 2 +- .../{hello_go11beta3.go => hello_go111.go} | 4 +- _examples/webassembly/basic/main.go | 2 +- configuration.go | 18 - configuration_test.go | 13 +- context/context.go | 146 +-- context/request_params.go | 206 ++++ context/route.go | 4 +- core/host/proxy_test.go | 2 +- core/maintenance/maintenance.go | 6 - core/maintenance/version.go | 78 -- core/maintenance/version/fetch.go | 59 -- core/maintenance/version/version.go | 64 -- core/memstore/memstore.go | 662 ++++++++++-- core/router/api_builder.go | 28 +- core/router/handler.go | 133 ++- core/router/macro.go | 253 ----- core/router/macro/interpreter/ast/ast.go | 215 ---- core/router/macro/macro.go | 292 ------ core/router/macro/macro_test.go | 186 ---- core/router/macro/template.go | 75 -- core/router/node/node.go | 448 -------- core/router/party.go | 12 +- core/router/path.go | 32 +- core/router/path_test.go | 10 +- core/router/route.go | 42 +- core/router/router.go | 24 +- core/router/router_wildcard_root_test.go | 13 +- core/router/trie.go | 268 +++++ deprecated.go | 1 - doc.go | 125 ++- go.mod | 68 ++ go.sum | 148 +++ hero/di.go | 11 + hero/di/func.go | 7 +- hero/di/object.go | 5 + hero/di/reflect.go | 12 +- hero/handler.go | 8 +- hero/param.go | 44 +- httptest/httptest.go | 3 +- iris.go | 24 +- macro/AUTHORS | 4 + macro/LICENSE | 27 + macro/handler/handler.go | 56 + macro/handler/handler_test.go | 41 + macro/interpreter/ast/ast.go | 132 +++ .../interpreter/lexer/lexer.go | 4 +- .../interpreter/lexer/lexer_test.go | 2 +- .../interpreter/parser/parser.go | 67 +- .../interpreter/parser/parser_test.go | 154 ++- .../interpreter/token/token.go | 2 +- macro/macro.go | 344 +++++++ macro/macro_test.go | 453 ++++++++ macro/macros.go | 550 ++++++++++ macro/template.go | 155 +++ mvc/controller.go | 12 +- mvc/controller_handle_test.go | 8 +- mvc/controller_method_parser.go | 84 +- mvc/controller_test.go | 19 +- mvc/param.go | 78 +- sessions/sessiondb/badger/database.go | 24 +- sessions/sessiondb/badger/vendor/badger.txt | 5 + .../vendor/github.com/dgraph-io/badger.txt | 3 - .../github.com/dgraph-io/badger/backup.go | 32 +- .../github.com/dgraph-io/badger/compaction.go | 8 +- .../vendor/github.com/dgraph-io/badger/db.go | 368 +++---- .../github.com/dgraph-io/badger/dir_unix.go | 43 +- .../dgraph-io/badger/dir_windows.go | 6 +- .../vendor/github.com/dgraph-io/badger/doc.go | 28 - .../github.com/dgraph-io/badger/errors.go | 22 +- .../github.com/dgraph-io/badger/iterator.go | 118 ++- .../github.com/dgraph-io/badger/levels.go | 303 ++++-- .../github.com/dgraph-io/badger/managed_db.go | 121 ++- .../github.com/dgraph-io/badger/manifest.go | 30 +- .../github.com/dgraph-io/badger/options.go | 41 +- .../github.com/dgraph-io/badger/skl/arena.go | 11 +- .../github.com/dgraph-io/badger/skl/skl.go | 16 +- .../dgraph-io/badger/table/builder.go | 2 +- .../dgraph-io/badger/table/iterator.go | 2 + .../dgraph-io/badger/table/table.go | 8 - .../dgraph-io/badger/transaction.go | 232 +++-- .../github.com/dgraph-io/badger/value.go | 481 ++++++--- .../golang/protobuf/proto/proto3_test.go | 151 --- .../dgraph-io/badger/y/file_dsync.go | 6 +- .../dgraph-io/badger/y/file_nodsync.go | 2 +- .../dgraph-io/badger/y/watermark.go | 130 +++ .../vendor/github.com/dgraph-io/badger/y/y.go | 25 +- sessions/sessiondb/boltdb/database.go | 5 +- .../{coreos => etcd-io}/bbolt/LICENSE | 0 .../{coreos => etcd-io}/bbolt/bolt_386.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_amd64.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_arm.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_arm64.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_linux.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_mips64x.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_mipsx.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_openbsd.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_ppc.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_ppc64.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_ppc64le.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_s390x.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_unix.go | 2 +- .../bbolt/bolt_unix_solaris.go | 2 +- .../{coreos => etcd-io}/bbolt/bolt_windows.go | 2 +- .../bbolt/boltsync_unix.go | 2 +- .../{coreos => etcd-io}/bbolt/bucket.go | 2 +- .../{coreos => etcd-io}/bbolt/cursor.go | 2 +- .../{coreos => etcd-io}/bbolt/db.go | 6 +- .../{coreos => etcd-io}/bbolt/errors.go | 2 +- .../{coreos => etcd-io}/bbolt/freelist.go | 2 +- .../{coreos => etcd-io}/bbolt/node.go | 2 +- .../{coreos => etcd-io}/bbolt/page.go | 2 +- .../{coreos => etcd-io}/bbolt/tx.go | 2 +- .../github.com/hashicorp/go-version/LICENSE | 354 ------- .../hashicorp/go-version/constraint.go | 204 ---- .../hashicorp/go-version/version.go | 347 ------- .../go-version/version_collection.go | 17 - vendor/github.com/kataras/survey/LICENSE | 22 - vendor/github.com/kataras/survey/confirm.go | 138 --- .../kataras/survey/core/renderer.go | 62 -- .../kataras/survey/core/template.go | 83 -- .../github.com/kataras/survey/core/write.go | 244 ----- .../kataras/survey/core/write_test.go | 543 ---------- vendor/github.com/kataras/survey/editor.go | 168 --- vendor/github.com/kataras/survey/input.go | 98 -- .../github.com/kataras/survey/multiselect.go | 203 ---- vendor/github.com/kataras/survey/password.go | 84 -- vendor/github.com/kataras/survey/select.go | 209 ---- vendor/github.com/kataras/survey/survey.go | 198 ---- .../kataras/survey/terminal/cursor.go | 134 --- .../kataras/survey/terminal/cursor_windows.go | 101 -- .../kataras/survey/terminal/display.go | 9 - .../kataras/survey/terminal/display_posix.go | 11 - .../survey/terminal/display_windows.go | 28 - .../kataras/survey/terminal/error.go | 9 - .../kataras/survey/terminal/output.go | 20 - .../kataras/survey/terminal/output_windows.go | 228 ----- .../kataras/survey/terminal/print.go | 25 - .../kataras/survey/terminal/runereader.go | 183 ---- .../kataras/survey/terminal/runereader_bsd.go | 13 - .../survey/terminal/runereader_linux.go | 12 - .../survey/terminal/runereader_posix.go | 84 -- .../survey/terminal/runereader_windows.go | 130 --- .../kataras/survey/terminal/sequences.go | 18 - .../survey/terminal/syscall_windows.go | 39 - .../kataras/survey/terminal/terminal.go | 8 - vendor/github.com/kataras/survey/transform.go | 76 -- vendor/github.com/kataras/survey/validate.go | 92 -- .../github.com/mattn/go-colorable/LICENSE | 21 - .../mattn/go-colorable/colorable_appengine.go | 29 - .../mattn/go-colorable/colorable_others.go | 30 - .../mattn/go-colorable/colorable_windows.go | 968 ------------------ .../mattn/go-colorable/noncolorable.go | 55 - .../vendor/github.com/mattn/go-isatty/LICENSE | 9 - .../vendor/github.com/mattn/go-isatty/doc.go | 2 - .../mattn/go-isatty/isatty_appengine.go | 15 - .../github.com/mattn/go-isatty/isatty_bsd.go | 18 - .../mattn/go-isatty/isatty_linux.go | 18 - .../mattn/go-isatty/isatty_linux_ppc64x.go | 19 - .../mattn/go-isatty/isatty_others.go | 10 - .../mattn/go-isatty/isatty_solaris.go | 16 - .../mattn/go-isatty/isatty_windows.go | 94 -- .../vendor/github.com/mgutz/ansi/LICENSE | 9 - .../vendor/github.com/mgutz/ansi/ansi.go | 285 ------ .../vendor/github.com/mgutz/ansi/doc.go | 65 -- .../vendor/github.com/mgutz/ansi/print.go | 57 -- vendor/github.com/satori/go.uuid/LICENSE | 20 - vendor/github.com/satori/go.uuid/codec.go | 206 ---- vendor/github.com/satori/go.uuid/generator.go | 265 ----- vendor/github.com/satori/go.uuid/sql.go | 78 -- vendor/github.com/satori/go.uuid/uuid.go | 161 --- websocket/connection.go | 8 + 218 files changed, 5750 insertions(+), 10143 deletions(-) delete mode 100644 Dockerfile delete mode 100644 Dockerfile.build delete mode 100644 _examples/http-listening/iris-configurator-and-host-configurator/README.md delete mode 100644 _examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go create mode 100644 _examples/routing/custom-high-level-router/main.go create mode 100644 _examples/routing/macros/main.go rename _examples/webassembly/basic/client/{hello_go11beta3.go => hello_go111.go} (70%) create mode 100644 context/request_params.go delete mode 100644 core/maintenance/maintenance.go delete mode 100644 core/maintenance/version.go delete mode 100644 core/maintenance/version/fetch.go delete mode 100644 core/maintenance/version/version.go delete mode 100644 core/router/macro.go delete mode 100644 core/router/macro/interpreter/ast/ast.go delete mode 100644 core/router/macro/macro.go delete mode 100644 core/router/macro/macro_test.go delete mode 100644 core/router/macro/template.go delete mode 100644 core/router/node/node.go create mode 100644 core/router/trie.go delete mode 100644 deprecated.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 macro/AUTHORS create mode 100644 macro/LICENSE create mode 100644 macro/handler/handler.go create mode 100644 macro/handler/handler_test.go create mode 100644 macro/interpreter/ast/ast.go rename {core/router/macro => macro}/interpreter/lexer/lexer.go (98%) rename {core/router/macro => macro}/interpreter/lexer/lexer_test.go (94%) rename {core/router/macro => macro}/interpreter/parser/parser.go (70%) rename {core/router/macro => macro}/interpreter/parser/parser_test.go (56%) rename {core/router/macro => macro}/interpreter/token/token.go (96%) create mode 100644 macro/macro.go create mode 100644 macro/macro_test.go create mode 100644 macro/macros.go create mode 100644 macro/template.go create mode 100644 sessions/sessiondb/badger/vendor/badger.txt delete mode 100644 sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt delete mode 100644 sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/doc.go delete mode 100644 sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/vendor/github.com/golang/protobuf/proto/proto3_test.go create mode 100644 sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/watermark.go rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/LICENSE (100%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_386.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_amd64.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_arm.go (98%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_arm64.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_linux.go (91%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_mips64x.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_mipsx.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_openbsd.go (97%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_ppc.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_ppc64.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_ppc64le.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_s390x.go (95%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_unix.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_unix_solaris.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bolt_windows.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/boltsync_unix.go (91%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/bucket.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/cursor.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/db.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/errors.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/freelist.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/node.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/page.go (99%) rename sessions/sessiondb/boltdb/vendor/github.com/{coreos => etcd-io}/bbolt/tx.go (99%) delete mode 100644 vendor/github.com/hashicorp/go-version/LICENSE delete mode 100644 vendor/github.com/hashicorp/go-version/constraint.go delete mode 100644 vendor/github.com/hashicorp/go-version/version.go delete mode 100644 vendor/github.com/hashicorp/go-version/version_collection.go delete mode 100644 vendor/github.com/kataras/survey/LICENSE delete mode 100644 vendor/github.com/kataras/survey/confirm.go delete mode 100644 vendor/github.com/kataras/survey/core/renderer.go delete mode 100644 vendor/github.com/kataras/survey/core/template.go delete mode 100644 vendor/github.com/kataras/survey/core/write.go delete mode 100644 vendor/github.com/kataras/survey/core/write_test.go delete mode 100644 vendor/github.com/kataras/survey/editor.go delete mode 100644 vendor/github.com/kataras/survey/input.go delete mode 100644 vendor/github.com/kataras/survey/multiselect.go delete mode 100644 vendor/github.com/kataras/survey/password.go delete mode 100644 vendor/github.com/kataras/survey/select.go delete mode 100644 vendor/github.com/kataras/survey/survey.go delete mode 100644 vendor/github.com/kataras/survey/terminal/cursor.go delete mode 100644 vendor/github.com/kataras/survey/terminal/cursor_windows.go delete mode 100644 vendor/github.com/kataras/survey/terminal/display.go delete mode 100644 vendor/github.com/kataras/survey/terminal/display_posix.go delete mode 100644 vendor/github.com/kataras/survey/terminal/display_windows.go delete mode 100644 vendor/github.com/kataras/survey/terminal/error.go delete mode 100644 vendor/github.com/kataras/survey/terminal/output.go delete mode 100644 vendor/github.com/kataras/survey/terminal/output_windows.go delete mode 100644 vendor/github.com/kataras/survey/terminal/print.go delete mode 100644 vendor/github.com/kataras/survey/terminal/runereader.go delete mode 100644 vendor/github.com/kataras/survey/terminal/runereader_bsd.go delete mode 100644 vendor/github.com/kataras/survey/terminal/runereader_linux.go delete mode 100644 vendor/github.com/kataras/survey/terminal/runereader_posix.go delete mode 100644 vendor/github.com/kataras/survey/terminal/runereader_windows.go delete mode 100644 vendor/github.com/kataras/survey/terminal/sequences.go delete mode 100644 vendor/github.com/kataras/survey/terminal/syscall_windows.go delete mode 100644 vendor/github.com/kataras/survey/terminal/terminal.go delete mode 100644 vendor/github.com/kataras/survey/transform.go delete mode 100644 vendor/github.com/kataras/survey/validate.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/LICENSE delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_appengine.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_others.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_windows.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/noncolorable.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/LICENSE delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/doc.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_appengine.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_bsd.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_others.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_solaris.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_windows.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/LICENSE delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/ansi.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/doc.go delete mode 100644 vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/print.go delete mode 100644 vendor/github.com/satori/go.uuid/LICENSE delete mode 100644 vendor/github.com/satori/go.uuid/codec.go delete mode 100644 vendor/github.com/satori/go.uuid/generator.go delete mode 100644 vendor/github.com/satori/go.uuid/sql.go delete mode 100644 vendor/github.com/satori/go.uuid/uuid.go diff --git a/.travis.yml b/.travis.yml index 6e6ada55f5..a1912a5077 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,13 +3,14 @@ os: - linux - osx go: - - "go1.9" - - "go1.10" + - 1.9.x + - 1.10.x + - 1.11.x go_import_path: github.com/kataras/iris # we disable test caching via GOCACHE=off -env: - global: - - GOCACHE=off +# env: +# global: +# - GOCACHE=off install: - go get ./... # for iris-contrib/httpexpect, kataras/golog script: diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 54e3725a70..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM irisgo/cloud-native-go:latest - -ENV APPSOURCES /go/src/github.com/iris-contrib/cloud-native-go - -RUN ${APPSOURCES}/cloud-native-go \ No newline at end of file diff --git a/Dockerfile.build b/Dockerfile.build deleted file mode 100644 index 6c127b0e21..0000000000 --- a/Dockerfile.build +++ /dev/null @@ -1,12 +0,0 @@ -FROM golang:1.9.3-alpine - -RUN apk update && apk upgrade && apk add --no-cache bash git -RUN go get github.com/iris-contrib/cloud-native-go - -ENV SOURCES /go/src/github.com/iris-contrib/cloud-native-go -# COPY . ${SOURCES} - -RUN cd ${SOURCES} $$ CGO_ENABLED=0 go build - -ENTRYPOINT cloud-native-go -EXPOSE 8080 \ No newline at end of file diff --git a/FAQ.md b/FAQ.md index f5c8110d6d..1f5daf7f44 100644 --- a/FAQ.md +++ b/FAQ.md @@ -30,7 +30,7 @@ More than 100 practical examples, tutorials and articles at: - https://github.com/kataras/iris/tree/master/_examples - https://github.com/iris-contrib/examples -- https://iris-go.com/v10/recipe +- https://iris-go.com/v11/recipe - https://docs.iris-go.com (in-progress) - https://godoc.org/github.com/kataras/iris diff --git a/Gopkg.lock b/Gopkg.lock index 944aae4d64..641072082a 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -115,12 +115,6 @@ packages = [".","terminal"] revision = "825e39f34365e7db2c9fbc3692c16220e3bd7418" -[[projects]] - branch = "v2" - name = "github.com/kataras/survey" - packages = [".","terminal","core"] - revision = "00934ae069eda15df26fa427ac393e67e239380c" - [[projects]] name = "github.com/klauspost/compress" packages = ["flate","gzip"] @@ -277,6 +271,24 @@ packages = ["."] revision = "aad8439df3bf67adb025382ee2e5f46a72fc9456" +[[projects]] + branch = "master" + name = "github.com/dgraph-io/badger" + packages = ["."] + revision = "99233d725dbdd26d156c61b2f42ae1671b794656" + +[[projects]] + branch = "master" + name = "github.com/etcd-io/bbolt" + packages = ["."] + revision = "acbc2c426a444a65e0cbfdcbb3573857bf18b465" + +[[projects]] + branch = "master" + name = "github.com/gomodule/redigo" + packages = ["internal","redis"] + revision = "2cd21d9966bf7ff9ae091419744f0b3fb0fecace" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 0226fcefa0..b03d869347 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -42,10 +42,6 @@ branch = "master" name = "github.com/kataras/golog" -[[constraint]] - branch = "v2" - name = "github.com/kataras/survey" - [[constraint]] name = "github.com/klauspost/compress" version = "1.2.1" @@ -80,4 +76,16 @@ [[constraint]] branch = "master" - name = "github.com/Shopify/goreferrer" \ No newline at end of file + name = "github.com/Shopify/goreferrer" + +[[constraint]] + name = "github.com/dgraph-io/badger" + version = "1.5.4" + +[[constraint]] + name = "github.com/etcd-io/bbolt" + version = "1.3.1-etcd.7" + +[[constraint]] + branch = "master" + name = "github.com/gomodule/redigo" diff --git a/HISTORY.md b/HISTORY.md index b042fd7cf1..9474eb4bb6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -17,6 +17,174 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you. +# Su, 21 October 2018 | v11.0.0 + +For the craziest of us, click [here](https://github.com/kataras/iris/compare/v10.7.0...v11) 🔥 to find out the commits and the code changes since our previous release. + +## Breaking changes + +- Remove the "Configurator" `WithoutVersionChecker` and the configuration field `DisableVersionChecker` +- `:int` parameter type **can accept negative numbers now**. +- `app.Macros().String/Int/Uint64/Path...RegisterFunc` should be replaced to: `app.Macros().Get("string" or "int" or "uint64" or "path" when "path" is the ":path" parameter type).RegisterFunc`, because you can now add custom macros and parameter types as well, see [here](_examples/routing/macros). +- `RegisterFunc("min", func(paramValue string) bool {...})` should be replaced to `RegisterFunc("min", func(paramValue ) bool {...})`, the `paramValue` argument is now stored in the exact type the macro's type evaluator inits it, i.e `uint64` or `int` and so on, therefore you don't have to convert the parameter value each time (this should make your handlers with macro functions activated even faster now) +- The `Context#ReadForm` will no longer return an error if it has no value to read from the request, we let those checks to the caller and validators as requested at: https://github.com/kataras/iris/issues/1095 by [@haritsfahreza](https://github.com/haritsfahreza) + +## Routing + +I wrote a [new router implementation](https://github.com/kataras/muxie#philosophy) for our Iris internal(low-level) routing mechanism, it is good to know that this was the second time we have updated the router internals without a single breaking change after the v6, thanks to the very well-writen and designed-first code we have for the high-level path syntax component called [macro interpreter](macro/interpreter). + +The new router supports things like **closest wildcard resolution**. + +> If the name doesn't sound good to you it is because I named that feature myself, I don't know any other framework or router that supports a thing like that so be gentle:) + +Previously you couldn't register routes like: `/{myparam:path}` and `/static` and `/{myparam:string}` and `/{myparam:string}/static` and `/static/{myparam:string}` all in one path prefix without a "decision handler". And generally if you had a wildcard it was possible to add (a single) static part and (a single) named parameter but not without performance cost and limits, why only one? (one is better than nothing: look the Iris' alternatives) We struggle to overcome our own selves, now you **can definitely do it without a bit of performance cost**, and surely we hand't imagine the wildcard to **catch all if nothing else found** without huge routing performance cost, the wildcard(`:path`) meant ONLY: "accept one or more path segments and put them into the declared parameter" so if you had register a dynamic single-path-segment named parameter like `:string, :int, :uint, :alphabetical...` in between those path segments it wouldn't work. The **closest wildcard resolution** offers you the opportunity to design your APIs even better via custom handlers and error handlers like `404 not found` to path prefixes for your API's groups, now you can do it without any custom code for path resolution inside a "decision handler" or a middleware. + +Code worths 1000 words, now it is possible to define your routes like this without any issues: + +```go +package main + +import ( + "github.com/kataras/iris" + "github.com/kataras/iris/context" +) + +func main() { + app := iris.New() + + // matches everyhing if nothing else found, + // so you can use it for custom 404 root-level/main pages! + app.Get("/{p:path}", func(ctx context.Context) { + path := ctx.Params().Get("p") + // gives the path without the first "/". + ctx.Writef("Site Custom 404 Error Message\nPage of: '%s' not found", path) + }) + + app.Get("/", indexHandler) + + // request: http://localhost:8080/profile + // response: "Profile Index" + app.Get("/profile", func(ctx context.Context) { + ctx.Writef("Profile Index") + }) + + // request: http://localhost:8080/profile/kataras + // response: "Profile of username: 'kataras'" + app.Get("/profile/{username}", func(ctx context.Context) { + username := ctx.Params().Get("username") + ctx.Writef("Profile of username: '%s'", username) + }) + + // request: http://localhost:8080/profile/settings + // response: "Profile personal settings" + app.Get("/profile/settings", func(ctx context.Context) { + ctx.Writef("Profile personal settings") + }) + + // request: http://localhost:8080/profile/settings/security + // response: "Profile personal security settings" + app.Get("/profile/settings/security", func(ctx context.Context) { + ctx.Writef("Profile personal security settings") + }) + + // matches everyhing /profile/*somethng_here* + // if no other route matches the path semgnet after the + // /profile or /profile/ + // + // So, you can use it for custom 404 profile pages + // side-by-side to your root wildcard without issues! + // For example: + // request: http://localhost:8080/profile/kataras/what + // response: + // Profile Page Custom 404 Error Message + // Profile Page of: 'kataras/what' was unable to be found + app.Get("/profile/{p:path}", func(ctx context.Context) { + path := ctx.Params().Get("p") + ctx.Writef("Profile Page Custom 404 Error Message\nProfile Page of: '%s' not found", path) + }) + + app.Run(iris.Addr(":8080")) +} + +func indexHandler(ctx context.Context) { + ctx.HTML("This is the index page") +} + +``` + +The `github.com/kataras/iris/core/router.AllMethods` is now a variable that can be altered by end-developers, so things like `app.Any` can register to custom methods as well, as requested at: https://github.com/kataras/iris/issues/1102. For example, import that package and do `router.AllMethods = append(router.AllMethods, "LINK")` in your `main` or `init` function. + +The old `github.com/kataras/iris/core/router/macro` package was moved to `guthub.com/kataras/iris/macro` to allow end-developers to add custom parameter types and macros, it supports all go standard types by default as you will see below. + +- `:int` parameter type as an alias to the old `:int` which can accept any numeric path segment now, both negative and positive numbers +- Add `:int8` parameter type and `ctx.Params().GetInt8` +- Add `:int16` parameter type and `ctx.Params().GetInt16` +- Add `:int32` parameter type and `ctx.Params().GetInt32` +- Add `:int64` parameter type and `ctx.Params().GetInt64` +- Add `:uint` parameter type and `ctx.Params().GetUint` +- Add `:uint8` parameter type and `ctx.Params().GetUint8` +- Add `:uint16` parameter type and `ctx.Params().GetUint16` +- Add `:uint32` parameter type and `ctx.Params().GetUint32` +- Add `:uint64` parameter type and `ctx.Params().GetUint64` +- Add alias `:bool` for the `:boolean` parameter type + +Here is the full list of the built'n parameter types that we support now, including their validations/path segment rules. + +| Param Type | Go Type | Validation | Retrieve Helper | +| -----------------|------|-------------|------| +| `:string` | string | the default if param type is missing, anything (single path segment) | `Params().Get` | +| `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` | +| `:int8` | int8 | -128 to 127 | `Params().GetInt8` | +| `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` | +| `:int32` | int32 | -2147483648 to 2147483647 | `Params().GetInt32` | +| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | +| `:uint` | uint | 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32), depends on the host arch | `Params().GetUint` | +| `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | +| `:uint16` | uint16 | 0 to 65535 | `Params().GetUint16` | +| `:uint32` | uint32 | 0 to 4294967295 | `Params().GetUint32` | +| `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | +| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | `Params().GetBool` | +| `:alphabetical` | string | lowercase or uppercase letters | `Params().Get` | +| `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | `Params().Get` | +| `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | `Params().Get` | + +**Usage**: + +```go +app.Get("/users/{id:uint64}", func(ctx iris.Context){ + id, _ := ctx.Params().GetUint64("id") + // [...] +}) +``` + +| Built'n Func | Param Types | +| -----------|---------------| +| `regexp`(expr string) | :string | +| `prefix`(prefix string) | :string | +| `suffix`(suffix string) | :string | +| `contains`(s string) | :string | +| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | + +**Usage**: + +```go +app.Get("/profile/{name:alphabetical max(255)}", func(ctx iris.Context){ + name := ctx.Params().Get("name") + // len(name) <=255 otherwise this route will fire 404 Not Found + // and this handler will not be executed at all. +}) +``` + +## Vendoring + +- Rename the vendor `sessions/sessiondb/vendor/...bbolt` from `coreos/bbolt` to `etcd-io/bbolt` and update to v1.3.1, based on [that](https://github.com/etcd-io/bbolt/releases/tag/v1.3.1-etcd.7) +- Update the vendor `sessions/sessiondb/vendor/...badger` to v1.5.3 + +I believe it is soon to adapt the new [go modules](https://github.com/golang/go/wiki/Modules#table-of-contents) inside Iris, the new `go mod` command may change until go 1.12, it is still an experimental feature. +The [vendor](https://github.com/kataras/iris/tree/master/vendor) folder will be kept until the majority of Go developers get acquainted with the new `go modules`. The `go.mod` and `go.sum` files will come at `iris v12` (or `go 1.12`), we could do that on this version as well but I don't want to have half-things, versioning should be passed on import path as well and that is a large breaking change to go with it right now, so it will probably have a new path such as `github.com/kataras/iris/v12` based on a `git tag` like every Iris release (we are lucky here because we used semantic versioning from day zero). No folder re-structure inside the root git repository to split versions will ever happen, so backwards-compatibility for older go versions(before go 1.9.3) and iris versions will be not enabled by-default although it's easy for anyone to grab any version from older [releases](https://github.com/kataras/iris/releases) or branch and target that. + # Sat, 11 August 2018 | v10.7.0 I am overjoyed to announce stage 1 of the the Iris Web framework **10.7 stable release is now available**. @@ -228,7 +396,7 @@ For example: at [_examples/mvc/basic/main.go line 100](_examples/mvc/basic/main. - fix `APIBuilder, Party#StaticWeb` and `APIBuilder, Party#StaticEmbedded` wrong strip prefix inside children parties - keep the `iris, core/router#StaticEmbeddedHandler` and remove the `core/router/APIBuilder#StaticEmbeddedHandler`, (note the `Handler` suffix) it's global and has nothing to do with the `Party` or the `APIBuilder` -- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](core/router/macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) +- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) - sync the `golang.org/x/sys/unix` vendor ## The most important diff --git a/HISTORY_GR.md b/HISTORY_GR.md index 30f1674517..98a1c416ae 100644 --- a/HISTORY_GR.md +++ b/HISTORY_GR.md @@ -17,9 +17,13 @@ **Πώς να αναβαθμίσετε**: Ανοίξτε την γραμμή εντολών σας και εκτελέστε αυτήν την εντολή: `go get -u github.com/kataras/iris` ή αφήστε το αυτόματο updater να το κάνει αυτό για σας. +# Su, 21 October 2018 | v11.0.0 + +Πατήστε [εδώ](https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v11) για να διαβάσετε στα αγγλικά τις αλλαγές και τα νέα features που φέρνει νεότερη έκδοση του Iris, version 11. + # Sat, 11 August 2018 | v10.7.0 -Είμαι στην πραγματικά ευχάριστη θέση να σας ανακοινώσω το πρώτο στάδιο της σταθερής κυκλοφορίας της έκδοσης **10.7** του Iris Web Framework. +Είμαι στην, πραγματικά, ευχάριστη θέση να σας ανακοινώσω το πρώτο στάδιο της σταθερής κυκλοφορίας της έκδοσης **10.7** του Iris Web Framework. Η έκδοση 10.7.0 είναι μέρος των [επίσημων διανομών μας](https://github.com/kataras/iris/releases). diff --git a/HISTORY_ID.md b/HISTORY_ID.md index 001bdab1d0..36e3088578 100644 --- a/HISTORY_ID.md +++ b/HISTORY_ID.md @@ -17,6 +17,10 @@ Developers tidak diwajibkan untuk melakukan upgrade apabila mereka tidak membutu **Cara Upgrade**: Bukan command-line anda dan eksekuis perintah ini: `go get -u github.com/kataras/iris` atau biarkan updater otomatis melakukannya untuk anda. +# Su, 21 October 2018 | v11.0.0 + +This history entry is not translated yet to the Indonesian language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v11) instead. + # Sat, 11 August 2018 | v10.7.0 This history entry is not translated yet to the Indonesian language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#sat-11-august-2018--v1070) instead. @@ -83,7 +87,7 @@ For example: at [_examples/mvc/basic/main.go line 100](_examples/mvc/basic/main. - fix `APIBuilder, Party#StaticWeb` and `APIBuilder, Party#StaticEmbedded` wrong strip prefix inside children parties - keep the `iris, core/router#StaticEmbeddedHandler` and remove the `core/router/APIBuilder#StaticEmbeddedHandler`, (note the `Handler` suffix) it's global and has nothing to do with the `Party` or the `APIBuilder` -- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](core/router/macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) +- fix high path cleaning between `{}` (we already escape those contents at the [interpreter](macro/interpreter) level but some symbols are still removed by the higher-level api builder) , i.e `\\` from the string's macro function `regex` contents as reported at [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) - sync the `golang.org/x/sys/unix` vendor ## The most important diff --git a/HISTORY_ZH.md b/HISTORY_ZH.md index 674ede1a38..7ee04145f3 100644 --- a/HISTORY_ZH.md +++ b/HISTORY_ZH.md @@ -17,6 +17,10 @@ **如何升级**: 打开命令行执行以下命令: `go get -u github.com/kataras/iris` 或者等待自动更新。 +# Su, 21 October 2018 | v11.0.0 + +This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v11) instead. + # Sat, 11 August 2018 | v10.7.0 This history entry is not translated yet to the Chinese language yet, please refer to the english version of the [HISTORY entry](https://github.com/kataras/iris/blob/master/HISTORY.md#sat-11-august-2018--v1070) instead. @@ -79,7 +83,7 @@ This history entry is not translated yet to the Chinese language yet, please ref - 修正 `APIBuilder, Party#StaticWeb` 和 `APIBuilder, Party#StaticEmbedded` 子分组内的前缀错误 - 保留 `iris, core/router#StaticEmbeddedHandler` 并移除 `core/router/APIBuilder#StaticEmbeddedHandler`, (`Handler` 后缀) 这是全局性的,与 `Party` `APIBuilder` 无关。 -- 修正 路径 `{}` 中的路径清理 (我们已经在 [解释器](core/router/macro/interpreter) 级别转义了这些字符, 但是一些符号仍然被更高级别的API构建器删除) , 例如 `\\` 字符串的宏函数正则表达式内容 [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) +- 修正 路径 `{}` 中的路径清理 (我们已经在 [解释器](macro/interpreter) 级别转义了这些字符, 但是一些符号仍然被更高级别的API构建器删除) , 例如 `\\` 字符串的宏函数正则表达式内容 [927](https://github.com/kataras/iris/issues/927) by [commit e85b113476eeefffbc7823297cc63cd152ebddfd](https://github.com/kataras/iris/commit/e85b113476eeefffbc7823297cc63cd152ebddfd) - 同步 `golang.org/x/sys/unix` ## 重要变更 diff --git a/README.md b/README.md index c5b94a083a..e565563947 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,8 @@ -# ⚡️ The upcoming release is in-progress - -Please stay tuned by following its changelog before its official release. Click [here](https://github.com/kataras/iris/blob/v11/HISTORY.md#whenever--v1100) to read more about the upcoming release, version 11. - -> And, for the craziest of us, click [here](https://github.com/kataras/iris/compare/v10.7.0...v11) 🔥 to find out the commits and the code changes since current v10 - # Iris Web Framework -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/routing%20by-example-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris is a fast, simple yet fully featured and very efficient web framework for Go. @@ -119,10 +113,18 @@ func main() { | Param Type | Go Type | Validation | Retrieve Helper | | -----------------|------|-------------|------| -| `:string` | string | anything | `Params().Get` | -| `:int` | uint, uint8, uint16, uint32, uint64, int, int8, int32, int64 | positive number, no digits limit | `Params().GetInt/Int64`...| -| `:long` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | -| `:boolean` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | `Params().GetBool` | +| `:string` | string | anything (single path segment) | `Params().Get` | +| `:int` | int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | `Params().GetInt` | +| `:int8` | int8 | -128 to 127 | `Params().GetInt8` | +| `:int16` | int16 | -32768 to 32767 | `Params().GetInt16` | +| `:int32` | int32 | -2147483648 to 2147483647 | `Params().GetInt32` | +| `:int64` | int64 | -9223372036854775808 to 9223372036854775807 | `Params().GetInt64` | +| `:uint` | uint | 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32), depends on the host arch | `Params().GetUint` | +| `:uint8` | uint8 | 0 to 255 | `Params().GetUint8` | +| `:uint16` | uint16 | 0 to 65535 | `Params().GetUint16` | +| `:uint32` | uint32 | 0 to 4294967295 | `Params().GetUint32` | +| `:uint64` | uint64 | 0 to 18446744073709551615 | `Params().GetUint64` | +| `:bool` | bool | "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" | `Params().GetBool` | | `:alphabetical` | string | lowercase or uppercase letters | `Params().Get` | | `:file` | string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | `Params().Get` | | `:path` | string | anything, can be separated by slashes (path segments) but should be the last part of the route path | `Params().Get` | @@ -130,8 +132,8 @@ func main() { **Usage**: ```go -app.Get("/users/{id:int64}", func(ctx iris.Context){ - id, _ := ctx.Params().GetInt64("id") +app.Get("/users/{id:uint64}", func(ctx iris.Context){ + id := ctx.Params().GetUint64Default("id", 0) // [...] }) ``` @@ -142,9 +144,9 @@ app.Get("/users/{id:int64}", func(ctx iris.Context){ | `prefix`(prefix string) | :string | | `suffix`(suffix string) | :string | | `contains`(s string) | :string | -| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int64 | -| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int64 | -| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :int, :int64 | +| `min`(minValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `max`(maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :string(char length), :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | +| `range`(minValue, maxValue int or int8 or int16 or int32 or int64 or uint8 or uint16 or uint32 or uint64 or float32 or float64) | :int, :int8, :int16, :int32, :int64, :uint, :uint8, :uint16, :uint32, :uint64 | **Usage**: @@ -168,7 +170,7 @@ latLonRegex, _ := regexp.Compile(latLonExpr) // Register your custom argument-less macro function to the :string param type. // MatchString is a type of func(string) bool, so we use it as it is. -app.Macros().String.RegisterFunc("coordinate", latLonRegex.MatchString) +app.Macros().Get("string").RegisterFunc("coordinate", latLonRegex.MatchString) app.Get("/coordinates/{lat:string coordinate()}/{lon:string coordinate()}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) @@ -179,7 +181,7 @@ Register your custom macro function which accepts two int arguments. ```go -app.Macros().String.RegisterFunc("range", func(minLength, maxLength int) func(string) bool { +app.Macros().Get("string").RegisterFunc("range", func(minLength, maxLength int) func(string) bool { return func(paramValue string) bool { return len(paramValue) >= minLength && len(paramValue) <= maxLength } @@ -195,7 +197,7 @@ app.Get("/limitchar/{name:string range(1,200) else 400}", func(ctx iris.Context) Register your custom macro function which accepts a slice of strings `[...,...]`. ```go -app.Macros().String.RegisterFunc("has", func(validNames []string) func(string) bool { +app.Macros().Get("string").RegisterFunc("has", func(validNames []string) func(string) bool { return func(paramValue string) bool { for _, validName := range validNames { if validName == paramValue { @@ -218,32 +220,32 @@ app.Get("/static_validation/{name:string has([kataras,gerasimos,maropoulos]}", f ```go func main() { - app := iris.Default() + app := iris.Default() - // This handler will match /user/john but will not match neither /user/ or /user. - app.Get("/user/{name}", func(ctx iris.Context) { - name := ctx.Params().Get("name") - ctx.Writef("Hello %s", name) - }) - - // This handler will match /users/42 - // but will not match - // neither /users or /users/. - app.Get("/users/{id:long}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt64("id") - ctx.Writef("User with ID: %d", id) - }) - - // This handler will match /user/john/send - // but will not match /user/john/ - app.Post("/user/{name:string}/{action:path}", func(ctx iris.Context) { - name := ctx.Params().Get("name") - action := ctx.Params().Get("action") - message := name + " is " + action - ctx.WriteString(message) - }) + // This handler will match /user/john but will not match neither /user/ or /user. + app.Get("/user/{name}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef("Hello %s", name) + }) - app.Run(iris.Addr(":8080")) + // This handler will match /users/42 + // but will not match /users/-1 because uint should be bigger than zero + // neither /users or /users/. + app.Get("/users/{id:uint64}", func(ctx iris.Context) { + id := ctx.Params().GetUint64Default("id", 0) + ctx.Writef("User with ID: %d", id) + }) + + // However, this one will match /user/john/send and also /user/john/everything/else/here + // but will not match /user/john neither /user/john/. + app.Post("/user/{name:string}/{action:path}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + action := ctx.Params().Get("action") + message := name + " is " + action + ctx.WriteString(message) + }) + + app.Run(iris.Addr(":8080")) } ``` @@ -251,6 +253,7 @@ func main() { > Learn more about path parameter's types by navigating [here](_examples/routing/dynamic-path/main.go#L31). + ### Dependency Injection The package [hero](hero) contains features for binding any object or functions that `handlers` can use, these are called dependencies. @@ -592,7 +595,6 @@ func main() { app.Run( iris.Addr(":8080"), iris.WithoutBanner, - iris.WithoutVersionChecker, iris.WithoutServerError(iris.ErrServerClosed), ) } @@ -897,7 +899,7 @@ func main() { ### Testing -Iris offers an incredible support for the [httpexpect](https://github.com/iris-contrib/httpexpect), a Testing Framework for web applications. However, you are able to use the standard Go's `net/http/httptest` package as well but in this example we will use the `kataras/iris/httptest`. +Iris offers an incredible support for the [httpexpect](github.com/iris-contrib/httpexpect), a Testing Framework for web applications. However, you are able to use the standard Go's `net/http/httptest` package as well but in this example we will use the `kataras/iris/httptest`. ```go package main @@ -1004,7 +1006,7 @@ Iris, unlike others, is 100% compatible with the standards and that's why the ma ## Support -- [HISTORY](HISTORY.md#sat-11-august-2018--v1070) file is your best friend, it contains information about the latest features and changes +- [HISTORY](HISTORY.md#su-21-october-2018--v1100) file is your best friend, it contains information about the latest features and changes - Did you happen to find a bug? Post it at [github issues](https://github.com/kataras/iris/issues) - Do you have any questions or need to speak with someone experienced to solve a problem at real-time? Join us to the [community chat](https://chat.iris-go.com) - Complete our form-based user experience report by clicking [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_GR.md b/README_GR.md index 8fb8f43acd..7cc0640a67 100644 --- a/README_GR.md +++ b/README_GR.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Το Iris είναι ένα γρήγορο, απλό αλλά και πλήρως λειτουργικό και πολύ αποδοτικό web framework για τη Go. @@ -108,7 +108,7 @@ _Η τελευταία ενημέρωση έγινε την [Τρίτη, 21 Νο ## Υποστήριξη -- To [HISTORY](HISTORY_GR.md#sat-11-august-2018--v1070) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές +- To [HISTORY](HISTORY_GR.md#su-21-october-2018--v1100) αρχείο είναι ο καλύτερος σας φίλος, περιέχει πληροφορίες σχετικά με τις τελευταίες λειτουργίες(features) και αλλαγές - Μήπως τυχαίνει να βρήκατε κάποιο bug; Δημοσιεύστε το στα [github issues](https://github.com/kataras/iris/issues) - Έχετε οποιεσδήποτε ερωτήσεις ή πρέπει να μιλήσετε με κάποιον έμπειρο για την επίλυση ενός προβλήματος σε πραγματικό χρόνο; Ελάτε μαζί μας στην [συνομιλία κοινότητας](https://chat.iris-go.com) - Συμπληρώστε την αναφορά εμπειρίας χρήστη κάνοντας κλικ [εδώ](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_ID.md b/README_ID.md index 1839c15c94..1c0f442e97 100644 --- a/README_ID.md +++ b/README_ID.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris adalah web framework yang cepat, sederhana namun berfitur lengkap dan sangat efisien untuk Go. @@ -106,7 +106,7 @@ _Diperbarui pada: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_ ## Dukungan -- File [HISTORY](HISTORY_ID.md#sat-11-august-2018--v1070) adalah sahabat anda, file tersebut memiliki informasi terkait fitur dan perubahan terbaru +- File [HISTORY](HISTORY_ID.md#su-21-october-2018--v1100) adalah sahabat anda, file tersebut memiliki informasi terkait fitur dan perubahan terbaru - Apakah anda menemukan bug? Laporkan itu melalui [github issues](https://github.com/kataras/iris/issues) - Apakah anda memiliki pertanyaan atau butuh untuk bicara kepada seseorang yang sudah berpengalaman untuk menyelesaikan masalah secara langsung? Gabung bersama kami di [community chat](https://chat.iris-go.com) - Lengkapi laporan user-experience berbasis formulir kami dengan tekan [disini](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_JPN.md b/README_JPN.md index e2da517d16..ac0603be5f 100644 --- a/README_JPN.md +++ b/README_JPN.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Irisはシンプルで高速、それにも関わらず充実した機能を有する効率的なGo言語のウェブフレームワークです。 @@ -106,7 +106,7 @@ _Updated at: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_ ## 支援 -- [HISTORY](HISTORY.md#sat-11-august-2018--v1070)ファイルはあなたの友人です。このファイルには、機能に関する最新の情報や変更点が記載されています。 +- [HISTORY](HISTORY.md#su-21-october-2018--v1100)ファイルはあなたの友人です。このファイルには、機能に関する最新の情報や変更点が記載されています。 - バグを発見しましたか?[github issues](https://github.com/kataras/iris/issues)に投稿をお願い致します。 - 質問がありますか?または問題を即時に解決するため、熟練者に相談する必要がありますか?[community chat](https://chat.iris-go.com)に参加しましょう。 - [here](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link)をクリックしてユーザーとしての体験を報告しましょう。 diff --git a/README_PT_BR.md b/README_PT_BR.md index cdaaf03c1c..22f0c4f2cc 100644 --- a/README_PT_BR.md +++ b/README_PT_BR.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris é um framework rápido, simples porém completo e muito eficiente para a linguagem Go. @@ -106,7 +106,7 @@ _Atualizado em : [Terça, 21 de Novembro de 2017](_benchmarks/README_UNIX.md)_ ## Apoie -- [HISTORY](HISTORY.md#sat-11-august-2018--v1070) o arquivo HISTORY é o seu melhor amigo, ele contém informações sobre as últimas features e mudanças. +- [HISTORY](HISTORY.md#su-21-october-2018--v1100) o arquivo HISTORY é o seu melhor amigo, ele contém informações sobre as últimas features e mudanças. - Econtrou algum bug ? Poste-o nas [issues](https://github.com/kataras/iris/issues) - Possui alguma dúvida ou gostaria de falar com alguém experiente para resolver seu problema em tempo real ? Junte-se ao [chat da nossa comunidade](https://chat.iris-go.com). - Complete nosso formulário de experiência do usuário clicando [aqui](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_RU.md b/README_RU.md index 201e2d9824..99317d9dbb 100644 --- a/README_RU.md +++ b/README_RU.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris - это быстрая, простая, но полнофункциональная и очень эффективная веб-платформа для Go. @@ -106,7 +106,7 @@ _Обновлено: [Вторник, 21 ноября 2017 г.](_benchmarks/READ ## Поддержка -- Файл [HISTORY](HISTORY.md#sat-11-august-2018--v1070) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях +- Файл [HISTORY](HISTORY.md#su-21-october-2018--v1100) - ваш лучший друг, он содержит информацию о последних особенностях и всех изменениях - Вы случайно обнаружили ошибку? Опубликуйте ее на [Github вопросы](https://github.com/kataras/iris/issues) - У Вас есть какие-либо вопросы или Вам нужно поговорить с кем-то, кто бы смог решить Вашу проблему в режиме реального времени? Присоединяйтесь к нам в [чате сообщества](https://chat.iris-go.com) - Заполните наш отчет о пользовательском опыте на основе формы, нажав [здесь](https://docs.google.com/forms/d/e/1FAIpQLSdCxZXPANg_xHWil4kVAdhmh7EBBHQZ_4_xSZVDL-oCC_z5pA/viewform?usp=sf_link) diff --git a/README_ZH.md b/README_ZH.md index 95721dd592..e6873a34ea 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -2,7 +2,7 @@ -[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://iris-go.com/v10/recipe) [![release](https://img.shields.io/badge/release%20-v10.7-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) +[![build status](https://img.shields.io/travis/kataras/iris/master.svg?style=flat-square)](https://travis-ci.org/kataras/iris) [![report card](https://img.shields.io/badge/report%20card-a%2B-ff3333.svg?style=flat-square)](http://goreportcard.com/report/kataras/iris) [![vscode-iris](https://img.shields.io/badge/ext%20-vscode-0c77e3.svg?style=flat-square)](https://marketplace.visualstudio.com/items?itemName=kataras2006.iris) [![chat](https://img.shields.io/badge/community-%20chat-00BCD4.svg?style=flat-square)](https://kataras.rocket.chat/channel/iris) [![view examples](https://img.shields.io/badge/learn%20by-examples-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/tree/master/_examples/routing) [![release](https://img.shields.io/badge/release%20-v11.0-0077b3.svg?style=flat-square)](https://github.com/kataras/iris/releases) Iris 是一款超快、简洁高效的 Go 语言 Web开发框架。 diff --git a/VERSION b/VERSION index 729c3531e6..236610e098 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -10.7.0:https://github.com/kataras/iris/blob/master/HISTORY.md#sat-11-august-2018--v1070 \ No newline at end of file +11.0.0:https://github.com/kataras/iris/blob/master/HISTORY.md#su-21-october-2018--v1100 \ No newline at end of file diff --git a/_benchmarks/iris-mvc-templates/main.go b/_benchmarks/iris-mvc-templates/main.go index 5601ad146e..cfd46558fd 100644 --- a/_benchmarks/iris-mvc-templates/main.go +++ b/_benchmarks/iris-mvc-templates/main.go @@ -24,7 +24,7 @@ func main() { mvc.New(app).Handle(new(controllers.HomeController)) - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } type err struct { diff --git a/_benchmarks/iris-mvc/main.go b/_benchmarks/iris-mvc/main.go index 6ea755de06..4b0e0a1859 100644 --- a/_benchmarks/iris-mvc/main.go +++ b/_benchmarks/iris-mvc/main.go @@ -16,7 +16,7 @@ func main() { mvc.New(app.Party("/api/values/{id}")). Handle(new(controllers.ValuesController)) - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } // +2MB/s faster than the previous implementation, 0.4MB/s difference from the raw handlers. diff --git a/_benchmarks/iris-sessions/main.go b/_benchmarks/iris-sessions/main.go index 956cad0d3b..d5a340629b 100644 --- a/_benchmarks/iris-sessions/main.go +++ b/_benchmarks/iris-sessions/main.go @@ -24,9 +24,7 @@ func main() { app.Delete("/del", delHandler) */ - // 24 August 2017: Iris has a built'n version updater but we don't need it - // when benchmarking... - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } // Set and Get diff --git a/_benchmarks/iris/main.go b/_benchmarks/iris/main.go index 7c1ee6e29d..d9885e884f 100644 --- a/_benchmarks/iris/main.go +++ b/_benchmarks/iris/main.go @@ -8,5 +8,5 @@ func main() { ctx.WriteString("value") }) - app.Run(iris.Addr(":5000"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":5000")) } diff --git a/_examples/README.md b/_examples/README.md index 26530a1931..fd0f4f7ce5 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -146,8 +146,10 @@ Navigate through examples for a better understanding. - [Custom HTTP Errors](routing/http-errors/main.go) - [Dynamic Path](routing/dynamic-path/main.go) * [root level wildcard path](routing/dynamic-path/root-wildcard/main.go) +- [Write your own custom parameter types](routing/macros/main.go) **NEW** - [Reverse routing](routing/reverse/main.go) -- [Custom wrapper](routing/custom-wrapper/main.go) +- [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW** +- [Custom Wrapper](routing/custom-wrapper/main.go) - Custom Context * [method overriding](routing/custom-context/method-overriding/main.go) * [new implementation](routing/custom-context/new-implementation/main.go) diff --git a/_examples/README_ZH.md b/_examples/README_ZH.md index 3958a5d4a0..1fd80a8664 100644 --- a/_examples/README_ZH.md +++ b/_examples/README_ZH.md @@ -105,7 +105,9 @@ app.Get("{root:path}", rootWildcardHandler) - [自定义 HTTP 错误](routing/http-errors/main.go) - [动态路径](routing/dynamic-path/main.go) * [根级通配符路径](routing/dynamic-path/root-wildcard/main.go) +- [Write your own custom parameter types](routing/macros/main.go) **NEW** - [反向路由](routing/reverse/main.go) +- [Custom Router (high-level)](routing/custom-high-level-router/main.go) **NEW** - [自定义包装](routing/custom-wrapper/main.go) - 自定义上下文    * [方法重写](routing/custom-context/method-overriding/main.go) diff --git a/_examples/hero/overview/main.go b/_examples/hero/overview/main.go index ac7221072b..efb188847f 100644 --- a/_examples/hero/overview/main.go +++ b/_examples/hero/overview/main.go @@ -50,8 +50,6 @@ func main() { app.Run( // Start the web server at localhost:8080 iris.Addr("localhost:8080"), - // disables updates: - iris.WithoutVersionChecker, // skip err server closed when CTRL/CMD+C pressed: iris.WithoutServerError(iris.ErrServerClosed), // enables faster json serialization and more: diff --git a/_examples/hero/sessions/main.go b/_examples/hero/sessions/main.go index aa1f67b3eb..7603453253 100644 --- a/_examples/hero/sessions/main.go +++ b/_examples/hero/sessions/main.go @@ -34,7 +34,6 @@ func main() { app.Run( iris.Addr(":8080"), - iris.WithoutVersionChecker, iris.WithoutServerError(iris.ErrServerClosed), ) } diff --git a/_examples/http-listening/iris-configurator-and-host-configurator/README.md b/_examples/http-listening/iris-configurator-and-host-configurator/README.md deleted file mode 100644 index 89ecb89c15..0000000000 --- a/_examples/http-listening/iris-configurator-and-host-configurator/README.md +++ /dev/null @@ -1,2 +0,0 @@ -A silly example for this issue: https://github.com/kataras/iris/issues/688#issuecomment-318828259. -However it seems useful and therefore is being included in the examples for everyone else. \ No newline at end of file diff --git a/_examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go b/_examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go deleted file mode 100644 index 7298830347..0000000000 --- a/_examples/http-listening/iris-configurator-and-host-configurator/counter/configurator.go +++ /dev/null @@ -1,30 +0,0 @@ -package counter - -import ( - "time" - - "github.com/kataras/iris" - "github.com/kataras/iris/core/host" -) - -func Configurator(app *iris.Application) { - counterValue := 0 - - go func() { - ticker := time.NewTicker(time.Second) - - for range ticker.C { - counterValue++ - } - - app.ConfigureHost(func(h *host.Supervisor) { // <- HERE: IMPORTANT - h.RegisterOnShutdown(func() { - ticker.Stop() - }) - }) // or put the ticker outside of the gofunc and put the configurator before or after the app.Get, outside of this gofunc - }() - - app.Get("/counter", func(ctx iris.Context) { - ctx.Writef("Counter value = %d", counterValue) - }) -} diff --git a/_examples/http-listening/iris-configurator-and-host-configurator/main.go b/_examples/http-listening/iris-configurator-and-host-configurator/main.go index a82d974f9e..f1d7ad85b9 100644 --- a/_examples/http-listening/iris-configurator-and-host-configurator/main.go +++ b/_examples/http-listening/iris-configurator-and-host-configurator/main.go @@ -1,14 +1,28 @@ package main import ( - "github.com/kataras/iris/_examples/http-listening/iris-configurator-and-host-configurator/counter" - "github.com/kataras/iris" ) func main() { app := iris.New() - app.Configure(counter.Configurator) - app.Run(iris.Addr(":8080")) + app.ConfigureHost(func(host *iris.Supervisor) { // <- HERE: IMPORTANT + // You can control the flow or defer something using some of the host's methods: + // host.RegisterOnError + // host.RegisterOnServe + host.RegisterOnShutdown(func() { + app.Logger().Infof("Application shutdown on signal") + }) + }) + + app.Get("/", func(ctx iris.Context) { + ctx.HTML("

Hello

\n") + }) + + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) + + /* There are more easy ways to notify for global shutdown using the `iris.RegisterOnInterrupt` for default signal interrupt events. + You can even go it even further by looking at the: "graceful-shutdown" example. + */ } diff --git a/_examples/http-listening/notify-on-shutdown/main.go b/_examples/http-listening/notify-on-shutdown/main.go index 67400aa864..9a6e37091a 100644 --- a/_examples/http-listening/notify-on-shutdown/main.go +++ b/_examples/http-listening/notify-on-shutdown/main.go @@ -1,11 +1,10 @@ package main import ( - stdContext "context" + "context" "time" "github.com/kataras/iris" - "github.com/kataras/iris/core/host" ) func main() { @@ -15,20 +14,20 @@ func main() { ctx.HTML("

Hello, try to refresh the page after ~10 secs

") }) - // app.ConfigureHost(configureHost) -> or pass "configureHost" as `app.Addr` argument, same result. - app.Logger().Info("Wait 10 seconds and check your terminal again") // simulate a shutdown action here... go func() { <-time.After(10 * time.Second) timeout := 5 * time.Second - ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout) + ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // close all hosts, this will notify the callback we had register // inside the `configureHost` func. app.Shutdown(ctx) }() + // app.ConfigureHost(configureHost) -> or pass "configureHost" as `app.Addr` argument, same result. + // start the server as usual, the only difference is that // we're adding a second (optional) function // to configure the just-created host supervisor. @@ -37,9 +36,14 @@ func main() { // wait 10 seconds and check your terminal. app.Run(iris.Addr(":8080", configureHost), iris.WithoutServerError(iris.ErrServerClosed)) + /* + Or for simple cases you can just use the: + iris.RegisterOnInterrupt for global catch of the CTRL/CMD+C and OS events. + Look at the "graceful-shutdown" example for more. + */ } -func configureHost(su *host.Supervisor) { +func configureHost(su *iris.Supervisor) { // here we have full access to the host that will be created // inside the `app.Run` function or `NewHost`. // diff --git a/_examples/http_request/extract-referer/main.go b/_examples/http_request/extract-referer/main.go index 8c14e54ea1..3e2436c562 100644 --- a/_examples/http_request/extract-referer/main.go +++ b/_examples/http_request/extract-referer/main.go @@ -25,5 +25,5 @@ func main() { // http://localhost:8080?referer=https://twitter.com/Xinterio/status/1023566830974251008 // http://localhost:8080?referer=https://www.google.com/search?q=Top+6+golang+web+frameworks&oq=Top+6+golang+web+frameworks - app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) } diff --git a/_examples/mvc/login/main.go b/_examples/mvc/login/main.go index 83bd350fc7..3d77e08ab2 100644 --- a/_examples/mvc/login/main.go +++ b/_examples/mvc/login/main.go @@ -79,8 +79,6 @@ func main() { app.Run( // Starts the web server at localhost:8080 iris.Addr("localhost:8080"), - // Disables the updater. - iris.WithoutVersionChecker, // Ignores err server closed log when CTRL/CMD+C pressed. iris.WithoutServerError(iris.ErrServerClosed), // Enables faster json serialization and more. diff --git a/_examples/mvc/overview/main.go b/_examples/mvc/overview/main.go index 9d7d9cf6d7..2201332524 100644 --- a/_examples/mvc/overview/main.go +++ b/_examples/mvc/overview/main.go @@ -33,8 +33,6 @@ func main() { app.Run( // Start the web server at localhost:8080 iris.Addr("localhost:8080"), - // disables updates: - iris.WithoutVersionChecker, // skip err server closed when CTRL/CMD+C pressed: iris.WithoutServerError(iris.ErrServerClosed), // enables faster json serialization and more: diff --git a/_examples/mvc/session-controller/main.go b/_examples/mvc/session-controller/main.go index 8774d448aa..aa3ea0514a 100644 --- a/_examples/mvc/session-controller/main.go +++ b/_examples/mvc/session-controller/main.go @@ -68,5 +68,5 @@ func main() { // 3. refresh the page some times // 4. close the browser // 5. re-open the browser and re-play 2. - app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080")) } diff --git a/_examples/overview/main.go b/_examples/overview/main.go index 29e52560e3..d416f29ee3 100644 --- a/_examples/overview/main.go +++ b/_examples/overview/main.go @@ -74,7 +74,7 @@ func main() { } // Listen for incoming HTTP/1.x & HTTP/2 clients on localhost port 8080. - app.Run(iris.Addr(":8080"), iris.WithCharset("UTF-8"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080"), iris.WithCharset("UTF-8")) } func logThisMiddleware(ctx iris.Context) { diff --git a/_examples/routing/README.md b/_examples/routing/README.md index 62cde33e32..88c40db999 100644 --- a/_examples/routing/README.md +++ b/_examples/routing/README.md @@ -110,9 +110,9 @@ app := iris.New() users := app.Party("/users", myAuthMiddlewareHandler) // http://localhost:8080/users/42/profile -users.Get("/{id:int}/profile", userProfileHandler) +users.Get("/{id:uint64}/profile", userProfileHandler) // http://localhost:8080/users/messages/1 -users.Get("/inbox/{id:int}", userMessageHandler) +users.Get("/inbox/{id:uint64}", userMessageHandler) ``` The same could be also written using a function which accepts the child router(the Party). @@ -124,13 +124,13 @@ app.PartyFunc("/users", func(users iris.Party) { users.Use(myAuthMiddlewareHandler) // http://localhost:8080/users/42/profile - users.Get("/{id:int}/profile", userProfileHandler) + users.Get("/{id:uint64}/profile", userProfileHandler) // http://localhost:8080/users/messages/1 - users.Get("/inbox/{id:int}", userMessageHandler) + users.Get("/inbox/{id:uint64}", userMessageHandler) }) ``` -> `id:int` is a (typed) dynamic path parameter, learn more by scrolling down. +> `id:uint` is a (typed) dynamic path parameter, learn more by scrolling down. # Dynamic Path Parameters @@ -152,23 +152,71 @@ Standard macro types for route path parameters | {param:string} | +------------------------+ string type -anything +anything (single path segmnent) + ++-------------------------------+ +| {param:int} | ++-------------------------------+ +int type +-9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch +------------------------+ -| {param:int} | +| {param:int8} | +------------------------+ -int type -only numbers (0-9) +int8 type +-128 to 127 + ++------------------------+ +| {param:int16} | ++------------------------+ +int16 type +-32768 to 32767 + ++------------------------+ +| {param:int32} | ++------------------------+ +int32 type +-2147483648 to 2147483647 +------------------------+ -| {param:long} | +| {param:int64} | +------------------------+ int64 type -only numbers (0-9) +-9223372036854775808 to 9223372036854775807 + ++------------------------+ +| {param:uint} | ++------------------------+ +uint type +0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32) + ++------------------------+ +| {param:uint8} | ++------------------------+ +uint8 type +0 to 255 + ++------------------------+ +| {param:uint16} | ++------------------------+ +uint16 type +0 to 65535 +------------------------+ -| {param:boolean} | +| {param:uint32} | +------------------------+ +uint32 type +0 to 4294967295 + ++------------------------+ +| {param:uint64} | ++------------------------+ +uint64 type +0 to 18446744073709551615 + ++---------------------------------+ +| {param:bool} or {param:boolean} | ++---------------------------------+ bool type only "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" @@ -194,8 +242,8 @@ no spaces ! or other character | {param:path} | +------------------------+ path type -anything, should be the last part, more than one path segment, -i.e: /path1/path2/path3 , ctx.Params().Get("param") == "/path1/path2/path3" +anything, should be the last part, can be more than one path segment, +i.e: "/test/*param" and request: "/test/path1/path2/path3" , ctx.Params().Get("param") == "path1/path2/path3" ``` If type is missing then parameter's type is defaulted to string, so @@ -209,18 +257,24 @@ you are able to register your own too!. Register a named path parameter function ```go -app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) bool { +app.Macros().Get("int").RegisterFunc("min", func(argument int) func(paramValue int) bool { // [...] - return true - // -> true means valid, false means invalid fire 404 or if "else 500" is appended to the macro syntax then internal server error. + return func(paramValue int) bool { + // -> true means valid, false means invalid fire 404 + // or if "else 500" is appended to the macro syntax then internal server error. + return true + } }) ``` -At the `func(argument ...)` you can have any standard type, it will be validated before the server starts so don't care about any performance cost there, the only thing it runs at serve time is the returning `func(paramValue string) bool`. +At the `func(argument ...)` you can have any standard type, it will be validated before the server starts so don't care about any performance cost there, the only thing it runs at serve time is the returning `func(paramValue ) bool`. ```go -{param:string equal(iris)} , "iris" will be the argument here: -app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool { +{param:string equal(iris)} +``` +The "iris" will be the argument here: +```go +app.Macros().Get("string").RegisterFunc("equal", func(argument string) func(paramValue string) bool { return func(paramValue string){ return argument == paramValue } }) ``` @@ -237,38 +291,34 @@ app.Get("/username/{name}", func(ctx iris.Context) { // Let's register our first macro attached to int macro type. // "min" = the function // "minValue" = the argument of the function -// func(string) bool = the macro's path parameter evaluator, this executes in serve time when +// func() bool = the macro's path parameter evaluator, this executes in serve time when // a user requests a path which contains the :int macro type with the min(...) macro parameter function. -app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool { +app.Macros().Get("int").RegisterFunc("min", func(minValue int) func(int) bool { // do anything before serve here [...] // at this case we don't need to do anything - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= minValue + return func(paramValue int) bool { + return paramValue >= minValue } }) // http://localhost:8080/profile/id>=1 // this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1 // macro parameter functions are optional of course. -app.Get("/profile/{id:int min(1)}", func(ctx iris.Context) { +app.Get("/profile/{id:uint64 min(1)}", func(ctx iris.Context) { // second parameter is the error but it will always nil because we use macros, // the validaton already happened. - id, _ := ctx.Params().GetInt("id") + id, _ := ctx.Params().GetUint64("id") ctx.Writef("Hello id: %d", id) }) // to change the error code per route's macro evaluator: -app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") - friendid, _ := ctx.Params().GetInt("friendid") +app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") + friendid, _ := ctx.Params().GetUint64("friendid") ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. -// http://localhost:8080/game/a-zA-Z/level/0-9 +// http://localhost:8080/game/a-zA-Z/level/42 // remember, alphabetical is lowercase or uppercase letters only. app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) { ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level")) @@ -297,12 +347,10 @@ app.Run(iris.Addr(":8080")) } ``` -A **path parameter name should contain only alphabetical letters. Symbols like '_' and numbers are NOT allowed**. - +A path parameter name should contain only alphabetical letters or digits. Symbols like '_' are NOT allowed. Last, do not confuse `ctx.Params()` with `ctx.Values()`. -Path parameter's values goes to `ctx.Params()` and context's local storage -that can be used to communicate between handlers and middleware(s) goes to -`ctx.Values()`. +Path parameter's values can be retrieved from `ctx.Params()`, +context's local storage that can be used to communicate between handlers and middleware(s) can be stored to `ctx.Values()`. # Routing and reverse lookups @@ -776,6 +824,32 @@ type Context interface { // IsStopped checks and returns true if the current position of the Context is 255, // means that the StopExecution() was called. IsStopped() bool + // OnConnectionClose registers the "cb" function which will fire (on its own goroutine, no need to be registered goroutine by the end-dev) + // when the underlying connection has gone away. + // + // This mechanism can be used to cancel long operations on the server + // if the client has disconnected before the response is ready. + // + // It depends on the `http#CloseNotify`. + // CloseNotify may wait to notify until Request.Body has been + // fully read. + // + // After the main Handler has returned, there is no guarantee + // that the channel receives a value. + // + // Finally, it reports whether the protocol supports pipelines (HTTP/1.1 with pipelines disabled is not supported). + // The "cb" will not fire for sure if the output value is false. + // + // Note that you can register only one callback for the entire request handler chain/per route. + // + // Look the `ResponseWriter#CloseNotifier` for more. + OnConnectionClose(fnGoroutine func()) bool + // OnClose registers the callback function "cb" to the underline connection closing event using the `Context#OnConnectionClose` + // and also in the end of the request handler using the `ResponseWriter#SetBeforeFlush`. + // Note that you can register only one callback for the entire request handler chain/per route. + // + // Look the `Context#OnConnectionClose` and `ResponseWriter#SetBeforeFlush` for more. + OnClose(cb func()) // +------------------------------------------------------------+ // | Current "user/request" storage | @@ -855,8 +929,12 @@ type Context interface { // // Keep note that this checks the "User-Agent" request header. IsMobile() bool + // GetReferrer extracts and returns the information from the "Referer" header as specified + // in https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + // or by the URL query parameter "referer". + GetReferrer() Referrer // +------------------------------------------------------------+ - // | Response Headers helpers | + // | Headers helpers | // +------------------------------------------------------------+ // Header adds a header to the response writer. @@ -867,16 +945,18 @@ type Context interface { // GetContentType returns the response writer's header value of "Content-Type" // which may, setted before with the 'ContentType'. GetContentType() string + // GetContentType returns the request's header value of "Content-Type". + GetContentTypeRequested() string // GetContentLength returns the request's header value of "Content-Length". // Returns 0 if header was unable to be found or its value was not a valid number. GetContentLength() int64 // StatusCode sets the status code header to the response. - // Look .GetStatusCode too. + // Look .`GetStatusCode` too. StatusCode(statusCode int) // GetStatusCode returns the current status code of the response. - // Look StatusCode too. + // Look `StatusCode` too. GetStatusCode() int // Redirect sends a redirect response to the client @@ -911,6 +991,9 @@ type Context interface { // URLParamIntDefault returns the url query parameter as int value from a request, // if not found or parse failed then "def" is returned. URLParamIntDefault(name string, def int) int + // URLParamInt32Default returns the url query parameter as int32 value from a request, + // if not found or parse failed then "def" is returned. + URLParamInt32Default(name string, def int32) int32 // URLParamInt64 returns the url query parameter as int64 value from a request, // returns -1 and an error if parse failed. URLParamInt64(name string) (int64, error) @@ -1058,6 +1141,10 @@ type Context interface { // Examples of usage: context.ReadJSON, context.ReadXML. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-custom-via-unmarshaler/main.go + // + // UnmarshalBody does not check about gzipped data. + // Do not rely on compressed data incoming to your server. The main reason is: https://en.wikipedia.org/wiki/Zip_bomb + // However you are still free to read the `ctx.Request().Body io.Reader` manually. UnmarshalBody(outPtr interface{}, unmarshaler Unmarshaler) error // ReadJSON reads JSON from request's body and binds it to a pointer of a value of any json-valid type. // @@ -1068,7 +1155,8 @@ type Context interface { // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go ReadXML(xmlObjectPtr interface{}) error // ReadForm binds the formObject with the form data - // it supports any kind of struct. + // it supports any kind of type, including custom structs. + // It will return nothing if request data are empty. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go ReadForm(formObjectPtr interface{}) error diff --git a/_examples/routing/basic/main.go b/_examples/routing/basic/main.go index d694b20cff..d03ea46c0d 100644 --- a/_examples/routing/basic/main.go +++ b/_examples/routing/basic/main.go @@ -29,12 +29,12 @@ func main() { app.Get("/donate", donateHandler, donateFinishHandler) // Pssst, don't forget dynamic-path example for more "magic"! - app.Get("/api/users/{userid:int min(1)}", func(ctx iris.Context) { - userID, err := ctx.Params().GetInt("userid") + app.Get("/api/users/{userid:uint64 min(1)}", func(ctx iris.Context) { + userID, err := ctx.Params().GetUint64("userid") if err != nil { ctx.Writef("error while trying to parse userid parameter," + - "this will never happen if :int is being used because if it's not integer it will fire Not Found automatically.") + "this will never happen if :uint64 is being used because if it's not a valid uint64 it will fire Not Found automatically.") ctx.StatusCode(iris.StatusBadRequest) return } diff --git a/_examples/routing/custom-high-level-router/main.go b/_examples/routing/custom-high-level-router/main.go new file mode 100644 index 0000000000..77830024c5 --- /dev/null +++ b/_examples/routing/custom-high-level-router/main.go @@ -0,0 +1,103 @@ +package main + +import ( + "strings" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + "github.com/kataras/iris/core/router" +) + +/* A Router should contain all three of the following methods: + - HandleRequest should handle the request based on the Context. + HandleRequest(ctx context.Context) + - Build should builds the handler, it's being called on router's BuildRouter. + Build(provider router.RoutesProvider) error + - RouteExists reports whether a particular route exists. + RouteExists(ctx context.Context, method, path string) bool + +For a more detailed, complete and useful example +you can take a look at the iris' router itself which is located at: +https://github.com/kataras/iris/tree/master/core/router/handler.go +which completes this exact interface, the `router#RequestHandler`. +*/ +type customRouter struct { + // a copy of routes (safer because you will not be able to alter a route on serve-time without a `app.RefreshRouter` call): + // []router.Route + // or just expect the whole routes provider: + provider router.RoutesProvider +} + +// HandleRequest a silly example which finds routes based only on the first part of the requested path +// which must be a static one as well, the rest goes to fill the parameters. +func (r *customRouter) HandleRequest(ctx context.Context) { + path := ctx.Path() + ctx.Application().Logger().Infof("Requested resource path: %s", path) + + parts := strings.Split(path, "/")[1:] + staticPath := "/" + parts[0] + for _, route := range r.provider.GetRoutes() { + if strings.HasPrefix(route.Path, staticPath) && route.Method == ctx.Method() { + paramParts := parts[1:] + for _, paramValue := range paramParts { + for _, p := range route.Tmpl().Params { + ctx.Params().Set(p.Name, paramValue) + } + } + + ctx.SetCurrentRouteName(route.Name) + ctx.Do(route.Handlers) + return + } + } + + // if nothing found... + ctx.StatusCode(iris.StatusNotFound) +} + +func (r *customRouter) Build(provider router.RoutesProvider) error { + for _, route := range provider.GetRoutes() { + // do any necessary validation or conversations based on your custom logic here + // but always run the "BuildHandlers" for each registered route. + route.BuildHandlers() + // [...] r.routes = append(r.routes, *route) + } + + r.provider = provider + return nil +} + +func (r *customRouter) RouteExists(ctx context.Context, method, path string) bool { + // [...] + return false +} + +func main() { + app := iris.New() + + // In case you are wondering, the parameter types and macros like "{param:string $func()}" still work inside + // your custom router if you fetch by the Route's Handler + // because they are middlewares under the hood, so you don't have to implement the logic of handling them manually, + // though you have to match what requested path is what route and fill the ctx.Params(), this is the work of your custom router. + app.Get("/hello/{name}", func(ctx context.Context) { + name := ctx.Params().Get("name") + ctx.Writef("Hello %s\n", name) + }) + + app.Get("/cs/{num:uint64 min(10) else 400}", func(ctx context.Context) { + num := ctx.Params().GetUint64Default("num", 0) + ctx.Writef("num is: %d\n", num) + }) + + // To replace the existing router with a customized one by using the iris/context.Context + // you have to use the `app.BuildRouter` method before `app.Run` and after the routes registered. + // You should pass your custom router's instance as the second input arg, which must completes the `router#RequestHandler` + // interface as shown above. + // + // To see how you can build something even more low-level without direct iris' context support (you can do that manually as well) + // navigate to the "custom-wrapper" example instead. + myCustomRouter := new(customRouter) + app.BuildRouter(app.ContextPool, myCustomRouter, app.APIBuilder, true) + + app.Run(iris.Addr(":8080"), iris.WithoutServerError(iris.ErrServerClosed)) +} diff --git a/_examples/routing/dynamic-path/main.go b/_examples/routing/dynamic-path/main.go index 5a3f0b5751..d94b0c1840 100644 --- a/_examples/routing/dynamic-path/main.go +++ b/_examples/routing/dynamic-path/main.go @@ -2,7 +2,6 @@ package main import ( "regexp" - "strconv" "github.com/kataras/iris" ) @@ -14,15 +13,14 @@ func main() { // we've seen static routes, group of routes, subdomains, wildcard subdomains, a small example of parameterized path // with a single known paramete and custom http errors, now it's time to see wildcard parameters and macros. - // iris, like net/http std package registers route's handlers + // Iris, like net/http std package registers route's handlers // by a Handler, the iris' type of handler is just a func(ctx iris.Context) // where context comes from github.com/kataras/iris/context. - // Until go 1.9 you will have to import that package too, after go 1.9 this will be not be necessary. // - // iris has the easiest and the most powerful routing process you have ever meet. + // Iris has the easiest and the most powerful routing process you have ever meet. // // At the same time, - // iris has its own interpeter(yes like a programming language) + // Iris has its own interpeter(yes like a programming language) // for route's path syntax and their dynamic path parameters parsing and evaluation, // We call them "macros" for shortcut. // How? It calculates its needs and if not any special regexp needed then it just @@ -36,21 +34,34 @@ func main() { // string type // anything // - // +------------------------+ - // | {param:int} | - // +------------------------+ + // +-------------------------------+ + // | {param:int} or {param:int} | + // +-------------------------------+ // int type - // only numbers (0-9) + // both positive and negative numbers, any number of digits (ctx.Params().GetInt will limit the digits based on the host arch) + // + // +-------------------------------+ + // | {param:int64} or {param:long} | + // +-------------------------------+ + // int64 type + // -9223372036854775808 to 9223372036854775807 // // +------------------------+ - // | {param:long} | + // | {param:uint8} | // +------------------------+ - // int64 type - // only numbers (0-9) + // uint8 type + // 0 to 255 + // // // +------------------------+ - // | {param:boolean} | + // | {param:uint64} | // +------------------------+ + // uint64 type + // 0 to 18446744073709551615 + // + // +---------------------------------+ + // | {param:bool} or {param:boolean} | + // +---------------------------------+ // bool type // only "1" or "t" or "T" or "TRUE" or "true" or "True" // or "0" or "f" or "F" or "FALSE" or "false" or "False" @@ -89,7 +100,7 @@ func main() { // you are able to register your own too!. // // Register a named path parameter function: - // app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) bool { + // app.Macros().Number.RegisterFunc("min", func(argument int) func(paramValue string) bool { // [...] // return true/false -> true means valid. // }) @@ -107,51 +118,52 @@ func main() { ctx.Writef("Hello %s", ctx.Params().Get("name")) }) // type is missing = {name:string} - // Let's register our first macro attached to int macro type. + // Let's register our first macro attached to uint64 macro type. // "min" = the function // "minValue" = the argument of the function - // func(string) bool = the macro's path parameter evaluator, this executes in serve time when - // a user requests a path which contains the :int macro type with the min(...) macro parameter function. - app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool { - // do anything before serve here [...] - // at this case we don't need to do anything - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= minValue + // func(uint64) bool = our func's evaluator, this executes in serve time when + // a user requests a path which contains the :uint64 macro parameter type with the min(...) macro parameter function. + app.Macros().Get("uint64").RegisterFunc("min", func(minValue uint64) func(uint64) bool { + // type of "paramValue" should match the type of the internal macro's evaluator function, which in this case is "uint64". + return func(paramValue uint64) bool { + return paramValue >= minValue } }) - // http://localhost:8080/profile/id>=1 + // http://localhost:8080/profile/id>=20 // this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1 // macro parameter functions are optional of course. - app.Get("/profile/{id:int min(1)}", func(ctx iris.Context) { + app.Get("/profile/{id:uint64 min(20)}", func(ctx iris.Context) { // second parameter is the error but it will always nil because we use macros, // the validaton already happened. - id, _ := ctx.Params().GetInt("id") + id := ctx.Params().GetUint64Default("id", 0) ctx.Writef("Hello id: %d", id) }) // to change the error code per route's macro evaluator: - app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") - friendid, _ := ctx.Params().GetInt("friendid") + app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { + id := ctx.Params().GetUint64Default("id", 0) + friendid := ctx.Params().GetUint64Default("friendid", 0) ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. - // Another example using a custom regexp and any custom logic. + // :uint8 0 to 255. + app.Get("/ages/{age:uint8 else 400}", func(ctx iris.Context) { + age, _ := ctx.Params().GetUint8("age") + ctx.Writef("age selected: %d", age) + }) + + // Another example using a custom regexp or any custom logic. + + // Register your custom argument-less macro function to the :string param type. latLonExpr := "^-?[0-9]{1,3}(?:\\.[0-9]{1,10})?$" latLonRegex, err := regexp.Compile(latLonExpr) if err != nil { panic(err) } - app.Macros().String.RegisterFunc("coordinate", func() func(paramName string) (ok bool) { - // MatchString is a type of func(string) bool, so we can return that as it's. - return latLonRegex.MatchString - }) + // MatchString is a type of func(string) bool, so we use it as it is. + app.Macros().Get("string").RegisterFunc("coordinate", latLonRegex.MatchString) app.Get("/coordinates/{lat:string coordinate() else 502}/{lon:string coordinate() else 502}", func(ctx iris.Context) { ctx.Writef("Lat: %s | Lon: %s", ctx.Params().Get("lat"), ctx.Params().Get("lon")) @@ -159,7 +171,43 @@ func main() { // - // http://localhost:8080/game/a-zA-Z/level/0-9 + // Another one is by using a custom body. + app.Macros().Get("string").RegisterFunc("range", func(minLength, maxLength int) func(string) bool { + return func(paramValue string) bool { + return len(paramValue) >= minLength && len(paramValue) <= maxLength + } + }) + + app.Get("/limitchar/{name:string range(1,200)}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef(`Hello %s | the name should be between 1 and 200 characters length + otherwise this handler will not be executed`, name) + }) + + // + + // Register your custom macro function which accepts a slice of strings `[...,...]`. + app.Macros().Get("string").RegisterFunc("has", func(validNames []string) func(string) bool { + return func(paramValue string) bool { + for _, validName := range validNames { + if validName == paramValue { + return true + } + } + + return false + } + }) + + app.Get("/static_validation/{name:string has([kataras,gerasimos,maropoulos]}", func(ctx iris.Context) { + name := ctx.Params().Get("name") + ctx.Writef(`Hello %s | the name should be "kataras" or "gerasimos" or "maropoulos" + otherwise this handler will not be executed`, name) + }) + + // + + // http://localhost:8080/game/a-zA-Z/level/42 // remember, alphabetical is lowercase or uppercase letters only. app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) { ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level")) @@ -197,10 +245,9 @@ func main() { // if "/mypath/{myparam:path}" then the parameter has two names, one is the "*" and the other is the user-defined "myparam". // WARNING: - // A path parameter name should contain only alphabetical letters. Symbols like '_' and numbers are NOT allowed. + // A path parameter name should contain only alphabetical letters or digits. Symbols like '_' are NOT allowed. // Last, do not confuse `ctx.Params()` with `ctx.Values()`. - // Path parameter's values goes to `ctx.Params()` and context's local storage - // that can be used to communicate between handlers and middleware(s) goes to - // `ctx.Values()`. + // Path parameter's values can be retrieved from `ctx.Params()`, + // context's local storage that can be used to communicate between handlers and middleware(s) can be stored to `ctx.Values()`. app.Run(iris.Addr(":8080")) } diff --git a/_examples/routing/macros/main.go b/_examples/routing/macros/main.go new file mode 100644 index 0000000000..3de4e29a80 --- /dev/null +++ b/_examples/routing/macros/main.go @@ -0,0 +1,76 @@ +// Package main shows how you can register a custom parameter type and macro functions that belongs to it. +package main + +import ( + "fmt" + "reflect" + "sort" + "strings" + + "github.com/kataras/iris" + "github.com/kataras/iris/context" + "github.com/kataras/iris/hero" +) + +func main() { + app := iris.New() + app.Logger().SetLevel("debug") + + app.Macros().Register("slice", "", false, true, func(paramValue string) (interface{}, bool) { + return strings.Split(paramValue, "/"), true + }).RegisterFunc("contains", func(expectedItems []string) func(paramValue []string) bool { + sort.Strings(expectedItems) + return func(paramValue []string) bool { + if len(paramValue) != len(expectedItems) { + return false + } + + sort.Strings(paramValue) + for i := 0; i < len(paramValue); i++ { + if paramValue[i] != expectedItems[i] { + return false + } + } + + return true + } + }) + + // In order to use your new param type inside MVC controller's function input argument or a hero function input argument + // you have to tell the Iris what type it is, the `ValueRaw` of the parameter is the same type + // as you defined it above with the func(paramValue string) (interface{}, bool). + // The new value and its type(from string to your new custom type) it is stored only once now, + // you don't have to do any conversions for simple cases like this. + context.ParamResolvers[reflect.TypeOf([]string{})] = func(paramIndex int) interface{} { + return func(ctx context.Context) []string { + // When you want to retrieve a parameter with a value type that it is not supported by-default, such as ctx.Params().GetInt + // then you can use the `GetEntry` or `GetEntryAt` and cast its underline `ValueRaw` to the desired type. + // The type should be the same as the macro's evaluator function (last argument on the Macros#Register) return value. + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.([]string) + } + } + + /* + http://localhost:8080/test_slice_hero/myvaluei1/myavlue2 -> + myparam's value (a trailing path parameter type) is: []string{"myvaluei1", "myavlue2"} + */ + app.Get("/test_slice_hero/{myparam:slice}", hero.Handler(func(myparam []string) string { + return fmt.Sprintf("myparam's value (a trailing path parameter type) is: %#v\n", myparam) + })) + + /* + http://localhost:8080/test_slice_contains/notcontains1/value2 -> + (404) Not Found + + http://localhost:8080/test_slice_contains/value1/value2 -> + myparam's value (a trailing path parameter type) is: []string{"value1", "value2"} + */ + app.Get("/test_slice_contains/{myparam:slice contains([value1,value2])}", func(ctx context.Context) { + // When it is not a built'n function available to retrieve your value with the type you want, such as ctx.Params().GetInt + // then you can use the `GetEntry.ValueRaw` to get the real value, which is set-ed by your macro above. + myparam := ctx.Params().GetEntry("myparam").ValueRaw.([]string) + ctx.Writef("myparam's value (a trailing path parameter type) is: %#v\n", myparam) + }) + + app.Run(iris.Addr(":8080")) +} diff --git a/_examples/routing/main.go b/_examples/routing/main.go index d9af5f3d34..9f4503274e 100644 --- a/_examples/routing/main.go +++ b/_examples/routing/main.go @@ -35,25 +35,25 @@ func registerGamesRoutes(app *iris.Application) { { // braces are optional of course, it's just a style of code // "GET" method - games.Get("/{gameID:int}/clans", h) - games.Get("/{gameID:int}/clans/clan/{clanPublicID:int}", h) - games.Get("/{gameID:int}/clans/search", h) + games.Get("/{gameID:uint64}/clans", h) + games.Get("/{gameID:uint64}/clans/clan/{clanPublicID:uint64}", h) + games.Get("/{gameID:uint64}/clans/search", h) // "PUT" method - games.Put("/{gameID:int}/players/{clanPublicID:int}", h) - games.Put("/{gameID:int}/clans/clan/{clanPublicID:int}", h) + games.Put("/{gameID:uint64}/players/{clanPublicID:uint64}", h) + games.Put("/{gameID:uint64}/clans/clan/{clanPublicID:uint64}", h) // remember: "clanPublicID" should not be changed to other routes with the same prefix. // "POST" method - games.Post("/{gameID:int}/clans", h) - games.Post("/{gameID:int}/players", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/leave", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/application", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/application/{action}", h) // {action} == {action:string} - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/invitation", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/invitation/{action}", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/delete", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/promote", h) - games.Post("/{gameID:int}/clans/{clanPublicID:int}/memberships/demote", h) + games.Post("/{gameID:uint64}/clans", h) + games.Post("/{gameID:uint64}/players", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/leave", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/application", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/application/{action}", h) // {action} == {action:string} + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/invitation", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/invitation/{action}", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/delete", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/promote", h) + games.Post("/{gameID:uint64}/clans/{clanPublicID:uint64}/memberships/demote", h) gamesCh := games.Party("/challenge") { diff --git a/_examples/routing/overview/main.go b/_examples/routing/overview/main.go index 65c15dc6d7..d2ff717843 100644 --- a/_examples/routing/overview/main.go +++ b/_examples/routing/overview/main.go @@ -68,8 +68,8 @@ func main() { // GET: http://localhost:8080/users/42 // **/users/42 and /users/help works after iris version 7.0.5** - usersRoutes.Get("/{id:int}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") + usersRoutes.Get("/{id:uint64}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") ctx.Writef("get user by id: %d", id) }) @@ -80,15 +80,15 @@ func main() { }) // PUT: http://localhost:8080/users - usersRoutes.Put("/{id:int}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") // or .Get to get its string represatantion. + usersRoutes.Put("/{id:uint64}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") // or .Get to get its string represatantion. username := ctx.PostValue("username") ctx.Writef("update user for id= %d and new username= %s", id, username) }) // DELETE: http://localhost:8080/users/42 - usersRoutes.Delete("/{id:int}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") + usersRoutes.Delete("/{id:uint64}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") ctx.Writef("delete user by id: %d", id) }) diff --git a/_examples/subdomains/www/main.go b/_examples/subdomains/www/main.go index c41e343d52..9ecfc96920 100644 --- a/_examples/subdomains/www/main.go +++ b/_examples/subdomains/www/main.go @@ -13,11 +13,11 @@ func newApp() *iris.Application { app.PartyFunc("/api/users", func(r iris.Party) { r.Get("/", info) - r.Get("/{id:int}", info) + r.Get("/{id:uint64}", info) r.Post("/", info) - r.Put("/{id:int}", info) + r.Put("/{id:uint64}", info) }) /* <- same as: usersAPI := app.Party("/api/users") { // those brackets are just syntactic-sugar things. diff --git a/_examples/tutorial/url-shortener/main.go b/_examples/tutorial/url-shortener/main.go index 1516627c68..7fa9112316 100644 --- a/_examples/tutorial/url-shortener/main.go +++ b/_examples/tutorial/url-shortener/main.go @@ -2,7 +2,7 @@ // // Article: https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7 // -// $ go get github.com/coreos/bbolt +// $ go get github.com/etcd-io/bbolt // $ go get github.com/satori/go.uuid // $ cd $GOPATH/src/github.com/kataras/iris/_examples/tutorial/url-shortener // $ go build diff --git a/_examples/tutorial/url-shortener/store.go b/_examples/tutorial/url-shortener/store.go index 32238b0961..f2b78891f9 100644 --- a/_examples/tutorial/url-shortener/store.go +++ b/_examples/tutorial/url-shortener/store.go @@ -3,7 +3,7 @@ package main import ( "bytes" - "github.com/coreos/bbolt" + bolt "github.com/etcd-io/bbolt" ) // Panic panics, change it if you don't want to panic on critical INITIALIZE-ONLY-ERRORS diff --git a/_examples/tutorial/vuejs-todo-mvc/README.md b/_examples/tutorial/vuejs-todo-mvc/README.md index 00c912fa50..b856373bcc 100644 --- a/_examples/tutorial/vuejs-todo-mvc/README.md +++ b/_examples/tutorial/vuejs-todo-mvc/README.md @@ -27,7 +27,7 @@ Many articles have been written, in the past, that lead developers not to use a You’ll need two dependencies: 1. Vue.js, for our client-side requirements. Download it from [here](https://vuejs.org/), latest v2. -2. The Iris Web Framework, for our server-side requirements. Can be found [here](https://github.com/kataras/iris), latest v10. +2. The Iris Web Framework, for our server-side requirements. Can be found [here](https://github.com/kataras/iris), latest v11. > If you have Go already installed then just execute `go get -u github.com/kataras/iris` to install the Iris Web Framework. @@ -530,7 +530,7 @@ func main() { todosApp.Handle(new(controllers.TodoController)) // start the web server at http://localhost:8080 - app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080")) } ``` diff --git a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go index c571e42630..34e5ed888d 100644 --- a/_examples/tutorial/vuejs-todo-mvc/src/web/main.go +++ b/_examples/tutorial/vuejs-todo-mvc/src/web/main.go @@ -51,5 +51,5 @@ func main() { todosApp.Handle(new(controllers.TodoController)) // start the web server at http://localhost:8080 - app.Run(iris.Addr(":8080"), iris.WithoutVersionChecker) + app.Run(iris.Addr(":8080")) } diff --git a/_examples/webassembly/basic/client/hello_go11beta3.go b/_examples/webassembly/basic/client/hello_go111.go similarity index 70% rename from _examples/webassembly/basic/client/hello_go11beta3.go rename to _examples/webassembly/basic/client/hello_go111.go index 9cbf5060d1..97dda1f577 100644 --- a/_examples/webassembly/basic/client/hello_go11beta3.go +++ b/_examples/webassembly/basic/client/hello_go111.go @@ -1,4 +1,4 @@ -// +build go1.11beta3 +// +build js package main @@ -9,7 +9,7 @@ import ( ) func main() { - // GOARCH=wasm GOOS=js /home/$yourusername/go1.11beta1/bin/go build -o hello.wasm hello_go11beta2.go + // GOARCH=wasm GOOS=js /home/$yourusername/go1.11/bin/go build -o hello.wasm hello_go111.go js.Global().Get("console").Call("log", "Hello WebAssemply!") message := fmt.Sprintf("Hello, the current time is: %s", time.Now().String()) js.Global().Get("document").Call("getElementById", "hello").Set("innerText", message) diff --git a/_examples/webassembly/basic/main.go b/_examples/webassembly/basic/main.go index 7dfe0ad282..4099da53a3 100644 --- a/_examples/webassembly/basic/main.go +++ b/_examples/webassembly/basic/main.go @@ -6,7 +6,7 @@ import ( /* You need to build the hello.wasm first, download the go1.11 and execute the below command: -$ cd client && GOARCH=wasm GOOS=js /home/$yourname/go1.11beta3/bin/go build -o hello.wasm hello_go11beta3.go +$ cd client && GOARCH=wasm GOOS=js /home/$yourname/go1.11/bin/go build -o hello.wasm hello_go111.go */ func main() { diff --git a/configuration.go b/configuration.go index 7bbc013939..6ee8071f20 100644 --- a/configuration.go +++ b/configuration.go @@ -222,14 +222,6 @@ var WithoutInterruptHandler = func(app *Application) { app.config.DisableInterruptHandler = true } -// WithoutVersionChecker will disable the version checker and updater. -// The Iris server will be not -// receive automatic updates if you pass this -// to the `Run` function. Use it only while you're ready for Production environment. -var WithoutVersionChecker = func(app *Application) { - app.config.DisableVersionChecker = true -} - // WithoutPathCorrection disables the PathCorrection setting. // // See `Configuration`. @@ -388,11 +380,6 @@ type Configuration struct { // Defaults to false. DisableInterruptHandler bool `json:"disableInterruptHandler,omitempty" yaml:"DisableInterruptHandler" toml:"DisableInterruptHandler"` - // DisableVersionChecker if true then process will be not be notified for any available updates. - // - // Defaults to false. - DisableVersionChecker bool `json:"disableVersionChecker,omitempty" yaml:"DisableVersionChecker" toml:"DisableVersionChecker"` - // DisablePathCorrection corrects and redirects the requested path to the registered path // for example, if /home/ path is requested but no handler for this Route found, // then the Router checks if /home handler exists, if yes, @@ -677,10 +664,6 @@ func WithConfiguration(c Configuration) Configurator { main.DisableInterruptHandler = v } - if v := c.DisableVersionChecker; v { - main.DisableVersionChecker = v - } - if v := c.DisablePathCorrection; v { main.DisablePathCorrection = v } @@ -758,7 +741,6 @@ func DefaultConfiguration() Configuration { return Configuration{ DisableStartupLog: false, DisableInterruptHandler: false, - DisableVersionChecker: false, DisablePathCorrection: false, EnablePathEscape: false, FireMethodNotAllowed: false, diff --git a/configuration_test.go b/configuration_test.go index ab770fbe08..913ebe849d 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -77,13 +77,12 @@ func TestConfigurationOptions(t *testing.T) { func TestConfigurationOptionsDeep(t *testing.T) { charset := "MYCHARSET" - app := New().Configure(WithCharset(charset), WithoutBodyConsumptionOnUnmarshal, WithoutBanner, WithoutVersionChecker) + app := New().Configure(WithCharset(charset), WithoutBodyConsumptionOnUnmarshal, WithoutBanner) expected := DefaultConfiguration() expected.Charset = charset expected.DisableBodyConsumptionOnUnmarshal = true expected.DisableStartupLog = true - expected.DisableVersionChecker = true has := *app.config @@ -142,7 +141,6 @@ func TestConfigurationYAML(t *testing.T) { }() yamlConfigurationContents := ` -DisableVersionChecker: true DisablePathCorrection: false EnablePathEscape: false FireMethodNotAllowed: true @@ -165,10 +163,6 @@ Other: c := app.config - if expected := true; c.DisableVersionChecker != expected { - t.Fatalf("error on TestConfigurationYAML: Expected DisableVersionChecker %v but got %v", expected, c.DisableVersionChecker) - } - if expected := false; c.DisablePathCorrection != expected { t.Fatalf("error on TestConfigurationYAML: Expected DisablePathCorrection %v but got %v", expected, c.DisablePathCorrection) } @@ -241,7 +235,6 @@ func TestConfigurationTOML(t *testing.T) { }() tomlConfigurationContents := ` -DisableVersionChecker = true EnablePathEscape = false FireMethodNotAllowed = true EnableOptimizations = true @@ -265,10 +258,6 @@ Charset = "UTF-8" c := app.config - if expected := true; c.DisableVersionChecker != expected { - t.Fatalf("error on TestConfigurationTOML: Expected DisableVersionChecker %v but got %v", expected, c.DisableVersionChecker) - } - if expected := false; c.DisablePathCorrection != expected { t.Fatalf("error on TestConfigurationTOML: Expected DisablePathCorrection %v but got %v", expected, c.DisablePathCorrection) } diff --git a/context/context.go b/context/context.go index 8f2d40c679..7ea7822921 100644 --- a/context/context.go +++ b/context/context.go @@ -76,131 +76,6 @@ func (u UnmarshalerFunc) Unmarshal(data []byte, v interface{}) error { return u(data, v) } -// RequestParams is a key string - value string storage which -// context's request dynamic path params are being kept. -// Empty if the route is static. -type RequestParams struct { - store memstore.Store -} - -// Set adds a key-value pair to the path parameters values -// it's being called internally so it shouldn't be used as a local storage by the user, use `ctx.Values()` instead. -func (r *RequestParams) Set(key, value string) { - r.store.Set(key, value) -} - -// Visit accepts a visitor which will be filled -// by the key-value params. -func (r *RequestParams) Visit(visitor func(key string, value string)) { - r.store.Visit(func(k string, v interface{}) { - visitor(k, v.(string)) // always string here. - }) -} - -var emptyEntry memstore.Entry - -// GetEntryAt returns the internal Entry of the memstore based on its index, -// the stored index by the router. -// If not found then it returns a zero Entry and false. -func (r RequestParams) GetEntryAt(index int) (memstore.Entry, bool) { - if len(r.store) > index { - return r.store[index], true - } - return emptyEntry, false -} - -// GetEntry returns the internal Entry of the memstore based on its "key". -// If not found then it returns a zero Entry and false. -func (r RequestParams) GetEntry(key string) (memstore.Entry, bool) { - // we don't return the pointer here, we don't want to give the end-developer - // the strength to change the entry that way. - if e := r.store.GetEntry(key); e != nil { - return *e, true - } - return emptyEntry, false -} - -// Get returns a path parameter's value based on its route's dynamic path key. -func (r RequestParams) Get(key string) string { - return r.store.GetString(key) -} - -// GetTrim returns a path parameter's value without trailing spaces based on its route's dynamic path key. -func (r RequestParams) GetTrim(key string) string { - return strings.TrimSpace(r.Get(key)) -} - -// GetEscape returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. -func (r RequestParams) GetEscape(key string) string { - return DecodeQuery(DecodeQuery(r.Get(key))) -} - -// GetDecoded returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. -// same as `GetEscape`. -func (r RequestParams) GetDecoded(key string) string { - return r.GetEscape(key) -} - -// GetInt returns the path parameter's value as int, based on its key. -// It checks for all available types of int, including int64, strings etc. -// It will return -1 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetInt(key string) (int, error) { - return r.store.GetInt(key) -} - -// GetInt64 returns the path paramete's value as int64, based on its key. -// It checks for all available types of int, including int, strings etc. -// It will return -1 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetInt64(key string) (int64, error) { - return r.store.GetInt64(key) -} - -// GetFloat64 returns a path parameter's value based as float64 on its route's dynamic path key. -// It checks for all available types of int, including float64, int, strings etc. -// It will return -1 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetFloat64(key string) (float64, error) { - return r.store.GetFloat64(key) -} - -// GetUint64 returns the path paramete's value as uint64, based on its key. -// It checks for all available types of int, including int, uint64, int64, strings etc. -// It will return 0 and a non-nil error if parameter wasn't found. -func (r RequestParams) GetUint64(key string) (uint64, error) { - return r.store.GetUint64(key) -} - -// GetBool returns the path parameter's value as bool, based on its key. -// a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" -// or "0" or "f" or "F" or "FALSE" or "false" or "False". -// Any other value returns an error. -func (r RequestParams) GetBool(key string) (bool, error) { - return r.store.GetBool(key) -} - -// GetIntUnslashed same as Get but it removes the first slash if found. -// Usage: Get an id from a wildcard path. -// -// Returns -1 with an error if the parameter couldn't be found. -func (r RequestParams) GetIntUnslashed(key string) (int, error) { - v := r.Get(key) - if v != "" { - if len(v) > 1 { - if v[0] == '/' { - v = v[1:] - } - } - return strconv.Atoi(v) - - } - - return -1, fmt.Errorf("unable to find int for '%s'", key) -} - -// Len returns the full length of the parameters. -func (r RequestParams) Len() int { - return r.store.Len() -} - // Context is the midle-man server's "object" for the clients. // // A New context is being acquired from a sync.Pool on each connection. @@ -685,7 +560,8 @@ type Context interface { // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-xml/main.go ReadXML(xmlObjectPtr interface{}) error // ReadForm binds the formObject with the form data - // it supports any kind of struct. + // it supports any kind of type, including custom structs. + // It will return nothing if request data are empty. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go ReadForm(formObjectPtr interface{}) error @@ -1116,7 +992,7 @@ func NewContext(app Application) Context { func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) { ctx.handlers = nil // will be filled by router.Serve/HTTP ctx.values = ctx.values[0:0] // >> >> by context.Values().Set - ctx.params.store = ctx.params.store[0:0] + ctx.params.Store = ctx.params.Store[0:0] ctx.request = r ctx.currentHandlerIndex = 0 ctx.writer = AcquireResponseWriter() @@ -2414,17 +2290,15 @@ var ( errReadBody = errors.New("while trying to read %s from the request body. Trace %s") ) -// ErrEmptyForm will be thrown from `context#ReadForm` when empty request data. -var ErrEmptyForm = errors.New("form data: empty") - // ReadForm binds the formObject with the form data -// it supports any kind of struct. +// it supports any kind of type, including custom structs. +// It will return nothing if request data are empty. // // Example: https://github.com/kataras/iris/blob/master/_examples/http_request/read-form/main.go func (ctx *context) ReadForm(formObject interface{}) error { values := ctx.FormValues() - if values == nil { - return ErrEmptyForm + if len(values) == 0 { + return nil } // or dec := formbinder.NewDecoder(&formbinder.DecoderOptions{TagName: "form"}) @@ -2990,12 +2864,10 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { options = opts[0] } - optimize := ctx.shouldOptimize() - ctx.ContentType(ContentJSONHeaderValue) if options.StreamingJSON { - if optimize { + if ctx.shouldOptimize() { var jsoniterConfig = jsoniter.Config{ EscapeHTML: !options.UnescapeHTML, IndentionStep: 4, @@ -3016,7 +2888,7 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) { return ctx.writer.Written(), err } - n, err = WriteJSON(ctx.writer, v, options, optimize) + n, err = WriteJSON(ctx.writer, v, options, ctx.shouldOptimize()) if err != nil { ctx.StatusCode(http.StatusInternalServerError) return 0, err diff --git a/context/request_params.go b/context/request_params.go new file mode 100644 index 0000000000..7757308b97 --- /dev/null +++ b/context/request_params.go @@ -0,0 +1,206 @@ +package context + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/kataras/iris/core/memstore" +) + +// RequestParams is a key string - value string storage which +// context's request dynamic path params are being kept. +// Empty if the route is static. +type RequestParams struct { + memstore.Store +} + +// Set inserts a value to the key-value storage. +// +// See `SetImmutable` and `Get` too. +func (r *RequestParams) Set(key, value string) { + r.Store.Set(key, value) +} + +// GetEntryAt will return the parameter's internal store's `Entry` based on the index. +// If not found it will return an emptry `Entry`. +func (r *RequestParams) GetEntryAt(index int) memstore.Entry { + entry, _ := r.Store.GetEntryAt(index) + return entry +} + +// GetEntry will return the parameter's internal store's `Entry` based on its name/key. +// If not found it will return an emptry `Entry`. +func (r *RequestParams) GetEntry(key string) memstore.Entry { + entry, _ := r.Store.GetEntry(key) + return entry +} + +// Visit accepts a visitor which will be filled +// by the key-value params. +func (r *RequestParams) Visit(visitor func(key string, value string)) { + r.Store.Visit(func(k string, v interface{}) { + visitor(k, fmt.Sprintf("%v", v)) // always string here. + }) +} + +// Get returns a path parameter's value based on its route's dynamic path key. +func (r RequestParams) Get(key string) string { + return r.GetString(key) +} + +// GetTrim returns a path parameter's value without trailing spaces based on its route's dynamic path key. +func (r RequestParams) GetTrim(key string) string { + return strings.TrimSpace(r.Get(key)) +} + +// GetEscape returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. +func (r RequestParams) GetEscape(key string) string { + return DecodeQuery(DecodeQuery(r.Get(key))) +} + +// GetDecoded returns a path parameter's double-url-query-escaped value based on its route's dynamic path key. +// same as `GetEscape`. +func (r RequestParams) GetDecoded(key string) string { + return r.GetEscape(key) +} + +// GetIntUnslashed same as Get but it removes the first slash if found. +// Usage: Get an id from a wildcard path. +// +// Returns -1 and false if not path parameter with that "key" found. +func (r RequestParams) GetIntUnslashed(key string) (int, bool) { + v := r.Get(key) + if v != "" { + if len(v) > 1 { + if v[0] == '/' { + v = v[1:] + } + } + + vInt, err := strconv.Atoi(v) + if err != nil { + return -1, false + } + return vInt, true + } + + return -1, false +} + +var ( + // ParamResolvers is the global param resolution for a parameter type for a specific go std or custom type. + // + // Key is the specific type, which should be unique. + // The value is a function which accepts the parameter index + // and it should return the value as the parameter type evaluator expects it. + // i.e [reflect.TypeOf("string")] = func(paramIndex int) interface{} { + // return func(ctx Context) { + // return ctx.Params().GetEntryAt(paramIndex).ValueRaw.() + // } + // } + // + // Read https://github.com/kataras/iris/tree/master/_examples/routing/macros for more details. + ParamResolvers = map[reflect.Type]func(paramIndex int) interface{}{ + reflect.TypeOf(""): func(paramIndex int) interface{} { + return func(ctx Context) string { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(string) + } + }, + reflect.TypeOf(int(1)): func(paramIndex int) interface{} { + return func(ctx Context) int { + // v, _ := ctx.Params().GetEntryAt(paramIndex).IntDefault(0) + // return v + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int) + } + }, + reflect.TypeOf(int8(1)): func(paramIndex int) interface{} { + return func(ctx Context) int8 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int8) + } + }, + reflect.TypeOf(int16(1)): func(paramIndex int) interface{} { + return func(ctx Context) int16 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int16) + } + }, + reflect.TypeOf(int32(1)): func(paramIndex int) interface{} { + return func(ctx Context) int32 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int32) + } + }, + reflect.TypeOf(int64(1)): func(paramIndex int) interface{} { + return func(ctx Context) int64 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(int64) + } + }, + reflect.TypeOf(uint(1)): func(paramIndex int) interface{} { + return func(ctx Context) uint { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint) + } + }, + reflect.TypeOf(uint8(1)): func(paramIndex int) interface{} { + return func(ctx Context) uint8 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint8) + } + }, + reflect.TypeOf(uint16(1)): func(paramIndex int) interface{} { + return func(ctx Context) uint16 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint16) + } + }, + reflect.TypeOf(uint32(1)): func(paramIndex int) interface{} { + return func(ctx Context) uint32 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint32) + } + }, + reflect.TypeOf(uint64(1)): func(paramIndex int) interface{} { + return func(ctx Context) uint64 { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(uint64) + } + }, + reflect.TypeOf(true): func(paramIndex int) interface{} { + return func(ctx Context) bool { + return ctx.Params().GetEntryAt(paramIndex).ValueRaw.(bool) + } + }, + } +) + +// ParamResolverByTypeAndIndex will return a function that can be used to bind path parameter's exact value by its Go std type +// and the parameter's index based on the registered path. +// Usage: nameResolver := ParamResolverByKindAndKey(reflect.TypeOf(""), 0) +// Inside a Handler: nameResolver.Call(ctx)[0] +// it will return the reflect.Value Of the exact type of the parameter(based on the path parameters and macros). +// It is only useful for dynamic binding of the parameter, it is used on "hero" package and it should be modified +// only when Macros are modified in such way that the default selections for the available go std types are not enough. +// +// Returns empty value and false if "k" does not match any valid parameter resolver. +func ParamResolverByTypeAndIndex(typ reflect.Type, paramIndex int) (reflect.Value, bool) { + /* NO: + // This could work but its result is not exact type, so direct binding is not possible. + resolver := m.ParamResolver + fn := func(ctx context.Context) interface{} { + entry, _ := ctx.Params().GetEntry(paramName) + return resolver(entry) + } + // + + // This works but it is slower on serve-time. + paramNameValue := []reflect.Value{reflect.ValueOf(paramName)} + var fnSignature func(context.Context) string + return reflect.MakeFunc(reflect.ValueOf(&fnSignature).Elem().Type(), func(in []reflect.Value) []reflect.Value { + return in[0].MethodByName("Params").Call(emptyIn)[0].MethodByName("Get").Call(paramNameValue) + // return []reflect.Value{reflect.ValueOf(in[0].Interface().(context.Context).Params().Get(paramName))} + }) + // + */ + + r, ok := ParamResolvers[typ] + if !ok || r == nil { + return reflect.Value{}, false + } + + return reflect.ValueOf(r(paramIndex)), true +} diff --git a/context/route.go b/context/route.go index e632f11f0f..7c680e7d95 100644 --- a/context/route.go +++ b/context/route.go @@ -1,6 +1,6 @@ package context -import "github.com/kataras/iris/core/router/macro" +import "github.com/kataras/iris/macro" // RouteReadOnly allows decoupled access to the current route // inside the context. @@ -25,7 +25,7 @@ type RouteReadOnly interface { // StaticPath returns the static part of the original, registered route path. // if /user/{id} it will return /user - // if /user/{id}/friend/{friendid:int} it will return /user too + // if /user/{id}/friend/{friendid:uint64} it will return /user too // if /assets/{filepath:path} it will return /assets. StaticPath() string diff --git a/core/host/proxy_test.go b/core/host/proxy_test.go index b7ad878e07..76561b342f 100644 --- a/core/host/proxy_test.go +++ b/core/host/proxy_test.go @@ -60,7 +60,7 @@ func TestProxy(t *testing.T) { t.Fatalf("%v while creating tcp4 listener for new tls local test listener", err) } // main server - go app.Run(iris.Listener(httptest.NewLocalTLSListener(l)), iris.WithoutVersionChecker, iris.WithoutStartupLog) + go app.Run(iris.Listener(httptest.NewLocalTLSListener(l)), iris.WithoutStartupLog) e := httptest.NewInsecure(t, httptest.URL("http://"+listener.Addr().String())) e.GET("/").Expect().Status(iris.StatusOK).Body().Equal(expectedIndex) diff --git a/core/maintenance/maintenance.go b/core/maintenance/maintenance.go deleted file mode 100644 index 613b67fda6..0000000000 --- a/core/maintenance/maintenance.go +++ /dev/null @@ -1,6 +0,0 @@ -package maintenance - -// Start starts the maintenance process. -func Start() { - CheckForUpdates() -} diff --git a/core/maintenance/version.go b/core/maintenance/version.go deleted file mode 100644 index f3d3113f68..0000000000 --- a/core/maintenance/version.go +++ /dev/null @@ -1,78 +0,0 @@ -package maintenance - -import ( - "fmt" - "os" - "os/exec" - - "github.com/kataras/iris/core/maintenance/version" - - "github.com/kataras/golog" - "github.com/kataras/survey" -) - -const ( - // Version is the string representation of the current local Iris Web Framework version. - Version = "10.7.0" -) - -// CheckForUpdates checks for any available updates -// and asks for the user if want to update now or not. -func CheckForUpdates() { - v := version.Acquire() - updateAvailale := v.Compare(Version) == version.Smaller - - if updateAvailale { - if confirmUpdate(v) { - installVersion() - return - } - } -} - -func confirmUpdate(v version.Version) bool { - // on help? when asking for installing the new update. - ignoreUpdatesMsg := "Would you like to ignore future updates? Disable the version checker via:\napp.Run(..., iris.WithoutVersionChecker)" - - // if update available ask for update action. - shouldUpdateNowMsg := - fmt.Sprintf("A new version is available online[%s > %s]. Type '?' for help.\nRelease notes: %s.\nUpdate now?", - v.String(), Version, v.ChangelogURL) - - var confirmUpdate bool - survey.AskOne(&survey.Confirm{ - Message: shouldUpdateNowMsg, - Help: ignoreUpdatesMsg, - }, &confirmUpdate, nil) - return confirmUpdate // it's true only when update was available and user typed "yes". -} - -func installVersion() { - golog.Infof("Downloading...\n") - repo := "github.com/kataras/iris/..." - cmd := exec.Command("go", "get", "-u", "-v", repo) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stdout - - if err := cmd.Run(); err != nil { - golog.Warnf("unexpected message while trying to go get,\nif you edited the original source code then you've to remove the whole $GOPATH/src/github.com/kataras folder and execute `go get -u github.com/kataras/iris/...` manually\n%v", err) - return - } - - golog.Infof("Update process finished.\nManual rebuild and restart is required to apply the changes...\n") - return -} - -/* Author's note: -We could use github webhooks to automatic notify for updates -when a new update is pushed to the repository -even when server is already started and running but this would expose -a route which dev may don't know about, so let it for now but if -they ask it then I should add an optional configuration field -to "live/realtime update" and implement the idea (which is already implemented in the iris-go server). -*/ - -/* Author's note: -The old remote endpoint for version checker is still available on the server for backwards -compatibility with older clients, it will stay there for a long period of time. -*/ diff --git a/core/maintenance/version/fetch.go b/core/maintenance/version/fetch.go deleted file mode 100644 index 82d3cd75d0..0000000000 --- a/core/maintenance/version/fetch.go +++ /dev/null @@ -1,59 +0,0 @@ -package version - -import ( - "io/ioutil" - "strings" - "time" - - "github.com/hashicorp/go-version" - - "github.com/kataras/golog" - "github.com/kataras/iris/core/netutil" -) - -const ( - versionURL = "https://raw.githubusercontent.com/kataras/iris/master/VERSION" -) - -func fetch() (*version.Version, string) { - client := netutil.Client(time.Duration(30 * time.Second)) - - r, err := client.Get(versionURL) - if err != nil { - golog.Debugf("err: %v\n", err) - return nil, "" - } - defer r.Body.Close() - - if r.StatusCode >= 400 { - golog.Debugf("Internet connection is missing, updater is unable to fetch the latest Iris version\n%v", err) - return nil, "" - } - - b, err := ioutil.ReadAll(r.Body) - - if len(b) == 0 || err != nil { - golog.Debugf("err: %v\n", err) - return nil, "" - } - - var ( - fetchedVersion = string(b) - changelogURL string - ) - // Example output: - // Version(8.5.5) - // 8.5.5:https://github.com/kataras/iris/blob/master/HISTORY.md#tu-02-november-2017--v855 - if idx := strings.IndexByte(fetchedVersion, ':'); idx > 0 { - changelogURL = fetchedVersion[idx+1:] - fetchedVersion = fetchedVersion[0:idx] - } - - latestVersion, err := version.NewVersion(fetchedVersion) - if err != nil { - golog.Debugf("while fetching and parsing latest version from github: %v\n", err) - return nil, "" - } - - return latestVersion, changelogURL -} diff --git a/core/maintenance/version/version.go b/core/maintenance/version/version.go deleted file mode 100644 index 4a33895212..0000000000 --- a/core/maintenance/version/version.go +++ /dev/null @@ -1,64 +0,0 @@ -package version - -import ( - "time" - - "github.com/hashicorp/go-version" -) - -// Version is a version wrapper which -// contains some additional customized properties. -type Version struct { - version.Version - WrittenAt time.Time - ChangelogURL string -} - -// Result is the compare result type. -// Available types are Invalid, Smaller, Equal or Larger. -type Result int32 - -const ( - // Smaller when the compared version is smaller than the latest one. - Smaller Result = -1 - // Equal when the compared version is equal with the latest one. - Equal Result = 0 - // Larger when the compared version is larger than the latest one. - Larger Result = 1 - // Invalid means that an error occurred when comparing the versions. - Invalid Result = -2 -) - -// Compare compares the "versionStr" with the latest Iris version, -// opossite to the version package -// it returns the result of the "versionStr" not the "v" itself. -func (v *Version) Compare(versionStr string) Result { - if len(v.Version.String()) == 0 { - // if version not refreshed, by an internet connection lose, - // then return Invalid. - return Invalid - } - - other, err := version.NewVersion(versionStr) - if err != nil { - return Invalid - } - return Result(other.Compare(&v.Version)) -} - -// Acquire returns the latest version info wrapper. -// It calls the fetch. -func Acquire() (v Version) { - newVersion, changelogURL := fetch() - if newVersion == nil { // if github was down then don't panic, just set it as the smallest version. - newVersion, _ = version.NewVersion("0.0.1") - } - - v = Version{ - Version: *newVersion, - WrittenAt: time.Now(), - ChangelogURL: changelogURL, - } - - return -} diff --git a/core/memstore/memstore.go b/core/memstore/memstore.go index 5b6bbdf2e3..001762e0e8 100644 --- a/core/memstore/memstore.go +++ b/core/memstore/memstore.go @@ -6,6 +6,8 @@ package memstore import ( + "fmt" + "math" "reflect" "strconv" "strings" @@ -14,6 +16,11 @@ import ( ) type ( + // ValueSetter is the interface which can be accepted as a generic solution of RequestParams or memstore when Set is the only requirement, + // i.e internally on macro/template/TemplateParam#Eval:paramChanger. + ValueSetter interface { + Set(key string, newValue interface{}) (Entry, bool) + } // Entry is the entry of the context storage Store - .Values() Entry struct { Key string @@ -25,6 +32,8 @@ type ( Store []Entry ) +var _ ValueSetter = (*Store)(nil) + // GetByKindOrNil will try to get this entry's value of "k" kind, // if value is not that kind it will NOT try to convert it the "k", instead // it will return nil, except if boolean; then it will return false @@ -67,11 +76,19 @@ func (e Entry) GetByKindOrNil(k reflect.Kind) interface{} { // If not found returns "def". func (e Entry) StringDefault(def string) string { v := e.ValueRaw + if v == nil { + return def + } if vString, ok := v.(string); ok { return vString } + val := fmt.Sprintf("%v", v) + if val != "" { + return val + } + return def } @@ -94,107 +111,339 @@ func (e Entry) IntDefault(def int) (int, error) { if v == nil { return def, errFindParse.Format("int", e.Key) } - if vint, ok := v.(int); ok { - return vint, nil - } else if vstring, sok := v.(string); sok && vstring != "" { - vint, err := strconv.Atoi(vstring) + + switch vv := v.(type) { + case string: + val, err := strconv.Atoi(vv) if err != nil { return def, err } - - return vint, nil + return val, nil + case int: + return vv, nil + case int8: + return int(vv), nil + case int16: + return int(vv), nil + case int32: + return int(vv), nil + case int64: + return int(vv), nil + case uint: + return int(vv), nil + case uint8: + return int(vv), nil + case uint16: + return int(vv), nil + case uint32: + return int(vv), nil + case uint64: + return int(vv), nil } return def, errFindParse.Format("int", e.Key) } -// Int64Default returns the entry's value as int64. +// Int8Default returns the entry's value as int8. // If not found returns "def" and a non-nil error. -func (e Entry) Int64Default(def int64) (int64, error) { +func (e Entry) Int8Default(def int8) (int8, error) { v := e.ValueRaw if v == nil { - return def, errFindParse.Format("int64", e.Key) + return def, errFindParse.Format("int8", e.Key) } - if vint64, ok := v.(int64); ok { - return vint64, nil + switch vv := v.(type) { + case string: + val, err := strconv.ParseInt(vv, 10, 8) + if err != nil { + return def, err + } + return int8(val), nil + case int: + return int8(vv), nil + case int8: + return vv, nil + case int16: + return int8(vv), nil + case int32: + return int8(vv), nil + case int64: + return int8(vv), nil } - if vint, ok := v.(int); ok { - return int64(vint), nil + return def, errFindParse.Format("int8", e.Key) +} + +// Int16Default returns the entry's value as int16. +// If not found returns "def" and a non-nil error. +func (e Entry) Int16Default(def int16) (int16, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("int16", e.Key) } - if vstring, sok := v.(string); sok { - return strconv.ParseInt(vstring, 10, 64) + switch vv := v.(type) { + case string: + val, err := strconv.ParseInt(vv, 10, 16) + if err != nil { + return def, err + } + return int16(val), nil + case int: + return int16(vv), nil + case int8: + return int16(vv), nil + case int16: + return vv, nil + case int32: + return int16(vv), nil + case int64: + return int16(vv), nil } - return def, errFindParse.Format("int64", e.Key) + return def, errFindParse.Format("int16", e.Key) } -// Float64Default returns the entry's value as float64. +// Int32Default returns the entry's value as int32. // If not found returns "def" and a non-nil error. -func (e Entry) Float64Default(def float64) (float64, error) { +func (e Entry) Int32Default(def int32) (int32, error) { v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("int32", e.Key) + } + + switch vv := v.(type) { + case string: + val, err := strconv.ParseInt(vv, 10, 32) + if err != nil { + return def, err + } + return int32(val), nil + case int: + return int32(vv), nil + case int8: + return int32(vv), nil + case int16: + return int32(vv), nil + case int32: + return vv, nil + case int64: + return int32(vv), nil + } + + return def, errFindParse.Format("int32", e.Key) +} +// Int64Default returns the entry's value as int64. +// If not found returns "def" and a non-nil error. +func (e Entry) Int64Default(def int64) (int64, error) { + v := e.ValueRaw if v == nil { - return def, errFindParse.Format("float64", e.Key) + return def, errFindParse.Format("int64", e.Key) } - if vfloat32, ok := v.(float32); ok { - return float64(vfloat32), nil + switch vv := v.(type) { + case string: + return strconv.ParseInt(vv, 10, 64) + case int64: + return vv, nil + case int32: + return int64(vv), nil + case int8: + return int64(vv), nil + case int: + return int64(vv), nil } - if vfloat64, ok := v.(float64); ok { - return vfloat64, nil + return def, errFindParse.Format("int64", e.Key) +} + +// UintDefault returns the entry's value as uint. +// If not found returns "def" and a non-nil error. +func (e Entry) UintDefault(def uint) (uint, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint", e.Key) } - if vint, ok := v.(int); ok { - return float64(vint), nil + x64 := strconv.IntSize == 64 + var maxValue uint = math.MaxUint32 + if x64 { + maxValue = math.MaxUint64 } - if vstring, sok := v.(string); sok { - vfloat64, err := strconv.ParseFloat(vstring, 64) + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, strconv.IntSize) if err != nil { return def, err } - - return vfloat64, nil + if val > uint64(maxValue) { + return def, errFindParse.Format("uint", e.Key) + } + return uint(val), nil + case uint: + return vv, nil + case uint8: + return uint(vv), nil + case uint16: + return uint(vv), nil + case uint32: + return uint(vv), nil + case uint64: + if vv > uint64(maxValue) { + return def, errFindParse.Format("uint", e.Key) + } + return uint(vv), nil + case int: + if vv < 0 || vv > int(maxValue) { + return def, errFindParse.Format("uint", e.Key) + } + return uint(vv), nil } - return def, errFindParse.Format("float64", e.Key) + return def, errFindParse.Format("uint", e.Key) } -// Float32Default returns the entry's value as float32. +// Uint8Default returns the entry's value as uint8. // If not found returns "def" and a non-nil error. -func (e Entry) Float32Default(key string, def float32) (float32, error) { +func (e Entry) Uint8Default(def uint8) (uint8, error) { v := e.ValueRaw - if v == nil { - return def, errFindParse.Format("float32", e.Key) + return def, errFindParse.Format("uint8", e.Key) } - if vfloat32, ok := v.(float32); ok { - return vfloat32, nil + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 8) + if err != nil { + return def, err + } + if val > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(val), nil + case uint: + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case uint8: + return vv, nil + case uint16: + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case uint32: + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case uint64: + if vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil + case int: + if vv < 0 || vv > math.MaxUint8 { + return def, errFindParse.Format("uint8", e.Key) + } + return uint8(vv), nil } - if vfloat64, ok := v.(float64); ok { - return float32(vfloat64), nil - } + return def, errFindParse.Format("uint8", e.Key) +} - if vint, ok := v.(int); ok { - return float32(vint), nil +// Uint16Default returns the entry's value as uint16. +// If not found returns "def" and a non-nil error. +func (e Entry) Uint16Default(def uint16) (uint16, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint16", e.Key) } - if vstring, sok := v.(string); sok { - vfloat32, err := strconv.ParseFloat(vstring, 32) + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 16) if err != nil { return def, err } + if val > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(val), nil + case uint: + if vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil + case uint8: + return uint16(vv), nil + case uint16: + return vv, nil + case uint32: + if vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil + case uint64: + if vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil + case int: + if vv < 0 || vv > math.MaxUint16 { + return def, errFindParse.Format("uint16", e.Key) + } + return uint16(vv), nil + } + + return def, errFindParse.Format("uint16", e.Key) +} - return float32(vfloat32), nil +// Uint32Default returns the entry's value as uint32. +// If not found returns "def" and a non-nil error. +func (e Entry) Uint32Default(def uint32) (uint32, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("uint32", e.Key) } - return def, errFindParse.Format("float32", e.Key) + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 32) + if err != nil { + return def, err + } + if val > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) + } + return uint32(val), nil + case uint: + if vv > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) + } + return uint32(vv), nil + case uint8: + return uint32(vv), nil + case uint16: + return uint32(vv), nil + case uint32: + return vv, nil + case uint64: + if vv > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) + } + return uint32(vv), nil + case int: + if vv < 0 || vv > math.MaxUint32 { + return def, errFindParse.Format("uint32", e.Key) + } + return uint32(vv), nil + } + + return def, errFindParse.Format("uint32", e.Key) } // Uint64Default returns the entry's value as uint64. @@ -205,23 +454,100 @@ func (e Entry) Uint64Default(def uint64) (uint64, error) { return def, errFindParse.Format("uint64", e.Key) } - if vuint64, ok := v.(uint64); ok { - return vuint64, nil + switch vv := v.(type) { + case string: + val, err := strconv.ParseUint(vv, 10, 64) + if err != nil { + return def, err + } + if val > math.MaxUint64 { + return def, errFindParse.Format("uint64", e.Key) + } + return uint64(val), nil + case uint: + if vv > math.MaxUint64 { + return def, errFindParse.Format("uint64", e.Key) + } + return uint64(vv), nil + case uint8: + return uint64(vv), nil + case uint16: + return uint64(vv), nil + case uint32: + return uint64(vv), nil + case uint64: + return vv, nil + case int64: + return uint64(vv), nil + case int: + return uint64(vv), nil } - if vint64, ok := v.(int64); ok { - return uint64(vint64), nil + return def, errFindParse.Format("uint64", e.Key) +} + +// Float32Default returns the entry's value as float32. +// If not found returns "def" and a non-nil error. +func (e Entry) Float32Default(key string, def float32) (float32, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("float32", e.Key) } - if vint, ok := v.(int); ok { - return uint64(vint), nil + switch vv := v.(type) { + case string: + val, err := strconv.ParseFloat(vv, 32) + if err != nil { + return def, err + } + if val > math.MaxFloat32 { + return def, errFindParse.Format("float32", e.Key) + } + return float32(val), nil + case float32: + return vv, nil + case float64: + if vv > math.MaxFloat32 { + return def, errFindParse.Format("float32", e.Key) + } + return float32(vv), nil + case int: + return float32(vv), nil } - if vstring, sok := v.(string); sok { - return strconv.ParseUint(vstring, 10, 64) + return def, errFindParse.Format("float32", e.Key) +} + +// Float64Default returns the entry's value as float64. +// If not found returns "def" and a non-nil error. +func (e Entry) Float64Default(def float64) (float64, error) { + v := e.ValueRaw + if v == nil { + return def, errFindParse.Format("float64", e.Key) } - return def, errFindParse.Format("uint64", e.Key) + switch vv := v.(type) { + case string: + val, err := strconv.ParseFloat(vv, 64) + if err != nil { + return def, err + } + return val, nil + case float32: + return float64(vv), nil + case float64: + return vv, nil + case int: + return float64(vv), nil + case int64: + return float64(vv), nil + case uint: + return float64(vv), nil + case uint64: + return float64(vv), nil + } + + return def, errFindParse.Format("float64", e.Key) } // BoolDefault returns the user's value as bool. @@ -236,20 +562,17 @@ func (e Entry) BoolDefault(def bool) (bool, error) { return def, errFindParse.Format("bool", e.Key) } - if vBoolean, ok := v.(bool); ok { - return vBoolean, nil - } - - if vString, ok := v.(string); ok { - b, err := strconv.ParseBool(vString) + switch vv := v.(type) { + case string: + val, err := strconv.ParseBool(vv) if err != nil { return def, err } - return b, nil - } - - if vInt, ok := v.(int); ok { - if vInt == 1 { + return val, nil + case bool: + return vv, nil + case int: + if vv == 1 { return true, nil } return false, nil @@ -361,28 +684,39 @@ func (r *Store) SetImmutable(key string, value interface{}) (Entry, bool) { return r.Save(key, value, true) } +var emptyEntry Entry + // GetEntry returns a pointer to the "Entry" found with the given "key" -// if nothing found then it returns nil, so be careful with that, -// it's not supposed to be used by end-developers. -func (r *Store) GetEntry(key string) *Entry { +// if nothing found then it returns an empty Entry and false. +func (r *Store) GetEntry(key string) (Entry, bool) { args := *r n := len(args) for i := 0; i < n; i++ { - kv := &args[i] - if kv.Key == key { - return kv + if kv := args[i]; kv.Key == key { + return kv, true } } - return nil + return emptyEntry, false +} + +// GetEntryAt returns the internal Entry of the memstore based on its index, +// the stored index by the router. +// If not found then it returns a zero Entry and false. +func (r *Store) GetEntryAt(index int) (Entry, bool) { + args := *r + if len(args) > index { + return args[index], true + } + return emptyEntry, false } // GetDefault returns the entry's value based on its key. // If not found returns "def". // This function checks for immutability as well, the rest don't. func (r *Store) GetDefault(key string, def interface{}) interface{} { - v := r.GetEntry(key) - if v == nil || v.ValueRaw == nil { + v, ok := r.GetEntry(key) + if !ok || v.ValueRaw == nil { return def } vv := v.Value() @@ -411,8 +745,8 @@ func (r *Store) Visit(visitor func(key string, value interface{})) { // GetStringDefault returns the entry's value as string, based on its key. // If not found returns "def". func (r *Store) GetStringDefault(key string, def string) string { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return def } @@ -432,8 +766,8 @@ func (r *Store) GetStringTrim(name string) string { // GetInt returns the entry's value as int, based on its key. // If not found returns -1 and a non-nil error. func (r *Store) GetInt(key string) (int, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return 0, errFindParse.Format("int", key) } return v.IntDefault(-1) @@ -449,20 +783,60 @@ func (r *Store) GetIntDefault(key string, def int) int { return def } -// GetUint64 returns the entry's value as uint64, based on its key. -// If not found returns 0 and a non-nil error. -func (r *Store) GetUint64(key string) (uint64, error) { - v := r.GetEntry(key) - if v == nil { - return 0, errFindParse.Format("uint64", key) +// GetInt8 returns the entry's value as int8, based on its key. +// If not found returns -1 and a non-nil error. +func (r *Store) GetInt8(key string) (int8, error) { + v, ok := r.GetEntry(key) + if !ok { + return -1, errFindParse.Format("int8", key) } - return v.Uint64Default(0) + return v.Int8Default(-1) } -// GetUint64Default returns the entry's value as uint64, based on its key. +// GetInt8Default returns the entry's value as int8, based on its key. // If not found returns "def". -func (r *Store) GetUint64Default(key string, def uint64) uint64 { - if v, err := r.GetUint64(key); err == nil { +func (r *Store) GetInt8Default(key string, def int8) int8 { + if v, err := r.GetInt8(key); err == nil { + return v + } + + return def +} + +// GetInt16 returns the entry's value as int16, based on its key. +// If not found returns -1 and a non-nil error. +func (r *Store) GetInt16(key string) (int16, error) { + v, ok := r.GetEntry(key) + if !ok { + return -1, errFindParse.Format("int16", key) + } + return v.Int16Default(-1) +} + +// GetInt16Default returns the entry's value as int16, based on its key. +// If not found returns "def". +func (r *Store) GetInt16Default(key string, def int16) int16 { + if v, err := r.GetInt16(key); err == nil { + return v + } + + return def +} + +// GetInt32 returns the entry's value as int32, based on its key. +// If not found returns -1 and a non-nil error. +func (r *Store) GetInt32(key string) (int32, error) { + v, ok := r.GetEntry(key) + if !ok { + return -1, errFindParse.Format("int32", key) + } + return v.Int32Default(-1) +} + +// GetInt32Default returns the entry's value as int32, based on its key. +// If not found returns "def". +func (r *Store) GetInt32Default(key string, def int32) int32 { + if v, err := r.GetInt32(key); err == nil { return v } @@ -472,8 +846,8 @@ func (r *Store) GetUint64Default(key string, def uint64) uint64 { // GetInt64 returns the entry's value as int64, based on its key. // If not found returns -1 and a non-nil error. func (r *Store) GetInt64(key string) (int64, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return -1, errFindParse.Format("int64", key) } return v.Int64Default(-1) @@ -489,11 +863,111 @@ func (r *Store) GetInt64Default(key string, def int64) int64 { return def } +// GetUint returns the entry's value as uint, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint(key string) (uint, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint", key) + } + return v.UintDefault(0) +} + +// GetUintDefault returns the entry's value as uint, based on its key. +// If not found returns "def". +func (r *Store) GetUintDefault(key string, def uint) uint { + if v, err := r.GetUint(key); err == nil { + return v + } + + return def +} + +// GetUint8 returns the entry's value as uint8, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint8(key string) (uint8, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint8", key) + } + return v.Uint8Default(0) +} + +// GetUint8Default returns the entry's value as uint8, based on its key. +// If not found returns "def". +func (r *Store) GetUint8Default(key string, def uint8) uint8 { + if v, err := r.GetUint8(key); err == nil { + return v + } + + return def +} + +// GetUint16 returns the entry's value as uint16, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint16(key string) (uint16, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint16", key) + } + return v.Uint16Default(0) +} + +// GetUint16Default returns the entry's value as uint16, based on its key. +// If not found returns "def". +func (r *Store) GetUint16Default(key string, def uint16) uint16 { + if v, err := r.GetUint16(key); err == nil { + return v + } + + return def +} + +// GetUint32 returns the entry's value as uint32, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint32(key string) (uint32, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint32", key) + } + return v.Uint32Default(0) +} + +// GetUint32Default returns the entry's value as uint32, based on its key. +// If not found returns "def". +func (r *Store) GetUint32Default(key string, def uint32) uint32 { + if v, err := r.GetUint32(key); err == nil { + return v + } + + return def +} + +// GetUint64 returns the entry's value as uint64, based on its key. +// If not found returns 0 and a non-nil error. +func (r *Store) GetUint64(key string) (uint64, error) { + v, ok := r.GetEntry(key) + if !ok { + return 0, errFindParse.Format("uint64", key) + } + return v.Uint64Default(0) +} + +// GetUint64Default returns the entry's value as uint64, based on its key. +// If not found returns "def". +func (r *Store) GetUint64Default(key string, def uint64) uint64 { + if v, err := r.GetUint64(key); err == nil { + return v + } + + return def +} + // GetFloat64 returns the entry's value as float64, based on its key. // If not found returns -1 and a non nil error. func (r *Store) GetFloat64(key string) (float64, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return -1, errFindParse.Format("float64", key) } return v.Float64Default(-1) @@ -516,8 +990,8 @@ func (r *Store) GetFloat64Default(key string, def float64) float64 { // // If not found returns false and a non-nil error. func (r *Store) GetBool(key string) (bool, error) { - v := r.GetEntry(key) - if v == nil { + v, ok := r.GetEntry(key) + if !ok { return false, errFindParse.Format("bool", key) } diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 242fbadbb3..9aa9a3386e 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -9,20 +9,18 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" ) -const ( - // MethodNone is a Virtual method - // to store the "offline" routes. - MethodNone = "NONE" -) +// MethodNone is a Virtual method +// to store the "offline" routes. +const MethodNone = "NONE" var ( // AllMethods contains the valid http methods: // "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD", // "PATCH", "OPTIONS", "TRACE". - AllMethods = [...]string{ + AllMethods = []string{ "GET", "POST", "PUT", @@ -68,7 +66,7 @@ func (r *repository) getAll() []*Route { // and child routers. type APIBuilder struct { // the api builder global macros registry - macros *macro.Map + macros *macro.Macros // the api builder global handlers per status code registry (used for custom http errors) errorCodeHandlers *ErrorCodeHandlers // the api builder global routes repository @@ -116,7 +114,7 @@ var _ RoutesProvider = (*APIBuilder)(nil) // passed to the default request handl // which is responsible to build the API and the router handler. func NewAPIBuilder() *APIBuilder { api := &APIBuilder{ - macros: defaultMacros(), + macros: macro.Defaults, errorCodeHandlers: defaultErrorCodeHandlers(), reporter: errors.NewReporter(), relativePath: "/", @@ -246,7 +244,7 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co ) for _, m := range methods { - route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, api.macros) + route, err = NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros) if err != nil { // template path parser errors: api.reporter.Add("%v -> %s:%s:%s", err, method, subdomain, path) return nil // fail on first error. @@ -270,10 +268,10 @@ func (api *APIBuilder) Handle(method string, relativePath string, handlers ...co // otherwise use `Party` which can handle many paths with different handlers and middlewares. // // Usage: -// app.HandleMany("GET", "/user /user/{id:int} /user/me", genericUserHandler) +// app.HandleMany("GET", "/user /user/{id:uint64} /user/me", genericUserHandler) // At the other side, with `Handle` we've had to write: // app.Handle("GET", "/user", userHandler) -// app.Handle("GET", "/user/{id:int}", userByIDHandler) +// app.Handle("GET", "/user/{id:uint64}", userByIDHandler) // app.Handle("GET", "/user/me", userMeHandler) // // This method is used behind the scenes at the `Controller` function @@ -411,11 +409,11 @@ func (api *APIBuilder) WildcardSubdomain(middleware ...context.Handler) Party { return api.Subdomain(SubdomainWildcardIndicator, middleware...) } -// Macros returns the macro map which is responsible -// to register custom macro functions for all routes. +// Macros returns the macro collection that is responsible +// to register custom macros with their own parameter types and their macro functions for all routes. // // Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path -func (api *APIBuilder) Macros() *macro.Map { +func (api *APIBuilder) Macros() *macro.Macros { return api.macros } diff --git a/core/router/handler.go b/core/router/handler.go index 24ecfc5d74..cc91819f19 100644 --- a/core/router/handler.go +++ b/core/router/handler.go @@ -11,40 +11,30 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/netutil" - "github.com/kataras/iris/core/router/node" ) // RequestHandler the middle man between acquiring a context and releasing it. // By-default is the router algorithm. type RequestHandler interface { - // HandleRequest is same as context.Handler but its usage is only about routing, - // separate the concept here. + // HandleRequest should handle the request based on the Context. HandleRequest(context.Context) - // Build should builds the handler, it's being called on router's BuildRouter. + // Build should builds the handler, it's being called on router's BuildRouter. Build(provider RoutesProvider) error // RouteExists reports whether a particular route exists. RouteExists(ctx context.Context, method, path string) bool } -type tree struct { - Method string - // subdomain is empty for default-hostname routes, - // ex: mysubdomain. - Subdomain string - Nodes *node.Nodes -} - type routerHandler struct { - trees []*tree + trees []*trie hosts bool // true if at least one route contains a Subdomain. } var _ RequestHandler = &routerHandler{} -func (h *routerHandler) getTree(method, subdomain string) *tree { +func (h *routerHandler) getTree(method, subdomain string) *trie { for i := range h.trees { t := h.trees[i] - if t.Method == method && t.Subdomain == subdomain { + if t.method == method && t.subdomain == subdomain { return t } } @@ -64,12 +54,14 @@ func (h *routerHandler) addRoute(r *Route) error { t := h.getTree(method, subdomain) if t == nil { - n := node.Nodes{} + n := newTrieNode() // first time we register a route to this method with this subdomain - t = &tree{Method: method, Subdomain: subdomain, Nodes: &n} + t = &trie{method: method, subdomain: subdomain, root: n} h.trees = append(h.trees, t) } - return t.Nodes.Add(routeName, path, handlers) + + t.insert(path, routeName, handlers) + return nil } // NewDefaultHandler returns the handler which is responsible @@ -189,11 +181,11 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { for i := range h.trees { t := h.trees[i] - if method != t.Method { + if method != t.method { continue } - if h.hosts && t.Subdomain != "" { + if h.hosts && t.subdomain != "" { requestHost := ctx.Host() if netutil.IsLoopbackSubdomain(requestHost) { // this fixes a bug when listening on @@ -202,7 +194,7 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { continue // it's not a subdomain, it's something like 127.0.0.1 probably } // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty - if t.Subdomain == SubdomainWildcardIndicator { + if t.subdomain == SubdomainWildcardIndicator { // mydomain.com -> invalid // localhost -> invalid // sub.mydomain.com -> valid @@ -220,14 +212,14 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { continue } // continue to that, any subdomain is valid. - } else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot. + } else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot. continue } } - routeName, handlers := t.Nodes.Find(path, ctx.Params()) - if len(handlers) > 0 { - ctx.SetCurrentRouteName(routeName) - ctx.Do(handlers) + n := t.search(path, ctx.Params()) + if n != nil { + ctx.SetCurrentRouteName(n.RouteName) + ctx.Do(n.Handlers) // found return } @@ -238,15 +230,12 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { if ctx.Application().ConfigurationReadOnly().GetFireMethodNotAllowed() { for i := range h.trees { t := h.trees[i] - // a bit slower than previous implementation but @kataras let me to apply this change - // because it's more reliable. - // // if `Configuration#FireMethodNotAllowed` is kept as defaulted(false) then this function will not // run, therefore performance kept as before. - if t.Nodes.Exists(path) { + if h.subdomainAndPathAndMethodExists(ctx, t, "", path) { // RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html // The response MUST include an Allow header containing a list of valid methods for the requested resource. - ctx.Header("Allow", t.Method) + ctx.Header("Allow", t.method) ctx.StatusCode(http.StatusMethodNotAllowed) return } @@ -256,55 +245,55 @@ func (h *routerHandler) HandleRequest(ctx context.Context) { ctx.StatusCode(http.StatusNotFound) } -// RouteExists reports whether a particular route exists -// It will search from the current subdomain of context's host, if not inside the root domain. -func (h *routerHandler) RouteExists(ctx context.Context, method, path string) bool { - for i := range h.trees { - t := h.trees[i] - if method != t.Method { - continue - } +func (h *routerHandler) subdomainAndPathAndMethodExists(ctx context.Context, t *trie, method, path string) bool { + if method != "" && method != t.method { + return false + } - if h.hosts && t.Subdomain != "" { - requestHost := ctx.Host() - if netutil.IsLoopbackSubdomain(requestHost) { - // this fixes a bug when listening on - // 127.0.0.1:8080 for example - // and have a wildcard subdomain and a route registered to root domain. - continue // it's not a subdomain, it's something like 127.0.0.1 probably + if h.hosts && t.subdomain != "" { + requestHost := ctx.Host() + if netutil.IsLoopbackSubdomain(requestHost) { + // this fixes a bug when listening on + // 127.0.0.1:8080 for example + // and have a wildcard subdomain and a route registered to root domain. + return false // it's not a subdomain, it's something like 127.0.0.1 probably + } + // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty + if t.subdomain == SubdomainWildcardIndicator { + // mydomain.com -> invalid + // localhost -> invalid + // sub.mydomain.com -> valid + // sub.localhost -> valid + serverHost := ctx.Application().ConfigurationReadOnly().GetVHost() + if serverHost == requestHost { + return false // it's not a subdomain, it's a full domain (with .com...) } - // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty - if t.Subdomain == SubdomainWildcardIndicator { - // mydomain.com -> invalid - // localhost -> invalid - // sub.mydomain.com -> valid - // sub.localhost -> valid - serverHost := ctx.Application().ConfigurationReadOnly().GetVHost() - if serverHost == requestHost { - continue // it's not a subdomain, it's a full domain (with .com...) - } - dotIdx := strings.IndexByte(requestHost, '.') - slashIdx := strings.IndexByte(requestHost, '/') - if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) { - // if "." was found anywhere but not at the first path segment (host). - } else { - continue - } - // continue to that, any subdomain is valid. - } else if !strings.HasPrefix(requestHost, t.Subdomain) { // t.Subdomain contains the dot. - continue + dotIdx := strings.IndexByte(requestHost, '.') + slashIdx := strings.IndexByte(requestHost, '/') + if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) { + // if "." was found anywhere but not at the first path segment (host). + } else { + return false } + // continue to that, any subdomain is valid. + } else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot. + return false } + } - _, handlers := t.Nodes.Find(path, ctx.Params()) - if len(handlers) > 0 { - // found + n := t.search(path, ctx.Params()) + return n != nil +} + +// RouteExists reports whether a particular route exists +// It will search from the current subdomain of context's host, if not inside the root domain. +func (h *routerHandler) RouteExists(ctx context.Context, method, path string) bool { + for i := range h.trees { + t := h.trees[i] + if h.subdomainAndPathAndMethodExists(ctx, t, method, path) { return true } - - // not found or method not allowed. - break } return false diff --git a/core/router/macro.go b/core/router/macro.go deleted file mode 100644 index ba6969ba30..0000000000 --- a/core/router/macro.go +++ /dev/null @@ -1,253 +0,0 @@ -package router - -import ( - "net/http" - "strconv" - "strings" - - "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" - "github.com/kataras/iris/core/router/macro" - "github.com/kataras/iris/core/router/macro/interpreter/ast" -) - -// defaultMacros returns a new macro map which -// contains the default router's named param types functions. -func defaultMacros() *macro.Map { - macros := macro.NewMap() - // registers the String and Int default macro funcs - // user can add or override of his own funcs later on - // i.e: - // app.Macro.String.RegisterFunc("equal", func(eqWith string) func(string) bool { - // return func(paramValue string) bool { - // return eqWith == paramValue - // }}) - registerBuiltinsMacroFuncs(macros) - - return macros -} - -func registerBuiltinsMacroFuncs(out *macro.Map) { - // register the String which is the default type if not - // parameter type is specified or - // if a given parameter into path given but the func doesn't exist on the - // parameter type's function list. - // - // these can be overridden by the user, later on. - registerStringMacroFuncs(out.String) - registerIntMacroFuncs(out.Int) - registerIntMacroFuncs(out.Long) - registerAlphabeticalMacroFuncs(out.Alphabetical) - registerFileMacroFuncs(out.File) - registerPathMacroFuncs(out.Path) -} - -// String -// anything one part -func registerStringMacroFuncs(out *macro.Macro) { - // this can be used everywhere, it's to help users to define custom regexp expressions - // on all macros - out.RegisterFunc("regexp", func(expr string) macro.EvaluatorFunc { - regexpEvaluator := macro.MustNewEvaluatorFromRegexp(expr) - return regexpEvaluator - }) - - // checks if param value starts with the 'prefix' arg - out.RegisterFunc("prefix", func(prefix string) macro.EvaluatorFunc { - return func(paramValue string) bool { - return strings.HasPrefix(paramValue, prefix) - } - }) - - // checks if param value ends with the 'suffix' arg - out.RegisterFunc("suffix", func(suffix string) macro.EvaluatorFunc { - return func(paramValue string) bool { - return strings.HasSuffix(paramValue, suffix) - } - }) - - // checks if param value contains the 's' arg - out.RegisterFunc("contains", func(s string) macro.EvaluatorFunc { - return func(paramValue string) bool { - return strings.Contains(paramValue, s) - } - }) - - // checks if param value's length is at least 'min' - out.RegisterFunc("min", func(min int) macro.EvaluatorFunc { - return func(paramValue string) bool { - return len(paramValue) >= min - } - }) - // checks if param value's length is not bigger than 'max' - out.RegisterFunc("max", func(max int) macro.EvaluatorFunc { - return func(paramValue string) bool { - return max >= len(paramValue) - } - }) -} - -// Int -// only numbers (0-9) -func registerIntMacroFuncs(out *macro.Macro) { - // checks if the param value's int representation is - // bigger or equal than 'min' - out.RegisterFunc("min", func(min int) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= min - } - }) - - // checks if the param value's int representation is - // smaller or equal than 'max' - out.RegisterFunc("max", func(max int) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n <= max - } - }) - - // checks if the param value's int representation is - // between min and max, including 'min' and 'max' - out.RegisterFunc("range", func(min, max int) macro.EvaluatorFunc { - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - - if n < min || n > max { - return false - } - return true - } - }) -} - -// Alphabetical -// letters only (upper or lowercase) -func registerAlphabeticalMacroFuncs(out *macro.Macro) { - -} - -// File -// letters (upper or lowercase) -// numbers (0-9) -// underscore (_) -// dash (-) -// point (.) -// no spaces! or other character -func registerFileMacroFuncs(out *macro.Macro) { - -} - -// Path -// File+slashes(anywhere) -// should be the latest param, it's the wildcard -func registerPathMacroFuncs(out *macro.Macro) { - -} - -// compileRoutePathAndHandlers receives a route info and returns its parsed/"compiled" path -// and the new handlers (prepend all the macro's handler, if any). -// -// It's not exported for direct use. -func compileRoutePathAndHandlers(handlers context.Handlers, tmpl *macro.Template) (string, context.Handlers, error) { - // parse the path to node's path, now. - path, err := convertTmplToNodePath(tmpl) - if err != nil { - return tmpl.Src, handlers, err - } - // prepend the macro handler to the route, now, - // right before the register to the tree, so routerbuilder.UseGlobal will work as expected. - if len(tmpl.Params) > 0 { - macroEvaluatorHandler := convertTmplToHandler(tmpl) - // may return nil if no really need a macro handler evaluator - if macroEvaluatorHandler != nil { - handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...) - } - } - - return path, handlers, nil -} - -func convertTmplToNodePath(tmpl *macro.Template) (string, error) { - routePath := tmpl.Src - if len(tmpl.Params) > 0 { - if routePath[len(routePath)-1] == '/' { - routePath = routePath[0 : len(routePath)-2] // remove the last "/" if macro syntax instead of underline's - } - } - - // if it has started with {} and it's valid - // then the tmpl.Params will be filled, - // so no any further check needed - for i, p := range tmpl.Params { - if p.Type == ast.ParamTypePath { - if i != len(tmpl.Params)-1 { - return "", errors.New("parameter type \"ParamTypePath\" should be putted to the very last of a path") - } - routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1) - } else { - routePath = strings.Replace(routePath, p.Src, Param(p.Name), 1) - } - } - - return routePath, nil -} - -// note: returns nil if not needed, the caller(router) should be check for that before adding that on route's Middleware -func convertTmplToHandler(tmpl *macro.Template) context.Handler { - - needMacroHandler := false - - // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. - // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) - // 2. if we don't have any named params then we don't need a handler too. - for _, p := range tmpl.Params { - if len(p.Funcs) == 0 && (p.Type == ast.ParamTypeUnExpected || p.Type == ast.ParamTypeString || p.Type == ast.ParamTypePath) && p.ErrCode == http.StatusNotFound { - } else { - // println("we need handler for: " + tmpl.Src) - needMacroHandler = true - } - } - - if !needMacroHandler { - // println("we don't need handler for: " + tmpl.Src) - return nil - } - - return func(tmpl macro.Template) context.Handler { - return func(ctx context.Context) { - for _, p := range tmpl.Params { - paramValue := ctx.Params().Get(p.Name) - // first, check for type evaluator - if !p.TypeEvaluator(paramValue) { - ctx.StatusCode(p.ErrCode) - ctx.StopExecution() - return - } - - // then check for all of its functions - for _, evalFunc := range p.Funcs { - if !evalFunc(paramValue) { - ctx.StatusCode(p.ErrCode) - ctx.StopExecution() - return - } - } - - } - // if all passed, just continue - ctx.Next() - } - }(*tmpl) - -} diff --git a/core/router/macro/interpreter/ast/ast.go b/core/router/macro/interpreter/ast/ast.go deleted file mode 100644 index 23e1723628..0000000000 --- a/core/router/macro/interpreter/ast/ast.go +++ /dev/null @@ -1,215 +0,0 @@ -package ast - -import ( - "fmt" - "reflect" - "strconv" -) - -// ParamType is a specific uint8 type -// which holds the parameter types' type. -type ParamType uint8 - -const ( - // ParamTypeUnExpected is an unexpected parameter type. - ParamTypeUnExpected ParamType = iota - // ParamTypeString is the string type. - // If parameter type is missing then it defaults to String type. - // Allows anything - // Declaration: /mypath/{myparam:string} or /mypath{myparam} - ParamTypeString - // ParamTypeInt is the integer, a number type. - // Allows only positive numbers (0-9) - // Declaration: /mypath/{myparam:int} - ParamTypeInt - // ParamTypeLong is the integer, a number type. - // Allows only positive numbers (0-9) - // Declaration: /mypath/{myparam:long} - ParamTypeLong - // ParamTypeBoolean is the bool type. - // Allows only "1" or "t" or "T" or "TRUE" or "true" or "True" - // or "0" or "f" or "F" or "FALSE" or "false" or "False". - // Declaration: /mypath/{myparam:boolean} - ParamTypeBoolean - // ParamTypeAlphabetical is the alphabetical/letter type type. - // Allows letters only (upper or lowercase) - // Declaration: /mypath/{myparam:alphabetical} - ParamTypeAlphabetical - // ParamTypeFile is the file single path type. - // Allows: - // letters (upper or lowercase) - // numbers (0-9) - // underscore (_) - // dash (-) - // point (.) - // no spaces! or other character - // Declaration: /mypath/{myparam:file} - ParamTypeFile - // ParamTypePath is the multi path (or wildcard) type. - // Allows anything, should be the last part - // Declaration: /mypath/{myparam:path} - ParamTypePath -) - -func (pt ParamType) String() string { - for k, v := range paramTypes { - if v == pt { - return k - } - } - - return "unexpected" -} - -// Not because for a single reason -// a string may be a -// ParamTypeString or a ParamTypeFile -// or a ParamTypePath or ParamTypeAlphabetical. -// -// func ParamTypeFromStd(k reflect.Kind) ParamType { - -// Kind returns the std kind of this param type. -func (pt ParamType) Kind() reflect.Kind { - switch pt { - case ParamTypeAlphabetical: - fallthrough - case ParamTypeFile: - fallthrough - case ParamTypePath: - fallthrough - case ParamTypeString: - return reflect.String - case ParamTypeInt: - return reflect.Int - case ParamTypeLong: - return reflect.Int64 - case ParamTypeBoolean: - return reflect.Bool - } - return reflect.Invalid // 0 -} - -// ValidKind will return true if at least one param type is supported -// for this std kind. -func ValidKind(k reflect.Kind) bool { - switch k { - case reflect.String: - fallthrough - case reflect.Int: - fallthrough - case reflect.Int64: - fallthrough - case reflect.Bool: - return true - default: - return false - } -} - -// Assignable returns true if the "k" standard type -// is assignabled to this ParamType. -func (pt ParamType) Assignable(k reflect.Kind) bool { - return pt.Kind() == k -} - -var paramTypes = map[string]ParamType{ - "string": ParamTypeString, - "int": ParamTypeInt, - "long": ParamTypeLong, - "boolean": ParamTypeBoolean, - "alphabetical": ParamTypeAlphabetical, - "file": ParamTypeFile, - "path": ParamTypePath, - // could be named also: - // "tail": - // "wild" - // "wildcard" - -} - -// LookupParamType accepts the string -// representation of a parameter type. -// Available: -// "string" -// "int" -// "long" -// "alphabetical" -// "file" -// "path" -func LookupParamType(ident string) ParamType { - if typ, ok := paramTypes[ident]; ok { - return typ - } - return ParamTypeUnExpected -} - -// LookupParamTypeFromStd accepts the string representation of a standard go type. -// It returns a ParamType, but it may differs for example -// the alphabetical, file, path and string are all string go types, so -// make sure that caller resolves these types before this call. -// -// string matches to string -// int matches to int -// int64 matches to long -// bool matches to boolean -func LookupParamTypeFromStd(goType string) ParamType { - switch goType { - case "string": - return ParamTypeString - case "int": - return ParamTypeInt - case "int64": - return ParamTypeLong - case "bool": - return ParamTypeBoolean - default: - return ParamTypeUnExpected - } -} - -// ParamStatement is a struct -// which holds all the necessary information about a macro parameter. -// It holds its type (string, int, alphabetical, file, path), -// its source ({param:type}), -// its name ("param"), -// its attached functions by the user (min, max...) -// and the http error code if that parameter -// failed to be evaluated. -type ParamStatement struct { - Src string // the original unparsed source, i.e: {id:int range(1,5) else 404} - Name string // id - Type ParamType // int - Funcs []ParamFunc // range - ErrorCode int // 404 -} - -// ParamFuncArg represents a single parameter function's argument -type ParamFuncArg interface{} - -// ParamFuncArgToInt converts and returns -// any type of "a", to an integer. -func ParamFuncArgToInt(a ParamFuncArg) (int, error) { - switch a.(type) { - case int: - return a.(int), nil - case string: - return strconv.Atoi(a.(string)) - case int64: - return int(a.(int64)), nil - default: - return -1, fmt.Errorf("unexpected function argument type: %q", a) - } -} - -// ParamFunc holds the name of a parameter's function -// and its arguments (values) -// A param func is declared with: -// {param:int range(1,5)}, -// the range is the -// param function name -// the 1 and 5 are the two param function arguments -// range(1,5) -type ParamFunc struct { - Name string // range - Args []ParamFuncArg // [1,5] -} diff --git a/core/router/macro/macro.go b/core/router/macro/macro.go deleted file mode 100644 index e4141c0789..0000000000 --- a/core/router/macro/macro.go +++ /dev/null @@ -1,292 +0,0 @@ -package macro - -import ( - "fmt" - "reflect" - "regexp" - "strconv" - "unicode" - - "github.com/kataras/iris/core/router/macro/interpreter/ast" -) - -// EvaluatorFunc is the signature for both param types and param funcs. -// It should accepts the param's value as string -// and return true if validated otherwise false. -type EvaluatorFunc func(paramValue string) bool - -// NewEvaluatorFromRegexp accepts a regexp "expr" expression -// and returns an EvaluatorFunc based on that regexp. -// the regexp is compiled before return. -// -// Returns a not-nil error on regexp compile failure. -func NewEvaluatorFromRegexp(expr string) (EvaluatorFunc, error) { - if expr == "" { - return nil, fmt.Errorf("empty regex expression") - } - - // add the last $ if missing (and not wildcard(?)) - if i := expr[len(expr)-1]; i != '$' && i != '*' { - expr += "$" - } - - r, err := regexp.Compile(expr) - if err != nil { - return nil, err - } - - return r.MatchString, nil -} - -// MustNewEvaluatorFromRegexp same as NewEvaluatorFromRegexp -// but it panics on the "expr" parse failure. -func MustNewEvaluatorFromRegexp(expr string) EvaluatorFunc { - r, err := NewEvaluatorFromRegexp(expr) - if err != nil { - panic(err) - } - return r -} - -var ( - goodParamFuncReturnType = reflect.TypeOf(func(string) bool { return false }) - goodParamFuncReturnType2 = reflect.TypeOf(EvaluatorFunc(func(string) bool { return false })) -) - -func goodParamFunc(typ reflect.Type) bool { - // should be a func - // which returns a func(string) bool - if typ.Kind() == reflect.Func { - if typ.NumOut() == 1 { - typOut := typ.Out(0) - if typOut == goodParamFuncReturnType || typOut == goodParamFuncReturnType2 { - return true - } - } - } - - return false -} - -// goodParamFuncName reports whether the function name is a valid identifier. -func goodParamFuncName(name string) bool { - if name == "" { - return false - } - // valid names are only letters and _ - for _, r := range name { - switch { - case r == '_': - case !unicode.IsLetter(r): - return false - } - } - return true -} - -// the convertBuilderFunc return value is generating at boot time. -// convertFunc converts an interface to a valid full param function. -func convertBuilderFunc(fn interface{}) ParamEvaluatorBuilder { - - typFn := reflect.TypeOf(fn) - if !goodParamFunc(typFn) { - return nil - } - - numFields := typFn.NumIn() - - return func(args []ast.ParamFuncArg) EvaluatorFunc { - if len(args) != numFields { - // no variadics support, for now. - panic("args should be the same len as numFields") - } - var argValues []reflect.Value - for i := 0; i < numFields; i++ { - field := typFn.In(i) - arg := args[i] - - if field.Kind() != reflect.TypeOf(arg).Kind() { - panic("fields should have the same type") - } - - argValues = append(argValues, reflect.ValueOf(arg)) - } - - evalFn := reflect.ValueOf(fn).Call(argValues)[0].Interface() - - var evaluator EvaluatorFunc - // check for typed and not typed - if _v, ok := evalFn.(EvaluatorFunc); ok { - evaluator = _v - } else if _v, ok = evalFn.(func(string) bool); ok { - evaluator = _v - } - return func(paramValue string) bool { - return evaluator(paramValue) - } - } -} - -type ( - // Macro represents the parsed macro, - // which holds - // the evaluator (param type's evaluator + param functions evaluators) - // and its param functions. - // - // Any type contains its own macro - // instance, so an String type - // contains its type evaluator - // which is the "Evaluator" field - // and it can register param functions - // to that macro which maps to a parameter type. - Macro struct { - Evaluator EvaluatorFunc - funcs []ParamFunc - } - - // ParamEvaluatorBuilder is a func - // which accepts a param function's arguments (values) - // and returns an EvaluatorFunc, its job - // is to make the macros to be registered - // by user at the most generic possible way. - ParamEvaluatorBuilder func([]ast.ParamFuncArg) EvaluatorFunc - - // ParamFunc represents the parsed - // parameter function, it holds - // the parameter's name - // and the function which will build - // the evaluator func. - ParamFunc struct { - Name string - Func ParamEvaluatorBuilder - } -) - -func newMacro(evaluator EvaluatorFunc) *Macro { - return &Macro{Evaluator: evaluator} -} - -// RegisterFunc registers a parameter function -// to that macro. -// Accepts the func name ("range") -// and the function body, which should return an EvaluatorFunc -// a bool (it will be converted to EvaluatorFunc later on), -// i.e RegisterFunc("min", func(minValue int) func(paramValue string) bool){}) -func (m *Macro) RegisterFunc(funcName string, fn interface{}) { - fullFn := convertBuilderFunc(fn) - m.registerFunc(funcName, fullFn) -} - -func (m *Macro) registerFunc(funcName string, fullFn ParamEvaluatorBuilder) { - if !goodParamFuncName(funcName) { - return - } - - for _, fn := range m.funcs { - if fn.Name == funcName { - fn.Func = fullFn - return - } - } - - m.funcs = append(m.funcs, ParamFunc{ - Name: funcName, - Func: fullFn, - }) -} - -func (m *Macro) getFunc(funcName string) ParamEvaluatorBuilder { - for _, fn := range m.funcs { - if fn.Name == funcName { - if fn.Func == nil { - continue - } - return fn.Func - } - } - return nil -} - -// Map contains the default macros mapped to their types. -// This is the manager which is used by the caller to register custom -// parameter functions per param-type (String, Int, Long, Boolean, Alphabetical, File, Path). -type Map struct { - // string type - // anything - String *Macro - // uint type - // only positive numbers (+0-9) - // it could be uint/uint32 but we keep int for simplicity - Int *Macro - // long an int64 type - // only positive numbers (+0-9) - // it could be uint64 but we keep int64 for simplicity - Long *Macro - // boolean as bool type - // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" - // or "0" or "f" or "F" or "FALSE" or "false" or "False". - Boolean *Macro - // alphabetical/letter type - // letters only (upper or lowercase) - Alphabetical *Macro - // file type - // letters (upper or lowercase) - // numbers (0-9) - // underscore (_) - // dash (-) - // point (.) - // no spaces! or other character - File *Macro - // path type - // anything, should be the last part - Path *Macro -} - -// NewMap returns a new macro Map with default -// type evaluators. -// -// Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path -func NewMap() *Map { - return &Map{ - // it allows everything, so no need for a regexp here. - String: newMacro(func(string) bool { return true }), - Int: newMacro(MustNewEvaluatorFromRegexp("^[0-9]+$")), - Long: newMacro(MustNewEvaluatorFromRegexp("^[0-9]+$")), - Boolean: newMacro(func(paramValue string) bool { - // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$ - // in this case. - _, err := strconv.ParseBool(paramValue) - return err == nil - }), - Alphabetical: newMacro(MustNewEvaluatorFromRegexp("^[a-zA-Z ]+$")), - File: newMacro(MustNewEvaluatorFromRegexp("^[a-zA-Z0-9_.-]*$")), - // it allows everything, we have String and Path as different - // types because I want to give the opportunity to the user - // to organise the macro functions based on wildcard or single dynamic named path parameter. - // Should be the last. - Path: newMacro(func(string) bool { return true }), - } -} - -// Lookup returns the specific Macro from the map -// based on the parameter type. -// i.e if ast.ParamTypeInt then it will return the m.Int. -// Returns the m.String if not matched. -func (m *Map) Lookup(typ ast.ParamType) *Macro { - switch typ { - case ast.ParamTypeInt: - return m.Int - case ast.ParamTypeLong: - return m.Long - case ast.ParamTypeBoolean: - return m.Boolean - case ast.ParamTypeAlphabetical: - return m.Alphabetical - case ast.ParamTypeFile: - return m.File - case ast.ParamTypePath: - return m.Path - default: - return m.String - } -} diff --git a/core/router/macro/macro_test.go b/core/router/macro/macro_test.go deleted file mode 100644 index d412da29e5..0000000000 --- a/core/router/macro/macro_test.go +++ /dev/null @@ -1,186 +0,0 @@ -package macro - -import ( - "reflect" - "testing" -) - -// Most important tests to look: -// ../parser/parser_test.go -// ../lexer/lexer_test.go - -func TestGoodParamFunc(t *testing.T) { - good1 := func(min int, max int) func(string) bool { - return func(paramValue string) bool { - return true - } - } - - good2 := func(min int, max int) func(string) bool { - return func(paramValue string) bool { - return true - } - } - - notgood1 := func(min int, max int) bool { - return false - } - - if !goodParamFunc(reflect.TypeOf(good1)) { - t.Fatalf("expected good1 func to be good but it's not") - } - - if !goodParamFunc(reflect.TypeOf(good2)) { - t.Fatalf("expected good2 func to be good but it's not") - } - - if goodParamFunc(reflect.TypeOf(notgood1)) { - t.Fatalf("expected notgood1 func to be the worst") - } -} - -func TestGoodParamFuncName(t *testing.T) { - tests := []struct { - name string - good bool - }{ - {"range", true}, - {"_range", true}, - {"range_", true}, - {"r_ange", true}, - // numbers or other symbols are invalid. - {"range1", false}, - {"2range", false}, - {"r@nge", false}, - {"rang3", false}, - } - for i, tt := range tests { - isGood := goodParamFuncName(tt.name) - if tt.good && !isGood { - t.Fatalf("tests[%d] - expecting valid name but got invalid for name %s", i, tt.name) - } else if !tt.good && isGood { - t.Fatalf("tests[%d] - expecting invalid name but got valid for name %s", i, tt.name) - } - } -} - -func testEvaluatorRaw(macroEvaluator *Macro, input string, pass bool, i int, t *testing.T) { - if got := macroEvaluator.Evaluator(input); pass != got { - t.Fatalf("tests[%d] - expecting %v but got %v", i, pass, got) - } -} - -func TestStringEvaluatorRaw(t *testing.T) { - f := NewMap() - - tests := []struct { - pass bool - input string - }{ - {true, "astring"}, // 0 - {true, "astringwith_numb3rS_and_symbol$"}, // 1 - {true, "32321"}, // 2 - {true, "main.css"}, // 3 - {true, "/assets/main.css"}, // 4 - // false never - } // 0 - - for i, tt := range tests { - testEvaluatorRaw(f.String, tt.input, tt.pass, i, t) - } -} - -func TestIntEvaluatorRaw(t *testing.T) { - f := NewMap() - - tests := []struct { - pass bool - input string - }{ - {false, "astring"}, // 0 - {false, "astringwith_numb3rS_and_symbol$"}, // 1 - {true, "32321"}, // 2 - {false, "main.css"}, // 3 - {false, "/assets/main.css"}, // 4 - } - - for i, tt := range tests { - testEvaluatorRaw(f.Int, tt.input, tt.pass, i, t) - } -} - -func TestAlphabeticalEvaluatorRaw(t *testing.T) { - f := NewMap() - - tests := []struct { - pass bool - input string - }{ - {true, "astring"}, // 0 - {false, "astringwith_numb3rS_and_symbol$"}, // 1 - {false, "32321"}, // 2 - {false, "main.css"}, // 3 - {false, "/assets/main.css"}, // 4 - } - - for i, tt := range tests { - testEvaluatorRaw(f.Alphabetical, tt.input, tt.pass, i, t) - } -} - -func TestFileEvaluatorRaw(t *testing.T) { - f := NewMap() - - tests := []struct { - pass bool - input string - }{ - {true, "astring"}, // 0 - {false, "astringwith_numb3rS_and_symbol$"}, // 1 - {true, "32321"}, // 2 - {true, "main.css"}, // 3 - {false, "/assets/main.css"}, // 4 - } - - for i, tt := range tests { - testEvaluatorRaw(f.File, tt.input, tt.pass, i, t) - } -} - -func TestPathEvaluatorRaw(t *testing.T) { - f := NewMap() - - pathTests := []struct { - pass bool - input string - }{ - {true, "astring"}, // 0 - {true, "astringwith_numb3rS_and_symbol$"}, // 1 - {true, "32321"}, // 2 - {true, "main.css"}, // 3 - {true, "/assets/main.css"}, // 4 - {true, "disk/assets/main.css"}, // 5 - } - - for i, tt := range pathTests { - testEvaluatorRaw(f.Path, tt.input, tt.pass, i, t) - } -} - -// func TestMapRegisterFunc(t *testing.T) { -// m := NewMap() -// m.String.RegisterFunc("prefix", func(prefix string) EvaluatorFunc { -// return func(paramValue string) bool { -// return strings.HasPrefix(paramValue, prefix) -// } -// }) - -// p, err := Parse("/user/@iris") -// if err != nil { -// t.Fatalf(err) -// } - -// // p.Params = append(p.) - -// testEvaluatorRaw(m.String, p.Src, false, 0, t) -// } diff --git a/core/router/macro/template.go b/core/router/macro/template.go deleted file mode 100644 index f26c1d0619..0000000000 --- a/core/router/macro/template.go +++ /dev/null @@ -1,75 +0,0 @@ -package macro - -import ( - "github.com/kataras/iris/core/router/macro/interpreter/ast" - "github.com/kataras/iris/core/router/macro/interpreter/parser" -) - -// Template contains a route's path full parsed template. -// -// Fields: -// Src is the raw source of the path, i.e /users/{id:int min(1)} -// Params is the list of the Params that are being used to the -// path, i.e the min as param name and 1 as the param argument. -type Template struct { - // Src is the original template given by the client - Src string `json:"src"` - Params []TemplateParam `json:"params"` -} - -// TemplateParam is the parsed macro parameter's template -// they are being used to describe the param's syntax result. -type TemplateParam struct { - Src string `json:"src"` // the unparsed param'false source - // Type is not useful anywhere here but maybe - // it's useful on host to decide how to convert the path template to specific router's syntax - Type ast.ParamType `json:"type"` - Name string `json:"name"` - ErrCode int `json:"errCode"` - TypeEvaluator EvaluatorFunc `json:"-"` - Funcs []EvaluatorFunc `json:"-"` -} - -// Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions) -// and returns a new Template. -// It builds all the parameter functions for that template -// and their evaluators, it's the api call that makes use the interpeter's parser -> lexer. -func Parse(src string, macros *Map) (*Template, error) { - params, err := parser.Parse(src) - if err != nil { - return nil, err - } - t := new(Template) - t.Src = src - - for _, p := range params { - funcMap := macros.Lookup(p.Type) - typEval := funcMap.Evaluator - - tmplParam := TemplateParam{ - Src: p.Src, - Type: p.Type, - Name: p.Name, - ErrCode: p.ErrorCode, - TypeEvaluator: typEval, - } - for _, paramfn := range p.Funcs { - tmplFn := funcMap.getFunc(paramfn.Name) - if tmplFn == nil { // if not find on this type, check for String's which is for global funcs too - tmplFn = macros.String.getFunc(paramfn.Name) - if tmplFn == nil { // if not found then just skip this param - continue - } - } - evalFn := tmplFn(paramfn.Args) - if evalFn == nil { - continue - } - tmplParam.Funcs = append(tmplParam.Funcs, evalFn) - } - - t.Params = append(t.Params, tmplParam) - } - - return t, nil -} diff --git a/core/router/node/node.go b/core/router/node/node.go deleted file mode 100644 index 4a4adb05eb..0000000000 --- a/core/router/node/node.go +++ /dev/null @@ -1,448 +0,0 @@ -package node - -import ( - "sort" - "strings" - - "github.com/kataras/iris/context" - "github.com/kataras/iris/core/errors" -) - -// Nodes a conversion type for []*node. -type Nodes []*node - -type node struct { - s string - routeName string - wildcardParamName string // name of the wildcard parameter, only one per whole Node is allowed - paramNames []string // only-names - childrenNodes Nodes - handlers context.Handlers - root bool - rootWildcard bool // if it's a wildcard {path} type on root, it should allow everything but it is not conflicts with - // any other static or dynamic or wildcard paths if exists on other nodes. -} - -// ErrDublicate returnned from `Add` when two or more routes have the same registered path. -var ErrDublicate = errors.New("two or more routes have the same registered path") - -/// TODO: clean up needed until v8.5 - -// Add adds a node to the tree, returns an ErrDublicate error on failure. -func (nodes *Nodes) Add(routeName string, path string, handlers context.Handlers) error { - // println("[Add] adding path: " + path) - // resolve params and if that node should be added as root - var params []string - var paramStart, paramEnd int - for { - paramStart = strings.IndexByte(path[paramEnd:], ':') - if paramStart == -1 { - break - } - paramStart += paramEnd - paramStart++ - paramEnd = strings.IndexByte(path[paramStart:], '/') - - if paramEnd == -1 { - params = append(params, path[paramStart:]) - path = path[:paramStart] - break - } - paramEnd += paramStart - params = append(params, path[paramStart:paramEnd]) - path = path[:paramStart] + path[paramEnd:] - paramEnd -= paramEnd - paramStart - } - - var p []int - for i := 0; i < len(path); i++ { - idx := strings.IndexByte(path[i:], ':') - if idx == -1 { - break - } - p = append(p, idx+i) - i = idx + i - } - - for _, idx := range p { - // print("-2 nodes.Add: path: " + path + " params len: ") - // println(len(params)) - if err := nodes.add(routeName, path[:idx], nil, nil, true); err != nil { - return err - } - // print("-1 nodes.Add: path: " + path + " params len: ") - // println(len(params)) - if nidx := idx + 1; len(path) > nidx { - if err := nodes.add(routeName, path[:nidx], nil, nil, true); err != nil { - return err - } - } - } - - // print("nodes.Add: path: " + path + " params len: ") - // println(len(params)) - if err := nodes.add(routeName, path, params, handlers, true); err != nil { - return err - } - - // prioritize by static path remember, they were already sorted by subdomains too. - nodes.prioritize() - return nil -} - -func (nodes *Nodes) add(routeName, path string, paramNames []string, handlers context.Handlers, root bool) (err error) { - // println("[add] route name: " + routeName) - // println("[add] adding path: " + path) - - // wraia etsi doulevei ara - // na to kanw na exei to node to diko tou wildcard parameter name - // kai sto telos na pernei auto, me vasi to *paramname - // alla edw mesa 9a ginete register vasi tou last / - - // set the wildcard param name to the root and its children. - wildcardIdx := strings.IndexByte(path, '*') - wildcardParamName := "" - if wildcardIdx > 0 && len(paramNames) == 0 { // 27 Oct comment: && len(paramNames) == 0 { - wildcardParamName = path[wildcardIdx+1:] - path = path[0:wildcardIdx-1] + "/" // replace *paramName with single slash - - // if path[len(path)-1] == '/' { - // if root wildcard, then add it as it's and return - rootWildcard := path == "/" - if rootWildcard { - path += "/" // if root wildcard, then do it like "//" instead of simple "/" - } - - n := &node{ - rootWildcard: rootWildcard, - s: path, - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - root: root, - } - *nodes = append(*nodes, n) - // println("1. nodes.Add path: " + path) - return - - } - -loop: - for _, n := range *nodes { - if n.rootWildcard { - continue - } - - if len(n.paramNames) == 0 && n.wildcardParamName != "" { - continue - } - - minlen := len(n.s) - if len(path) < minlen { - minlen = len(path) - } - - for i := 0; i < minlen; i++ { - if n.s[i] == path[i] { - continue - } - if i == 0 { - continue loop - } - - *n = node{ - s: n.s[:i], - childrenNodes: Nodes{ - { - s: n.s[i:], - routeName: n.routeName, - wildcardParamName: n.wildcardParamName, // wildcardParamName - paramNames: n.paramNames, - childrenNodes: n.childrenNodes, - handlers: n.handlers, - }, - { - s: path[i:], - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - }, - }, - root: n.root, - } - - // println("2. change n and return " + n.s[:i] + " and " + path[i:]) - return - } - - if len(path) < len(n.s) { - // println("3. change n and return | n.s[:len(path)] = " + n.s[:len(path)-1] + " and child: " + n.s[len(path)-1:]) - - *n = node{ - s: n.s[:len(path)], - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - childrenNodes: Nodes{ - { - s: n.s[len(path):], - routeName: n.routeName, - wildcardParamName: n.wildcardParamName, // wildcardParamName - paramNames: n.paramNames, - childrenNodes: n.childrenNodes, - handlers: n.handlers, - }, - }, - handlers: handlers, - root: n.root, - } - - return - } - - if len(path) > len(n.s) { - if n.wildcardParamName != "" { - n := &node{ - s: path, - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - root: root, - } - // println("3.5. nodes.Add path: " + n.s) - *nodes = append(*nodes, n) - return - } - - pathToAdd := path[len(n.s):] - // println("4. nodes.Add route name: " + routeName) - // println("4. nodes.Add path: " + pathToAdd) - err = n.childrenNodes.add(routeName, pathToAdd, paramNames, handlers, false) - return err - } - - if len(handlers) == 0 { // missing handlers - return nil - } - - if len(n.handlers) > 0 { // n.handlers already setted - return ErrDublicate - } - n.paramNames = paramNames - n.handlers = handlers - n.routeName = routeName - return - } - - // START - // Author's note: - // 27 Oct 2017; fixes s|i|l+static+p - // without breaking the current tests. - if wildcardIdx > 0 { - wildcardParamName = path[wildcardIdx+1:] - path = path[0:wildcardIdx-1] + "/" - } - // END - - n := &node{ - s: path, - routeName: routeName, - wildcardParamName: wildcardParamName, - paramNames: paramNames, - handlers: handlers, - root: root, - } - *nodes = append(*nodes, n) - - // println("5. node add on path: " + path + " n.s: " + n.s + " wildcard param: " + n.wildcardParamName) - return -} - -// Find resolves the path, fills its params -// and returns the registered to the resolved node's handlers. -func (nodes Nodes) Find(path string, params *context.RequestParams) (string, context.Handlers) { - n, paramValues := nodes.findChild(path, nil) - if n != nil { - // map the params, - // n.params are the param names - if len(paramValues) > 0 { - // println("-----------") - // print("param values returned len: ") - // println(len(paramValues)) - // println("first value is: " + paramValues[0]) - // print("n.paramNames len: ") - // println(len(n.paramNames)) - for i, name := range n.paramNames { - // println("setting param name: " + name + " = " + paramValues[i]) - params.Set(name, paramValues[i]) - } - // last is the wildcard, - // if paramValues are exceed from the registered param names. - // Note that n.wildcardParamName can be not empty but that doesn't meaning - // that it contains a wildcard path, so the check is required. - if len(paramValues) > len(n.paramNames) { - // println("len(paramValues) > len(n.paramNames)") - lastWildcardVal := paramValues[len(paramValues)-1] - // println("setting wildcard param name: " + n.wildcardParamName + " = " + lastWildcardVal) - params.Set(n.wildcardParamName, lastWildcardVal) - } - } - - return n.routeName, n.handlers - } - - return "", nil -} - -// Exists returns true if a node with that "path" exists, -// otherise false. -// -// We don't care about parameters here. -func (nodes Nodes) Exists(path string) bool { - n, _ := nodes.findChild(path, nil) - return n != nil && len(n.handlers) > 0 -} - -func (nodes Nodes) findChild(path string, params []string) (*node, []string) { - - for _, n := range nodes { - if n.s == ":" { - paramEnd := strings.IndexByte(path, '/') - if paramEnd == -1 { - if len(n.handlers) == 0 { - return nil, nil - } - return n, append(params, path) - } - return n.childrenNodes.findChild(path[paramEnd:], append(params, path[:paramEnd])) - } - - // println("n.s: " + n.s) - // print("n.childrenNodes len: ") - // println(len(n.childrenNodes)) - // print("n.root: ") - // println(n.root) - - // by runtime check of:, - // if n.s == "//" && n.root && n.wildcardParamName != "" { - // but this will slow down, so we have a static field on the node itself: - if n.rootWildcard { - // println("return from n.rootWildcard") - // single root wildcard - if len(path) < 2 { - // do not remove that, it seems useless but it's not, - // we had an error while production, this fixes that. - path = "/" + path - } - return n, append(params, path[1:]) - } - - // second conditional may be unnecessary - // because of the n.rootWildcard before, but do it. - if n.wildcardParamName != "" && len(path) > 2 { - // println("n has wildcard n.s: " + n.s + " on path: " + path) - // n.s = static/, path = static - - // println(n.s + " vs path: " + path) - - // we could have /other/ as n.s so - // we must do this check, remember: - // now wildcards live on their own nodes - if len(path) == len(n.s)-1 { - // then it's like: - // path = /other2 - // ns = /other2/ - if path == n.s[0:len(n.s)-1] { - return n, params - } - } - - // othwerwise path = /other2/dsadas - // ns= /other2/ - if strings.HasPrefix(path, n.s) { - if len(path) > len(n.s)+1 { - return n, append(params, path[len(n.s):]) // without slash - } - } - - } - - if !strings.HasPrefix(path, n.s) { - // fmt.Printf("---here root: %v, n.s: "+n.s+" and path: "+path+" is dynamic: %v , wildcardParamName: %s, children len: %v \n", n.root, n.isDynamic(), n.wildcardParamName, len(n.childrenNodes)) - // println(path + " n.s: " + n.s + " continue...") - continue - } - - if len(path) == len(n.s) { - if len(n.handlers) == 0 { - return nil, nil - } - return n, params - } - - child, childParamNames := n.childrenNodes.findChild(path[len(n.s):], params) - - // print("childParamNames len: ") - // println(len(childParamNames)) - - // if len(childParamNames) > 0 { - // println("childParamsNames[0] = " + childParamNames[0]) - // } - - if child == nil || len(child.handlers) == 0 { - if n.s[len(n.s)-1] == '/' && !(n.root && (n.s == "/" || len(n.childrenNodes) > 0)) { - if len(n.handlers) == 0 { - return nil, nil - } - - // println("if child == nil.... | n.s = " + n.s) - // print("n.paramNames len: ") - // println(n.paramNames) - // print("n.wildcardParamName is: ") - // println(n.wildcardParamName) - // print("return n, append(params, path[len(n.s) | params: ") - // println(path[len(n.s):]) - return n, append(params, path[len(n.s):]) - } - - continue - } - - return child, childParamNames - } - return nil, nil -} - -// childLen returns all the children's and their children's length. -func (n *node) childLen() (i int) { - for _, n := range n.childrenNodes { - i++ - i += n.childLen() - } - return -} - -func (n *node) isDynamic() bool { - return n.s == ":" || n.wildcardParamName != "" || n.rootWildcard -} - -// prioritize sets the static paths first. -func (nodes Nodes) prioritize() { - sort.Slice(nodes, func(i, j int) bool { - if nodes[i].isDynamic() { - return false - } - if nodes[j].isDynamic() { - return true - } - - return nodes[i].childLen() > nodes[j].childLen() - }) - - for _, n := range nodes { - n.childrenNodes.prioritize() - } -} diff --git a/core/router/party.go b/core/router/party.go index 8e8b56bd83..5d462392f6 100644 --- a/core/router/party.go +++ b/core/router/party.go @@ -3,7 +3,7 @@ package router import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" ) // Party is just a group joiner of routes which have the same prefix and share same middleware(s) also. @@ -18,11 +18,11 @@ type Party interface { GetRelPath() string // GetReporter returns the reporter for adding errors GetReporter() *errors.Reporter - // Macros returns the macro map which is responsible - // to register custom macro functions for all routes. + // Macros returns the macro collection that is responsible + // to register custom macros with their own parameter types and their macro functions for all routes. // // Learn more at: https://github.com/kataras/iris/tree/master/_examples/routing/dynamic-path - Macros() *macro.Map + Macros() *macro.Macros // Party groups routes which may have the same prefix and share same handlers, // returns that new rich subrouter. @@ -110,10 +110,10 @@ type Party interface { // otherwise use `Party` which can handle many paths with different handlers and middlewares. // // Usage: - // app.HandleMany(iris.MethodGet, "/user /user/{id:int} /user/me", userHandler) + // app.HandleMany(iris.MethodGet, "/user /user/{id:uint64} /user/me", userHandler) // At the other side, with `Handle` we've had to write: // app.Handle(iris.MethodGet, "/user", userHandler) - // app.Handle(iris.MethodGet, "/user/{id:int}", userHandler) + // app.Handle(iris.MethodGet, "/user/{id:uint64}", userHandler) // app.Handle(iris.MethodGet, "/user/me", userHandler) // // This method is used behind the scenes at the `Controller` function diff --git a/core/router/path.go b/core/router/path.go index 3a471f9023..0a8b4014b7 100644 --- a/core/router/path.go +++ b/core/router/path.go @@ -7,15 +7,9 @@ import ( "strings" "github.com/kataras/iris/core/netutil" - "github.com/kataras/iris/core/router/macro/interpreter/lexer" -) - -const ( - // ParamStart the character in string representation where the underline router starts its dynamic named parameter. - ParamStart = ":" - // WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard - // path parameter. - WildcardParamStart = "*" + "github.com/kataras/iris/macro" + "github.com/kataras/iris/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/lexer" ) // Param receives a parameter name prefixed with the ParamStart symbol. @@ -31,6 +25,26 @@ func WildcardParam(name string) string { return prefix(name, WildcardParamStart) } +func convertMacroTmplToNodePath(tmpl macro.Template) string { + routePath := tmpl.Src + if len(routePath) > 1 && routePath[len(routePath)-1] == '/' { + routePath = routePath[0 : len(routePath)-1] // remove any last "/" + } + + // if it has started with {} and it's valid + // then the tmpl.Params will be filled, + // so no any further check needed. + for _, p := range tmpl.Params { + if ast.IsTrailing(p.Type) { + routePath = strings.Replace(routePath, p.Src, WildcardParam(p.Name), 1) + } else { + routePath = strings.Replace(routePath, p.Src, Param(p.Name), 1) + } + } + + return routePath +} + func prefix(s string, prefix string) string { if !strings.HasPrefix(s, prefix) { return prefix + s diff --git a/core/router/path_test.go b/core/router/path_test.go index 66ef283bac..2c26c6da54 100644 --- a/core/router/path_test.go +++ b/core/router/path_test.go @@ -27,8 +27,8 @@ func TestCleanPath(t *testing.T) { "/total/{year:string regexp(\\d{4})}/more/{s:string regexp(\\d{7})}"}, {"/single_no_params", "/single_no_params"}, - {"/single/{id:int}", - "/single/{id:int}"}, + {"/single/{id:uint64}", + "/single/{id:uint64}"}, } for i, tt := range tests { @@ -45,8 +45,10 @@ func TestSplitPath(t *testing.T) { }{ {"/v2/stores/{id:string format(uuid)} /v3", []string{"/v2/stores/{id:string format(uuid)}", "/v3"}}, - {"/user/{id:int} /admin/{id:int}", - []string{"/user/{id:int}", "/admin/{id:int}"}}, + {"/user/{id:uint64} /admin/{id:uint64}", + []string{"/user/{id:uint64}", "/admin/{id:uint64}"}}, + {"/users/{id:int} /admins/{id:int64}", + []string{"/users/{id:int}", "/admins/{id:int64}"}}, {"/user /admin", []string{"/user", "/admin"}}, {"/single_no_params", diff --git a/core/router/route.go b/core/router/route.go index 6c4918f157..132eb7c728 100644 --- a/core/router/route.go +++ b/core/router/route.go @@ -5,18 +5,19 @@ import ( "strings" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/router/macro" + "github.com/kataras/iris/macro" + "github.com/kataras/iris/macro/handler" ) // Route contains the information about a registered Route. // If any of the following fields are changed then the // caller should Refresh the router. type Route struct { - Name string `json:"name"` // "userRoute" - Method string `json:"method"` // "GET" - methodBckp string // if Method changed to something else (which is possible at runtime as well, via RefreshRouter) then this field will be filled with the old one. - Subdomain string `json:"subdomain"` // "admin." - tmpl *macro.Template // Tmpl().Src: "/api/user/{id:int}" + Name string `json:"name"` // "userRoute" + Method string `json:"method"` // "GET" + methodBckp string // if Method changed to something else (which is possible at runtime as well, via RefreshRouter) then this field will be filled with the old one. + Subdomain string `json:"subdomain"` // "admin." + tmpl macro.Template // Tmpl().Src: "/api/user/{id:uint64}" // temp storage, they're appended to the Handlers on build. // Execution happens before Handlers, can be empty. beginHandlers context.Handlers @@ -39,16 +40,19 @@ type Route struct { // It parses the path based on the "macros", // handlers are being changed to validate the macros at serve time, if needed. func NewRoute(method, subdomain, unparsedPath, mainHandlerName string, - handlers context.Handlers, macros *macro.Map) (*Route, error) { + handlers context.Handlers, macros macro.Macros) (*Route, error) { tmpl, err := macro.Parse(unparsedPath, macros) if err != nil { return nil, err } - path, handlers, err := compileRoutePathAndHandlers(handlers, tmpl) - if err != nil { - return nil, err + path := convertMacroTmplToNodePath(tmpl) + // prepend the macro handler to the route, now, + // right before the register to the tree, so APIBuilder#UseGlobal will work as expected. + if handler.CanMakeHandler(tmpl) { + macroEvaluatorHandler := handler.MakeHandler(tmpl) + handlers = append(context.Handlers{macroEvaluatorHandler}, handlers...) } path = cleanPath(path) // maybe unnecessary here but who cares in this moment @@ -152,7 +156,18 @@ func (r Route) String() string { // via Tmpl().Src, Route.Path is the path // converted to match the underline router's specs. func (r Route) Tmpl() macro.Template { - return *r.tmpl + return r.tmpl +} + +// RegisteredHandlersLen returns the end-developer's registered handlers, all except the macro evaluator handler +// if was required by the build process. +func (r Route) RegisteredHandlersLen() int { + n := len(r.Handlers) + if handler.CanMakeHandler(r.tmpl) { + n-- + } + + return n } // IsOnline returns true if the route is marked as "online" (state). @@ -198,7 +213,7 @@ func formatPath(path string) string { // StaticPath returns the static part of the original, registered route path. // if /user/{id} it will return /user -// if /user/{id}/friend/{friendid:int} it will return /user too +// if /user/{id}/friend/{friendid:uint64} it will return /user too // if /assets/{filepath:path} it will return /assets. func (r Route) StaticPath() string { src := r.tmpl.Src @@ -242,7 +257,8 @@ func (r Route) Trace() string { printfmt += fmt.Sprintf(" %s", r.Subdomain) } printfmt += fmt.Sprintf(" %s ", r.Tmpl().Src) - if l := len(r.Handlers); l > 1 { + + if l := r.RegisteredHandlersLen(); l > 1 { printfmt += fmt.Sprintf("-> %s() and %d more", r.MainHandlerName, l-1) } else { printfmt += fmt.Sprintf("-> %s()", r.MainHandlerName) diff --git a/core/router/router.go b/core/router/router.go index 50526395b5..f4e9840d76 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -31,7 +31,7 @@ func NewRouter() *Router { return &Router{} } // RefreshRouter re-builds the router. Should be called when a route's state // changed (i.e Method changed at serve-time). func (router *Router) RefreshRouter() error { - return router.BuildRouter(router.cPool, router.requestHandler, router.routesProvider) + return router.BuildRouter(router.cPool, router.requestHandler, router.routesProvider, true) } // BuildRouter builds the router based on @@ -41,7 +41,7 @@ func (router *Router) RefreshRouter() error { // its wrapper. // // Use of RefreshRouter to re-build the router if needed. -func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHandler, routesProvider RoutesProvider) error { +func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHandler, routesProvider RoutesProvider, force bool) error { if requestHandler == nil { return errors.New("router: request handler is nil") @@ -60,9 +60,23 @@ func (router *Router) BuildRouter(cPool *context.Pool, requestHandler RequestHan defer router.mu.Unlock() // store these for RefreshRouter's needs. - router.cPool = cPool - router.requestHandler = requestHandler - router.routesProvider = routesProvider + if force { + router.cPool = cPool + router.requestHandler = requestHandler + router.routesProvider = routesProvider + } else { + if router.cPool == nil { + router.cPool = cPool + } + + if router.requestHandler == nil { + router.requestHandler = requestHandler + } + + if router.routesProvider == nil && routesProvider != nil { + router.routesProvider = routesProvider + } + } // the important router.mainHandler = func(w http.ResponseWriter, r *http.Request) { diff --git a/core/router/router_wildcard_root_test.go b/core/router/router_wildcard_root_test.go index 110192ebeb..c3190ce5a2 100644 --- a/core/router/router_wildcard_root_test.go +++ b/core/router/router_wildcard_root_test.go @@ -122,20 +122,25 @@ func TestRouterWildcardRootMany(t *testing.T) { func TestRouterWildcardRootManyAndRootStatic(t *testing.T) { var tt = []testRoute{ - // all routes will be handlded by "h" because we added wildcard to root, + // routes that may return 404 will be handled by the below route ("h" handler) because we added wildcard to root, // this feature is very important and can remove noumerous of previous hacks on our apps. + // + // Static paths and parameters have priority over wildcard, all three types can be registered in the same path prefix. + // + // Remember, all of those routes are registered don't be tricked by the visual appearance of the below test blocks. {"GET", "/{p:path}", h, []testRouteRequest{ {"GET", "", "/other2almost/some", iris.StatusOK, same_as_request_path}, }}, {"GET", "/static/{p:path}", h, []testRouteRequest{ - {"GET", "", "/static", iris.StatusOK, same_as_request_path}, + {"GET", "", "/static", iris.StatusOK, same_as_request_path}, // HERE<- IF NOT FOUND THEN BACKWARDS TO WILDCARD IF THERE IS ONE, HMM. {"GET", "", "/static/something/here", iris.StatusOK, same_as_request_path}, }}, {"GET", "/", h, []testRouteRequest{ {"GET", "", "/", iris.StatusOK, same_as_request_path}, }}, {"GET", "/other/{paramother:path}", h2, []testRouteRequest{ - {"GET", "", "/other", iris.StatusForbidden, same_as_request_path}, + // OK and not h2 because of the root wildcard. + {"GET", "", "/other", iris.StatusOK, same_as_request_path}, {"GET", "", "/other/wildcard", iris.StatusForbidden, same_as_request_path}, {"GET", "", "/other/wildcard/here", iris.StatusForbidden, same_as_request_path}, }}, @@ -145,6 +150,7 @@ func TestRouterWildcardRootManyAndRootStatic(t *testing.T) { }}, {"GET", "/other2/static", h3, []testRouteRequest{ {"GET", "", "/other2/static", iris.StatusOK, prefix_static_path_following_by_request_path}, + // h2(Forbiddenn) instead of h3 OK because it will be handled by the /other2/{paramothersecond:path}'s handler which gives 403. {"GET", "", "/other2/staticed", iris.StatusForbidden, same_as_request_path}, }}, } @@ -165,6 +171,7 @@ func testTheRoutes(t *testing.T, tests []testRoute, debug bool) { // run the tests for _, tt := range tests { for _, req := range tt.requests { + // t.Logf("req: %s:%s\n", tt.method, tt.path) method := req.method if method == "" { method = tt.method diff --git a/core/router/trie.go b/core/router/trie.go new file mode 100644 index 0000000000..9e98efbb3a --- /dev/null +++ b/core/router/trie.go @@ -0,0 +1,268 @@ +package router + +import ( + "strings" + + "github.com/kataras/iris/context" +) + +const ( + // ParamStart the character in string representation where the underline router starts its dynamic named parameter. + ParamStart = ":" + // WildcardParamStart the character in string representation where the underline router starts its dynamic wildcard + // path parameter. + WildcardParamStart = "*" +) + +// An iris-specific identical version of the https://github.com/kataras/muxie version 1.0.0 released at 15 Oct 2018 +type trieNode struct { + parent *trieNode + + children map[string]*trieNode + hasDynamicChild bool // does one of the children contains a parameter or wildcard? + childNamedParameter bool // is the child a named parameter (single segmnet) + childWildcardParameter bool // or it is a wildcard (can be more than one path segments) ? + paramKeys []string // the param keys without : or *. + end bool // it is a complete node, here we stop and we can say that the node is valid. + key string // if end == true then key is filled with the original value of the insertion's key. + // if key != "" && its parent has childWildcardParameter == true, + // we need it to track the static part for the closest-wildcard's parameter storage. + staticKey string + + // insert data. + Handlers context.Handlers + RouteName string +} + +func newTrieNode() *trieNode { + n := new(trieNode) + return n +} + +func (tn *trieNode) hasChild(s string) bool { + return tn.getChild(s) != nil +} + +func (tn *trieNode) getChild(s string) *trieNode { + if tn.children == nil { + tn.children = make(map[string]*trieNode) + } + + return tn.children[s] +} + +func (tn *trieNode) addChild(s string, n *trieNode) { + if tn.children == nil { + tn.children = make(map[string]*trieNode) + } + + if _, exists := tn.children[s]; exists { + return + } + + n.parent = tn + tn.children[s] = n +} + +func (tn *trieNode) findClosestParentWildcardNode() *trieNode { + tn = tn.parent + for tn != nil { + if tn.childWildcardParameter { + return tn.getChild(WildcardParamStart) + } + + tn = tn.parent + } + + return nil +} + +func (tn *trieNode) String() string { + return tn.key +} + +type trie struct { + root *trieNode + + // if true then it will handle any path if not other parent wildcard exists, + // so even 404 (on http services) is up to it, see trie#insert. + hasRootWildcard bool + + method string + // subdomain is empty for default-hostname routes, + // ex: mysubdomain. + subdomain string +} + +func newTrie() *trie { + return &trie{ + root: newTrieNode(), + } +} + +const ( + pathSep = "/" + pathSepB = '/' +) + +func slowPathSplit(path string) []string { + if path == "/" { + return []string{"/"} + } + + return strings.Split(path, pathSep)[1:] +} + +func (tr *trie) insert(path, routeName string, handlers context.Handlers) { + input := slowPathSplit(path) + + n := tr.root + var paramKeys []string + + for _, s := range input { + c := s[0] + + if isParam, isWildcard := c == ParamStart[0], c == WildcardParamStart[0]; isParam || isWildcard { + n.hasDynamicChild = true + paramKeys = append(paramKeys, s[1:]) // without : or *. + + // if node has already a wildcard, don't force a value, check for true only. + if isParam { + n.childNamedParameter = true + s = ParamStart + } + + if isWildcard { + n.childWildcardParameter = true + s = WildcardParamStart + if tr.root == n { + tr.hasRootWildcard = true + } + } + } + + if !n.hasChild(s) { + child := newTrieNode() + n.addChild(s, child) + } + + n = n.getChild(s) + } + + n.RouteName = routeName + n.Handlers = handlers + n.paramKeys = paramKeys + n.key = path + n.end = true + + i := strings.Index(path, ParamStart) + if i == -1 { + i = strings.Index(path, WildcardParamStart) + } + if i == -1 { + i = len(n.key) + } + + n.staticKey = path[:i] +} + +func (tr *trie) search(q string, params *context.RequestParams) *trieNode { + end := len(q) + + if end == 0 || (end == 1 && q[0] == pathSepB) { + return tr.root.getChild(pathSep) + } + + n := tr.root + start := 1 + i := 1 + var paramValues []string + + for { + if i == end || q[i] == pathSepB { + if child := n.getChild(q[start:i]); child != nil { + n = child + } else if n.childNamedParameter { + n = n.getChild(ParamStart) + if ln := len(paramValues); cap(paramValues) > ln { + paramValues = paramValues[:ln+1] + paramValues[ln] = q[start:i] + } else { + paramValues = append(paramValues, q[start:i]) + } + } else if n.childWildcardParameter { + n = n.getChild(WildcardParamStart) + if ln := len(paramValues); cap(paramValues) > ln { + paramValues = paramValues[:ln+1] + paramValues[ln] = q[start:] + } else { + paramValues = append(paramValues, q[start:]) + } + break + } else { + n = n.findClosestParentWildcardNode() + if n != nil { + // means that it has :param/static and *wildcard, we go trhough the :param + // but the next path segment is not the /static, so go back to *wildcard + // instead of not found. + // + // Fixes: + // /hello/*p + // /hello/:p1/static/:p2 + // req: http://localhost:8080/hello/dsadsa/static/dsadsa => found + // req: http://localhost:8080/hello/dsadsa => but not found! + // and + // /second/wild/*p + // /second/wild/static/otherstatic/ + // req: /second/wild/static/otherstatic/random => but not found! + params.Set(n.paramKeys[0], q[len(n.staticKey):]) + return n + } + + return nil + } + + if i == end { + break + } + + i++ + start = i + continue + } + + i++ + } + + if n == nil || !n.end { + if n != nil { // we need it on both places, on last segment (below) or on the first unnknown (above). + if n = n.findClosestParentWildcardNode(); n != nil { + params.Set(n.paramKeys[0], q[len(n.staticKey):]) + return n + } + } + + if tr.hasRootWildcard { + // that's the case for root wildcard, tests are passing + // even without it but stick with it for reference. + // Note ote that something like: + // Routes: /other2/*myparam and /other2/static + // Reqs: /other2/staticed will be handled + // the /other2/*myparam and not the root wildcard, which is what we want. + // + n = tr.root.getChild(WildcardParamStart) + params.Set(n.paramKeys[0], q[1:]) + return n + } + + return nil + } + + for i, paramValue := range paramValues { + if len(n.paramKeys) > i { + params.Set(n.paramKeys[i], paramValue) + } + } + + return n +} diff --git a/deprecated.go b/deprecated.go deleted file mode 100644 index d44707b25f..0000000000 --- a/deprecated.go +++ /dev/null @@ -1 +0,0 @@ -package iris diff --git a/doc.go b/doc.go index 00a781aef7..6c8bb56794 100644 --- a/doc.go +++ b/doc.go @@ -35,11 +35,11 @@ Source code and other details for the project are available at GitHub: Current Version -10.7.0 +11.0.0 Installation -The only requirement is the Go Programming Language, at least version 1.8 but 1.10 and above is highly recommended. +The only requirement is the Go Programming Language, at least version 1.8 but 1.11.1 and above is highly recommended. $ go get -u github.com/kataras/iris @@ -119,7 +119,7 @@ Example code: usersRoutes := app.Party("/users", logThisMiddleware) { // Method GET: http://localhost:8080/users/42 - usersRoutes.Get("/{id:int min(1)}", getUserByID) + usersRoutes.Get("/{id:uint64 min(1)}", getUserByID) // Method POST: http://localhost:8080/users/create usersRoutes.Post("/create", createUser) } @@ -146,7 +146,7 @@ Example code: } func getUserByID(ctx iris.Context) { - userID := ctx.Params().Get("id") // Or convert directly using: .Values().GetInt/GetInt64 etc... + userID := ctx.Params().Get("id") // Or convert directly using: .Values().GetInt/GetUint64/GetInt64 etc... // your own db fetch here instead of user :=... user := User{Username: "username" + userID} @@ -489,7 +489,7 @@ Example code: users := app.Party("/users", myAuthMiddlewareHandler) // http://myhost.com/users/42/profile - users.Get("/{id:int}/profile", userProfileHandler) + users.Get("/{id:uint64}/profile", userProfileHandler) // http://myhost.com/users/messages/1 users.Get("/inbox/{id:int}", userMessageHandler) @@ -548,8 +548,8 @@ Example code: app.Get("/donate", donateHandler, donateFinishHandler) // Pssst, don't forget dynamic-path example for more "magic"! - app.Get("/api/users/{userid:int min(1)}", func(ctx iris.Context) { - userID, err := ctx.Params().GetInt("userid") + app.Get("/api/users/{userid:uint64 min(1)}", func(ctx iris.Context) { + userID, err := ctx.Params().GetUint64("userid") if err != nil { ctx.Writef("error while trying to parse userid parameter," + @@ -622,8 +622,8 @@ Example code: ctx.Writef("All users") }) // http://v1.localhost:8080/api/users/42 - usersAPI.Get("/{userid:int}", func(ctx iris.Context) { - ctx.Writef("user with id: %s", ctx.Params().Get("userid")) + usersAPI.Get("/{userid:uint64}", func(ctx iris.Context) { + ctx.Writef("user with id: %s", ctx.Params().GetUint64("userid")) }) } } @@ -709,23 +709,71 @@ Standard macro types for parameters: | {param:string} | +------------------------+ string type - anything + anything (single path segmnent) + + +-------------------------------+ + | {param:int} | + +-------------------------------+ + int type + -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch +------------------------+ - | {param:int} | + | {param:int8} | +------------------------+ - int type - only numbers (0-9) + int8 type + -128 to 127 +------------------------+ - | {param:long} | + | {param:int16} | + +------------------------+ + int16 type + -32768 to 32767 + + +------------------------+ + | {param:int32} | + +------------------------+ + int32 type + -2147483648 to 2147483647 + + +------------------------+ + | {param:int64} | +------------------------+ int64 type - only numbers (0-9) + -9223372036854775808 to 9223372036854775807 + + +------------------------+ + | {param:uint} | + +------------------------+ + uint type + 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32) +------------------------+ - | {param:boolean} | + | {param:uint8} | +------------------------+ + uint8 type + 0 to 255 + + +------------------------+ + | {param:uint16} | + +------------------------+ + uint16 type + 0 to 65535 + + +------------------------+ + | {param:uint32} | + +------------------------+ + uint32 type + 0 to 4294967295 + + +------------------------+ + | {param:uint64} | + +------------------------+ + uint64 type + 0 to 18446744073709551615 + + +---------------------------------+ + | {param:bool} or {param:boolean} | + +---------------------------------+ bool type only "1" or "t" or "T" or "TRUE" or "true" or "True" or "0" or "f" or "F" or "FALSE" or "false" or "False" @@ -751,8 +799,8 @@ Standard macro types for parameters: | {param:path} | +------------------------+ path type - anything, should be the last part, more than one path segment, - i.e: /path1/path2/path3 , ctx.Params().Get("param") == "/path1/path2/path3" + anything, should be the last part, can be more than one path segment, + i.e: "/test/*param" and request: "/test/path1/path2/path3" , ctx.Params().Get("param") == "path1/path2/path3" if type is missing then parameter's type is defaulted to string, so {param} == {param:string}. @@ -770,16 +818,18 @@ you are able to register your own too!. Register a named path parameter function: - app.Macros().Int.RegisterFunc("min", func(argument int) func(paramValue string) bool { - [...] - return true/false -> true means valid. + app.Macros().Get("int").RegisterFunc("min", func(argument int) func(paramValue int) bool { + return func(paramValue int) bool { + [...] + return true/false -> true means valid. + } }) at the func(argument ...) you can have any standard type, it will be validated before the server starts so don't care about performance here, the only thing it runs at serve time is the returning func(paramValue string) bool. {param:string equal(iris)} , "iris" will be the argument here: - app.Macros().String.RegisterFunc("equal", func(argument string) func(paramValue string) bool { + app.Macros().Get("string").RegisterFunc("equal", func(argument string) func(paramValue string) bool { return func(paramValue string){ return argument == paramValue } }) @@ -795,38 +845,34 @@ Example Code: // Let's register our first macro attached to int macro type. // "min" = the function // "minValue" = the argument of the function - // func(string) bool = the macro's path parameter evaluator, this executes in serve time when - // a user requests a path which contains the :int macro type with the min(...) macro parameter function. - app.Macros().Int.RegisterFunc("min", func(minValue int) func(string) bool { + // func() bool = the macro's path parameter evaluator, this executes in serve time when + // a user requests a path which contains the int macro type with the min(...) macro parameter function. + app.Macros().Get("int").RegisterFunc("min", func(minValue int) func(int) bool { // do anything before serve here [...] // at this case we don't need to do anything - return func(paramValue string) bool { - n, err := strconv.Atoi(paramValue) - if err != nil { - return false - } - return n >= minValue + return func(paramValue int) bool { + return paramValue >= minValue } }) // http://localhost:8080/profile/id>=1 // this will throw 404 even if it's found as route on : /profile/0, /profile/blabla, /profile/-1 // macro parameter functions are optional of course. - app.Get("/profile/{id:int min(1)}", func(ctx iris.Context) { + app.Get("/profile/{id:uint64 min(1)}", func(ctx iris.Context) { // second parameter is the error but it will always nil because we use macros, // the validaton already happened. - id, _ := ctx.Params().GetInt("id") + id, _ := ctx.Params().GetUint64("id") ctx.Writef("Hello id: %d", id) }) // to change the error code per route's macro evaluator: - app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx iris.Context) { - id, _ := ctx.Params().GetInt("id") - friendid, _ := ctx.Params().GetInt("friendid") + app.Get("/profile/{id:uint64 min(1)}/friends/{friendid:uint64 min(1) else 504}", func(ctx iris.Context) { + id, _ := ctx.Params().GetUint64("id") + friendid, _ := ctx.Params().GetUint64("friendid") ctx.Writef("Hello id: %d looking for friend id: ", id, friendid) }) // this will throw e 504 error code instead of 404 if all route's macros not passed. - // http://localhost:8080/game/a-zA-Z/level/0-9 + // http://localhost:8080/game/a-zA-Z/level/42 // remember, alphabetical is lowercase or uppercase letters only. app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) { ctx.Writef("name: %s | level: %s", ctx.Params().Get("name"), ctx.Params().Get("level")) @@ -855,11 +901,6 @@ Example Code: } - -A path parameter name should contain only alphabetical letters, symbols, containing '_' and numbers are NOT allowed. -If route failed to be registered, the app will panic without any warnings -if you didn't catch the second return value(error) on .Handle/.Get.... - Last, do not confuse ctx.Values() with ctx.Params(). Path parameter's values goes to ctx.Params() and context's local storage that can be used to communicate between handlers and middleware(s) goes to diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000..8711dc1227 --- /dev/null +++ b/go.mod @@ -0,0 +1,68 @@ +module github.com/kataras/iris + +require ( + github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 // indirect + github.com/BurntSushi/toml v0.3.1 + github.com/Joker/jade v0.7.0 + github.com/Shopify/goreferrer v0.0.0-20180807163728-b9777dc9f9cc + github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f // indirect + github.com/aymerick/raymond v2.0.2+incompatible + github.com/boltdb/bolt v1.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgraph-io/badger v1.5.4 + github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 // indirect + github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 + github.com/etcd-io/bbolt v1.3.0 + github.com/fatih/structs v1.0.0 + github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0 + github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d // indirect + github.com/gomodule/redigo v2.0.0+incompatible + github.com/google/go-querystring v1.0.0 // indirect + github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c // indirect + github.com/gorilla/websocket v1.4.0 + github.com/imkira/go-interpol v1.1.0 // indirect + github.com/iris-contrib/formBinder v0.0.0-20171010160137-ad9fb86c356f + github.com/iris-contrib/go.uuid v2.0.0+incompatible + github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce + github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0 + github.com/json-iterator/go v1.1.5 + github.com/jtolds/gls v4.2.1+incompatible // indirect + github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 // indirect + github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect + github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect + github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect + github.com/kataras/golog v0.0.0-20180321173939-03be10146386 + github.com/kataras/pio v0.0.0-20180511174041-a9733b5b6b83 // indirect + github.com/klauspost/compress v1.4.0 + github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e // indirect + github.com/mattn/go-colorable v0.0.9 // indirect + github.com/mattn/go-isatty v0.0.4 // indirect + github.com/microcosm-cc/bluemonday v1.0.1 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b // indirect + github.com/onsi/gomega v1.4.2 // indirect + github.com/pkg/errors v0.8.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/ryanuber/columnize v2.1.0+incompatible + github.com/sergi/go-diff v1.0.0 // indirect + github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect + github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect + github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect + github.com/stretchr/testify v1.2.2 // indirect + github.com/valyala/bytebufferpool v1.0.0 + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 // indirect + github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect + github.com/yudai/gojsondiff v1.0.0 // indirect + github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + github.com/yudai/pp v2.0.1+incompatible // indirect + golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 + golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 // indirect + golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 // indirect + gopkg.in/ini.v1 v1.38.3 // indirect + gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect + gopkg.in/russross/blackfriday.v2 v2.0.0+incompatible + gopkg.in/yaml.v2 v2.2.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000000..40e4805455 --- /dev/null +++ b/go.sum @@ -0,0 +1,148 @@ +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7 h1:PqzgE6kAMi81xWQA2QIVxjWkFHptGgC547vchpUbtFo= +github.com/AndreasBriese/bbloom v0.0.0-20180913140656-343706a395b7/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Joker/jade v0.7.0 h1:9sEpF/GAfkn0D0n+x8/SVGpxvdjIStsyPaTvtXW6z/U= +github.com/Joker/jade v0.7.0/go.mod h1:R1kvvouJogE6SnKqO5Qw3j2rCE2T9HjIWaFeSET/qMQ= +github.com/Shopify/goreferrer v0.0.0-20180807163728-b9777dc9f9cc h1:zZYkIbeMNcH1lhztdVxy4+Ykk8NoMhqUfSigsrT/x7Y= +github.com/Shopify/goreferrer v0.0.0-20180807163728-b9777dc9f9cc/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM= +github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/aymerick/raymond v2.0.2+incompatible h1:VEp3GpgdAnv9B2GFyTvqgcKvY+mfKMjPOA3SbKLtnU0= +github.com/aymerick/raymond v2.0.2+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= +github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.5.4 h1:gVTrpUTbbr/T24uvoCaqY2KSHfNLVGm0w+hbee2HMeg= +github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= +github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102 h1:afESQBXJEnj3fu+34X//E8Wg3nEbMJxJkwSc0tPePK0= +github.com/dgryski/go-farm v0.0.0-20180109070241-2de33835d102/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/etcd-io/bbolt v1.3.0 h1:ec0U3x11Mk69A8YwQyZEhNaUqHkQSv2gDR3Bioz5DfU= +github.com/etcd-io/bbolt v1.3.0/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fatih/structs v1.0.0 h1:BrX964Rv5uQ3wwS+KRUAJCBBw5PQmgJfJ6v4yly5QwU= +github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0 h1:ZHx2BEERvWkuwuE7qWN9TuRxucHDH2JrsvneZjVJfo0= +github.com/flosch/pongo2 v0.0.0-20180809100617-24195e6d38b0/go.mod h1:rE0ErqqBaMcp9pzj8JxV1GcfDBpuypXYxlR1c37AUwg= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d h1:oYXrtNhqNKL1dVtKdv8XUq5zqdGVFNQ0/4tvccXZOLM= +github.com/gavv/monotime v0.0.0-20171021193802-6f8212e8d10d/go.mod h1:vmp8DIyckQMXOPl0AQVHt+7n5h7Gb7hS6CUydiV8QeA= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= +github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/iris-contrib/formBinder v0.0.0-20171010160137-ad9fb86c356f h1:WgD6cqCSncBgkftw34mSPlMKU5JgoruAomW/SJtRrGU= +github.com/iris-contrib/formBinder v0.0.0-20171010160137-ad9fb86c356f/go.mod h1:i8kTYUOEstd/S8TG0ChTXQdf4ermA/e8vJX0+QruD9w= +github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce h1:q8Ka/exfHNgK7izJE+aUOZd7KZXJ7oQbnJWiZakEiMo= +github.com/iris-contrib/httpexpect v0.0.0-20180314041918-ebe99fcebbce/go.mod h1:VER17o2JZqquOx41avolD/wMGQSFEFBKWmhag9/RQRY= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0 h1:Kyp9KiXwsyZRTeoNjgVCrWks7D8ht9+kg6yCjh8K97o= +github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI= +github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/juju/errors v0.0.0-20180806074554-22422dad46e1 h1:wnhMXidtb70kDZCeLt/EfsVtkXS5c8zLnE9y/6DIRAU= +github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= +github.com/juju/loggo v0.0.0-20180524022052-584905176618 h1:MK144iBQF9hTSwBW/9eJm034bVoG30IshVm688T2hi8= +github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 h1:WQM1NildKThwdP7qWrNAFGzp4ijNLw8RlgENkaI4MJs= +github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kataras/golog v0.0.0-20180321173939-03be10146386 h1:VT6AeCHO/mc+VedKBMhoqb5eAK8B1i9F6nZl7EGlHvA= +github.com/kataras/golog v0.0.0-20180321173939-03be10146386/go.mod h1:PcaEvfvhGsqwXZ6S3CgCbmjcp+4UDUh2MIfF2ZEul8M= +github.com/kataras/pio v0.0.0-20180511174041-a9733b5b6b83 h1:NoJ+fI58ptwrPc1blX116i+5xWGAY/2TJww37AN8X54= +github.com/kataras/pio v0.0.0-20180511174041-a9733b5b6b83/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0= +github.com/klauspost/compress v1.4.0 h1:8nsMz3tWa9SWWPL60G1V6CUsf4lLjWLTNEtibhe8gh8= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e h1:+lIPJOWl+jSiJOc70QXJ07+2eg2Jy2EC7Mi11BWujeM= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b h1:Pip12xNtMvEFUBF4f8/b5yRXj94LLrNdLWELfOr2KcY= +github.com/moul/http2curl v0.0.0-20170919181001-9ac6cf4d929b/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/ryanuber/columnize v2.1.0+incompatible h1:j1Wcmh8OrK4Q7GXY+V7SVSY8nUWQxHW5TkBe7YUl+2s= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609 h1:BcMExZAULPkihVZ7UJXK7t8rwGqisXFw75tILnafhBY= +github.com/xeipuuv/gojsonschema v0.0.0-20180816142147-da425ebb7609/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332 h1:hvQVdF6P9DX4OiKA5tpehlG6JsgzmyQiThG7q5Bn3UQ= +golang.org/x/crypto v0.0.0-20180927165925-5295e8364332/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 h1:dgd4x4kJt7G4k4m93AYLzM8Ni6h2qLTfh9n9vXJT3/0= +golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611 h1:O33LKL7WyJgjN9CvxfTIomjIClbd/Kq86/iipowHQU0= +golang.org/x/sys v0.0.0-20180928133829-e4b3c5e90611/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.38.3 h1:ourkRZgR6qjJYoec9lYhX4+nuN1tEbV34dQEQ3IRk9U= +gopkg.in/ini.v1 v1.38.3/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= +gopkg.in/russross/blackfriday.v2 v2.0.0+incompatible h1:l1Mna0cVh8WlpyB8uFtc2c+5cdvrI5CDyuwTgIChojI= +gopkg.in/russross/blackfriday.v2 v2.0.0+incompatible/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/hero/di.go b/hero/di.go index 4244cfa0a9..d50665145b 100644 --- a/hero/di.go +++ b/hero/di.go @@ -8,6 +8,17 @@ import ( func init() { di.DefaultHijacker = func(fieldOrFuncInput reflect.Type) (*di.BindObject, bool) { + // if IsExpectingStore(fieldOrFuncInput) { + // return &di.BindObject{ + // Type: memstoreTyp, + // BindType: di.Dynamic, + // ReturnValue: func(ctxValue []reflect.Value) reflect.Value { + // // return ctxValue[0].MethodByName("Params").Call(di.EmptyIn)[0] + // return ctxValue[0].MethodByName("Params").Call(di.EmptyIn)[0].Field(0) // the Params' memstore.Store. + // }, + // }, true + // } + if !IsContext(fieldOrFuncInput) { return nil, false } diff --git a/hero/di/func.go b/hero/di/func.go index da81d6f320..3c417d8f0c 100644 --- a/hero/di/func.go +++ b/hero/di/func.go @@ -132,9 +132,8 @@ func (s *FuncInjector) addValue(inputIndex int, value reflect.Value) bool { } if b.IsAssignable(inTyp) { - // println(inTyp.String() + " is assignable to " + val.Type().String()) // fmt.Printf("binded input index: %d for type: %s and value: %v with pointer: %v\n", - // i, b.Type.String(), value.String(), val.Pointer()) + // i, b.Type.String(), inTyp.String(), inTyp.Pointer()) s.inputs = append(s.inputs, &targetFuncInput{ InputIndex: inputIndex, Object: &b, @@ -194,8 +193,8 @@ func (s *FuncInjector) Inject(in *[]reflect.Value, ctx ...reflect.Value) { args := *in for _, input := range s.inputs { input.Object.Assign(ctx, func(v reflect.Value) { - // fmt.Printf("assign input index: %d for value: %v\n", - // input.InputIndex, v.String()) + // fmt.Printf("assign input index: %d for value: %v of type: %s\n", + // input.InputIndex, v.String(), v.Type().Name()) args[input.InputIndex] = v }) diff --git a/hero/di/object.go b/hero/di/object.go index 392abcc587..385162bd87 100644 --- a/hero/di/object.go +++ b/hero/di/object.go @@ -101,6 +101,11 @@ func MakeReturnValue(fn reflect.Value, goodFunc TypeChecker) (func([]reflect.Val if !v.IsValid() { return zeroOutVal } + // if v.String() == "" { + // println("di/object.go: " + v.String()) + // // println("di/object.go: because it's interface{} it should be returned as: " + v.Elem().Type().String() + " and its value: " + v.Elem().Interface().(string)) + // return v.Elem() + // } return v } diff --git a/hero/di/reflect.go b/hero/di/reflect.go index 3b91ebf8d5..08fc7f2a7a 100644 --- a/hero/di/reflect.go +++ b/hero/di/reflect.go @@ -54,6 +54,7 @@ func IsZero(v reflect.Value) bool { // if can't interface, i.e return value from unexported field or method then return false return false } + zero := reflect.Zero(v.Type()) return v.Interface() == zero.Interface() } @@ -62,7 +63,10 @@ func IsZero(v reflect.Value) bool { // If "v" is a nil pointer, Indirect returns a zero Value. // If "v" is not a pointer, Indirect returns v. func IndirectValue(v reflect.Value) reflect.Value { - return reflect.Indirect(v) + if k := v.Kind(); k == reflect.Ptr { //|| k == reflect.Interface { + return v.Elem() + } + return v } // ValueOf returns the reflect.Value of "o". @@ -123,6 +127,11 @@ func equalTypes(got reflect.Type, expected reflect.Type) bool { // fmt.Printf("expected interface = %s and got to set on the arg is: %s\n", expected.String(), got.String()) return got.Implements(expected) } + + // if got.String() == "interface {}" { + // return true + // } + return false } @@ -161,7 +170,6 @@ func lookupFields(elemTyp reflect.Type, skipUnexported bool, parentIndex []int) for i, n := 0, elemTyp.NumField(); i < n; i++ { f := elemTyp.Field(i) - if IndirectType(f.Type).Kind() == reflect.Struct && !structFieldIgnored(f) { fields = append(fields, lookupFields(f.Type, skipUnexported, append(parentIndex, i))...) diff --git a/hero/handler.go b/hero/handler.go index bb427d772c..f0b4704408 100644 --- a/hero/handler.go +++ b/hero/handler.go @@ -5,13 +5,15 @@ import ( "reflect" "runtime" + "github.com/kataras/iris/context" "github.com/kataras/iris/hero/di" "github.com/kataras/golog" - "github.com/kataras/iris/context" ) -var contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() +var ( + contextTyp = reflect.TypeOf((*context.Context)(nil)).Elem() +) // IsContext returns true if the "inTyp" is a type of Context. func IsContext(inTyp reflect.Type) bool { @@ -70,7 +72,7 @@ func makeHandler(handler interface{}, values ...reflect.Value) (context.Handler, // is invalid when input len and values are not match // or their types are not match, we will take look at the // second statement, here we will re-try it - // using binders for path parameters: string, int, int64, bool. + // using binders for path parameters: string, int, int64, uint8, uint64, bool and so on. // We don't have access to the path, so neither to the macros here, // but in mvc. So we have to do it here. if valid = funcInjector.Retry(new(params).resolve); !valid { diff --git a/hero/param.go b/hero/param.go index 9a9f028f33..6941d5543c 100644 --- a/hero/param.go +++ b/hero/param.go @@ -19,50 +19,8 @@ type params struct { func (p *params) resolve(index int, typ reflect.Type) (reflect.Value, bool) { currentParamIndex := p.next - v, ok := resolveParam(currentParamIndex, typ) + v, ok := context.ParamResolverByTypeAndIndex(typ, currentParamIndex) p.next = p.next + 1 return v, ok } - -func resolveParam(currentParamIndex int, typ reflect.Type) (reflect.Value, bool) { - var fn interface{} - - switch typ.Kind() { - case reflect.Int: - fn = func(ctx context.Context) int { - // the second "ok/found" check is not necessary, - // because even if the entry didn't found on that "index" - // it will return an empty entry which will return the - // default value passed from the xDefault(def) because its `ValueRaw` is nil. - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.IntDefault(0) - return v - } - case reflect.Int64: - fn = func(ctx context.Context) int64 { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.Int64Default(0) - - return v - } - case reflect.Bool: - fn = func(ctx context.Context) bool { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - v, _ := entry.BoolDefault(false) - return v - } - case reflect.String: - fn = func(ctx context.Context) string { - entry, _ := ctx.Params().GetEntryAt(currentParamIndex) - // print(entry.Key + " with index of: ") - // print(currentParamIndex) - // println(" and value: " + entry.String()) - return entry.String() - } - default: - return reflect.Value{}, false - } - - return reflect.ValueOf(fn), true -} diff --git a/httptest/httptest.go b/httptest/httptest.go index b37f97505c..50253c5396 100644 --- a/httptest/httptest.go +++ b/httptest/httptest.go @@ -84,8 +84,7 @@ func New(t *testing.T, app *iris.Application, setters ...OptionSetter) *httpexpe setter.Set(conf) } - // set the logger or disable it (default) and disable the updater (for any case). - app.Configure(iris.WithoutVersionChecker) + // set the logger or disable it (default). app.Logger().SetLevel(conf.LogLevel) if err := app.Build(); err != nil { diff --git a/iris.go b/iris.go index f84accea30..5660847ba7 100644 --- a/iris.go +++ b/iris.go @@ -17,7 +17,6 @@ import ( // core packages, needed to build the application "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/host" - "github.com/kataras/iris/core/maintenance" "github.com/kataras/iris/core/netutil" "github.com/kataras/iris/core/router" // handlerconv conversions @@ -34,7 +33,7 @@ import ( var ( // Version is the current version number of the Iris Web Framework. - Version = maintenance.Version + Version = "11.0.0" ) // HTTP status codes as registered with IANA. @@ -58,13 +57,13 @@ const ( StatusAlreadyReported = 208 // RFC 5842, 7.1 StatusIMUsed = 226 // RFC 3229, 10.4.1 - StatusMultipleChoices = 300 // RFC 7231, 6.4.1 - StatusMovedPermanently = 301 // RFC 7231, 6.4.2 - StatusFound = 302 // RFC 7231, 6.4.3 - StatusSeeOther = 303 // RFC 7231, 6.4.4 - StatusNotModified = 304 // RFC 7232, 4.1 - StatusUseProxy = 305 // RFC 7231, 6.4.5 - _ = 306 // RFC 7231, 6.4.6 (Unused) + StatusMultipleChoices = 300 // RFC 7231, 6.4.1 + StatusMovedPermanently = 301 // RFC 7231, 6.4.2 + StatusFound = 302 // RFC 7231, 6.4.3 + StatusSeeOther = 303 // RFC 7231, 6.4.4 + StatusNotModified = 304 // RFC 7232, 4.1 + StatusUseProxy = 305 // RFC 7231, 6.4.5 + StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7 StatusPermanentRedirect = 308 // RFC 7538, 3 @@ -87,6 +86,7 @@ const ( StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4 StatusExpectationFailed = 417 // RFC 7231, 6.5.14 StatusTeapot = 418 // RFC 7168, 2.3.3 + StatusMisdirectedRequest = 421 // RFC 7540, 9.1.2 StatusUnprocessableEntity = 422 // RFC 4918, 11.2 StatusLocked = 423 // RFC 4918, 11.3 StatusFailedDependency = 424 // RFC 4918, 11.4 @@ -761,7 +761,7 @@ func (app *Application) Build() error { // create the request handler, the default routing handler routerHandler := router.NewDefaultHandler() - rp.Describe("router: %v", app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder)) + rp.Describe("router: %v", app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder, false)) // re-build of the router from outside can be done with; // app.RefreshRouter() } @@ -810,10 +810,6 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error { app.Configure(withOrWithout...) app.logger.Debugf("Application: running using %d host(s)", len(app.Hosts)+1) - if !app.config.DisableVersionChecker { - go maintenance.Start() - } - // this will block until an error(unless supervisor's DeferFlow called from a Task). err := serve(app) if err != nil { diff --git a/macro/AUTHORS b/macro/AUTHORS new file mode 100644 index 0000000000..0476475022 --- /dev/null +++ b/macro/AUTHORS @@ -0,0 +1,4 @@ +# This is the official list of Iris Macro and Route path interpreter authors for copyright +# purposes. + +Gerasimos Maropoulos diff --git a/macro/LICENSE b/macro/LICENSE new file mode 100644 index 0000000000..c73df4cefa --- /dev/null +++ b/macro/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2017-2018 The Iris Macro and Route path interpreter. 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 Iris 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. \ No newline at end of file diff --git a/macro/handler/handler.go b/macro/handler/handler.go new file mode 100644 index 0000000000..ea36c7d566 --- /dev/null +++ b/macro/handler/handler.go @@ -0,0 +1,56 @@ +// Package handler is the highest level module of the macro package which makes use the rest of the macro package, +// it is mainly used, internally, by the router package. +package handler + +import ( + "github.com/kataras/iris/context" + "github.com/kataras/iris/macro" +) + +// CanMakeHandler reports whether a macro template needs a special macro's evaluator handler to be validated +// before procceed to the next handler(s). +// If the template does not contain any dynamic attributes and a special handler is NOT required +// then it returns false. +func CanMakeHandler(tmpl macro.Template) (needsMacroHandler bool) { + if len(tmpl.Params) == 0 { + return + } + + // check if we have params like: {name:string} or {name} or {anything:path} without else keyword or any functions used inside these params. + // 1. if we don't have, then we don't need to add a handler before the main route's handler (as I said, no performance if macro is not really used) + // 2. if we don't have any named params then we don't need a handler too. + for _, p := range tmpl.Params { + if p.CanEval() { + // if at least one needs it, then create the handler. + needsMacroHandler = true + break + } + } + + return +} + +// MakeHandler creates and returns a handler from a macro template, the handler evaluates each of the parameters if necessary at all. +// If the template does not contain any dynamic attributes and a special handler is NOT required +// then it returns a nil handler. +func MakeHandler(tmpl macro.Template) context.Handler { + if !CanMakeHandler(tmpl) { + return nil + } + + return func(ctx context.Context) { + for _, p := range tmpl.Params { + if !p.CanEval() { + continue // allow. + } + + if !p.Eval(ctx.Params().Get(p.Name), &ctx.Params().Store) { + ctx.StatusCode(p.ErrCode) + ctx.StopExecution() + return + } + } + // if all passed, just continue. + ctx.Next() + } +} diff --git a/macro/handler/handler_test.go b/macro/handler/handler_test.go new file mode 100644 index 0000000000..0acc1e830a --- /dev/null +++ b/macro/handler/handler_test.go @@ -0,0 +1,41 @@ +package handler + +import ( + "testing" + + "github.com/kataras/iris/macro" +) + +func TestCanMakeHandler(t *testing.T) { + tests := []struct { + src string + needsHandler bool + }{ + {"/static/static", false}, + {"/{myparam}", false}, + {"/{myparam min(1)}", true}, + {"/{myparam else 500}", true}, + {"/{myparam else 404}", false}, + {"/{myparam:string}/static", false}, + {"/{myparam:int}", true}, + {"/static/{myparam:int}/static", true}, + {"/{myparam:path}", false}, + {"/{myparam:path min(1) else 404}", true}, + } + + availableMacros := *macro.Defaults + for i, tt := range tests { + tmpl, err := macro.Parse(tt.src, availableMacros) + if err != nil { + t.Fatalf("[%d] '%s' failed to be parsed: %v", i, tt.src, err) + } + + if got := CanMakeHandler(tmpl); got != tt.needsHandler { + if tt.needsHandler { + t.Fatalf("[%d] '%s' expected to be able to generate an evaluator handler instead of a nil one", i, tt.src) + } else { + t.Fatalf("[%d] '%s' should not need an evaluator handler", i, tt.src) + } + } + } +} diff --git a/macro/interpreter/ast/ast.go b/macro/interpreter/ast/ast.go new file mode 100644 index 0000000000..a3508373ac --- /dev/null +++ b/macro/interpreter/ast/ast.go @@ -0,0 +1,132 @@ +package ast + +type ( + // ParamType holds the necessary information about a parameter type for the parser to lookup for. + ParamType interface { + // The name of the parameter type. + // Indent should contain the characters for the parser. + Indent() string + } + + // MasterParamType if implemented and its `Master()` returns true then empty type param will be translated to this param type. + // Also its functions will be available to the rest of the macro param type's funcs. + // + // Only one Master is allowed. + MasterParamType interface { + ParamType + Master() bool + } + + // TrailingParamType if implemented and its `Trailing()` returns true + // then it should be declared at the end of a route path and can accept any trailing path segment as one parameter. + TrailingParamType interface { + ParamType + Trailing() bool + } + + // AliasParamType if implemeneted nad its `Alias()` returns a non-empty string + // then the param type can be written with that string literal too. + AliasParamType interface { + ParamType + Alias() string + } +) + +// IsMaster returns true if the "pt" param type is a master one. +func IsMaster(pt ParamType) bool { + p, ok := pt.(MasterParamType) + return ok && p.Master() +} + +// IsTrailing returns true if the "pt" param type is a marked as trailing, +// which should accept more than one path segment when in the end. +func IsTrailing(pt ParamType) bool { + p, ok := pt.(TrailingParamType) + return ok && p.Trailing() +} + +// HasAlias returns any alias of the "pt" param type. +// If alias is empty or not found then it returns false as its second output argument. +func HasAlias(pt ParamType) (string, bool) { + if p, ok := pt.(AliasParamType); ok { + alias := p.Alias() + return alias, len(alias) > 0 + } + + return "", false +} + +// GetMasterParamType accepts a list of ParamType and returns its master. +// If no `Master` specified: +// and len(paramTypes) > 0 then it will return the first one, +// otherwise it returns nil. +func GetMasterParamType(paramTypes ...ParamType) ParamType { + for _, pt := range paramTypes { + if IsMaster(pt) { + return pt + } + } + + if len(paramTypes) > 0 { + return paramTypes[0] + } + + return nil +} + +// LookupParamType accepts the string +// representation of a parameter type. +// Example: +// "string" +// "number" or "int" +// "long" or "int64" +// "uint8" +// "uint64" +// "boolean" or "bool" +// "alphabetical" +// "file" +// "path" +func LookupParamType(indentOrAlias string, paramTypes ...ParamType) (ParamType, bool) { + for _, pt := range paramTypes { + if pt.Indent() == indentOrAlias { + return pt, true + } + + if alias, has := HasAlias(pt); has { + if alias == indentOrAlias { + return pt, true + } + } + } + + return nil, false +} + +// ParamStatement is a struct +// which holds all the necessary information about a macro parameter. +// It holds its type (string, int, alphabetical, file, path), +// its source ({param:type}), +// its name ("param"), +// its attached functions by the user (min, max...) +// and the http error code if that parameter +// failed to be evaluated. +type ParamStatement struct { + Src string // the original unparsed source, i.e: {id:int range(1,5) else 404} + Name string // id + Type ParamType // int + Funcs []ParamFunc // range + ErrorCode int // 404 +} + +// ParamFunc holds the name of a parameter's function +// and its arguments (values) +// A param func is declared with: +// {param:int range(1,5)}, +// the range is the +// param function name +// the 1 and 5 are the two param function arguments +// range(1,5) +type ParamFunc struct { + Name string // range + Args []string // ["1","5"] +} diff --git a/core/router/macro/interpreter/lexer/lexer.go b/macro/interpreter/lexer/lexer.go similarity index 98% rename from core/router/macro/interpreter/lexer/lexer.go rename to macro/interpreter/lexer/lexer.go index 79f7111fc0..6a77260252 100644 --- a/core/router/macro/interpreter/lexer/lexer.go +++ b/macro/interpreter/lexer/lexer.go @@ -1,7 +1,7 @@ package lexer import ( - "github.com/kataras/iris/core/router/macro/interpreter/token" + "github.com/kataras/iris/macro/interpreter/token" ) // Lexer helps us to read/scan characters of a source and resolve their token types. @@ -179,7 +179,7 @@ func (l *Lexer) skipWhitespace() { func (l *Lexer) readIdentifier() string { pos := l.pos - for isLetter(l.ch) { + for isLetter(l.ch) || isDigit(l.ch) { l.readChar() } return l.input[pos:l.pos] diff --git a/core/router/macro/interpreter/lexer/lexer_test.go b/macro/interpreter/lexer/lexer_test.go similarity index 94% rename from core/router/macro/interpreter/lexer/lexer_test.go rename to macro/interpreter/lexer/lexer_test.go index dad919f3f1..848731e0f2 100644 --- a/core/router/macro/interpreter/lexer/lexer_test.go +++ b/macro/interpreter/lexer/lexer_test.go @@ -3,7 +3,7 @@ package lexer import ( "testing" - "github.com/kataras/iris/core/router/macro/interpreter/token" + "github.com/kataras/iris/macro/interpreter/token" ) func TestNextToken(t *testing.T) { diff --git a/core/router/macro/interpreter/parser/parser.go b/macro/interpreter/parser/parser.go similarity index 70% rename from core/router/macro/interpreter/parser/parser.go rename to macro/interpreter/parser/parser.go index 8a352a73f3..1a0b86086e 100644 --- a/core/router/macro/interpreter/parser/parser.go +++ b/macro/interpreter/parser/parser.go @@ -5,15 +5,19 @@ import ( "strconv" "strings" - "github.com/kataras/iris/core/router/macro/interpreter/ast" - "github.com/kataras/iris/core/router/macro/interpreter/lexer" - "github.com/kataras/iris/core/router/macro/interpreter/token" + "github.com/kataras/iris/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/lexer" + "github.com/kataras/iris/macro/interpreter/token" ) // Parse takes a route "fullpath" // and returns its param statements -// and an error on failure. -func Parse(fullpath string) ([]*ast.ParamStatement, error) { +// or an error if failed. +func Parse(fullpath string, paramTypes []ast.ParamType) ([]*ast.ParamStatement, error) { + if len(paramTypes) == 0 { + return nil, fmt.Errorf("empty parameter types") + } + pathParts := strings.SplitN(fullpath, "/", -1) p := new(ParamParser) statements := make([]*ast.ParamStatement, 0) @@ -28,14 +32,14 @@ func Parse(fullpath string) ([]*ast.ParamStatement, error) { } p.Reset(s) - stmt, err := p.Parse() + stmt, err := p.Parse(paramTypes) if err != nil { // exit on first error return nil, err } // if we have param type path but it's not the last path part - if stmt.Type == ast.ParamTypePath && i < len(pathParts)-1 { - return nil, fmt.Errorf("param type 'path' should be lived only inside the last path segment, but was inside: %s", s) + if ast.IsTrailing(stmt.Type) && i < len(pathParts)-1 { + return nil, fmt.Errorf("%s: parameter type \"%s\" should be registered to the very last of a path", s, stmt.Type.Indent()) } statements = append(statements, stmt) @@ -77,15 +81,18 @@ const ( // per-parameter. An error code can be setted via // the "else" keyword inside a route's path. DefaultParamErrorCode = 404 - // DefaultParamType when parameter type is missing use this param type, defaults to string - // and it should be remains unless earth split in two. - DefaultParamType = ast.ParamTypeString ) -func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) { - if t.Type == token.INT { - return ast.ParamFuncArgToInt(t.Literal) - } +// func parseParamFuncArg(t token.Token) (a ast.ParamFuncArg, err error) { +// if t.Type == token.INT { +// return ast.ParamFuncArgToInt(t.Literal) +// } +// // act all as strings here, because of int vs int64 vs uint64 and etc. +// return t.Literal, nil +// } + +func parseParamFuncArg(t token.Token) (a string, err error) { + // act all as strings here, because of int vs int64 vs uint64 and etc. return t.Literal, nil } @@ -96,14 +103,14 @@ func (p ParamParser) Error() error { return nil } -// Parse parses the p.src and returns its param statement +// Parse parses the p.src based on the given param types and returns its param statement // and an error on failure. -func (p *ParamParser) Parse() (*ast.ParamStatement, error) { +func (p *ParamParser) Parse(paramTypes []ast.ParamType) (*ast.ParamStatement, error) { l := lexer.New(p.src) stmt := &ast.ParamStatement{ ErrorCode: DefaultParamErrorCode, - Type: DefaultParamType, + Type: ast.GetMasterParamType(paramTypes...), Src: p.src, } @@ -120,14 +127,15 @@ func (p *ParamParser) Parse() (*ast.ParamStatement, error) { switch t.Type { case token.LBRACE: - // name, alphabetical and _, param names are not allowed to contain any number. + // can accept only letter or number only. nextTok := l.NextToken() stmt.Name = nextTok.Literal case token.COLON: - // type + // type can accept both letters and numbers but not symbols ofc. nextTok := l.NextToken() - paramType := ast.LookupParamType(nextTok.Literal) - if paramType == ast.ParamTypeUnExpected { + paramType, found := ast.LookupParamType(nextTok.Literal, paramTypes...) + + if !found { p.appendErr("[%d:%d] unexpected parameter type: %s", t.Start, t.End, nextTok.Literal) } stmt.Type = paramType @@ -143,25 +151,14 @@ func (p *ParamParser) Parse() (*ast.ParamStatement, error) { argValTok := l.NextDynamicToken() // catch anything from "(" and forward, until ")", because we need to // be able to use regex expression as a macro type's func argument too. - argVal, err := parseParamFuncArg(argValTok) - if err != nil { - p.appendErr("[%d:%d] expected param func argument to be a string or number but got %s", t.Start, t.End, argValTok.Literal) - continue - } // fmt.Printf("argValTok: %#v\n", argValTok) // fmt.Printf("argVal: %#v\n", argVal) - lastParamFunc.Args = append(lastParamFunc.Args, argVal) + lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal) case token.COMMA: argValTok := l.NextToken() - argVal, err := parseParamFuncArg(argValTok) - if err != nil { - p.appendErr("[%d:%d] expected param func argument to be a string or number type but got %s", t.Start, t.End, argValTok.Literal) - continue - } - - lastParamFunc.Args = append(lastParamFunc.Args, argVal) + lastParamFunc.Args = append(lastParamFunc.Args, argValTok.Literal) case token.RPAREN: stmt.Funcs = append(stmt.Funcs, lastParamFunc) lastParamFunc = ast.ParamFunc{} // reset diff --git a/core/router/macro/interpreter/parser/parser_test.go b/macro/interpreter/parser/parser_test.go similarity index 56% rename from core/router/macro/interpreter/parser/parser_test.go rename to macro/interpreter/parser/parser_test.go index b1ce0ad85b..5424f67565 100644 --- a/core/router/macro/interpreter/parser/parser_test.go +++ b/macro/interpreter/parser/parser_test.go @@ -6,9 +6,47 @@ import ( "strings" "testing" - "github.com/kataras/iris/core/router/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/ast" ) +type simpleParamType string + +func (pt simpleParamType) Indent() string { return string(pt) } + +type masterParamType simpleParamType + +func (pt masterParamType) Indent() string { return string(pt) } +func (pt masterParamType) Master() bool { return true } + +type wildcardParamType string + +func (pt wildcardParamType) Indent() string { return string(pt) } +func (pt wildcardParamType) Trailing() bool { return true } + +type aliasedParamType []string + +func (pt aliasedParamType) Indent() string { return string(pt[0]) } +func (pt aliasedParamType) Alias() string { return pt[1] } + +var ( + paramTypeString = masterParamType("string") + paramTypeNumber = aliasedParamType{"number", "int"} + paramTypeInt64 = aliasedParamType{"int64", "long"} + paramTypeUint8 = simpleParamType("uint8") + paramTypeUint64 = simpleParamType("uint64") + paramTypeBool = aliasedParamType{"bool", "boolean"} + paramTypeAlphabetical = simpleParamType("alphabetical") + paramTypeFile = simpleParamType("file") + paramTypePath = wildcardParamType("path") +) + +var testParamTypes = []ast.ParamType{ + paramTypeString, + paramTypeNumber, paramTypeInt64, paramTypeUint8, paramTypeUint64, + paramTypeBool, + paramTypeAlphabetical, paramTypeFile, paramTypePath, +} + func TestParseParamError(t *testing.T) { // fail illegalChar := '$' @@ -16,7 +54,7 @@ func TestParseParamError(t *testing.T) { input := "{id" + string(illegalChar) + "int range(1,5) else 404}" p := NewParamParser(input) - _, err := p.Parse() + _, err := p.Parse(testParamTypes) if err == nil { t.Fatalf("expecting not empty error on input '%s'", input) @@ -30,9 +68,9 @@ func TestParseParamError(t *testing.T) { // // success - input2 := "{id:int range(1,5) else 404}" + input2 := "{id:uint64 range(1,5) else 404}" p.Reset(input2) - _, err = p.Parse() + _, err = p.Parse(testParamTypes) if err != nil { t.Fatalf("expecting empty error on input '%s', but got: %s", input2, err.Error()) @@ -40,6 +78,16 @@ func TestParseParamError(t *testing.T) { // } +// mustLookupParamType same as `ast.LookupParamType` but it panics if "indent" does not match with a valid Param Type. +func mustLookupParamType(indent string) ast.ParamType { + pt, found := ast.LookupParamType(indent, testParamTypes...) + if !found { + panic("param type '" + indent + "' is not part of the provided param types") + } + + return pt +} + func TestParseParam(t *testing.T) { tests := []struct { valid bool @@ -49,27 +97,28 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{id:int min(1) max(5) else 404}", Name: "id", - Type: ast.ParamTypeInt, + Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "min", - Args: []ast.ParamFuncArg{1}}, + Args: []string{"1"}}, { Name: "max", - Args: []ast.ParamFuncArg{5}}, + Args: []string{"5"}}, }, ErrorCode: 404, }}, // 0 {true, ast.ParamStatement{ - Src: "{id:int range(1,5)}", + // test alias of int. + Src: "{id:number range(1,5)}", Name: "id", - Type: ast.ParamTypeInt, + Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "range", - Args: []ast.ParamFuncArg{1, 5}}, + Args: []string{"1", "5"}}, }, ErrorCode: 404, }}, // 1 @@ -77,11 +126,11 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{file:path contains(.)}", Name: "file", - Type: ast.ParamTypePath, + Type: mustLookupParamType("path"), Funcs: []ast.ParamFunc{ { Name: "contains", - Args: []ast.ParamFuncArg{"."}}, + Args: []string{"."}}, }, ErrorCode: 404, }}, // 2 @@ -89,35 +138,35 @@ func TestParseParam(t *testing.T) { ast.ParamStatement{ Src: "{username:alphabetical}", Name: "username", - Type: ast.ParamTypeAlphabetical, + Type: mustLookupParamType("alphabetical"), ErrorCode: 404, }}, // 3 {true, ast.ParamStatement{ Src: "{myparam}", Name: "myparam", - Type: ast.ParamTypeString, + Type: mustLookupParamType("string"), ErrorCode: 404, }}, // 4 {false, ast.ParamStatement{ Src: "{myparam_:thisianunexpected}", Name: "myparam_", - Type: ast.ParamTypeUnExpected, + Type: nil, ErrorCode: 404, }}, // 5 - {false, // false because it will give an error of unexpeced token type with value 2 + {true, ast.ParamStatement{ Src: "{myparam2}", - Name: "myparam", // expected "myparam" because we don't allow integers to the parameter names. - Type: ast.ParamTypeString, + Name: "myparam2", // we now allow integers to the parameter names. + Type: ast.GetMasterParamType(testParamTypes...), ErrorCode: 404, }}, // 6 {true, ast.ParamStatement{ Src: "{id:int even()}", // test param funcs without any arguments (LPAREN peek for RPAREN) Name: "id", - Type: ast.ParamTypeInt, + Type: mustLookupParamType("number"), Funcs: []ast.ParamFunc{ { Name: "even"}, @@ -126,25 +175,46 @@ func TestParseParam(t *testing.T) { }}, // 7 {true, ast.ParamStatement{ - Src: "{id:long else 404}", + Src: "{id:int64 else 404}", Name: "id", - Type: ast.ParamTypeLong, + Type: mustLookupParamType("int64"), ErrorCode: 404, }}, // 8 {true, ast.ParamStatement{ - Src: "{has:boolean else 404}", - Name: "has", - Type: ast.ParamTypeBoolean, + Src: "{id:long else 404}", // backwards-compatible test. + Name: "id", + Type: mustLookupParamType("int64"), ErrorCode: 404, }}, // 9 + {true, + ast.ParamStatement{ + Src: "{id:long else 404}", + Name: "id", + Type: mustLookupParamType("int64"), // backwards-compatible test of LookupParamType. + ErrorCode: 404, + }}, // 10 + {true, + ast.ParamStatement{ + Src: "{has:bool else 404}", + Name: "has", + Type: mustLookupParamType("bool"), + ErrorCode: 404, + }}, // 11 + {true, + ast.ParamStatement{ + Src: "{has:boolean else 404}", // backwards-compatible test. + Name: "has", + Type: mustLookupParamType("bool"), + ErrorCode: 404, + }}, // 12 } p := new(ParamParser) for i, tt := range tests { p.Reset(tt.expectedStatement.Src) - resultStmt, err := p.Parse() + resultStmt, err := p.Parse(testParamTypes) if tt.valid && err != nil { t.Fatalf("tests[%d] - error %s", i, err.Error()) @@ -171,27 +241,27 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{id:int min(1) max(5) else 404}", Name: "id", - Type: ast.ParamTypeInt, + Type: paramTypeNumber, Funcs: []ast.ParamFunc{ { Name: "min", - Args: []ast.ParamFuncArg{1}}, + Args: []string{"1"}}, { Name: "max", - Args: []ast.ParamFuncArg{5}}, + Args: []string{"5"}}, }, ErrorCode: 404, }, }}, // 0 - {"/admin/{id:int range(1,5)}", true, + {"/admin/{id:uint64 range(1,5)}", true, []ast.ParamStatement{{ - Src: "{id:int range(1,5)}", + Src: "{id:uint64 range(1,5)}", Name: "id", - Type: ast.ParamTypeInt, + Type: paramTypeUint64, Funcs: []ast.ParamFunc{ { Name: "range", - Args: []ast.ParamFuncArg{1, 5}}, + Args: []string{"1", "5"}}, }, ErrorCode: 404, }, @@ -200,11 +270,11 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{file:path contains(.)}", Name: "file", - Type: ast.ParamTypePath, + Type: paramTypePath, Funcs: []ast.ParamFunc{ { Name: "contains", - Args: []ast.ParamFuncArg{"."}}, + Args: []string{"."}}, }, ErrorCode: 404, }, @@ -213,7 +283,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{username:alphabetical}", Name: "username", - Type: ast.ParamTypeAlphabetical, + Type: paramTypeAlphabetical, ErrorCode: 404, }, }}, // 3 @@ -221,7 +291,7 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{myparam}", Name: "myparam", - Type: ast.ParamTypeString, + Type: paramTypeString, ErrorCode: 404, }, }}, // 4 @@ -229,15 +299,15 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{myparam_:thisianunexpected}", Name: "myparam_", - Type: ast.ParamTypeUnExpected, + Type: nil, ErrorCode: 404, }, }}, // 5 - {"/p2/{myparam2}", false, // false because it will give an error of unexpeced token type with value 2 + {"/p2/{myparam2}", true, []ast.ParamStatement{{ Src: "{myparam2}", - Name: "myparam", // expected "myparam" because we don't allow integers to the parameter names. - Type: ast.ParamTypeString, + Name: "myparam2", // we now allow integers to the parameter names. + Type: paramTypeString, ErrorCode: 404, }, }}, // 6 @@ -245,13 +315,13 @@ func TestParse(t *testing.T) { []ast.ParamStatement{{ Src: "{file:path}", Name: "file", - Type: ast.ParamTypePath, + Type: paramTypePath, ErrorCode: 404, }, }}, // 7 } for i, tt := range tests { - statements, err := Parse(tt.path) + statements, err := Parse(tt.path, testParamTypes) if tt.valid && err != nil { t.Fatalf("tests[%d] - error %s", i, err.Error()) diff --git a/core/router/macro/interpreter/token/token.go b/macro/interpreter/token/token.go similarity index 96% rename from core/router/macro/interpreter/token/token.go rename to macro/interpreter/token/token.go index 620ad64103..b964db1d70 100644 --- a/core/router/macro/interpreter/token/token.go +++ b/macro/interpreter/token/token.go @@ -13,7 +13,7 @@ type Token struct { // /about/{fullname:alphabetical} // /profile/{anySpecialName:string} -// {id:int range(1,5) else 404} +// {id:uint64 range(1,5) else 404} // /admin/{id:int eq(1) else 402} // /file/{filepath:file else 405} const ( diff --git a/macro/macro.go b/macro/macro.go new file mode 100644 index 0000000000..c4c557043d --- /dev/null +++ b/macro/macro.go @@ -0,0 +1,344 @@ +package macro + +import ( + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "unicode" +) + +type ( + // ParamEvaluator is the signature for param type evaluator. + // It accepts the param's value as string and returns + // the value (which its type is used for the input argument of the parameter functions, if any) + // and a true value for passed, otherwise nil and false should be returned. + ParamEvaluator func(paramValue string) (interface{}, bool) +) + +var goodEvaluatorFuncs = []reflect.Type{ + reflect.TypeOf(func(string) (interface{}, bool) { return nil, false }), + reflect.TypeOf(ParamEvaluator(func(string) (interface{}, bool) { return nil, false })), +} + +func goodParamFunc(typ reflect.Type) bool { + if typ.Kind() == reflect.Func { // it should be a func which returns a func (see below check). + if typ.NumOut() == 1 { + typOut := typ.Out(0) + if typOut.Kind() != reflect.Func { + return false + } + + if typOut.NumOut() == 2 { // if it's a type of EvaluatorFunc, used for param evaluator. + for _, fType := range goodEvaluatorFuncs { + if typOut == fType { + return true + } + } + return false + } + + if typOut.NumIn() == 1 && typOut.NumOut() == 1 { // if it's a type of func(paramValue [int,string...]) bool, used for param funcs. + return typOut.Out(0).Kind() == reflect.Bool + } + } + } + + return false +} + +// Regexp accepts a regexp "expr" expression +// and returns its MatchString. +// The regexp is compiled before return. +// +// Returns a not-nil error on regexp compile failure. +func Regexp(expr string) (func(string) bool, error) { + if expr == "" { + return nil, fmt.Errorf("empty regex expression") + } + + // add the last $ if missing (and not wildcard(?)) + if i := expr[len(expr)-1]; i != '$' && i != '*' { + expr += "$" + } + + r, err := regexp.Compile(expr) + if err != nil { + return nil, err + } + + return r.MatchString, nil +} + +// MustRegexp same as Regexp +// but it panics on the "expr" parse failure. +func MustRegexp(expr string) func(string) bool { + r, err := Regexp(expr) + if err != nil { + panic(err) + } + return r +} + +// goodParamFuncName reports whether the function name is a valid identifier. +func goodParamFuncName(name string) bool { + if name == "" { + return false + } + // valid names are only letters and _ + for _, r := range name { + switch { + case r == '_': + case !unicode.IsLetter(r): + return false + } + } + return true +} + +// the convertBuilderFunc return value is generating at boot time. +// convertFunc converts an interface to a valid full param function. +func convertBuilderFunc(fn interface{}) ParamFuncBuilder { + + typFn := reflect.TypeOf(fn) + if !goodParamFunc(typFn) { + return nil + } + + numFields := typFn.NumIn() + + return func(args []string) reflect.Value { + if len(args) != numFields { + // no variadics support, for now. + panic("args should be the same len as numFields") + } + var argValues []reflect.Value + for i := 0; i < numFields; i++ { + field := typFn.In(i) + arg := args[i] + + // try to convert the string literal as we get it from the parser. + var ( + val interface{} + + panicIfErr = func(err error) { + if err != nil { + panic(fmt.Sprintf("on field index: %d: %v", i, err)) + } + } + ) + + // try to get the value based on the expected type. + switch field.Kind() { + case reflect.Int: + v, err := strconv.Atoi(arg) + panicIfErr(err) + val = v + case reflect.Int8: + v, err := strconv.ParseInt(arg, 10, 8) + panicIfErr(err) + val = int8(v) + case reflect.Int16: + v, err := strconv.ParseInt(arg, 10, 16) + panicIfErr(err) + val = int16(v) + case reflect.Int32: + v, err := strconv.ParseInt(arg, 10, 32) + panicIfErr(err) + val = int32(v) + case reflect.Int64: + v, err := strconv.ParseInt(arg, 10, 64) + panicIfErr(err) + val = v + case reflect.Uint: + v, err := strconv.ParseUint(arg, 10, strconv.IntSize) + panicIfErr(err) + val = uint(v) + case reflect.Uint8: + v, err := strconv.ParseUint(arg, 10, 8) + panicIfErr(err) + val = uint8(v) + case reflect.Uint16: + v, err := strconv.ParseUint(arg, 10, 16) + panicIfErr(err) + val = uint16(v) + case reflect.Uint32: + v, err := strconv.ParseUint(arg, 10, 32) + panicIfErr(err) + val = uint32(v) + case reflect.Uint64: + v, err := strconv.ParseUint(arg, 10, 64) + panicIfErr(err) + val = v + case reflect.Float32: + v, err := strconv.ParseFloat(arg, 32) + panicIfErr(err) + val = float32(v) + case reflect.Float64: + v, err := strconv.ParseFloat(arg, 64) + panicIfErr(err) + val = v + case reflect.Bool: + v, err := strconv.ParseBool(arg) + panicIfErr(err) + val = v + case reflect.Slice: + if len(arg) > 1 { + if arg[0] == '[' && arg[len(arg)-1] == ']' { + // it is a single argument but as slice. + val = strings.Split(arg[1:len(arg)-1], ",") // only string slices. + } + } + + default: + val = arg + } + + argValue := reflect.ValueOf(val) + if expected, got := field.Kind(), argValue.Kind(); expected != got { + panic(fmt.Sprintf("func's input arguments should have the same type: [%d] expected %s but got %s", i, expected, got)) + } + + argValues = append(argValues, argValue) + } + + evalFn := reflect.ValueOf(fn).Call(argValues)[0] + + // var evaluator EvaluatorFunc + // // check for typed and not typed + // if _v, ok := evalFn.(EvaluatorFunc); ok { + // evaluator = _v + // } else if _v, ok = evalFn.(func(string) bool); ok { + // evaluator = _v + // } + // return func(paramValue interface{}) bool { + // return evaluator(paramValue) + // } + return evalFn + } +} + +type ( + // Macro represents the parsed macro, + // which holds + // the evaluator (param type's evaluator + param functions evaluators) + // and its param functions. + // + // Any type contains its own macro + // instance, so an String type + // contains its type evaluator + // which is the "Evaluator" field + // and it can register param functions + // to that macro which maps to a parameter type. + Macro struct { + indent string + alias string + master bool + trailing bool + + Evaluator ParamEvaluator + funcs []ParamFunc + } + + // ParamFuncBuilder is a func + // which accepts a param function's arguments (values) + // and returns a function as value, its job + // is to make the macros to be registered + // by user at the most generic possible way. + ParamFuncBuilder func([]string) reflect.Value // the func() bool + + // ParamFunc represents the parsed + // parameter function, it holds + // the parameter's name + // and the function which will build + // the evaluator func. + ParamFunc struct { + Name string + Func ParamFuncBuilder + } +) + +// NewMacro creates and returns a Macro that can be used as a registry for +// a new customized parameter type and its functions. +func NewMacro(indent, alias string, master, trailing bool, evaluator ParamEvaluator) *Macro { + return &Macro{ + indent: indent, + alias: alias, + master: master, + trailing: trailing, + + Evaluator: evaluator, + } +} + +// Indent returns the name of the parameter type. +func (m *Macro) Indent() string { + return m.indent +} + +// Alias returns the alias of the parameter type, if any. +func (m *Macro) Alias() string { + return m.alias +} + +// Master returns true if that macro's parameter type is the +// default one if not :type is followed by a parameter type inside the route path. +func (m *Macro) Master() bool { + return m.master +} + +// Trailing returns true if that macro's parameter type +// is wildcard and can accept one or more path segments as one parameter value. +// A wildcard should be registered in the last path segment only. +func (m *Macro) Trailing() bool { + return m.trailing +} + +// func (m *Macro) SetParamResolver(fn func(memstore.Entry) interface{}) *Macro { +// m.ParamResolver = fn +// return m +// } + +// RegisterFunc registers a parameter function +// to that macro. +// Accepts the func name ("range") +// and the function body, which should return an EvaluatorFunc +// a bool (it will be converted to EvaluatorFunc later on), +// i.e RegisterFunc("min", func(minValue int) func(paramValue string) bool){}) +func (m *Macro) RegisterFunc(funcName string, fn interface{}) *Macro { + fullFn := convertBuilderFunc(fn) + m.registerFunc(funcName, fullFn) + + return m +} + +func (m *Macro) registerFunc(funcName string, fullFn ParamFuncBuilder) { + if !goodParamFuncName(funcName) { + return + } + + for _, fn := range m.funcs { + if fn.Name == funcName { + fn.Func = fullFn + return + } + } + + m.funcs = append(m.funcs, ParamFunc{ + Name: funcName, + Func: fullFn, + }) +} + +func (m *Macro) getFunc(funcName string) ParamFuncBuilder { + for _, fn := range m.funcs { + if fn.Name == funcName { + if fn.Func == nil { + continue + } + return fn.Func + } + } + return nil +} diff --git a/macro/macro_test.go b/macro/macro_test.go new file mode 100644 index 0000000000..ff25c817db --- /dev/null +++ b/macro/macro_test.go @@ -0,0 +1,453 @@ +package macro + +import ( + "reflect" + "strconv" + "testing" +) + +// Most important tests to look: +// ../parser/parser_test.go +// ../lexer/lexer_test.go + +func TestGoodParamFunc(t *testing.T) { + good1 := func(min int, max int) func(string) bool { + return func(paramValue string) bool { + return true + } + } + + good2 := func(min uint64, max uint64) func(string) bool { + return func(paramValue string) bool { + return true + } + } + + notgood1 := func(min int, max int) bool { + return false + } + + if !goodParamFunc(reflect.TypeOf(good1)) { + t.Fatalf("expected good1 func to be good but it's not") + } + + if !goodParamFunc(reflect.TypeOf(good2)) { + t.Fatalf("expected good2 func to be good but it's not") + } + + if goodParamFunc(reflect.TypeOf(notgood1)) { + t.Fatalf("expected notgood1 func to be the worst") + } +} + +func TestGoodParamFuncName(t *testing.T) { + tests := []struct { + name string + good bool + }{ + {"range", true}, + {"_range", true}, + {"range_", true}, + {"r_ange", true}, + // numbers or other symbols are invalid. + {"range1", false}, + {"2range", false}, + {"r@nge", false}, + {"rang3", false}, + } + for i, tt := range tests { + isGood := goodParamFuncName(tt.name) + if tt.good && !isGood { + t.Fatalf("tests[%d] - expecting valid name but got invalid for name %s", i, tt.name) + } else if !tt.good && isGood { + t.Fatalf("tests[%d] - expecting invalid name but got valid for name %s", i, tt.name) + } + } +} + +func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, expectedType reflect.Kind, pass bool, i int) { + if macroEvaluator.Evaluator == nil && pass { + return // if not evaluator defined then it should allow everything. + } + value, passed := macroEvaluator.Evaluator(input) + if pass != passed { + t.Fatalf("%s - tests[%d] - expecting[pass] %v but got %v", t.Name(), i, pass, passed) + } + + if !passed { + return + } + + if value == nil && expectedType != reflect.Invalid { + t.Fatalf("%s - tests[%d] - expecting[value] to not be nil", t.Name(), i) + } + + if v := reflect.ValueOf(value); v.Kind() != expectedType { + t.Fatalf("%s - tests[%d] - expecting[value.Kind] %v but got %v", t.Name(), i, expectedType, v.Kind()) + } +} + +func TestStringEvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {true, "astring"}, // 0 + {true, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "main.css"}, // 3 + {true, "/assets/main.css"}, // 4 + // false never + } // 0 + + for i, tt := range tests { + testEvaluatorRaw(t, String, tt.input, reflect.String, tt.pass, i) + } +} + +func TestIntEvaluatorRaw(t *testing.T) { + x64 := strconv.IntSize == 64 + + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {x64, "9223372036854775807" /* max int64 */}, // 3 + {x64, "-9223372036854775808" /* min int64 */}, // 4 + {false, "-18446744073709553213213213213213121615"}, // 5 + {false, "42 18446744073709551615"}, // 6 + {false, "--42"}, // 7 + {false, "+42"}, // 8 + {false, "main.css"}, // 9 + {false, "/assets/main.css"}, // 10 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int, tt.input, reflect.Int, tt.pass, i) + } +} + +func TestInt8EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {false, "32321"}, // 2 + {true, "127" /* max int8 */}, // 3 + {true, "-128" /* min int8 */}, // 4 + {false, "128"}, // 5 + {false, "-129"}, // 6 + {false, "-18446744073709553213213213213213121615"}, // 7 + {false, "42 18446744073709551615"}, // 8 + {false, "--42"}, // 9 + {false, "+42"}, // 10 + {false, "main.css"}, // 11 + {false, "/assets/main.css"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int8, tt.input, reflect.Int8, tt.pass, i) + } +} + +func TestInt16EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "32767" /* max int16 */}, // 3 + {true, "-32768" /* min int16 */}, // 4 + {false, "-32769"}, // 5 + {false, "32768"}, // 6 + {false, "-18446744073709553213213213213213121615"}, // 7 + {false, "42 18446744073709551615"}, // 8 + {false, "--42"}, // 9 + {false, "+42"}, // 10 + {false, "main.css"}, // 11 + {false, "/assets/main.css"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int16, tt.input, reflect.Int16, tt.pass, i) + } +} + +func TestInt32EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "1"}, // 3 + {true, "42"}, // 4 + {true, "2147483647" /* max int32 */}, // 5 + {true, "-2147483648" /* min int32 */}, // 6 + {false, "-2147483649"}, // 7 + {false, "2147483648"}, // 8 + {false, "-18446744073709553213213213213213121615"}, // 9 + {false, "42 18446744073709551615"}, // 10 + {false, "--42"}, // 11 + {false, "+42"}, // 12 + {false, "main.css"}, // 13 + {false, "/assets/main.css"}, // 14 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int32, tt.input, reflect.Int32, tt.pass, i) + } +} + +func TestInt64EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {false, "18446744073709551615"}, // 2 + {false, "92233720368547758079223372036854775807"}, // 3 + {false, "9223372036854775808 9223372036854775808"}, // 4 + {false, "main.css"}, // 5 + {false, "/assets/main.css"}, // 6 + {true, "9223372036854775807"}, // 7 + {true, "-9223372036854775808"}, // 8 + {true, "-0"}, // 9 + {true, "1"}, // 10 + {true, "-042"}, // 11 + {true, "142"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Int64, tt.input, reflect.Int64, tt.pass, i) + } +} + +func TestUintEvaluatorRaw(t *testing.T) { + x64 := strconv.IntSize == 64 + + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "1"}, // 3 + {true, "42"}, // 4 + {x64, "18446744073709551615" /* max uint64 */}, // 5 + {true, "4294967295" /* max uint32 */}, // 6 + {false, "-2147483649"}, // 7 + {true, "2147483648"}, // 8 + {false, "-18446744073709553213213213213213121615"}, // 9 + {false, "42 18446744073709551615"}, // 10 + {false, "--42"}, // 11 + {false, "+42"}, // 12 + {false, "main.css"}, // 13 + {false, "/assets/main.css"}, // 14 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint, tt.input, reflect.Uint, tt.pass, i) + } +} + +func TestUint8EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {false, "-9223372036854775808"}, // 2 + {false, "main.css"}, // 3 + {false, "/assets/main.css"}, // 4 + {false, "92233720368547758079223372036854775807"}, // 5 + {false, "9223372036854775808 9223372036854775808"}, // 6 + {false, "-1"}, // 7 + {false, "-0"}, // 8 + {false, "+1"}, // 9 + {false, "18446744073709551615"}, // 10 + {false, "9223372036854775807"}, // 11 + {false, "021"}, // 12 - no leading zeroes are allowed. + {false, "300"}, // 13 + {true, "0"}, // 14 + {true, "255"}, // 15 + {true, "21"}, // 16 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint8, tt.input, reflect.Uint8, tt.pass, i) + } +} + +func TestUint16EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "65535" /* max uint16 */}, // 3 + {true, "0" /* min uint16 */}, // 4 + {false, "-32769"}, // 5 + {true, "32768"}, // 6 + {false, "-18446744073709553213213213213213121615"}, // 7 + {false, "42 18446744073709551615"}, // 8 + {false, "--42"}, // 9 + {false, "+42"}, // 10 + {false, "main.css"}, // 11 + {false, "/assets/main.css"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint16, tt.input, reflect.Uint16, tt.pass, i) + } +} + +func TestUint32EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "1"}, // 3 + {true, "42"}, // 4 + {true, "4294967295" /* max uint32*/}, // 5 + {true, "0" /* min uint32 */}, // 6 + {false, "-2147483649"}, // 7 + {true, "2147483648"}, // 8 + {false, "-18446744073709553213213213213213121615"}, // 9 + {false, "42 18446744073709551615"}, // 10 + {false, "--42"}, // 11 + {false, "+42"}, // 12 + {false, "main.css"}, // 13 + {false, "/assets/main.css"}, // 14 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint32, tt.input, reflect.Uint32, tt.pass, i) + } +} + +func TestUint64EvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {false, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {false, "-9223372036854775808"}, // 2 + {false, "main.css"}, // 3 + {false, "/assets/main.css"}, // 4 + {false, "92233720368547758079223372036854775807"}, // 5 + {false, "9223372036854775808 9223372036854775808"}, // 6 + {false, "-1"}, // 7 + {false, "-0"}, // 8 + {false, "+1"}, // 9 + {true, "18446744073709551615"}, // 10 + {true, "9223372036854775807"}, // 11 + {true, "0"}, // 12 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Uint64, tt.input, reflect.Uint64, tt.pass, i) + } +} + +func TestAlphabeticalEvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {true, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {false, "32321"}, // 2 + {false, "main.css"}, // 3 + {false, "/assets/main.css"}, // 4 + } + + for i, tt := range tests { + testEvaluatorRaw(t, Alphabetical, tt.input, reflect.String, tt.pass, i) + } +} + +func TestFileEvaluatorRaw(t *testing.T) { + tests := []struct { + pass bool + input string + }{ + {true, "astring"}, // 0 + {false, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "main.css"}, // 3 + {false, "/assets/main.css"}, // 4 + } + + for i, tt := range tests { + testEvaluatorRaw(t, File, tt.input, reflect.String, tt.pass, i) + } +} + +func TestPathEvaluatorRaw(t *testing.T) { + pathTests := []struct { + pass bool + input string + }{ + {true, "astring"}, // 0 + {true, "astringwith_numb3rS_and_symbol$"}, // 1 + {true, "32321"}, // 2 + {true, "main.css"}, // 3 + {true, "/assets/main.css"}, // 4 + {true, "disk/assets/main.css"}, // 5 + } + + for i, tt := range pathTests { + testEvaluatorRaw(t, Path, tt.input, reflect.String, tt.pass, i) + } +} + +func TestConvertBuilderFunc(t *testing.T) { + fn := func(min uint64, slice []string) func(string) bool { + return func(paramValue string) bool { + if expected, got := "ok", paramValue; expected != got { + t.Fatalf("paramValue is not the expected one: %s vs %s", expected, got) + } + + if expected, got := uint64(1), min; expected != got { + t.Fatalf("min argument is not the expected one: %d vs %d", expected, got) + } + + if expected, got := []string{"name1", "name2"}, slice; len(expected) == len(got) { + if expected, got := "name1", slice[0]; expected != got { + t.Fatalf("slice argument[%d] does not contain the expected value: %s vs %s", 0, expected, got) + } + + if expected, got := "name2", slice[1]; expected != got { + t.Fatalf("slice argument[%d] does not contain the expected value: %s vs %s", 1, expected, got) + } + } else { + t.Fatalf("slice argument is not the expected one, the length is difference: %d vs %d", len(expected), len(got)) + } + + return true + } + } + + evalFunc := convertBuilderFunc(fn) + if !evalFunc([]string{"1", "[name1,name2]"}).Call([]reflect.Value{reflect.ValueOf("ok")})[0].Interface().(bool) { + t.Fatalf("failed, it should fail already") + } +} diff --git a/macro/macros.go b/macro/macros.go new file mode 100644 index 0000000000..4c6f641617 --- /dev/null +++ b/macro/macros.go @@ -0,0 +1,550 @@ +package macro + +import ( + "strconv" + "strings" + + "github.com/kataras/iris/macro/interpreter/ast" +) + +var ( + // String type + // Allows anything (single path segment, as everything except the `Path`). + // Its functions can be used by the rest of the macros and param types whenever not available function by name is used. + // Because of its "master" boolean value to true (third parameter). + String = NewMacro("string", "", true, false, nil). + RegisterFunc("regexp", MustRegexp). + // checks if param value starts with the 'prefix' arg + RegisterFunc("prefix", func(prefix string) func(string) bool { + return func(paramValue string) bool { + return strings.HasPrefix(paramValue, prefix) + } + }). + // checks if param value ends with the 'suffix' arg + RegisterFunc("suffix", func(suffix string) func(string) bool { + return func(paramValue string) bool { + return strings.HasSuffix(paramValue, suffix) + } + }). + // checks if param value contains the 's' arg + RegisterFunc("contains", func(s string) func(string) bool { + return func(paramValue string) bool { + return strings.Contains(paramValue, s) + } + }). + // checks if param value's length is at least 'min' + RegisterFunc("min", func(min int) func(string) bool { + return func(paramValue string) bool { + return len(paramValue) >= min + } + }). + // checks if param value's length is not bigger than 'max' + RegisterFunc("max", func(max int) func(string) bool { + return func(paramValue string) bool { + return max >= len(paramValue) + } + }) + + simpleNumberEval = MustRegexp("^-?[0-9]+$") + // Int or number type + // both positive and negative numbers, actual value can be min-max int64 or min-max int32 depends on the arch. + // If x64: -9223372036854775808 to 9223372036854775807. + // If x32: -2147483648 to 2147483647 and etc.. + Int = NewMacro("int", "number", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.Atoi(paramValue) + if err != nil { + return nil, false + } + + return v, true + }). + // checks if the param value's int representation is + // bigger or equal than 'min' + RegisterFunc("min", func(min int) func(int) bool { + return func(paramValue int) bool { + return paramValue >= min + } + }). + // checks if the param value's int representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max int) func(int) bool { + return func(paramValue int) bool { + return paramValue <= max + } + }). + // checks if the param value's int representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max int) func(int) bool { + return func(paramValue int) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Int8 type + // -128 to 127. + Int8 = NewMacro("int8", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.ParseInt(paramValue, 10, 8) + if err != nil { + return nil, false + } + return int8(v), true + }). + RegisterFunc("min", func(min int8) func(int8) bool { + return func(paramValue int8) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max int8) func(int8) bool { + return func(paramValue int8) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max int8) func(int8) bool { + return func(paramValue int8) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Int16 type + // -32768 to 32767. + Int16 = NewMacro("int16", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.ParseInt(paramValue, 10, 16) + if err != nil { + return nil, false + } + return int16(v), true + }). + RegisterFunc("min", func(min int16) func(int16) bool { + return func(paramValue int16) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max int16) func(int16) bool { + return func(paramValue int16) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max int16) func(int16) bool { + return func(paramValue int16) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Int32 type + // -2147483648 to 2147483647. + Int32 = NewMacro("int32", "", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.ParseInt(paramValue, 10, 32) + if err != nil { + return nil, false + } + return int32(v), true + }). + RegisterFunc("min", func(min int32) func(int32) bool { + return func(paramValue int32) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max int32) func(int32) bool { + return func(paramValue int32) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max int32) func(int32) bool { + return func(paramValue int32) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Int64 as int64 type + // -9223372036854775808 to 9223372036854775807. + Int64 = NewMacro("int64", "long", false, false, func(paramValue string) (interface{}, bool) { + if !simpleNumberEval(paramValue) { + return nil, false + } + + v, err := strconv.ParseInt(paramValue, 10, 64) + if err != nil { // if err == strconv.ErrRange... + return nil, false + } + return v, true + }). + // checks if the param value's int64 representation is + // bigger or equal than 'min'. + RegisterFunc("min", func(min int64) func(int64) bool { + return func(paramValue int64) bool { + return paramValue >= min + } + }). + // checks if the param value's int64 representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max int64) func(int64) bool { + return func(paramValue int64) bool { + return paramValue <= max + } + }). + // checks if the param value's int64 representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max int64) func(int64) bool { + return func(paramValue int64) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Uint as uint type + // actual value can be min-max uint64 or min-max uint32 depends on the arch. + // If x64: 0 to 18446744073709551615. + // If x32: 0 to 4294967295 and etc. + Uint = NewMacro("uint", "", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, strconv.IntSize) // 32,64... + if err != nil { + return nil, false + } + return uint(v), true + }). + // checks if the param value's int representation is + // bigger or equal than 'min' + RegisterFunc("min", func(min uint) func(uint) bool { + return func(paramValue uint) bool { + return paramValue >= min + } + }). + // checks if the param value's int representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max uint) func(uint) bool { + return func(paramValue uint) bool { + return paramValue <= max + } + }). + // checks if the param value's int representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max uint) func(uint) bool { + return func(paramValue uint) bool { + return !(paramValue < min || paramValue > max) + } + }) + + uint8Eval = MustRegexp("^([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$") + // Uint8 as uint8 type + // 0 to 255. + Uint8 = NewMacro("uint8", "", false, false, func(paramValue string) (interface{}, bool) { + if !uint8Eval(paramValue) { + return nil, false + } + + v, err := strconv.ParseUint(paramValue, 10, 8) + if err != nil { + return nil, false + } + return uint8(v), true + }). + // checks if the param value's uint8 representation is + // bigger or equal than 'min'. + RegisterFunc("min", func(min uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return paramValue >= min + } + }). + // checks if the param value's uint8 representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return paramValue <= max + } + }). + // checks if the param value's uint8 representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max uint8) func(uint8) bool { + return func(paramValue uint8) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Uint16 as uint16 type + // 0 to 65535. + Uint16 = NewMacro("uint16", "", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, 16) + if err != nil { + return nil, false + } + return uint16(v), true + }). + RegisterFunc("min", func(min uint16) func(uint16) bool { + return func(paramValue uint16) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max uint16) func(uint16) bool { + return func(paramValue uint16) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max uint16) func(uint16) bool { + return func(paramValue uint16) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Uint32 as uint32 type + // 0 to 4294967295. + Uint32 = NewMacro("uint32", "", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, 32) + if err != nil { + return nil, false + } + return uint32(v), true + }). + RegisterFunc("min", func(min uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return paramValue >= min + } + }). + RegisterFunc("max", func(max uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return paramValue <= max + } + }). + RegisterFunc("range", func(min, max uint32) func(uint32) bool { + return func(paramValue uint32) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Uint64 as uint64 type + // 0 to 18446744073709551615. + Uint64 = NewMacro("uint64", "", false, false, func(paramValue string) (interface{}, bool) { + v, err := strconv.ParseUint(paramValue, 10, 64) + if err != nil { + return nil, false + } + return v, true + }). + // checks if the param value's uint64 representation is + // bigger or equal than 'min'. + RegisterFunc("min", func(min uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return paramValue >= min + } + }). + // checks if the param value's uint64 representation is + // smaller or equal than 'max'. + RegisterFunc("max", func(max uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return paramValue <= max + } + }). + // checks if the param value's uint64 representation is + // between min and max, including 'min' and 'max'. + RegisterFunc("range", func(min, max uint64) func(uint64) bool { + return func(paramValue uint64) bool { + return !(paramValue < min || paramValue > max) + } + }) + + // Bool or boolean as bool type + // a string which is "1" or "t" or "T" or "TRUE" or "true" or "True" + // or "0" or "f" or "F" or "FALSE" or "false" or "False". + Bool = NewMacro("bool", "boolean", false, false, func(paramValue string) (interface{}, bool) { + // a simple if statement is faster than regex ^(true|false|True|False|t|0|f|FALSE|TRUE)$ + // in this case. + v, err := strconv.ParseBool(paramValue) + if err != nil { + return nil, false + } + return v, true + }) + + alphabeticalEval = MustRegexp("^[a-zA-Z ]+$") + // Alphabetical letter type + // letters only (upper or lowercase) + Alphabetical = NewMacro("alphabetical", "", false, false, func(paramValue string) (interface{}, bool) { + if !alphabeticalEval(paramValue) { + return nil, false + } + return paramValue, true + }) + + fileEval = MustRegexp("^[a-zA-Z0-9_.-]*$") + // File type + // letters (upper or lowercase) + // numbers (0-9) + // underscore (_) + // dash (-) + // point (.) + // no spaces! or other character + File = NewMacro("file", "", false, false, func(paramValue string) (interface{}, bool) { + if !fileEval(paramValue) { + return nil, false + } + return paramValue, true + }) + // Path type + // anything, should be the last part + // + // It allows everything, we have String and Path as different + // types because I want to give the opportunity to the user + // to organise the macro functions based on wildcard or single dynamic named path parameter. + // Should be living in the latest path segment of a route path. + Path = NewMacro("path", "", false, true, nil) + + // Defaults contains the defaults macro and parameters types for the router. + // + // Read https://github.com/kataras/iris/tree/master/_examples/routing/macros for more details. + Defaults = &Macros{ + String, + Int, + Int8, + Int16, + Int32, + Int64, + Uint, + Uint8, + Uint16, + Uint32, + Uint64, + Bool, + Alphabetical, + Path, + } +) + +// Macros is just a type of a slice of *Macro +// which is responsible to register and search for macros based on the indent(parameter type). +type Macros []*Macro + +// Register registers a custom Macro. +// The "indent" should not be empty and should be unique, it is the parameter type's name, i.e "string". +// The "alias" is optionally and it should be unique, it is the alias of the parameter type. +// "isMaster" and "isTrailing" is for default parameter type and wildcard respectfully. +// The "evaluator" is the function that is converted to an Iris handler which is executed every time +// before the main chain of a route's handlers that contains this macro of the specific parameter type. +// +// Read https://github.com/kataras/iris/tree/master/_examples/routing/macros for more details. +func (ms *Macros) Register(indent, alias string, isMaster, isTrailing bool, evaluator ParamEvaluator) *Macro { + macro := NewMacro(indent, alias, isMaster, isTrailing, evaluator) + if ms.register(macro) { + return macro + } + return nil +} + +func (ms *Macros) register(macro *Macro) bool { + if macro.Indent() == "" { + return false + } + + cp := *ms + + for _, m := range cp { + // can't add more than one with the same ast characteristics. + if macro.Indent() == m.Indent() { + return false + } + + if alias := macro.Alias(); alias != "" { + if alias == m.Alias() || alias == m.Indent() { + return false + } + } + + if macro.Master() && m.Master() { + return false + } + } + + cp = append(cp, macro) + + *ms = cp + return true +} + +// Unregister removes a macro and its parameter type from the list. +func (ms *Macros) Unregister(indent string) bool { + cp := *ms + + for i, m := range cp { + if m.Indent() == indent { + copy(cp[i:], cp[i+1:]) + cp[len(cp)-1] = nil + cp = cp[:len(cp)-1] + + *ms = cp + return true + } + } + + return false +} + +// Lookup returns the responsible macro for a parameter type, it can return nil. +func (ms *Macros) Lookup(pt ast.ParamType) *Macro { + if m := ms.Get(pt.Indent()); m != nil { + return m + } + + if alias, has := ast.HasAlias(pt); has { + if m := ms.Get(alias); m != nil { + return m + } + } + + return nil +} + +// Get returns the responsible macro for a parameter type, it can return nil. +func (ms *Macros) Get(indentOrAlias string) *Macro { + if indentOrAlias == "" { + return nil + } + + for _, m := range *ms { + if m.Indent() == indentOrAlias { + return m + } + + if m.Alias() == indentOrAlias { + return m + } + } + + return nil +} + +// GetMaster returns the default macro and its parameter type, +// by default it will return the `String` macro which is responsible for the "string" parameter type. +func (ms *Macros) GetMaster() *Macro { + for _, m := range *ms { + if m.Master() { + return m + } + } + + return nil +} + +// GetTrailings returns the macros that have support for wildcards parameter types. +// By default it will return the `Path` macro which is responsible for the "path" parameter type. +func (ms *Macros) GetTrailings() (macros []*Macro) { + for _, m := range *ms { + if m.Trailing() { + macros = append(macros, m) + } + } + + return +} diff --git a/macro/template.go b/macro/template.go new file mode 100644 index 0000000000..8e766cb5dd --- /dev/null +++ b/macro/template.go @@ -0,0 +1,155 @@ +package macro + +import ( + "reflect" + + "github.com/kataras/iris/core/memstore" + "github.com/kataras/iris/macro/interpreter/ast" + "github.com/kataras/iris/macro/interpreter/parser" +) + +// Template contains a route's path full parsed template. +// +// Fields: +// Src is the raw source of the path, i.e /users/{id:int min(1)} +// Params is the list of the Params that are being used to the +// path, i.e the min as param name and 1 as the param argument. +type Template struct { + // Src is the original template given by the client + Src string `json:"src"` + Params []TemplateParam `json:"params"` +} + +// TemplateParam is the parsed macro parameter's template +// they are being used to describe the param's syntax result. +type TemplateParam struct { + Src string `json:"src"` // the unparsed param'false source + // Type is not useful anywhere here but maybe + // it's useful on host to decide how to convert the path template to specific router's syntax + Type ast.ParamType `json:"type"` + Name string `json:"name"` + Index int `json:"index"` + ErrCode int `json:"errCode"` + TypeEvaluator ParamEvaluator `json:"-"` + Funcs []reflect.Value `json:"-"` + + stringInFuncs []func(string) bool + canEval bool +} + +func (p TemplateParam) preComputed() TemplateParam { + for _, pfn := range p.Funcs { + if fn, ok := pfn.Interface().(func(string) bool); ok { + p.stringInFuncs = append(p.stringInFuncs, fn) + } + } + + // if true then it should be execute the type parameter or its functions + // else it can be ignored, + // i.e {myparam} or {myparam:string} or {myparam:path} -> + // their type evaluator is nil because they don't do any checks and they don't change + // the default parameter value's type (string) so no need for any work). + p.canEval = p.TypeEvaluator != nil || len(p.Funcs) > 0 || p.ErrCode != parser.DefaultParamErrorCode + + return p +} + +// CanEval returns true if this "p" TemplateParam should be evaluated in serve time. +// It is computed before server ran and it is used to determinate if a route needs to build a macro handler (middleware). +func (p *TemplateParam) CanEval() bool { + return p.canEval +} + +// Eval is the most critical part of the TEmplateParam. +// It is responsible to return "passed:true" or "not passed:false" +// if the "paramValue" is the correct type of the registered parameter type +// and all functions, if any, are passed. +// "paramChanger" is the same form of context's Params().Set +// we could accept a memstore.Store or even context.RequestParams +// but this form has been chosed in order to test easier and fully decoupled from a request when necessary. +// +// It is called from the converted macro handler (middleware) +// from the higher-level component of "kataras/iris/macro/handler#MakeHandler". +func (p *TemplateParam) Eval(paramValue string, paramSetter memstore.ValueSetter) bool { + if p.TypeEvaluator == nil { + for _, fn := range p.stringInFuncs { + if !fn(paramValue) { + return false + } + } + return true + } + + newValue, passed := p.TypeEvaluator(paramValue) + if !passed { + return false + } + + if len(p.Funcs) > 0 { + paramIn := []reflect.Value{reflect.ValueOf(newValue)} + for _, evalFunc := range p.Funcs { + // or make it as func(interface{}) bool and pass directly the "newValue" + // but that would not be as easy for end-developer, so keep that "slower": + if !evalFunc.Call(paramIn)[0].Interface().(bool) { // i.e func(paramValue int) bool + return false + } + } + } + + paramSetter.Set(p.Name, newValue) + return true +} + +// Parse takes a full route path and a macro map (macro map contains the macro types with their registered param functions) +// and returns a new Template. +// It builds all the parameter functions for that template +// and their evaluators, it's the api call that makes use the interpeter's parser -> lexer. +func Parse(src string, macros Macros) (Template, error) { + types := make([]ast.ParamType, len(macros)) + for i, m := range macros { + types[i] = m + } + + tmpl := Template{Src: src} + params, err := parser.Parse(src, types) + if err != nil { + return tmpl, err + } + + for idx, p := range params { + m := macros.Lookup(p.Type) + typEval := m.Evaluator + + tmplParam := TemplateParam{ + Src: p.Src, + Type: p.Type, + Name: p.Name, + Index: idx, + ErrCode: p.ErrorCode, + TypeEvaluator: typEval, + } + + for _, paramfn := range p.Funcs { + tmplFn := m.getFunc(paramfn.Name) + if tmplFn == nil { // if not find on this type, check for Master's which is for global funcs too. + if m := macros.GetMaster(); m != nil { + tmplFn = m.getFunc(paramfn.Name) + } + + if tmplFn == nil { // if not found then just skip this param. + continue + } + } + + evalFn := tmplFn(paramfn.Args) + if evalFn.IsNil() || !evalFn.IsValid() || evalFn.Kind() != reflect.Func { + continue + } + tmplParam.Funcs = append(tmplParam.Funcs, evalFn) + } + + tmpl.Params = append(tmpl.Params, tmplParam.preComputed()) + } + + return tmpl, nil +} diff --git a/mvc/controller.go b/mvc/controller.go index 41f37ac200..f7c75eeffc 100644 --- a/mvc/controller.go +++ b/mvc/controller.go @@ -7,9 +7,9 @@ import ( "github.com/kataras/iris/context" "github.com/kataras/iris/core/router" - "github.com/kataras/iris/core/router/macro" "github.com/kataras/iris/hero" "github.com/kataras/iris/hero/di" + "github.com/kataras/iris/macro" "github.com/kataras/golog" ) @@ -247,7 +247,7 @@ func (c *ControllerActivator) parseMethods() { } func (c *ControllerActivator) parseMethod(m reflect.Method) { - httpMethod, httpPath, err := parseMethod(m, c.isReservedMethod) + httpMethod, httpPath, err := parseMethod(*c.router.Macros(), m, c.isReservedMethod) if err != nil { if err != errSkip { c.addErr(fmt.Errorf("MVC: fail to parse the route path and HTTP method for '%s.%s': %v", c.fullName, m.Name, err)) @@ -283,7 +283,7 @@ func (c *ControllerActivator) Handle(method, path, funcName string, middleware . } // parse a route template which contains the parameters organised. - tmpl, err := macro.Parse(path, c.router.Macros()) + tmpl, err := macro.Parse(path, *c.router.Macros()) if err != nil { c.addErr(fmt.Errorf("MVC: fail to parse the path for '%s.%s': %v", c.fullName, funcName, err)) return nil @@ -338,6 +338,7 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref } // fmt.Printf("for %s | values: %s\n", funcName, funcDependencies) + funcInjector := di.Func(m.Func, funcDependencies...) // fmt.Printf("actual injector's inputs length: %d\n", funcInjector.Length) if funcInjector.Has { @@ -396,6 +397,11 @@ func (c *ControllerActivator) handlerOf(m reflect.Method, funcDependencies []ref in := make([]reflect.Value, n, n) in[0] = ctrl funcInjector.Inject(&in, ctxValue) + + // for idxx, inn := range in { + // println("controller.go: execution: in.Value = "+inn.String()+" and in.Type = "+inn.Type().Kind().String()+" of index: ", idxx) + // } + hero.DispatchFuncResult(ctx, call(in)) return } diff --git a/mvc/controller_handle_test.go b/mvc/controller_handle_test.go index 55e200d4c4..9864b3ad6e 100644 --- a/mvc/controller_handle_test.go +++ b/mvc/controller_handle_test.go @@ -37,6 +37,7 @@ type testControllerHandle struct { func (c *testControllerHandle) BeforeActivation(b BeforeActivation) { b.Handle("GET", "/histatic", "HiStatic") b.Handle("GET", "/hiservice", "HiService") + b.Handle("GET", "/hiservice/{ps:string}", "HiServiceBy") b.Handle("GET", "/hiparam/{ps:string}", "HiParamBy") b.Handle("GET", "/hiparamempyinput/{ps:string}", "HiParamEmptyInputBy") } @@ -84,6 +85,10 @@ func (c *testControllerHandle) HiService() string { return c.Service.Say("hi") } +func (c *testControllerHandle) HiServiceBy(v string) string { + return c.Service.Say("hi with param: " + v) +} + func (c *testControllerHandle) HiParamBy(v string) string { return v } @@ -116,7 +121,8 @@ func TestControllerHandle(t *testing.T) { // and can be used in a user-defined, dynamic "mvc handler". e.GET("/hiservice").Expect().Status(httptest.StatusOK). Body().Equal("service: hi") - + e.GET("/hiservice/value").Expect().Status(httptest.StatusOK). + Body().Equal("service: hi with param: value") // this worked with a temporary variadic on the resolvemethodfunc which is not // correct design, I should split the path and params with the rest of implementation // in order a simple template.Src can be given. diff --git a/mvc/controller_method_parser.go b/mvc/controller_method_parser.go index 97c1282edb..1deb40ef46 100644 --- a/mvc/controller_method_parser.go +++ b/mvc/controller_method_parser.go @@ -4,11 +4,12 @@ import ( "errors" "fmt" "reflect" + "strconv" "strings" "unicode" "github.com/kataras/iris/core/router" - "github.com/kataras/iris/core/router/macro/interpreter/ast" + "github.com/kataras/iris/macro" ) const ( @@ -95,47 +96,25 @@ func (l *methodLexer) peekPrev() (w string) { return w } -var posWords = map[int]string{ - 0: "", - 1: "first", - 2: "second", - 3: "third", - 4: "forth", - 5: "five", - 6: "sixth", - 7: "seventh", - 8: "eighth", - 9: "ninth", - 10: "tenth", - 11: "eleventh", - 12: "twelfth", - 13: "thirteenth", - 14: "fourteenth", - 15: "fifteenth", - 16: "sixteenth", - 17: "seventeenth", - 18: "eighteenth", - 19: "nineteenth", - 20: "twentieth", -} - func genParamKey(argIdx int) string { - return "arg" + posWords[argIdx] // argfirst, argsecond... + return "param" + strconv.Itoa(argIdx) // param0, param1, param2... } type methodParser struct { - lexer *methodLexer - fn reflect.Method + lexer *methodLexer + fn reflect.Method + macros macro.Macros } -func parseMethod(fn reflect.Method, skipper func(string) bool) (method, path string, err error) { +func parseMethod(macros macro.Macros, fn reflect.Method, skipper func(string) bool) (method, path string, err error) { if skipper(fn.Name) { return "", "", errSkip } p := &methodParser{ - fn: fn, - lexer: newMethodLexer(fn.Name), + fn: fn, + lexer: newMethodLexer(fn.Name), + macros: macros, } return p.parse() } @@ -211,34 +190,45 @@ func (p *methodParser) parsePathParam(path string, w string, funcArgPos int) (st var ( paramKey = genParamKey(funcArgPos) // argfirst, argsecond... - paramType = ast.ParamTypeString // default string + m = p.macros.GetMaster() // default (String by-default) + trailings = p.macros.GetTrailings() ) // string, int... - goType := typ.In(funcArgPos).Name() + goType := typ.In(funcArgPos).Kind() nextWord := p.lexer.peekNext() if nextWord == tokenWildcard { p.lexer.skip() // skip the Wildcard word. - paramType = ast.ParamTypePath - } else if pType := ast.LookupParamTypeFromStd(goType); pType != ast.ParamTypeUnExpected { - // it's not wildcard, so check base on our available macro types. - paramType = pType + if len(trailings) == 0 { + return "", 0, errors.New("no trailing path parameter found") + } + m = trailings[0] } else { - if typ.NumIn() > funcArgPos { - // has more input arguments but we are not in the correct - // index now, maybe the first argument was an `iris/context.Context` - // so retry with the "funcArgPos" incremented. - // - // the "funcArgPos" will be updated to the caller as well - // because we return it among the path and the error. - return p.parsePathParam(path, w, funcArgPos+1) + // validMacros := p.macros.LookupForGoType(goType) + + // instead of mapping with a reflect.Kind which has its limitation, + // we map the param types with a go type as a string, + // so custom structs such as "user" can be mapped to a macro with indent || alias == "user". + m = p.macros.Get(strings.ToLower(goType.String())) + + if m == nil { + if typ.NumIn() > funcArgPos { + // has more input arguments but we are not in the correct + // index now, maybe the first argument was an `iris/context.Context` + // so retry with the "funcArgPos" incremented. + // + // the "funcArgPos" will be updated to the caller as well + // because we return it among the path and the error. + return p.parsePathParam(path, w, funcArgPos+1) + } + + return "", 0, fmt.Errorf("invalid syntax: the standard go type: %s found in controller's function: %s at position: %d does not match any valid macro", goType, p.fn.Name, funcArgPos) } - return "", 0, errors.New("invalid syntax for " + p.fn.Name) } // /{argfirst:path}, /{argfirst:long}... - path += fmt.Sprintf("/{%s:%s}", paramKey, paramType.String()) + path += fmt.Sprintf("/{%s:%s}", paramKey, m.Indent()) if nextWord == "" && typ.NumIn() > funcArgPos+1 { // By is the latest word but func is expected diff --git a/mvc/controller_test.go b/mvc/controller_test.go index 475b79dc5c..7103c9e944 100644 --- a/mvc/controller_test.go +++ b/mvc/controller_test.go @@ -365,7 +365,9 @@ func (c *testControllerRelPathFromFunc) EndRequest(ctx context.Context) { } func (c *testControllerRelPathFromFunc) Get() {} -func (c *testControllerRelPathFromFunc) GetBy(int64) {} +func (c *testControllerRelPathFromFunc) GetBy(uint64) {} +func (c *testControllerRelPathFromFunc) GetUint8RatioBy(uint8) {} +func (c *testControllerRelPathFromFunc) GetInt64RatioBy(int64) {} func (c *testControllerRelPathFromFunc) GetAnythingByWildcard(string) {} func (c *testControllerRelPathFromFunc) GetLogin() {} @@ -375,8 +377,10 @@ func (c *testControllerRelPathFromFunc) GetAdminLogin() {} func (c *testControllerRelPathFromFunc) PutSomethingIntoThis() {} -func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {} -func (c *testControllerRelPathFromFunc) GetSomethingByBy(string, int) {} +func (c *testControllerRelPathFromFunc) GetSomethingBy(bool) {} + +func (c *testControllerRelPathFromFunc) GetSomethingByBy(string, int) {} + func (c *testControllerRelPathFromFunc) GetSomethingNewBy(string, int) {} // two input arguments, one By which is the latest word. func (c *testControllerRelPathFromFunc) GetSomethingByElseThisBy(bool, int) {} // two input arguments @@ -388,8 +392,13 @@ func TestControllerRelPathFromFunc(t *testing.T) { e.GET("/").Expect().Status(iris.StatusOK). Body().Equal("GET:/") - e.GET("/42").Expect().Status(iris.StatusOK). - Body().Equal("GET:/42") + e.GET("/18446744073709551615").Expect().Status(iris.StatusOK). + Body().Equal("GET:/18446744073709551615") + e.GET("/uint8/ratio/255").Expect().Status(iris.StatusOK). + Body().Equal("GET:/uint8/ratio/255") + e.GET("/uint8/ratio/256").Expect().Status(iris.StatusNotFound) + e.GET("/int64/ratio/-42").Expect().Status(iris.StatusOK). + Body().Equal("GET:/int64/ratio/-42") e.GET("/something/true").Expect().Status(iris.StatusOK). Body().Equal("GET:/something/true") e.GET("/something/false").Expect().Status(iris.StatusOK). diff --git a/mvc/param.go b/mvc/param.go index 8b680aeef6..faa6839626 100644 --- a/mvc/param.go +++ b/mvc/param.go @@ -4,8 +4,7 @@ import ( "reflect" "github.com/kataras/iris/context" - "github.com/kataras/iris/core/router/macro" - "github.com/kataras/iris/core/router/macro/interpreter/ast" + "github.com/kataras/iris/macro" ) func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) (values []reflect.Value) { @@ -13,51 +12,40 @@ func getPathParamsForInput(params []macro.TemplateParam, funcIn ...reflect.Type) return } - consumedParams := make(map[int]bool, 0) - for _, in := range funcIn { - for j, p := range params { - if _, consumed := consumedParams[j]; consumed { - continue - } - paramType := p.Type - paramName := p.Name - // fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String()) - if paramType.Assignable(in.Kind()) { - consumedParams[j] = true - // fmt.Printf("param.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String()) - values = append(values, makeFuncParamGetter(paramType, paramName)) - } + // consumedParams := make(map[int]bool, 0) + // for _, in := range funcIn { + // for j, p := range params { + // if _, consumed := consumedParams[j]; consumed { + // continue + // } + + // // fmt.Printf("%s input arg type vs %s param type\n", in.Kind().String(), p.Type.Kind().String()) + // if m := macros.Lookup(p.Type); m != nil && m.GoType == in.Kind() { + // consumedParams[j] = true + // // fmt.Printf("param.go: bind path param func for paramName = '%s' and paramType = '%s'\n", paramName, paramType.String()) + // funcDep, ok := context.ParamResolverByKindAndIndex(m.GoType, p.Index) + // // funcDep, ok := context.ParamResolverByKindAndKey(in.Kind(), paramName) + // if !ok { + // // here we can add a logger about invalid parameter type although it should never happen here + // // unless the end-developer modified the macro/macros with a special type but not the context/ParamResolvers. + // continue + // } + // values = append(values, funcDep) + // } + // } + // } + + for i, param := range params { + if len(funcIn) <= i { + return } - } - - return -} - -func makeFuncParamGetter(paramType ast.ParamType, paramName string) reflect.Value { - var fn interface{} - - switch paramType { - case ast.ParamTypeInt: - fn = func(ctx context.Context) int { - v, _ := ctx.Params().GetInt(paramName) - return v - } - case ast.ParamTypeLong: - fn = func(ctx context.Context) int64 { - v, _ := ctx.Params().GetInt64(paramName) - return v - } - case ast.ParamTypeBoolean: - fn = func(ctx context.Context) bool { - v, _ := ctx.Params().GetBool(paramName) - return v - } - default: - // string, path... - fn = func(ctx context.Context) string { - return ctx.Params().Get(paramName) + funcDep, ok := context.ParamResolverByTypeAndIndex(funcIn[i], param.Index) + if !ok { + continue } + + values = append(values, funcDep) } - return reflect.ValueOf(fn) + return } diff --git a/sessions/sessiondb/badger/database.go b/sessions/sessiondb/badger/database.go index 223deafeea..5c990e064b 100644 --- a/sessions/sessiondb/badger/database.go +++ b/sessions/sessiondb/badger/database.go @@ -7,11 +7,11 @@ import ( "sync/atomic" "time" - "github.com/kataras/golog" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" "github.com/dgraph-io/badger" + "github.com/kataras/golog" ) // DefaultFileMode used as the default database's "fileMode" @@ -144,7 +144,13 @@ func (db *Database) Get(sid string, key string) (value interface{}) { if err != nil { return err } - // item.ValueCopy + + // return item.Value(func(valueBytes []byte) { + // if err := sessions.DefaultTranscoder.Unmarshal(valueBytes, &value); err != nil { + // golog.Error(err) + // } + // }) + valueBytes, err := item.Value() if err != nil { return err @@ -173,13 +179,25 @@ func (db *Database) Visit(sid string, cb func(key string, value interface{})) { for iter.Rewind(); iter.ValidForPrefix(prefix); iter.Next() { item := iter.Item() + var value interface{} + + // err := item.Value(func(valueBytes []byte) { + // if err := sessions.DefaultTranscoder.Unmarshal(valueBytes, &value); err != nil { + // golog.Error(err) + // } + // }) + + // if err != nil { + // golog.Error(err) + // continue + // } + valueBytes, err := item.Value() if err != nil { golog.Error(err) continue } - var value interface{} if err = sessions.DefaultTranscoder.Unmarshal(valueBytes, &value); err != nil { golog.Error(err) continue diff --git a/sessions/sessiondb/badger/vendor/badger.txt b/sessions/sessiondb/badger/vendor/badger.txt new file mode 100644 index 0000000000..f890a03872 --- /dev/null +++ b/sessions/sessiondb/badger/vendor/badger.txt @@ -0,0 +1,5 @@ +02 Oct 2018 +----------- +Keep this vendor because go modules uses the v1.5.4 which is the latest published release(~19 Sep 2018) +while the master repo will be used if host's golang version is lower than 1.11 or inside $GOPATH, which +has a breaking change on the `item.Value` input & output values, see the comments inside this `database.go`. \ No newline at end of file diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt deleted file mode 100644 index 12081a43fd..0000000000 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger.txt +++ /dev/null @@ -1,3 +0,0 @@ -27 Dec 2017 - -Commit: 0225b784d8dfc250b3be597fa806f0fd677a6628 \ No newline at end of file diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/backup.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/backup.go index 375b5a3162..4649de25b7 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/backup.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/backup.go @@ -1,9 +1,26 @@ +/* + * Copyright 2017 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package badger import ( "bufio" "encoding/binary" "io" + "log" "sync" "github.com/dgraph-io/badger/y" @@ -36,6 +53,8 @@ func (db *DB) Backup(w io.Writer, since uint64) (uint64, error) { opts := DefaultIteratorOptions opts.AllVersions = true it := txn.NewIterator(opts) + defer it.Close() + for it.Rewind(); it.Valid(); it.Next() { item := it.Item() if item.Version() < since { @@ -44,7 +63,8 @@ func (db *DB) Backup(w io.Writer, since uint64) (uint64, error) { } val, err := item.Value() if err != nil { - return err + log.Printf("Key [%x]. Error while fetching value [%v]\n", item.Key(), err) + continue } entry := &protos.KVPair{ @@ -68,7 +88,7 @@ func (db *DB) Backup(w io.Writer, since uint64) (uint64, error) { // Load reads a protobuf-encoded list of all entries from a reader and writes // them to the database. This can be used to restore the database from a backup -// made by calling DB.Dump(). +// made by calling DB.Backup(). // // DB.Load() should be called on a database that is not running any other // concurrent transactions while it is running. @@ -124,12 +144,17 @@ func (db *DB) Load(r io.Reader) error { UserMeta: e.UserMeta[0], ExpiresAt: e.ExpiresAt, }) + // Update nextCommit, memtable stores this timestamp in badger head + // when flushed. + if e.Version >= db.orc.commitTs() { + db.orc.nextCommit = e.Version + 1 + } if len(entries) == 1000 { if err := batchSetAsyncIfNoErr(entries); err != nil { return err } - entries = entries[:0] + entries = make([]*Entry, 0, 1000) } } @@ -145,6 +170,7 @@ func (db *DB) Load(r io.Reader) error { case err := <-errChan: return err default: + db.orc.curRead = db.orc.commitTs() - 1 return nil } } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/compaction.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/compaction.go index 23824ae175..00be8f6efa 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/compaction.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/compaction.go @@ -20,6 +20,7 @@ import ( "bytes" "fmt" "log" + "math" "sync" "golang.org/x/net/trace" @@ -37,7 +38,7 @@ type keyRange struct { var infRange = keyRange{inf: true} func (r keyRange) String() string { - return fmt.Sprintf("[left=%q, right=%q, inf=%v]", r.left, r.right, r.inf) + return fmt.Sprintf("[left=%x, right=%x, inf=%v]", r.left, r.right, r.inf) } func (r keyRange) equals(dst keyRange) bool { @@ -75,7 +76,10 @@ func getKeyRange(tables []*table.Table) keyRange { biggest = tables[i].Biggest() } } - return keyRange{left: smallest, right: biggest} + return keyRange{ + left: y.KeyWithTs(y.ParseKey(smallest), math.MaxUint64), + right: y.KeyWithTs(y.ParseKey(biggest), 0), + } } type levelCompactStatus struct { diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go index f03a5caaba..1bd59e031f 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/db.go @@ -17,8 +17,6 @@ package badger import ( - "bytes" - "container/heap" "encoding/binary" "expvar" "log" @@ -27,6 +25,7 @@ import ( "path/filepath" "strconv" "sync" + "sync/atomic" "time" "github.com/dgraph-io/badger/options" @@ -43,6 +42,7 @@ var ( badgerPrefix = []byte("!badger!") // Prefix for internal keys used by badger. head = []byte("!badger!head") // For storing value offset for replay. txnKey = []byte("!badger!txn") // For indicating end of entries in txn. + badgerMove = []byte("!badger!move") // For key-value pairs which got moved during GC. ) type closers struct { @@ -74,6 +74,8 @@ type DB struct { writeCh chan *request flushChan chan flushTask // For flushing memtables. + blockWrites int32 + orc *oracle } @@ -101,7 +103,7 @@ func replayFunction(out *DB) func(Entry, valuePointer) error { first := true return func(e Entry, vp valuePointer) error { // Function for replaying. if first { - out.elog.Printf("First key=%s\n", e.Key) + out.elog.Printf("First key=%q\n", e.Key) } first = false @@ -168,12 +170,24 @@ func Open(opt Options) (db *DB, err error) { opt.maxBatchSize = (15 * opt.MaxTableSize) / 100 opt.maxBatchCount = opt.maxBatchSize / int64(skl.MaxNodeSize) + if opt.ValueThreshold > math.MaxUint16-16 { + return nil, ErrValueThreshold + } + + if opt.ReadOnly { + // Can't truncate if the DB is read only. + opt.Truncate = false + } + for _, path := range []string{opt.Dir, opt.ValueDir} { dirExists, err := exists(path) if err != nil { return nil, y.Wrapf(err, "Invalid Dir: %q", path) } if !dirExists { + if opt.ReadOnly { + return nil, y.Wrapf(err, "Cannot find Dir for read-only open: %q", path) + } // Try to create the directory err = os.Mkdir(path, 0700) if err != nil { @@ -189,8 +203,8 @@ func Open(opt Options) (db *DB, err error) { if err != nil { return nil, err } - - dirLockGuard, err := acquireDirectoryLock(opt.Dir, lockFile) + var dirLockGuard, valueDirLockGuard *directoryLockGuard + dirLockGuard, err = acquireDirectoryLock(opt.Dir, lockFile, opt.ReadOnly) if err != nil { return nil, err } @@ -199,9 +213,8 @@ func Open(opt Options) (db *DB, err error) { _ = dirLockGuard.release() } }() - var valueDirLockGuard *directoryLockGuard if absValueDir != absDir { - valueDirLockGuard, err = acquireDirectoryLock(opt.ValueDir, lockFile) + valueDirLockGuard, err = acquireDirectoryLock(opt.ValueDir, lockFile, opt.ReadOnly) if err != nil { return nil, err } @@ -218,7 +231,7 @@ func Open(opt Options) (db *DB, err error) { opt.ValueLogLoadingMode == options.MemoryMap) { return nil, ErrInvalidLoadingMode } - manifestFile, manifest, err := openOrCreateManifestFile(opt.Dir) + manifestFile, manifest, err := openOrCreateManifestFile(opt.Dir, opt.ReadOnly) if err != nil { return nil, err } @@ -229,12 +242,12 @@ func Open(opt Options) (db *DB, err error) { }() orc := &oracle{ - isManaged: opt.managedTxns, - nextCommit: 1, - pendingCommits: make(map[uint64]struct{}), - commits: make(map[uint64]uint64), + isManaged: opt.managedTxns, + nextCommit: 1, + commits: make(map[uint64]uint64), + readMark: y.WaterMark{}, } - heap.Init(&orc.commitMark) + orc.readMark.Init() db = &DB{ imm: make([]*skl.Skiplist, 0, opt.NumMemtables), @@ -259,11 +272,13 @@ func Open(opt Options) (db *DB, err error) { return nil, err } - db.closers.compactors = y.NewCloser(1) - db.lc.startCompact(db.closers.compactors) + if !opt.ReadOnly { + db.closers.compactors = y.NewCloser(1) + db.lc.startCompact(db.closers.compactors) - db.closers.memtable = y.NewCloser(1) - go db.flushMemtable(db.closers.memtable) // Need levels controller to be up. + db.closers.memtable = y.NewCloser(1) + go db.flushMemtable(db.closers.memtable) // Need levels controller to be up. + } if err = db.vlog.Open(db, opt); err != nil { return nil, err @@ -319,7 +334,8 @@ func Open(opt Options) (db *DB, err error) { } // Close closes a DB. It's crucial to call it to ensure all the pending updates -// make their way to disk. +// make their way to disk. Calling DB.Close() multiple times is not safe and would +// cause panic. func (db *DB) Close() (err error) { db.elog.Printf("Closing database") // Stop value GC first. @@ -366,11 +382,31 @@ func (db *DB) Close() (err error) { } db.flushChan <- flushTask{nil, valuePointer{}} // Tell flusher to quit. - db.closers.memtable.Wait() - db.elog.Printf("Memtable flushed") + if db.closers.memtable != nil { + db.closers.memtable.Wait() + db.elog.Printf("Memtable flushed") + } + if db.closers.compactors != nil { + db.closers.compactors.SignalAndWait() + db.elog.Printf("Compaction finished") + } - db.closers.compactors.SignalAndWait() - db.elog.Printf("Compaction finished") + // Force Compact L0 + // We don't need to care about cstatus since no parallel compaction is running. + cd := compactDef{ + elog: trace.New("Badger", "Compact"), + thisLevel: db.lc.levels[0], + nextLevel: db.lc.levels[1], + } + cd.elog.SetMaxEvents(100) + defer cd.elog.Finish() + if db.lc.fillTablesL0(&cd) { + if err := db.lc.runCompactDef(0, cd); err != nil { + cd.elog.LazyPrintf("\tLOG Compact FAILED with error: %+v: %+v", err, cd) + } + } else { + cd.elog.LazyPrintf("fillTables failed for level zero. No compaction required") + } if lcErr := db.lc.close(); err == nil { err = errors.Wrap(lcErr, "DB.Close") @@ -380,8 +416,10 @@ func (db *DB) Close() (err error) { db.elog.Finish() - if guardErr := db.dirLockGuard.release(); err == nil { - err = errors.Wrap(guardErr, "DB.Close") + if db.dirLockGuard != nil { + if guardErr := db.dirLockGuard.release(); err == nil { + err = errors.Wrap(guardErr, "DB.Close") + } } if db.valueDirGuard != nil { if guardErr := db.valueDirGuard.release(); err == nil { @@ -451,33 +489,25 @@ func (db *DB) getMemTables() ([]*skl.Skiplist, func()) { // get returns the value in memtable or disk for given key. // Note that value will include meta byte. +// +// IMPORTANT: We should never write an entry with an older timestamp for the same key, We need to +// maintain this invariant to search for the latest value of a key, or else we need to search in all +// tables and find the max version among them. To maintain this invariant, we also need to ensure +// that all versions of a key are always present in the same table from level 1, because compaction +// can push any table down. func (db *DB) get(key []byte) (y.ValueStruct, error) { tables, decr := db.getMemTables() // Lock should be released. defer decr() y.NumGets.Add(1) - version := y.ParseTs(key) - var maxVs y.ValueStruct - // Need to search for values in all tables, with managed db - // latest value needn't be present in the latest table. - // Even without managed db, purging can cause this constraint - // to be violated. - // Search until required version is found or iterate over all - // tables and return max version. for i := 0; i < len(tables); i++ { vs := tables[i].Get(key) y.NumMemtableGets.Add(1) - if vs.Meta == 0 && vs.Value == nil { - continue - } - if vs.Version == version { + if vs.Meta != 0 || vs.Value != nil { return vs, nil } - if maxVs.Version < vs.Version { - maxVs = vs - } } - return db.lc.get(key, maxVs) + return db.lc.get(key) } func (db *DB) updateOffset(ptrs []valuePointer) { @@ -568,8 +598,12 @@ func (db *DB) writeRequests(reqs []*request) error { continue } count += len(b.Entries) - for err := db.ensureRoomForWrite(); err != nil; err = db.ensureRoomForWrite() { - db.elog.Printf("Making room for writes") + var i uint64 + for err := db.ensureRoomForWrite(); err == errNoRoom; err = db.ensureRoomForWrite() { + i++ + if i%100 == 0 { + db.elog.Printf("Making room for writes") + } // We need to poll a bit because both hasRoomForWrite and the flusher need access to s.imm. // When flushChan is full and you are blocked there, and the flusher is trying to update s.imm, // you will get a deadlock. @@ -591,6 +625,9 @@ func (db *DB) writeRequests(reqs []*request) error { } func (db *DB) sendToWriteCh(entries []*Entry) (*request, error) { + if atomic.LoadInt32(&db.blockWrites) == 1 { + return nil, ErrBlockedWrites + } var count, size int64 for _, e := range entries { size += int64(e.estimateSize(db.opt.ValueThreshold)) @@ -681,11 +718,7 @@ func (db *DB) batchSet(entries []*Entry) error { return err } - req.Wg.Wait() - req.Entries = nil - err = req.Err - requestPool.Put(req) - return err + return req.Wait() } // batchSetAsync is the asynchronous version of batchSet. It accepts a callback @@ -700,10 +733,7 @@ func (db *DB) batchSetAsync(entries []*Entry, f func(error)) error { return err } go func() { - req.Wg.Wait() - err := req.Err - req.Entries = nil - requestPool.Put(req) + err := req.Wait() // Write is complete. Let's call the callback function now. f(err) }() @@ -748,7 +778,7 @@ func arenaSize(opt Options) int64 { return opt.MaxTableSize + opt.maxBatchSize + opt.maxBatchCount*int64(skl.MaxNodeSize) } -// WriteLevel0Table flushes memtable. It drops deleteValues. +// WriteLevel0Table flushes memtable. func writeLevel0Table(s *skl.Skiplist, f *os.File) error { iter := s.NewIterator() defer iter.Close() @@ -768,6 +798,8 @@ type flushTask struct { vptr valuePointer } +// TODO: Ensure that this function doesn't return, or is handled by another wrapper function. +// Otherwise, we would have no goroutine which can flush memtables. func (db *DB) flushMemtable(lc *y.Closer) error { defer lc.Done() @@ -824,7 +856,12 @@ func (db *DB) flushMemtable(lc *y.Closer) error { // Update s.imm. Need a lock. db.Lock() - y.AssertTrue(ft.mt == db.imm[0]) //For now, single threaded. + // This is a single-threaded operation. ft.mt corresponds to the head of + // db.imm list. Once we flush it, we advance db.imm. The next ft.mt + // which would arrive here would match db.imm[0], because we acquire a + // lock over DB when pushing to flushChan. + // TODO: This logic is dirty AF. Any change and this could easily break. + y.AssertTrue(ft.mt == db.imm[0]) db.imm = db.imm[1:] ft.mt.DecrRef() // Return memory. db.Unlock() @@ -879,7 +916,6 @@ func (db *DB) calculateSize() { _, vlogSize = totalSize(db.opt.ValueDir) } y.VlogSize.Set(db.opt.Dir, newInt(vlogSize)) - } func (db *DB) updateSize(lc *y.Closer) { @@ -898,141 +934,14 @@ func (db *DB) updateSize(lc *y.Closer) { } } -// PurgeVersionsBelow will delete all versions of a key below the specified version -func (db *DB) PurgeVersionsBelow(key []byte, ts uint64) error { - txn := db.NewTransaction(false) - defer txn.Discard() - return db.purgeVersionsBelow(txn, key, ts) -} - -func (db *DB) purgeVersionsBelow(txn *Txn, key []byte, ts uint64) error { - opts := DefaultIteratorOptions - opts.AllVersions = true - opts.PrefetchValues = false - it := txn.NewIterator(opts) - defer it.Close() - - var entries []*Entry - - for it.Seek(key); it.ValidForPrefix(key); it.Next() { - item := it.Item() - if !bytes.Equal(key, item.Key()) || item.Version() >= ts { - continue - } - if isDeletedOrExpired(item.meta, item.ExpiresAt()) { - continue - } - - // Found an older version. Mark for deletion - entries = append(entries, - &Entry{ - Key: y.KeyWithTs(key, item.version), - meta: bitDelete, - }) - db.vlog.updateGCStats(item) - } - return db.batchSet(entries) -} - -// PurgeOlderVersions deletes older versions of all keys. -// -// This function could be called prior to doing garbage collection to clean up -// older versions that are no longer needed. The caller must make sure that -// there are no long-running read transactions running before this function is -// called, otherwise they will not work as expected. -func (db *DB) PurgeOlderVersions() error { - return db.View(func(txn *Txn) error { - opts := DefaultIteratorOptions - opts.AllVersions = true - opts.PrefetchValues = false - it := txn.NewIterator(opts) - defer it.Close() - - var entries []*Entry - var lastKey []byte - var count, size int - var wg sync.WaitGroup - errChan := make(chan error, 1) - - // func to check for pending error before sending off a batch for writing - batchSetAsyncIfNoErr := func(entries []*Entry) error { - select { - case err := <-errChan: - return err - default: - wg.Add(1) - return txn.db.batchSetAsync(entries, func(err error) { - defer wg.Done() - if err != nil { - select { - case errChan <- err: - default: - } - } - }) - } - } - - for it.Rewind(); it.Valid(); it.Next() { - item := it.Item() - if !bytes.Equal(lastKey, item.Key()) { - lastKey = y.SafeCopy(lastKey, item.Key()) - continue - } - if isDeletedOrExpired(item.meta, item.ExpiresAt()) { - continue - } - // Found an older version. Mark for deletion - e := &Entry{ - Key: y.KeyWithTs(lastKey, item.version), - meta: bitDelete, - } - db.vlog.updateGCStats(item) - curSize := e.estimateSize(db.opt.ValueThreshold) - - // Batch up min(1000, maxBatchCount) entries at a time and write - // Ensure that total batch size doesn't exceed maxBatchSize - if count == 1000 || count+1 >= int(db.opt.maxBatchCount) || - size+curSize >= int(db.opt.maxBatchSize) { - if err := batchSetAsyncIfNoErr(entries); err != nil { - return err - } - count = 0 - size = 0 - entries = []*Entry{} - } - size += curSize - count++ - entries = append(entries, e) - } - - // Write last batch pending deletes - if count > 0 { - if err := batchSetAsyncIfNoErr(entries); err != nil { - return err - } - } - - wg.Wait() - - select { - case err := <-errChan: - return err - default: - return nil - } - }) -} - // RunValueLogGC triggers a value log garbage collection. // // It picks value log files to perform GC based on statistics that are collected -// duing the session, when DB.PurgeOlderVersions() and DB.PurgeVersions() is -// called. If no such statistics are available, then log files are picked in -// random order. The process stops as soon as the first log file is encountered -// which does not result in garbage collection. +// duing compactions. If no such statistics are available, then log files are +// picked in random order. The process stops as soon as the first log file is +// encountered which does not result in garbage collection. // -// When a log file is picked, it is first sampled If the sample shows that we +// When a log file is picked, it is first sampled. If the sample shows that we // can discard at least discardRatio space of that file, it would be rewritten. // // If a call to RunValueLogGC results in no rewrites, then an ErrNoRewrite is @@ -1060,8 +969,7 @@ func (db *DB) RunValueLogGC(discardRatio float64) error { // Find head on disk headKey := y.KeyWithTs(head, math.MaxUint64) // Need to pass with timestamp, lsm get removes the last 8 bytes and compares key - var maxVs y.ValueStruct - val, err := db.lc.get(headKey, maxVs) + val, err := db.lc.get(headKey) if err != nil { return errors.Wrap(err, "Retrieving head from on-disk LSM") } @@ -1179,14 +1087,27 @@ func (db *DB) GetSequence(key []byte, bandwidth uint64) (*Sequence, error) { return seq, err } +func (db *DB) Tables() []TableInfo { + return db.lc.getTableInfo() +} + +// MaxBatchCount returns max possible entries in batch +func (db *DB) MaxBatchCount() int64 { + return db.opt.maxBatchCount +} + +// MaxBatchCount returns max possible batch size +func (db *DB) MaxBatchSize() int64 { + return db.opt.maxBatchSize +} + // MergeOperator represents a Badger merge operator. type MergeOperator struct { sync.RWMutex - f MergeFunc - db *DB - key []byte - skipAtOrBelow uint64 - closer *y.Closer + f MergeFunc + db *DB + key []byte + closer *y.Closer } // MergeFunc accepts two byte slices, one representing an existing value, and @@ -1214,66 +1135,67 @@ func (db *DB) GetMergeOperator(key []byte, return op } -func (op *MergeOperator) iterateAndMerge(txn *Txn) (maxVersion uint64, val []byte, err error) { +var errNoMerge = errors.New("No need for merge") + +func (op *MergeOperator) iterateAndMerge(txn *Txn) (val []byte, err error) { opt := DefaultIteratorOptions opt.AllVersions = true it := txn.NewIterator(opt) - var first bool + defer it.Close() + + var numVersions int for it.Rewind(); it.ValidForPrefix(op.key); it.Next() { item := it.Item() - if item.Version() <= op.skipAtOrBelow { - continue - } - if item.Version() > maxVersion { - maxVersion = item.Version() - } - if !first { - first = true + numVersions++ + if numVersions == 1 { val, err = item.ValueCopy(val) if err != nil { - return 0, nil, err + return nil, err } } else { newVal, err := item.Value() if err != nil { - return 0, nil, err + return nil, err } val = op.f(val, newVal) } + if item.DiscardEarlierVersions() { + break + } } - if !first { - return 0, nil, ErrKeyNotFound + if numVersions == 0 { + return nil, ErrKeyNotFound + } else if numVersions == 1 { + return val, errNoMerge } - return maxVersion, val, nil + return val, nil } func (op *MergeOperator) compact() error { op.Lock() defer op.Unlock() - var maxVersion uint64 err := op.db.Update(func(txn *Txn) error { var ( val []byte err error ) - maxVersion, val, err = op.iterateAndMerge(txn) + val, err = op.iterateAndMerge(txn) if err != nil { return err } // Write value back to db - if maxVersion > op.skipAtOrBelow { - if err := txn.Set(op.key, val); err != nil { - return err - } + if err := txn.SetWithDiscard(op.key, val, 0); err != nil { + return err } return nil }) - if err != nil && err != ErrKeyNotFound { // Ignore ErrKeyNotFound errors during compaction + + if err == ErrKeyNotFound || err == errNoMerge { + // pass. + } else if err != nil { return err } - // Update version - op.skipAtOrBelow = maxVersion return nil } @@ -1287,16 +1209,9 @@ func (op *MergeOperator) runCompactions(dur time.Duration) { stop = true case <-ticker.C: // wait for tick } - oldSkipVersion := op.skipAtOrBelow if err := op.compact(); err != nil { log.Printf("Error while running merge operation: %s", err) } - // Purge older versions if version has updated - if op.skipAtOrBelow > oldSkipVersion { - if err := op.db.PurgeVersionsBelow(op.key, op.skipAtOrBelow+1); err != nil { - log.Printf("Error purging merged keys: %s", err) - } - } if stop { ticker.Stop() break @@ -1315,15 +1230,18 @@ func (op *MergeOperator) Add(val []byte) error { // Get returns the latest value for the merge operator, which is derived by // applying the merge function to all the values added so far. // -// If Add has not been called even once, Get will return ErrKeyNotFound +// If Add has not been called even once, Get will return ErrKeyNotFound. func (op *MergeOperator) Get() ([]byte, error) { op.RLock() defer op.RUnlock() var existing []byte err := op.db.View(func(txn *Txn) (err error) { - _, existing, err = op.iterateAndMerge(txn) + existing, err = op.iterateAndMerge(txn) return err }) + if err == errNoMerge { + return existing, nil + } return existing, err } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_unix.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_unix.go index a058f45438..a5e0fa33c5 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_unix.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_unix.go @@ -35,11 +35,14 @@ type directoryLockGuard struct { f *os.File // The absolute path to our pid file. path string + // Was this a shared lock for a read-only database? + readOnly bool } -// acquireDirectoryLock gets an exclusive lock on the directory (using flock). It writes our pid -// to dirPath/pidFileName for convenience. -func acquireDirectoryLock(dirPath string, pidFileName string) (*directoryLockGuard, error) { +// acquireDirectoryLock gets a lock on the directory (using flock). If +// this is not read-only, it will also write our pid to +// dirPath/pidFileName for convenience. +func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) { // Convert to absolute path so that Release still works even if we do an unbalanced // chdir in the meantime. absPidFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) @@ -50,7 +53,12 @@ func acquireDirectoryLock(dirPath string, pidFileName string) (*directoryLockGua if err != nil { return nil, errors.Wrapf(err, "cannot open directory %q", dirPath) } - err = unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB) + opts := unix.LOCK_EX | unix.LOCK_NB + if readOnly { + opts = unix.LOCK_SH | unix.LOCK_NB + } + + err = unix.Flock(int(f.Fd()), opts) if err != nil { f.Close() return nil, errors.Wrapf(err, @@ -58,22 +66,27 @@ func acquireDirectoryLock(dirPath string, pidFileName string) (*directoryLockGua dirPath) } - // Yes, we happily overwrite a pre-existing pid file. We're the only badger process using this - // directory. - err = ioutil.WriteFile(absPidFilePath, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666) - if err != nil { - f.Close() - return nil, errors.Wrapf(err, - "Cannot write pid file %q", absPidFilePath) + if !readOnly { + // Yes, we happily overwrite a pre-existing pid file. We're the + // only read-write badger process using this directory. + err = ioutil.WriteFile(absPidFilePath, []byte(fmt.Sprintf("%d\n", os.Getpid())), 0666) + if err != nil { + f.Close() + return nil, errors.Wrapf(err, + "Cannot write pid file %q", absPidFilePath) + } } - - return &directoryLockGuard{f, absPidFilePath}, nil + return &directoryLockGuard{f, absPidFilePath, readOnly}, nil } // Release deletes the pid file and releases our lock on the directory. func (guard *directoryLockGuard) release() error { - // It's important that we remove the pid file first. - err := os.Remove(guard.path) + var err error + if !guard.readOnly { + // It's important that we remove the pid file first. + err = os.Remove(guard.path) + } + if closeErr := guard.f.Close(); err == nil { err = closeErr } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_windows.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_windows.go index 36d599fd03..80de84e16f 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_windows.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/dir_windows.go @@ -57,7 +57,11 @@ type directoryLockGuard struct { } // AcquireDirectoryLock acquires exclusive access to a directory. -func acquireDirectoryLock(dirPath string, pidFileName string) (*directoryLockGuard, error) { +func acquireDirectoryLock(dirPath string, pidFileName string, readOnly bool) (*directoryLockGuard, error) { + if readOnly { + return nil, ErrWindowsNotSupported + } + // Convert to absolute path so that Release still works even if we do an unbalanced // chdir in the meantime. absLockFilePath, err := filepath.Abs(filepath.Join(dirPath, pidFileName)) diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/doc.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/doc.go deleted file mode 100644 index 83dc9a28ac..0000000000 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/doc.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -Package badger implements an embeddable, simple and fast key-value database, -written in pure Go. It is designed to be highly performant for both reads and -writes simultaneously. Badger uses Multi-Version Concurrency Control (MVCC), and -supports transactions. It runs transactions concurrently, with serializable -snapshot isolation guarantees. - -Badger uses an LSM tree along with a value log to separate keys from values, -hence reducing both write amplification and the size of the LSM tree. This -allows LSM tree to be served entirely from RAM, while the values are served -from SSD. - - -Usage - -Badger has the following main types: DB, Txn, Item and Iterator. DB contains -keys that are associated with values. It must be opened with the appropriate -options before it can be accessed. - -All operations happen inside a Txn. Txn represents a transaction, which can -be read-only or read-write. Read-only transactions can read values for a -given key (which are returned inside an Item), or iterate over a set of -key-value pairs using an Iterator (which are returned as Item type values as -well). Read-write transactions can also update and delete keys from the DB. - -See the examples for more usage details. -*/ -package badger diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go index b03a24f892..1de35826e9 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/errors.go @@ -27,6 +27,10 @@ var ( // range. ErrValueLogSize = errors.New("Invalid ValueLogFileSize, must be between 1MB and 2GB") + // ErrValueThreshold is returned when ValueThreshold is set to a value close to or greater than + // uint16. + ErrValueThreshold = errors.New("Invalid ValueThreshold, must be lower than uint16.") + // ErrKeyNotFound is returned when key isn't found on a txn.Get. ErrKeyNotFound = errors.New("Key not found") @@ -81,9 +85,25 @@ var ( // ErrInvalidLoadingMode is returned when opt.ValueLogLoadingMode option is not // within the valid range ErrInvalidLoadingMode = errors.New("Invalid ValueLogLoadingMode, must be FileIO or MemoryMap") + + // ErrReplayNeeded is returned when opt.ReadOnly is set but the + // database requires a value log replay. + ErrReplayNeeded = errors.New("Database was not properly closed, cannot open read-only") + + // ErrWindowsNotSupported is returned when opt.ReadOnly is used on Windows + ErrWindowsNotSupported = errors.New("Read-only mode is not supported on Windows") + + // ErrTruncateNeeded is returned when the value log gets corrupt, and requires truncation of + // corrupt data to allow Badger to run properly. + ErrTruncateNeeded = errors.New("Value log truncate required to run DB. This might result in data loss.") + + // ErrBlockedWrites is returned if the user called DropAll. During the process of dropping all + // data from Badger, we stop accepting new writes, by returning this error. + ErrBlockedWrites = errors.New("Writes are blocked possibly due to DropAll") ) -const maxKeySize = 1 << 16 // Key length can't be more than uint16, as determined by table::header. +// Key length can't be more than uint16, as determined by table::header. +const maxKeySize = 1<<16 - 8 // 8 bytes are for storing timestamp func exceedsMaxKeySizeError(key []byte) error { return errors.Errorf("Key with size %d exceeded %d limit. Key:\n%s", diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go index c7ce634655..3a6bec9819 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/iterator.go @@ -20,6 +20,7 @@ import ( "bytes" "fmt" "sync" + "sync/atomic" "time" "github.com/dgraph-io/badger/options" @@ -53,20 +54,32 @@ type Item struct { txn *Txn } -// ToString returns a string representation of Item -func (item *Item) ToString() string { +// String returns a string representation of Item +func (item *Item) String() string { return fmt.Sprintf("key=%q, version=%d, meta=%x", item.Key(), item.Version(), item.meta) +} +// Deprecated +// ToString returns a string representation of Item +func (item *Item) ToString() string { + return item.String() } // Key returns the key. // // Key is only valid as long as item is valid, or transaction is valid. If you need to use it -// outside its validity, please copy it. +// outside its validity, please use KeyCopy func (item *Item) Key() []byte { return item.key } +// KeyCopy returns a copy of the key of the item, writing it to dst slice. +// If nil is passed, or capacity of dst isn't sufficient, a new slice would be allocated and +// returned. +func (item *Item) KeyCopy(dst []byte) []byte { + return y.SafeCopy(dst, item.key) +} + // Version returns the commit timestamp of the item. func (item *Item) Version() uint64 { return item.version @@ -80,7 +93,8 @@ func (item *Item) Version() uint64 { // reused. // // If you need to use a value outside a transaction, please use Item.ValueCopy -// instead, or copy it yourself. +// instead, or copy it yourself. Value might change once discard or commit is called. +// Use ValueCopy if you want to do a Set after Get. func (item *Item) Value() ([]byte, error) { item.wg.Wait() if item.status == prefetched { @@ -117,24 +131,70 @@ func (item *Item) hasValue() bool { return true } +// IsDeletedOrExpired returns true if item contains deleted or expired value. +func (item *Item) IsDeletedOrExpired() bool { + return isDeletedOrExpired(item.meta, item.expiresAt) +} + +func (item *Item) DiscardEarlierVersions() bool { + return item.meta&bitDiscardEarlierVersions > 0 +} + func (item *Item) yieldItemValue() ([]byte, func(), error) { - if !item.hasValue() { - return nil, nil, nil - } + key := item.Key() // No need to copy. + for { + if !item.hasValue() { + return nil, nil, nil + } - if item.slice == nil { - item.slice = new(y.Slice) - } + if item.slice == nil { + item.slice = new(y.Slice) + } - if (item.meta & bitValuePointer) == 0 { - val := item.slice.Resize(len(item.vptr)) - copy(val, item.vptr) - return val, nil, nil - } + if (item.meta & bitValuePointer) == 0 { + val := item.slice.Resize(len(item.vptr)) + copy(val, item.vptr) + return val, nil, nil + } - var vp valuePointer - vp.Decode(item.vptr) - return item.db.vlog.Read(vp, item.slice) + var vp valuePointer + vp.Decode(item.vptr) + result, cb, err := item.db.vlog.Read(vp, item.slice) + if err != ErrRetry { + return result, cb, err + } + if bytes.HasPrefix(key, badgerMove) { + // err == ErrRetry + // Error is retry even after checking the move keyspace. So, let's + // just assume that value is not present. + return nil, cb, nil + } + + // The value pointer is pointing to a deleted value log. Look for the + // move key and read that instead. + runCallback(cb) + // Do not put badgerMove on the left in append. It seems to cause some sort of manipulation. + key = append([]byte{}, badgerMove...) + key = append(key, y.KeyWithTs(item.Key(), item.Version())...) + // Note that we can't set item.key to move key, because that would + // change the key user sees before and after this call. Also, this move + // logic is internal logic and should not impact the external behavior + // of the retrieval. + vs, err := item.db.get(key) + if err != nil { + return nil, nil, err + } + if vs.Version != item.Version() { + return nil, nil, nil + } + // Bug fix: Always copy the vs.Value into vptr here. Otherwise, when item is reused this + // slice gets overwritten. + item.vptr = y.SafeCopy(item.vptr, vs.Value) + item.meta &^= bitValuePointer // Clear the value pointer bit. + if vs.Meta&bitValuePointer > 0 { + item.meta |= bitValuePointer // This meta would only be about value pointer. + } + } } func runCallback(cb func()) { @@ -235,6 +295,8 @@ type IteratorOptions struct { PrefetchSize int Reverse bool // Direction of iteration. False is forward, true is backward. AllVersions bool // Fetch all valid versions of the same key. + + internalAccess bool // Used to allow internal access to badger keys. } // DefaultIteratorOptions contains default options when iterating over Badger key-value stores. @@ -264,6 +326,10 @@ type Iterator struct { // Using prefetch is highly recommended if you're doing a long running iteration. // Avoid long running iterations in update transactions. func (txn *Txn) NewIterator(opt IteratorOptions) *Iterator { + if atomic.AddInt32(&txn.numIterators, 1) > 1 { + panic("Only one iterator can be active at one time.") + } + tables, decr := txn.db.getMemTables() defer decr() txn.db.vlog.incrIteratorCount() @@ -315,8 +381,22 @@ func (it *Iterator) ValidForPrefix(prefix []byte) bool { // Close would close the iterator. It is important to call this when you're done with iteration. func (it *Iterator) Close() { it.iitr.Close() + + // It is important to wait for the fill goroutines to finish. Otherwise, we might leave zombie + // goroutines behind, which are waiting to acquire file read locks after DB has been closed. + waitFor := func(l list) { + item := l.pop() + for item != nil { + item.wg.Wait() + item = l.pop() + } + } + waitFor(it.waste) + waitFor(it.data) + // TODO: We could handle this error. _ = it.txn.db.vlog.decrIteratorCount() + atomic.AddInt32(&it.txn.numIterators, -1) } // Next would advance the iterator by one. Always check it.Valid() after a Next() @@ -367,7 +447,7 @@ func (it *Iterator) parseItem() bool { } // Skip badger keys. - if bytes.HasPrefix(key, badgerPrefix) { + if !it.opt.internalAccess && bytes.HasPrefix(key, badgerPrefix) { mi.Next() return false } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go index 55002b4fa6..31b7fe6bfd 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/levels.go @@ -18,6 +18,7 @@ package badger import ( "fmt" + "math" "math/rand" "os" "sort" @@ -104,7 +105,11 @@ func newLevelsController(kv *DB, mf *Manifest) (*levelsController, error) { var maxFileID uint64 for fileID, tableManifest := range mf.Tables { fname := table.NewFilename(fileID, kv.opt.Dir) - fd, err := y.OpenExistingSyncedFile(fname, true) + var flags uint32 = y.Sync + if kv.opt.ReadOnly { + flags |= y.ReadOnly + } + fd, err := y.OpenExistingFile(fname, flags) if err != nil { closeAllTables(tables) return nil, errors.Wrapf(err, "Opening file: %q", fname) @@ -165,6 +170,62 @@ func (s *levelsController) cleanupLevels() error { return firstErr } +// This function picks all tables from all levels, creates a manifest changeset, +// applies it, and then decrements the refs of these tables, which would result +// in their deletion. It spares one table from L0, to keep the badgerHead key +// persisted, so we don't lose where we are w.r.t. value log. +// NOTE: This function in itself isn't sufficient to completely delete all the +// data. After this, one would still need to iterate over the KV pairs and mark +// them as deleted. +func (s *levelsController) deleteLSMTree() (int, error) { + var all []*table.Table + var keepOne *table.Table + for _, l := range s.levels { + l.RLock() + if l.level == 0 && len(l.tables) > 1 { + // Skip the last table. We do this to keep the badgerMove key persisted. + lastIdx := len(l.tables) - 1 + keepOne = l.tables[lastIdx] + all = append(all, l.tables[:lastIdx]...) + } else { + all = append(all, l.tables...) + } + l.RUnlock() + } + if len(all) == 0 { + return 0, nil + } + + // Generate the manifest changes. + changes := []*protos.ManifestChange{} + for _, table := range all { + changes = append(changes, makeTableDeleteChange(table.ID())) + } + changeSet := protos.ManifestChangeSet{Changes: changes} + if err := s.kv.manifest.addChanges(changeSet.Changes); err != nil { + return 0, err + } + + for _, l := range s.levels { + l.Lock() + l.totalSize = 0 + if l.level == 0 && len(l.tables) > 1 { + l.tables = []*table.Table{keepOne} + l.totalSize += keepOne.Size() + } else { + l.tables = l.tables[:0] + } + l.Unlock() + } + // Now allow deletion of tables. + for _, table := range all { + if err := table.DecrRef(); err != nil { + return 0, err + } + } + return len(all), nil +} + func (s *levelsController) startCompact(lc *y.Closer) { n := s.kv.opt.NumCompactors lc.AddRunning(n - 1) @@ -258,6 +319,35 @@ func (s *levelsController) compactBuildTables( topTables := cd.top botTables := cd.bot + var hasOverlap bool + { + kr := getKeyRange(cd.top) + for i, lh := range s.levels { + if i <= l { // Skip upper levels. + continue + } + lh.RLock() + left, right := lh.overlappingTables(levelHandlerRLocked{}, kr) + lh.RUnlock() + if right-left > 0 { + hasOverlap = true + break + } + } + cd.elog.LazyPrintf("Key range overlaps with lower levels: %v", hasOverlap) + } + + // Try to collect stats so that we can inform value log about GC. That would help us find which + // value log file should be GCed. + discardStats := make(map[uint32]int64) + updateStats := func(vs y.ValueStruct) { + if vs.Meta&bitValuePointer > 0 { + var vp valuePointer + vp.Decode(vs.Value) + discardStats[vp.Fid] += int64(vp.Len) + } + } + // Create iterators across all the tables involved first. var iters []y.Iterator if l == 0 { @@ -274,54 +364,111 @@ func (s *levelsController) compactBuildTables( it.Rewind() + // Pick a discard ts, so we can discard versions below this ts. We should + // never discard any versions starting from above this timestamp, because + // that would affect the snapshot view guarantee provided by transactions. + discardTs := s.kv.orc.discardAtOrBelow() + // Start generating new tables. type newTableResult struct { table *table.Table err error } resultCh := make(chan newTableResult) - var i int - for ; it.Valid(); i++ { + var numBuilds, numVersions int + var lastKey, skipKey []byte + for it.Valid() { timeStart := time.Now() builder := table.NewTableBuilder() + var numKeys, numSkips uint64 for ; it.Valid(); it.Next() { - if builder.ReachedCapacity(s.kv.opt.MaxTableSize) { - break + // See if we need to skip this key. + if len(skipKey) > 0 { + if y.SameKey(it.Key(), skipKey) { + numSkips++ + updateStats(it.Value()) + continue + } else { + skipKey = skipKey[:0] + } + } + + if !y.SameKey(it.Key(), lastKey) { + if builder.ReachedCapacity(s.kv.opt.MaxTableSize) { + // Only break if we are on a different key, and have reached capacity. We want + // to ensure that all versions of the key are stored in the same sstable, and + // not divided across multiple tables at the same level. + break + } + lastKey = y.SafeCopy(lastKey, it.Key()) + numVersions = 0 } + + vs := it.Value() + version := y.ParseTs(it.Key()) + if version <= discardTs { + // Keep track of the number of versions encountered for this key. Only consider the + // versions which are below the minReadTs, otherwise, we might end up discarding the + // only valid version for a running transaction. + numVersions++ + lastValidVersion := vs.Meta&bitDiscardEarlierVersions > 0 + if isDeletedOrExpired(vs.Meta, vs.ExpiresAt) || + numVersions > s.kv.opt.NumVersionsToKeep || + lastValidVersion { + // If this version of the key is deleted or expired, skip all the rest of the + // versions. Ensure that we're only removing versions below readTs. + skipKey = y.SafeCopy(skipKey, it.Key()) + + if lastValidVersion { + // Add this key. We have set skipKey, so the following key versions + // would be skipped. + } else if hasOverlap { + // If this key range has overlap with lower levels, then keep the deletion + // marker with the latest version, discarding the rest. We have set skipKey, + // so the following key versions would be skipped. + } else { + // If no overlap, we can skip all the versions, by continuing here. + numSkips++ + updateStats(vs) + continue // Skip adding this key. + } + } + } + numKeys++ y.Check(builder.Add(it.Key(), it.Value())) } // It was true that it.Valid() at least once in the loop above, which means we // called Add() at least once, and builder is not Empty(). - y.AssertTrue(!builder.Empty()) - - cd.elog.LazyPrintf("LOG Compact. Iteration to generate one table took: %v\n", time.Since(timeStart)) - - fileID := s.reserveFileID() - go func(builder *table.Builder) { - defer builder.Close() - - fd, err := y.CreateSyncedFile(table.NewFilename(fileID, s.kv.opt.Dir), true) - if err != nil { - resultCh <- newTableResult{nil, errors.Wrapf(err, "While opening new table: %d", fileID)} - return - } + cd.elog.LazyPrintf("Added %d keys. Skipped %d keys.", numKeys, numSkips) + cd.elog.LazyPrintf("LOG Compact. Iteration took: %v\n", time.Since(timeStart)) + if !builder.Empty() { + numBuilds++ + fileID := s.reserveFileID() + go func(builder *table.Builder) { + defer builder.Close() + + fd, err := y.CreateSyncedFile(table.NewFilename(fileID, s.kv.opt.Dir), true) + if err != nil { + resultCh <- newTableResult{nil, errors.Wrapf(err, "While opening new table: %d", fileID)} + return + } - if _, err := fd.Write(builder.Finish()); err != nil { - resultCh <- newTableResult{nil, errors.Wrapf(err, "Unable to write to file: %d", fileID)} - return - } + if _, err := fd.Write(builder.Finish()); err != nil { + resultCh <- newTableResult{nil, errors.Wrapf(err, "Unable to write to file: %d", fileID)} + return + } - tbl, err := table.OpenTable(fd, s.kv.opt.TableLoadingMode) - // decrRef is added below. - resultCh <- newTableResult{tbl, errors.Wrapf(err, "Unable to open table: %q", fd.Name())} - }(builder) + tbl, err := table.OpenTable(fd, s.kv.opt.TableLoadingMode) + // decrRef is added below. + resultCh <- newTableResult{tbl, errors.Wrapf(err, "Unable to open table: %q", fd.Name())} + }(builder) + } } newTables := make([]*table.Table, 0, 20) - // Wait for all table builders to finish. var firstErr error - for x := 0; x < i; x++ { + for x := 0; x < numBuilds; x++ { res := <-resultCh newTables = append(newTables, res.table) if firstErr == nil { @@ -339,7 +486,7 @@ func (s *levelsController) compactBuildTables( if firstErr != nil { // An error happened. Delete all the newly created table files (by calling DecrRef // -- we're the only holders of a ref). - for j := 0; j < i; j++ { + for j := 0; j < numBuilds; j++ { if newTables[j] != nil { newTables[j].DecrRef() } @@ -351,7 +498,8 @@ func (s *levelsController) compactBuildTables( sort.Slice(newTables, func(i, j int) bool { return y.CompareKeys(newTables[i].Biggest(), newTables[j].Biggest()) < 0 }) - + s.kv.vlog.updateGCStats(discardStats) + cd.elog.LazyPrintf("Discard stats: %v", discardStats) return newTables, func() error { return decrRefs(newTables) }, nil } @@ -442,8 +590,10 @@ func (s *levelsController) fillTables(cd *compactDef) bool { for _, t := range tbls { cd.thisSize = t.Size() cd.thisRange = keyRange{ - left: t.Smallest(), - right: t.Biggest(), + // We pick all the versions of the smallest and the biggest key. + left: y.KeyWithTs(y.ParseKey(t.Smallest()), math.MaxUint64), + // Note that version zero would be the rightmost key. + right: y.KeyWithTs(y.ParseKey(t.Biggest()), 0), } if s.cstatus.overlapsWith(cd.thisLevel.level, cd.thisRange) { continue @@ -482,40 +632,8 @@ func (s *levelsController) runCompactDef(l int, cd compactDef) (err error) { thisLevel := cd.thisLevel nextLevel := cd.nextLevel - if thisLevel.level >= 1 && len(cd.bot) == 0 { - y.AssertTrue(len(cd.top) == 1) - tbl := cd.top[0] - - // We write to the manifest _before_ we delete files (and after we created files). - changes := []*protos.ManifestChange{ - // The order matters here -- you can't temporarily have two copies of the same - // table id when reloading the manifest. - makeTableDeleteChange(tbl.ID()), - makeTableCreateChange(tbl.ID(), nextLevel.level), - } - if err := s.kv.manifest.addChanges(changes); err != nil { - return err - } - - // We have to add to nextLevel before we remove from thisLevel, not after. This way, we - // don't have a bug where reads would see keys missing from both levels. - - // Note: It's critical that we add tables (replace them) in nextLevel before deleting them - // in thisLevel. (We could finagle it atomically somehow.) Also, when reading we must - // read, or at least acquire s.RLock(), in increasing order by level, so that we don't skip - // a compaction. - - if err := nextLevel.replaceTables(cd.top); err != nil { - return err - } - if err := thisLevel.deleteTables(cd.top); err != nil { - return err - } - - cd.elog.LazyPrintf("\tLOG Compact-Move %d->%d smallest:%s biggest:%s took %v\n", - l, l+1, string(tbl.Smallest()), string(tbl.Biggest()), time.Since(timeStart)) - return nil - } + // Table should never be moved directly between levels, always be rewritten to allow discarding + // invalid versions. newTables, decr, err := s.compactBuildTables(l, cd) if err != nil { @@ -557,7 +675,7 @@ func (s *levelsController) doCompact(p compactionPriority) (bool, error) { y.AssertTrue(l+1 < s.kv.opt.MaxLevels) // Sanity check. cd := compactDef{ - elog: trace.New("Badger", "Compact"), + elog: trace.New(fmt.Sprintf("Badger.L%d", l), "Compact"), thisLevel: s.levels[l], nextLevel: s.levels[l+1], } @@ -580,6 +698,7 @@ func (s *levelsController) doCompact(p compactionPriority) (bool, error) { return false, nil } } + defer s.cstatus.delete(cd) // Remove the ranges from compaction status. cd.elog.LazyPrintf("Running for level: %d\n", cd.thisLevel.level) s.cstatus.toLog(cd.elog) @@ -589,8 +708,6 @@ func (s *levelsController) doCompact(p compactionPriority) (bool, error) { return false, err } - // Done with compaction. So, remove the ranges from compaction status. - s.cstatus.delete(cd) s.cstatus.toLog(cd.elog) cd.elog.LazyPrintf("Compaction for level: %d DONE", cd.thisLevel.level) return true, nil @@ -625,7 +742,7 @@ func (s *levelsController) addLevel0Table(t *table.Table) error { // Before we unstall, we need to make sure that level 0 and 1 are healthy. Otherwise, we // will very quickly fill up level 0 again and if the compaction strategy favors level 0, // then level 1 is going to super full. - for { + for i := 0; ; i++ { // Passing 0 for delSize to compactable means we're treating incomplete compactions as // not having finished -- we wait for them to finish. Also, it's crucial this behavior // replicates pickCompactLevels' behavior in computing compactability in order to @@ -634,6 +751,11 @@ func (s *levelsController) addLevel0Table(t *table.Table) error { break } time.Sleep(10 * time.Millisecond) + if i%100 == 0 { + prios := s.pickCompactLevels() + s.elog.Printf("Waiting to add level 0 table. Compaction priorities: %+v\n", prios) + i = 0 + } } { s.elog.Printf("UNSTALLED UNSTALLED UNSTALLED UNSTALLED UNSTALLED UNSTALLED: %v\n", @@ -651,14 +773,12 @@ func (s *levelsController) close() error { } // get returns the found value if any. If not found, we return nil. -func (s *levelsController) get(key []byte, maxVs y.ValueStruct) (y.ValueStruct, error) { +func (s *levelsController) get(key []byte) (y.ValueStruct, error) { // It's important that we iterate the levels from 0 on upward. The reason is, if we iterated // in opposite order, or in parallel (naively calling all the h.RLock() in some order) we could // read level L's tables post-compaction and level L+1's tables pre-compaction. (If we do // parallelize this, we will need to call the h.RLock() function by increasing order of level // number.) - - version := y.ParseTs(key) for _, h := range s.levels { vs, err := h.get(key) // Calls h.RLock() and h.RUnlock(). if err != nil { @@ -667,14 +787,9 @@ func (s *levelsController) get(key []byte, maxVs y.ValueStruct) (y.ValueStruct, if vs.Value == nil && vs.Meta == 0 { continue } - if vs.Version == version { - return vs, nil - } - if maxVs.Version < vs.Version { - maxVs = vs - } + return vs, nil } - return maxVs, nil + return y.ValueStruct{}, nil } func appendIteratorsReversed(out []y.Iterator, th []*table.Table, reversed bool) []y.Iterator { @@ -696,3 +811,31 @@ func (s *levelsController) appendIterators( } return iters } + +type TableInfo struct { + ID uint64 + Level int + Left []byte + Right []byte +} + +func (s *levelsController) getTableInfo() (result []TableInfo) { + for _, l := range s.levels { + for _, t := range l.tables { + info := TableInfo{ + ID: t.ID(), + Level: l.level, + Left: t.Smallest(), + Right: t.Biggest(), + } + result = append(result, info) + } + } + sort.Slice(result, func(i, j int) bool { + if result[i].Level != result[j].Level { + return result[i].Level < result[j].Level + } + return result[i].ID < result[j].ID + }) + return +} diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go index 3b52c1e784..c3898130e9 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/managed_db.go @@ -16,6 +16,16 @@ package badger +import ( + "math" + "sync" + "sync/atomic" + "time" + + "github.com/dgraph-io/badger/y" + "github.com/pkg/errors" +) + // ManagedDB allows end users to manage the transactions themselves. Transaction // start and commit timestamps are set by end-user. // @@ -72,15 +82,112 @@ func (txn *Txn) CommitAt(commitTs uint64, callback func(error)) error { return txn.Commit(callback) } -// PurgeVersionsBelow will delete all versions of a key below the specified version -func (db *ManagedDB) PurgeVersionsBelow(key []byte, ts uint64) error { - txn := db.NewTransactionAt(ts, false) - defer txn.Discard() - return db.purgeVersionsBelow(txn, key, ts) -} - // GetSequence is not supported on ManagedDB. Calling this would result // in a panic. func (db *ManagedDB) GetSequence(_ []byte, _ uint64) (*Sequence, error) { panic("Cannot use GetSequence for ManagedDB.") } + +// SetDiscardTs sets a timestamp at or below which, any invalid or deleted +// versions can be discarded from the LSM tree, and thence from the value log to +// reclaim disk space. +func (db *ManagedDB) SetDiscardTs(ts uint64) { + db.orc.setDiscardTs(ts) +} + +var errDone = errors.New("Done deleting keys") + +// DropAll would drop all the data stored in Badger. It does this in the following way. +// - Stop accepting new writes. +// - Pause the compactions. +// - Pick all tables from all levels, create a changeset to delete all these +// tables and apply it to manifest. DO not pick up the latest table from level +// 0, to preserve the (persistent) badgerHead key. +// - Iterate over the KVs in Level 0, and run deletes on them via transactions. +// - The deletions are done at the same timestamp as the latest version of the +// key. Thus, we could write the keys back at the same timestamp as before. +func (db *ManagedDB) DropAll() error { + // Stop accepting new writes. + atomic.StoreInt32(&db.blockWrites, 1) + + // Wait for writeCh to reach size of zero. This is not ideal, but a very + // simple way to allow writeCh to flush out, before we proceed. + tick := time.NewTicker(100 * time.Millisecond) + for range tick.C { + if len(db.writeCh) == 0 { + break + } + } + tick.Stop() + + // Stop the compactions. + if db.closers.compactors != nil { + db.closers.compactors.SignalAndWait() + } + + _, err := db.lc.deleteLSMTree() + // Allow writes so that we can run transactions. Ideally, the user must ensure that they're not + // doing more writes concurrently while this operation is happening. + atomic.StoreInt32(&db.blockWrites, 0) + // Need compactions to happen so deletes below can be flushed out. + if db.closers.compactors != nil { + db.closers.compactors = y.NewCloser(1) + db.lc.startCompact(db.closers.compactors) + } + if err != nil { + return err + } + + type KV struct { + key []byte + version uint64 + } + + var kvs []KV + getKeys := func() error { + txn := db.NewTransactionAt(math.MaxUint64, false) + defer txn.Discard() + + opts := DefaultIteratorOptions + opts.PrefetchValues = false + itr := txn.NewIterator(opts) + defer itr.Close() + + for itr.Rewind(); itr.Valid(); itr.Next() { + item := itr.Item() + kvs = append(kvs, KV{item.KeyCopy(nil), item.Version()}) + } + return nil + } + if err := getKeys(); err != nil { + return err + } + + var wg sync.WaitGroup + errCh := make(chan error, 1) + for _, kv := range kvs { + wg.Add(1) + txn := db.NewTransactionAt(math.MaxUint64, true) + if err := txn.Delete(kv.key); err != nil { + return err + } + if err := txn.CommitAt(kv.version, func(rerr error) { + if rerr != nil { + select { + case errCh <- rerr: + default: + } + } + wg.Done() + }); err != nil { + return err + } + } + wg.Wait() + select { + case err := <-errCh: + return err + default: + return nil + } +} diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/manifest.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/manifest.go index f2e57d9b06..e16bbfb467 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/manifest.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/manifest.go @@ -32,7 +32,7 @@ import ( "github.com/pkg/errors" ) -// Manifest represnts the contents of the MANIFEST file in a Badger store. +// Manifest represents the contents of the MANIFEST file in a Badger store. // // The MANIFEST file describes the startup state of the db -- all LSM files and what level they're // at. @@ -112,17 +112,24 @@ func (m *Manifest) clone() Manifest { // openOrCreateManifestFile opens a Badger manifest file if it exists, or creates on if // one doesn’t. -func openOrCreateManifestFile(dir string) (ret *manifestFile, result Manifest, err error) { - return helpOpenOrCreateManifestFile(dir, manifestDeletionsRewriteThreshold) +func openOrCreateManifestFile(dir string, readOnly bool) (ret *manifestFile, result Manifest, err error) { + return helpOpenOrCreateManifestFile(dir, readOnly, manifestDeletionsRewriteThreshold) } -func helpOpenOrCreateManifestFile(dir string, deletionsThreshold int) (ret *manifestFile, result Manifest, err error) { +func helpOpenOrCreateManifestFile(dir string, readOnly bool, deletionsThreshold int) (ret *manifestFile, result Manifest, err error) { path := filepath.Join(dir, ManifestFilename) - fp, err := y.OpenExistingSyncedFile(path, false) // We explicitly sync in addChanges, outside the lock. + var flags uint32 + if readOnly { + flags |= y.ReadOnly + } + fp, err := y.OpenExistingFile(path, flags) // We explicitly sync in addChanges, outside the lock. if err != nil { if !os.IsNotExist(err) { return nil, Manifest{}, err } + if readOnly { + return nil, Manifest{}, fmt.Errorf("no manifest found, required for read-only db") + } m := createManifest() fp, netCreations, err := helpRewrite(dir, &m) if err != nil { @@ -144,12 +151,13 @@ func helpOpenOrCreateManifestFile(dir string, deletionsThreshold int) (ret *mani return nil, Manifest{}, err } - // Truncate file so we don't have a half-written entry at the end. - if err := fp.Truncate(truncOffset); err != nil { - _ = fp.Close() - return nil, Manifest{}, err + if !readOnly { + // Truncate file so we don't have a half-written entry at the end. + if err := fp.Truncate(truncOffset); err != nil { + _ = fp.Close() + return nil, Manifest{}, err + } } - if _, err = fp.Seek(0, io.SeekEnd); err != nil { _ = fp.Close() return nil, Manifest{}, err @@ -256,7 +264,7 @@ func helpRewrite(dir string, m *Manifest) (*os.File, int, error) { if err := os.Rename(rewritePath, manifestPath); err != nil { return nil, 0, err } - fp, err = y.OpenExistingSyncedFile(manifestPath, false) + fp, err = y.OpenExistingFile(manifestPath, 0) if err != nil { return nil, 0, err } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go index 9b8bc6ee99..21fc2a5a3a 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/options.go @@ -46,9 +46,12 @@ type Options struct { // How should LSM tree be accessed. TableLoadingMode options.FileLoadingMode - // How should value log be accessed + // How should value log be accessed. ValueLogLoadingMode options.FileLoadingMode + // How many versions to keep per key. + NumVersionsToKeep int + // 3. Flags that user might want to review // ---------------------------------------- // The following affect all levels of LSM tree. @@ -73,6 +76,10 @@ type Options struct { // Size of single value log file. ValueLogFileSize int64 + // Max number of entries a value log file can hold (approximately). A value log file would be + // determined by the smaller of its file size and max entries. + ValueLogMaxEntries uint32 + // Number of compaction workers to run concurrently. NumCompactors int @@ -86,6 +93,15 @@ type Options struct { maxBatchCount int64 // max entries in batch maxBatchSize int64 // max batch size in bytes + + // Open the DB as read-only. With this set, multiple processes can + // open the same Badger DB. Note: if the DB being opened had crashed + // before and has vlog data to be replayed, ReadOnly will cause Open + // to fail with an appropriate message. + ReadOnly bool + + // Truncate value log to delete corrupt data, if any. Would not truncate if ReadOnly is set. + Truncate bool } // DefaultOptions sets a list of recommended options for good performance. @@ -105,8 +121,27 @@ var DefaultOptions = Options{ NumLevelZeroTablesStall: 10, NumMemtables: 5, SyncWrites: true, + NumVersionsToKeep: 1, // Nothing to read/write value log using standard File I/O // MemoryMap to mmap() the value log files - ValueLogFileSize: 1 << 30, - ValueThreshold: 20, + // (2^30 - 1)*2 when mmapping < 2^31 - 1, max int32. + // -1 so 2*ValueLogFileSize won't overflow on 32-bit systems. + ValueLogFileSize: 1<<30 - 1, + + ValueLogMaxEntries: 1000000, + ValueThreshold: 32, + Truncate: false, +} + +// LSMOnlyOptions follows from DefaultOptions, but sets a higher ValueThreshold so values would +// be colocated with the LSM tree, with value log largely acting as a write-ahead log only. These +// options would reduce the disk usage of value log, and make Badger act like a typical LSM tree. +var LSMOnlyOptions = Options{} + +func init() { + LSMOnlyOptions = DefaultOptions + + LSMOnlyOptions.ValueThreshold = 65500 // Max value length which fits in uint16. + LSMOnlyOptions.ValueLogFileSize = 64 << 20 // Allow easy space reclamation. + LSMOnlyOptions.ValueLogLoadingMode = options.FileIO } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/arena.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/arena.go index 849e5ee564..def550712f 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/arena.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/arena.go @@ -25,7 +25,12 @@ import ( const ( offsetSize = int(unsafe.Sizeof(uint32(0))) - ptrAlign = int(unsafe.Sizeof(uintptr(0))) - 1 + + // Always align nodes on 64-bit boundaries, even on 32-bit architectures, + // so that the node.value field is 64-bit aligned. This is necessary because + // node.getValueOffset uses atomic.LoadUint64, which expects its input + // pointer to be 64-bit aligned. + nodeAlign = int(unsafe.Sizeof(uint64(0))) - 1 ) // Arena should be lock-free. @@ -61,14 +66,14 @@ func (s *Arena) putNode(height int) uint32 { unusedSize := (maxHeight - height) * offsetSize // Pad the allocation with enough bytes to ensure pointer alignment. - l := uint32(MaxNodeSize - unusedSize + ptrAlign) + l := uint32(MaxNodeSize - unusedSize + nodeAlign) n := atomic.AddUint32(&s.n, l) y.AssertTruef(int(n) <= len(s.buf), "Arena too small, toWrite:%d newTotal:%d limit:%d", l, n, len(s.buf)) // Return the aligned offset. - m := (n - l + uint32(ptrAlign)) & ^uint32(ptrAlign) + m := (n - l + uint32(nodeAlign)) & ^uint32(nodeAlign) return m } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/skl.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/skl.go index 7361751a50..b465b09ecc 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/skl.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/skl/skl.go @@ -54,8 +54,7 @@ type node struct { // can be atomically loaded and stored: // value offset: uint32 (bits 0-31) // value size : uint16 (bits 32-47) - // 12 bytes are allocated to ensure 8 byte alignment also on 32bit systems. - value [12]byte + value uint64 // A byte slice is 24 bytes. We are trying to save space here. keyOffset uint32 // Immutable. No need to lock to access key. @@ -109,7 +108,7 @@ func newNode(arena *Arena, key []byte, v y.ValueStruct, height int) *node { node.keyOffset = arena.putKey(key) node.keySize = uint16(len(key)) node.height = uint16(height) - *node.value64BitAlignedPtr() = encodeValue(arena.putVal(v), v.EncodedSize()) + node.value = encodeValue(arena.putVal(v), v.EncodedSize()) return node } @@ -135,15 +134,8 @@ func NewSkiplist(arenaSize int64) *Skiplist { } } -func (s *node) value64BitAlignedPtr() *uint64 { - if uintptr(unsafe.Pointer(&s.value))%8 == 0 { - return (*uint64)(unsafe.Pointer(&s.value)) - } - return (*uint64)(unsafe.Pointer(&s.value[4])) -} - func (s *node) getValueOffset() (uint32, uint16) { - value := atomic.LoadUint64(s.value64BitAlignedPtr()) + value := atomic.LoadUint64(&s.value) return decodeValue(value) } @@ -154,7 +146,7 @@ func (s *node) key(arena *Arena) []byte { func (s *node) setValue(arena *Arena, v y.ValueStruct) { valOffset := arena.putVal(v) value := encodeValue(valOffset, v.EncodedSize()) - atomic.StoreUint64(s.value64BitAlignedPtr(), value) + atomic.StoreUint64(&s.value, value) } func (s *node) getNextOffset(h int) uint32 { diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/builder.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/builder.go index 7ce0d61f9e..43e6562239 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/builder.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/builder.go @@ -212,7 +212,7 @@ func (b *Builder) Finish() []byte { } kl := int(binary.BigEndian.Uint16(klen[:])) if cap(key) < kl { - key = make([]byte, 2*kl) + key = make([]byte, 2*int(kl)) // 2 * uint16 will overflow } key = key[:kl] y.Check2(b.keyBuf.Read(key)) diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/iterator.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/iterator.go index 533fe2a6ed..0eb5ed01a9 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/iterator.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/iterator.go @@ -341,6 +341,7 @@ func (itr *Iterator) next() { } itr.bi = block.NewIterator() itr.bi.SeekToFirst() + itr.err = itr.bi.Error() return } @@ -368,6 +369,7 @@ func (itr *Iterator) prev() { } itr.bi = block.NewIterator() itr.bi.SeekToLast() + itr.err = itr.bi.Error() return } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/table.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/table.go index 52a17b17ab..9804fa1764 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/table.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/table/table.go @@ -22,7 +22,6 @@ import ( "os" "path" "path/filepath" - "sort" "strconv" "strings" "sync" @@ -102,12 +101,6 @@ func (b block) NewIterator() *blockIterator { return &blockIterator{data: b.data} } -type byKey []keyOffset - -func (b byKey) Len() int { return len(b) } -func (b byKey) Swap(i int, j int) { b[i], b[j] = b[j], b[i] } -func (b byKey) Less(i int, j int) bool { return y.CompareKeys(b[i].key, b[j].key) < 0 } - // OpenTable assumes file has only one table and opens it. Takes ownership of fd upon function // entry. Returns a table with one reference count on it (decrementing which may delete the file! // -- consider t.Close() instead). The fd has to writeable because we call Truncate on it before @@ -290,7 +283,6 @@ func (t *Table) readIndex() error { return readError } - sort.Sort(byKey(t.blockIndex)) return nil } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go index ac9d1a25b8..32a6a96aec 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/transaction.go @@ -18,8 +18,6 @@ package badger import ( "bytes" - "container/heap" - "fmt" "math" "sort" "strconv" @@ -32,32 +30,20 @@ import ( "github.com/pkg/errors" ) -type uint64Heap []uint64 - -func (u uint64Heap) Len() int { return len(u) } -func (u uint64Heap) Less(i int, j int) bool { return u[i] < u[j] } -func (u uint64Heap) Swap(i int, j int) { u[i], u[j] = u[j], u[i] } -func (u *uint64Heap) Push(x interface{}) { *u = append(*u, x.(uint64)) } -func (u *uint64Heap) Pop() interface{} { - old := *u - n := len(old) - x := old[n-1] - *u = old[0 : n-1] - return x -} - type oracle struct { + // curRead must be at the top for memory alignment. See issue #311. curRead uint64 // Managed by the mutex. refCount int64 isManaged bool // Does not change value, so no locking required. sync.Mutex + writeLock sync.Mutex nextCommit uint64 - // These two structures are used to figure out when a commit is done. The minimum done commit is - // used to update curRead. - commitMark uint64Heap - pendingCommits map[uint64]struct{} + // Either of these is used to determine which versions can be permanently + // discarded during compaction. + discardTs uint64 // Used by ManagedDB. + readMark y.WaterMark // Used by DB. // commits stores a key fingerprint and latest commit counter for it. // refCount is used to clear out commits map to avoid a memory blowup. @@ -70,15 +56,14 @@ func (o *oracle) addRef() { func (o *oracle) decrRef() { if count := atomic.AddInt64(&o.refCount, -1); count == 0 { - // Clear out pendingCommits maps to release memory. + // Clear out commits maps to release memory. o.Lock() - // There could be race here, so check again. - // Checking commitMark is safe since it is protected by mutex. - if len(o.commitMark) > 0 { + // Avoids the race where something new is added to commitsMap + // after we check refCount and before we take Lock. + if atomic.LoadInt64(&o.refCount) != 0 { o.Unlock() return } - y.AssertTrue(len(o.pendingCommits) == 0) if len(o.commits) >= 1000 { // If the map is still small, let it slide. o.commits = make(map[uint64]uint64) } @@ -99,6 +84,23 @@ func (o *oracle) commitTs() uint64 { return o.nextCommit } +// Any deleted or invalid versions at or below ts would be discarded during +// compaction to reclaim disk space in LSM tree and thence value log. +func (o *oracle) setDiscardTs(ts uint64) { + o.Lock() + defer o.Unlock() + o.discardTs = ts +} + +func (o *oracle) discardAtOrBelow() uint64 { + if o.isManaged { + o.Lock() + defer o.Unlock() + return o.discardTs + } + return o.readMark.MinReadTs() +} + // hasConflict must be called while having a lock. func (o *oracle) hasConflict(txn *Txn) bool { if len(txn.reads) == 0 { @@ -134,15 +136,6 @@ func (o *oracle) newCommitTs(txn *Txn) uint64 { for _, w := range txn.writes { o.commits[w] = ts // Update the commitTs. } - if o.isManaged { - // No need to update the heap. - return ts - } - heap.Push(&o.commitMark, ts) - if _, has := o.pendingCommits[ts]; has { - panic(fmt.Sprintf("We shouldn't have the commit ts: %d", ts)) - } - o.pendingCommits[ts] = struct{}{} return ts } @@ -151,29 +144,14 @@ func (o *oracle) doneCommit(cts uint64) { // No need to update anything. return } - o.Lock() - defer o.Unlock() - if _, has := o.pendingCommits[cts]; !has { - panic(fmt.Sprintf("We should already have the commit ts: %d", cts)) - } - delete(o.pendingCommits, cts) - - var min uint64 - for len(o.commitMark) > 0 { - ts := o.commitMark[0] - if _, has := o.pendingCommits[ts]; has { - // Still waiting for a txn to commit. - break + for { + curRead := atomic.LoadUint64(&o.curRead) + if cts <= curRead { + return } - min = ts - heap.Pop(&o.commitMark) + atomic.CompareAndSwapUint64(&o.curRead, curRead, cts) } - if min == 0 { - return - } - atomic.StoreUint64(&o.curRead, min) - // nextCommit must never be reset. } // Txn represents a Badger transaction. @@ -191,8 +169,9 @@ type Txn struct { callbacks []func() discarded bool - size int64 - count int64 + size int64 + count int64 + numIterators int32 } type pendingWritesIterator struct { @@ -285,6 +264,9 @@ func (txn *Txn) checkSize(e *Entry) error { // // It will return ErrReadOnlyTxn if update flag was set to false when creating the // transaction. +// +// The current transaction keeps a reference to the key and val byte slice +// arguments. Users must not modify key and val until the end of the transaction. func (txn *Txn) Set(key, val []byte) error { e := &Entry{ Key: key, @@ -294,36 +276,66 @@ func (txn *Txn) Set(key, val []byte) error { } // SetWithMeta adds a key-value pair to the database, along with a metadata -// byte. This byte is stored alongside the key, and can be used as an aid to +// byte. +// +// This byte is stored alongside the key, and can be used as an aid to // interpret the value or store other contextual bits corresponding to the // key-value pair. +// +// The current transaction keeps a reference to the key and val byte slice +// arguments. Users must not modify key and val until the end of the transaction. func (txn *Txn) SetWithMeta(key, val []byte, meta byte) error { e := &Entry{Key: key, Value: val, UserMeta: meta} return txn.SetEntry(e) } +// SetWithDiscard acts like SetWithMeta, but adds a marker to discard earlier +// versions of the key. +// +// This method is only useful if you have set a higher limit for +// options.NumVersionsToKeep. The default setting is 1, in which case, this +// function doesn't add any more benefit than just calling the normal +// SetWithMeta (or Set) function. If however, you have a higher setting for +// NumVersionsToKeep (in Dgraph, we set it to infinity), you can use this method +// to indicate that all the older versions can be discarded and removed during +// compactions. +// +// The current transaction keeps a reference to the key and val byte slice +// arguments. Users must not modify key and val until the end of the +// transaction. +func (txn *Txn) SetWithDiscard(key, val []byte, meta byte) error { + e := &Entry{ + Key: key, + Value: val, + UserMeta: meta, + meta: bitDiscardEarlierVersions, + } + return txn.SetEntry(e) +} + // SetWithTTL adds a key-value pair to the database, along with a time-to-live -// (TTL) setting. A key stored with with a TTL would automatically expire after -// the time has elapsed , and be eligible for garbage collection. +// (TTL) setting. A key stored with a TTL would automatically expire after the +// time has elapsed , and be eligible for garbage collection. +// +// The current transaction keeps a reference to the key and val byte slice +// arguments. Users must not modify key and val until the end of the +// transaction. func (txn *Txn) SetWithTTL(key, val []byte, dur time.Duration) error { expire := time.Now().Add(dur).Unix() e := &Entry{Key: key, Value: val, ExpiresAt: uint64(expire)} return txn.SetEntry(e) } -// SetEntry takes an Entry struct and adds the key-value pair in the struct, along -// with other metadata to the database. -func (txn *Txn) SetEntry(e *Entry) error { - switch { - case !txn.update: +func (txn *Txn) modify(e *Entry) error { + if !txn.update { return ErrReadOnlyTxn - case txn.discarded: + } else if txn.discarded { return ErrDiscardedTxn - case len(e.Key) == 0: + } else if len(e.Key) == 0 { return ErrEmptyKey - case len(e.Key) > maxKeySize: + } else if len(e.Key) > maxKeySize { return exceedsMaxKeySizeError(e.Key) - case int64(len(e.Value)) > txn.db.opt.ValueLogFileSize: + } else if int64(len(e.Value)) > txn.db.opt.ValueLogFileSize { return exceedsMaxValueSizeError(e.Value, txn.db.opt.ValueLogFileSize) } if err := txn.checkSize(e); err != nil { @@ -336,33 +348,29 @@ func (txn *Txn) SetEntry(e *Entry) error { return nil } -// Delete deletes a key. This is done by adding a delete marker for the key at commit timestamp. -// Any reads happening before this timestamp would be unaffected. Any reads after this commit would -// see the deletion. -func (txn *Txn) Delete(key []byte) error { - if !txn.update { - return ErrReadOnlyTxn - } else if txn.discarded { - return ErrDiscardedTxn - } else if len(key) == 0 { - return ErrEmptyKey - } else if len(key) > maxKeySize { - return exceedsMaxKeySizeError(key) - } +// SetEntry takes an Entry struct and adds the key-value pair in the struct, +// along with other metadata to the database. +// +// The current transaction keeps a reference to the entry passed in argument. +// Users must not modify the entry until the end of the transaction. +func (txn *Txn) SetEntry(e *Entry) error { + return txn.modify(e) +} +// Delete deletes a key. +// +// This is done by adding a delete marker for the key at commit timestamp. Any +// reads happening before this timestamp would be unaffected. Any reads after +// this commit would see the deletion. +// +// The current transaction keeps a reference to the key byte slice argument. +// Users must not modify the key until the end of the transaction. +func (txn *Txn) Delete(key []byte) error { e := &Entry{ Key: key, meta: bitDelete, } - if err := txn.checkSize(e); err != nil { - return err - } - - fp := farm.Fingerprint64(key) // Avoid dealing with byte arrays. - txn.writes = append(txn.writes, fp) - - txn.pendingWrites[string(key)] = e - return nil + return txn.modify(e) } // Get looks for key and returns corresponding Item. @@ -387,6 +395,7 @@ func (txn *Txn) Get(key []byte) (item *Item, rerr error) { item.key = key item.status = prefetched item.version = txn.readTs + item.expiresAt = e.ExpiresAt // We probably don't need to set db on item here. return item, nil } @@ -415,9 +424,17 @@ func (txn *Txn) Get(key []byte) (item *Item, rerr error) { item.db = txn.db item.vptr = vs.Value item.txn = txn + item.expiresAt = vs.ExpiresAt return item, nil } +func (txn *Txn) runCallbacks() { + for _, cb := range txn.callbacks { + cb() + } + txn.callbacks = txn.callbacks[:0] +} + // Discard discards a created transaction. This method is very important and must be called. Commit // method calls this internally, however, calling this multiple times doesn't cause any issues. So, // this can safely be called via a defer right when transaction is created. @@ -427,11 +444,12 @@ func (txn *Txn) Discard() { if txn.discarded { // Avoid a re-run. return } - txn.discarded = true - - for _, cb := range txn.callbacks { - cb() + if atomic.LoadInt32(&txn.numIterators) > 0 { + panic("Unclosed iterator at time of Txn.Discard.") } + txn.discarded = true + txn.db.orc.readMark.Done(txn.readTs) + txn.runCallbacks() if txn.update { txn.db.orc.decrRef() } @@ -468,8 +486,10 @@ func (txn *Txn) Commit(callback func(error)) error { } state := txn.db.orc + state.writeLock.Lock() commitTs := state.newCommitTs(txn) if commitTs == 0 { + state.writeLock.Unlock() return ErrConflict } @@ -488,18 +508,30 @@ func (txn *Txn) Commit(callback func(error)) error { } entries = append(entries, e) + req, err := txn.db.sendToWriteCh(entries) + state.writeLock.Unlock() + if err != nil { + return err + } + + // Need to release all locks or writes can get deadlocked. + txn.runCallbacks() + if callback == nil { // If batchSet failed, LSM would not have been updated. So, no need to rollback anything. // TODO: What if some of the txns successfully make it to value log, but others fail. // Nothing gets updated to LSM, until a restart happens. defer state.doneCommit(commitTs) - return txn.db.batchSet(entries) + return req.Wait() } - return txn.db.batchSetAsync(entries, func(err error) { + go func() { + err := req.Wait() + // Write is complete. Let's call the callback function now. state.doneCommit(commitTs) callback(err) - }) + }() + return nil } // NewTransaction creates a new transaction. Badger supports concurrent execution of transactions, @@ -523,6 +555,11 @@ func (txn *Txn) Commit(callback func(error)) error { // defer txn.Discard() // // Call various APIs. func (db *DB) NewTransaction(update bool) *Txn { + if db.opt.ReadOnly && update { + // DB is read-only, force read-only transaction. + update = false + } + txn := &Txn{ update: update, db: db, @@ -530,6 +567,7 @@ func (db *DB) NewTransaction(update bool) *Txn { count: 1, // One extra entry for BitFin. size: int64(len(txnKey) + 10), // Some buffer for the extra entry. } + db.orc.readMark.Begin(txn.readTs) if update { txn.pendingWrites = make(map[string]*Entry) txn.db.orc.addRef() diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go index ff78f847a1..6b8de919e1 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/value.go @@ -45,8 +45,9 @@ import ( // Values have their first byte being byteData or byteDelete. This helps us distinguish between // a key that has never been seen and a key that has been explicitly deleted. const ( - bitDelete byte = 1 << 0 // Set if the key has been deleted. - bitValuePointer byte = 1 << 1 // Set if the value is NOT stored directly next to key. + bitDelete byte = 1 << 0 // Set if the key has been deleted. + bitValuePointer byte = 1 << 1 // Set if the value is NOT stored directly next to key. + bitDiscardEarlierVersions byte = 1 << 2 // Set if earlier versions can be discarded. // The MSB 2 bits are for transactions. bitTxn byte = 1 << 6 // Set if the entry is part of a txn. @@ -175,9 +176,75 @@ func (lf *logFile) sync() error { } var errStop = errors.New("Stop iteration") +var errTruncate = errors.New("Do truncate") type logEntry func(e Entry, vp valuePointer) error +type safeRead struct { + k []byte + v []byte + + recordOffset uint32 +} + +func (r *safeRead) Entry(reader *bufio.Reader) (*Entry, error) { + var hbuf [headerBufSize]byte + var err error + + hash := crc32.New(y.CastagnoliCrcTable) + tee := io.TeeReader(reader, hash) + if _, err = io.ReadFull(tee, hbuf[:]); err != nil { + return nil, err + } + + var h header + h.Decode(hbuf[:]) + if h.klen > maxKeySize { + return nil, errTruncate + } + kl := int(h.klen) + if cap(r.k) < kl { + r.k = make([]byte, 2*kl) + } + vl := int(h.vlen) + if cap(r.v) < vl { + r.v = make([]byte, 2*vl) + } + + e := &Entry{} + e.offset = r.recordOffset + e.Key = r.k[:kl] + e.Value = r.v[:vl] + + if _, err = io.ReadFull(tee, e.Key); err != nil { + if err == io.EOF { + err = errTruncate + } + return nil, err + } + if _, err = io.ReadFull(tee, e.Value); err != nil { + if err == io.EOF { + err = errTruncate + } + return nil, err + } + var crcBuf [4]byte + if _, err = io.ReadFull(reader, crcBuf[:]); err != nil { + if err == io.EOF { + err = errTruncate + } + return nil, err + } + crc := binary.BigEndian.Uint32(crcBuf[:]) + if crc != hash.Sum32() { + return nil, errTruncate + } + e.meta = h.meta + e.UserMeta = h.userMeta + e.ExpiresAt = h.expiresAt + return e, nil +} + // iterate iterates over log file. It doesn't not allocate new memory for every kv pair. // Therefore, the kv pair is only valid for the duration of fn call. func (vlog *valueLog) iterate(lf *logFile, offset uint32, fn logEntry) error { @@ -187,87 +254,69 @@ func (vlog *valueLog) iterate(lf *logFile, offset uint32, fn logEntry) error { } reader := bufio.NewReader(lf.fd) - var hbuf [headerBufSize]byte - var h header - k := make([]byte, 1<<10) - v := make([]byte, 1<<20) + read := &safeRead{ + k: make([]byte, 10), + v: make([]byte, 10), + recordOffset: offset, + } truncate := false - recordOffset := offset + var lastCommit uint64 + var validEndOffset uint32 for { - hash := crc32.New(y.CastagnoliCrcTable) - tee := io.TeeReader(reader, hash) - - // TODO: Move this entry decode into structs.go - if _, err = io.ReadFull(tee, hbuf[:]); err != nil { - if err == io.EOF { - break - } else if err == io.ErrUnexpectedEOF { - truncate = true - break - } - return err - } - - var e Entry - e.offset = recordOffset - h.Decode(hbuf[:]) - if h.klen > maxKeySize { + e, err := read.Entry(reader) + if err == io.EOF { + break + } else if err == io.ErrUnexpectedEOF || err == errTruncate { truncate = true break - } - vl := int(h.vlen) - if cap(v) < vl { - v = make([]byte, 2*vl) + } else if err != nil { + return err + } else if e == nil { + continue } - kl := int(h.klen) - if cap(k) < kl { - k = make([]byte, 2*kl) - } - e.Key = k[:kl] - e.Value = v[:vl] + var vp valuePointer + vp.Len = uint32(headerBufSize + len(e.Key) + len(e.Value) + 4) // len(crcBuf) + read.recordOffset += vp.Len + + vp.Offset = e.offset + vp.Fid = lf.fid - if _, err = io.ReadFull(tee, e.Key); err != nil { - if err == io.EOF || err == io.ErrUnexpectedEOF { + if e.meta&bitTxn > 0 { + txnTs := y.ParseTs(e.Key) + if lastCommit == 0 { + lastCommit = txnTs + } + if lastCommit != txnTs { truncate = true break } - return err - } - if _, err = io.ReadFull(tee, e.Value); err != nil { - if err == io.EOF || err == io.ErrUnexpectedEOF { + + } else if e.meta&bitFinTxn > 0 { + txnTs, err := strconv.ParseUint(string(e.Value), 10, 64) + if err != nil || lastCommit != txnTs { truncate = true break } - return err - } + // Got the end of txn. Now we can store them. + lastCommit = 0 + validEndOffset = read.recordOffset - var crcBuf [4]byte - if _, err = io.ReadFull(reader, crcBuf[:]); err != nil { - if err == io.EOF || err == io.ErrUnexpectedEOF { + } else { + if lastCommit != 0 { + // This is most likely an entry which was moved as part of GC. + // We shouldn't get this entry in the middle of a transaction. truncate = true break } - return err - } - crc := binary.BigEndian.Uint32(crcBuf[:]) - if crc != hash.Sum32() { - truncate = true - break + validEndOffset = read.recordOffset } - e.meta = h.meta - e.UserMeta = h.userMeta - e.ExpiresAt = h.expiresAt - var vp valuePointer - vp.Len = headerBufSize + h.klen + h.vlen + uint32(len(crcBuf)) - recordOffset += vp.Len - - vp.Offset = e.offset - vp.Fid = lf.fid - - if err := fn(e, vp); err != nil { + if vlog.opt.ReadOnly { + return ErrReplayNeeded + } + if err := fn(*e, vp); err != nil { if err == errStop { break } @@ -275,33 +324,32 @@ func (vlog *valueLog) iterate(lf *logFile, offset uint32, fn logEntry) error { } } - if truncate && len(lf.fmap) == 0 { + if vlog.opt.Truncate && truncate && len(lf.fmap) == 0 { // Only truncate if the file isn't mmaped. Otherwise, Windows would puke. - if err := lf.fd.Truncate(int64(recordOffset)); err != nil { + if err := lf.fd.Truncate(int64(validEndOffset)); err != nil { return err } + } else if truncate { + return ErrTruncateNeeded } return nil } -func (vlog *valueLog) rewrite(f *logFile) error { +func (vlog *valueLog) rewrite(f *logFile, tr trace.Trace) error { maxFid := atomic.LoadUint32(&vlog.maxFid) y.AssertTruef(uint32(f.fid) < maxFid, "fid to move: %d. Current max fid: %d", f.fid, maxFid) - - elog := trace.NewEventLog("badger", "vlog-rewrite") - defer elog.Finish() - elog.Printf("Rewriting fid: %d", f.fid) + tr.LazyPrintf("Rewriting fid: %d", f.fid) wb := make([]*Entry, 0, 1000) var size int64 y.AssertTrue(vlog.kv != nil) - var count int + var count, moved int fe := func(e Entry) error { count++ - if count%10000 == 0 { - elog.Printf("Processing entry %d", count) + if count%100000 == 0 { + tr.LazyPrintf("Processing entry %d", count) } vs, err := vlog.kv.get(e.Key) @@ -326,18 +374,28 @@ func (vlog *valueLog) rewrite(f *logFile) error { return nil } if vp.Fid == f.fid && vp.Offset == e.offset { + moved++ // This new entry only contains the key, and a pointer to the value. ne := new(Entry) - ne.meta = 0 // Remove all bits. + ne.meta = 0 // Remove all bits. Different keyspace doesn't need these bits. ne.UserMeta = e.UserMeta - ne.Key = make([]byte, len(e.Key)) - copy(ne.Key, e.Key) - ne.Value = make([]byte, len(e.Value)) - copy(ne.Value, e.Value) + + // Create a new key in a separate keyspace, prefixed by moveKey. We are not + // allowed to rewrite an older version of key in the LSM tree, because then this older + // version would be at the top of the LSM tree. To work correctly, reads expect the + // latest versions to be at the top, and the older versions at the bottom. + if bytes.HasPrefix(e.Key, badgerMove) { + ne.Key = append([]byte{}, e.Key...) + } else { + ne.Key = append([]byte{}, badgerMove...) + ne.Key = append(ne.Key, e.Key...) + } + + ne.Value = append([]byte{}, e.Value...) wb = append(wb, ne) size += int64(e.estimateSize(vlog.opt.ValueThreshold)) if size >= 64*mi { - elog.Printf("request has %d entries, size %d", len(wb), size) + tr.LazyPrintf("request has %d entries, size %d", len(wb), size) if err := vlog.kv.batchSet(wb); err != nil { return err } @@ -357,7 +415,7 @@ func (vlog *valueLog) rewrite(f *logFile) error { return err } - elog.Printf("request has %d entries, size %d", len(wb), size) + tr.LazyPrintf("request has %d entries, size %d", len(wb), size) batchSize := 1024 var loops int for i := 0; i < len(wb); { @@ -374,16 +432,16 @@ func (vlog *valueLog) rewrite(f *logFile) error { if err == ErrTxnTooBig { // Decrease the batch size to half. batchSize = batchSize / 2 - elog.Printf("Dropped batch size to %d", batchSize) + tr.LazyPrintf("Dropped batch size to %d", batchSize) continue } return err } i += batchSize } - elog.Printf("Processed %d entries in %d loops", len(wb), loops) - - elog.Printf("Removing fid: %d", f.fid) + tr.LazyPrintf("Processed %d entries in %d loops", len(wb), loops) + tr.LazyPrintf("Total entries: %d. Moved: %d", count, moved) + tr.LazyPrintf("Removing fid: %d", f.fid) var deleteFileNow bool // Entries written to LSM. Remove the older file now. { @@ -409,6 +467,63 @@ func (vlog *valueLog) rewrite(f *logFile) error { return nil } +func (vlog *valueLog) deleteMoveKeysFor(fid uint32, tr trace.Trace) { + db := vlog.kv + var result []*Entry + var count, pointers uint64 + tr.LazyPrintf("Iterating over move keys to find invalids for fid: %d", fid) + err := db.View(func(txn *Txn) error { + opt := DefaultIteratorOptions + opt.internalAccess = true + opt.PrefetchValues = false + itr := txn.NewIterator(opt) + defer itr.Close() + + for itr.Seek(badgerMove); itr.ValidForPrefix(badgerMove); itr.Next() { + count++ + item := itr.Item() + if item.meta&bitValuePointer == 0 { + continue + } + pointers++ + var vp valuePointer + vp.Decode(item.vptr) + if vp.Fid == fid { + e := &Entry{Key: item.KeyCopy(nil), meta: bitDelete} + result = append(result, e) + } + } + return nil + }) + if err != nil { + tr.LazyPrintf("Got error while iterating move keys: %v", err) + tr.SetError() + return + } + tr.LazyPrintf("Num total move keys: %d. Num pointers: %d", count, pointers) + tr.LazyPrintf("Number of invalid move keys found: %d", len(result)) + batchSize := 10240 + for i := 0; i < len(result); { + end := i + batchSize + if end > len(result) { + end = len(result) + } + if err := db.batchSet(result[i:end]); err != nil { + if err == ErrTxnTooBig { + batchSize /= 2 + tr.LazyPrintf("Dropped batch size to %d", batchSize) + continue + } + tr.LazyPrintf("Error while doing batchSet: %v", err) + tr.SetError() + return + } + i += batchSize + } + tr.LazyPrintf("Move keys deletion done.") + return +} + func (vlog *valueLog) incrIteratorCount() { atomic.AddInt32(&vlog.numActiveIterators, 1) } @@ -470,6 +585,7 @@ type valueLog struct { kv *DB maxFid uint32 writableLogOffset uint32 + numEntriesWritten uint32 opt Options garbageCh chan struct{} @@ -484,7 +600,7 @@ func (vlog *valueLog) fpath(fid uint32) string { return vlogFilePath(vlog.dirPath, fid) } -func (vlog *valueLog) openOrCreateFiles() error { +func (vlog *valueLog) openOrCreateFiles(readOnly bool) error { files, err := ioutil.ReadDir(vlog.dirPath) if err != nil { return errors.Wrapf(err, "Error while opening value log") @@ -519,12 +635,18 @@ func (vlog *valueLog) openOrCreateFiles() error { vlog.maxFid = uint32(maxFid) // Open all previous log files as read only. Open the last log file - // as read write. + // as read write (unless the DB is read only). for fid, lf := range vlog.filesMap { if fid == maxFid { - if lf.fd, err = y.OpenExistingSyncedFile(vlog.fpath(fid), - vlog.opt.SyncWrites); err != nil { - return errors.Wrapf(err, "Unable to open value log file as RDWR") + var flags uint32 + if vlog.opt.SyncWrites { + flags |= y.Sync + } + if readOnly { + flags |= y.ReadOnly + } + if lf.fd, err = y.OpenExistingFile(vlog.fpath(fid), flags); err != nil { + return errors.Wrapf(err, "Unable to open value log file") } } else { if err := lf.openReadOnly(); err != nil { @@ -548,6 +670,7 @@ func (vlog *valueLog) createVlogFile(fid uint32) (*logFile, error) { path := vlog.fpath(fid) lf := &logFile{fid: fid, path: path, loadingMode: vlog.opt.ValueLogLoadingMode} vlog.writableLogOffset = 0 + vlog.numEntriesWritten = 0 var err error if lf.fd, err = y.CreateSyncedFile(path, vlog.opt.SyncWrites); err != nil { @@ -570,7 +693,7 @@ func (vlog *valueLog) Open(kv *DB, opt Options) error { vlog.opt = opt vlog.kv = kv vlog.filesMap = make(map[uint32]*logFile) - if err := vlog.openOrCreateFiles(); err != nil { + if err := vlog.openOrCreateFiles(kv.opt.ReadOnly); err != nil { return errors.Wrapf(err, "Unable to open value log") } @@ -592,7 +715,7 @@ func (vlog *valueLog) Close() error { err = munmapErr } - if id == vlog.maxFid { + if !vlog.opt.ReadOnly && id == vlog.maxFid { // truncate writable log file to correct offset. if truncErr := f.fd.Truncate( int64(vlog.writableLogOffset)); truncErr != nil && err == nil { @@ -631,7 +754,8 @@ func (vlog *valueLog) sortedFids() []uint32 { func (vlog *valueLog) Replay(ptr valuePointer, fn logEntry) error { fid := ptr.Fid offset := ptr.Offset + ptr.Len - vlog.elog.Printf("Seeking at value pointer: %+v\n", ptr) + // @kataras removed: vlog.elog.Printf("Seeking at value pointer: %+v\n", ptr) + // @kataras removed: log.Printf("Replaying from value pointer: %+v\n", ptr) fids := vlog.sortedFids() @@ -644,7 +768,11 @@ func (vlog *valueLog) Replay(ptr valuePointer, fn logEntry) error { of = 0 } f := vlog.filesMap[id] + // @kataras removed: + // log.Printf("Iterating file id: %d", id) + // now := time.Now() err := vlog.iterate(f, of, fn) + // @kataras removed: log.Printf("Iteration took: %s\n", time.Since(now)) if err != nil { return errors.Wrapf(err, "Unable to replay value log: %q", f.path) } @@ -667,6 +795,14 @@ type request struct { Err error } +func (req *request) Wait() error { + req.Wg.Wait() + req.Entries = nil + err := req.Err + requestPool.Put(req) + return err +} + // sync is thread-unsafe and should not be called concurrently with write. func (vlog *valueLog) sync() error { if vlog.opt.SyncWrites { @@ -718,14 +854,15 @@ func (vlog *valueLog) write(reqs []*request) error { atomic.AddUint32(&vlog.writableLogOffset, uint32(n)) vlog.buf.Reset() - if vlog.writableOffset() > uint32(vlog.opt.ValueLogFileSize) { + if vlog.writableOffset() > uint32(vlog.opt.ValueLogFileSize) || + vlog.numEntriesWritten > vlog.opt.ValueLogMaxEntries { var err error if err = curlf.doneWriting(vlog.writableLogOffset); err != nil { return err } newid := atomic.AddUint32(&vlog.maxFid, 1) - y.AssertTruef(newid <= math.MaxUint32, "newid will overflow uint32: %v", newid) + y.AssertTruef(newid > 0, "newid has overflown uint32: %v", newid) newlf, err := vlog.createVlogFile(newid) if err != nil { return err @@ -756,11 +893,16 @@ func (vlog *valueLog) write(reqs []*request) error { } p.Len = uint32(plen) b.Ptrs = append(b.Ptrs, p) - - if p.Offset > uint32(vlog.opt.ValueLogFileSize) { - if err := toDisk(); err != nil { - return err - } + } + vlog.numEntriesWritten += uint32(len(b.Entries)) + // We write to disk here so that all entries that are part of the same transaction are + // written to the same vlog file. + writeNow := + vlog.writableOffset()+uint32(vlog.buf.Len()) > uint32(vlog.opt.ValueLogFileSize) || + vlog.numEntriesWritten > uint32(vlog.opt.ValueLogMaxEntries) + if writeNow { + if err := toDisk(); err != nil { + return err } } } @@ -807,7 +949,7 @@ func (vlog *valueLog) Read(vp valuePointer, s *y.Slice) ([]byte, func(), error) func (vlog *valueLog) readValueBytes(vp valuePointer, s *y.Slice) ([]byte, func(), error) { lf, err := vlog.getFileRLocked(vp.Fid) if err != nil { - return nil, nil, errors.Wrapf(err, "Unable to read from value log: %+v", vp) + return nil, nil, err } buf, err := lf.read(vp, s) @@ -834,11 +976,15 @@ func valueBytesToEntry(buf []byte) (e Entry) { return } -func (vlog *valueLog) pickLog(head valuePointer) *logFile { +func (vlog *valueLog) pickLog(head valuePointer, tr trace.Trace) (files []*logFile) { vlog.filesLock.RLock() defer vlog.filesLock.RUnlock() fids := vlog.sortedFids() - if len(fids) <= 1 || head.Fid == 0 { + if len(fids) <= 1 { + tr.LazyPrintf("Only one or less value log file.") + return nil + } else if head.Fid == 0 { + tr.LazyPrintf("Head pointer is at zero.") return nil } @@ -860,7 +1006,10 @@ func (vlog *valueLog) pickLog(head valuePointer) *logFile { vlog.lfDiscardStats.Unlock() if candidate.fid != math.MaxUint32 { // Found a candidate - return vlog.filesMap[candidate.fid] + tr.LazyPrintf("Found candidate via discard stats: %v", candidate) + files = append(files, vlog.filesMap[candidate.fid]) + } else { + tr.LazyPrintf("Could not find candidate via discard stats. Randomly picking one.") } // Fallback to randomly picking a log file @@ -872,13 +1021,16 @@ func (vlog *valueLog) pickLog(head valuePointer) *logFile { } } if idxHead == 0 { // Not found or first file + tr.LazyPrintf("Could not find any file.") return nil } idx := rand.Intn(idxHead) // Don’t include head.Fid. We pick a random file before it. if idx > 0 { idx = rand.Intn(idx + 1) // Another level of rand to favor smaller fids. } - return vlog.filesMap[fids[idx]] + tr.LazyPrintf("Randomly chose fid: %d", fids[idx]) + files = append(files, vlog.filesMap[fids[idx]]) + return files } func discardEntry(e Entry, vs y.ValueStruct) bool { @@ -900,13 +1052,7 @@ func discardEntry(e Entry, vs y.ValueStruct) bool { return false } -func (vlog *valueLog) doRunGC(gcThreshold float64, head valuePointer) (err error) { - // Pick a log file for GC - lf := vlog.pickLog(head) - if lf == nil { - return ErrNoRewrite - } - +func (vlog *valueLog) doRunGC(lf *logFile, discardRatio float64, tr trace.Trace) (err error) { // Update stats before exiting defer func() { if err == nil { @@ -918,39 +1064,57 @@ func (vlog *valueLog) doRunGC(gcThreshold float64, head valuePointer) (err error type reason struct { total float64 - keep float64 discard float64 + count int } - var r reason - var window = 100.0 - count := 0 + fi, err := lf.fd.Stat() + if err != nil { + tr.LazyPrintf("Error while finding file size: %v", err) + tr.SetError() + return err + } + + // Set up the sampling window sizes. + sizeWindow := float64(fi.Size()) * 0.1 // 10% of the file as window. + countWindow := int(float64(vlog.opt.ValueLogMaxEntries) * 0.01) // 1% of num entries. + tr.LazyPrintf("Size window: %5.2f. Count window: %d.", sizeWindow, countWindow) // Pick a random start point for the log. - skipFirstM := float64(rand.Intn(int(vlog.opt.ValueLogFileSize/mi))) - window + skipFirstM := float64(rand.Int63n(fi.Size())) // Pick a random starting location. + skipFirstM -= sizeWindow // Avoid hitting EOF by moving back by window. + skipFirstM /= float64(mi) // Convert to MBs. + tr.LazyPrintf("Skip first %5.2f MB of file of size: %d MB", skipFirstM, fi.Size()/mi) var skipped float64 + var r reason start := time.Now() y.AssertTrue(vlog.kv != nil) s := new(y.Slice) + var numIterations int err = vlog.iterate(lf, 0, func(e Entry, vp valuePointer) error { - esz := float64(vp.Len) / (1 << 20) // in MBs. +4 for the CAS stuff. - skipped += esz + numIterations++ + esz := float64(vp.Len) / (1 << 20) // in MBs. if skipped < skipFirstM { + skipped += esz return nil } - count++ - if count%100 == 0 { - time.Sleep(time.Millisecond) + // Sample until we reach the window sizes or exceed 10 seconds. + if r.count > countWindow { + tr.LazyPrintf("Stopping sampling after %d entries.", countWindow) + return errStop } - r.total += esz - if r.total > window { + if r.total > sizeWindow { + tr.LazyPrintf("Stopping sampling after reaching window size.") return errStop } if time.Since(start) > 10*time.Second { + tr.LazyPrintf("Stopping sampling after 10 seconds.") return errStop } + r.total += esz + r.count++ vs, err := vlog.kv.get(e.Key) if err != nil { @@ -977,7 +1141,6 @@ func (vlog *valueLog) doRunGC(gcThreshold float64, head valuePointer) (err error } if vp.Fid == lf.fid && vp.Offset == e.offset { // This is still the active entry. This would need to be rewritten. - r.keep += esz } else { vlog.elog.Printf("Reason=%+v\n", r) @@ -998,21 +1161,23 @@ func (vlog *valueLog) doRunGC(gcThreshold float64, head valuePointer) (err error }) if err != nil { - vlog.elog.Errorf("Error while iterating for RunGC: %v", err) + tr.LazyPrintf("Error while iterating for RunGC: %v", err) + tr.SetError() return err } - vlog.elog.Printf("Fid: %d Data status=%+v\n", lf.fid, r) + tr.LazyPrintf("Fid: %d. Skipped: %5.2fMB Num iterations: %d. Data status=%+v\n", + lf.fid, skipped, numIterations, r) - if r.total < 10.0 || r.discard < gcThreshold*r.total { - vlog.elog.Printf("Skipping GC on fid: %d\n\n", lf.fid) + // If we couldn't sample at least a 1000 KV pairs or at least 75% of the window size, + // and what we can discard is below the threshold, we should skip the rewrite. + if (r.count < countWindow && r.total < sizeWindow*0.75) || r.discard < discardRatio*r.total { + tr.LazyPrintf("Skipping GC on fid: %d", lf.fid) return ErrNoRewrite } - - vlog.elog.Printf("REWRITING VLOG %d\n", lf.fid) - if err = vlog.rewrite(lf); err != nil { + if err = vlog.rewrite(lf, tr); err != nil { return err } - vlog.elog.Printf("Done rewriting.") + tr.LazyPrintf("Done rewriting.") return nil } @@ -1026,24 +1191,34 @@ func (vlog *valueLog) waitOnGC(lc *y.Closer) { vlog.garbageCh <- struct{}{} } -func (vlog *valueLog) runGC(gcThreshold float64, head valuePointer) error { +func (vlog *valueLog) runGC(discardRatio float64, head valuePointer) error { select { case vlog.garbageCh <- struct{}{}: - // Run GC - var ( - err error - count int - ) - for { - err = vlog.doRunGC(gcThreshold, head) - if err != nil { - break - } - count++ + // Pick a log file for GC. + tr := trace.New("Badger.ValueLog", "GC") + tr.SetMaxEvents(100) + defer func() { + tr.Finish() + <-vlog.garbageCh + }() + + var err error + files := vlog.pickLog(head, tr) + if len(files) == 0 { + tr.LazyPrintf("PickLog returned zero results.") + return ErrNoRewrite } - <-vlog.garbageCh - if err == ErrNoRewrite && count > 0 { - return nil + tried := make(map[uint32]bool) + for _, lf := range files { + if _, done := tried[lf.fid]; done { + continue + } + tried[lf.fid] = true + err = vlog.doRunGC(lf, discardRatio, tr) + if err == nil { + vlog.deleteMoveKeysFor(lf.fid, tr) + return nil + } } return err default: @@ -1051,12 +1226,10 @@ func (vlog *valueLog) runGC(gcThreshold float64, head valuePointer) error { } } -func (vlog *valueLog) updateGCStats(item *Item) { - if item.meta&bitValuePointer > 0 { - var vp valuePointer - vp.Decode(item.vptr) - vlog.lfDiscardStats.Lock() - vlog.lfDiscardStats.m[vp.Fid] += int64(vp.Len) - vlog.lfDiscardStats.Unlock() +func (vlog *valueLog) updateGCStats(stats map[uint32]int64) { + vlog.lfDiscardStats.Lock() + for fid, sz := range stats { + vlog.lfDiscardStats.m[fid] += sz } + vlog.lfDiscardStats.Unlock() } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/vendor/github.com/golang/protobuf/proto/proto3_test.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/vendor/github.com/golang/protobuf/proto/proto3_test.go deleted file mode 100644 index 73eed6c0b5..0000000000 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/vendor/github.com/golang/protobuf/proto/proto3_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Go support for Protocol Buffers - Google's data interchange format -// -// Copyright 2014 The Go Authors. All rights reserved. -// https://github.com/golang/protobuf -// -// 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. - -package proto_test - -import ( - "bytes" - "testing" - - "github.com/golang/protobuf/proto" - pb "github.com/golang/protobuf/proto/proto3_proto" - tpb "github.com/golang/protobuf/proto/test_proto" -) - -func TestProto3ZeroValues(t *testing.T) { - tests := []struct { - desc string - m proto.Message - }{ - {"zero message", &pb.Message{}}, - {"empty bytes field", &pb.Message{Data: []byte{}}}, - } - for _, test := range tests { - b, err := proto.Marshal(test.m) - if err != nil { - t.Errorf("%s: proto.Marshal: %v", test.desc, err) - continue - } - if len(b) > 0 { - t.Errorf("%s: Encoding is non-empty: %q", test.desc, b) - } - } -} - -func TestRoundTripProto3(t *testing.T) { - m := &pb.Message{ - Name: "David", // (2 | 1<<3): 0x0a 0x05 "David" - Hilarity: pb.Message_PUNS, // (0 | 2<<3): 0x10 0x01 - HeightInCm: 178, // (0 | 3<<3): 0x18 0xb2 0x01 - Data: []byte("roboto"), // (2 | 4<<3): 0x20 0x06 "roboto" - ResultCount: 47, // (0 | 7<<3): 0x38 0x2f - TrueScotsman: true, // (0 | 8<<3): 0x40 0x01 - Score: 8.1, // (5 | 9<<3): 0x4d <8.1> - - Key: []uint64{1, 0xdeadbeef}, - Nested: &pb.Nested{ - Bunny: "Monty", - }, - } - t.Logf(" m: %v", m) - - b, err := proto.Marshal(m) - if err != nil { - t.Fatalf("proto.Marshal: %v", err) - } - t.Logf(" b: %q", b) - - m2 := new(pb.Message) - if err := proto.Unmarshal(b, m2); err != nil { - t.Fatalf("proto.Unmarshal: %v", err) - } - t.Logf("m2: %v", m2) - - if !proto.Equal(m, m2) { - t.Errorf("proto.Equal returned false:\n m: %v\nm2: %v", m, m2) - } -} - -func TestGettersForBasicTypesExist(t *testing.T) { - var m pb.Message - if got := m.GetNested().GetBunny(); got != "" { - t.Errorf("m.GetNested().GetBunny() = %q, want empty string", got) - } - if got := m.GetNested().GetCute(); got { - t.Errorf("m.GetNested().GetCute() = %t, want false", got) - } -} - -func TestProto3SetDefaults(t *testing.T) { - in := &pb.Message{ - Terrain: map[string]*pb.Nested{ - "meadow": new(pb.Nested), - }, - Proto2Field: new(tpb.SubDefaults), - Proto2Value: map[string]*tpb.SubDefaults{ - "badlands": new(tpb.SubDefaults), - }, - } - - got := proto.Clone(in).(*pb.Message) - proto.SetDefaults(got) - - // There are no defaults in proto3. Everything should be the zero value, but - // we need to remember to set defaults for nested proto2 messages. - want := &pb.Message{ - Terrain: map[string]*pb.Nested{ - "meadow": new(pb.Nested), - }, - Proto2Field: &tpb.SubDefaults{N: proto.Int64(7)}, - Proto2Value: map[string]*tpb.SubDefaults{ - "badlands": &tpb.SubDefaults{N: proto.Int64(7)}, - }, - } - - if !proto.Equal(got, want) { - t.Errorf("with in = %v\nproto.SetDefaults(in) =>\ngot %v\nwant %v", in, got, want) - } -} - -func TestUnknownFieldPreservation(t *testing.T) { - b1 := "\x0a\x05David" // Known tag 1 - b2 := "\xc2\x0c\x06Google" // Unknown tag 200 - b := []byte(b1 + b2) - - m := new(pb.Message) - if err := proto.Unmarshal(b, m); err != nil { - t.Fatalf("proto.Unmarshal: %v", err) - } - - if !bytes.Equal(m.XXX_unrecognized, []byte(b2)) { - t.Fatalf("mismatching unknown fields:\ngot %q\nwant %q", m.XXX_unrecognized, b2) - } -} diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_dsync.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_dsync.go index bd25c9e9a3..3f3445e2e9 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_dsync.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_dsync.go @@ -1,4 +1,4 @@ -// +build !dragonfly,!freebsd,!windows,!darwin +// +build !dragonfly,!freebsd,!windows /* * Copyright 2017 Dgraph Labs, Inc. and Contributors @@ -18,8 +18,8 @@ package y -import "syscall" +import "golang.org/x/sys/unix" func init() { - datasyncFileFlag = syscall.O_DSYNC + datasyncFileFlag = unix.O_DSYNC } diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_nodsync.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_nodsync.go index be295f2e02..b68be7ab94 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_nodsync.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/file_nodsync.go @@ -1,4 +1,4 @@ -// +build dragonfly freebsd windows darwin +// +build dragonfly freebsd windows /* * Copyright 2017 Dgraph Labs, Inc. and Contributors diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/watermark.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/watermark.go new file mode 100644 index 0000000000..6fd3c89968 --- /dev/null +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/watermark.go @@ -0,0 +1,130 @@ +/* + * Copyright 2018 Dgraph Labs, Inc. and Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package y + +import ( + "container/heap" + "sync/atomic" + + "golang.org/x/net/trace" +) + +type uint64Heap []uint64 + +func (u uint64Heap) Len() int { return len(u) } +func (u uint64Heap) Less(i int, j int) bool { return u[i] < u[j] } +func (u uint64Heap) Swap(i int, j int) { u[i], u[j] = u[j], u[i] } +func (u *uint64Heap) Push(x interface{}) { *u = append(*u, x.(uint64)) } +func (u *uint64Heap) Pop() interface{} { + old := *u + n := len(old) + x := old[n-1] + *u = old[0 : n-1] + return x +} + +type mark struct { + readTs uint64 + done bool // Set to true if the pending mutation is done. +} +type WaterMark struct { + markCh chan mark + minReadTs uint64 + elog trace.EventLog +} + +// Init initializes a WaterMark struct. MUST be called before using it. +func (w *WaterMark) Init() { + w.markCh = make(chan mark, 1000) + w.elog = trace.NewEventLog("Badger", "MinReadTs") + go w.process() +} + +func (w *WaterMark) Begin(readTs uint64) { + w.markCh <- mark{readTs: readTs, done: false} +} +func (w *WaterMark) Done(readTs uint64) { + w.markCh <- mark{readTs: readTs, done: true} +} + +// DoneUntil returns the maximum index until which all tasks are done. +func (w *WaterMark) MinReadTs() uint64 { + return atomic.LoadUint64(&w.minReadTs) +} + +// process is used to process the Mark channel. This is not thread-safe, +// so only run one goroutine for process. One is sufficient, because +// all ops in the goroutine use only memory and cpu. +func (w *WaterMark) process() { + var reads uint64Heap + // pending maps raft proposal index to the number of pending mutations for this proposal. + pending := make(map[uint64]int) + + heap.Init(&reads) + var loop uint64 + + processOne := func(readTs uint64, done bool) { + // If not already done, then set. Otherwise, don't undo a done entry. + prev, present := pending[readTs] + if !present { + heap.Push(&reads, readTs) + } + + delta := 1 + if done { + delta = -1 + } + pending[readTs] = prev + delta + + loop++ + if len(reads) > 0 && loop%1000 == 0 { + min := reads[0] + w.elog.Printf("ReadTs: %4d. Size: %4d MinReadTs: %-4d Looking for: %-4d. Value: %d\n", + readTs, len(reads), w.MinReadTs(), min, pending[min]) + } + + // Update mark by going through all reads in order; and checking if they have + // been done. Stop at the first readTs, which isn't done. + minReadTs := w.MinReadTs() + // Don't assert that minReadTs < readTs, to avoid any inconsistencies caused by managed + // transactions, or testing where we explicitly set the readTs for transactions like in + // TestTxnVersions. + until := minReadTs + loops := 0 + + for len(reads) > 0 { + min := reads[0] + if done := pending[min]; done != 0 { + break // len(reads) will be > 0. + } + heap.Pop(&reads) + delete(pending, min) + until = min + loops++ + } + if until != minReadTs { + AssertTrue(atomic.CompareAndSwapUint64(&w.minReadTs, minReadTs, until)) + w.elog.Printf("MinReadTs: %d. Loops: %d\n", until, loops) + } + } + + for mark := range w.markCh { + if mark.readTs > 0 { + processOne(mark.readTs, mark.done) + } + } +} diff --git a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/y.go b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/y.go index 20a8ea55e4..5927fd3836 100644 --- a/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/y.go +++ b/sessions/sessiondb/badger/vendor/github.com/dgraph-io/badger/y/y.go @@ -31,6 +31,15 @@ import ( // and encountering the end of slice. var ErrEOF = errors.New("End of mapped region") +const ( + // Sync indicates that O_DSYNC should be set on the underlying file, + // ensuring that data writes do not return until the data is flushed + // to disk. + Sync = 1 << iota + // ReadOnly opens the underlying file on a read-only basis. + ReadOnly +) + var ( // This is O_DSYNC (datasync) on platforms that support it -- see file_unix.go datasyncFileFlag = 0x0 @@ -39,13 +48,17 @@ var ( CastagnoliCrcTable = crc32.MakeTable(crc32.Castagnoli) ) -// OpenExistingSyncedFile opens an existing file, errors if it doesn't exist. -func OpenExistingSyncedFile(filename string, sync bool) (*os.File, error) { - flags := os.O_RDWR - if sync { - flags |= datasyncFileFlag +// OpenExistingFile opens an existing file, errors if it doesn't exist. +func OpenExistingFile(filename string, flags uint32) (*os.File, error) { + openFlags := os.O_RDWR + if flags&ReadOnly != 0 { + openFlags = os.O_RDONLY + } + + if flags&Sync != 0 { + openFlags |= datasyncFileFlag } - return os.OpenFile(filename, flags, 0) + return os.OpenFile(filename, openFlags, 0) } // CreateSyncedFile creates a new file (using O_EXCL), errors if it already existed. diff --git a/sessions/sessiondb/boltdb/database.go b/sessions/sessiondb/boltdb/database.go index fe1c90feb0..7a27d5cc0e 100644 --- a/sessions/sessiondb/boltdb/database.go +++ b/sessions/sessiondb/boltdb/database.go @@ -6,10 +6,11 @@ import ( "runtime" "time" - "github.com/coreos/bbolt" - "github.com/kataras/golog" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/sessions" + + bolt "github.com/etcd-io/bbolt" + "github.com/kataras/golog" ) // DefaultFileMode used as the default database's "fileMode" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/LICENSE b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/LICENSE similarity index 100% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/LICENSE rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/LICENSE diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_386.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_386.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_386.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_386.go index 820d533c15..4d35ee7cf3 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_386.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_386.go @@ -1,4 +1,4 @@ -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0x7FFFFFFF // 2GB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_amd64.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_amd64.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_amd64.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_amd64.go index 98fafdb47d..60a52dad56 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_amd64.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_amd64.go @@ -1,4 +1,4 @@ -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm.go similarity index 98% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm.go index 7e5cb4b941..105d27ddb7 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import "unsafe" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm64.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm64.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm64.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm64.go index b26d84f91b..f5aa2a5ee2 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_arm64.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_arm64.go @@ -1,6 +1,6 @@ // +build arm64 -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_linux.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_linux.go similarity index 91% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_linux.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_linux.go index 2b67666140..7707bcacf0 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_linux.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_linux.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "syscall" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mips64x.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mips64x.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mips64x.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mips64x.go index 134b578bd4..baeb289fd9 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mips64x.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mips64x.go @@ -1,6 +1,6 @@ // +build mips64 mips64le -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0x8000000000 // 512GB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mipsx.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mipsx.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mipsx.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mipsx.go index d5ecb0597e..2d9b1a91f3 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_mipsx.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_mipsx.go @@ -1,6 +1,6 @@ // +build mips mipsle -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0x40000000 // 1GB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_openbsd.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_openbsd.go similarity index 97% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_openbsd.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_openbsd.go index 7058c3d734..d7f50358ef 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_openbsd.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_openbsd.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "syscall" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc.go index 55cb8a72cc..69804714aa 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc.go @@ -1,6 +1,6 @@ // +build ppc -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0x7FFFFFFF // 2GB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64.go index 9331d9771e..3565908576 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64.go @@ -1,6 +1,6 @@ // +build ppc64 -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64le.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64le.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64le.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64le.go index 8c143bc5d1..422c7c69d6 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_ppc64le.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_ppc64le.go @@ -1,6 +1,6 @@ // +build ppc64le -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_s390x.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_s390x.go similarity index 95% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_s390x.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_s390x.go index d7c39af925..6d3fcb825d 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_s390x.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_s390x.go @@ -1,6 +1,6 @@ // +build s390x -package bolt +package bbolt // maxMapSize represents the largest mmap size supported by Bolt. const maxMapSize = 0xFFFFFFFFFFFF // 256TB diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix.go index add3bd8823..38e11d4c4d 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix.go @@ -1,6 +1,6 @@ // +build !windows,!plan9,!solaris -package bolt +package bbolt import ( "fmt" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix_solaris.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix_solaris.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix_solaris.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix_solaris.go index fd8335ecc9..492eaf302c 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_unix_solaris.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_unix_solaris.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "fmt" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_windows.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_windows.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_windows.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_windows.go index ca6f9a11c2..4e3f90fb43 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bolt_windows.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bolt_windows.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "fmt" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/boltsync_unix.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/boltsync_unix.go similarity index 91% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/boltsync_unix.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/boltsync_unix.go index f50442523c..9587afefee 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/boltsync_unix.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/boltsync_unix.go @@ -1,6 +1,6 @@ // +build !windows,!plan9,!linux,!openbsd -package bolt +package bbolt // fdatasync flushes written data to a file descriptor. func fdatasync(db *DB) error { diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bucket.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bucket.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bucket.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bucket.go index 44db88b8ab..84bfd4d6a2 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/bucket.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/bucket.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "bytes" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/cursor.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/cursor.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/cursor.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/cursor.go index 1bdda63a2f..3000aced6c 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/cursor.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/cursor.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "bytes" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/db.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/db.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/db.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/db.go index ab97c6014a..4e38ab8c62 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/db.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/db.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "errors" @@ -454,8 +454,8 @@ func (db *DB) Close() error { db.metalock.Lock() defer db.metalock.Unlock() - db.mmaplock.RLock() - defer db.mmaplock.RUnlock() + db.mmaplock.Lock() + defer db.mmaplock.Unlock() return db.close() } diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/errors.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/errors.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/errors.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/errors.go index a3620a3ebb..48758ca577 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/errors.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/errors.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import "errors" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/freelist.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/freelist.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/freelist.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/freelist.go index 266f154294..e4bcb2dcf9 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/freelist.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/freelist.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "fmt" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/node.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/node.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/node.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/node.go index f4ce240edd..6c3fa553ea 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/node.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/node.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "bytes" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/page.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/page.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/page.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/page.go index cde403ae86..bca9615f0f 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/page.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/page.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "fmt" diff --git a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/tx.go b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/tx.go similarity index 99% rename from sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/tx.go rename to sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/tx.go index 41a9bc619a..f508641427 100644 --- a/sessions/sessiondb/boltdb/vendor/github.com/coreos/bbolt/tx.go +++ b/sessions/sessiondb/boltdb/vendor/github.com/etcd-io/bbolt/tx.go @@ -1,4 +1,4 @@ -package bolt +package bbolt import ( "fmt" diff --git a/vendor/github.com/hashicorp/go-version/LICENSE b/vendor/github.com/hashicorp/go-version/LICENSE deleted file mode 100644 index c33dcc7c92..0000000000 --- a/vendor/github.com/hashicorp/go-version/LICENSE +++ /dev/null @@ -1,354 +0,0 @@ -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. “Contributor” - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. “Contributor Version” - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor’s Contribution. - -1.3. “Contribution” - - means Covered Software of a particular Contributor. - -1.4. “Covered Software” - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. “Incompatible With Secondary Licenses” - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of version - 1.1 or earlier of the License, but not also under the terms of a - Secondary License. - -1.6. “Executable Form” - - means any form of the work other than Source Code Form. - -1.7. “Larger Work” - - means a work that combines Covered Software with other material, in a separate - file or files, that is not Covered Software. - -1.8. “License” - - means this document. - -1.9. “Licensable” - - means having the right to grant, to the maximum extent possible, whether at the - time of the initial grant or subsequently, any and all of the rights conveyed by - this License. - -1.10. “Modifications” - - means any of the following: - - a. any file in Source Code Form that results from an addition to, deletion - from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. “Patent Claims” of a Contributor - - means any patent claim(s), including without limitation, method, process, - and apparatus claims, in any patent Licensable by such Contributor that - would be infringed, but for the grant of the License, by the making, - using, selling, offering for sale, having made, import, or transfer of - either its Contributions or its Contributor Version. - -1.12. “Secondary License” - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. “Source Code Form” - - means the form of the work preferred for making modifications. - -1.14. “You” (or “Your”) - - means an individual or a legal entity exercising rights under this - License. For legal entities, “You” includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, “control” means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or as - part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its Contributions - or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution become - effective for each Contribution on the date the Contributor first distributes - such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under this - License. No additional rights or licenses will be implied from the distribution - or licensing of Covered Software under this License. Notwithstanding Section - 2.1(b) above, no patent license is granted by a Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party’s - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of its - Contributions. - - This License does not grant any rights in the trademarks, service marks, or - logos of any Contributor (except as may be necessary to comply with the - notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this License - (see Section 10.2) or under the terms of a Secondary License (if permitted - under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its Contributions - are its original creation(s) or it has sufficient rights to grant the - rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under applicable - copyright doctrines of fair use, fair dealing, or other equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under the - terms of this License. You must inform recipients that the Source Code Form - of the Covered Software is governed by the terms of this License, and how - they can obtain a copy of this License. You may not attempt to alter or - restrict the recipients’ rights in the Source Code Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this License, - or sublicense it under different terms, provided that the license for - the Executable Form does not attempt to limit or alter the recipients’ - rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for the - Covered Software. If the Larger Work is a combination of Covered Software - with a work governed by one or more Secondary Licenses, and the Covered - Software is not Incompatible With Secondary Licenses, this License permits - You to additionally distribute such Covered Software under the terms of - such Secondary License(s), so that the recipient of the Larger Work may, at - their option, further distribute the Covered Software under the terms of - either this License or such Secondary License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices (including - copyright notices, patent notices, disclaimers of warranty, or limitations - of liability) contained within the Source Code Form of the Covered - Software, except that You may alter any license notices to the extent - required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on behalf - of any Contributor. You must make it absolutely clear that any such - warranty, support, indemnity, or liability obligation is offered by You - alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, judicial - order, or regulation then You must: (a) comply with the terms of this License - to the maximum extent possible; and (b) describe the limitations and the code - they affect. Such description must be placed in a text file included with all - distributions of the Covered Software under this License. Except to the - extent prohibited by statute or regulation, such description must be - sufficiently detailed for a recipient of ordinary skill to be able to - understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing basis, - if such Contributor fails to notify You of the non-compliance by some - reasonable means prior to 60 days after You have come back into compliance. - Moreover, Your grants from a particular Contributor are reinstated on an - ongoing basis if such Contributor notifies You of the non-compliance by - some reasonable means, this is the first time You have received notice of - non-compliance with this License from such Contributor, and You become - compliant prior to 30 days after Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, counter-claims, - and cross-claims) alleging that a Contributor Version directly or - indirectly infringes any patent, then the rights granted to You by any and - all Contributors for the Covered Software under Section 2.1 of this License - shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an “as is” basis, without - warranty of any kind, either expressed, implied, or statutory, including, - without limitation, warranties that the Covered Software is free of defects, - merchantable, fit for a particular purpose or non-infringing. The entire - risk as to the quality and performance of the Covered Software is with You. - Should any Covered Software prove defective in any respect, You (not any - Contributor) assume the cost of any necessary servicing, repair, or - correction. This disclaimer of warranty constitutes an essential part of this - License. No use of any Covered Software is authorized under this License - except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from such - party’s negligence to the extent applicable law prohibits such limitation. - Some jurisdictions do not allow the exclusion or limitation of incidental or - consequential damages, so this exclusion and limitation may not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts of - a jurisdiction where the defendant maintains its principal place of business - and such litigation shall be governed by laws of that jurisdiction, without - reference to its conflict-of-law provisions. Nothing in this Section shall - prevent a party’s ability to bring cross-claims or counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject matter - hereof. If any provision of this License is held to be unenforceable, such - provision shall be reformed only to the extent necessary to make it - enforceable. Any law or regulation which provides that the language of a - contract shall be construed against the drafter shall not be used to construe - this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version of - the License under which You originally received the Covered Software, or - under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a modified - version of this License if you rename the license and remove any - references to the name of the license steward (except to note that such - modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses - If You choose to distribute Source Code Form that is Incompatible With - Secondary Licenses under the terms of this version of the License, the - notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular file, then -You may include the notice in a location (such as a LICENSE file in a relevant -directory) where a recipient would be likely to look for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - “Incompatible With Secondary Licenses” Notice - - This Source Code Form is “Incompatible - With Secondary Licenses”, as defined by - the Mozilla Public License, v. 2.0. - diff --git a/vendor/github.com/hashicorp/go-version/constraint.go b/vendor/github.com/hashicorp/go-version/constraint.go deleted file mode 100644 index d055759611..0000000000 --- a/vendor/github.com/hashicorp/go-version/constraint.go +++ /dev/null @@ -1,204 +0,0 @@ -package version - -import ( - "fmt" - "reflect" - "regexp" - "strings" -) - -// Constraint represents a single constraint for a version, such as -// ">= 1.0". -type Constraint struct { - f constraintFunc - check *Version - original string -} - -// Constraints is a slice of constraints. We make a custom type so that -// we can add methods to it. -type Constraints []*Constraint - -type constraintFunc func(v, c *Version) bool - -var constraintOperators map[string]constraintFunc - -var constraintRegexp *regexp.Regexp - -func init() { - constraintOperators = map[string]constraintFunc{ - "": constraintEqual, - "=": constraintEqual, - "!=": constraintNotEqual, - ">": constraintGreaterThan, - "<": constraintLessThan, - ">=": constraintGreaterThanEqual, - "<=": constraintLessThanEqual, - "~>": constraintPessimistic, - } - - ops := make([]string, 0, len(constraintOperators)) - for k := range constraintOperators { - ops = append(ops, regexp.QuoteMeta(k)) - } - - constraintRegexp = regexp.MustCompile(fmt.Sprintf( - `^\s*(%s)\s*(%s)\s*$`, - strings.Join(ops, "|"), - VersionRegexpRaw)) -} - -// NewConstraint will parse one or more constraints from the given -// constraint string. The string must be a comma-separated list of -// constraints. -func NewConstraint(v string) (Constraints, error) { - vs := strings.Split(v, ",") - result := make([]*Constraint, len(vs)) - for i, single := range vs { - c, err := parseSingle(single) - if err != nil { - return nil, err - } - - result[i] = c - } - - return Constraints(result), nil -} - -// Check tests if a version satisfies all the constraints. -func (cs Constraints) Check(v *Version) bool { - for _, c := range cs { - if !c.Check(v) { - return false - } - } - - return true -} - -// Returns the string format of the constraints -func (cs Constraints) String() string { - csStr := make([]string, len(cs)) - for i, c := range cs { - csStr[i] = c.String() - } - - return strings.Join(csStr, ",") -} - -// Check tests if a constraint is validated by the given version. -func (c *Constraint) Check(v *Version) bool { - return c.f(v, c.check) -} - -func (c *Constraint) String() string { - return c.original -} - -func parseSingle(v string) (*Constraint, error) { - matches := constraintRegexp.FindStringSubmatch(v) - if matches == nil { - return nil, fmt.Errorf("Malformed constraint: %s", v) - } - - check, err := NewVersion(matches[2]) - if err != nil { - return nil, err - } - - return &Constraint{ - f: constraintOperators[matches[1]], - check: check, - original: v, - }, nil -} - -func prereleaseCheck(v, c *Version) bool { - switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; { - case cPre && vPre: - // A constraint with a pre-release can only match a pre-release version - // with the same base segments. - return reflect.DeepEqual(c.Segments64(), v.Segments64()) - - case !cPre && vPre: - // A constraint without a pre-release can only match a version without a - // pre-release. - return false - - case cPre && !vPre: - // OK, except with the pessimistic operator - case !cPre && !vPre: - // OK - } - return true -} - -//------------------------------------------------------------------- -// Constraint functions -//------------------------------------------------------------------- - -func constraintEqual(v, c *Version) bool { - return v.Equal(c) -} - -func constraintNotEqual(v, c *Version) bool { - return !v.Equal(c) -} - -func constraintGreaterThan(v, c *Version) bool { - return prereleaseCheck(v, c) && v.Compare(c) == 1 -} - -func constraintLessThan(v, c *Version) bool { - return prereleaseCheck(v, c) && v.Compare(c) == -1 -} - -func constraintGreaterThanEqual(v, c *Version) bool { - return prereleaseCheck(v, c) && v.Compare(c) >= 0 -} - -func constraintLessThanEqual(v, c *Version) bool { - return prereleaseCheck(v, c) && v.Compare(c) <= 0 -} - -func constraintPessimistic(v, c *Version) bool { - // Using a pessimistic constraint with a pre-release, restricts versions to pre-releases - if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") { - return false - } - - // If the version being checked is naturally less than the constraint, then there - // is no way for the version to be valid against the constraint - if v.LessThan(c) { - return false - } - // We'll use this more than once, so grab the length now so it's a little cleaner - // to write the later checks - cs := len(c.segments) - - // If the version being checked has less specificity than the constraint, then there - // is no way for the version to be valid against the constraint - if cs > len(v.segments) { - return false - } - - // Check the segments in the constraint against those in the version. If the version - // being checked, at any point, does not have the same values in each index of the - // constraints segments, then it cannot be valid against the constraint. - for i := 0; i < c.si-1; i++ { - if v.segments[i] != c.segments[i] { - return false - } - } - - // Check the last part of the segment in the constraint. If the version segment at - // this index is less than the constraints segment at this index, then it cannot - // be valid against the constraint - if c.segments[cs-1] > v.segments[cs-1] { - return false - } - - // If nothing has rejected the version by now, it's valid - return true -} diff --git a/vendor/github.com/hashicorp/go-version/version.go b/vendor/github.com/hashicorp/go-version/version.go deleted file mode 100644 index 4d1e6e2210..0000000000 --- a/vendor/github.com/hashicorp/go-version/version.go +++ /dev/null @@ -1,347 +0,0 @@ -package version - -import ( - "bytes" - "fmt" - "reflect" - "regexp" - "strconv" - "strings" -) - -// The compiled regular expression used to test the validity of a version. -var versionRegexp *regexp.Regexp - -// The raw regular expression string used for testing the validity -// of a version. -const VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` + - `(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` + - `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` + - `?` - -// Version represents a single version. -type Version struct { - metadata string - pre string - segments []int64 - si int - original string -} - -func init() { - versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$") -} - -// NewVersion parses the given version and returns a new -// Version. -func NewVersion(v string) (*Version, error) { - matches := versionRegexp.FindStringSubmatch(v) - if matches == nil { - return nil, fmt.Errorf("Malformed version: %s", v) - } - segmentsStr := strings.Split(matches[1], ".") - segments := make([]int64, len(segmentsStr)) - si := 0 - for i, str := range segmentsStr { - val, err := strconv.ParseInt(str, 10, 64) - if err != nil { - return nil, fmt.Errorf( - "Error parsing version: %s", err) - } - - segments[i] = int64(val) - si++ - } - - // Even though we could support more than three segments, if we - // got less than three, pad it with 0s. This is to cover the basic - // default usecase of semver, which is MAJOR.MINOR.PATCH at the minimum - for i := len(segments); i < 3; i++ { - segments = append(segments, 0) - } - - pre := matches[7] - if pre == "" { - pre = matches[4] - } - - return &Version{ - metadata: matches[10], - pre: pre, - segments: segments, - si: si, - original: v, - }, nil -} - -// Must is a helper that wraps a call to a function returning (*Version, error) -// and panics if error is non-nil. -func Must(v *Version, err error) *Version { - if err != nil { - panic(err) - } - - return v -} - -// Compare compares this version to another version. This -// returns -1, 0, or 1 if this version is smaller, equal, -// or larger than the other version, respectively. -// -// If you want boolean results, use the LessThan, Equal, -// or GreaterThan methods. -func (v *Version) Compare(other *Version) int { - // A quick, efficient equality check - if v.String() == other.String() { - return 0 - } - - segmentsSelf := v.Segments64() - segmentsOther := other.Segments64() - - // If the segments are the same, we must compare on prerelease info - if reflect.DeepEqual(segmentsSelf, segmentsOther) { - preSelf := v.Prerelease() - preOther := other.Prerelease() - if preSelf == "" && preOther == "" { - return 0 - } - if preSelf == "" { - return 1 - } - if preOther == "" { - return -1 - } - - return comparePrereleases(preSelf, preOther) - } - - // Get the highest specificity (hS), or if they're equal, just use segmentSelf length - lenSelf := len(segmentsSelf) - lenOther := len(segmentsOther) - hS := lenSelf - if lenSelf < lenOther { - hS = lenOther - } - // Compare the segments - // Because a constraint could have more/less specificity than the version it's - // checking, we need to account for a lopsided or jagged comparison - for i := 0; i < hS; i++ { - if i > lenSelf-1 { - // This means Self had the lower specificity - // Check to see if the remaining segments in Other are all zeros - if !allZero(segmentsOther[i:]) { - // if not, it means that Other has to be greater than Self - return -1 - } - break - } else if i > lenOther-1 { - // this means Other had the lower specificity - // Check to see if the remaining segments in Self are all zeros - - if !allZero(segmentsSelf[i:]) { - //if not, it means that Self has to be greater than Other - return 1 - } - break - } - lhs := segmentsSelf[i] - rhs := segmentsOther[i] - if lhs == rhs { - continue - } else if lhs < rhs { - return -1 - } - // Otherwis, rhs was > lhs, they're not equal - return 1 - } - - // if we got this far, they're equal - return 0 -} - -func allZero(segs []int64) bool { - for _, s := range segs { - if s != 0 { - return false - } - } - return true -} - -func comparePart(preSelf string, preOther string) int { - if preSelf == preOther { - return 0 - } - - var selfInt int64 - selfNumeric := true - selfInt, err := strconv.ParseInt(preSelf, 10, 64) - if err != nil { - selfNumeric = false - } - - var otherInt int64 - otherNumeric := true - otherInt, err = strconv.ParseInt(preOther, 10, 64) - if err != nil { - otherNumeric = false - } - - // if a part is empty, we use the other to decide - if preSelf == "" { - if otherNumeric { - return -1 - } - return 1 - } - - if preOther == "" { - if selfNumeric { - return 1 - } - return -1 - } - - if selfNumeric && !otherNumeric { - return -1 - } else if !selfNumeric && otherNumeric { - return 1 - } else if !selfNumeric && !otherNumeric && preSelf > preOther { - return 1 - } else if selfInt > otherInt { - return 1 - } - - return -1 -} - -func comparePrereleases(v string, other string) int { - // the same pre release! - if v == other { - return 0 - } - - // split both pre releases for analyse their parts - selfPreReleaseMeta := strings.Split(v, ".") - otherPreReleaseMeta := strings.Split(other, ".") - - selfPreReleaseLen := len(selfPreReleaseMeta) - otherPreReleaseLen := len(otherPreReleaseMeta) - - biggestLen := otherPreReleaseLen - if selfPreReleaseLen > otherPreReleaseLen { - biggestLen = selfPreReleaseLen - } - - // loop for parts to find the first difference - for i := 0; i < biggestLen; i = i + 1 { - partSelfPre := "" - if i < selfPreReleaseLen { - partSelfPre = selfPreReleaseMeta[i] - } - - partOtherPre := "" - if i < otherPreReleaseLen { - partOtherPre = otherPreReleaseMeta[i] - } - - compare := comparePart(partSelfPre, partOtherPre) - // if parts are equals, continue the loop - if compare != 0 { - return compare - } - } - - return 0 -} - -// Equal tests if two versions are equal. -func (v *Version) Equal(o *Version) bool { - return v.Compare(o) == 0 -} - -// GreaterThan tests if this version is greater than another version. -func (v *Version) GreaterThan(o *Version) bool { - return v.Compare(o) > 0 -} - -// LessThan tests if this version is less than another version. -func (v *Version) LessThan(o *Version) bool { - return v.Compare(o) < 0 -} - -// Metadata returns any metadata that was part of the version -// string. -// -// Metadata is anything that comes after the "+" in the version. -// For example, with "1.2.3+beta", the metadata is "beta". -func (v *Version) Metadata() string { - return v.metadata -} - -// Prerelease returns any prerelease data that is part of the version, -// or blank if there is no prerelease data. -// -// Prerelease information is anything that comes after the "-" in the -// version (but before any metadata). For example, with "1.2.3-beta", -// the prerelease information is "beta". -func (v *Version) Prerelease() string { - return v.pre -} - -// Segments returns the numeric segments of the version as a slice of ints. -// -// This excludes any metadata or pre-release information. For example, -// for a version "1.2.3-beta", segments will return a slice of -// 1, 2, 3. -func (v *Version) Segments() []int { - segmentSlice := make([]int, len(v.segments)) - for i, v := range v.segments { - segmentSlice[i] = int(v) - } - return segmentSlice -} - -// Segments64 returns the numeric segments of the version as a slice of int64s. -// -// This excludes any metadata or pre-release information. For example, -// for a version "1.2.3-beta", segments will return a slice of -// 1, 2, 3. -func (v *Version) Segments64() []int64 { - result := make([]int64, len(v.segments)) - copy(result, v.segments) - return result -} - -// String returns the full version string included pre-release -// and metadata information. -// -// This value is rebuilt according to the parsed segments and other -// information. Therefore, ambiguities in the version string such as -// prefixed zeroes (1.04.0 => 1.4.0), `v` prefix (v1.0.0 => 1.0.0), and -// missing parts (1.0 => 1.0.0) will be made into a canonicalized form -// as shown in the parenthesized examples. -func (v *Version) String() string { - var buf bytes.Buffer - fmtParts := make([]string, len(v.segments)) - for i, s := range v.segments { - // We can ignore err here since we've pre-parsed the values in segments - str := strconv.FormatInt(s, 10) - fmtParts[i] = str - } - fmt.Fprintf(&buf, strings.Join(fmtParts, ".")) - if v.pre != "" { - fmt.Fprintf(&buf, "-%s", v.pre) - } - if v.metadata != "" { - fmt.Fprintf(&buf, "+%s", v.metadata) - } - - return buf.String() -} - -// Original returns the original parsed version as-is, including any -// potential whitespace, `v` prefix, etc. -func (v *Version) Original() string { - return v.original -} diff --git a/vendor/github.com/hashicorp/go-version/version_collection.go b/vendor/github.com/hashicorp/go-version/version_collection.go deleted file mode 100644 index cc888d43e6..0000000000 --- a/vendor/github.com/hashicorp/go-version/version_collection.go +++ /dev/null @@ -1,17 +0,0 @@ -package version - -// Collection is a type that implements the sort.Interface interface -// so that versions can be sorted. -type Collection []*Version - -func (v Collection) Len() int { - return len(v) -} - -func (v Collection) Less(i, j int) bool { - return v[i].LessThan(v[j]) -} - -func (v Collection) Swap(i, j int) { - v[i], v[j] = v[j], v[i] -} diff --git a/vendor/github.com/kataras/survey/LICENSE b/vendor/github.com/kataras/survey/LICENSE deleted file mode 100644 index f8ef08748a..0000000000 --- a/vendor/github.com/kataras/survey/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2016 Alec Aivazis -Copyright (c) 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. diff --git a/vendor/github.com/kataras/survey/confirm.go b/vendor/github.com/kataras/survey/confirm.go deleted file mode 100644 index d1ef6f3a25..0000000000 --- a/vendor/github.com/kataras/survey/confirm.go +++ /dev/null @@ -1,138 +0,0 @@ -package survey - -import ( - "fmt" - "os" - "regexp" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -// Confirm is a regular text input that accept yes/no answers. Response type is a bool. -type Confirm struct { - core.Renderer - Message string - Default bool - Help string -} - -// data available to the templates when processing -type ConfirmTemplateData struct { - Confirm - Answer string - ShowHelp bool -} - -// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format -var ConfirmQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if .Answer}} - {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}} - {{- color "white"}}{{if .Default}}(Y/n) {{else}}(y/N) {{end}}{{color "reset"}} -{{- end}}` - -// the regex for answers -var ( - yesRx = regexp.MustCompile("^(?i:y(?:es)?)$") - noRx = regexp.MustCompile("^(?i:n(?:o)?)$") -) - -func yesNo(t bool) string { - if t { - return "Yes" - } - return "No" -} - -func (c *Confirm) getBool(showHelp bool) (bool, error) { - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - // start waiting for input - for { - line, err := rr.ReadLine(0) - if err != nil { - return false, err - } - // move back up a line to compensate for the \n echoed from terminal - terminal.CursorPreviousLine(1) - val := string(line) - - // get the answer that matches the - var answer bool - switch { - case yesRx.Match([]byte(val)): - answer = true - case noRx.Match([]byte(val)): - answer = false - case val == "": - answer = c.Default - case val == string(core.HelpInputRune) && c.Help != "": - err := c.Render( - ConfirmQuestionTemplate, - ConfirmTemplateData{Confirm: *c, ShowHelp: true}, - ) - if err != nil { - // use the default value and bubble up - return c.Default, err - } - showHelp = true - continue - default: - // we didnt get a valid answer, so print error and prompt again - if err := c.Error(fmt.Errorf("%q is not a valid answer, please try again.", val)); err != nil { - return c.Default, err - } - err := c.Render( - ConfirmQuestionTemplate, - ConfirmTemplateData{Confirm: *c, ShowHelp: showHelp}, - ) - if err != nil { - // use the default value and bubble up - return c.Default, err - } - continue - } - return answer, nil - } - // should not get here - return c.Default, nil -} - -/* -Prompt prompts the user with a simple text field and expects a reply followed -by a carriage return. - - likesPie := false - prompt := &survey.Confirm{ Message: "What is your name?" } - survey.AskOne(prompt, &likesPie, nil) -*/ -func (c *Confirm) Prompt() (interface{}, error) { - // render the question template - err := c.Render( - ConfirmQuestionTemplate, - ConfirmTemplateData{Confirm: *c}, - ) - if err != nil { - return "", err - } - - // get input and return - return c.getBool(false) -} - -// Cleanup overwrite the line with the finalized formatted version -func (c *Confirm) Cleanup(val interface{}) error { - // if the value was previously true - ans := yesNo(val.(bool)) - // render the template - return c.Render( - ConfirmQuestionTemplate, - ConfirmTemplateData{Confirm: *c, Answer: ans}, - ) -} diff --git a/vendor/github.com/kataras/survey/core/renderer.go b/vendor/github.com/kataras/survey/core/renderer.go deleted file mode 100644 index 53b457871b..0000000000 --- a/vendor/github.com/kataras/survey/core/renderer.go +++ /dev/null @@ -1,62 +0,0 @@ -package core - -import ( - "strings" - - "github.com/kataras/survey/terminal" -) - -type Renderer struct { - lineCount int - errorLineCount int -} - -var ErrorTemplate = `{{color "red"}}{{ ErrorIcon }} Sorry, your reply was invalid: {{.Error}}{{color "reset"}} -` - -func (r *Renderer) Error(invalid error) error { - // since errors are printed on top we need to reset the prompt - // as well as any previous error print - r.resetPrompt(r.lineCount + r.errorLineCount) - // we just cleared the prompt lines - r.lineCount = 0 - out, err := RunTemplate(ErrorTemplate, invalid) - if err != nil { - return err - } - // keep track of how many lines are printed so we can clean up later - r.errorLineCount = strings.Count(out, "\n") - - // send the message to the user - terminal.Print(out) - return nil -} - -func (r *Renderer) resetPrompt(lines int) { - // clean out current line in case tmpl didnt end in newline - terminal.CursorHorizontalAbsolute(0) - terminal.EraseLine(terminal.ERASE_LINE_ALL) - // clean up what we left behind last time - for i := 0; i < lines; i++ { - terminal.CursorPreviousLine(1) - terminal.EraseLine(terminal.ERASE_LINE_ALL) - } -} - -func (r *Renderer) Render(tmpl string, data interface{}) error { - r.resetPrompt(r.lineCount) - // render the template summarizing the current state - out, err := RunTemplate(tmpl, data) - if err != nil { - return err - } - - // keep track of how many lines are printed so we can clean up later - r.lineCount = strings.Count(out, "\n") - - // print the summary - terminal.Print(out) - - // nothing went wrong - return nil -} diff --git a/vendor/github.com/kataras/survey/core/template.go b/vendor/github.com/kataras/survey/core/template.go deleted file mode 100644 index 9c31cc3069..0000000000 --- a/vendor/github.com/kataras/survey/core/template.go +++ /dev/null @@ -1,83 +0,0 @@ -package core - -import ( - "bytes" - "text/template" - - "github.com/mgutz/ansi" -) - -var DisableColor = false - -var ( - HelpInputRune = '?' - - ErrorIcon = "✘" - HelpIcon = "ⓘ" - QuestionIcon = "?" - - MarkedOptionIcon = "◉" - UnmarkedOptionIcon = "◯" - - SelectFocusIcon = "❯" -) - -var TemplateFuncs = map[string]interface{}{ - // Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format - "color": func(color string) string { - if DisableColor { - return "" - } - return ansi.ColorCode(color) - }, - "HelpInputRune": func() string { - return string(HelpInputRune) - }, - "ErrorIcon": func() string { - return ErrorIcon - }, - "HelpIcon": func() string { - return HelpIcon - }, - "QuestionIcon": func() string { - return QuestionIcon - }, - "MarkedOptionIcon": func() string { - return MarkedOptionIcon - }, - "UnmarkedOptionIcon": func() string { - return UnmarkedOptionIcon - }, - "SelectFocusIcon": func() string { - return SelectFocusIcon - }, -} - -var memoizedGetTemplate = map[string]*template.Template{} - -func getTemplate(tmpl string) (*template.Template, error) { - if t, ok := memoizedGetTemplate[tmpl]; ok { - return t, nil - } - - t, err := template.New("prompt").Funcs(TemplateFuncs).Parse(tmpl) - if err != nil { - return nil, err - } - - memoizedGetTemplate[tmpl] = t - return t, nil -} - -func RunTemplate(tmpl string, data interface{}) (string, error) { - t, err := getTemplate(tmpl) - if err != nil { - return "", err - } - buf := bytes.NewBufferString("") - err = t.Execute(buf, data) - if err != nil { - return "", err - } - return buf.String(), err -} diff --git a/vendor/github.com/kataras/survey/core/write.go b/vendor/github.com/kataras/survey/core/write.go deleted file mode 100644 index becc7b4870..0000000000 --- a/vendor/github.com/kataras/survey/core/write.go +++ /dev/null @@ -1,244 +0,0 @@ -package core - -import ( - "errors" - "fmt" - "reflect" - "strconv" - "strings" -) - -// the tag used to denote the name of the question -const tagName = "survey" - -// add a few interfaces so users can configure how the prompt values are set -type settable interface { - WriteAnswer(field string, value interface{}) error -} - -func WriteAnswer(t interface{}, name string, v interface{}) (err error) { - // if the field is a custom type - if s, ok := t.(settable); ok { - // use the interface method - return s.WriteAnswer(name, v) - } - - // the target to write to - target := reflect.ValueOf(t) - // the value to write from - value := reflect.ValueOf(v) - - // make sure we are writing to a pointer - if target.Kind() != reflect.Ptr { - return errors.New("you must pass a pointer as the target of a Write operation") - } - // the object "inside" of the target pointer - elem := target.Elem() - - // handle the special types - switch elem.Kind() { - // if we are writing to a struct - case reflect.Struct: - // get the name of the field that matches the string we were given - fieldIndex, err := findFieldIndex(elem, name) - // if something went wrong - if err != nil { - // bubble up - return err - } - field := elem.Field(fieldIndex) - // handle references to the settable interface aswell - if s, ok := field.Interface().(settable); ok { - // use the interface method - return s.WriteAnswer(name, v) - } - if field.CanAddr() { - if s, ok := field.Addr().Interface().(settable); ok { - // use the interface method - return s.WriteAnswer(name, v) - } - } - - // copy the value over to the normal struct - return copy(field, value) - case reflect.Map: - mapType := reflect.TypeOf(t).Elem() - if mapType.Key().Kind() != reflect.String || mapType.Elem().Kind() != reflect.Interface { - return errors.New("answer maps must be of type map[string]interface") - } - mt := *t.(*map[string]interface{}) - mt[name] = value.Interface() - return nil - } - // otherwise just copy the value to the target - return copy(elem, value) -} - -func findFieldIndex(s reflect.Value, name string) (int, error) { - // the type of the value - sType := s.Type() - - // first look for matching tags so we can overwrite matching field names - for i := 0; i < sType.NumField(); i++ { - // the field we are current scanning - field := sType.Field(i) - - // the value of the survey tag - tag := field.Tag.Get(tagName) - // if the tag matches the name we are looking for - if tag != "" && tag == name { - // then we found our index - return i, nil - } - } - - // then look for matching names - for i := 0; i < sType.NumField(); i++ { - // the field we are current scanning - field := sType.Field(i) - - // if the name of the field matches what we're looking for - if strings.ToLower(field.Name) == strings.ToLower(name) { - return i, nil - } - } - - // we didn't find the field - return -1, fmt.Errorf("could not find field matching %v", name) -} - -// isList returns true if the element is something we can Len() -func isList(v reflect.Value) bool { - switch v.Type().Kind() { - case reflect.Array, reflect.Slice: - return true - default: - return false - } -} - -// Write takes a value and copies it to the target -func copy(t reflect.Value, v reflect.Value) (err error) { - // if something ends up panicing we need to catch it in a deferred func - defer func() { - if r := recover(); r != nil { - // if we paniced with an error - if _, ok := r.(error); ok { - // cast the result to an error object - err = r.(error) - } else if _, ok := r.(string); ok { - // otherwise we could have paniced with a string so wrap it in an error - err = errors.New(r.(string)) - } - } - }() - - // if we are copying from a string result to something else - if v.Kind() == reflect.String && v.Type() != t.Type() { - var castVal interface{} - var casterr error - vString := v.Interface().(string) - - switch t.Kind() { - case reflect.Bool: - castVal, casterr = strconv.ParseBool(vString) - case reflect.Int: - castVal, casterr = strconv.Atoi(vString) - case reflect.Int8: - var val64 int64 - val64, casterr = strconv.ParseInt(vString, 10, 8) - if casterr == nil { - castVal = int8(val64) - } - case reflect.Int16: - var val64 int64 - val64, casterr = strconv.ParseInt(vString, 10, 16) - if casterr == nil { - castVal = int16(val64) - } - case reflect.Int32: - var val64 int64 - val64, casterr = strconv.ParseInt(vString, 10, 32) - if casterr == nil { - castVal = int32(val64) - } - case reflect.Int64: - castVal, casterr = strconv.ParseInt(vString, 10, 64) - case reflect.Uint: - var val64 uint64 - val64, casterr = strconv.ParseUint(vString, 10, 8) - if casterr == nil { - castVal = uint(val64) - } - case reflect.Uint8: - var val64 uint64 - val64, casterr = strconv.ParseUint(vString, 10, 8) - if casterr == nil { - castVal = uint8(val64) - } - case reflect.Uint16: - var val64 uint64 - val64, casterr = strconv.ParseUint(vString, 10, 16) - if casterr == nil { - castVal = uint16(val64) - } - case reflect.Uint32: - var val64 uint64 - val64, casterr = strconv.ParseUint(vString, 10, 32) - if casterr == nil { - castVal = uint32(val64) - } - case reflect.Uint64: - castVal, casterr = strconv.ParseUint(vString, 10, 64) - case reflect.Float32: - var val64 float64 - val64, casterr = strconv.ParseFloat(vString, 32) - if casterr == nil { - castVal = float32(val64) - } - case reflect.Float64: - castVal, casterr = strconv.ParseFloat(vString, 64) - default: - return fmt.Errorf("Unable to convert from string to type %s", t.Kind()) - } - - if casterr != nil { - return casterr - } - - t.Set(reflect.ValueOf(castVal)) - return - } - - // if we are copying from one slice or array to another - if isList(v) && isList(t) { - // loop over every item in the desired value - for i := 0; i < v.Len(); i++ { - // write to the target given its kind - switch t.Kind() { - // if its a slice - case reflect.Slice: - // an object of the correct type - obj := reflect.Indirect(reflect.New(t.Type().Elem())) - - // write the appropriate value to the obj and catch any errors - if err := copy(obj, v.Index(i)); err != nil { - return err - } - - // just append the value to the end - t.Set(reflect.Append(t, obj)) - // otherwise it could be an array - case reflect.Array: - // set the index to the appropriate value - copy(t.Slice(i, i+1).Index(0), v.Index(i)) - } - } - } else { - // set the value to the target - t.Set(v) - } - - // we're done - return -} diff --git a/vendor/github.com/kataras/survey/core/write_test.go b/vendor/github.com/kataras/survey/core/write_test.go deleted file mode 100644 index 815c4f540e..0000000000 --- a/vendor/github.com/kataras/survey/core/write_test.go +++ /dev/null @@ -1,543 +0,0 @@ -package core - -import ( - "fmt" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestWrite_returnsErrorIfTargetNotPtr(t *testing.T) { - // try to copy a value to a non-pointer - err := WriteAnswer(true, "hello", true) - // make sure there was an error - if err == nil { - t.Error("Did not encounter error when writing to non-pointer.") - } -} - -func TestWrite_canWriteToBool(t *testing.T) { - // a pointer to hold the boolean value - ptr := true - - // try to copy a false value to the pointer - WriteAnswer(&ptr, "", false) - - // if the value is true - if ptr { - // the test failed - t.Error("Could not write a false bool to a pointer") - } -} - -func TestWrite_canWriteString(t *testing.T) { - // a pointer to hold the boolean value - ptr := "" - - // try to copy a false value to the pointer - err := WriteAnswer(&ptr, "", "hello") - if err != nil { - t.Error(err) - } - - // if the value is not what we wrote - if ptr != "hello" { - t.Error("Could not write a string value to a pointer") - } -} - -func TestWrite_canWriteSlice(t *testing.T) { - // a pointer to hold the value - ptr := []string{} - - // copy in a value - WriteAnswer(&ptr, "", []string{"hello", "world"}) - - // make sure there are two entries - assert.Equal(t, []string{"hello", "world"}, ptr) -} - -func TestWrite_recoversInvalidReflection(t *testing.T) { - // a variable to mutate - ptr := false - - // write a boolean value to the string - err := WriteAnswer(&ptr, "", "hello") - - // if there was no error - if err == nil { - // the test failed - t.Error("Did not encounter error when forced invalid write.") - } -} - -func TestWriteAnswer_handlesNonStructValues(t *testing.T) { - // the value to write to - ptr := "" - - // write a value to the pointer - WriteAnswer(&ptr, "", "world") - - // if we didn't change the value appropriate - if ptr != "world" { - // the test failed - t.Error("Did not write value to primitive pointer") - } -} - -func TestWriteAnswer_canMutateStruct(t *testing.T) { - // the struct to hold the answer - ptr := struct{ Name string }{} - - // write a value to an existing field - err := WriteAnswer(&ptr, "name", "world") - if err != nil { - // the test failed - t.Errorf("Encountered error while writing answer: %v", err.Error()) - // we're done here - return - } - - // make sure we changed the field - if ptr.Name != "world" { - // the test failed - t.Error("Did not mutate struct field when writing answer.") - } -} - -func TestWriteAnswer_canMutateMap(t *testing.T) { - // the map to hold the answer - ptr := make(map[string]interface{}) - - // write a value to an existing field - err := WriteAnswer(&ptr, "name", "world") - if err != nil { - // the test failed - t.Errorf("Encountered error while writing answer: %v", err.Error()) - // we're done here - return - } - - // make sure we changed the field - if ptr["name"] != "world" { - // the test failed - t.Error("Did not mutate map when writing answer.") - } -} - -func TestWrite_returnsErrorIfInvalidMapType(t *testing.T) { - // try to copy a value to a non map[string]interface{} - ptr := make(map[int]string) - - err := WriteAnswer(&ptr, "name", "world") - // make sure there was an error - if err == nil { - t.Error("Did not encounter error when writing to invalid map.") - } -} - -func TestWrite_writesStringSliceToIntSlice(t *testing.T) { - // make a slice of int to write to - target := []int{} - - // write the answer - err := WriteAnswer(&target, "name", []string{"1", "2", "3"}) - - // make sure there was no error - assert.Nil(t, err, "WriteSlice to Int Slice") - // and we got what we wanted - assert.Equal(t, []int{1, 2, 3}, target) -} - -func TestWrite_writesStringArrayToIntArray(t *testing.T) { - // make an array of int to write to - target := [3]int{} - - // write the answer - err := WriteAnswer(&target, "name", [3]string{"1", "2", "3"}) - - // make sure there was no error - assert.Nil(t, err, "WriteArray to Int Array") - // and we got what we wanted - assert.Equal(t, [3]int{1, 2, 3}, target) -} - -func TestWriteAnswer_returnsErrWhenFieldNotFound(t *testing.T) { - // the struct to hold the answer - ptr := struct{ Name string }{} - - // write a value to an existing field - err := WriteAnswer(&ptr, "", "world") - - if err == nil { - // the test failed - t.Error("Did not encountered error while writing answer to non-existing field.") - } -} - -func TestFindFieldIndex_canFindExportedField(t *testing.T) { - // create a reflective wrapper over the struct to look through - val := reflect.ValueOf(struct{ Name string }{}) - - // find the field matching "name" - fieldIndex, err := findFieldIndex(val, "name") - // if something went wrong - if err != nil { - // the test failed - t.Error(err.Error()) - return - } - - // make sure we got the right value - if val.Type().Field(fieldIndex).Name != "Name" { - // the test failed - t.Errorf("Did not find the correct field name. Expected 'Name' found %v.", val.Type().Field(fieldIndex).Name) - } -} - -func TestFindFieldIndex_canFindTaggedField(t *testing.T) { - // the struct to look through - val := reflect.ValueOf(struct { - Username string `survey:"name"` - }{}) - - // find the field matching "name" - fieldIndex, err := findFieldIndex(val, "name") - // if something went wrong - if err != nil { - // the test failed - t.Error(err.Error()) - return - } - - // make sure we got the right value - if val.Type().Field(fieldIndex).Name != "Username" { - // the test failed - t.Errorf("Did not find the correct field name. Expected 'Username' found %v.", val.Type().Field(fieldIndex).Name) - } -} - -func TestFindFieldIndex_canHandleCapitalAnswerNames(t *testing.T) { - // create a reflective wrapper over the struct to look through - val := reflect.ValueOf(struct{ Name string }{}) - - // find the field matching "name" - fieldIndex, err := findFieldIndex(val, "Name") - // if something went wrong - if err != nil { - // the test failed - t.Error(err.Error()) - return - } - - // make sure we got the right value - if val.Type().Field(fieldIndex).Name != "Name" { - // the test failed - t.Errorf("Did not find the correct field name. Expected 'Name' found %v.", val.Type().Field(fieldIndex).Name) - } -} - -func TestFindFieldIndex_tagOverwriteFieldName(t *testing.T) { - // the struct to look through - val := reflect.ValueOf(struct { - Name string - Username string `survey:"name"` - }{}) - - // find the field matching "name" - fieldIndex, err := findFieldIndex(val, "name") - // if something went wrong - if err != nil { - // the test failed - t.Error(err.Error()) - return - } - - // make sure we got the right value - if val.Type().Field(fieldIndex).Name != "Username" { - // the test failed - t.Errorf("Did not find the correct field name. Expected 'Username' found %v.", val.Type().Field(fieldIndex).Name) - } -} - -type testFieldSettable struct { - Values map[string]string -} - -type testStringSettable struct { - Value string `survey:"string"` -} - -type testTaggedStruct struct { - TaggedValue testStringSettable `survey:"tagged"` -} - -type testPtrTaggedStruct struct { - TaggedValue *testStringSettable `survey:"tagged"` -} - -func (t *testFieldSettable) WriteAnswer(name string, value interface{}) error { - if t.Values == nil { - t.Values = map[string]string{} - } - if v, ok := value.(string); ok { - t.Values[name] = v - return nil - } - return fmt.Errorf("Incompatible type %T", value) -} - -func (t *testStringSettable) WriteAnswer(_ string, value interface{}) error { - t.Value = value.(string) - return nil -} - -func TestWriteWithFieldSettable(t *testing.T) { - testSet1 := testFieldSettable{} - err := WriteAnswer(&testSet1, "values", "stringVal") - assert.Nil(t, err) - assert.Equal(t, map[string]string{"values": "stringVal"}, testSet1.Values) - - testSet2 := testFieldSettable{} - err = WriteAnswer(&testSet2, "values", 123) - assert.Error(t, fmt.Errorf("Incompatible type int64"), err) - assert.Equal(t, map[string]string{}, testSet2.Values) - - testString1 := testStringSettable{} - err = WriteAnswer(&testString1, "", "value1") - assert.Nil(t, err) - assert.Equal(t, testStringSettable{"value1"}, testString1) - - testSetStruct := testTaggedStruct{} - err = WriteAnswer(&testSetStruct, "tagged", "stringVal1") - assert.Nil(t, err) - assert.Equal(t, testTaggedStruct{TaggedValue: testStringSettable{"stringVal1"}}, testSetStruct) - - testPtrSetStruct := testPtrTaggedStruct{&testStringSettable{}} - err = WriteAnswer(&testPtrSetStruct, "tagged", "stringVal1") - assert.Nil(t, err) - assert.Equal(t, testPtrTaggedStruct{TaggedValue: &testStringSettable{"stringVal1"}}, testPtrSetStruct) -} - -// CONVERSION TESTS -func TestWrite_canStringToBool(t *testing.T) { - // a pointer to hold the boolean value - ptr := true - - // try to copy a false value to the pointer - WriteAnswer(&ptr, "", "false") - - // if the value is true - if ptr { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToInt(t *testing.T) { - // a pointer to hold the value - var ptr int = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToInt8(t *testing.T) { - // a pointer to hold the value - var ptr int8 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToInt16(t *testing.T) { - // a pointer to hold the value - var ptr int16 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToInt32(t *testing.T) { - // a pointer to hold the value - var ptr int32 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToInt64(t *testing.T) { - // a pointer to hold the value - var ptr int64 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToUint(t *testing.T) { - // a pointer to hold the value - var ptr uint = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToUint8(t *testing.T) { - // a pointer to hold the value - var ptr uint8 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToUint16(t *testing.T) { - // a pointer to hold the value - var ptr uint16 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToUint32(t *testing.T) { - // a pointer to hold the value - var ptr uint32 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToUint64(t *testing.T) { - // a pointer to hold the value - var ptr uint64 = 1 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2") - - // if the value is true - if ptr != 2 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToFloat32(t *testing.T) { - // a pointer to hold the value - var ptr float32 = 1.0 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2.5") - - // if the value is true - if ptr != 2.5 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canStringToFloat64(t *testing.T) { - // a pointer to hold the value - var ptr float64 = 1.0 - - // try to copy a value to the pointer - WriteAnswer(&ptr, "", "2.5") - - // if the value is true - if ptr != 2.5 { - // the test failed - t.Error("Could not convert string to pointer type") - } -} - -func TestWrite_canConvertStructFieldTypes(t *testing.T) { - // the struct to hold the answer - ptr := struct { - Name string - Age uint - Male bool - Height float64 - }{} - - // write the values as strings - check(t, WriteAnswer(&ptr, "name", "Bob")) - check(t, WriteAnswer(&ptr, "age", "22")) - check(t, WriteAnswer(&ptr, "male", "true")) - check(t, WriteAnswer(&ptr, "height", "6.2")) - - // make sure we changed the fields - if ptr.Name != "Bob" { - t.Error("Did not mutate Name when writing answer.") - } - - if ptr.Age != 22 { - t.Error("Did not mutate Age when writing answer.") - } - - if !ptr.Male { - t.Error("Did not mutate Male when writing answer.") - } - - if ptr.Height != 6.2 { - t.Error("Did not mutate Height when writing answer.") - } -} - -func check(t *testing.T, err error) { - if err != nil { - t.Fatalf("Encountered error while writing answer: %v", err.Error()) - } -} diff --git a/vendor/github.com/kataras/survey/editor.go b/vendor/github.com/kataras/survey/editor.go deleted file mode 100644 index 2241dd3435..0000000000 --- a/vendor/github.com/kataras/survey/editor.go +++ /dev/null @@ -1,168 +0,0 @@ -package survey - -import ( - "bytes" - "io/ioutil" - "os" - "os/exec" - "runtime" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -/* -Editor launches an instance of the users preferred editor on a temporary file. -The editor to use is determined by reading the $VISUAL or $EDITOR environment -variables. If neither of those are present, notepad (on Windows) or vim -(others) is used. -The launch of the editor is triggered by the enter key. Since the response may -be long, it will not be echoed as Input does, instead, it print . -Response type is a string. - - message := "" - prompt := &survey.Editor{ Message: "What is your commit message?" } - survey.AskOne(prompt, &message, nil) -*/ -type Editor struct { - core.Renderer - Message string - Default string - Help string -} - -// data available to the templates when processing -type EditorTemplateData struct { - Editor - Answer string - ShowAnswer bool - ShowHelp bool -} - -// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format -var EditorQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if .ShowAnswer}} - {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}} - {{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}} - {{- color "cyan"}}[Enter to launch editor] {{color "reset"}} -{{- end}}` - -var ( - bom = []byte{0xef, 0xbb, 0xbf} - editor = "vim" -) - -func init() { - if runtime.GOOS == "windows" { - editor = "notepad" - } - if v := os.Getenv("VISUAL"); v != "" { - editor = v - } else if e := os.Getenv("EDITOR"); e != "" { - editor = e - } -} - -func (e *Editor) Prompt() (interface{}, error) { - // render the template - err := e.Render( - EditorQuestionTemplate, - EditorTemplateData{Editor: *e}, - ) - if err != nil { - return "", err - } - - // start reading runes from the standard in - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - - terminal.CursorHide() - defer terminal.CursorShow() - - for { - r, _, err := rr.ReadRune() - if err != nil { - return "", err - } - if r == '\r' || r == '\n' { - break - } - if r == terminal.KeyInterrupt { - return "", terminal.InterruptErr - } - if r == terminal.KeyEndTransmission { - break - } - if r == core.HelpInputRune && e.Help != "" { - err = e.Render( - EditorQuestionTemplate, - EditorTemplateData{Editor: *e, ShowHelp: true}, - ) - if err != nil { - return "", err - } - } - continue - } - - // prepare the temp file - f, err := ioutil.TempFile("", "survey") - if err != nil { - return "", err - } - defer os.Remove(f.Name()) - - // write utf8 BOM header - // The reason why we do this is because notepad.exe on Windows determines the - // encoding of an "empty" text file by the locale, for example, GBK in China, - // while golang string only handles utf8 well. However, a text file with utf8 - // BOM header is not considered "empty" on Windows, and the encoding will then - // be determined utf8 by notepad.exe, instead of GBK or other encodings. - if _, err := f.Write(bom); err != nil { - return "", err - } - // close the fd to prevent the editor unable to save file - if err := f.Close(); err != nil { - return "", err - } - - // open the editor - cmd := exec.Command(editor, f.Name()) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - terminal.CursorShow() - if err := cmd.Run(); err != nil { - return "", err - } - - // raw is a BOM-unstripped UTF8 byte slice - raw, err := ioutil.ReadFile(f.Name()) - if err != nil { - return "", err - } - - // strip BOM header - text := string(bytes.TrimPrefix(raw, bom)) - - // check length, return default value on empty - if len(text) == 0 { - return e.Default, nil - } - - return text, nil -} - -func (e *Editor) Cleanup(val interface{}) error { - return e.Render( - EditorQuestionTemplate, - EditorTemplateData{Editor: *e, Answer: "", ShowAnswer: true}, - ) -} diff --git a/vendor/github.com/kataras/survey/input.go b/vendor/github.com/kataras/survey/input.go deleted file mode 100644 index a774751326..0000000000 --- a/vendor/github.com/kataras/survey/input.go +++ /dev/null @@ -1,98 +0,0 @@ -package survey - -import ( - "os" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -/* -Input is a regular text input that prints each character the user types on the screen -and accepts the input with the enter key. Response type is a string. - - name := "" - prompt := &survey.Input{ Message: "What is your name?" } - survey.AskOne(prompt, &name, nil) -*/ -type Input struct { - core.Renderer - Message string - Default string - Help string -} - -// data available to the templates when processing -type InputTemplateData struct { - Input - Answer string - ShowAnswer bool - ShowHelp bool -} - -// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format -var InputQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if .ShowAnswer}} - {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}} - {{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}} -{{- end}}` - -func (i *Input) Prompt() (interface{}, error) { - // render the template - err := i.Render( - InputQuestionTemplate, - InputTemplateData{Input: *i}, - ) - if err != nil { - return "", err - } - - // start reading runes from the standard in - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - - line := []rune{} - // get the next line - for { - line, err = rr.ReadLine(0) - if err != nil { - return string(line), err - } - // terminal will echo the \n so we need to jump back up one row - terminal.CursorPreviousLine(1) - - if string(line) == string(core.HelpInputRune) && i.Help != "" { - err = i.Render( - InputQuestionTemplate, - InputTemplateData{Input: *i, ShowHelp: true}, - ) - if err != nil { - return "", err - } - continue - } - break - } - - // if the line is empty - if line == nil || len(line) == 0 { - // use the default value - return i.Default, err - } - - // we're done - return string(line), err -} - -func (i *Input) Cleanup(val interface{}) error { - return i.Render( - InputQuestionTemplate, - InputTemplateData{Input: *i, Answer: val.(string), ShowAnswer: true}, - ) -} diff --git a/vendor/github.com/kataras/survey/multiselect.go b/vendor/github.com/kataras/survey/multiselect.go deleted file mode 100644 index 1d72b49ce5..0000000000 --- a/vendor/github.com/kataras/survey/multiselect.go +++ /dev/null @@ -1,203 +0,0 @@ -package survey - -import ( - "errors" - "os" - "strings" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -/* -MultiSelect is a prompt that presents a list of various options to the user -for them to select using the arrow keys and enter. Response type is a slice of strings. - - days := []string{} - prompt := &survey.MultiSelect{ - Message: "What days do you prefer:", - Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}, - } - survey.AskOne(prompt, &days, nil) -*/ -type MultiSelect struct { - core.Renderer - Message string - Options []string - Default []string - Help string - PageSize int - selectedIndex int - checked map[string]bool - showingHelp bool -} - -// data available to the templates when processing -type MultiSelectTemplateData struct { - MultiSelect - Answer string - ShowAnswer bool - Checked map[string]bool - SelectedIndex int - ShowHelp bool - PageEntries []string -} - -var MultiSelectQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }}{{color "reset"}} -{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- if and .Help (not .ShowHelp)}} {{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}}{{end}} - {{- "\n"}} - {{- range $ix, $option := .PageEntries}} - {{- if eq $ix $.SelectedIndex}}{{color "cyan"}}{{ SelectFocusIcon }}{{color "reset"}}{{else}} {{end}} - {{- if index $.Checked $option}}{{color "green"}} {{ MarkedOptionIcon }} {{else}}{{color "default+hb"}} {{ UnmarkedOptionIcon }} {{end}} - {{- color "reset"}} - {{- " "}}{{$option}}{{"\n"}} - {{- end}} -{{- end}}` - -// OnChange is called on every keypress. -func (m *MultiSelect) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { - if key == terminal.KeyArrowUp { - // if we are at the top of the list - if m.selectedIndex == 0 { - // go to the bottom - m.selectedIndex = len(m.Options) - 1 - } else { - // decrement the selected index - m.selectedIndex-- - } - } else if key == terminal.KeyArrowDown { - // if we are at the bottom of the list - if m.selectedIndex == len(m.Options)-1 { - // start at the top - m.selectedIndex = 0 - } else { - // increment the selected index - m.selectedIndex++ - } - // if the user pressed down and there is room to move - } else if key == terminal.KeySpace { - if old, ok := m.checked[m.Options[m.selectedIndex]]; !ok { - // otherwise just invert the current value - m.checked[m.Options[m.selectedIndex]] = true - } else { - // otherwise just invert the current value - m.checked[m.Options[m.selectedIndex]] = !old - } - // only show the help message if we have one to show - } else if key == core.HelpInputRune && m.Help != "" { - m.showingHelp = true - } - - // paginate the options - opts, idx := paginate(m.PageSize, m.Options, m.selectedIndex) - - // render the options - m.Render( - MultiSelectQuestionTemplate, - MultiSelectTemplateData{ - MultiSelect: *m, - SelectedIndex: idx, - Checked: m.checked, - ShowHelp: m.showingHelp, - PageEntries: opts, - }, - ) - - // if we are not pressing ent - return line, 0, true -} - -func (m *MultiSelect) Prompt() (interface{}, error) { - // compute the default state - m.checked = make(map[string]bool) - // if there is a default - if len(m.Default) > 0 { - for _, dflt := range m.Default { - for _, opt := range m.Options { - // if the option correponds to the default - if opt == dflt { - // we found our initial value - m.checked[opt] = true - // stop looking - break - } - } - } - } - - // if there are no options to render - if len(m.Options) == 0 { - // we failed - return "", errors.New("please provide options to select from") - } - - // hide the cursor - terminal.CursorHide() - // show the cursor when we're done - defer terminal.CursorShow() - - // paginate the options - opts, idx := paginate(m.PageSize, m.Options, m.selectedIndex) - - // ask the question - err := m.Render( - MultiSelectQuestionTemplate, - MultiSelectTemplateData{ - MultiSelect: *m, - SelectedIndex: idx, - Checked: m.checked, - PageEntries: opts, - }, - ) - if err != nil { - return "", err - } - - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - - // start waiting for input - for { - r, _, _ := rr.ReadRune() - if r == '\r' || r == '\n' { - break - } - if r == terminal.KeyInterrupt { - return "", terminal.InterruptErr - } - if r == terminal.KeyEndTransmission { - break - } - m.OnChange(nil, 0, r) - } - - answers := []string{} - for _, option := range m.Options { - if val, ok := m.checked[option]; ok && val { - answers = append(answers, option) - } - } - - return answers, nil -} - -// Cleanup removes the options section, and renders the ask like a normal question. -func (m *MultiSelect) Cleanup(val interface{}) error { - // execute the output summary template with the answer - return m.Render( - MultiSelectQuestionTemplate, - MultiSelectTemplateData{ - MultiSelect: *m, - SelectedIndex: m.selectedIndex, - Checked: m.checked, - Answer: strings.Join(val.([]string), ", "), - ShowAnswer: true, - }, - ) -} diff --git a/vendor/github.com/kataras/survey/password.go b/vendor/github.com/kataras/survey/password.go deleted file mode 100644 index 9065fec588..0000000000 --- a/vendor/github.com/kataras/survey/password.go +++ /dev/null @@ -1,84 +0,0 @@ -package survey - -import ( - "os" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -/* -Password is like a normal Input but the text shows up as *'s and there is no default. Response -type is a string. - - password := "" - prompt := &survey.Password{ Message: "Please type your password" } - survey.AskOne(prompt, &password, nil) -*/ -type Password struct { - core.Renderer - Message string - Help string -} - -type PasswordTemplateData struct { - Password - ShowHelp bool -} - -// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format -var PasswordQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}` - -func (p *Password) Prompt() (line interface{}, err error) { - // render the question template - out, err := core.RunTemplate( - PasswordQuestionTemplate, - PasswordTemplateData{Password: *p}, - ) - terminal.Print(out) - if err != nil { - return "", err - } - - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - - // no help msg? Just return any response - if p.Help == "" { - line, err := rr.ReadLine('*') - return string(line), err - } - - // process answers looking for help prompt answer - for { - line, err := rr.ReadLine('*') - if err != nil { - return string(line), err - } - - if string(line) == string(core.HelpInputRune) { - // terminal will echo the \n so we need to jump back up one row - terminal.CursorPreviousLine(1) - - err = p.Render( - PasswordQuestionTemplate, - PasswordTemplateData{Password: *p, ShowHelp: true}, - ) - if err != nil { - return "", err - } - continue - } - return string(line), err - } -} - -// Cleanup hides the string with a fixed number of characters. -func (prompt *Password) Cleanup(val interface{}) error { - return nil -} diff --git a/vendor/github.com/kataras/survey/select.go b/vendor/github.com/kataras/survey/select.go deleted file mode 100644 index 12820428a4..0000000000 --- a/vendor/github.com/kataras/survey/select.go +++ /dev/null @@ -1,209 +0,0 @@ -package survey - -import ( - "errors" - "os" - - "github.com/kataras/survey/core" - "github.com/kataras/survey/terminal" -) - -/* -Select is a prompt that presents a list of various options to the user -for them to select using the arrow keys and enter. Response type is a string. - - color := "" - prompt := &survey.Select{ - Message: "Choose a color:", - Options: []string{"red", "blue", "green"}, - } - survey.AskOne(prompt, &color, nil) -*/ -type Select struct { - core.Renderer - Message string - Options []string - Default string - Help string - PageSize int - selectedIndex int - useDefault bool - showingHelp bool -} - -// the data available to the templates when processing -type SelectTemplateData struct { - Select - PageEntries []string - SelectedIndex int - Answer string - ShowAnswer bool - ShowHelp bool -} - -var SelectQuestionTemplate = ` -{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }}{{color "reset"}} -{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}} -{{- else}} - {{- if and .Help (not .ShowHelp)}} {{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}}{{end}} - {{- "\n"}} - {{- range $ix, $choice := .PageEntries}} - {{- if eq $ix $.SelectedIndex}}{{color "cyan+b"}}{{ SelectFocusIcon }} {{else}}{{color "default+hb"}} {{end}} - {{- $choice}} - {{- color "reset"}}{{"\n"}} - {{- end}} -{{- end}}` - -// OnChange is called on every keypress. -func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) { - // if the user pressed the enter key - if key == terminal.KeyEnter { - return []rune(s.Options[s.selectedIndex]), 0, true - // if the user pressed the up arrow - } else if key == terminal.KeyArrowUp { - s.useDefault = false - - // if we are at the top of the list - if s.selectedIndex == 0 { - // start from the button - s.selectedIndex = len(s.Options) - 1 - } else { - // otherwise we are not at the top of the list so decrement the selected index - s.selectedIndex-- - } - // if the user pressed down and there is room to move - } else if key == terminal.KeyArrowDown { - s.useDefault = false - // if we are at the bottom of the list - if s.selectedIndex == len(s.Options)-1 { - // start from the top - s.selectedIndex = 0 - } else { - // increment the selected index - s.selectedIndex++ - } - // only show the help message if we have one - } else if key == core.HelpInputRune && s.Help != "" { - s.showingHelp = true - } - - // figure out the options and index to render - opts, idx := paginate(s.PageSize, s.Options, s.selectedIndex) - - // render the options - s.Render( - SelectQuestionTemplate, - SelectTemplateData{ - Select: *s, - SelectedIndex: idx, - ShowHelp: s.showingHelp, - PageEntries: opts, - }, - ) - - // if we are not pressing ent - return []rune(s.Options[s.selectedIndex]), 0, true -} - -func (s *Select) Prompt() (interface{}, error) { - // if there are no options to render - if len(s.Options) == 0 { - // we failed - return "", errors.New("please provide options to select from") - } - - // start off with the first option selected - sel := 0 - // if there is a default - if s.Default != "" { - // find the choice - for i, opt := range s.Options { - // if the option correponds to the default - if opt == s.Default { - // we found our initial value - sel = i - // stop looking - break - } - } - } - // save the selected index - s.selectedIndex = sel - - // figure out the options and index to render - opts, idx := paginate(s.PageSize, s.Options, sel) - - // ask the question - err := s.Render( - SelectQuestionTemplate, - SelectTemplateData{ - Select: *s, - PageEntries: opts, - SelectedIndex: idx, - }, - ) - if err != nil { - return "", err - } - - // hide the cursor - terminal.CursorHide() - // show the cursor when we're done - defer terminal.CursorShow() - - // by default, use the default value - s.useDefault = true - - rr := terminal.NewRuneReader(os.Stdin) - rr.SetTermMode() - defer rr.RestoreTermMode() - // start waiting for input - for { - r, _, err := rr.ReadRune() - if err != nil { - return "", err - } - if r == '\r' || r == '\n' { - break - } - if r == terminal.KeyInterrupt { - return "", terminal.InterruptErr - } - if r == terminal.KeyEndTransmission { - break - } - s.OnChange(nil, 0, r) - } - - var val string - // if we are supposed to use the default value - if s.useDefault { - // if there is a default value - if s.Default != "" { - // use the default value - val = s.Default - } else { - // there is no default value so use the first - val = s.Options[0] - } - // otherwise the selected index points to the value - } else { - // the - val = s.Options[s.selectedIndex] - } - - return val, err -} - -func (s *Select) Cleanup(val interface{}) error { - return s.Render( - SelectQuestionTemplate, - SelectTemplateData{ - Select: *s, - Answer: val.(string), - ShowAnswer: true, - }, - ) -} diff --git a/vendor/github.com/kataras/survey/survey.go b/vendor/github.com/kataras/survey/survey.go deleted file mode 100644 index 2e5fd40e61..0000000000 --- a/vendor/github.com/kataras/survey/survey.go +++ /dev/null @@ -1,198 +0,0 @@ -package survey - -import ( - "errors" - - "github.com/kataras/survey/core" -) - -// PageSize is the default maximum number of items to show in select/multiselect prompts -var PageSize = 7 - -// Validator is a function passed to a Question after a user has provided a response. -// If the function returns an error, then the user will be prompted again for another -// response. -type Validator func(ans interface{}) error - -// Transformer is a function passed to a Question after a user has provided a response. -// The function can be used to implement a custom logic that will result to return -// a different representation of the given answer. -// -// Look `TransformString`, `ToLower` `Title` and `ComposeTransformers` for more. -type Transformer func(ans interface{}) (newAns interface{}) - -// Question is the core data structure for a survey questionnaire. -type Question struct { - Name string - Prompt Prompt - Validate Validator - Transform Transformer -} - -// Prompt is the primary interface for the objects that can take user input -// and return a response. -type Prompt interface { - Prompt() (interface{}, error) - Cleanup(interface{}) error - Error(error) error -} - -/* -AskOne performs the prompt for a single prompt and asks for validation if required. -Response types should be something that can be casted from the response type designated -in the documentation. For example: - - name := "" - prompt := &survey.Input{ - Message: "name", - } - - survey.AskOne(prompt, &name, nil) - -*/ -func AskOne(p Prompt, response interface{}, v Validator) error { - err := Ask([]*Question{{Prompt: p, Validate: v}}, response) - if err != nil { - return err - } - - return nil -} - -/* -Ask performs the prompt loop, asking for validation when appropriate. The response -type can be one of two options. If a struct is passed, the answer will be written to -the field whose name matches the Name field on the corresponding question. Field types -should be something that can be casted from the response type designated in the -documentation. Note, a survey tag can also be used to identify a Otherwise, a -map[string]interface{} can be passed, responses will be written to the key with the -matching name. For example: - - qs := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "What is your name?"}, - Validate: survey.Required, - Transform: survey.Title, - }, - } - - answers := struct{ Name string }{} - - - err := survey.Ask(qs, &answers) -*/ -func Ask(qs []*Question, response interface{}) error { - - // if we weren't passed a place to record the answers - if response == nil { - // we can't go any further - return errors.New("cannot call Ask() with a nil reference to record the answers") - } - - // go over every question - for _, q := range qs { - // grab the user input and save it - ans, err := q.Prompt.Prompt() - // if there was a problem - if err != nil { - return err - } - - // if there is a validate handler for this question - if q.Validate != nil { - // wait for a valid response - for invalid := q.Validate(ans); invalid != nil; invalid = q.Validate(ans) { - err := q.Prompt.Error(invalid) - // if there was a problem - if err != nil { - return err - } - - // ask for more input - ans, err = q.Prompt.Prompt() - // if there was a problem - if err != nil { - return err - } - } - } - - if q.Transform != nil { - // check if we have a transformer available, if so - // then try to acquire the new representation of the - // answer, if the resulting answer is not nil. - if newAns := q.Transform(ans); newAns != nil { - ans = newAns - } - } - - // tell the prompt to cleanup with the validated value - q.Prompt.Cleanup(ans) - - // if something went wrong - if err != nil { - // stop listening - return err - } - - // add it to the map - err = core.WriteAnswer(response, q.Name, ans) - // if something went wrong - if err != nil { - return err - } - - } - // return the response - return nil -} - -// paginate returns a single page of choices given the page size, the total list of -// possible choices, and the current selected index in the total list. -func paginate(page int, choices []string, sel int) ([]string, int) { - // the number of elements to show in a single page - var pageSize int - // if the select has a specific page size - if page != 0 { - // use the specified one - pageSize = page - // otherwise the select does not have a page size - } else { - // use the package default - pageSize = PageSize - } - - var start, end, cursor int - - if len(choices) < pageSize { - // if we dont have enough options to fill a page - start = 0 - end = len(choices) - cursor = sel - - } else if sel < pageSize/2 { - // if we are in the first half page - start = 0 - end = pageSize - cursor = sel - - } else if len(choices)-sel-1 < pageSize/2 { - // if we are in the last half page - start = len(choices) - pageSize - end = len(choices) - cursor = sel - start - - } else { - // somewhere in the middle - above := pageSize / 2 - below := pageSize - above - - cursor = pageSize / 2 - start = sel - above - end = sel + below - } - - // return the subset we care about and the index - return choices[start:end], cursor -} diff --git a/vendor/github.com/kataras/survey/terminal/cursor.go b/vendor/github.com/kataras/survey/terminal/cursor.go deleted file mode 100644 index 533b75a644..0000000000 --- a/vendor/github.com/kataras/survey/terminal/cursor.go +++ /dev/null @@ -1,134 +0,0 @@ -// +build !windows - -package terminal - -import ( - "bufio" - "fmt" - "os" - "regexp" - "strconv" - "strings" -) - -// CursorUp moves the cursor n cells to up. -func CursorUp(n int) { - fmt.Printf("\x1b[%dA", n) -} - -// CursorDown moves the cursor n cells to down. -func CursorDown(n int) { - fmt.Printf("\x1b[%dB", n) -} - -// CursorForward moves the cursor n cells to right. -func CursorForward(n int) { - fmt.Printf("\x1b[%dC", n) -} - -// CursorBack moves the cursor n cells to left. -func CursorBack(n int) { - fmt.Printf("\x1b[%dD", n) -} - -// CursorNextLine moves cursor to beginning of the line n lines down. -func CursorNextLine(n int) { - fmt.Printf("\x1b[%dE", n) -} - -// CursorPreviousLine moves cursor to beginning of the line n lines up. -func CursorPreviousLine(n int) { - fmt.Printf("\x1b[%dF", n) -} - -// CursorHorizontalAbsolute moves cursor horizontally to x. -func CursorHorizontalAbsolute(x int) { - fmt.Printf("\x1b[%dG", x) -} - -// CursorShow shows the cursor. -func CursorShow() { - fmt.Print("\x1b[?25h") -} - -// CursorHide hide the cursor. -func CursorHide() { - fmt.Print("\x1b[?25l") -} - -// CursorMove moves the cursor to a specific x,y location. -func CursorMove(x int, y int) { - fmt.Printf("\x1b[%d;%df", x, y) -} - -// CursorLocation returns the current location of the cursor in the terminal -func CursorLocation() (*Coord, error) { - // print the escape sequence to receive the position in our stdin - fmt.Print("\x1b[6n") - - // read from stdin to get the response - reader := bufio.NewReader(os.Stdin) - // spec says we read 'til R, so do that - text, err := reader.ReadSlice('R') - if err != nil { - return nil, err - } - - // spec also says they're split by ;, so do that too - if strings.Contains(string(text), ";") { - // a regex to parse the output of the ansi code - re := regexp.MustCompile(`\d+;\d+`) - line := re.FindString(string(text)) - - // find the column and rows embedded in the string - coords := strings.Split(line, ";") - - // try to cast the col number to an int - col, err := strconv.Atoi(coords[1]) - if err != nil { - return nil, err - } - - // try to cast the row number to an int - row, err := strconv.Atoi(coords[0]) - if err != nil { - return nil, err - } - - // return the coordinate object with the col and row we calculated - return &Coord{Short(col), Short(row)}, nil - } - - // it didn't work so return an error - return nil, fmt.Errorf("could not compute the cursor position using ascii escape sequences") -} - -// Size returns the height and width of the terminal. -func Size() (*Coord, error) { - // the general approach here is to move the cursor to the very bottom - // of the terminal, ask for the current location and then move the - // cursor back where we started - - // save the current location of the cursor - origin, err := CursorLocation() - if err != nil { - return nil, err - } - - // move the cursor to the very bottom of the terminal - CursorMove(999, 999) - - // ask for the current location - bottom, err := CursorLocation() - if err != nil { - return nil, err - } - - // move back where we began - CursorUp(int(bottom.Y - origin.Y)) - CursorHorizontalAbsolute(int(origin.X)) - - // sice the bottom was calcuated in the lower right corner, it - // is the dimensions we are looking for - return bottom, nil -} diff --git a/vendor/github.com/kataras/survey/terminal/cursor_windows.go b/vendor/github.com/kataras/survey/terminal/cursor_windows.go deleted file mode 100644 index 9a7d5b4721..0000000000 --- a/vendor/github.com/kataras/survey/terminal/cursor_windows.go +++ /dev/null @@ -1,101 +0,0 @@ -package terminal - -import ( - "os" - "syscall" - "unsafe" -) - -func CursorUp(n int) { - cursorMove(0, n) -} - -func CursorDown(n int) { - cursorMove(0, -1*n) -} - -func CursorForward(n int) { - cursorMove(n, 0) -} - -func CursorBack(n int) { - cursorMove(-1*n, 0) -} - -func cursorMove(x int, y int) { - handle := syscall.Handle(os.Stdout.Fd()) - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - - var cursor Coord - cursor.X = csbi.cursorPosition.X + Short(x) - cursor.Y = csbi.cursorPosition.Y + Short(y) - - procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor)))) -} - -func CursorNextLine(n int) { - CursorUp(n) - CursorHorizontalAbsolute(0) -} - -func CursorPreviousLine(n int) { - CursorDown(n) - CursorHorizontalAbsolute(0) -} - -func CursorHorizontalAbsolute(x int) { - handle := syscall.Handle(os.Stdout.Fd()) - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - - var cursor Coord - cursor.X = Short(x) - cursor.Y = csbi.cursorPosition.Y - - if csbi.size.X < cursor.X { - cursor.X = csbi.size.X - } - - procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor)))) -} - -func CursorShow() { - handle := syscall.Handle(os.Stdout.Fd()) - - var cci consoleCursorInfo - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) - cci.visible = 1 - - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) -} - -func CursorHide() { - handle := syscall.Handle(os.Stdout.Fd()) - - var cci consoleCursorInfo - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) - cci.visible = 0 - - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))) -} - -func CursorLocation() (Coord, error) { - handle := syscall.Handle(os.Stdout.Fd()) - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - - return csbi.cursorPosition, nil -} - -func Size() (Coord, error) { - handle := syscall.Handle(os.Stdout.Fd()) - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - - return csbi.size, nil -} diff --git a/vendor/github.com/kataras/survey/terminal/display.go b/vendor/github.com/kataras/survey/terminal/display.go deleted file mode 100644 index 0f014b1353..0000000000 --- a/vendor/github.com/kataras/survey/terminal/display.go +++ /dev/null @@ -1,9 +0,0 @@ -package terminal - -type EraseLineMode int - -const ( - ERASE_LINE_END EraseLineMode = iota - ERASE_LINE_START - ERASE_LINE_ALL -) diff --git a/vendor/github.com/kataras/survey/terminal/display_posix.go b/vendor/github.com/kataras/survey/terminal/display_posix.go deleted file mode 100644 index 47a765c794..0000000000 --- a/vendor/github.com/kataras/survey/terminal/display_posix.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build !windows - -package terminal - -import ( - "fmt" -) - -func EraseLine(mode EraseLineMode) { - fmt.Printf("\x1b[%dK", mode) -} diff --git a/vendor/github.com/kataras/survey/terminal/display_windows.go b/vendor/github.com/kataras/survey/terminal/display_windows.go deleted file mode 100644 index bb1ef45d7b..0000000000 --- a/vendor/github.com/kataras/survey/terminal/display_windows.go +++ /dev/null @@ -1,28 +0,0 @@ -package terminal - -import ( - "os" - "syscall" - "unsafe" -) - -func EraseLine(mode EraseLineMode) { - handle := syscall.Handle(os.Stdout.Fd()) - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - - var w uint32 - var x Short - cursor := csbi.cursorPosition - switch mode { - case ERASE_LINE_END: - x = csbi.size.X - case ERASE_LINE_START: - x = 0 - case ERASE_LINE_ALL: - cursor.X = 0 - x = csbi.size.X - } - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w))) -} diff --git a/vendor/github.com/kataras/survey/terminal/error.go b/vendor/github.com/kataras/survey/terminal/error.go deleted file mode 100644 index 710c361406..0000000000 --- a/vendor/github.com/kataras/survey/terminal/error.go +++ /dev/null @@ -1,9 +0,0 @@ -package terminal - -import ( - "errors" -) - -var ( - InterruptErr = errors.New("interrupt") -) diff --git a/vendor/github.com/kataras/survey/terminal/output.go b/vendor/github.com/kataras/survey/terminal/output.go deleted file mode 100644 index fabbb9fcde..0000000000 --- a/vendor/github.com/kataras/survey/terminal/output.go +++ /dev/null @@ -1,20 +0,0 @@ -// +build !windows - -package terminal - -import ( - "io" - "os" -) - -// Returns special stdout, which converts escape sequences to Windows API calls -// on Windows environment. -func NewAnsiStdout() io.Writer { - return os.Stdout -} - -// Returns special stderr, which converts escape sequences to Windows API calls -// on Windows environment. -func NewAnsiStderr() io.Writer { - return os.Stderr -} diff --git a/vendor/github.com/kataras/survey/terminal/output_windows.go b/vendor/github.com/kataras/survey/terminal/output_windows.go deleted file mode 100644 index 7d7f47fb14..0000000000 --- a/vendor/github.com/kataras/survey/terminal/output_windows.go +++ /dev/null @@ -1,228 +0,0 @@ -package terminal - -import ( - "bytes" - "fmt" - "io" - "os" - "strconv" - "strings" - "syscall" - "unsafe" - - "github.com/mattn/go-isatty" -) - -var ( - singleArgFunctions = map[rune]func(int){ - 'A': CursorUp, - 'B': CursorDown, - 'C': CursorForward, - 'D': CursorBack, - 'E': CursorNextLine, - 'F': CursorPreviousLine, - 'G': CursorHorizontalAbsolute, - } -) - -const ( - foregroundBlue = 0x1 - foregroundGreen = 0x2 - foregroundRed = 0x4 - foregroundIntensity = 0x8 - foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) - backgroundBlue = 0x10 - backgroundGreen = 0x20 - backgroundRed = 0x40 - backgroundIntensity = 0x80 - backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) -) - -type Writer struct { - out io.Writer - handle syscall.Handle - orgAttr word -} - -func NewAnsiStdout() io.Writer { - var csbi consoleScreenBufferInfo - out := os.Stdout - if !isatty.IsTerminal(out.Fd()) { - return out - } - handle := syscall.Handle(out.Fd()) - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - return &Writer{out: out, handle: handle, orgAttr: csbi.attributes} -} - -func NewAnsiStderr() io.Writer { - var csbi consoleScreenBufferInfo - out := os.Stderr - if !isatty.IsTerminal(out.Fd()) { - return out - } - handle := syscall.Handle(out.Fd()) - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - return &Writer{out: out, handle: handle, orgAttr: csbi.attributes} -} - -func (w *Writer) Write(data []byte) (n int, err error) { - r := bytes.NewReader(data) - - for { - ch, size, err := r.ReadRune() - if err != nil { - break - } - n += size - - switch ch { - case '\x1b': - size, err = w.handleEscape(r) - n += size - if err != nil { - break - } - default: - fmt.Fprint(w.out, string(ch)) - } - } - return -} - -func (w *Writer) handleEscape(r *bytes.Reader) (n int, err error) { - buf := make([]byte, 0, 10) - buf = append(buf, "\x1b"...) - - // Check '[' continues after \x1b - ch, size, err := r.ReadRune() - if err != nil { - fmt.Fprint(w.out, string(buf)) - return - } - n += size - if ch != '[' { - fmt.Fprint(w.out, string(buf)) - return - } - - // Parse escape code - var code rune - argBuf := make([]byte, 0, 10) - for { - ch, size, err = r.ReadRune() - if err != nil { - fmt.Fprint(w.out, string(buf)) - return - } - n += size - if ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') { - code = ch - break - } - argBuf = append(argBuf, string(ch)...) - } - - w.applyEscapeCode(buf, string(argBuf), code) - return -} - -func (w *Writer) applyEscapeCode(buf []byte, arg string, code rune) { - switch arg + string(code) { - case "?25h": - CursorShow() - return - case "?25l": - CursorHide() - return - } - - if f, ok := singleArgFunctions[code]; ok { - if n, err := strconv.Atoi(arg); err == nil { - f(n) - return - } - } - - switch code { - case 'm': - w.applySelectGraphicRendition(arg) - default: - buf = append(buf, string(code)...) - fmt.Fprint(w.out, string(buf)) - } -} - -// Original implementation: https://github.com/mattn/go-colorable -func (w *Writer) applySelectGraphicRendition(arg string) { - if arg == "" { - procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.orgAttr)) - return - } - - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) - attr := csbi.attributes - - for _, param := range strings.Split(arg, ";") { - n, err := strconv.Atoi(param) - if err != nil { - continue - } - - switch { - case n == 0 || n == 100: - attr = w.orgAttr - case 1 <= n && n <= 5: - attr |= foregroundIntensity - case 30 <= n && n <= 37: - attr = (attr & backgroundMask) - if (n-30)&1 != 0 { - attr |= foregroundRed - } - if (n-30)&2 != 0 { - attr |= foregroundGreen - } - if (n-30)&4 != 0 { - attr |= foregroundBlue - } - case 40 <= n && n <= 47: - attr = (attr & foregroundMask) - if (n-40)&1 != 0 { - attr |= backgroundRed - } - if (n-40)&2 != 0 { - attr |= backgroundGreen - } - if (n-40)&4 != 0 { - attr |= backgroundBlue - } - case 90 <= n && n <= 97: - attr = (attr & backgroundMask) - attr |= foregroundIntensity - if (n-90)&1 != 0 { - attr |= foregroundRed - } - if (n-90)&2 != 0 { - attr |= foregroundGreen - } - if (n-90)&4 != 0 { - attr |= foregroundBlue - } - case 100 <= n && n <= 107: - attr = (attr & foregroundMask) - attr |= backgroundIntensity - if (n-100)&1 != 0 { - attr |= backgroundRed - } - if (n-100)&2 != 0 { - attr |= backgroundGreen - } - if (n-100)&4 != 0 { - attr |= backgroundBlue - } - } - } - - procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) -} diff --git a/vendor/github.com/kataras/survey/terminal/print.go b/vendor/github.com/kataras/survey/terminal/print.go deleted file mode 100644 index d7b6f612fc..0000000000 --- a/vendor/github.com/kataras/survey/terminal/print.go +++ /dev/null @@ -1,25 +0,0 @@ -package terminal - -import ( - "fmt" -) - -var ( - Stdout = NewAnsiStdout() -) - -// Print prints given arguments with escape sequence conversion for windows. -func Print(a ...interface{}) (n int, err error) { - return fmt.Fprint(Stdout, a...) -} - -// Printf prints a given format with escape sequence conversion for windows. -func Printf(format string, a ...interface{}) (n int, err error) { - return fmt.Fprintf(Stdout, format, a...) -} - -// Println prints given arguments with newline and escape sequence conversion -// for windows. -func Println(a ...interface{}) (n int, err error) { - return fmt.Fprintln(Stdout, a...) -} diff --git a/vendor/github.com/kataras/survey/terminal/runereader.go b/vendor/github.com/kataras/survey/terminal/runereader.go deleted file mode 100644 index 96226425d5..0000000000 --- a/vendor/github.com/kataras/survey/terminal/runereader.go +++ /dev/null @@ -1,183 +0,0 @@ -package terminal - -import ( - "os" - "unicode" -) - -type RuneReader struct { - Input *os.File - - state runeReaderState -} - -func NewRuneReader(input *os.File) *RuneReader { - return &RuneReader{ - Input: input, - state: newRuneReaderState(input), - } -} - -func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) { - line := []rune{} - - // we only care about horizontal displacements from the origin so start counting at 0 - index := 0 - - for { - // wait for some input - r, _, err := rr.ReadRune() - if err != nil { - return line, err - } - - // if the user pressed enter or some other newline/termination like ctrl+d - if r == '\r' || r == '\n' || r == KeyEndTransmission { - // go to the beginning of the next line - Print("\r\n") - - // we're done processing the input - return line, nil - } - - // if the user interrupts (ie with ctrl+c) - if r == KeyInterrupt { - // go to the beginning of the next line - Print("\r\n") - - // we're done processing the input, and treat interrupt like an error - return line, InterruptErr - } - - // allow for backspace/delete editing of inputs - if r == KeyBackspace || r == KeyDelete { - // and we're not at the beginning of the line - if index > 0 && len(line) > 0 { - // if we are at the end of the word - if index == len(line) { - // just remove the last letter from the internal representation - line = line[:len(line)-1] - - // go back one - CursorBack(1) - - // clear the rest of the line - EraseLine(ERASE_LINE_END) - } else { - // we need to remove a character from the middle of the word - - // remove the current index from the list - line = append(line[:index-1], line[index:]...) - - // go back one space so we can clear the rest - CursorBack(1) - - // clear the rest of the line - EraseLine(ERASE_LINE_END) - - // print what comes after - Print(string(line[index-1:])) - - // leave the cursor where the user left it - CursorBack(len(line) - index + 1) - } - - // decrement the index - index-- - } else { - // otherwise the user pressed backspace while at the beginning of the line - soundBell() - } - - // we're done processing this key - continue - } - - // if the left arrow is pressed - if r == KeyArrowLeft { - // and we have space to the left - if index > 0 { - // move the cursor to the left - CursorBack(1) - // decrement the index - index-- - - } else { - // otherwise we are at the beginning of where we started reading lines - // sound the bell - soundBell() - } - - // we're done processing this key press - continue - } - - // if the right arrow is pressed - if r == KeyArrowRight { - // and we have space to the right of the word - if index < len(line) { - // move the cursor to the right - CursorForward(1) - // increment the index - index++ - - } else { - // otherwise we are at the end of the word and can't go past - // sound the bell - soundBell() - } - - // we're done processing this key press - continue - } - - // if the letter is another escape sequence - if unicode.IsControl(r) { - // ignore it - continue - } - - // the user pressed a regular key - - // if we are at the end of the line - if index == len(line) { - // just append the character at the end of the line - line = append(line, r) - // increment the location counter - index++ - - // if we don't need to mask the input - if mask == 0 { - // just print the character the user pressed - Printf("%c", r) - } else { - // otherwise print the mask we were given - Printf("%c", mask) - } - } else { - // we are in the middle of the word so we need to insert the character the user pressed - line = append(line[:index], append([]rune{r}, line[index:]...)...) - - // visually insert the character by deleting the rest of the line - EraseLine(ERASE_LINE_END) - - // print the rest of the word after - for _, char := range line[index:] { - // if we don't need to mask the input - if mask == 0 { - // just print the character the user pressed - Printf("%c", char) - } else { - // otherwise print the mask we were given - Printf("%c", mask) - } - } - - // leave the cursor where the user left it - CursorBack(len(line) - index - 1) - - // accommodate the new letter in our counter - index++ - } - } -} diff --git a/vendor/github.com/kataras/survey/terminal/runereader_bsd.go b/vendor/github.com/kataras/survey/terminal/runereader_bsd.go deleted file mode 100644 index 6ea340923a..0000000000 --- a/vendor/github.com/kataras/survey/terminal/runereader_bsd.go +++ /dev/null @@ -1,13 +0,0 @@ -// copied from: https://github.com/golang/crypto/blob/master/ssh/terminal/util_bsd.go -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin dragonfly freebsd netbsd openbsd - -package terminal - -import "syscall" - -const ioctlReadTermios = syscall.TIOCGETA -const ioctlWriteTermios = syscall.TIOCSETA diff --git a/vendor/github.com/kataras/survey/terminal/runereader_linux.go b/vendor/github.com/kataras/survey/terminal/runereader_linux.go deleted file mode 100644 index 74e1b697ff..0000000000 --- a/vendor/github.com/kataras/survey/terminal/runereader_linux.go +++ /dev/null @@ -1,12 +0,0 @@ -// copied from https://github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package terminal - -// These constants are declared here, rather than importing -// them from the syscall package as some syscall packages, even -// on linux, for example gccgo, do not declare them. -const ioctlReadTermios = 0x5401 // syscall.TCGETS -const ioctlWriteTermios = 0x5402 // syscall.TCSETS diff --git a/vendor/github.com/kataras/survey/terminal/runereader_posix.go b/vendor/github.com/kataras/survey/terminal/runereader_posix.go deleted file mode 100644 index 1f463fb8cc..0000000000 --- a/vendor/github.com/kataras/survey/terminal/runereader_posix.go +++ /dev/null @@ -1,84 +0,0 @@ -// +build !windows - -// The terminal mode manipulation code is derived heavily from: -// https://github.com/golang/crypto/blob/master/ssh/terminal/util.go: -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package terminal - -import ( - "bufio" - "fmt" - "os" - "syscall" - "unsafe" -) - -type runeReaderState struct { - term syscall.Termios - buf *bufio.Reader -} - -func newRuneReaderState(input *os.File) runeReaderState { - return runeReaderState{ - buf: bufio.NewReader(input), - } -} - -// For reading runes we just want to disable echo. -func (rr *RuneReader) SetTermMode() error { - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.Input.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 { - return err - } - - newState := rr.state.term - newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG - - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.Input.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { - return err - } - - return nil -} - -func (rr *RuneReader) RestoreTermMode() error { - if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.Input.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 { - return err - } - return nil -} - -func (rr *RuneReader) ReadRune() (rune, int, error) { - r, size, err := rr.state.buf.ReadRune() - if err != nil { - return r, size, err - } - // parse ^[ sequences to look for arrow keys - if r == '\033' { - r, size, err = rr.state.buf.ReadRune() - if err != nil { - return r, size, err - } - if r != '[' { - return r, size, fmt.Errorf("Unexpected Escape Sequence: %q", []rune{'\033', r}) - } - r, size, err = rr.state.buf.ReadRune() - if err != nil { - return r, size, err - } - switch r { - case 'D': - return KeyArrowLeft, 1, nil - case 'C': - return KeyArrowRight, 1, nil - case 'A': - return KeyArrowUp, 1, nil - case 'B': - return KeyArrowDown, 1, nil - } - return r, size, fmt.Errorf("Unknown Escape Sequence: %q", []rune{'\033', '[', r}) - } - return r, size, err -} diff --git a/vendor/github.com/kataras/survey/terminal/runereader_windows.go b/vendor/github.com/kataras/survey/terminal/runereader_windows.go deleted file mode 100644 index a0cea826ac..0000000000 --- a/vendor/github.com/kataras/survey/terminal/runereader_windows.go +++ /dev/null @@ -1,130 +0,0 @@ -package terminal - -import ( - "os" - "syscall" - "unsafe" -) - -var ( - dll = syscall.NewLazyDLL("kernel32.dll") - setConsoleMode = dll.NewProc("SetConsoleMode") - getConsoleMode = dll.NewProc("GetConsoleMode") - readConsoleInput = dll.NewProc("ReadConsoleInputW") -) - -const ( - EVENT_KEY = 0x0001 - - // key codes for arrow keys - // https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx - VK_LEFT = 0x25 - VK_UP = 0x26 - VK_RIGHT = 0x27 - VK_DOWN = 0x28 - - RIGHT_CTRL_PRESSED = 0x0004 - LEFT_CTRL_PRESSED = 0x0008 - - ENABLE_ECHO_INPUT uint32 = 0x0004 - ENABLE_LINE_INPUT uint32 = 0x0002 - ENABLE_PROCESSED_INPUT uint32 = 0x0001 -) - -type inputRecord struct { - eventType uint16 - padding uint16 - event [16]byte -} - -type keyEventRecord struct { - bKeyDown int32 - wRepeatCount uint16 - wVirtualKeyCode uint16 - wVirtualScanCode uint16 - unicodeChar uint16 - wdControlKeyState uint32 -} - -type runeReaderState struct { - term uint32 -} - -func newRuneReaderState(input *os.File) runeReaderState { - return runeReaderState{} -} - -func (rr *RuneReader) SetTermMode() error { - r, _, err := getConsoleMode.Call(uintptr(rr.Input.Fd()), uintptr(unsafe.Pointer(&rr.state.term))) - // windows return 0 on error - if r == 0 { - return err - } - - newState := rr.state.term - newState &^= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT - r, _, err = setConsoleMode.Call(uintptr(rr.Input.Fd()), uintptr(newState)) - // windows return 0 on error - if r == 0 { - return err - } - return nil -} - -func (rr *RuneReader) RestoreTermMode() error { - r, _, err := setConsoleMode.Call(uintptr(rr.Input.Fd()), uintptr(rr.state.term)) - // windows return 0 on error - if r == 0 { - return err - } - return nil -} - -func (rr *RuneReader) ReadRune() (rune, int, error) { - ir := &inputRecord{} - bytesRead := 0 - for { - rv, _, e := readConsoleInput.Call(rr.Input.Fd(), uintptr(unsafe.Pointer(ir)), 1, uintptr(unsafe.Pointer(&bytesRead))) - // windows returns non-zero to indicate success - if rv == 0 && e != nil { - return 0, 0, e - } - - if ir.eventType != EVENT_KEY { - continue - } - - // the event data is really a c struct union, so here we have to do an usafe - // cast to put the data into the keyEventRecord (since we have already verified - // above that this event does correspond to a key event - key := (*keyEventRecord)(unsafe.Pointer(&ir.event[0])) - // we only care about key down events - if key.bKeyDown == 0 { - continue - } - if key.wdControlKeyState&(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED) != 0 && key.unicodeChar == 'C' { - return KeyInterrupt, bytesRead, nil - } - - // not a normal character so look up the input sequence from the - // virtual key code mappings (VK_*) - if key.unicodeChar == 0 { - switch key.wVirtualKeyCode { - case VK_DOWN: - return KeyArrowDown, bytesRead, nil - case VK_LEFT: - return KeyArrowLeft, bytesRead, nil - case VK_RIGHT: - return KeyArrowRight, bytesRead, nil - case VK_UP: - return KeyArrowUp, bytesRead, nil - default: - // not a virtual key that we care about so just continue on to - // the next input key - continue - } - } - r := rune(key.unicodeChar) - return r, bytesRead, nil - } -} diff --git a/vendor/github.com/kataras/survey/terminal/sequences.go b/vendor/github.com/kataras/survey/terminal/sequences.go deleted file mode 100644 index 0fc1396944..0000000000 --- a/vendor/github.com/kataras/survey/terminal/sequences.go +++ /dev/null @@ -1,18 +0,0 @@ -package terminal - -const ( - KeyArrowLeft = '\x02' - KeyArrowRight = '\x06' - KeyArrowUp = '\x10' - KeyArrowDown = '\x0e' - KeySpace = ' ' - KeyEnter = '\r' - KeyBackspace = '\b' - KeyDelete = '\x7f' - KeyInterrupt = '\x03' - KeyEndTransmission = '\x04' -) - -func soundBell() { - Print("\a") -} diff --git a/vendor/github.com/kataras/survey/terminal/syscall_windows.go b/vendor/github.com/kataras/survey/terminal/syscall_windows.go deleted file mode 100644 index 63b85d4c39..0000000000 --- a/vendor/github.com/kataras/survey/terminal/syscall_windows.go +++ /dev/null @@ -1,39 +0,0 @@ -package terminal - -import ( - "syscall" -) - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") - procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") - procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") - procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") - procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") - procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") -) - -type wchar uint16 -type dword uint32 -type word uint16 - -type smallRect struct { - left Short - top Short - right Short - bottom Short -} - -type consoleScreenBufferInfo struct { - size Coord - cursorPosition Coord - attributes word - window smallRect - maximumWindowSize Coord -} - -type consoleCursorInfo struct { - size dword - visible int32 -} diff --git a/vendor/github.com/kataras/survey/terminal/terminal.go b/vendor/github.com/kataras/survey/terminal/terminal.go deleted file mode 100644 index 4b59062c7d..0000000000 --- a/vendor/github.com/kataras/survey/terminal/terminal.go +++ /dev/null @@ -1,8 +0,0 @@ -package terminal - -type Short int16 - -type Coord struct { - X Short - Y Short -} diff --git a/vendor/github.com/kataras/survey/transform.go b/vendor/github.com/kataras/survey/transform.go deleted file mode 100644 index ccc75e0843..0000000000 --- a/vendor/github.com/kataras/survey/transform.go +++ /dev/null @@ -1,76 +0,0 @@ -package survey - -import ( - "reflect" - "strings" -) - -// TransformString returns a `Transformer` based on the "f" -// function which accepts a string representation of the answer -// and returns a new one, transformed, answer. -// Take for example the functions inside the std `strings` package, -// they can be converted to a compatible `Transformer` by using this function, -// i.e: `TransformString(strings.Title)`, `TransformString(strings.ToUpper)`. -// -// Note that `TransformString` is just a helper, `Transformer` can be used -// to transform any type of answer. -func TransformString(f func(s string) string) Transformer { - return func(ans interface{}) interface{} { - // if the answer value passed in is the zero value of the appropriate type - if isZero(reflect.ValueOf(ans)) { - // skip this `Transformer` by returning a nil value. - // The original answer will be not affected, - // see survey.go#L125. - return nil - } - - // "ans" is never nil here, so we don't have to check that - // see survey.go#L97 for more. - // Make sure that the the answer's value was a typeof string. - s, ok := ans.(string) - if !ok { - return nil - } - - return f(s) - } -} - -// ToLower is a `Transformer`. -// It receives an answer value -// and returns a copy of the "ans" -// with all Unicode letters mapped to their lower case. -// -// Note that if "ans" is not a string then it will -// return a nil value, meaning that the above answer -// will not be affected by this call at all. -func ToLower(ans interface{}) interface{} { - transformer := TransformString(strings.ToLower) - return transformer(ans) -} - -// Title is a `Transformer`. -// It receives an answer value -// and returns a copy of the "ans" -// with all Unicode letters that begin words -// mapped to their title case. -// -// Note that if "ans" is not a string then it will -// return a nil value, meaning that the above answer -// will not be affected by this call at all. -func Title(ans interface{}) interface{} { - transformer := TransformString(strings.Title) - return transformer(ans) -} - -// ComposeTransformers is a variadic function used to create one transformer from many. -func ComposeTransformers(transformers ...Transformer) Transformer { - // return a transformer that calls each one sequentially - return func(ans interface{}) interface{} { - // execute each transformer - for _, t := range transformers { - ans = t(ans) - } - return ans - } -} diff --git a/vendor/github.com/kataras/survey/validate.go b/vendor/github.com/kataras/survey/validate.go deleted file mode 100644 index 50162c2582..0000000000 --- a/vendor/github.com/kataras/survey/validate.go +++ /dev/null @@ -1,92 +0,0 @@ -package survey - -import ( - "errors" - "fmt" - "reflect" -) - -// Required does not allow an empty value -func Required(val interface{}) error { - // if the value passed in is the zero value of the appropriate type - if isZero(reflect.ValueOf(val)) { - return errors.New("Value is required") - } - return nil -} - -// MaxLength requires that the string is no longer than the specified value -func MaxLength(length int) Validator { - // return a validator that checks the length of the string - return func(val interface{}) error { - if str, ok := val.(string); ok { - // if the string is longer than the given value - if len(str) > length { - // yell loudly - return fmt.Errorf("value is too long. Max length is %v", length) - } - } else { - // otherwise we cannot convert the value into a string and cannot enforce length - return fmt.Errorf("cannot enforce length on response of type %v", reflect.TypeOf(val).Name()) - } - - // the input is fine - return nil - } -} - -// MinLength requires that the string is longer or equal in length to the specified value -func MinLength(length int) Validator { - // return a validator that checks the length of the string - return func(val interface{}) error { - if str, ok := val.(string); ok { - // if the string is shorter than the given value - if len(str) < length { - // yell loudly - return fmt.Errorf("value is too short. Min length is %v", length) - } - } else { - // otherwise we cannot convert the value into a string and cannot enforce length - return fmt.Errorf("cannot enforce length on response of type %v", reflect.TypeOf(val).Name()) - } - - // the input is fine - return nil - } -} - -// ComposeValidators is a variadic function used to create one validator from many. -func ComposeValidators(validators ...Validator) Validator { - // return a validator that calls each one sequentially - return func(val interface{}) error { - // execute each validator - for _, validator := range validators { - // if the answer's value is not valid - if err := validator(val); err != nil { - // return the error - return err - } - } - // we passed all validators, the answer is valid - return nil - } -} - -// isZero returns true if the passed value is the zero object -func isZero(v reflect.Value) bool { - switch v.Kind() { - case reflect.Slice, reflect.Map: - return v.Len() == 0 - // fixes: - // if confirm and `Validate: survey.Required` is used - // and answer is "No" (== false) - // then it shows "Sorry, your reply was invalid: Value is required" - // and it stucks there. - // This happens because 'false' is the zero value of a "bool" type. - case reflect.Bool: - return false - } - - // compare the types directly with more general coverage - return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/LICENSE b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/LICENSE deleted file mode 100644 index 91b5cef30e..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Yasuhiro Matsumoto - -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. diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_appengine.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_appengine.go deleted file mode 100644 index 1f28d773d7..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_appengine.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build appengine - -package colorable - -import ( - "io" - "os" - - _ "github.com/mattn/go-isatty" -) - -// NewColorable return new instance of Writer which handle escape sequence. -func NewColorable(file *os.File) io.Writer { - if file == nil { - panic("nil passed instead of *os.File to NewColorable()") - } - - return file -} - -// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. -func NewColorableStdout() io.Writer { - return os.Stdout -} - -// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. -func NewColorableStderr() io.Writer { - return os.Stderr -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_others.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_others.go deleted file mode 100644 index 887f203dc7..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_others.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build !windows -// +build !appengine - -package colorable - -import ( - "io" - "os" - - _ "github.com/mattn/go-isatty" -) - -// NewColorable return new instance of Writer which handle escape sequence. -func NewColorable(file *os.File) io.Writer { - if file == nil { - panic("nil passed instead of *os.File to NewColorable()") - } - - return file -} - -// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. -func NewColorableStdout() io.Writer { - return os.Stdout -} - -// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. -func NewColorableStderr() io.Writer { - return os.Stderr -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_windows.go deleted file mode 100644 index 15a014fd30..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/colorable_windows.go +++ /dev/null @@ -1,968 +0,0 @@ -// +build windows -// +build !appengine - -package colorable - -import ( - "bytes" - "io" - "math" - "os" - "strconv" - "strings" - "syscall" - "unsafe" - - "github.com/mattn/go-isatty" -) - -const ( - foregroundBlue = 0x1 - foregroundGreen = 0x2 - foregroundRed = 0x4 - foregroundIntensity = 0x8 - foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) - backgroundBlue = 0x10 - backgroundGreen = 0x20 - backgroundRed = 0x40 - backgroundIntensity = 0x80 - backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) -) - -const ( - genericRead = 0x80000000 - genericWrite = 0x40000000 -) - -const ( - consoleTextmodeBuffer = 0x1 -) - -type wchar uint16 -type short int16 -type dword uint32 -type word uint16 - -type coord struct { - x short - y short -} - -type smallRect struct { - left short - top short - right short - bottom short -} - -type consoleScreenBufferInfo struct { - size coord - cursorPosition coord - attributes word - window smallRect - maximumWindowSize coord -} - -type consoleCursorInfo struct { - size dword - visible int32 -} - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") - procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") - procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") - procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") - procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") - procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") - procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") - procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW") - procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer") -) - -// Writer provide colorable Writer to the console -type Writer struct { - out io.Writer - handle syscall.Handle - althandle syscall.Handle - oldattr word - oldpos coord - rest bytes.Buffer -} - -// NewColorable return new instance of Writer which handle escape sequence from File. -func NewColorable(file *os.File) io.Writer { - if file == nil { - panic("nil passed instead of *os.File to NewColorable()") - } - - if isatty.IsTerminal(file.Fd()) { - var csbi consoleScreenBufferInfo - handle := syscall.Handle(file.Fd()) - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}} - } - return file -} - -// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. -func NewColorableStdout() io.Writer { - return NewColorable(os.Stdout) -} - -// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. -func NewColorableStderr() io.Writer { - return NewColorable(os.Stderr) -} - -var color256 = map[int]int{ - 0: 0x000000, - 1: 0x800000, - 2: 0x008000, - 3: 0x808000, - 4: 0x000080, - 5: 0x800080, - 6: 0x008080, - 7: 0xc0c0c0, - 8: 0x808080, - 9: 0xff0000, - 10: 0x00ff00, - 11: 0xffff00, - 12: 0x0000ff, - 13: 0xff00ff, - 14: 0x00ffff, - 15: 0xffffff, - 16: 0x000000, - 17: 0x00005f, - 18: 0x000087, - 19: 0x0000af, - 20: 0x0000d7, - 21: 0x0000ff, - 22: 0x005f00, - 23: 0x005f5f, - 24: 0x005f87, - 25: 0x005faf, - 26: 0x005fd7, - 27: 0x005fff, - 28: 0x008700, - 29: 0x00875f, - 30: 0x008787, - 31: 0x0087af, - 32: 0x0087d7, - 33: 0x0087ff, - 34: 0x00af00, - 35: 0x00af5f, - 36: 0x00af87, - 37: 0x00afaf, - 38: 0x00afd7, - 39: 0x00afff, - 40: 0x00d700, - 41: 0x00d75f, - 42: 0x00d787, - 43: 0x00d7af, - 44: 0x00d7d7, - 45: 0x00d7ff, - 46: 0x00ff00, - 47: 0x00ff5f, - 48: 0x00ff87, - 49: 0x00ffaf, - 50: 0x00ffd7, - 51: 0x00ffff, - 52: 0x5f0000, - 53: 0x5f005f, - 54: 0x5f0087, - 55: 0x5f00af, - 56: 0x5f00d7, - 57: 0x5f00ff, - 58: 0x5f5f00, - 59: 0x5f5f5f, - 60: 0x5f5f87, - 61: 0x5f5faf, - 62: 0x5f5fd7, - 63: 0x5f5fff, - 64: 0x5f8700, - 65: 0x5f875f, - 66: 0x5f8787, - 67: 0x5f87af, - 68: 0x5f87d7, - 69: 0x5f87ff, - 70: 0x5faf00, - 71: 0x5faf5f, - 72: 0x5faf87, - 73: 0x5fafaf, - 74: 0x5fafd7, - 75: 0x5fafff, - 76: 0x5fd700, - 77: 0x5fd75f, - 78: 0x5fd787, - 79: 0x5fd7af, - 80: 0x5fd7d7, - 81: 0x5fd7ff, - 82: 0x5fff00, - 83: 0x5fff5f, - 84: 0x5fff87, - 85: 0x5fffaf, - 86: 0x5fffd7, - 87: 0x5fffff, - 88: 0x870000, - 89: 0x87005f, - 90: 0x870087, - 91: 0x8700af, - 92: 0x8700d7, - 93: 0x8700ff, - 94: 0x875f00, - 95: 0x875f5f, - 96: 0x875f87, - 97: 0x875faf, - 98: 0x875fd7, - 99: 0x875fff, - 100: 0x878700, - 101: 0x87875f, - 102: 0x878787, - 103: 0x8787af, - 104: 0x8787d7, - 105: 0x8787ff, - 106: 0x87af00, - 107: 0x87af5f, - 108: 0x87af87, - 109: 0x87afaf, - 110: 0x87afd7, - 111: 0x87afff, - 112: 0x87d700, - 113: 0x87d75f, - 114: 0x87d787, - 115: 0x87d7af, - 116: 0x87d7d7, - 117: 0x87d7ff, - 118: 0x87ff00, - 119: 0x87ff5f, - 120: 0x87ff87, - 121: 0x87ffaf, - 122: 0x87ffd7, - 123: 0x87ffff, - 124: 0xaf0000, - 125: 0xaf005f, - 126: 0xaf0087, - 127: 0xaf00af, - 128: 0xaf00d7, - 129: 0xaf00ff, - 130: 0xaf5f00, - 131: 0xaf5f5f, - 132: 0xaf5f87, - 133: 0xaf5faf, - 134: 0xaf5fd7, - 135: 0xaf5fff, - 136: 0xaf8700, - 137: 0xaf875f, - 138: 0xaf8787, - 139: 0xaf87af, - 140: 0xaf87d7, - 141: 0xaf87ff, - 142: 0xafaf00, - 143: 0xafaf5f, - 144: 0xafaf87, - 145: 0xafafaf, - 146: 0xafafd7, - 147: 0xafafff, - 148: 0xafd700, - 149: 0xafd75f, - 150: 0xafd787, - 151: 0xafd7af, - 152: 0xafd7d7, - 153: 0xafd7ff, - 154: 0xafff00, - 155: 0xafff5f, - 156: 0xafff87, - 157: 0xafffaf, - 158: 0xafffd7, - 159: 0xafffff, - 160: 0xd70000, - 161: 0xd7005f, - 162: 0xd70087, - 163: 0xd700af, - 164: 0xd700d7, - 165: 0xd700ff, - 166: 0xd75f00, - 167: 0xd75f5f, - 168: 0xd75f87, - 169: 0xd75faf, - 170: 0xd75fd7, - 171: 0xd75fff, - 172: 0xd78700, - 173: 0xd7875f, - 174: 0xd78787, - 175: 0xd787af, - 176: 0xd787d7, - 177: 0xd787ff, - 178: 0xd7af00, - 179: 0xd7af5f, - 180: 0xd7af87, - 181: 0xd7afaf, - 182: 0xd7afd7, - 183: 0xd7afff, - 184: 0xd7d700, - 185: 0xd7d75f, - 186: 0xd7d787, - 187: 0xd7d7af, - 188: 0xd7d7d7, - 189: 0xd7d7ff, - 190: 0xd7ff00, - 191: 0xd7ff5f, - 192: 0xd7ff87, - 193: 0xd7ffaf, - 194: 0xd7ffd7, - 195: 0xd7ffff, - 196: 0xff0000, - 197: 0xff005f, - 198: 0xff0087, - 199: 0xff00af, - 200: 0xff00d7, - 201: 0xff00ff, - 202: 0xff5f00, - 203: 0xff5f5f, - 204: 0xff5f87, - 205: 0xff5faf, - 206: 0xff5fd7, - 207: 0xff5fff, - 208: 0xff8700, - 209: 0xff875f, - 210: 0xff8787, - 211: 0xff87af, - 212: 0xff87d7, - 213: 0xff87ff, - 214: 0xffaf00, - 215: 0xffaf5f, - 216: 0xffaf87, - 217: 0xffafaf, - 218: 0xffafd7, - 219: 0xffafff, - 220: 0xffd700, - 221: 0xffd75f, - 222: 0xffd787, - 223: 0xffd7af, - 224: 0xffd7d7, - 225: 0xffd7ff, - 226: 0xffff00, - 227: 0xffff5f, - 228: 0xffff87, - 229: 0xffffaf, - 230: 0xffffd7, - 231: 0xffffff, - 232: 0x080808, - 233: 0x121212, - 234: 0x1c1c1c, - 235: 0x262626, - 236: 0x303030, - 237: 0x3a3a3a, - 238: 0x444444, - 239: 0x4e4e4e, - 240: 0x585858, - 241: 0x626262, - 242: 0x6c6c6c, - 243: 0x767676, - 244: 0x808080, - 245: 0x8a8a8a, - 246: 0x949494, - 247: 0x9e9e9e, - 248: 0xa8a8a8, - 249: 0xb2b2b2, - 250: 0xbcbcbc, - 251: 0xc6c6c6, - 252: 0xd0d0d0, - 253: 0xdadada, - 254: 0xe4e4e4, - 255: 0xeeeeee, -} - -// `\033]0;TITLESTR\007` -func doTitleSequence(er *bytes.Reader) error { - var c byte - var err error - - c, err = er.ReadByte() - if err != nil { - return err - } - if c != '0' && c != '2' { - return nil - } - c, err = er.ReadByte() - if err != nil { - return err - } - if c != ';' { - return nil - } - title := make([]byte, 0, 80) - for { - c, err = er.ReadByte() - if err != nil { - return err - } - if c == 0x07 || c == '\n' { - break - } - title = append(title, c) - } - if len(title) > 0 { - title8, err := syscall.UTF16PtrFromString(string(title)) - if err == nil { - procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8))) - } - } - return nil -} - -// Write write data on console -func (w *Writer) Write(data []byte) (n int, err error) { - var csbi consoleScreenBufferInfo - procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) - - handle := w.handle - - var er *bytes.Reader - if w.rest.Len() > 0 { - var rest bytes.Buffer - w.rest.WriteTo(&rest) - w.rest.Reset() - rest.Write(data) - er = bytes.NewReader(rest.Bytes()) - } else { - er = bytes.NewReader(data) - } - var bw [1]byte -loop: - for { - c1, err := er.ReadByte() - if err != nil { - break loop - } - if c1 != 0x1b { - bw[0] = c1 - w.out.Write(bw[:]) - continue - } - c2, err := er.ReadByte() - if err != nil { - break loop - } - - if c2 == ']' { - w.rest.WriteByte(c1) - w.rest.WriteByte(c2) - er.WriteTo(&w.rest) - if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 { - break loop - } - er = bytes.NewReader(w.rest.Bytes()[2:]) - err := doTitleSequence(er) - if err != nil { - break loop - } - w.rest.Reset() - continue - } - if c2 != 0x5b { - continue - } - - w.rest.WriteByte(c1) - w.rest.WriteByte(c2) - er.WriteTo(&w.rest) - - var buf bytes.Buffer - var m byte - for i, c := range w.rest.Bytes()[2:] { - if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { - m = c - er = bytes.NewReader(w.rest.Bytes()[2+i+1:]) - w.rest.Reset() - break - } - buf.Write([]byte(string(c))) - } - if m == 0 { - break loop - } - - switch m { - case 'A': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.y -= short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'B': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.y += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'C': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'D': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x -= short(n) - if csbi.cursorPosition.x < 0 { - csbi.cursorPosition.x = 0 - } - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'E': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x = 0 - csbi.cursorPosition.y += short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'F': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x = 0 - csbi.cursorPosition.y -= short(n) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'G': - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - csbi.cursorPosition.x = short(n - 1) - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'H', 'f': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - if buf.Len() > 0 { - token := strings.Split(buf.String(), ";") - switch len(token) { - case 1: - n1, err := strconv.Atoi(token[0]) - if err != nil { - continue - } - csbi.cursorPosition.y = short(n1 - 1) - case 2: - n1, err := strconv.Atoi(token[0]) - if err != nil { - continue - } - n2, err := strconv.Atoi(token[1]) - if err != nil { - continue - } - csbi.cursorPosition.x = short(n2 - 1) - csbi.cursorPosition.y = short(n1 - 1) - } - } else { - csbi.cursorPosition.y = 0 - } - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) - case 'J': - n := 0 - if buf.Len() > 0 { - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - } - var count, written dword - var cursor coord - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - switch n { - case 0: - cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} - count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) - case 1: - cursor = coord{x: csbi.window.left, y: csbi.window.top} - count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.window.top-csbi.cursorPosition.y)*csbi.size.x) - case 2: - cursor = coord{x: csbi.window.left, y: csbi.window.top} - count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) - } - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - case 'K': - n := 0 - if buf.Len() > 0 { - n, err = strconv.Atoi(buf.String()) - if err != nil { - continue - } - } - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - var cursor coord - var count, written dword - switch n { - case 0: - cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} - count = dword(csbi.size.x - csbi.cursorPosition.x) - case 1: - cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} - count = dword(csbi.size.x - csbi.cursorPosition.x) - case 2: - cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} - count = dword(csbi.size.x) - } - procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) - case 'm': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - attr := csbi.attributes - cs := buf.String() - if cs == "" { - procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr)) - continue - } - token := strings.Split(cs, ";") - for i := 0; i < len(token); i++ { - ns := token[i] - if n, err = strconv.Atoi(ns); err == nil { - switch { - case n == 0 || n == 100: - attr = w.oldattr - case 1 <= n && n <= 5: - attr |= foregroundIntensity - case n == 7: - attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) - case n == 22 || n == 25: - attr |= foregroundIntensity - case n == 27: - attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) - case 30 <= n && n <= 37: - attr &= backgroundMask - if (n-30)&1 != 0 { - attr |= foregroundRed - } - if (n-30)&2 != 0 { - attr |= foregroundGreen - } - if (n-30)&4 != 0 { - attr |= foregroundBlue - } - case n == 38: // set foreground color. - if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { - if n256, err := strconv.Atoi(token[i+2]); err == nil { - if n256foreAttr == nil { - n256setup() - } - attr &= backgroundMask - attr |= n256foreAttr[n256] - i += 2 - } - } else if len(token) == 5 && token[i+1] == "2" { - var r, g, b int - r, _ = strconv.Atoi(token[i+2]) - g, _ = strconv.Atoi(token[i+3]) - b, _ = strconv.Atoi(token[i+4]) - i += 4 - if r > 127 { - attr |= foregroundRed - } - if g > 127 { - attr |= foregroundGreen - } - if b > 127 { - attr |= foregroundBlue - } - } else { - attr = attr & (w.oldattr & backgroundMask) - } - case n == 39: // reset foreground color. - attr &= backgroundMask - attr |= w.oldattr & foregroundMask - case 40 <= n && n <= 47: - attr &= foregroundMask - if (n-40)&1 != 0 { - attr |= backgroundRed - } - if (n-40)&2 != 0 { - attr |= backgroundGreen - } - if (n-40)&4 != 0 { - attr |= backgroundBlue - } - case n == 48: // set background color. - if i < len(token)-2 && token[i+1] == "5" { - if n256, err := strconv.Atoi(token[i+2]); err == nil { - if n256backAttr == nil { - n256setup() - } - attr &= foregroundMask - attr |= n256backAttr[n256] - i += 2 - } - } else if len(token) == 5 && token[i+1] == "2" { - var r, g, b int - r, _ = strconv.Atoi(token[i+2]) - g, _ = strconv.Atoi(token[i+3]) - b, _ = strconv.Atoi(token[i+4]) - i += 4 - if r > 127 { - attr |= backgroundRed - } - if g > 127 { - attr |= backgroundGreen - } - if b > 127 { - attr |= backgroundBlue - } - } else { - attr = attr & (w.oldattr & foregroundMask) - } - case n == 49: // reset foreground color. - attr &= foregroundMask - attr |= w.oldattr & backgroundMask - case 90 <= n && n <= 97: - attr = (attr & backgroundMask) - attr |= foregroundIntensity - if (n-90)&1 != 0 { - attr |= foregroundRed - } - if (n-90)&2 != 0 { - attr |= foregroundGreen - } - if (n-90)&4 != 0 { - attr |= foregroundBlue - } - case 100 <= n && n <= 107: - attr = (attr & foregroundMask) - attr |= backgroundIntensity - if (n-100)&1 != 0 { - attr |= backgroundRed - } - if (n-100)&2 != 0 { - attr |= backgroundGreen - } - if (n-100)&4 != 0 { - attr |= backgroundBlue - } - } - procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr)) - } - } - case 'h': - var ci consoleCursorInfo - cs := buf.String() - if cs == "5>" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 0 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?25" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 1 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?1049" { - if w.althandle == 0 { - h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0) - w.althandle = syscall.Handle(h) - if w.althandle != 0 { - handle = w.althandle - } - } - } - case 'l': - var ci consoleCursorInfo - cs := buf.String() - if cs == "5>" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 1 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?25" { - procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - ci.visible = 0 - procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci))) - } else if cs == "?1049" { - if w.althandle != 0 { - syscall.CloseHandle(w.althandle) - w.althandle = 0 - handle = w.handle - } - } - case 's': - procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) - w.oldpos = csbi.cursorPosition - case 'u': - procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) - } - } - - return len(data), nil -} - -type consoleColor struct { - rgb int - red bool - green bool - blue bool - intensity bool -} - -func (c consoleColor) foregroundAttr() (attr word) { - if c.red { - attr |= foregroundRed - } - if c.green { - attr |= foregroundGreen - } - if c.blue { - attr |= foregroundBlue - } - if c.intensity { - attr |= foregroundIntensity - } - return -} - -func (c consoleColor) backgroundAttr() (attr word) { - if c.red { - attr |= backgroundRed - } - if c.green { - attr |= backgroundGreen - } - if c.blue { - attr |= backgroundBlue - } - if c.intensity { - attr |= backgroundIntensity - } - return -} - -var color16 = []consoleColor{ - {0x000000, false, false, false, false}, - {0x000080, false, false, true, false}, - {0x008000, false, true, false, false}, - {0x008080, false, true, true, false}, - {0x800000, true, false, false, false}, - {0x800080, true, false, true, false}, - {0x808000, true, true, false, false}, - {0xc0c0c0, true, true, true, false}, - {0x808080, false, false, false, true}, - {0x0000ff, false, false, true, true}, - {0x00ff00, false, true, false, true}, - {0x00ffff, false, true, true, true}, - {0xff0000, true, false, false, true}, - {0xff00ff, true, false, true, true}, - {0xffff00, true, true, false, true}, - {0xffffff, true, true, true, true}, -} - -type hsv struct { - h, s, v float32 -} - -func (a hsv) dist(b hsv) float32 { - dh := a.h - b.h - switch { - case dh > 0.5: - dh = 1 - dh - case dh < -0.5: - dh = -1 - dh - } - ds := a.s - b.s - dv := a.v - b.v - return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) -} - -func toHSV(rgb int) hsv { - r, g, b := float32((rgb&0xFF0000)>>16)/256.0, - float32((rgb&0x00FF00)>>8)/256.0, - float32(rgb&0x0000FF)/256.0 - min, max := minmax3f(r, g, b) - h := max - min - if h > 0 { - if max == r { - h = (g - b) / h - if h < 0 { - h += 6 - } - } else if max == g { - h = 2 + (b-r)/h - } else { - h = 4 + (r-g)/h - } - } - h /= 6.0 - s := max - min - if max != 0 { - s /= max - } - v := max - return hsv{h: h, s: s, v: v} -} - -type hsvTable []hsv - -func toHSVTable(rgbTable []consoleColor) hsvTable { - t := make(hsvTable, len(rgbTable)) - for i, c := range rgbTable { - t[i] = toHSV(c.rgb) - } - return t -} - -func (t hsvTable) find(rgb int) consoleColor { - hsv := toHSV(rgb) - n := 7 - l := float32(5.0) - for i, p := range t { - d := hsv.dist(p) - if d < l { - l, n = d, i - } - } - return color16[n] -} - -func minmax3f(a, b, c float32) (min, max float32) { - if a < b { - if b < c { - return a, c - } else if a < c { - return a, b - } else { - return c, b - } - } else { - if a < c { - return b, c - } else if b < c { - return b, a - } else { - return c, a - } - } -} - -var n256foreAttr []word -var n256backAttr []word - -func n256setup() { - n256foreAttr = make([]word, 256) - n256backAttr = make([]word, 256) - t := toHSVTable(color16) - for i, rgb := range color256 { - c := t.find(rgb) - n256foreAttr[i] = c.foregroundAttr() - n256backAttr[i] = c.backgroundAttr() - } -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/noncolorable.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/noncolorable.go deleted file mode 100644 index 9721e16f4b..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-colorable/noncolorable.go +++ /dev/null @@ -1,55 +0,0 @@ -package colorable - -import ( - "bytes" - "io" -) - -// NonColorable hold writer but remove escape sequence. -type NonColorable struct { - out io.Writer -} - -// NewNonColorable return new instance of Writer which remove escape sequence from Writer. -func NewNonColorable(w io.Writer) io.Writer { - return &NonColorable{out: w} -} - -// Write write data on console -func (w *NonColorable) Write(data []byte) (n int, err error) { - er := bytes.NewReader(data) - var bw [1]byte -loop: - for { - c1, err := er.ReadByte() - if err != nil { - break loop - } - if c1 != 0x1b { - bw[0] = c1 - w.out.Write(bw[:]) - continue - } - c2, err := er.ReadByte() - if err != nil { - break loop - } - if c2 != 0x5b { - continue - } - - var buf bytes.Buffer - for { - c, err := er.ReadByte() - if err != nil { - break loop - } - if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { - break - } - buf.Write([]byte(string(c))) - } - } - - return len(data), nil -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/LICENSE b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/LICENSE deleted file mode 100644 index 65dc692b6b..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -Copyright (c) Yasuhiro MATSUMOTO - -MIT License (Expat) - -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. diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/doc.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/doc.go deleted file mode 100644 index 17d4f90ebc..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package isatty implements interface to isatty -package isatty diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_appengine.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_appengine.go deleted file mode 100644 index 9584a98842..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_appengine.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build appengine - -package isatty - -// IsTerminal returns true if the file descriptor is terminal which -// is always false on on appengine classic which is a sandboxed PaaS. -func IsTerminal(fd uintptr) bool { - return false -} - -// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 -// terminal. This is also always false on this environment. -func IsCygwinTerminal(fd uintptr) bool { - return false -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_bsd.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_bsd.go deleted file mode 100644 index 42f2514d13..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_bsd.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build darwin freebsd openbsd netbsd dragonfly -// +build !appengine - -package isatty - -import ( - "syscall" - "unsafe" -) - -const ioctlReadTermios = syscall.TIOCGETA - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - var termios syscall.Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux.go deleted file mode 100644 index 7384cf9916..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build linux -// +build !appengine,!ppc64,!ppc64le - -package isatty - -import ( - "syscall" - "unsafe" -) - -const ioctlReadTermios = syscall.TCGETS - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - var termios syscall.Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go deleted file mode 100644 index 44e5d21302..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_linux_ppc64x.go +++ /dev/null @@ -1,19 +0,0 @@ -// +build linux -// +build ppc64 ppc64le - -package isatty - -import ( - "unsafe" - - syscall "golang.org/x/sys/unix" -) - -const ioctlReadTermios = syscall.TCGETS - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - var termios syscall.Termios - _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) - return err == 0 -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_others.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_others.go deleted file mode 100644 index ff4de3d9a5..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_others.go +++ /dev/null @@ -1,10 +0,0 @@ -// +build !windows -// +build !appengine - -package isatty - -// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 -// terminal. This is also always false on this environment. -func IsCygwinTerminal(fd uintptr) bool { - return false -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_solaris.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_solaris.go deleted file mode 100644 index 1f0c6bf53d..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_solaris.go +++ /dev/null @@ -1,16 +0,0 @@ -// +build solaris -// +build !appengine - -package isatty - -import ( - "golang.org/x/sys/unix" -) - -// IsTerminal returns true if the given file descriptor is a terminal. -// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c -func IsTerminal(fd uintptr) bool { - var termio unix.Termio - err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) - return err == nil -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_windows.go b/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_windows.go deleted file mode 100644 index af51cbcaa4..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mattn/go-isatty/isatty_windows.go +++ /dev/null @@ -1,94 +0,0 @@ -// +build windows -// +build !appengine - -package isatty - -import ( - "strings" - "syscall" - "unicode/utf16" - "unsafe" -) - -const ( - fileNameInfo uintptr = 2 - fileTypePipe = 3 -) - -var ( - kernel32 = syscall.NewLazyDLL("kernel32.dll") - procGetConsoleMode = kernel32.NewProc("GetConsoleMode") - procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") - procGetFileType = kernel32.NewProc("GetFileType") -) - -func init() { - // Check if GetFileInformationByHandleEx is available. - if procGetFileInformationByHandleEx.Find() != nil { - procGetFileInformationByHandleEx = nil - } -} - -// IsTerminal return true if the file descriptor is terminal. -func IsTerminal(fd uintptr) bool { - var st uint32 - r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) - return r != 0 && e == 0 -} - -// Check pipe name is used for cygwin/msys2 pty. -// Cygwin/MSYS2 PTY has a name like: -// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master -func isCygwinPipeName(name string) bool { - token := strings.Split(name, "-") - if len(token) < 5 { - return false - } - - if token[0] != `\msys` && token[0] != `\cygwin` { - return false - } - - if token[1] == "" { - return false - } - - if !strings.HasPrefix(token[2], "pty") { - return false - } - - if token[3] != `from` && token[3] != `to` { - return false - } - - if token[4] != "master" { - return false - } - - return true -} - -// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2 -// terminal. -func IsCygwinTerminal(fd uintptr) bool { - if procGetFileInformationByHandleEx == nil { - return false - } - - // Cygwin/msys's pty is a pipe. - ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) - if ft != fileTypePipe || e != 0 { - return false - } - - var buf [2 + syscall.MAX_PATH]uint16 - r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), - 4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), - uintptr(len(buf)*2), 0, 0) - if r == 0 || e != 0 { - return false - } - - l := *(*uint32)(unsafe.Pointer(&buf)) - return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/LICENSE b/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/LICENSE deleted file mode 100644 index 06ce0c3b51..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -The MIT License (MIT) -Copyright (c) 2013 Mario L. Gutierrez - -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. - diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/ansi.go b/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/ansi.go deleted file mode 100644 index dc0413649e..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/ansi.go +++ /dev/null @@ -1,285 +0,0 @@ -package ansi - -import ( - "bytes" - "fmt" - "strconv" - "strings" -) - -const ( - black = iota - red - green - yellow - blue - magenta - cyan - white - defaultt = 9 - - normalIntensityFG = 30 - highIntensityFG = 90 - normalIntensityBG = 40 - highIntensityBG = 100 - - start = "\033[" - bold = "1;" - blink = "5;" - underline = "4;" - inverse = "7;" - strikethrough = "9;" - - // Reset is the ANSI reset escape sequence - Reset = "\033[0m" - // DefaultBG is the default background - DefaultBG = "\033[49m" - // DefaultFG is the default foreground - DefaultFG = "\033[39m" -) - -// Black FG -var Black string - -// Red FG -var Red string - -// Green FG -var Green string - -// Yellow FG -var Yellow string - -// Blue FG -var Blue string - -// Magenta FG -var Magenta string - -// Cyan FG -var Cyan string - -// White FG -var White string - -// LightBlack FG -var LightBlack string - -// LightRed FG -var LightRed string - -// LightGreen FG -var LightGreen string - -// LightYellow FG -var LightYellow string - -// LightBlue FG -var LightBlue string - -// LightMagenta FG -var LightMagenta string - -// LightCyan FG -var LightCyan string - -// LightWhite FG -var LightWhite string - -var ( - plain = false - // Colors maps common color names to their ANSI color code. - Colors = map[string]int{ - "black": black, - "red": red, - "green": green, - "yellow": yellow, - "blue": blue, - "magenta": magenta, - "cyan": cyan, - "white": white, - "default": defaultt, - } -) - -func init() { - for i := 0; i < 256; i++ { - Colors[strconv.Itoa(i)] = i - } - - Black = ColorCode("black") - Red = ColorCode("red") - Green = ColorCode("green") - Yellow = ColorCode("yellow") - Blue = ColorCode("blue") - Magenta = ColorCode("magenta") - Cyan = ColorCode("cyan") - White = ColorCode("white") - LightBlack = ColorCode("black+h") - LightRed = ColorCode("red+h") - LightGreen = ColorCode("green+h") - LightYellow = ColorCode("yellow+h") - LightBlue = ColorCode("blue+h") - LightMagenta = ColorCode("magenta+h") - LightCyan = ColorCode("cyan+h") - LightWhite = ColorCode("white+h") -} - -// ColorCode returns the ANSI color color code for style. -func ColorCode(style string) string { - return colorCode(style).String() -} - -// Gets the ANSI color code for a style. -func colorCode(style string) *bytes.Buffer { - buf := bytes.NewBufferString("") - if plain || style == "" { - return buf - } - if style == "reset" { - buf.WriteString(Reset) - return buf - } else if style == "off" { - return buf - } - - foregroundBackground := strings.Split(style, ":") - foreground := strings.Split(foregroundBackground[0], "+") - fgKey := foreground[0] - fg := Colors[fgKey] - fgStyle := "" - if len(foreground) > 1 { - fgStyle = foreground[1] - } - - bg, bgStyle := "", "" - - if len(foregroundBackground) > 1 { - background := strings.Split(foregroundBackground[1], "+") - bg = background[0] - if len(background) > 1 { - bgStyle = background[1] - } - } - - buf.WriteString(start) - base := normalIntensityFG - if len(fgStyle) > 0 { - if strings.Contains(fgStyle, "b") { - buf.WriteString(bold) - } - if strings.Contains(fgStyle, "B") { - buf.WriteString(blink) - } - if strings.Contains(fgStyle, "u") { - buf.WriteString(underline) - } - if strings.Contains(fgStyle, "i") { - buf.WriteString(inverse) - } - if strings.Contains(fgStyle, "s") { - buf.WriteString(strikethrough) - } - if strings.Contains(fgStyle, "h") { - base = highIntensityFG - } - } - - // if 256-color - n, err := strconv.Atoi(fgKey) - if err == nil { - fmt.Fprintf(buf, "38;5;%d;", n) - } else { - fmt.Fprintf(buf, "%d;", base+fg) - } - - base = normalIntensityBG - if len(bg) > 0 { - if strings.Contains(bgStyle, "h") { - base = highIntensityBG - } - // if 256-color - n, err := strconv.Atoi(bg) - if err == nil { - fmt.Fprintf(buf, "48;5;%d;", n) - } else { - fmt.Fprintf(buf, "%d;", base+Colors[bg]) - } - } - - // remove last ";" - buf.Truncate(buf.Len() - 1) - buf.WriteRune('m') - return buf -} - -// Color colors a string based on the ANSI color code for style. -func Color(s, style string) string { - if plain || len(style) < 1 { - return s - } - buf := colorCode(style) - buf.WriteString(s) - buf.WriteString(Reset) - return buf.String() -} - -// ColorFunc creates a closure to avoid computation ANSI color code. -func ColorFunc(style string) func(string) string { - if style == "" { - return func(s string) string { - return s - } - } - color := ColorCode(style) - return func(s string) string { - if plain || s == "" { - return s - } - buf := bytes.NewBufferString(color) - buf.WriteString(s) - buf.WriteString(Reset) - result := buf.String() - return result - } -} - -// DisableColors disables ANSI color codes. The default is false (colors are on). -func DisableColors(disable bool) { - plain = disable - if plain { - Black = "" - Red = "" - Green = "" - Yellow = "" - Blue = "" - Magenta = "" - Cyan = "" - White = "" - LightBlack = "" - LightRed = "" - LightGreen = "" - LightYellow = "" - LightBlue = "" - LightMagenta = "" - LightCyan = "" - LightWhite = "" - } else { - Black = ColorCode("black") - Red = ColorCode("red") - Green = ColorCode("green") - Yellow = ColorCode("yellow") - Blue = ColorCode("blue") - Magenta = ColorCode("magenta") - Cyan = ColorCode("cyan") - White = ColorCode("white") - LightBlack = ColorCode("black+h") - LightRed = ColorCode("red+h") - LightGreen = ColorCode("green+h") - LightYellow = ColorCode("yellow+h") - LightBlue = ColorCode("blue+h") - LightMagenta = ColorCode("magenta+h") - LightCyan = ColorCode("cyan+h") - LightWhite = ColorCode("white+h") - } -} diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/doc.go b/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/doc.go deleted file mode 100644 index 43c217e11d..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/doc.go +++ /dev/null @@ -1,65 +0,0 @@ -/* -Package ansi is a small, fast library to create ANSI colored strings and codes. - -Installation - - # this installs the color viewer and the package - go get -u github.com/mgutz/ansi/cmd/ansi-mgutz - -Example - - // colorize a string, SLOW - msg := ansi.Color("foo", "red+b:white") - - // create a closure to avoid recalculating ANSI code compilation - phosphorize := ansi.ColorFunc("green+h:black") - msg = phosphorize("Bring back the 80s!") - msg2 := phospohorize("Look, I'm a CRT!") - - // cache escape codes and build strings manually - lime := ansi.ColorCode("green+h:black") - reset := ansi.ColorCode("reset") - - fmt.Println(lime, "Bring back the 80s!", reset) - -Other examples - - Color(s, "red") // red - Color(s, "red+b") // red bold - Color(s, "red+B") // red blinking - Color(s, "red+u") // red underline - Color(s, "red+bh") // red bold bright - Color(s, "red:white") // red on white - Color(s, "red+b:white+h") // red bold on white bright - Color(s, "red+B:white+h") // red blink on white bright - -To view color combinations, from terminal - - ansi-mgutz - -Style format - - "foregroundColor+attributes:backgroundColor+attributes" - -Colors - - black - red - green - yellow - blue - magenta - cyan - white - -Attributes - - b = bold foreground - B = Blink foreground - u = underline foreground - h = high intensity (bright) foreground, background - i = inverse - -Wikipedia ANSI escape codes [Colors](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) -*/ -package ansi diff --git a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/print.go b/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/print.go deleted file mode 100644 index 806f436bb3..0000000000 --- a/vendor/github.com/kataras/survey/vendor/github.com/mgutz/ansi/print.go +++ /dev/null @@ -1,57 +0,0 @@ -package ansi - -import ( - "fmt" - "sort" - - colorable "github.com/mattn/go-colorable" -) - -// PrintStyles prints all style combinations to the terminal. -func PrintStyles() { - // for compatibility with Windows, not needed for *nix - stdout := colorable.NewColorableStdout() - - bgColors := []string{ - "", - ":black", - ":red", - ":green", - ":yellow", - ":blue", - ":magenta", - ":cyan", - ":white", - } - - keys := make([]string, 0, len(Colors)) - for k := range Colors { - keys = append(keys, k) - } - - sort.Sort(sort.StringSlice(keys)) - - for _, fg := range keys { - for _, bg := range bgColors { - fmt.Fprintln(stdout, padColor(fg, []string{"" + bg, "+b" + bg, "+bh" + bg, "+u" + bg})) - fmt.Fprintln(stdout, padColor(fg, []string{"+s" + bg, "+i" + bg})) - fmt.Fprintln(stdout, padColor(fg, []string{"+uh" + bg, "+B" + bg, "+Bb" + bg /* backgrounds */, "" + bg + "+h"})) - fmt.Fprintln(stdout, padColor(fg, []string{"+b" + bg + "+h", "+bh" + bg + "+h", "+u" + bg + "+h", "+uh" + bg + "+h"})) - } - } -} - -func pad(s string, length int) string { - for len(s) < length { - s += " " - } - return s -} - -func padColor(color string, styles []string) string { - buffer := "" - for _, style := range styles { - buffer += Color(pad(color+style, 20), color+style) - } - return buffer -} diff --git a/vendor/github.com/satori/go.uuid/LICENSE b/vendor/github.com/satori/go.uuid/LICENSE deleted file mode 100644 index 926d549870..0000000000 --- a/vendor/github.com/satori/go.uuid/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (C) 2013-2018 by Maxim Bublis - -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. diff --git a/vendor/github.com/satori/go.uuid/codec.go b/vendor/github.com/satori/go.uuid/codec.go deleted file mode 100644 index 656892c53e..0000000000 --- a/vendor/github.com/satori/go.uuid/codec.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (C) 2013-2018 by Maxim Bublis -// -// 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. - -package uuid - -import ( - "bytes" - "encoding/hex" - "fmt" -) - -// FromBytes returns UUID converted from raw byte slice input. -// It will return error if the slice isn't 16 bytes long. -func FromBytes(input []byte) (u UUID, err error) { - err = u.UnmarshalBinary(input) - return -} - -// FromBytesOrNil returns UUID converted from raw byte slice input. -// Same behavior as FromBytes, but returns a Nil UUID on error. -func FromBytesOrNil(input []byte) UUID { - uuid, err := FromBytes(input) - if err != nil { - return Nil - } - return uuid -} - -// FromString returns UUID parsed from string input. -// Input is expected in a form accepted by UnmarshalText. -func FromString(input string) (u UUID, err error) { - err = u.UnmarshalText([]byte(input)) - return -} - -// FromStringOrNil returns UUID parsed from string input. -// Same behavior as FromString, but returns a Nil UUID on error. -func FromStringOrNil(input string) UUID { - uuid, err := FromString(input) - if err != nil { - return Nil - } - return uuid -} - -// MarshalText implements the encoding.TextMarshaler interface. -// The encoding is the same as returned by String. -func (u UUID) MarshalText() (text []byte, err error) { - text = []byte(u.String()) - return -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface. -// Following formats are supported: -// "6ba7b810-9dad-11d1-80b4-00c04fd430c8", -// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", -// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" -// "6ba7b8109dad11d180b400c04fd430c8" -// ABNF for supported UUID text representation follows: -// uuid := canonical | hashlike | braced | urn -// plain := canonical | hashlike -// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct -// hashlike := 12hexoct -// braced := '{' plain '}' -// urn := URN ':' UUID-NID ':' plain -// URN := 'urn' -// UUID-NID := 'uuid' -// 12hexoct := 6hexoct 6hexoct -// 6hexoct := 4hexoct 2hexoct -// 4hexoct := 2hexoct 2hexoct -// 2hexoct := hexoct hexoct -// hexoct := hexdig hexdig -// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | -// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | -// 'A' | 'B' | 'C' | 'D' | 'E' | 'F' -func (u *UUID) UnmarshalText(text []byte) (err error) { - switch len(text) { - case 32: - return u.decodeHashLike(text) - case 36: - return u.decodeCanonical(text) - case 38: - return u.decodeBraced(text) - case 41: - fallthrough - case 45: - return u.decodeURN(text) - default: - return fmt.Errorf("uuid: incorrect UUID length: %s", text) - } -} - -// decodeCanonical decodes UUID string in format -// "6ba7b810-9dad-11d1-80b4-00c04fd430c8". -func (u *UUID) decodeCanonical(t []byte) (err error) { - if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' { - return fmt.Errorf("uuid: incorrect UUID format %s", t) - } - - src := t[:] - dst := u[:] - - for i, byteGroup := range byteGroups { - if i > 0 { - src = src[1:] // skip dash - } - _, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup]) - if err != nil { - return - } - src = src[byteGroup:] - dst = dst[byteGroup/2:] - } - - return -} - -// decodeHashLike decodes UUID string in format -// "6ba7b8109dad11d180b400c04fd430c8". -func (u *UUID) decodeHashLike(t []byte) (err error) { - src := t[:] - dst := u[:] - - if _, err = hex.Decode(dst, src); err != nil { - return err - } - return -} - -// decodeBraced decodes UUID string in format -// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format -// "{6ba7b8109dad11d180b400c04fd430c8}". -func (u *UUID) decodeBraced(t []byte) (err error) { - l := len(t) - - if t[0] != '{' || t[l-1] != '}' { - return fmt.Errorf("uuid: incorrect UUID format %s", t) - } - - return u.decodePlain(t[1 : l-1]) -} - -// decodeURN decodes UUID string in format -// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format -// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8". -func (u *UUID) decodeURN(t []byte) (err error) { - total := len(t) - - urn_uuid_prefix := t[:9] - - if !bytes.Equal(urn_uuid_prefix, urnPrefix) { - return fmt.Errorf("uuid: incorrect UUID format: %s", t) - } - - return u.decodePlain(t[9:total]) -} - -// decodePlain decodes UUID string in canonical format -// "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format -// "6ba7b8109dad11d180b400c04fd430c8". -func (u *UUID) decodePlain(t []byte) (err error) { - switch len(t) { - case 32: - return u.decodeHashLike(t) - case 36: - return u.decodeCanonical(t) - default: - return fmt.Errorf("uuid: incorrrect UUID length: %s", t) - } -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface. -func (u UUID) MarshalBinary() (data []byte, err error) { - data = u.Bytes() - return -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. -// It will return error if the slice isn't 16 bytes long. -func (u *UUID) UnmarshalBinary(data []byte) (err error) { - if len(data) != Size { - err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) - return - } - copy(u[:], data) - - return -} diff --git a/vendor/github.com/satori/go.uuid/generator.go b/vendor/github.com/satori/go.uuid/generator.go deleted file mode 100644 index 499dc35fb7..0000000000 --- a/vendor/github.com/satori/go.uuid/generator.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright (C) 2013-2018 by Maxim Bublis -// -// 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. - -package uuid - -import ( - "crypto/md5" - "crypto/rand" - "crypto/sha1" - "encoding/binary" - "fmt" - "hash" - "io" - "net" - "os" - "sync" - "time" -) - -// Difference in 100-nanosecond intervals between -// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). -const epochStart = 122192928000000000 - -type epochFunc func() time.Time -type hwAddrFunc func() (net.HardwareAddr, error) - -var ( - global = newRFC4122Generator() - - posixUID = uint32(os.Getuid()) - posixGID = uint32(os.Getgid()) -) - -// NewV1 returns UUID based on current timestamp and MAC address. -func NewV1() (UUID, error) { - return global.NewV1() -} - -// NewV2 returns DCE Security UUID based on POSIX UID/GID. -func NewV2(domain byte) (UUID, error) { - return global.NewV2(domain) -} - -// NewV3 returns UUID based on MD5 hash of namespace UUID and name. -func NewV3(ns UUID, name string) UUID { - return global.NewV3(ns, name) -} - -// NewV4 returns random generated UUID. -func NewV4() (UUID, error) { - return global.NewV4() -} - -// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. -func NewV5(ns UUID, name string) UUID { - return global.NewV5(ns, name) -} - -// Generator provides interface for generating UUIDs. -type Generator interface { - NewV1() (UUID, error) - NewV2(domain byte) (UUID, error) - NewV3(ns UUID, name string) UUID - NewV4() (UUID, error) - NewV5(ns UUID, name string) UUID -} - -// Default generator implementation. -type rfc4122Generator struct { - clockSequenceOnce sync.Once - hardwareAddrOnce sync.Once - storageMutex sync.Mutex - - rand io.Reader - - epochFunc epochFunc - hwAddrFunc hwAddrFunc - lastTime uint64 - clockSequence uint16 - hardwareAddr [6]byte -} - -func newRFC4122Generator() Generator { - return &rfc4122Generator{ - epochFunc: time.Now, - hwAddrFunc: defaultHWAddrFunc, - rand: rand.Reader, - } -} - -// NewV1 returns UUID based on current timestamp and MAC address. -func (g *rfc4122Generator) NewV1() (UUID, error) { - u := UUID{} - - timeNow, clockSeq, err := g.getClockSequence() - if err != nil { - return Nil, err - } - binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) - binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) - binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) - binary.BigEndian.PutUint16(u[8:], clockSeq) - - hardwareAddr, err := g.getHardwareAddr() - if err != nil { - return Nil, err - } - copy(u[10:], hardwareAddr) - - u.SetVersion(V1) - u.SetVariant(VariantRFC4122) - - return u, nil -} - -// NewV2 returns DCE Security UUID based on POSIX UID/GID. -func (g *rfc4122Generator) NewV2(domain byte) (UUID, error) { - u, err := g.NewV1() - if err != nil { - return Nil, err - } - - switch domain { - case DomainPerson: - binary.BigEndian.PutUint32(u[:], posixUID) - case DomainGroup: - binary.BigEndian.PutUint32(u[:], posixGID) - } - - u[9] = domain - - u.SetVersion(V2) - u.SetVariant(VariantRFC4122) - - return u, nil -} - -// NewV3 returns UUID based on MD5 hash of namespace UUID and name. -func (g *rfc4122Generator) NewV3(ns UUID, name string) UUID { - u := newFromHash(md5.New(), ns, name) - u.SetVersion(V3) - u.SetVariant(VariantRFC4122) - - return u -} - -// NewV4 returns random generated UUID. -func (g *rfc4122Generator) NewV4() (UUID, error) { - u := UUID{} - if _, err := g.rand.Read(u[:]); err != nil { - return Nil, err - } - u.SetVersion(V4) - u.SetVariant(VariantRFC4122) - - return u, nil -} - -// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. -func (g *rfc4122Generator) NewV5(ns UUID, name string) UUID { - u := newFromHash(sha1.New(), ns, name) - u.SetVersion(V5) - u.SetVariant(VariantRFC4122) - - return u -} - -// Returns epoch and clock sequence. -func (g *rfc4122Generator) getClockSequence() (uint64, uint16, error) { - var err error - g.clockSequenceOnce.Do(func() { - buf := make([]byte, 2) - if _, err = g.rand.Read(buf); err != nil { - return - } - g.clockSequence = binary.BigEndian.Uint16(buf) - }) - if err != nil { - return 0, 0, err - } - - g.storageMutex.Lock() - defer g.storageMutex.Unlock() - - timeNow := g.getEpoch() - // Clock didn't change since last UUID generation. - // Should increase clock sequence. - if timeNow <= g.lastTime { - g.clockSequence++ - } - g.lastTime = timeNow - - return timeNow, g.clockSequence, nil -} - -// Returns hardware address. -func (g *rfc4122Generator) getHardwareAddr() ([]byte, error) { - var err error - g.hardwareAddrOnce.Do(func() { - if hwAddr, err := g.hwAddrFunc(); err == nil { - copy(g.hardwareAddr[:], hwAddr) - return - } - - // Initialize hardwareAddr randomly in case - // of real network interfaces absence. - if _, err = g.rand.Read(g.hardwareAddr[:]); err != nil { - return - } - // Set multicast bit as recommended by RFC 4122 - g.hardwareAddr[0] |= 0x01 - }) - if err != nil { - return []byte{}, err - } - return g.hardwareAddr[:], nil -} - -// Returns difference in 100-nanosecond intervals between -// UUID epoch (October 15, 1582) and current time. -func (g *rfc4122Generator) getEpoch() uint64 { - return epochStart + uint64(g.epochFunc().UnixNano()/100) -} - -// Returns UUID based on hashing of namespace UUID and name. -func newFromHash(h hash.Hash, ns UUID, name string) UUID { - u := UUID{} - h.Write(ns[:]) - h.Write([]byte(name)) - copy(u[:], h.Sum(nil)) - - return u -} - -// Returns hardware address. -func defaultHWAddrFunc() (net.HardwareAddr, error) { - ifaces, err := net.Interfaces() - if err != nil { - return []byte{}, err - } - for _, iface := range ifaces { - if len(iface.HardwareAddr) >= 6 { - return iface.HardwareAddr, nil - } - } - return []byte{}, fmt.Errorf("uuid: no HW address found") -} diff --git a/vendor/github.com/satori/go.uuid/sql.go b/vendor/github.com/satori/go.uuid/sql.go deleted file mode 100644 index 56759d3905..0000000000 --- a/vendor/github.com/satori/go.uuid/sql.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2013-2018 by Maxim Bublis -// -// 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. - -package uuid - -import ( - "database/sql/driver" - "fmt" -) - -// Value implements the driver.Valuer interface. -func (u UUID) Value() (driver.Value, error) { - return u.String(), nil -} - -// Scan implements the sql.Scanner interface. -// A 16-byte slice is handled by UnmarshalBinary, while -// a longer byte slice or a string is handled by UnmarshalText. -func (u *UUID) Scan(src interface{}) error { - switch src := src.(type) { - case []byte: - if len(src) == Size { - return u.UnmarshalBinary(src) - } - return u.UnmarshalText(src) - - case string: - return u.UnmarshalText([]byte(src)) - } - - return fmt.Errorf("uuid: cannot convert %T to UUID", src) -} - -// NullUUID can be used with the standard sql package to represent a -// UUID value that can be NULL in the database -type NullUUID struct { - UUID UUID - Valid bool -} - -// Value implements the driver.Valuer interface. -func (u NullUUID) Value() (driver.Value, error) { - if !u.Valid { - return nil, nil - } - // Delegate to UUID Value function - return u.UUID.Value() -} - -// Scan implements the sql.Scanner interface. -func (u *NullUUID) Scan(src interface{}) error { - if src == nil { - u.UUID, u.Valid = Nil, false - return nil - } - - // Delegate to UUID Scan function - u.Valid = true - return u.UUID.Scan(src) -} diff --git a/vendor/github.com/satori/go.uuid/uuid.go b/vendor/github.com/satori/go.uuid/uuid.go deleted file mode 100644 index a2b8e2ca2a..0000000000 --- a/vendor/github.com/satori/go.uuid/uuid.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (C) 2013-2018 by Maxim Bublis -// -// 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. - -// Package uuid provides implementation of Universally Unique Identifier (UUID). -// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and -// version 2 (as specified in DCE 1.1). -package uuid - -import ( - "bytes" - "encoding/hex" -) - -// Size of a UUID in bytes. -const Size = 16 - -// UUID representation compliant with specification -// described in RFC 4122. -type UUID [Size]byte - -// UUID versions -const ( - _ byte = iota - V1 - V2 - V3 - V4 - V5 -) - -// UUID layout variants. -const ( - VariantNCS byte = iota - VariantRFC4122 - VariantMicrosoft - VariantFuture -) - -// UUID DCE domains. -const ( - DomainPerson = iota - DomainGroup - DomainOrg -) - -// String parse helpers. -var ( - urnPrefix = []byte("urn:uuid:") - byteGroups = []int{8, 4, 4, 4, 12} -) - -// Nil is special form of UUID that is specified to have all -// 128 bits set to zero. -var Nil = UUID{} - -// Predefined namespace UUIDs. -var ( - NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) - NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) - NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) - NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) -) - -// Equal returns true if u1 and u2 equals, otherwise returns false. -func Equal(u1 UUID, u2 UUID) bool { - return bytes.Equal(u1[:], u2[:]) -} - -// Version returns algorithm version used to generate UUID. -func (u UUID) Version() byte { - return u[6] >> 4 -} - -// Variant returns UUID layout variant. -func (u UUID) Variant() byte { - switch { - case (u[8] >> 7) == 0x00: - return VariantNCS - case (u[8] >> 6) == 0x02: - return VariantRFC4122 - case (u[8] >> 5) == 0x06: - return VariantMicrosoft - case (u[8] >> 5) == 0x07: - fallthrough - default: - return VariantFuture - } -} - -// Bytes returns bytes slice representation of UUID. -func (u UUID) Bytes() []byte { - return u[:] -} - -// Returns canonical string representation of UUID: -// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. -func (u UUID) String() string { - buf := make([]byte, 36) - - hex.Encode(buf[0:8], u[0:4]) - buf[8] = '-' - hex.Encode(buf[9:13], u[4:6]) - buf[13] = '-' - hex.Encode(buf[14:18], u[6:8]) - buf[18] = '-' - hex.Encode(buf[19:23], u[8:10]) - buf[23] = '-' - hex.Encode(buf[24:], u[10:]) - - return string(buf) -} - -// SetVersion sets version bits. -func (u *UUID) SetVersion(v byte) { - u[6] = (u[6] & 0x0f) | (v << 4) -} - -// SetVariant sets variant bits. -func (u *UUID) SetVariant(v byte) { - switch v { - case VariantNCS: - u[8] = (u[8]&(0xff>>1) | (0x00 << 7)) - case VariantRFC4122: - u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) - case VariantMicrosoft: - u[8] = (u[8]&(0xff>>3) | (0x06 << 5)) - case VariantFuture: - fallthrough - default: - u[8] = (u[8]&(0xff>>3) | (0x07 << 5)) - } -} - -// Must is a helper that wraps a call to a function returning (UUID, error) -// and panics if the error is non-nil. It is intended for use in variable -// initializations such as -// var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")); -func Must(u UUID, err error) UUID { - if err != nil { - panic(err) - } - return u -} diff --git a/websocket/connection.go b/websocket/connection.go index 748f8067af..4d1ec8abc8 100644 --- a/websocket/connection.go +++ b/websocket/connection.go @@ -2,6 +2,7 @@ package websocket import ( "bytes" + "errors" "io" "net" "strconv" @@ -580,7 +581,14 @@ func (c *connection) Wait() { c.startReader() } +// ErrAlreadyDisconnected can be reported on the `Connection#Disconnect` function whenever the caller tries to close the +// connection when it is already closed by the client or the caller previously. +var ErrAlreadyDisconnected = errors.New("already disconnected") + func (c *connection) Disconnect() error { + if c == nil || c.disconnected { + return ErrAlreadyDisconnected + } return c.server.Disconnect(c.ID()) } From 6989e152b4310e70da8cb418a7d07cba1c8c971b Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 21 Oct 2018 19:21:23 +0300 Subject: [PATCH 48/91] fix no slash req path --- core/router/trie.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/router/trie.go b/core/router/trie.go index 2d11e25776..9e98efbb3a 100644 --- a/core/router/trie.go +++ b/core/router/trie.go @@ -168,11 +168,12 @@ func (tr *trie) insert(path, routeName string, handlers context.Handlers) { func (tr *trie) search(q string, params *context.RequestParams) *trieNode { end := len(q) - n := tr.root - if end == 1 && q[0] == pathSepB { - return n.getChild(pathSep) + + if end == 0 || (end == 1 && q[0] == pathSepB) { + return tr.root.getChild(pathSep) } + n := tr.root start := 1 i := 1 var paramValues []string From 934e668595ae357fae895002fe5d7e2252f1e315 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Sun, 21 Oct 2018 20:56:25 +0300 Subject: [PATCH 49/91] sync with kataras/muxie --- core/router/trie.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/core/router/trie.go b/core/router/trie.go index 9e98efbb3a..b323ac06d1 100644 --- a/core/router/trie.go +++ b/core/router/trie.go @@ -45,7 +45,7 @@ func (tn *trieNode) hasChild(s string) bool { func (tn *trieNode) getChild(s string) *trieNode { if tn.children == nil { - tn.children = make(map[string]*trieNode) + return nil } return tn.children[s] @@ -87,6 +87,7 @@ type trie struct { // if true then it will handle any path if not other parent wildcard exists, // so even 404 (on http services) is up to it, see trie#insert. hasRootWildcard bool + hasRootSlash bool method string // subdomain is empty for default-hostname routes, @@ -117,6 +118,10 @@ func (tr *trie) insert(path, routeName string, handlers context.Handlers) { input := slowPathSplit(path) n := tr.root + if path == pathSep { + tr.hasRootSlash = true + } + var paramKeys []string for _, s := range input { @@ -170,7 +175,15 @@ func (tr *trie) search(q string, params *context.RequestParams) *trieNode { end := len(q) if end == 0 || (end == 1 && q[0] == pathSepB) { - return tr.root.getChild(pathSep) + // fixes only root wildcard but no / registered at. + if tr.hasRootSlash { + return tr.root.getChild(pathSep) + } else if tr.hasRootWildcard { + // no need to going through setting parameters, this one has not but it is wildcard. + return tr.root.getChild(WildcardParamStart) + } + + return nil } n := tr.root From d1b47b1ec65ae77a2ca7485e510386f4a5456ac4 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 22 Oct 2018 13:52:31 +0300 Subject: [PATCH 50/91] A year after, it was time to update benchmarks, .NET Core had many performance improvements over the last year, good job Microsoft's Net Core team and the Open source community --- README.md | 4 +- _benchmarks/README.md | 225 +++++++------- _benchmarks/README_UNIX.md | 61 ---- .../benchmarks_graph_22_october_2018.png | Bin 0 -> 16379 bytes .../benchmarks_graph_22_october_2018_gray.png | Bin 0 -> 16362 bytes .../package-lock.json | 281 +++++++++--------- .../expressjs-throng-sessions/package.json | 4 +- .../expressjs-throng/package-lock.json | 249 ++++++++-------- _benchmarks/expressjs-throng/package.json | 4 +- .../netcore-mvc-templates.csproj | 6 +- _benchmarks/netcore-mvc/netcore-mvc.csproj | 9 +- .../netcore-sessions/netcore-sessions.csproj | 6 +- _benchmarks/netcore/netcore.csproj | 6 +- _benchmarks/screens/1m_requests_expressjs.png | Bin 11581 -> 11885 bytes .../1m_requests_iris-mvc-templates.png | Bin 11085 -> 11340 bytes _benchmarks/screens/1m_requests_iris.png | Bin 11606 -> 11749 bytes .../1m_requests_netcore-mvc-templates.png | Bin 11102 -> 11410 bytes _benchmarks/screens/1m_requests_netcore.png | Bin 11508 -> 11758 bytes .../screens/5m_requests_express-sessions.png | Bin 0 -> 11697 bytes _benchmarks/screens/5m_requests_iris-mvc.png | Bin 11783 -> 11813 bytes .../screens/5m_requests_iris-sessions.png | Bin 11251 -> 11706 bytes _benchmarks/screens/5m_requests_iris.png | Bin 11448 -> 0 bytes .../screens/5m_requests_netcore-mvc.png | Bin 11659 -> 11779 bytes .../5m_requests_netcore-mvc_addmvccore.png | Bin 11789 -> 0 bytes .../screens/5m_requests_netcore-sessions.png | Bin 11580 -> 11671 bytes _benchmarks/screens/hardware_win.png | Bin 0 -> 5675 bytes _benchmarks/screens/unix/system_info_cpu.png | Bin 149092 -> 0 bytes ..._1m_requests_expressjs-throng-sessions.png | Bin 51388 -> 0 bytes .../unix_1m_requests_expressjs-throng.png | Bin 61237 -> 0 bytes .../unix_1m_requests_iris-mvc-templates.png | Bin 51167 -> 0 bytes .../unix/unix_1m_requests_iris-mvc.png | Bin 52824 -> 0 bytes .../unix/unix_1m_requests_iris-sessions.png | Bin 50846 -> 0 bytes .../screens/unix/unix_1m_requests_iris.png | Bin 61567 -> 0 bytes ...unix_1m_requests_netcore-mvc-templates.png | Bin 59043 -> 0 bytes .../unix/unix_1m_requests_netcore-mvc.png | Bin 51319 -> 0 bytes .../unix_1m_requests_netcore-sessions.png | Bin 51395 -> 0 bytes .../screens/unix/unix_1m_requests_netcore.png | Bin 60316 -> 0 bytes ..._5m_requests_expressjs-throng-sessions.png | Bin 60017 -> 0 bytes .../unix/unix_5m_requests_iris-mvc.png | Bin 62578 -> 0 bytes .../unix/unix_5m_requests_iris-sessions.png | Bin 59229 -> 0 bytes .../unix/unix_5m_requests_netcore-mvc.png | Bin 61215 -> 0 bytes .../unix_5m_requests_netcore-sessions.png | Bin 60086 -> 0 bytes .../screens/unix/unix_system_info_ram.png | Bin 155663 -> 0 bytes 43 files changed, 402 insertions(+), 453 deletions(-) delete mode 100644 _benchmarks/README_UNIX.md create mode 100644 _benchmarks/benchmarks_graph_22_october_2018.png create mode 100644 _benchmarks/benchmarks_graph_22_october_2018_gray.png create mode 100644 _benchmarks/screens/5m_requests_express-sessions.png delete mode 100644 _benchmarks/screens/5m_requests_iris.png delete mode 100644 _benchmarks/screens/5m_requests_netcore-mvc_addmvccore.png create mode 100644 _benchmarks/screens/hardware_win.png delete mode 100644 _benchmarks/screens/unix/system_info_cpu.png delete mode 100644 _benchmarks/screens/unix/unix_1m_requests_expressjs-throng-sessions.png delete mode 100644 _benchmarks/screens/unix/unix_1m_requests_expressjs-throng.png delete mode 100644 _benchmarks/screens/unix/unix_1m_requests_iris-mvc-templates.png delete mode 100644 _benchmarks/screens/unix/unix_1m_requests_iris-mvc.png delete mode 100644 _benchmarks/screens/unix/unix_1m_requests_iris-sessions.png delete mode 100644 _benchmarks/screens/unix/unix_1m_requests_iris.png delete mode 100644 _benchmarks/screens/unix/unix_1m_requests_netcore-mvc-templates.png delete mode 100644 _benchmarks/screens/unix/unix_1m_requests_netcore-mvc.png delete mode 100644 _benchmarks/screens/unix/unix_1m_requests_netcore-sessions.png delete mode 100644 _benchmarks/screens/unix/unix_1m_requests_netcore.png delete mode 100644 _benchmarks/screens/unix/unix_5m_requests_expressjs-throng-sessions.png delete mode 100644 _benchmarks/screens/unix/unix_5m_requests_iris-mvc.png delete mode 100644 _benchmarks/screens/unix/unix_5m_requests_iris-sessions.png delete mode 100644 _benchmarks/screens/unix/unix_5m_requests_netcore-mvc.png delete mode 100644 _benchmarks/screens/unix/unix_5m_requests_netcore-sessions.png delete mode 100644 _benchmarks/screens/unix/unix_system_info_ram.png diff --git a/README.md b/README.md index e565563947..839da65823 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ $ go get -u github.com/kataras/iris Iris takes advantage of the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature. You get truly reproducible builds, as this method guards against upstream renames and deletes. -[![Iris vs .NET Core(C#) vs Node.js (Express)](https://iris-go.com/images/benchmark-new-gray.png)](_benchmarks/README_UNIX.md) +[![Iris vs .NET Core(C#) vs Node.js (Express)](_benchmarks/benchmarks_graph_22_october_2018_gray.png)](_benchmarks/README.md) -_Updated at: [Tuesday, 21 November 2017](_benchmarks/README_UNIX.md)_ +_Updated at: [Monday, 22 October 2018](_benchmarks/README.md)_
Benchmarks from third-party source over the rest web frameworks diff --git a/_benchmarks/README.md b/_benchmarks/README.md index 04ee836fc8..d595c98bd2 100644 --- a/_benchmarks/README.md +++ b/_benchmarks/README.md @@ -1,14 +1,55 @@ +# Benchmarks: Monday, 22 October 2018 + +![](benchmarks_graph_22_october_2018.png) + ## Hardware -* Processor: Intel(R) Core(TM) **i7-4710HQ** CPU @ 2.50GHz 2.50GHz -* RAM: **8.00 GB** +![hardware screen](screens/hardware_win.png) ## Software -* OS: Microsoft **Windows** [Version **10**.0.15063], power plan is "High performance" -* HTTP Benchmark Tool: https://github.com/codesenberg/bombardier, latest version **1.1** -* **.NET Core**: https://www.microsoft.com/net/core, latest version **2.0** -* **Iris**: https://github.com/kataras/iris, latest version **8.3** built with [go1.8.3](https://golang.org) +* OS: Microsoft **Windows 10** [Version 1803 (OS Build 17134.345)] +* HTTP Benchmark Tool: https://github.com/codesenberg/bombardier, latest version **1.2.0** +* **Iris [Go]**: https://github.com/kataras/iris, latest version **11.0.0** built with [go1.11.1](https://golang.org) +* **.NET Core (both Kestrel & MVC) [C#]**: https://www.microsoft.com/net/core, latest version **2.1.5** +* **Node.js (express + throng) [Javascript]**: https://nodejs.org/, latest version **10.12.0**, express: https://github.com/expressjs/express latest version **4.16.4** and [throng](https://www.npmjs.com/package/throng) latest version **4.0.0** + +## Results + +* Throughput - `how much data transferred per second`. +* Reqs/sec (Requests Per Second in Average) - `the highest the better, important`. +* Latency - `the smallest the better, important`. +* Time To Complete - `the smallest the better, important`. + +| Name | Throughput | Reqs/sec | Latency | Time To Complete | Total Requests | +|-------|:-----------|:--------|:-------------|---------|------| +| Iris | 24.41MB/s | **131268** | **0.95ms** | **7s** | 1000000 | +| .NET Core (Kestrel) | 19.95MB/s | 110150 | 1.13ms | 9s | 1000000 | +| Expressjs| 18.60MB/s | 70352 | 1.80ms | 14s | 1000000 | +| Iris with Sessions | 25.32MB/s | **82477** | **1.53ms** | **1m1s** | 5000000 | +| .NET Core (Kestrel) with Sessions | 28.45MB/s | 61946 | 2.06ms | 1m1s | 5000000 | +| Expressjs with Sessions | 12.81MB/s | 46962 | 2.53mss | 1m42s | 5000000 | +| Iris MVC | 21.27MB/s | **114378** | **1.09ms** | **43s** | 5000000 | +| .Net Core MVC | 18.19MB/s | 82608 | 1.51ms | 1m | 5000000 | +| Expressjs MVC | - | - | - | - | - | +| Iris MVC with Templates | 306.60MB/s | **42501** | **2.94ms** | **23s** | 1000000 | +| .Net Core MVC with Templates | 188.01MB/s | 26051 | 4.78ms | 38s | 1000000 | +| Expressjs MVC with Templates | - | - | - | - | - | + +Go ahead and read the rest of the page to learn how you can reproduce the benchmarks. Don't be afraid! It's actually very easy, you can do things like that as well! + +## Old results (August of 2017) + + **Go vs .NET Core in terms of HTTP performance (Sa, 19 August 2017)** + +- https://medium.com/@kataras/go-vs-net-core-in-terms-of-http-performance-7535a61b67b8 +- https://dev.to/kataras/go-vsnet-core-in-terms-of-http-performance + +**Iris Go vs .NET Core Kestrel in terms of HTTP performance (Mo, 21 August 2017)** + +- https://medium.com/@kataras/iris-go-vs-net-core-kestrel-in-terms-of-http-performance-806195dc93d5 + +**Thank you all** for the 100% green feedback, have fun! # .NET Core MVC vs Iris MVC @@ -22,7 +63,7 @@ We will compare two identical things here, in terms of application, the expected ```bash $ cd netcore-mvc -$ dotnet run -c Release +$ dotnet run --urls=http://localhost:5000 -c Release Hosting environment: Production Content root path: C:\mygopath\src\github.com\kataras\iris\_benchmarks\netcore-mvc Now listening on: http://localhost:5000 @@ -31,16 +72,16 @@ Application started. Press Ctrl+C to shut down. ```bash $ bombardier -c 125 -n 5000000 http://localhost:5000/api/values/5 -Bombarding http://localhost:5000/api/values/5 with 5000000 requests using 125 connections - 5000000 / 5000000 [=====================================================================================] 100.00% 2m3s +Bombarding http://localhost:5000/api/values/5 with 5000000 request(s) using 125 connection(s) + 5000000 / 5000000 [=====================================================================================] 100.00% 1m0s Done! Statistics Avg Stdev Max - Reqs/sec 40226.03 8724.30 161919 - Latency 3.09ms 1.40ms 169.12ms + Reqs/sec 82608.44 4072.64 96896.66 + Latency 1.51ms 255.49us 235.36ms HTTP codes: 1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0 others - 0 - Throughput: 8.91MB/s + Throughput: 18.19MB/s ``` ### Iris MVC @@ -54,16 +95,16 @@ Application started. Press CTRL+C to shut down. ```bash $ bombardier -c 125 -n 5000000 http://localhost:5000/api/values/5 -Bombarding http://localhost:5000/api/values/5 with 5000000 requests using 125 connections - 5000000 / 5000000 [======================================================================================] 100.00% 47s +Bombarding http://localhost:5000/api/values/5 with 5000000 request(s) using 125 connection(s) + 5000000 / 5000000 [======================================================================================] 100.00% 43s Done! Statistics Avg Stdev Max - Reqs/sec 105643.81 7687.79 122564 - Latency 1.18ms 366.55us 22.01ms + Reqs/sec 114378.40 5080.77 135410.87 + Latency 1.09ms 26.25us 19.92ms HTTP codes: 1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0 others - 0 - Throughput: 19.65MB/s + Throughput: 21.27MB/s ``` Click [here](screens) to navigate to the screenshots. @@ -77,17 +118,15 @@ Click [here](screens) to navigate to the screenshots. * Memory usage - smaller is better. * LOC (Lines Of Code) - smaller is better. -.NET Core MVC Application, written using 86 lines of code, ran for **2 minutes and 3 seconds** serving **40226.03** requests per second within **3.09ms** latency in average and **169.12ms** max, the memory usage of all these was ~123MB (without the dotnet host). +.NET Core MVC Application, written using 86 lines of code, ran for **1 minute** serving **82608.44** requests per second within **1.51ms** latency in average and **235.36ms** max, the memory usage of all these was ~123MB (without the dotnet host). -Iris MVC Application, written using 27 lines of code, ran for **47 seconds** serving **105643.71** requests per second within **1.18ms** latency in average and **22.01ms** max, the memory usage of all these was ~12MB. +Iris MVC Application, written using 27 lines of code, ran for **43 seconds** serving **114378.40** requests per second within **1.09ms** latency in average and **19.92ms** max, the memory usage of all these was ~12MB. -#### Update: 20 August 2017 +#### Update: 20 August 2017 and benchmarks re-ran at 22 October 2018 As [Josh Clark](https://twitter.com/clarkis117) and [Scott Hanselman‏](https://twitter.com/shanselman)‏ pointed out [on this status](https://twitter.com/shanselman/status/899005786826788865), on .NET Core MVC `Startup.cs` file the line with `services.AddMvc();` can be replaced with `services.AddMvcCore();`. I followed their helpful instructions and re-run the benchmarks. The article now contains the latest benchmark output for the .NET Core application with the change both Josh and Scott noted. -The twitter conversion: https://twitter.com/MakisMaropoulos/status/899113215895982080 - -For those who want to compare with the standard services.AddMvc(); you can see the old output by pressing [here](screens/5m_requests_netcore-mvc.png). +The twitter conversation: https://twitter.com/MakisMaropoulos/status/899113215895982080 ## MVC + Templates @@ -97,7 +136,7 @@ Let’s run one more benchmark, spawn `1000000 requests` but this time we expect ```bash $ cd netcore-mvc-templates -$ dotnet run -c Release +$ dotnet run --urls=http://localhost:5000 -c Release Hosting environment: Production Content root path: C:\mygopath\src\github.com\kataras\iris\_benchmarks\netcore-mvc-templates Now listening on: http://localhost:5000 @@ -106,16 +145,16 @@ Application started. Press Ctrl+C to shut down. ```bash $ bombardier -c 125 -n 1000000 http://localhost:5000 -Bombarding http://localhost:5000 with 1000000 requests using 125 connections - 1000000 / 1000000 [=====================================================================================] 100.00% 1m20s +Bombarding http://localhost:5000 with 1000000 request(s) using 125 connection(s) + 1000000 / 1000000 [======================================================================================] 100.00% 38s Done! Statistics Avg Stdev Max - Reqs/sec 11738.60 7741.36 125887 - Latency 10.10ms 22.10ms 1.97s + Reqs/sec 26051.53 3256.67 42363.32 + Latency 4.78ms 0.93ms 417.39ms HTTP codes: 1xx - 0, 2xx - 1000000, 3xx - 0, 4xx - 0, 5xx - 0 others - 0 - Throughput: 89.03MB/s + Throughput: 188.01MB/s ``` ### Iris MVC with Templates @@ -129,34 +168,26 @@ Application started. Press CTRL+C to shut down. ```bash $ bombardier -c 125 -n 1000000 http://localhost:5000 -Bombarding http://localhost:5000 with 1000000 requests using 125 connections - 1000000 / 1000000 [======================================================================================] 100.00% 37s +Bombarding http://localhost:5000 with 1000000 request(s) using 125 connection(s) + 1000000 / 1000000 [======================================================================================] 100.00% 23s Done! Statistics Avg Stdev Max - Reqs/sec 26656.76 1944.73 31188 - Latency 4.69ms 1.20ms 22.52ms + Reqs/sec 42501.30 1604.82 46023.80 + Latency 2.94ms 81.93us 24.93ms HTTP codes: 1xx - 0, 2xx - 1000000, 3xx - 0, 4xx - 0, 5xx - 0 others - 0 - Throughput: 192.51MB/s + Throughput: 306.60MB/s ``` ### Summary -* Time to complete the `1000000 requests` - smaller is better. -* Reqs/sec - bigger is better. -* Latency - smaller is better -* Memory usage - smaller is better. -* Throughput - bigger is better. - -.NET Core MVC with Templates Application ran for **1 minute and 20 seconds** serving **11738.60** requests per second with **89.03MB/s** within **10.10ms** latency in average and **1.97s** max, the memory usage of all these was ~193MB (without the dotnet host). +.NET Core MVC with Templates Application ran for **38 seconds** serving **26051.53** requests per second with **188.01MB/s** within **4.78ms** latency in average and **417.39ms** max, the memory usage of all these was ~193MB (without the dotnet host). -Iris MVC with Templates Application ran for **37 seconds** serving **26656.76** requests per second with **192.51MB/s** within **1.18ms** latency in average and **22.52ms** max, the memory usage of all these was ~17MB. +Iris MVC with Templates Application ran for **23 seconds** serving **42501.30** requests per second with **306.60MB/s** within **2.94ms** latency in average and **24.93ms** max, the memory usage of all these was ~17MB. # .NET Core (Kestrel) vs Iris -_Monday, 21 August 2017_ - This time we will compare the speed of the “low-level” .NET Core’s server implementation named Kestrel and Iris’ “low-level” handlers, we will test two simple applications, the first will be the same as our previous application but written using handlers and the second test will contain a single route which sets and gets a session value(string) based on a key(string). ## Simple @@ -167,7 +198,7 @@ Spawn `1000000 requests` with 125 different "threads", targeting to a dynamic re ```bash $ cd netcore -$ dotnet run -c Release +$ dotnet run --urls=http://localhost:5000 -c Release Hosting environment: Production Content root path: C:\mygopath\src\github.com\kataras\iris\_benchmarks\netcore Now listening on: http://localhost:5000 @@ -176,16 +207,16 @@ Application started. Press Ctrl+C to shut down. ```bash $ bombardier -c 125 -n 1000000 http://localhost:5000/api/values/5 -Bombarding http://localhost:5000/api/values/5 with 1000000 requests using 125 connections - 1000000 / 1000000 [======================================================================================] 100.00% 10s +Bombarding http://localhost:5000/api/values/5 with 1000000 request(s) using 125 connection(s) + 1000000 / 1000000 [=======================================================================================] 100.00% 9s Done! Statistics Avg Stdev Max - Reqs/sec 97884.57 8699.94 110509 - Latency 1.28ms 682.63us 61.04ms + Reqs/sec 110150.12 8195.73 122486.03 + Latency 1.13ms 178.98us 81.78ms HTTP codes: 1xx - 0, 2xx - 1000000, 3xx - 0, 4xx - 0, 5xx - 0 others - 0 - Throughput: 17.73MB/s + Throughput: 19.95MB/s ``` ### Iris @@ -199,22 +230,22 @@ Application started. Press CTRL+C to shut down. ```bash $ bombardier -c 125 -n 1000000 http://localhost:5000/api/values/5 -Bombarding http://localhost:5000/api/values/5 with 1000000 requests using 125 connections - 1000000 / 1000000 [=======================================================================================] 100.00% 8s +Bombarding http://localhost:5000/api/values/5 with 1000000 request(s) using 125 connection(s) + 1000000 / 1000000 [=======================================================================================] 100.00% 7s Done! Statistics Avg Stdev Max - Reqs/sec 117917.79 4437.04 125614 - Latency 1.06ms 278.12us 19.03ms + Reqs/sec 131268.51 5757.43 141530.72 + Latency 0.95ms 62.10us 19.92ms HTTP codes: 1xx - 0, 2xx - 1000000, 3xx - 0, 4xx - 0, 5xx - 0 others - 0 - Throughput: 21.93MB/s + Throughput: 24.41MB/s ``` ### Node.js (Express) ```bash -$ cd expressjs +$ cd expressjs-throng $ npm install $ node app.js Now listening on: http://localhost:5000 @@ -223,31 +254,25 @@ Application started. Press CTRL+C to shut down. ```bash $ bombardier -c 125 -n 1000000 http://localhost:5000/api/values/5 -Bombarding http://localhost:5000/api/values/5 with 1000000 requests using 125 connections - 1000000 / 1000000 [=======================================================================================] 100.00% 1m25s +Bombarding http://localhost:5000/api/values/5 with 1000000 request(s) using 125 connection(s) + 1000000 / 1000000 [======================================================================================] 100.00% 14s Done! Statistics Avg Stdev Max - Reqs/sec 11665.30 628.41 21978 - Latency 10.72ms 1.45ms 112.10ms + Reqs/sec 70352.00 10947.26 115334.47 + Latency 1.80ms 1.39ms 206.45ms HTTP codes: 1xx - 0, 2xx - 1000000, 3xx - 0, 4xx - 0, 5xx - 0 others - 0 - Throughput: 3.14MB/s + Throughput: 18.60MB/s ``` ### Summary -* Time to complete the `1000000 requests` - smaller is better. -* Reqs/sec - bigger is better. -* Latency - smaller is better -* Throughput - bigger is better. -* LOC (Lines Of Code) - smaller is better. - -.NET Core (Kestrel) Application written using **63 code of lines** ran for **10 seconds** serving **97884.57** requests per second with **17.73MB/s** within **1.28ms** latency in average and **61.04ms** max. +.NET Core (Kestrel) Application written using **63 code of lines** ran for **9 seconds** serving **110150.12** requests per second with **19.95MB/s** within **1.13ms** latency in average and **81.78ms** max. -Iris Application written using **14 code of lines** ran for **8 seconds** serving **117917.79** requests per second with **21.93MB/s** within **1.06ms** latency in average and **19.03ms** max. +Iris Application written using **14 code of lines** ran for **7 seconds** serving **131268.51** requests per second with **24.41MB/s** within **0.95ms** latency in average and **19.92ms** max. -Node.js (Express) Application written using **12 code of lines** ran for **1 minute and 25 seconds** serving **11665.30** requests per second with **3.14MB/s** within **10.72ms** latency in average and **112.10ms** max. +Node.js (Express) Application written using **12 code of lines** ran for **14 seconds** serving **70352** requests per second with **18.60MB/s** within **1.80ms** latency in average and **206.45ms** max. ## Sessions @@ -257,7 +282,7 @@ Spawn `5000000 requests` with 125 different "threads" targeting a static request ```bash $ cd netcore-sessions -$ dotnet run -c Release +$ dotnet run --urls=http://localhost:5000 -c Release Hosting environment: Production Content root path: C:\mygopath\src\github.com\kataras\iris\_benchmarks\netcore-sessions Now listening on: http://localhost:5000 @@ -266,16 +291,16 @@ Application started. Press Ctrl+C to shut down. ```bash $ bombardier -c 125 -n 5000000 http://localhost:5000/setget -Bombarding http://localhost:5000/setget with 5000000 requests using 125 connections - 5000000 / 5000000 [====================================================================================] 100.00% 2m40s +Bombarding http://localhost:5000/setget with 5000000 request(s) using 125 connection(s) + 5000000 / 5000000 [=====================================================================================] 100.00% 1m1s Done! Statistics Avg Stdev Max - Reqs/sec 31844.77 13856.19 253746 - Latency 4.02ms 15.57ms 0.96s + Reqs/sec 61946.59 13164.30 100166.94 + Latency 2.06ms 2.42ms 781.91ms HTTP codes: 1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0 others - 0 - Throughput: 14.51MB/s + Throughput: 28.45MB/s ``` ### Iris with Sessions @@ -289,22 +314,22 @@ Application started. Press CTRL+C to shut down. ```bash $ bombardier -c 125 -n 5000000 http://localhost:5000/setget -Bombarding http://localhost:5000/setget with 5000000 requests using 125 connections - 5000000 / 5000000 [====================================================================================] 100.00% 1m15s +Bombarding http://localhost:5000/setget with 5000000 request(s) using 125 connection(s) + 5000000 / 5000000 [=====================================================================================] 100.00% 1m1s Done! Statistics Avg Stdev Max - Reqs/sec 66749.70 32110.67 110445 - Latency 1.88ms 9.13ms 1.94s + Reqs/sec 82477.76 29886.37 166399.36 + Latency 1.53ms 462.79us 286.23ms HTTP codes: 1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0 others - 0 - Throughput: 20.65MB/s + Throughput: 25.32MB/s ``` ### Node.js (Express) with Sessions ```bash -$ cd expressjs-sessions +$ cd expressjs-throng-sessions $ npm install $ node app.js Now listening on: http://localhost:5000 @@ -313,42 +338,24 @@ Application started. Press CTRL+C to shut down. ```bash $ bombardier -c 125 -n 5000000 http://localhost:5000/setget -Bombarding http://localhost:5000/setget with 5000000 requests using 125 connections - 5000000 / 5000000 [====================================================================================] 100.00% 15m47s +Bombarding http://localhost:5000/setget with 5000000 request(s) using 125 connection(s) + 5000000 / 5000000 [====================================================================================] 100.00% 1m42s Done! Statistics Avg Stdev Max - Reqs/sec 5634.27 2317.30 9945 - Latency 22.17ms 8.19ms 119.08ms + Reqs/sec 46962.17 10028.21 104302.48 + Latency 2.53ms 1.45ms 368.01ms HTTP codes: 1xx - 0, 2xx - 5000000, 3xx - 0, 4xx - 0, 5xx - 0 others - 0 - Throughput: 1.48MB/s + Throughput: 12.81MB/s ``` ### Summary -* Time to complete the `5000000 requests` - smaller is better. -* Reqs/sec - bigger is better. -* Latency - smaller is better -* Throughput - bigger is better. - -.NET Core with Sessions Application ran for **2 minutes and 40 seconds** serving **31844.77** requests per second with **14.51MB/s** within **4.02ms** latency in average and **0.96s** max. - -Iris with Sessions Application ran for **1 minute and 15 seconds** serving **66749.70** requests per second with **20.65MB/s** within **1.88ms** latency in average and **1.94s** max. +.NET Core with Sessions Application ran for **1 minute and 1 second** serving **61946.59** requests per second with **28.45MB/s** within **2.06ms** latency in average and **781.91ms** max. -Node.js (Express) with Sessions Application ran for **15 minutes and 47 seconds** serving **5634.27** requests per second with **1.48MB/s** within **22.17ms** latency in average and **119.08ms** max. +Iris with Sessions Application ran for **1 minute and 1 second** serving **82477.76** requests per second with **25.32MB/s** within **1.53ms** latency in average and **286.23ms** max. -> Click [here](screens) to navigate to the screenshots. - -### Articles - - **Go vs .NET Core in terms of HTTP performance (Sa, 19 August 2017)** - -- https://medium.com/@kataras/go-vs-net-core-in-terms-of-http-performance-7535a61b67b8 -- https://dev.to/kataras/go-vsnet-core-in-terms-of-http-performance - -**Iris Go vs .NET Core Kestrel in terms of HTTP performance (Mo, 21 August 2017)** - -- https://medium.com/@kataras/iris-go-vs-net-core-kestrel-in-terms-of-http-performance-806195dc93d5 +Node.js (Express) with Sessions Application ran for **1 minute and 42 seconds** serving **46962.17** requests per second with **12.81MB/s** within **2.53ms** latency in average and **368.01ms** max. -**Thank you all** for the 100% green feedback, have fun! \ No newline at end of file +> Click [here](screens) to navigate to the screenshots. \ No newline at end of file diff --git a/_benchmarks/README_UNIX.md b/_benchmarks/README_UNIX.md deleted file mode 100644 index a7e87d2cb1..0000000000 --- a/_benchmarks/README_UNIX.md +++ /dev/null @@ -1,61 +0,0 @@ -![Iris vs .NET Core(C#) vs Node.js (Express)](https://iris-go.com/images/benchmark-new.png)] - -## Hardware - -* [Processor](screens/unix/system_info_cpu.png): Intel(R) Core(TM) **i7-4710HQ** CPU @ 2.50GHz -* [RAM](screens/unix/system_info_ram.png): **8.00 GB** - -## Software - -* OS: Linux **Ubuntu** [Version **17.10**] with latest kernel version **4.14.0-041400-generic x86_64 GNU/Linux** -* HTTP Benchmark Tool: https://github.com/codesenberg/bombardier, latest version **1.1** -* **Iris [Go]**: https://github.com/kataras/iris, latest version **8.5.7** built with [go1.9.2](https://golang.org) -* **.NET Core [C#]**: https://www.microsoft.com/net/core, latest version **2.0.2** -* **Node.js (express + throng) [Javascript]**: https://nodejs.org/, latest version **9.2.0**, express: https://github.com/expressjs/express latest version **4.16.0** and [throng](https://www.npmjs.com/package/throng) latest version **4.0.0** - -Go ahead to the [README.md](README.md) and read how you can reproduce the benchmarks. Don't be scary it's actually very easy, you can do these things as well! - -## Results - -* Throughput - `bigger is better`. -* Reqs/sec (Requests Per Second in Average) - `bigger is better`. -* Latency - `smaller is better`. -* Time To Complete - `smaller is better`. -* Total Requests in this fortune are all 1 million, in order to be easier to do the graph later on. - -### Native - -| Name | Throughput | Reqs/sec | Latency | Time To Complete | Total Requests | -|-------|:-----------|:--------|:-------------|---------|------| -| Iris | **29.31MB/s** | 157628 | 791.58us | 6s | 1000000 | -| Kestrel | **25.28MB/s** | 139642 | 0.89ms | 7s | 1000000 | -| Node.js | **13.69MB/s** | 50907 | 2.45ms | 19s | 1000000 | -| Iris with Sessions | **22.37MB/s** | 71922 | 1.74ms | 14s | 1000000 | -| Kestrel with Sessions | **14.51MB/s** | 31102 | 4.02ms | 32s | 1000000 | -| Node.js with Sessions | **5.08MB/s** | 19358 | 6.48ms | 51s | 1000000 | - -> each test has its own screenshot, click [here](screens/unix) to explore - -### MVC (Model View Controller) - -| Name | Throughput | Reqs/sec | Latency | Time To Complete | Total Requests | -|-------|:-----------|:--------|:-------------|---------|------| -| Iris MVC | **26.39MB/s** | 141868 | 0.88ms | 7s | 1000000 | -| .Net Core MVC | **11.99MB/s** | 54418 | 2.30ms | 18s | 1000000 | -| - | - | - | - | - | - | -| Iris MVC with Templates | **136.58MB/s** | 18933 | 6.60ms | 52s | 1000000 | -| .Net Core MVC with Templates | **88.95MB/s** | 12347 | 10.12ms | 1m21s | 1000000 | -| - | - | - | - | - | - | - -> nodejs express does not contain any MVC features - -### Updates - -- 21 November 2017: initial run and publish - -## Articles (ms windows OS) - -- https://hackernoon.com/go-vs-net-core-in-terms-of-http-performance-7535a61b67b8 -- https://hackernoon.com/iris-go-vs-net-core-kestrel-in-terms-of-http-performance-806195dc93d5 - -**Thank you all** for the 100% green feedback, have fun! \ No newline at end of file diff --git a/_benchmarks/benchmarks_graph_22_october_2018.png b/_benchmarks/benchmarks_graph_22_october_2018.png new file mode 100644 index 0000000000000000000000000000000000000000..3a4c9f1eb6ede96914ddbd1463dd12a87fabf463 GIT binary patch literal 16379 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 literal 0 HcmV?d00001 diff --git a/_benchmarks/expressjs-throng-sessions/package-lock.json b/_benchmarks/expressjs-throng-sessions/package-lock.json index cec4bda590..1de893ae76 100644 --- a/_benchmarks/expressjs-throng-sessions/package-lock.json +++ b/_benchmarks/expressjs-throng-sessions/package-lock.json @@ -1,15 +1,15 @@ { "name": "expressjs-app", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { "accepts": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "requires": { - "mime-types": "2.1.17", + "mime-types": "~2.1.18", "negotiator": "0.6.1" } }, @@ -19,20 +19,20 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", "requires": { "bytes": "3.0.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.1", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.15" + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" } }, "bytes": { @@ -41,9 +41,9 @@ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" }, "clone": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", - "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, "content-disposition": { "version": "0.5.2", @@ -79,9 +79,9 @@ } }, "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "destroy": { "version": "1.0.4", @@ -94,9 +94,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "encodeurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "escape-html": { "version": "1.0.3", @@ -109,40 +109,40 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "express": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.0.tgz", - "integrity": "sha1-tRljjk61jnF4yBtJjvIveYyy4lU=", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", "requires": { - "accepts": "1.3.4", + "accepts": "~1.3.5", "array-flatten": "1.1.1", - "body-parser": "1.18.2", + "body-parser": "1.18.3", "content-disposition": "0.5.2", - "content-type": "1.0.4", + "content-type": "~1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "1.1.1", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.1", - "finalhandler": "1.1.0", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.2", - "qs": "6.5.1", - "range-parser": "1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.0", - "serve-static": "1.13.0", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", "setprototypeof": "1.1.0", - "statuses": "1.3.1", - "type-is": "1.6.15", + "statuses": "~1.4.0", + "type-is": "~1.6.16", "utils-merge": "1.0.1", - "vary": "1.1.2" + "vary": "~1.1.2" } }, "express-session": { @@ -154,25 +154,25 @@ "cookie-signature": "1.0.6", "crc": "3.4.4", "debug": "2.6.9", - "depd": "1.1.1", - "on-headers": "1.0.1", - "parseurl": "1.3.2", - "uid-safe": "2.1.5", + "depd": "~1.1.1", + "on-headers": "~1.0.1", + "parseurl": "~1.3.2", + "uid-safe": "~2.1.5", "utils-merge": "1.0.1" } }, "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "requires": { "debug": "2.6.9", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" } }, "forwarded": { @@ -186,27 +186,23 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "version": "1.6.3", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { - "depd": "1.1.1", + "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.3.1" - }, - "dependencies": { - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" } }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "inherits": { "version": "2.0.3", @@ -214,13 +210,13 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ipaddr.js": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", - "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" }, "lodash": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.2.tgz", "integrity": "sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4=" }, "lodash.defaults": { @@ -230,7 +226,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "merge-descriptors": { @@ -249,16 +245,16 @@ "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" }, "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" }, "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "1.30.0" + "mime-db": "~1.37.0" } }, "ms": { @@ -276,14 +272,14 @@ "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-3.2.1.tgz", "integrity": "sha1-p5WNMqikLZEZziWYZWfqLF+WZ3M=", "requires": { - "clone": "1.0.3", - "lodash": "4.17.4" + "clone": "1.0.x", + "lodash": "4.x" }, "dependencies": { "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" } } }, @@ -311,18 +307,18 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "proxy-addr": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", - "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", "requires": { - "forwarded": "0.1.2", - "ipaddr.js": "1.5.2" + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" } }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "random-bytes": { "version": "1.0.0", @@ -335,50 +331,55 @@ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" }, "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "requires": { "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", "unpipe": "1.0.0" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "send": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.0.tgz", - "integrity": "sha1-FjONu5ou3krVe0hCDsO4LY6ApXs=", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", "requires": { "debug": "2.6.9", - "depd": "1.1.1", - "destroy": "1.0.4", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.6.2", + "http-errors": "~1.6.2", "mime": "1.4.1", "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" } }, "serve-static": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.0.tgz", - "integrity": "sha1-gQyR24AOlLoofq5rTgbKq5/cFvE=", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", "requires": { - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.0" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" } }, "session-memory-store": { @@ -386,8 +387,8 @@ "resolved": "https://registry.npmjs.org/session-memory-store/-/session-memory-store-0.2.2.tgz", "integrity": "sha1-1GQ48oTPjg7RVE9v1Zk+4Ud8dlY=", "requires": { - "lodash": "2.4.2", - "node-cache": "3.2.1" + "lodash": "^2.4.2", + "node-cache": "^3.1.0" } }, "setprototypeof": { @@ -396,25 +397,25 @@ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, "throng": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/throng/-/throng-4.0.0.tgz", "integrity": "sha1-mDxroZk7WOroWZmKpof/6I34TBc=", "requires": { - "lodash.defaults": "4.2.0" + "lodash.defaults": "^4.0.1" } }, "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.17" + "mime-types": "~2.1.18" } }, "uid-safe": { @@ -422,7 +423,7 @@ "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", "requires": { - "random-bytes": "1.0.0" + "random-bytes": "~1.0.0" } }, "unpipe": { diff --git a/_benchmarks/expressjs-throng-sessions/package.json b/_benchmarks/expressjs-throng-sessions/package.json index b8e257bc89..d99d23cc0d 100644 --- a/_benchmarks/expressjs-throng-sessions/package.json +++ b/_benchmarks/expressjs-throng-sessions/package.json @@ -1,6 +1,6 @@ { "name": "expressjs-app", - "version": "1.0.0", + "version": "1.0.1", "description": "", "main": "app.js", "scripts": { @@ -9,7 +9,7 @@ "author": "Gerasimos (Makis) Maropoulos ", "license": "ISC", "dependencies": { - "express": "^4.16.0", + "express": "^4.16.4", "express-session": "^1.15.6", "session-memory-store": "^0.2.2", "throng": "4.0.0" diff --git a/_benchmarks/expressjs-throng/package-lock.json b/_benchmarks/expressjs-throng/package-lock.json index 6747dc5e40..ca2ae8b029 100644 --- a/_benchmarks/expressjs-throng/package-lock.json +++ b/_benchmarks/expressjs-throng/package-lock.json @@ -1,15 +1,15 @@ { "name": "expressjs-app", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { "accepts": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "integrity": "sha1-hiRnWMfdbSGmR0/whKR0DsBesh8=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "requires": { - "mime-types": "2.1.17", + "mime-types": "~2.1.18", "negotiator": "0.6.1" } }, @@ -19,20 +19,20 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", "requires": { "bytes": "3.0.0", - "content-type": "1.0.4", + "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "1.1.1", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.15" + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" } }, "bytes": { @@ -69,9 +69,9 @@ } }, "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "destroy": { "version": "1.0.4", @@ -84,9 +84,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "encodeurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz", - "integrity": "sha1-eePVhlU0aQn+bw9Fpd5oEDspTSA=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "escape-html": { "version": "1.0.3", @@ -99,54 +99,54 @@ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "express": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.0.tgz", - "integrity": "sha1-tRljjk61jnF4yBtJjvIveYyy4lU=", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", "requires": { - "accepts": "1.3.4", + "accepts": "~1.3.5", "array-flatten": "1.1.1", - "body-parser": "1.18.2", + "body-parser": "1.18.3", "content-disposition": "0.5.2", - "content-type": "1.0.4", + "content-type": "~1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "1.1.1", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.1", - "finalhandler": "1.1.0", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", "fresh": "0.5.2", "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.2", - "qs": "6.5.1", - "range-parser": "1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.0", - "serve-static": "1.13.0", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", "setprototypeof": "1.1.0", - "statuses": "1.3.1", - "type-is": "1.6.15", + "statuses": "~1.4.0", + "type-is": "~1.6.16", "utils-merge": "1.0.1", - "vary": "1.1.2" + "vary": "~1.1.2" } }, "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "version": "1.1.1", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "requires": { "debug": "2.6.9", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" } }, "forwarded": { @@ -160,27 +160,23 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "version": "1.6.3", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "requires": { - "depd": "1.1.1", + "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.3.1" - }, - "dependencies": { - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" } }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "inherits": { "version": "2.0.3", @@ -188,9 +184,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ipaddr.js": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", - "integrity": "sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A=" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" }, "lodash.defaults": { "version": "4.2.0", @@ -199,7 +195,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "merge-descriptors": { @@ -218,16 +214,16 @@ "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" }, "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" }, "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "1.30.0" + "mime-db": "~1.37.0" } }, "ms": { @@ -259,18 +255,18 @@ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "proxy-addr": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", - "integrity": "sha1-ZXFQT0e7mI7IGAJT+F3X4UlSvew=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", "requires": { - "forwarded": "0.1.2", - "ipaddr.js": "1.5.2" + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" } }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "range-parser": { "version": "1.2.0", @@ -278,50 +274,55 @@ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" }, "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "requires": { "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", "unpipe": "1.0.0" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "send": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.0.tgz", - "integrity": "sha1-FjONu5ou3krVe0hCDsO4LY6ApXs=", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", "requires": { "debug": "2.6.9", - "depd": "1.1.1", - "destroy": "1.0.4", - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "etag": "1.8.1", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.6.2", + "http-errors": "~1.6.2", "mime": "1.4.1", "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" } }, "serve-static": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.0.tgz", - "integrity": "sha1-gQyR24AOlLoofq5rTgbKq5/cFvE=", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", "requires": { - "encodeurl": "1.0.1", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.0" + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" } }, "setprototypeof": { @@ -330,25 +331,25 @@ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" }, "throng": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/throng/-/throng-4.0.0.tgz", "integrity": "sha1-mDxroZk7WOroWZmKpof/6I34TBc=", "requires": { - "lodash.defaults": "4.2.0" + "lodash.defaults": "^4.0.1" } }, "type-is": { - "version": "1.6.15", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.15.tgz", - "integrity": "sha1-yrEPtJCeRByChC6v4a1kbIGARBA=", + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", "requires": { "media-typer": "0.3.0", - "mime-types": "2.1.17" + "mime-types": "~2.1.18" } }, "unpipe": { diff --git a/_benchmarks/expressjs-throng/package.json b/_benchmarks/expressjs-throng/package.json index c5f53166c8..b1d2430a13 100644 --- a/_benchmarks/expressjs-throng/package.json +++ b/_benchmarks/expressjs-throng/package.json @@ -1,6 +1,6 @@ { "name": "expressjs-app", - "version": "1.0.0", + "version": "1.0.1", "description": "", "main": "app.js", "scripts": { @@ -9,7 +9,7 @@ "author": "Gerasimos (Makis) Maropoulos ", "license": "ISC", "dependencies": { - "express": "^4.16.0", + "express": "^4.16.4", "throng": "4.0.0" } } diff --git a/_benchmarks/netcore-mvc-templates/netcore-mvc-templates.csproj b/_benchmarks/netcore-mvc-templates/netcore-mvc-templates.csproj index 74c7d770c5..dde6ee07ec 100644 --- a/_benchmarks/netcore-mvc-templates/netcore-mvc-templates.csproj +++ b/_benchmarks/netcore-mvc-templates/netcore-mvc-templates.csproj @@ -1,15 +1,15 @@ - netcoreapp2.0 + netcoreapp2.1 - + - + diff --git a/_benchmarks/netcore-mvc/netcore-mvc.csproj b/_benchmarks/netcore-mvc/netcore-mvc.csproj index e071cdd521..0e7f839d79 100644 --- a/_benchmarks/netcore-mvc/netcore-mvc.csproj +++ b/_benchmarks/netcore-mvc/netcore-mvc.csproj @@ -1,7 +1,7 @@ - netcoreapp2.0 + netcoreapp2.1 @@ -9,11 +9,12 @@ - + + - + - + diff --git a/_benchmarks/netcore-sessions/netcore-sessions.csproj b/_benchmarks/netcore-sessions/netcore-sessions.csproj index 74c7d770c5..dde6ee07ec 100644 --- a/_benchmarks/netcore-sessions/netcore-sessions.csproj +++ b/_benchmarks/netcore-sessions/netcore-sessions.csproj @@ -1,15 +1,15 @@ - netcoreapp2.0 + netcoreapp2.1 - + - + diff --git a/_benchmarks/netcore/netcore.csproj b/_benchmarks/netcore/netcore.csproj index 74c7d770c5..dde6ee07ec 100644 --- a/_benchmarks/netcore/netcore.csproj +++ b/_benchmarks/netcore/netcore.csproj @@ -1,15 +1,15 @@ - netcoreapp2.0 + netcoreapp2.1 - + - + diff --git a/_benchmarks/screens/1m_requests_expressjs.png b/_benchmarks/screens/1m_requests_expressjs.png index 7d0603e04193f7adb7e24b6457e4c81e487710e8..941e38f8f65e35ed93896c5275a2546d167e558f 100644 GIT binary patch literal 11885 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 B0*C+r literal 11581 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 D1w@jJ diff --git a/_benchmarks/screens/1m_requests_iris-mvc-templates.png b/_benchmarks/screens/1m_requests_iris-mvc-templates.png index 28a23cbf1a114682767f8caae187d81b60cb5131..e94a174cbb574e244736dc7043ce370410815308 100644 GIT binary patch literal 11340 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 diff --git a/_benchmarks/screens/1m_requests_iris.png b/_benchmarks/screens/1m_requests_iris.png index 78617a9a8bfeff1a1cf010ddada539bdc8b4f83e..b4931f72d6e75c86f372fd32158933a0316a59a3 100644 GIT binary patch literal 11749 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 literal 11606 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 diff --git a/_benchmarks/screens/1m_requests_netcore-mvc-templates.png b/_benchmarks/screens/1m_requests_netcore-mvc-templates.png index 46c48672a07a65ec43dd2e3c9530a3b9af61ee3e..324c20d27d90d51050e622b9c5e732ab2ee7f8ab 100644 GIT binary patch literal 11410 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 P1=GHvtC4%n%Kv`=J;jU7 diff --git a/_benchmarks/screens/5m_requests_express-sessions.png b/_benchmarks/screens/5m_requests_express-sessions.png new file mode 100644 index 0000000000000000000000000000000000000000..22886a156a2ac26c32e2295d454836d4958a9c73 GIT binary patch literal 11697 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 diff --git a/_benchmarks/screens/5m_requests_iris-sessions.png b/_benchmarks/screens/5m_requests_iris-sessions.png index 8da528a57c77eea4b56098048fdf9ad17b1a2387..a96f0ac3b8316580141e129c4afd764fa8a56c4e 100644 GIT binary patch literal 11706 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 LO>UKGJ4XE%erjYz literal 11251 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 N@GaAu<@yhw{vR{_R^I>s diff --git a/_benchmarks/screens/5m_requests_iris.png b/_benchmarks/screens/5m_requests_iris.png deleted file mode 100644 index e82f42fe0a36f781d34d8da5778dbfb04397b4d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11448 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 literal 11659 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 M-_h47yKNouKU_U>hyVZp diff --git a/_benchmarks/screens/5m_requests_netcore-sessions.png b/_benchmarks/screens/5m_requests_netcore-sessions.png index 6baeebb39e8bd00a6d6a030b20922e3cf50f3424..3df3c0636314b58197e7e2840775fb87bf6faf78 100644 GIT binary patch literal 11671 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 bY-~9E6lN-@64y!R46-n_F{!-f8uz~dFnMO; literal 11580 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 T%w)9xUU}!1`OVrJE{XpE_~1>O literal 0 HcmV?d00001 diff --git a/_benchmarks/screens/unix/system_info_cpu.png b/_benchmarks/screens/unix/system_info_cpu.png deleted file mode 100644 index df91e815db12862aac5fdc11173b3e9b350d2a53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 149092 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 e(*M>7=WnkHixc9SGmWe}x4~V5Gq^hp?hFpQ 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⁣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 diff --git a/_benchmarks/screens/unix/unix_1m_requests_expressjs-throng.png b/_benchmarks/screens/unix/unix_1m_requests_expressjs-throng.png deleted file mode 100644 index ee0d32625abf7074e4a8305e67259550d78f6d35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61237 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

(ct78VmLwVYDap?7iF?fx}nkf&BcH1Vngv0c*i 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 diff --git a/_benchmarks/screens/unix/unix_1m_requests_iris-mvc-templates.png b/_benchmarks/screens/unix/unix_1m_requests_iris-mvc-templates.png deleted file mode 100644 index 94b5e00047e448f4399e1208044b40d9cfee326f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51167 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 HH}C!zLKZq1 diff --git a/_benchmarks/screens/unix/unix_1m_requests_iris-mvc.png b/_benchmarks/screens/unix/unix_1m_requests_iris-mvc.png deleted file mode 100644 index b4573e0775d2182476aee7b5eed173993683f838..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52824 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 diff --git a/_benchmarks/screens/unix/unix_1m_requests_iris-sessions.png b/_benchmarks/screens/unix/unix_1m_requests_iris-sessions.png deleted file mode 100644 index 66e15cf65a58597edce62d2e5cb3acf443d6f597..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50846 zcmd43cT`hh_caKjq9{cH=}ka7NbjH^p!D90N+%Q{^eQ4Cz4sCUY0`TqNN)i`?;w!S zJ0W!D^7?zf`PR(*Gi%Ks<67u4$$fI~Q_k6EpS=lDRhGlWA;-bMz`*_R{+&7o#=Rp9 z3{0O#4}n)K62j+!f9`{&K4?Ay{&+t!`vLr)%vna)S;N7?+3k~)IfkXZgPl1C*wo3~ z+#YP@;Jl9omB7HD#rW{49o_>#ULhO=f9F20$J z^F+(clxqv5Xcg^u0;xxI|F8bC|0E9<{F(@V`0ujRxbAsk`$<~;hui-~;tL;o>#KoV zZobU$e{Yz7WA-}a-}kP(dW~XlEbUR8r0N_X z^Y}NOeQXe3tJXM8=fE4||IF==w9fcHby*bp=CoY@ri9n3ce!cH)_dZ%B-+3E-&leP z!T}COf>r&L|1(aif1w7*hO=z^+;2jXEv!!Y-$G?+m=0x6iBoVzL5Th{<14SY1)`?} zh0288lM4C&EyBDRQbMkE?`sQkoLrdx-?w%ueIC+tHd@-zKeqheUfX#%(YUmz@5UNI zEJ!VBjaZ%7n+f7;pR2>(6%$#R9U9p2i&QRMTelGSHLDHl?hbO<7UZE7O7`(Wr?#{< zHHk_{NDvW|Xc!wW-sj-Ukk9>mu>5EKwk?1?8T$1BTC#0=&OXQQ7Sr9slaPc&&f3}< zbyTOm1hTP(ZrK!G7x&H0rIkU?@ow#JD22W28`4Z28*BZA=guvBxs@|+#7Aj0!yuH4t@Vk{b7Ilf62>;th$Yh^lstNPWkZ@^Steuk4iA**~VmV)& z7yi6y>I!lQwxY&Op*KN4^@jcN37DRq6?C4EExD9)UC(ekgw$z|CMprvV&4+}HGPe$wOX4Ghs85oMsvm_8cDpGNd$G|mL!&j zMnRzqjV2M!c=*sdc{sn?%LYg zxaZQuM1qd7k-SV`s`-F1qa@eTS|x=@bXlRA<*Lg}rTG(H%b{wsm^v{TwX?aUOv~Pp zEbsHK^{dl4YGe1mG-qzdJ#cu)V7TJx&P*2<^eW2lX50MXQ&Nn!fim_7%Y!Ln#qO6J zE!)%6)8(Epk;xJx1<2RMlO1^4n5A;X7L;bw2>WAu=ZATIJ93FdV2cqyBOw{^+An2b zVhV0JM#4)oDE3>czO6Dv@**M&6;qu>jg5^vdwRx7jYNUbI<{#1KEmTvxIe8h)Y@Gm z5;vMyG+7hi0=tVfgeE4EjjkvrF3BV;D9y*QJlv~Q2-oJ$y20(a;OXh;lT!}nI703q)U0?_2ubb&i_=x5i`mZl>r z3^B3UbD3NCUPikxL6N;*u7mw zNbQUH^>kOZdd_iClwZ8I~5-Q0F+KijcKa zI{Wz$Z$nT+(6~9Csqo73Mq7IzKKGYp+T!BkM^8kD>z2Hg?CtGKcK7y>%2TB`Kme(* z9Oe{om^FKyFJN!z@VbX0RwIuR?b0=;+Pb^gQUr&{WjhvYYU}D19-vUG2|7uWKghcrBI+E5C>JijArv2N!fbF&Q^-3#voY}AZDvx8?9oyXvcn*_?hlh}m$ZOl_ zFE7hvzg%@XS`0QPJ$SOW-sZnFQ;Q2CRaGr?{!X3cp-H!KDpSV1o^J40BqGl z&E}*2B6m*@$E|S|_1HL4F*9arYU&9T|10Xt(;ExdQY~xX9N8Hf3VE)*?g)-rUi8{u zHQW&9G{Tu8gBZ7XZo*jD*mnJ)_L29lP-QB--ktA(_tuxQu!Mw#W73j}+(eU~SEZ8A z4tI9GBjl00q)Q^8=@~K-B@^%#s@iLcDlUEstXv7$L;9NE+#oH^@8Ywr4oF;<8Y}agiB4$=E-8lGmK3uLSEsQ8@n$NqwV$nwvun7F zP{G7tsB+3UH#_@Vl8FZ~5J{dB66&??@K@c|^SIHPp!@jw{p?TP+zg5Cn;oun73J3NU9QvDz#iwIZ0bPJ7JF_1_1$qd9QtT z&cNU*AaG(55E8=d>LReKv1Wh$V(@PEv5#qWGaKEF1GUt8tjfyED{P^Lb<;{sqE=8- zNmyTwIvp3U&<5dDqKUa>t=UzOg+>=sV8?wA3Od>oWy82Tv7=Lo+~7%eiHl{Q0L0$h zC51xb74e&WTHk|ZI&|W8DszxJNiRD+ry$mHmDEwiI5=-oZh$X&{k&0x4Kqvq%R=yeZKJ zpB;RhETVn9Lc5&_)xy*KLW&;2~FhMo7}tKDx} zmAyqSE-)Lxzintu1m&8XH`@=M(LctA_JR~lCOjVG+KYF8)}Bh?%_kpM(ZD_e_CS%T z-^s?aMc+%R?r^evFs(xYG|wIcK2kAx3bKjlVb-|N(pd1^Hja56--MjZ4a=h39Ao+_ zfr+^@T@kByo8&Ixw4FC_-2U>W^R{_mW-A*08Cy zo9o7=@y2NDj-WU{Ux(OI?i#SLaddRRaKDaMYc@Bxl-Jd* zuXKfZ&Lw1K4lTb8aENI2a(9|C8na2bJ53_kN5~U;5s}o;P#m$@x~VxiNvQJIQ+1z& zgM+a6c%rdF4W_t$7z{SiV3^Qzdu2(PkzQU=@ps?P&%g86yPe%#U49d07qx%ef3xkj4!{#{q;*M_7}wmGsTnqv9O;#e=c=p6n}OPt+!5=l1WO zu`$9CnyT)CITdhMcc-ehcAj7lE~<`hZdUik|F5BaNe{E>^=}287Jj`-PSY*q4+w+{ zLnB|=d~3Y0Ced!G)d!W3oVvnRM%h?j&E1GU0W6_XUty4~=R5{A)(~_ zB(Z>+8r&t{V}9T#0$$19qO7k^J}lPs{rVCOpPWdz%O1ORi+?63U+~XjWw*45pSsRs zB?JWAFcCD=3u&mX%T7(sqQUD@$-G|LC4K6VGsX>hV!NSng)ZEx{Na*g)q4O{;fM~7DI(PM`DsSqL zG0C8XNsLgWVS1+HJNdFX5oPk+*QW1Fv^CTXr94tv<#@LC9i(e(Sk5h(lMJ@G`*op% zF1*{n)9B9=G*n!7rvHrUm=vP83Y|ubl4La!_#Lq*jQ7&%y&NqGAQo2J}uPC$X!M`5nn zn$W;^HXMLmXssJMBBF8puejS&PRnikhOLywpp4M|w6By@4p#{dwp2#pC2=vCLrgSf z04SsWI}N9IK0)QW?Ms+hm|-mM?nnaYd-j|Y@GD{$ln$9OK7_yP1TKfS~CP3)RQ;@#U zQx$GILLC?R!lrfmriMv?vSfwOK3%3HB`WA*F51p6oTXu&JW4HbQO?1_$OX*6seS1u zSm*jZ*rXU$HvK(+i?qKmZzOuxFs{@a>o|%k+ZqT*M_Z#)2glp*yuB#_c;dV<;-6&f z+qIm-f@`g&0TQsCZVSStn65NeK&70s8r0gt8-{4EHw7L)er!8mPicr-9DzEV?HR|JBFUXum?C3@F<<=33Y>CF0jMR~KUvk;8%rd@&AhK9dD zu{CAl=b~_8L{p=$WTVdt6mK8*=FeZ%6oPd8Zcfe!fu(-^DOSK@uGVg1ZWxzh(s9r5 z5l|ld-DDglprPoA7tQ;rU39*g50f3J0ixYCzQnuS0^RD3L(k(i1^G}AY;%I~_3LOL zhF5~u%RC`spG$!nf!Az9^&uX4r#}`{=NA42WpiJr)bP{bv}3%EW^{afXEL|x@2Mu& zUHzODwHhN>Uk!(Wfa#CcGd_m=&!0aFIUit{c1N0Sf(C?qwumB?BiGhR2nc*uHbOm z%`VThito3eOX0N?{e?!NZhS(oF8Itub>K-35>%qeK05@7QveY66s-L{vE?b)W}$`r zTF8zXClABhaKRnA-N1770jib}D?T8JR9UZj-c7q$`N7*ozgr^_x4rfeI_bm1!@C;S zd2ck=vqd@>?->DU@7J3@ikUnqMLK0ii%o2}r8xto8KNhps~;UyJ}$Kac;$Su%4_?1A=yaEK48&h)YP8Q%SOJWE$5QMqe0qq5>vY$&;{XB`~fZ) zn&GvyPifsgo*;o7Pn$-8r>d>R&^ym_>{ywZ+v-f^y2GiZ)a0_Se1h7h6tlV+ zV>uD_lZYQ5ClV8H`JF$Z>9JiR=&@5F$qVdhU{Z?89%@-DioK*SOU(dvZ-hlPc@n?j0Up--zkZH89v*zypwtj`YRjgBeT6!CIWkGfGND3yW8x zZdd&aWBY|QgNKAYhet;iCk5QU@^bGGUcLLmG*;;|%io3j6K*npS z?@VK7Wz-RbTgLC!EDzjt&BLQ0r~AtR=q1a=tMjMSeBW=dF5=?j6%B0_Q711d2f+lgpc?3i8h=D-JaT#ZVclVAO0 zjqFr>ME4W%bj?6YRR4lR>M@*_Y;&`@YQ-+oYj6Jwwb`WzwR|{LDx)3^i7G3j`9#w0 z?28#s;DSpbLTSll={s{NnWKUE?OQby-iotWiPXj`ZbGT#B1SPNmAWIVgq>MgI(o*a9zG*G4CZ#aAR$=F^EwyTC=Q3V%7#1HGFVrC8F=Nh=jmj(k z8NIf~P|1U9GDMQy-x(5zv87amsoT7!*WWK65*C&Nsq&J?+3pV zv{#1Xj!;~j^}XPZR*4j}lgO>-H#ax0w191GacvW{QyMQC-@pIxIR%BZqT+6TRDmXI z|G>cJRGDOsTofHIZ$Jxl_Y-Ps;zbkuRGS`nlpm8hGJ`QB|ER>|d>J3Q_PnV{iYft1ErGx@wuQ%wOf@(fiQb(nYVZBr$0x_2(1<#SWtad_g58 zRAYGyN?*RbwwtYwPD@i5uVJpWUy}FKpH*^D(Lhd)>LmUByP1W)1~@l*e#Wj9)Z{Pb z&@QJxqZOV&@)807uowD98({TK{Nj|-1fG+VkFwhl0&}S!mnIR7J>BX*=jL1G#nh@x~?MAcMfQ+CDW+e zWlg|gV;cw*1d7EM?Ddeng{EiEo~d1XepEFHRS0eH0`b%K zLGp>Rg#pBvg}vSwGdp|nO+$6{_fQ;{0H@#F`t7X@0KxtvZ-O1mL;7yTXnamE;ZtQ( zUc-Oj{xfJ;mdYE9Egb%QWr*1}7gSYEEh~p%psK3Mbg}vT{=?JUU%!$ETsYTtYJ9e+ z+@6z?K57f5xk*w;;ymnQDk9f%d;1Afr%aY$yv(n}-y8bVH(k=L^@808c19LDrJ-_7 z15CB;LL)H-2n1R*u85_A+M5n%iepp*6NeaBT1F(@%4lnA2h5eitpFzQ&%nUkGSMT` zIXC2>-cw2;0umBS00GW4qDY1^#j)B#iP_Fi#-bYBCT+dFn>`K#Nk**36(JHCzSr0v z9V}H875P9@rGBYAZo4!|kgy?o(tloo(tuJ>=tEr;!c@TlY| zbaY;pzAHy1-Ewc9&OLtg=sutAQfo#>Fg|7t_!FWZJ>77u^AMLjJU>ZrbJP0jtOLKZ zyPGt^4$R{QF=;pw$D@*X|K<(tp8X!c(o|W%Y(~e%9GnJ#0PrJk3Yc~Z3WFUg%HMuE zIt45o9NdkIIZDMz33c@RrlK`OSw9j|BuK2QvgAKs^wbnkr>&{rCxq#E-f(*9OLPDsk{qEV( z02fOcv*qCmquB8d*mk-+lf%IGb8j@=U?y)cP_Ok33>0ZW5)yExYVGuzTye?BQU)Q= zuK+Jq@wv-D)^v_&YtK@PDB-hq`WDI0<+ZiJ#u`xX(2&K2IQn4x_Bs$C45V}5d@?$B z+umY~U8N+&70tKkVQG$n%F+0YZ;0&e?VW5Aw#}kI%*@QR^z;Eh%m+61_%AzyX0yi_ zLqnYR@kx6dT|n>Ozo%tn45XKf)UCER9fF9;i3+-jxb8f9Gi=;~QSWr_)M6gd#kM0w z>f7pDU+egQ_6{~hQs)`e*-y_--y@i=;{q54gI~MB_%tgmZaOP-eMz|rN!)&a?im^y zD%x|s(bOcG(bFrKAWj!^!J#mqm|t+1*KG6{lLf#{Y+71aRn-&eU|dUBD$NIZVy<0^x`pwDuNpE`M(l!OfopI?eAdjLUzos7TAt7|52y1x9TV z1L#!7*H@w^l147KvVCAc!LlWbE~~S%Ga3Tv+2qZ)K~3q}a+K`(YNLiHCTcuWg7%K{ zRWcY@Swja06(`F~f`OhEK!7<^w%&y>SgzhsufzE!Q-(_M>&}i2EFyr4yDjup%B_6K zu~4ZZ2A>j889v2e=da;fZGrW40I$pc8Ls`t> zt`b0(hjfD7Y{bUJMIG_<@qsB4f#a(JiejzAl3~6tdVr6A@KTTn*8T!4*&YD2Xj6+WJ^PCeN z92CT3)=L1;E*}8uGEKm4q#>Q<5CE_Aw6xJhF)`D#v(IU0hP$4SY%Yj?J*spH#d#7E z7FFd=Q)<{C_3qt$6|ssReR25*Kx!hlD+3llh0C}d89{M|SckCDs9a<&iV(@Xg_o4|EBZl7ea(yS)M#Q1;vW|FTA8x02_HBsBt@YI?M zQP&v{5(gLe55V7EU*fjG;W1$(9WI=f^3Cn-Lu2_WG#BXYo~|$wjC%LOmzbEC_a8od z_QE2?n!nMCk6c|{y#nA%3R}cIPZ$^(Q|RTQqEi!XocCM-o<>`H(%oY#(_%C|RWxav zhox32W>7Ynk_pQW?CScbB!eO(L~#9;1+bG=ENtL++3>l@`FWN)5imQ#zAHkMnYF>ix-Co+GTcOEZG%ZtzV|)Q?(r>dD8}=zU3WAY*Ds1#siLbe0=;C z`?{eOvuRJb=}aZ!oviH9WID(FjP7%6J?R$=}3>Y8_OOnm`D>eNf2#-d&lXw@m zikO(#u)#ZI(dQljH`-MXEkc^f|JV_}8fk68W|g7i=8h?Ay@)L@e;qgLb=WS0M zl24+cso5DrFW1-4cg2@M<9FrnWx3Gvgsb&OL=6WEOJA`;UE`u#f@0!K)NhOL(6PC`GVl$?Ek0|{=dWE zdV+3P{}DEPKg+4AsO9mZ`mes=-Tz((0KM(+1a--$b{QMHbD2BeUbdI!^SOaLY2tH_ zRFCu?U8k;vHy=SZ3u?Kf$mUvDLLJ0yOWx*=d+Dv@KzOp4e(#3aUtJ<^koK`w<~Q7K z>?Y!(0n2+5K-=45Y@sJ2m4N%ynp~1};+XIVtN0=^@fBTeN1x`mL4Pq%_ie6?z8>q@ii_;d`!N z)|LmK&$h5uS#)#`x>EyydwjLNi)^RBSNT;?9S*&6YwGH^v*xz#KjqVU>Y!E0@f2iz z8Ofq=ns)nxNXFtT#n=({QAv2^M}q!q>5-kVH^;k(jYHkw&3aUy5#$_4Vzm`GQtV z3&pCPB?biSW{6}yU7UE4N;MGtn#vXR_7jVI)G71+aHHCf91dWaFh(e&WUIXM zPhIE{nrbCJ_h+ZG^jrwcA9btwnC|&j8qM&XrcE@^Rj%s z@QD)N>bS+92aMPXg&~{bW@~>F#gBW(3+81VT4Q}^@S8u=jeWdZxHm>3rH45m5ux zf5k&S!2Wt`@?g}V_S@S-u%;=9jwMuEJ`Zt_(9mHdG_e&x^Ce|P6;k>0Ip&vnS)KK(INeQdOqU|v+@Yi8?S0a0LQxL9q*584y` z*uGtEa|1=n<>7CSuYXT95T$jCrbTMr+V_#(ti$K5mnK8%1gVkoh9cOR$ie5g`77C0Cw2y^3?9kZCCcqN7~!V zr5C=7m0fF%K&tHUsj?iaDU!s)!vny?(QzUNrO0@Fk(81zOv=M5*)^IxA@TwOwZbm^ zItryH=d$Fp{0zVH#&Zf)%R71NVa1G*n*Ne+j`Qb?pO%4YgZ|}Z?UOI8r?Tg={e@GR zGNvO$Nm(;KWY3*Ogr<`XBP+i=nkV}an~6^$J(3>oPrdu6v@%@u;*A^6} zu-f&NWxOy}&4aW0o2*sS56BY*z1NRmAy)7E0EI5U&lVm=OYx+sslh>~VU16BO|&c`@Rn`+Kq^kK>OQ~&7^dS4l!o`42C2GF=)0mN9HJzd+2 zv=8q;K;>5#r;}v*WzzVaMe3np7Hb7+6Z=s#lKwMQ>*e+f`k6+R$4!OW%n?dtjXfZI>Sy#V!`Qb}9y-?~LMOZN@4{BdfAPB%=Ip?s3*QdOvX zsZ>d-J_01>-uQepRIfRLlwFy5FMts*&LMQ1Pr9AuJRG0*UkAW<1y zv@o-;XaR{(`Nn}q>%?IELPj?Yd+G3u}`pW>RiOixD~ z_Uk-{q%9H3UE5^7a#5RzAU_A8_v*6L(w;76a3kqtiEKf+4v-1n?RhWbX^^brviDO5 zF|nAxEk5VIDvyY5aJ`0%SMxhaDyk~FyZ%HD9FnnTa;LX(W@4BjUsQN5c{DE=k}mc{ zRfghd?~9fRCVEM~Jl{NEv-9_u^jgN&*_)}pOUBH2aolXiTkCA|DJRe46GncjSCJ!hh^)d$Y-G#-S`LjmU3=D z`qFDSXMWHnSX%y{^N|DcF!HdH7e*qRThH#2#hk6YaH36>@Cl--s7jpv;!I6fz58ea zs$r<@-%`TA&ym`HhG{h8Ez~?oh6Yl^w1-G+_NWtZ{0eIPQo!@CYa!$V9tsFuV(H63 zmbvY#bJa`_q_D+MdSF}}FF;$?%rysi%UBOUYwP@5k8#TJxU8cvsp04mh^VJU#C2m~~GuCAx`_4Uzl zai;1pyZwUm{NfcqQpl}M;;z{XZ51!5>$O0^lMc_zN>nHDL+4}J68IN(!=@IoIT4_AR4LW_IQ9A|5 zpxx06Zpl0zI2odDf9Ljnm(LDOy>GAU*47c*ws8`VA0O7-Jo*t4lIO4-!N$BOA+fUr zt(z2q!>2Z9C!iN@K>J%p=7D+cY%4$#Wfv;a8q85}^Jpglv>bXc`K_v|E7d+*R|O4(fIwnrL&k96ip+PEu}^U)y z$Ne55X!s;ve6)x1@t1wqyq=#{$=r%CY)bDQH#f~)38Tt?+>|K=L`hj}*U#^M(rB}? zDH}&J(Y2h?P`lypDt(rvwk&(K#iFJ%#&+u`E*y4NP3b+?#AD~~07Os#a5M6ncxhK| z#eRYzL5e#HTJBq?3;kQ&nIDZjv}AMO%kb`)o_sthHKNGCp6AqQL2)e{$M#R8a#x>~ z=BcKQXiIa8IdLbh6^jL-j&hR9M6tZiUY7^s7u9Ai?@U}+9{0034=;_!v0BvRbI)aS zD~5a`PRRBXwdRwLfbiGcIN*Rk7l6byw2S9FHBQTZoH^STmu&3s!$yp$ggsjyuK`zextrFTY$`1On{q$O45F-KHd&SgL}oZh4s2VKXYA34erB@hhLp z2g$81${AvPg>g*hQwm%a_PhEwSDi02J-4`;uP6Nmx6N8c#`iT~yLvU>`yej-1H9@} zD~w@|uJ#@1B|imuCmw(q5xZ>o~JSsnyl&Tf-OqU+j!h;|-Zv1&cT_n3$N-DKh|> zOxCZH?u6uMkRY<_MPhu#<`s-rApaXr}$aTgv6-F47nClTp^vUKn4_ z+9QgXtY>C`iZa8Mg2=H_snL!>o*`)v;l$LnG;0jU^ z;ps0N++m=Q*`h}p578forr+EX_1dKD-6l+nm5##O3x9Ngn9kMhXB%h6ajyZ>2_JXQ z@JruCn&gh>-0Y?4FPC*aPKP@t)b=ZVn%ti!#D4&;nx4_saX_cq(@f*y>&TTdT1;Ln zW3O`a3F1d^yWGzD?TJ_m2VQ}0gpg7DJzdFsIH3xMuHf4D5@{Ndba{!UHbVwmXQq!~ za0{PvLw?$-s@j#db1R->`?Y##ltD|OK~S>iycIi;8I>+hpIA*hqGoGAJJ)vj$1jp^ zTX?8^d_YRD&G7~>YToPX+AoGi=UsJB#*k5CZ2IS$h>@Tz^+HVmVy*-2f#<%5FEJ)3 z0ru1beC@fa>@0At0eYzpzj~47Q+r`bKhg}P$>z-}zPDpnt#bB$SkA-j`_KeG*#l-7 z#PY|8tqIW)285LJt5l<;LDn8x!T~jEn%s=%PQt{Y@~X00#-1T%%?6iJ&-)LCtj2A| z_xI&cR}As6sJE&93UB>>oh8$ADu^!h&fp;;tO*h@^#ZK~z9%nmA@*7cHUdUvyc$%F zFDLZDlepdGKkvha*VQa=-=I#P@;zf=zOKd=hHZ^mOg#XS`dBic)MnFe?gEY)EDCql zBePHM9Q9BPDMC#>oDTQD+Z3Am-6`b9vQLU5EMeKBJP&nz z>vPRg-K^bIdiiA^xMnAnVLM3hgaGAz^Fn$9X`J@Y``=Ab_5;& z4W`kALcvqY)LuI(hB)|4EB(Z>;gmAyy@m7Bv@&-6B>j?CEG!{EeXoUFwirUQ%fI}U z288NlEEDxjT! zA9UqT=-g8_RxXeb)kVxLNi`5;goXuBE!q#+N)~1e=&0o^Dh0RCc)t7=8|nm<>aj68 zo%mkT+Y1{w<(~V(>6EtR+5ilDzKw4Kre}jD57%Ze(9uT;n_@LMDALMtc1;@4{FEyB zdnr!$%YtO2u81V=TN8X~so6n*k1;68bZq43CwCf?J<@qB0}7C9M#x%SG{|BiA<^ir zQ{(#BL!1K~W)lh#ZaDxIYrDPq&GWS{Pi{PsVZoq@=#&OIWQYLGjr@A#!2=?i{ z41!WNDVj;WqMA?Ng#U>kOhQSjecrt}u6C*J>7ETO-)1SkU1&D_#=_0VgpX_8dsA~h zA_JgPQmW|?3Ey3g#9?H-=?@^=#vbQ&_Kb`1b;mogPgsCS``63dG7w3oNqrpbS$={J zo|g?8n);>S-w||qYt4+htUbIbjVsY zNASWGPt<^7(eu)dTK9~ALeO^ktbzhE0LT*&`LHy=Wrn%$b3kY6K+yv@zuv!lcRh^m zo<#k@-iHHDB8lsrClY4|^nf;^o~ZQlcaw0q(qJL^23vxpf!2*};Hhov|EZxe3cSi1 zr1`$(PdA$;=4SjY{2pIxDRWD6wPjqFk_O7w4W&Ur`|3EcaC?)iV{>^i$j`SWAD;As zSmqKp*mu{+!*us;cC1a^Lf;}l8IEoZToVRd>%DHJ(ODVu@RGjHXV}z0UH#Q*os*La zI!p^sOJy{5ubMS2w4(!<><0K~b@y3yx4@GUK>ZmwmBaajsmNd$i!HVD#0l@SXg5gpnvko%fqB+N~;l5CJuYAs@-{NWbb;*e!4BJ4&jpk;qq-eVacEF zokr~c7&tBJpCsj9rX( zWvWL*;-&sdVyVyN=^*qLv(ark66kqep6(&WR3uAl2G0%&lV5*&+3M?y@y-M~bb;Ci zYSZpI2rNw!;u3;GL_gv5Wyq~rDBx(jpm z&FwO%=|bbg&SFcG+JJ7VeC`UMm|bbxznp3Ii0d1;GY4TF9I+`lXlvlwSnn2&PTSdR zIp#C~WzJE4g+0<2PI>v0U}bV7d#HCi&LnymI4kdL^liX`h`frLDta!aqzDSZ==Ahr za7s*$tdqnJmi|y-A}l#2x%N3+AV`LT^rn{R)EP#)b(n%!2I19x9hys(6yA4^dq|J z)Sd9FwZ6C%!F}IFA2b!U_W_;v^;ItnhR7gxct%DRQd7eZNJJh1#wwYT(vJW+jOqtu z(w+ToOGP)=G+YwHH6Rp}+GnTUah})o70{0{Gc!Aw%UrOXt0k_v=q7ng3W{Q^v71Z0 zq|xpDiup%KL?||zU>H~kVK6ej&m0gS9K!@nFu$M|##}fx(C5Da;nSVLkC0L<`uiD$ zNs_U?w)~09aVvEr-3ty+oT$^?4?m(T*9u|^G_?WSru}58o~gEQ-pTb)(;nbN>)NVv z1Qc&9SlD=<0m;E}2mVsstoa}}K|%IdY%=Z4 zU8a2)z_=4q0($V+j09Mle*iO}gSo6Nx!U)WoB~A!e|0=2Q)NUi2*n1 z%GB;03p=~*0nY_!CF%$k2q1Yr%5;8Kjp0=D2CJeaR{oE{6M0{dA zz!UAqBqquxmEO2tx*Jqe8&OhGsS!0CN0t0n3lP{mpRQl#cCy<$wdAb;m~=rgF~v8B z*effmslRZ6Q;g_5G!;8s?*TMkYk*twIr;PcT!p_cO7gwXnLG0pId}CwY~shGMnE+6 zJBeGX%-9DI(pb!Z6dnVfEMPhcBf)RqVuCG(@12-b3Yw>|0-~1nuwhBliQiEhvo&mh z5l;ED`b)rbP+T()v;la%hA8hmUyEM+5v1Jr+yn)EuU))y65*CtXO96NPw=qhW&95| z9a2D)3&=a9oaAw0bm%e0PBjGG1cx$2duI8I`UZxXgxqQU&bP;Y<>pSLwp!pw&kc+V zptq-@0-w;VU85Hn0m*r3sU4Y+)kha9q^-v5)|PN8!Bh}*B#EMX1*=;USkS-WWN04O z9bN5fv~+B2T(ab?zbC{b@wdVwx~JBoh5WU(wch9jK|mz{XgdZI29iD~Dq?)>r*bPl zo}x+@2Hy#*C2m)EiMpoI8WVzm=)(8HnisJ5GVt>Uj*O@RO8z@_?x0OnZa5`Bjsus& zqQ@midwV<34nm$)(bKSZOMM42c6 zwV1&!4-yb`EjKwpUkL~V0bb*n%uM@3A)OB;^LgYEK?McOku;Ls03pd&zz%e*+$Mc? z<{JX8ycf{T$(7-BZh*-G5YgY6UKkje4#!l|euRgw4T(Bs@!r z3t3opW*Y(k{nb-SzTO=HvN_|t>Y0Iow9!#@z%T?j_SP;?%mNNJspo&YBPn1tCZzqi z8Y`wl>w$C{`|16FrU?vKMpsr>&X2-|w>V4-8v)63^eb_C6Juj#P0edAFBd1LY(UEj z2nuzei7}bVzP_z`h^EHImA$>8Z}-?i_#tNZ z1zvQbe$$dnpbVfqN!E(u*77^h(<@|V<$NVBo-tafUR+XQfZ{;{C!(0&g3ttb zEiUEiT066g*)caT{h#b7-SCNB0_khK0sLlwA4MR8Y*Twh(f?`ug&*uvgv%`s5^hmP4IS zbq1PdzSn%NnEW~hFuF>~JB9u+KR!Boub==L(WEi;!5*=4?wq=M!tpR6DM>?B^=nZP z+gzQ)D$q8~vQ^PMx!x9_-j5B}IXWX1bF@AJ(0tNF!Rs)yTvxz9m`lFhTe zzP#<)7S)b}K`E@Q7iscQ3R2)XMH|ek<9BIY)8uEsW9uIp3hK0P3E|0d;Qa=HY!7NN zO0k5*oY>Hibg&5JxVy^r`0JtL_`aUytC!Cn-zcvxUw?L^^_~3%T8-MG?lv7%(Jz`bWZ5&)iCZ_t{-XKCY$)@+gmF8K( z6B8;=3(6d2-e$;lC5pUzK(aq!BI3!AA2F=YOwh_#%d9hatKjf7CDz*X(&VXeAj#D# z9lZ}j7*m6FwLx74dTazN1}*ee0d@(BZ!muVAp9g%v8>;E!`K+y>GQ&RtK+Zjex7jN zTi_J!(Pbg#w3tjH9x5?9&47HGT2J2xd>yg`chZ(vY-D7^Ajs;rE0`q3gi&&fL@kKaLxz$y7+@Q~j@g(G} zJe%9Q*548Li{cm4-i|ex$$T$bSQM99sDXx7gIXX%c4~ay4bl|~y!6yGHC^EU;P-p$ zgspekFJcq@8Z1y&)0Y$=G*nZI^9p4-Vgdf0Z>cw90ZdVbl{d&DN&}cu!a}`F@<^y_4q7;Bto$R|ro|j+U18Nh(5mnIVzBZg@C? zs*Ohoob|?*d*Ai?49+i)$;A`VXQ|lbObSJCT2iy(k&a1#u3LPuM~#dDBlxlNlr@SkA+#KxK2u0)* zHnt>Ve{zt@U)Ivn+Fhk9uc>LyD*qfB%3fhpg3gy%;Z;*p6TNU7`_n>C9tFv{i|Dlm zJISw?_1RX9u@9!Auwvj&b!X`P^sU)5Y-9T{e(&bZn@??J5BPin(rxYRxEiS%F?R@> z){_Lpn;YD2H&@+zFnKCd9(Gr4euW6k=26@uV0m}|-_VGgJ+z^r`Kwx~$>=(tlbsP_DXwO(EJK6^Hvg@7#mkN>rr~}pgcJ(zg9^F_=VVl z`nkO&h8>q&_}5Tdn%%`<83!z&+Xmnx`~PHWD6@k{t=XLH72tf`FINB%V!)&s7zn0S?Cua^`JS z-<3G^s^G$^fCx6~tzz*`)&PCdgf+awLKV2Vvc0mN_}oFKB&$Tc7M_5e-5?;SOUQom zrI*af%1*m!|)zgd^BfE-@kt(l`3W^+s-`v-uZvTM|Ee5*GXl&K|b=lBhr0L$Y=cXZv^Yx$Cr@xk8 zkA3M<`hfmd+^^BmDEU0)IOC~0kcoqhq}p;UlZ<*%K_-dZDY?4I^pz6FUkRji&ZgP) zOwz_5|55j|&c9Bl*o%JNf3uia#J$!Us*an|$~WrX$;hH|HE8ZGtSG9N0$Cegq(9|O z5taNoebsiO7XSEYr6`TrH8od%;3})(K;10ePzZ-Pylot1biK8s`{JBRn_6d7?Nn;% z_L-MEYV(B>_lu&B%_g)UngYYwr$zLQ=xl@UL;ZnVcQ6orP0k%`EcLGi_YnVK>vpQ> zs~p4118>M`$*T&W@S=8SV1|o0N!Gl7Qlvakh)A0ZfyeI556-p6$?4KUG9#NMb1Hia z{l-)48~6!KbaZrq3tfqe!lsscAT!1bKV*F=Ag~1-4>;;2?=#>?b~=#AYLMUs$_M*x zz15gj)6;7%zJjZB@KOle`s`-w_dpUwc~dSFk)ArZHI+;~YDpq%zHp9`-QsZ1vVLUb z3%N-6StBw=KPcc#HYUdg?cTfwk(>VV`D`eYExBP!bbXx{FQfkc6FWP5 zn$%KViYeKP%~fytRV7trk|d{gR=Zqnb`~K8X4UoUjoTC))M1dCp^K_0`tS*9hlhu! zWT$hn>cqm0Z7igIrujxoH0lo43Lyd4Y;-&#K-rw#r-zl5oO^{FEXCwJJeB<+P5Npw z15vM9#r?e_l?*B_v)j7Vdfdq|nxIhE(Ae1JT|++ZujsI6(oaB~od!bcw>& zL8JDEe$mmcU@v>HG?K5w#B*flySo!^*wV$5dJ=`F9P!-#-8}#iZTxMNki2h1eeMF4epFj`j}?_(w#veEN8@JXqilhbcq} zE#&CCm2MwwHp_tP$?N0CSUHbG(MbL;7cK0zJ6vtr)HGU?@^{l;3Y&qm$^q-g`h*xB zq15liZ>qCh;rM(3fqAu!4vg0t<3FmqJG&>xt6?D+@V$KbbS(4aBBdQFH^s2kmDKKg z#?h46otrmJT(FiL+eN$N@b0b>B}~8V6%5ccWNnK38NHk;nPOyQ6km%=*I%TSWnqIl zUEM$M(%eZ6baHm-%nK%D(rP8=x&LWhNadLdA2#N($9%G$aL7~lK;_JPPu9;%C5cQ0 zM?3Z!Ov8zkV>re$+ZHYW3(I<<7XMdmz47wA1V%a{9Uvu4UW2z{HvAVKKUTDw=?Mj` z;)0#|dGVoQR<$}$jh)57m#Z3cYkuVp&znZ2g%8L zp1WJMkQqlWm-*PZ3>^kg<&A2*)+UyuShib!6n!BAo2PZmQ5oaI^lQ??-IeA|`kzTzts z850ng_8Dax-Cx}Qz=w{Mbz29@OU;R&VuJSZ)?mZS$tl#_GeEh1rE>SP(0)G@a$&7P z%tDq`D46yE71fnX^>_N&^813Mf>;y9bdH2{plm=2hcSoNdhk%t(%!muO{lckqN7(L z$o}f#p4=3<*Ma6r5u7%-H%O3&q?HuZ9D#& zi1{HN&@rA1*=)Jhn=UV+txf4gz%reHpY_c@ye4X-?6x5vS$%HIV&|NyHf4rF(c{90 zW2xqZ-lQnpc1IE#A)dp*yclUs!nD-gbTOSZ<-@_d&m1jrT(E4z=;iFH0|8iBSm8^P z&2V$OOfN+uxX9SeF4&l(Pbs*V>TUlqHxcFqt;W`{$JCSpN;+qbs)7;qG@U{$s zgQ=AffrT;&h`4lWeQmGscZR)&{S7{ZY=B-}V>dpgFVr81k6c?|cX}v*4dGQ)H{d2g zddw9ZoO-ZWB}IJqr(nl5x%kna;vBmz>v^>)j*#$hJvA@~9;^--x_Nm`fUIC{U%4Ki zt6z!}1vUNh@r>4k2M=Bgzy2^b%Kg6N<{Lb@z6aKQnTp|SmG6YCte(otTVAfP^6l^c zX?W+Ntxc}w9?9p=XWhSgtL}u!BugJgrO1>!{W^d4?5=-wbX$q37Fdendd^X--B;r4 zht221hkG3o9d-qpG9`k_K6~di5Cc7<4*8{Zmt1$iQFVr9E2C9)m|ukk6AGu_dNan3Jm8 zCK#}Lnh@`0jlBy%>KI^Blne|EaB;8zx7R@Oy-{@G5RjQIL4Grr(Iowz4!HtiunR&R z+a=m%86_v!c$Ct48dHf#e#HOgos6HUOlmgGW_9)RQ{evxzH9Q6VAPky+0& z6)Rr2?h@x`wYIe2GnhOg=H!4Q!<|2_uDjb447j%&Ioa5Pn}a=gzW2g)gHpj5%zi8R z(@TAg4GomdnjM0I-Nc0EoLj7#n$D!6i@g~h&dy|VnWLzyOAwkApEO1*rw9!N$Iu*{ zse`4MbUcqm=gi;h6Rivu(cdi6XeN)uv8Y`*RktKy*8TzcZ(P#CIsy57urEOW8O#xt zL5mP3wMW<2pFU9liup*HR$27*>&rJLCWtsK0=m1q8RyO=tYyJ7V6LQGZsiNRUZSI; zWuf6%>PdE5M{0GSM0O3akB-MnlEjt7p$SQp^EFxo9YpCjK)`2y?zBePhv2uukp0d3nEaNOf zK#}w|4(&Vj8zUxKtx?XQ^4z@{M?p^eRuCi+!iMrv%}N|Au}|vfTpDhsVPg~1vN15o zjJ5L!l4}{U-Buc=AL~IKAFGruSfxm_Y{tTH2@~&1zvvuN5h#X74<5vJkEI33X+$_u=XuG<*viJn@6jh9gcCl0 zjx|h1iJMg_$n5UwCz}RLAk7bZo3rP7nwHN|vS=tW0Lahx=fsWfIQtdF_^Azun^XBl zM~i{Q8&2cyyzCEct0QLf{hxX>6ao*5h+G=#zoGAkg{{L|!J)VCG{cwgtIU*2!rtAr z;9$TnGLT3TRg8>4EcVfxDt;(74d^rxj5Dmjln>^S>9`cativeA-4lIbtun6S^1HA} zQhqLw*r+0ins)92Etv5tya0VmJq6UrRo+xzs-brK*iB+ zXOfum;g0QBmtCxl4g6OS5IXSAo0K*zJbbGqU{1R`nE}X?IDtKq<&k&$HG}_v?36_P zZ*VN1pfhU3_U`12a~c}d)Bk1KmFOQFoa9*Tdi{SwQl$$X3~X##O?hpd$r5U?=76Lj zJae>no2tH9@2+zC{>?dL*3xP^M-_ig>Aib;rPrA z{=TC;%m%}EF9`^gm%A_;9PcwC{**_OlaphPOh!NMK|+F5zshTo1XC@%$ES*w8yh&` zJ~wp>2;g^!h=@}V?+F2kg#XhSF3w?1gxyi2NQ+OrvkvNot|UWco05`_Vq>*L(Qt2H z-_-*uF0P0>bPDb9I=H|_0&H@dy^kuTS$r-zH$aMjY)Jz);wRFi&l+=>W(MZ&J~g@y z4T4wB8)8JcmzNhSf1|b175lZM5B;YxJWx*gMXaTGZcXsY^tThp^%($U`4EmiLN)`V zS+ghMY-6NqM@unZ-pVU5S`1r3D5{&7o7MrI(3NY~XcD?7Be`uaP7U(gZ7w7Ym-yEP zwT82w&Yh6z(8}k!0Sc67(nFA&m-p0W8|SoAe3z3P5(u%b30>oPm+$7ejS01k|F4%L zS<$B_yF+G+)3Np^9re*?uiqxFt(t9-ec#u{yY{2A>2oNPmap8mdV1cIm)8Vts^RPHwBJA{VtTtag590~*T2***XX{b!5B1dUdKCq8;mm(lanvDW?SWf zRKdAz+6L=XyLb2#%682jlJjch^kj7$B{JiQH8r$y#zC3<#Q0|3T)E2|Vq8^Kwf=IQ zIMzg3M0&nmfViJ2&?LY z`J(1+j%o=q2V#v{ngVIoH+E|cx3wV>Ykzvm$IZis$;#8OFh>0daMx$D7OXX{Sm-(m zipgS&9RgdA5>k7eA@M&a>;BQ?1>md%)>EV~A~?*y0$6vQz^3Y*Hr2gEN$M$C{Y{>k`hzTL+PC?p5Af z@9MqF_fB2aH8oA9*HysfE;)-nf~ker6C`q#pd>f2#~`cshV&S7Vc`%?%P3^-nKMW7 zd8O+Dg@UjLQCjn|sH4P5$q+Jckb42WvRFN=r*FnSrrDB`Nn4_Nhfm(|O}Jvjl{pfg zDJV75m)MqL{do_Z2uqd9ZGL1P)OJJ&>k1CjJLZca+3Q_Cq^S`&JjZS{zQG5JX)HLm zACidmR3N&Y)a2c4A!Lj1!D9jKwTJYnE)i@bIQ#`oJhVwa;m=J;&(Cp2L_}P^a^*e+ zTlkHKo7jZ$3&7J^-jGv}% zcv5TL9v*IP=)fqp{gwIDa0tz)C`ypNiT(^Z2(HZf-0QK&)8+RA{9^vbmjgtHnURt| zBz6^4GJw-z3j7Pa`_FLQH?zM9M9H7x|E8V4#FDY?Rj^hb^Q64oc!DeP`D%B z6|L8k3gk1;S3do6SXlV$r9fA62A*z#%ehOtTT?Dr91tSUO4L@(mtB}c4sjVG@j!6 zr&>uVsZee`@;}}Gjkfzr5W=NOOB>JhCwHF25Z>6w$D^jY}~5COTwpBt{5eDxq_3D}WP4s!xW<>K!rIeKuY9$2jOX;3E# z_(VaQWHNDV&1k6AnWse$+{Y`KmxF)5=v{a})K2^JbXq!mP6IAr(V>3NJN8I$a&}%& z-aiu>8ft|_+yv2)$r{og{ULXGj>eMU(U$)AD^PQ9tzlJ2jGVkP-7~~;COzx9dWtyT zz1W#kJ9D*3>d*ZW2$G-^2@Uq8DEqTaCCVvpV5z5hHeYlzXSLaW$y6+p z6c_&iG4z|TMpHS+&=*}A0$Xb9`|tqvg@$`zf67ND(l1$Erq{Qqpg?2T$vxjLmJCuH zh&Zgi0SIu*{XaszQ)*3u8M1j+jf@Vc6*I#{vumP;jOcrM z`_`{|QdH4<^PpFOSQ#k@ePNqH5x@+D?d2S1i-LR6ur#bi`C^F~> z2HDa(7-l-k#7Gx=((0qQ%bN2e^tI3nARwOc=!)Ro*i4A#Ha;D;P875C{{G!x*g;cp zfUQLl$^>pKf_?^v;q!3G6jl$LQkyt789E@T{fI>X8wJIx1c+2KZ9f`Ee@Y=x+DuFNJ@x{fA@mz zW3b9gk5El|=(RDAU?I+}-5yAFZ9OFI^$oJf>ko;kamfSV9}SkI_9!!$?h@5n2gED* z_!E0Ejb}(r4sU)^u>zr%ec%c zdI}$Ab!b0fVIiZV^BOg%2<14j9(6Q412_b;!C-t{T|nS}14JI4sIckXfB-S+9*f6~ zjs6s&Y=#EzTWV^mkR23o*WLH`PfC7BlXU1>vr8%sJ9{Y3s3Xot*qkO-89o|}l-|64 zUHtx3ZzQLAU26uLDN1JZwgo4rEfg~l08CD^giS?71%#Hvd}$g&;B>U_9)VZbkmafo z;5E6XxhXPfvR>X)X{%xuNdT2E4<87XG*gP05AnzpL<*TK$zBW%EfS1~hJHa*)ceWYxmnsm6ABuCHYaI_=GbGYV6qdwG=Cg&rkWF4p>gcrG1hVHRGL8jf$;fq)i-c|tOM}wm zrz7w2M~W!zMgdaC&ma_hPVr@bF|7x}gb37+fG7F@;5nh;={rt{xW&kJsNvUavk8P2 z_wUZTGFVXGm_i)ISH4a>Q?F;L=^rF}S}9bp(r^xVgNg3UiroT*3;<{fk9U`{SUC7o z;IZjQHdf;~UjM>K|LzX@p6*&a_QK5ga}Rw(7Vk(6V4hU!>*{{h8TmPZ&c3>sMRT9w zlh5hVr@}lQ(^>t>_aza4aF%ax(qQfqeFipQW`v021-%QS)~;}*4lGmW;RqtYI$sG2 zezkz!AHCmS1|+24O6Q*v}ACgCU`z6XWx*MJ<4f zAzUzUbR4x?LIA>i1DXODOd)OI1~>bDy|vg!eS!h zSAF#RhnlVq+QVogpWwbHf5G6(T*fc7qV6v?A(6dQQVA~b83C_oXl<-+czvL#w~dHS zU#4BLD5apG5z;z31AYPAYeGVgBY4GTSA=bLmoGrCLs?sLOCUk(v|>mq6{ygXqav4| zIA{1RQLJZm%@_J#Y$V%DNlEoOlw~$-_xIn`Y%4`I){WGhBQ;b`N9en`yH6aM0|RHm zuN4}0Hx9Id*G!e8+i2+9#_POpom>leR+z_#J`gx94Vurzz=qiUl+-cz8w3>vE;VMt zzKpTHMJS~zFxqZ@2D0U%pkV*#{E8dt;*_m=u=!x@JlFEBbvtl^_b52u&AApJk&$<2 z<^-U3{d?jS8Vm@3`0$;zE;)!_LKG)8*Snl@ZOZM3_@K=a2a153_LI_Z;TjV^5Aj?l^=Wvx3Ku1nGL9XHBR)Iv$NFK zRccOndelJ?5*jWeZT_cL;F863cy$^DC@lJjfSrKdlGO=dJI&-iBUS{p-C_vLv-$Pf z@FEU@XF}j@$e`69Ds|>PeHu$QRg0%3KGqu)GOuk@vI7QaqwyaO4#U~clQJt$4jEMA z*>){gu|Jg;iVPpJ$=)m&Tc@Beh2C(!x}k zmKOOuoq^N>WG*p>**y-9uXs_>$k;aC8X$2gk}L)f&{P7eIR`Id3>h(yw?DhYf=SF8 zQ5_W(n4|mWyGgqX9_8pQdfiITK;&m}bGsT%H>fzEPS`*q3Nev16B85Qy;`6R{uc7= z*$)R0FF?FHt?`C&zHW~o=h#1C&kxwA@op)_&^H9v-~FBI=@ypp)1~)V%HjEhQJ5_n zc)+*7c>rK%w|jF_snjFabWV8; z^-&}(>g(^zb6EmQT0vlRa4;-jdWoa!>Q!luESYD|pI7s$e#M{5>^xBeP6z!M8)ENq ztQRpFQZ@appchjmU3!6d0BVsC>ExuHR9jyQd&zg)zTs-$eJ88h8Hh55Ahj0&Yu~O_ zt}tq4dw-wC?&YSjRiogTSpL<=-! z#SSMnSIGVrizhN~1sp)|R1;0|e6!-x3&emhzm1E{o&Vg>?kzvQsi&za8eWKCU}bff-$93gLfuH6vfWa}w#{$8XV^MY#d6Ex@{`h| zq8s9ALiKloX(+}1d|?uK{sLwVrNmAHUaH?$!08Fm!ruq<|D&F5SL^XD%E?jL3PNyg z{KEACSyA`P=AeuZ@kN$0N$9ugetT%}HpONfWxMBBwTG%WYSKd)YHP7v`;lCD!@)t# zYQq^{IO3^0=2Asd;E1iyrL0TleWD4xXq{FxeJ9d(%gee}iG_$m|7p}0*AECa5q|!QbMz5f3zM&IO9m$-kil4) zSb)eD!@9{J8xRmzm~N9yNgCq`rw)Dj(=P{Gv$=3BKr$=zNVy987Ph0p#tikTVYOqi z(L}jLC?J31Q%m*0**@gug^2W4CaI`6t)2 z6JdS5p0Uo=bAIpJ)==#1FU5&w1wnPIewy*F=5CXz#jmdZh`V&J-9pgc-~Zrf?OO59YDO1>+0sZbl$JW2 z5V?8SUSy~Y>nQ*_`8uy~H^jLiYWe6U&1AKKp`l=<3fnyW1<9MZlc7&_OnedZP)k#_ z39Gb*p~tNN*vqFBwsDocgnfCS;@!sS%|l|IV?uP7aB}8oVR<-oZ&dlCPaEZmo&DS> zL{{9fKgExJd`!B!NpecDo{wUjUjU@BEd%QvLI5W+ z1QJNw^91Ob!+wfYW9bvN!~VGvDDd%rk-9S(FK;t-=Wz9R`B#+fUB7kfwY1r-w7&Eg z37sb|+W+DB!a#WXosew<9`!6PrM5U{3Rr6w&LYF57CDpE3WpPp=BoYATG6)NNw6&g zy^g+E03A}>nnToHj?8_z>9%Oj`er7jUyY6K&_;I@i7E3RdCkW8pqj_<9#cN9hx+Ko zLxE1{#{f~+X4VxJX)QKe@T)e(#^kDopxy)30}~Lmxn0JY1D+#3F_EDB@gIuOKP2ES z-KMp)ImjeR6V0!%6Q2E%GA^~he+Yze!pZZo4PGAx^F`(ePIL4IXtT2_^1|-yMDS0H zWZ?Gi+zEPLLXbkY_lN9EKfaxM*5zps>?$d8StN2q6o@}3w*Cm=lmNju z59DbLl$tXco*a#N60n5rjlKI`>?xn8rJ^b8W4=^LcAqB-*4%8XV_ZWZ7vF0U5slsS zP^ei+po+1=-uP)5K$_JWDxwgk!r4YGbLQ49>2O#Vzz@EJJ$g3HFuw~xO)-nLY6yb- z_%r4{ER%`eG&b~V0VS!koF&%S#k4e6?x1U{g*(H;vZ|FMDyZ7 z-5)Vn$YP}kSV>Nip<4!GVJbav_Tg7R9Jmgq{q;w3ovpJ_LPu+DY61%#C($)}*jo_N zzxfMStUX=c^mKp9%|Z#_a2=hLryTk1qIvoGzu!n`I-WKC{y8V11&ZC?i?vZ^J=3apKjoZt_(_KDRCq8+f)3Z#$50840d;OyT{7P zpOnsBw)f;$^EIGSn05Qz<`P9fNw5|z!{di(OnYv5s4Tep3pdnD{GbSDvk~^4(u9f( zlVG~>wIM06cAg*%S5o4`XLBeQjewO-;@xR#2wAIW8pi|A9tnlNe8Hhml&u9d^ei&| z#n)eOxlm7k5{Fd$IJvecSg5K$zCTC5^@#29!9h}VzH;6e?tc;kV>r1e#XCCU)Oi!v ze}Dc3HbUX&4rjmt9C7uktZLpkN-^pkV`E(Zi-Yj;`NKg3A8DZ5mRP~Y zc7i`OaOzuYmtN_n6^h?4?{wXT?B9*&f2xI(6~+oQPh7n2;^Uuj_ObTya-W@xcAU+s z@|H8!bcXH9Bbat-Z_Ghss4zn&Qq(Kky17s_>C%PBX@4Kyvp>M+VT?=Ox#=F%KQv@I zKmJ@c^L-4ip{6F%=?d=rAp#Gn_(1=Sw`Wj+n6 zpXOU}PJVWto}PEv47j*$cVm^fuVp5^r}%(!uvj@x5H^=_imDh6VVYwRy>MY;qaM{G zO3GKZ;Ffhf7zSrC>RI#$s(3-it?ASlGyPsZ|dZ2BSIY#*a%(Wg-bc z?-~OG|6rV8S*lbD1ITUCwg!LwazY)?q4u}h)CP45yu99sl{E&@g>P&TD(bV-S1^Bp ze+J5MRY^a8uv3hCv7uUr`)04>kzRU6U!>#|!CeF692*;3XBQ?OS{+4NhVr7!LFIb8 zo-*c9G=o}w$Jodh(D&XTw)es&=GcVIqa@I`I;)C=y32w&{vy`$U0#E;aD>rWIG9N@ z3kB2lRwA1_>{XLG(HG$m0=U*Eg_!#DHv~kZFxB1q#An+aJxWleRrCSVQn*y?7OPZ< zDjF(*H=cg>5o zk&%kYdXm8)p#ta1OL6h>8bmj>85yJGTtRT3I3ttcg4wiMY@B@At{#R9j$`R-e5j-! zXJ!IWjs+^(OCM@ncFfiW{7*?p^xvY+VURkkKld%|b11{c3w}H(TIzS#K47*h(VP$& zg*DL<`T+xtAArNnDpn)y4{G-p4regR*2-ZRU>|t3JM<+WGdQNHqh4=b9ALAolOTPN zr-u%#-V3WknICW*pR%qZ~O7v7dbo7DRek987W>;yV zx|Wt+QkgC0hbAVew|SZ@F)=Z)OMMI0EPo+tfNg7mr$n{3wpyL7BDOX2mA=?y=IX-= zd+nao+c$1Z44OnlMuvj81GaZjFgHMTWM;e~smcS7%CEXXh#@>NPek8MWL<%m{QBsT zx|T+sS>po$2aJsLKiTsgxsC?)fE-ZuTM|Xp$srB<0Q=WqDZOq`Nt>9OXMLYl*XqjE zXucVo_FuY6oZC{PcF0Ixbbve%GBApEsymZ-XKCoB*{0KEDJEig{|ao^#>ZJxWERte zY&tD$dR1if^wDnXMrdGPqC;ZvK(Mb_d}bbAS`XynarTj*Yy=O6LZPlqq8*6_n6B;!Tw*GL!{ zJDcBogXE*6M8#06twm0WoPp}b$Nl~NT*Du_>7XHF&NpoM^zkDjU9f*7A6#jOKAkmN zrvhji?Q9YWV7!SAi~sll^CXpU)o8{7JOF2v*^a zp$s;uB;K~8HM)B&bb)mu;Q>_90)c)f2A2i*;8}IsOoN9NY|+L5$D!wx#^9czF>%>AD_OIZY2LUD(cQRB7>STDr11X^JDCu8Z1nsPV)nSuF zSl$=4Yw(E+BKHM+VaN(dZXU8eE^KMee0zFuEpt}&Y7%;s>DOoy(^?accz%L1ho>Na7Cb~fAe_y^*gt-#yb?;EK1p|2vO~|uX z$0+aoE~?2KPckK+pSh6G3llQ_NrpFqEcaQX1oxMKjg1XETo%(aw*sO}MS9`*M?QW` ztF#Sy6FxpZbCE@3v7(V2Fayc8yIUIFYf7#J2gwDaI^;b05SEM$6xP7_afdsWP3z zPDx1tPoG$lXyjAd)pX?X8-MbxZmARb^;OP6QW2ED-(3voG6?!vf4~@y(5((_eWuXv ze`x`jL%JELHV0_4zJDA{2#}Nb%g4-CoYJpUt*;wBK0RUEQbOTTQPtwmGyJxl%5omZ zmoM#P&jb|G1MX&|+oyD-@Yi?xv&Q3BP(WK;YWYz`c~c&m(WjJK$!g$D{{9^UrC5fR zv%^NkXJfb*t)2Gw zG$a>_4!}HxUq3tjfTZ=e=Jb!^wmoA+CurGH080m(=rW~AU?uGgW#a*e#XZ+|HKC8# zcNpQg>3>saFdiR^pj)f$94|?lyQdyu7o7d@Y<~+5mQ$|;rfXmXN9o^|Q1*d=L2nB{ zBegqz`huP5g9^Z_Ia9?B-0oj4;b=F9ZZ4Y^?^(_P<@ba&mDuZ*Wp{;pp{L`0@o9Jp|53aPu(b3lIJ3~0yg~Z$6l$V$PcAK7_ z!0DI*r=#FrnV*_Cy4Z+`CxSY)HcbMW?uV|9TEoQ~`}^LLWri@a@)?HwzyKyGD-mq^ z>KY>xgFSlS9vxl1ghPU^Gj*h*1>!o4)<1ytRI!tajfn|2D~1oB(}JkLQ}+fTZjBb| zD4@Up$nX(R^yn_oIod%|)d5arKT}zYJJ&|-U4emTutn;@s=80k>e3p{N}$Rlo5Ev% z1cEEcNuT#3!ovI8T__!8HfWA0VDdSn5e(#Mo^>X!`t*fMr#t(G{D9?CQ*tL#hqv{S zLq!G6Aj*}f|K$WjZWaPauV+WIq{&fvgVC4=4ZT2e5(DDUY%#R&Av;+pVyG}%?Z~2b zN1Ck% z(&Z2F^+Sy-|99%o3%5nk`Jh`A*mKXBo2^REYUp;`|5Y$ucmB76>E8s{cU^9NhyAXH zXt`?Ki5-^YDBGAF80wY=P(P$DylPedkG4Hib}%c_SlGb&g3A&2pv(4u$~9VcxW8f+ z(Eno-(Z4UCqx?_p2b0l2gGSDw5yq$IZ~c=g4MWM!_~H%%0o74r@H_f$U93yk%X6@0 znwjM0=c_|(jM?fk+Fgepx24lX4h|T!%C~1R>usqzL%k0wMKMvJO{pb}B^*yV1SSV7 zI3uzIUi~)%TX0zfjju097EmI7q1I`ywE$UzIWqdasBD<##%(@ZpxYC+$GiUxfL9<= zFMzNi-^lK>#mdmuRvYI&xDye}?J6}JL|o_f2=&kvh~os85FS0XUcko({qjVhoR%Mq zOs(~BGzC_8r9Gn4rMLK>JBPL2IyA`o4_-7lRvE61+D0#@>jT^H5_kb@Z@O$lZJcLt z%6+0ZjRj9fq_-U^=@8e7QJiB#=5>N3LqnJUn8Qh6#4=H&^;Df0pjO^fj>Pnx)=oc> z8Xv}$UkWIqGl0^MO)h|YB!mM&P<4FF1Y*9YoSaNLo$7b!4f*VmCYLD zv^*t(>0JO4;F6O1NR?T1XBi5$H{x;_`_xpT5#Qf#F0yEx?OJsC1i)sTWXk2LTbacM z1^ixu2@lxV^i7;u)PD^>Ub>40dN%BBA=(^4Rpa6)`YqDWZodS07ET93MV{MLp5smX$5 z$YiN$uIc4#*EHI{SA+ZT72u_?4inz^CxSEjcA4;-dhG6Fbn(|C4<_*N&`>7T>DL?< zW_6vNH@*11Qd02YER}rk0>?TlE30Sp6WzFWZcdsjklb=rovoSZPIe0Uvxg|Fe;-WC^E%P{-iu<4lX z>U1RMTol7yOc>-Xt|WW$nY=uHYU){-G9vxx=Bi>1RxKl*5qV=#St5@teF!^ z13s|Y81&zshnammrSXqAEEj&B?C}Cee+Kvhea89+)Vc&v-Hj&%M2 zdQ4}w!!w4@bz^+k)oP_juI12Nkw@wH25N6Tj#j>u=u7nvm>>@q3kNZnpHgUNK8M|p z%7N~!4!MeZP(RDf54=3Mcj;dXi6q3veol(Eh6%sv$+d4Ssy`R8XFED(kjeCjcP-Yr ze!Uv#n5>)0rvY&vn8bIGIAe}&-&AV)OU?6iXe+g?GLDbu2h#A?=oPdJxhN?w)6>&O z*{@#cV$A5>$8AzR*(03g-hCZuP7_FsCJC&4xCNn(pYKediK~CfID@2&va%y7De=o6 z=A7f{J>AB&wIHn05E2&t{;n@mIMjp!eIahvLtF;FldHhGkDM|&9ZQCi225Usalbdv z)9LfZN1TO2nBB>u-}3m2!#bp|*yn;m>`xYajcHreYgtXrz+4uP%jqoF!|u2INs$7* zSGKp4&?@XlEhN%HU>&3euv^x8y~UV0Wb7h6ZH6x7TVUWTZEfvHcRNL7BzJv-Z=B9R zF4@Au0!t7Kv8DkN@IyuuT7*K83j9&7Kv9Cp($aExWVo@lH6cGee~>Xwz>6FZ=W~}@ z;DGT@va%bE(`-K7ZLgVcZkDgKqmq{gaj^Bf;X4&dV!|yFs;b_bQ)0a92Xo4u;K$fq z9nw4T9*7C^c9u=6?Wrwj(i*=iQ`^{xU%cT{n=ZfC?)c-GlKlZ$SId(fDpN8q{_5I+ zgeDUqq=%p1O+i7ybwtN!n5L3#G7~J*)NQu7IgJi5=S7eU;o8qDWC@y_>}W7j2FXwE{)Vu zpI+aX7RKy7HJN^*_Tb?gn93-xMWoQRw_BMwAYw4B2GzK|6VA1{xLVZDfu7p!sNm>e z0nHhi_0lGoRX5AMVTrvwBmlG!_@yvO7z2L%ut--bNNr-(^^I&fQ0K*nTcW6fZH zhzan{fXM)@viMPnT1uS-Efi-_{jVONkeq9>^1J)x0v!uxcI(v-PB(*(7d4z#=6j-L~OwEPVuSpaveAEPXj?=_jhw+6tHQ6Z6xZ;sn(8+Z-$7sef60R#)Jx29^#+HJ7w^_}V~@K~<$nJx83!uz^eZ$*7aeLTa`d!xdJ z1-&EZ37c(GuR4xD=l8sOlr~{V&(jD)T3}>-{ZM$>YSb&HJbiQ)4NZ#pIqc~9w@pQOvt5v8J9w&ZmbRT;liwjgAHxGD5vTVPXf`70@xj^>-$`x z6hGxUr68@WN)}YiG{`J2DfJDGrr?zXE*VBrHVl|QaLfn{2s%4HKYiIbC}V1Qx+N{E zLR4M{pVT4SZF}47^Jl(woOQN9d{$aeoutsdD|PCZN}BI;#PH!^_36%Mo)oP=`^0+l z)*%j2Llw{Qw`)2n!`w?G00-+7W~{qSv{YKMHV4yBK>?U=K>TBj#3sbVo_(n=)3 zTA<-qCr(~nbNAwQ1}L)PNSaq{p*RQEEsP`Z!nM)aAEzh1E`P+5%-{K=8^$71TUi;VOiTe*o|*V&d{Jie%fdQ{F@z<`Mk0Y^^8a?-(1x4o;g`>!g=%cMxVblWb~I)cEt$45%UTzyVQ4Wo51c4)`~OdK zUl|o;8?8+!sGum_h(SmR0z)GbN`pv9D?KzwjliHtNGeEoH$x5$(%s#iLw9p-Uf=gS z>s#kN=dADF$KvO#d7gQmeebyTwXZ#w)aVPEJF_yHlobwF5}?@_VlVz0@rVx=qI|~m z7Zi{i2p*h~fx!d#X7uXGVrzDefua8PQq&$h>9dxOwnX42*1+o0o60$b(V5SdQ0UM_ z7|GQzim9V*<=~WQD8B2%b6#Fv;N=v7D2oJ0Ey)Dwf*2hnb{gjQS~_zgJO1KW-a31@ z*P3kOZg~n|Txl z+s4@a#ZvS}N#i?FjDz?H9_j9al6-&FXNK#{sef6e$&XC$S>bHhU0ia`a6l5npP~6p z5L5=3M~UqcuPxu zM>uc)yHv|*S3hz=PY|z;k;Woo4Y$znyWli%)yFSGhM)JYh zZA|Rf*gt(67kB$HXm4*5J?#_Wls4U1guYHEI#_@Gc3|Gm0NJrPSG=A-5wzqGMVPRq1 zhi_75skqdG84k1R98Z0d>AWXhhiXm4*Q^D=&;UB4ua#+O*<-nSdm9=ki%*c`>Y|O? zHW#OOGds<3Gd;ELl?Ovn!=E(nCM1010nv7|bY8B|psb8{;%c~N#98C+25euH_M3|_ zzUo3ubWCe^=VLw?7eKa$D|os4vsBrZE;nd_FhZMcw@@cY_#KxbmTzz4<((i;@mAzJ zXkI(Pdmj-qJ6@}dYCEqQj1`%i?wNBvkv{c&YK?5>K)%)1?gGrPlVuId-60LjpiuiS z%dzk;ha2PL;OZH)dX9=&Dy|*T)+AtvbJ&dHZbpJ$_$Q(7X>qQ7`&-4TI#qPzCU1t5 zXztLidm#gcL)tHEj#0TJS2f4dh}0|0^GccuiH*u<*AjueHsKB5Zg_gU{t*+?xm82+ zyefAP>ZErW^=XT`t=a{^)}KBtGXdszwm4(l^O?=C1bR0<#p;J_>dFgn=G*4_N5CqHVAi&~@5eILD%@0SCl zXn`o8BS&9xg!^h z>$RAKyzcnRep7r=+6MnO_$N|iWMs^jE032Z9B6B%C-HKvVMNIef$THVkwPu2_NO_?9FXKPKY@`Spcvs-q--3_FL zYx~=Mho`~=OG*MW{2CN)Stps`Ln_qQW@@KL%MF+uy34)f!`O1{qcfixFtb~n-`K4F z9J+cEW!Xi)wDQ1rSe0--ojylJ)LQa3_+h_7!e(czHtzV$0ef&mFk~<_n?fOlF-IKi6`r%in+b%vnK2R1W_UzFR(K)$TfmV9y zP*9BXOuLjc6={BC_*VL}rJ~lLNSy`rsp3JI1op2K0FpB-B){R%e4NL5C4SXOLLyZe zg*t$NHPk!%`}!MWalDS(4rq~(qlXasG;Q;OhN9IMx9|dIXb{yh>QMEN;SEO5&_8Zs z=wFLi%}Df?*i#A7rsw~6Iqi7Xr+Rha2`cDUFj%=sSwjy8fW4B_(&lMqERlcTZ%<@< zNBOe$L?qYz$#N`MZmr=>DwL>gz^th7BTz2w#YI;4Fw)vO|I_MN=e{P$Ofr;=x*8gl z8By!&Ul9m4u*d!Y%Jv)7md^n$eO+6ED0yJ8&iwa1FFn%O_h&NUyu+$GU}Wi%lM0SiikKmeSV=k^HR!38oZ+>V4XnH0nMVOitUBbRAbcM^h3aV#91{FIR349kLolg zx{Zk@^q0w-nCFE2XrQG_RXfy${0ty-+Z;Yz2Di3D#Qg)ChlfA3`S~ zF;XwFkR2E>`Q~w;3p9pTK1sT{fD`HS0c`gIPu}nfNT!LsH(^8#BSe*zUw%{z1IWsk zBzH`JJpeY9)fFaQ&qTd@igc)gn%b?2W8gDJN=`e}`=8)DK_C!fw##o19!t#4&WeDU z12XoHPd=?GhmI|Qlg%?Tv4E} zrB-rzar%JW`36vIrDJBs)d~tY5>^v~O5N$<1g9p?@<4JSgipJMXMgGAZtis<$5TD^ zPa(IEJ;-wZ?%WB9cHQKpKiH4C34@4pa`ZuRV;i>DBym0N%W7>+Rpn^EZfb#`gXPPt zT6}whs#j%GvJd?I_Q75$_DUFVci+-!%Ki@h4R&;@e|LS1A3gb7h4R1642YJxT)cq_ z;tmV~_6=MopOm={nB-C2LK?N88Po`9zX%5<@)!`}>EvU-TH@${-uH2L8xi1FYHk_+ zRZekA+K6kbB?B^W$HllPcq<{xu`ZE7G<&<_MK{5WG9t$!4#gO zL+yA&BN-g_dA)SUkD3dMMu+Ckojb>S$8pZbmx-ni68WmO=X}BHgPpZ3Hq7{5V>JLu z@H?EhwS^2e%l-9O)KZ1^S|HyOxavJU2#C1ASZCP%&ufI&sY`G3($C0HiHTJ;;PoQN zxo)O^HkJkIFn|TJSPZX}0-9`~{;B_2|C+~ojt31Ky$F{rvrsmjCLp?DJjv%)Be?&3 z@ziDrp<$#F7F68sl$;zl#Umh0^SaQG8V!ig&Y@849Tl5~ zpsbQt$`KKs8999~9xXFdT`RCQ9#O8q4$srA9j62$>|N!+ zKX(WbYO?+h2=36+z3~$B`RxSv)MYZV2$dYfFbZs;Xz1?`29J9QgnV@4sR{Q9m~$RD zbKt6~2ua)=Nm)ryytJ6TKa1CW+y6G}7t3&1!XOk?4APW;>a5{Y)jre#$e#@cGZ9Wp z5%ks$4ru@iQ5jbHC#Y?Xe<=t3!@d+#NWZPWgec&A@G3d!<^FXgV>Q`FKc~0bv9Ruv zcK`@~d+i#{;f|MH7*Wv(F%i8{ag4gt~ImuD4is)cmpo?eNx4?hBr$nT- zR!kzmps6E5Ww5`*Qdq*DT2xn;-&v#R;iHc?0N&WGp&Xc#tx*vQC^+n(WB|+Dx-szt zyurrDDE;cc7%<+bMDTM*k#;y!+S)R4>;#qoJL!M>9<+4;mraGS0(9Ls;kv9e@Jd2( z7lo_zEcs4*bW-hYfNv=Ptdx<3g_fQ6Cm<#dR_z*q)Ww^UKXkR6hO5GuR%SJl|@{}pl5>0bJd%D!^DY&f_6#br9Akjti5cl;d)GqelrbttU_ z1u?Vgec#=+0W@3bF%M1!T5yZqf>#*bLVJW@6dq0BVn5gr1wh!{Hl{0Gp!Sn$0RC=Z zA;CZ|LV?z{FUH1XwLYR7?RK~-(RCrx6(jFq+kt-|EafnsVLqM78f-%7b z)vnLdVNNgIE`S76lP8CpN!;{f?#0Ndnx8N#ZR*P`r~$xkZ^_^JV)`nwO2}s6O{#p2 zuLsWbV(jP56m#8&fR0mHF=Gxi(m~zvUm_I{eeHkOJW+->J6`7nhJ!z-9}bP?Zsiz|lGo|WQngeHvXCT`Hk&y19o-ulQ3Ak;H<&7nz&|;*UG8WDM>Qoqy@&ay zsxl^!j`#qyz0Z&5uSf+xk%Cq*SWYrwg0xUn%+H9Ur=xrRNsT9l&n^>b4)W2~;Iurj z;1is!4sD8m!zWwjF0Lh9#O8;L37kwntqpiAEWFb`Yt91mV>Vmsyp+7o-w5afqC#wK z>_3hbtukvMzwIpt-@0=zF+H91`Mr8?pU5__CCOXR&d2ovdwWH1?~SbV-meA1b4(0oi@@T~#&6 z^{!0b|6ci5T98qmo?M#T6d{cEZqWJZO$FtHU3}%CUWP%lmak1}eTF$h=Qsp8?pTf4 z3NGWe^WoesSoN>{{waT}+6PiVCB3&y0Mu}K;+!kPLBU5S6ZtMhlSx<@cDUA<%G5Nd zV}Tm#bly2Jq7<}A9vfy0_$mM9%{2oXY#S;8eEkPN%)ZdZL8dI&zvldvtE+1WA3$cp zk4BQtQT2FbKkUEs>OFF@oIP`dNpW1`gA)uE-eOb4ZUUF`;I(RwgrzAe0lhT1p;; zy%KVORcC9IAcycm!oWxp;0VtwI5YmNSq#-MnM}Pae5rN-Js1BsczHC@{2EeD+zA$| z?(@{)es0{O>xoMy|e_GwyVOSH%He2P=xL6ac*{1vk-|yRY6C>lyY5noNVI zDlc$Cc+F&Ain65RQpEKk1gJ7t0hfUc0EH<#be|M_$Lx6G%?A%2qrDM;S5N)b-4L6% z5uI3v;*29LYRB7sS6>*;ZEVOw)t~tapZ*vbk%n+?PM3sjBqUTGo;rgcK2oNK4O+N{ z2Vj}nG=KzJ!Mvt~hk9+G@cGL(R?im~2XRv}>U$ppS$ezqjhi?OjI4%(nU*6ZmI&pI zGULTIqKT{DnnIV3i~({1gL1+q@>sZ}ZlDJ#FLY8gx(i0L3Yn^olR+ecoq)a_YK2Bp5^aDW8x}~dYY2{~qwzkWv;V>8g>IK*+apj)9za3~t{7cJr zA+jdquXM~M!}R4rV>?o6QO|r)kEIC@Js>2I3f*feftG`%i3lV-I=SaQctUFWgR;rj zhn$r)m)UVS$8?16L*P{SmHb?H4A@f`rn8lLomJFHz>bivk;Y|@EcJ5KPkK|NNdQ}w zVK|K4dQKmde%`#~wpCGd7HFN3PFDLZ50GYM)TI^O_f~ooe1$23f8Q7w^3zwSt*;+cXGzvSWNX zO6!Z$r(Ah>-BQ=gZ{f#9GR{Jh-t!;z)pT!_Ps!rjFO`|NpBxbcuME=8z#xm|tM_N> z*MAC<4W_wnu_JwEz2Fq>HF4o$6w^zp%JO=9`vYIj&%R4e3}tj4SV$$=7hm)Y9cbn> z4aHH6f1WJnVbgZp5sw`H}oU-`7yRnAO&t^#s_W~X2W(!g;xUP-Sev&{#*y8TwXX0-col! zd}(&Hm$16V(H&x_8GB{uMwtqGrDAJ-x|SZl1M`PKa|<6raS6BKlpXqAdx?lq?8RIS z#ge8(*L;%3`kRLPB2I=pYlXtvTT5K7)~o5@U+bcm^W7uMW!Sgl{@f6Hn)AGp&ALQ& ze-=;G@mN9nuEOO8CyJ6x6R4R>Qho-D5OU26x!G+aLje(2yxwD3|LD>7(cW@D94=>5 z5CDglM+4*wHCZWHq{k{}zVQZKEaeS+(Ye5IYS$Ug9l;f1C6_foRT$%kBc2VSHD z2hCYc2v2jZuE9`vcaMRs;Xc3< zef+t%SWCLk)_3H<2t(1mA~;s5r7(^R{!1!}L1tyz16>Q#WOGSILi<6~y{3FYx~YLC zyen>E2i4aq=E4$=ATJis85PQTI^~x4h)&@h&o;$fB3hf#mfaA1PiKx4)(R zxrk&&>_cHT?Q+wV6wsU4-J+*Mw>p-2Msv}99%y44wIRgMHSAMOd;5$2bh18^SEw zNvrLqZ-EoNC%4Zk7t^i#v8GFAWjJDkeJ6Xuxgk4N7tTga4ri+Foa7;ff9Zx$r{*Q& znhp@vm!Bp)T^6^?KQ4o-FO&ZK)nB>m}PI)(gKR+7QN-Z|K>_a(3NA;0`EyoLPA zh0*ztHq(~{rzscLG9>(=4zD-T5iX*ucqDK2x6ntcZs-$$pIP<;tU7G4ACYmxoc-_Es|sC%Ddnnh{);^cNLaDvbQq zI`sGAWNBX@ys2p~WvD2jupFfd;$u#S#gVdi+*E##5v6`22^$d{@l0q=jVWe=HlbA^ zs!RJsSV(H#_Myi{Sr_h*#i6T+_r8lQXS;9q zY~!NR$|n2rv{`Fbkbz~m5T*9WWb`HvKUsV6{- zvXPK$tE~Ld&Tne3s1VUJnt5%OA<#9i&;dR3I0U26wS|tGNeUvNg`YNTcloyoRoTrZJbUU~IR^5(P994&_XVmg>4W-gv#JUzc6!Jv zfl+an@}uygAN_822WHn>SMQ%)DRUSn_0h`s{smvj;@Kp9X^xTxnk&Dd40B`!Z-163 zTIL-~#FV6zp%8cl`U*!6`?)8gsmP`(FtL;uMTD%$Ki z=Ki(Q&FtPYnXDXkt|puIS)S@~yRVX%ez&#HLbV4&vlu;dT>KzKfT&f+R@3}J zL7B3M92cv&96Iu81vd*WXb9Vk=ZfX8|Liq0xBfDlHQ2+{HL0@BO+s;d&&1IBGczwS zTD~M%1D!z+Li(ZBcuD5lqhg;ATGzK$QW4PqH1Dar#NFroo1y7Uoq-L{9!L4zyT)vi(>uh(?69miN*yCzU%XKw2iDJoURxw9P z$X<*VM&MkO-+22@y$hvdo|asRK|+sB+(}iR7hCIGfhrv(IyyMh8{Pg)*=PVu#a)Nm zyT0G6RvdOxSB)!Crm09r?$rg&6A{>c0}818!SwRAlT~2K8ZwzlR@isl7Ryc|kRa7RdSxUDESgQAA z9#H|bK2W(UkE$HSt8_$7(4^QHrQTbW>ll6K)4u-=>ul%Jp`sK2q-#UHC%>32?%X-P z&FJZK?b)n!0oO$$tTrZL86G|;`pC{}40loY(kfdG*D(j5SjdTs+*2j#N2JU9h*H9LgO1{@jjgrosExvmCel>HMWwf?&5f<- zG;Ko3n(Xy9Rnyr7hmc!*ec zA8epA?PVCIJAdLgtoVIAZn~8{xX7eSms+}vrBuVfM?H+B&|gY+_-%CLG!%LEH3RK6 zx66)?p;x=%_X?Z@RSQ{O=Xfrn;LCxwCY%nohPoy8`zT^>l_Z0!)+e9HmS_Z;riUUH zI-CJ&o14%?9;tnDRW@J-9Gb~^U$C_c|NPQ=%g#3(eIinX6qGaaS*I$?M40bvnay7&PorX^nT42_hlTemRsZ_mAS46|qch(Y47Ze%O zy%B@4IxrMAMTNOC$?n!DyfNRinyI?CPc#@fot==SHW-`5ORQQSM5>*v2o;jc%+x$o z<2swhA8N=eKVW{p(>A1GcWeOS_>tZuKt>jwH80F5>qN&i7UB0R2S_v5C-Cr)w zzzQvrlO+_wX{NSJV85rn65#v?W5X1q#Z`}L4tbzZ>bAO82E2P>g4yop__y{k{IS5G z<@ER+H;(v7rXe?*9cZkI-uh^mgBsIXMo7v5r-OvxPb~UA1#|-``Cn(Jd&&O^6J4fBWI(?QOBU`<{EF3@-lTZ~J%S)hq{*XkZ-3+QIgEnMYrN z^mGHIkgbWPnW71=sXdiZXYTyFG*Jo~z7o^g0=t^wGg(UA=D6D(l;lmN<9rx-sF4&) z`951eRiX3B36s@TIhY0QB;q)e>uI4INHv()959^0PMeN2qs8c^)iRG?zoKepk8p24 zlnWCdKFqPQ>PbXA|DmNI6K2l>K+HZH!8i+^8!zJmazCF|NUfbkl;+;A_+743UrnuXygrUD?xB5HiE=D6^P(2gD&_gGUs;iN@ z17lm+i}x8F)iG2>=i~(PDwioj)c7+>1}iG&%r4WV>fr2$Wvy1aomL@Y0mq9a3c~J^ zdY&s|mwS=Tl~1AoX(nG*xsnzhI4TiwCjO=B=9^^g8R&=ci}xZ^ORJMvjB&_43o}wx zA}MtNd|-`@K07l_g?K1wLijh&lZAzw46Nd0_dhz*X6#yQ=rJ<5j3<%fl1@!~0`(Mz z>C2marYFzR85_Y>3jOQ-BYBfLXt0f5&{o{CJNS@wBuzF=Rem%623gIfQb`;)MWMDS zE{?P!+*>4~kD!x3SV6B}q0Z0JW=*w5ql(215Htb3v+e6mOIx5mgVJ-ijy8hYi-de@ zR{tDG&u*s+p`lsv?*ZRc}E`OhlqWn`jl!SiJrPB7EFaYA(?=cF1>&GzKbyORv!g<@b4j~M$vtZvUVvAuq^Z)y5(1y-E66^B zI`q%Jz+D*%kTciVUW%Rt<=*^Ozn?HcU#Fo%Mh-}uaojT8D?m?o8$tORwLIxZR__NH zy*M;VTs3vkXm~#RiuDVNU_=iBhiV)d-`&Y|b*m#?h^n+Khi!w&3uj$8C5uDo64_;b zYv-tTv^GHscg-iE_#%&WC|YOvZQ;p=<0H(9V!gE=pqtcA8wFy@T84khf!f$I$|n${ z;;bOg&f?ZcO1+GZ)o7^>mYkR>V?j((bbbGZOX&YfLkAB$Re>@y_eQWr$YmZnni;5- zQND-4jM4)Px+f5b7eVfi(gZZLn=6uHBJY~z;~B_7=U4Z)n@F^P^<3t--DBvFh{M6| zBl0vE%HG>`WATQ>iPgoox!5Td*<2KtCIZS2-W`Coa?hRjnVu|*Y}qpqyqZNGHo}p3Rui>+9uO7S{c3eRkb{=+X`)iqq4b45y^q%mW1<0AdY?r`kDNij4C$U z>BqeX0gXDp>j%l;afaEGmeI3ma|Ii@qfXIrhmCL56Vgo2RP>if3T@hdK7*8FI2SK6 z0Dr>6V6`A~C(EAnOu}B|wlsVyko83zFaYc2c#CuUNBM21Xhc{cY!IrYS8}wI?iZv} zC_30TwWD^=(1vaOk9^Obxl|Mx?6Xi=+kBb{bGJ4Va&F2rPgy41JzR@9w`RQys!Kj> zWPGwrcmA=o-YXW9z^BcRDu-opx9kWe$0>}E+dEbUj*A@dEon&W=p#?zO4Wpb?dXgTW#8r@k;{{l=(#UNx?4O!j3nL+LN!iUu z?&cHE4>~uuH3ztfZuR^q98~U025{EWriMSn$$|$x!Xw>WP?7J13_<6MWovEW0L+1w z7UelQloWj0H&(*i4M(nK);v-noM)rNLhkEd4)gOweVnof56wraT}J2V_5Pg?OL@=b zo*Ha-tz{NI8C;k^fbWsxuc(V60nK6 z@bDP}Q0T*aLTT2i`@-D9?}q9alm0|raOioXf|Q?r$0i-A5_}-&E-BM3su=%yk^~lskD=ebB zoe4Qo@V@TV-n8R943Yp)C$4M6+qi&Fd5TmkxM25Am+~c+XC5BMADM#0+dOtc;FBUF zYI-t#h!CuZ;y>~Qw?1h6ztb)L7ef302VdBt^1Tw81p5od|8}3abWBVe|IZ*6Z7$|N z`2N4M^;^cos*&tF9NK?x|G;|m53eszl;a}#uNKFB`&X-<{p!yg dff(iqTq7s~Tk6h(PBic*DK00L`%3rge*jf+KXCv6 diff --git a/_benchmarks/screens/unix/unix_1m_requests_iris.png b/_benchmarks/screens/unix/unix_1m_requests_iris.png deleted file mode 100644 index 4fcd203eabd7800080806c082610b79c3a24ad9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61567 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

99dfzxs$*MQ+xWs8EOq)u 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 F0su#ge<1(> diff --git a/_benchmarks/screens/unix/unix_1m_requests_netcore-mvc.png b/_benchmarks/screens/unix/unix_1m_requests_netcore-mvc.png deleted file mode 100644 index 8066799a9f8fe0beca17a198c181c34575dc3a41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51319 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 diff --git a/_benchmarks/screens/unix/unix_1m_requests_netcore.png b/_benchmarks/screens/unix/unix_1m_requests_netcore.png deleted file mode 100644 index a3c017cfc0274fbcc9b14de652a8526196f843ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60316 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 F{{iOJ?m_?n diff --git a/_benchmarks/screens/unix/unix_5m_requests_expressjs-throng-sessions.png b/_benchmarks/screens/unix/unix_5m_requests_expressjs-throng-sessions.png deleted file mode 100644 index 15a72bcee9bb4824d0878bb3d245e9bb29d2d8fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60017 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 DFz^03 diff --git a/_benchmarks/screens/unix/unix_5m_requests_iris-mvc.png b/_benchmarks/screens/unix/unix_5m_requests_iris-mvc.png deleted file mode 100644 index 3f6a0a5468983f2710416afc381634ae0403b513..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62578 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 QB6b^t&8$pIuRe(QZz1=l7XSbN diff --git a/_benchmarks/screens/unix/unix_5m_requests_iris-sessions.png b/_benchmarks/screens/unix/unix_5m_requests_iris-sessions.png deleted file mode 100644 index f57a6b9c4a41528eb9dd3c5489e40ff355f975fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59229 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 diff --git a/_benchmarks/screens/unix/unix_system_info_ram.png b/_benchmarks/screens/unix/unix_system_info_ram.png deleted file mode 100644 index fedfd7bdad488fc2dbc0b1bb34951eeb89c3b091..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155663 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
=$W2_To>?vVt3ff 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

x>> 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 Date: Tue, 23 Oct 2018 18:35:53 +0300 Subject: [PATCH 51/91] add third-party source benchmark results for proof of what we already knew --- README.md | 27 +++++++++++------- ...rty_source_snapshot_go_23_october_2018.png | Bin 0 -> 47294 bytes 2 files changed, 16 insertions(+), 11 deletions(-) create mode 100644 _benchmarks/benchmarks_third_party_source_snapshot_go_23_october_2018.png diff --git a/README.md b/README.md index 839da65823..efb9cc2aca 100644 --- a/README.md +++ b/README.md @@ -22,17 +22,6 @@ $ go get -u github.com/kataras/iris Iris takes advantage of the [vendor directory](https://docs.google.com/document/d/1Bz5-UB7g2uPBdOx-rw5t9MxJwkfpx90cqG9AFL0JAYo) feature. You get truly reproducible builds, as this method guards against upstream renames and deletes. -[![Iris vs .NET Core(C#) vs Node.js (Express)](_benchmarks/benchmarks_graph_22_october_2018_gray.png)](_benchmarks/README.md) - -_Updated at: [Monday, 22 October 2018](_benchmarks/README.md)_ - -
-Benchmarks from third-party source over the rest web frameworks - -![Comparison with other frameworks](https://raw.githubusercontent.com/smallnest/go-web-framework-benchmark/4db507a22c964c9bc9774c5b31afdc199a0fe8b7/benchmark.png) - -
-
Known issues for code editors and IDEs at general @@ -52,6 +41,22 @@ import (
+## Benchmarks + +### Iris vs .NET Core vs Expressjs + +[![Iris vs .NET Core(C#) vs Node.js (Express)](_benchmarks/benchmarks_graph_22_october_2018_gray.png)](_benchmarks/README.md) + +_Updated at: [Monday, 22 October 2018](_benchmarks/README.md)_ + +### Iris vs the rest Go web frameworks and routers vs any other alternative + +[![](_benchmarks/benchmarks_third_party_source_snapshot_go_23_october_2018.png)](https://github.com/the-benchmarker/web-frameworks#full-table) + +As shown in the benchmarks (from a [third-party source](https://github.com/the-benchmarker)), Iris is the fastest open-source Go web framework in the planet. The net/http 100% compatible router [muxie](https://github.com/kataras/muxie) I've created some weeks ago is also trending there with amazing results, fastest net/http router ever created as well. View the results at: + +https://github.com/the-benchmarker/web-frameworks#full-table + ## Philosophy The Iris philosophy is to provide robust tooling for HTTP, making it a great solution for single page applications, web sites, hybrids, or public HTTP APIs. Keep note that, so far, iris is the fastest web framework ever created in terms of performance. diff --git a/_benchmarks/benchmarks_third_party_source_snapshot_go_23_october_2018.png b/_benchmarks/benchmarks_third_party_source_snapshot_go_23_october_2018.png new file mode 100644 index 0000000000000000000000000000000000000000..fd723256a835c9b8ba2aa0dfa2ad52da271675ee GIT binary patch literal 47294 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 literal 0 HcmV?d00001 From 2c8526fbaa093b56044cdbd8270702470bc0ce40 Mon Sep 17 00:00:00 2001 From: visgoods Date: Thu, 25 Oct 2018 12:57:57 +0800 Subject: [PATCH 52/91] add part function in html.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit layout.html {{ part "css" }} home/index.html {{ define "home/index-css"}} {{ end }} in html/temple the defined block is global variable, so the way of "{{ block "css" }}" in layout and "{{ define "css"}}{{end}}" in home/index.html and home/about.html just one definition work, others will be over coved。 No use!!! This part function like extend the block in layout. --- view/html.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/view/html.go b/view/html.go index 24bb20ea8e..2d927ae1ae 100644 --- a/view/html.go +++ b/view/html.go @@ -44,6 +44,9 @@ var emptyFuncs = template.FuncMap{ "yield": func() (string, error) { return "", fmt.Errorf("yield was called, yet no layout defined") }, + "part": func() (string, error) { + return "", fmt.Errorf("block was called, yet no layout defined") + }, "partial": func() (string, error) { return "", fmt.Errorf("block was called, yet no layout defined") }, @@ -389,6 +392,15 @@ func (s *HTMLEngine) layoutFuncsFor(name string, binding interface{}) { // Return safe HTML here since we are rendering our own template. return template.HTML(buf.String()), err }, + "part": func(partName string) (template.HTML, error) { + nameTemp := strings.Replace(name, ".html", "", -1) + fullPartName := fmt.Sprintf("%s-%s", nameTemp, partName) + buf, err := s.executeTemplateBuf(fullPartName, binding) + if err != nil { + return "", nil + } + return template.HTML(buf.String()), err + }, "current": func() (string, error) { return name, nil }, From fe6e9d68949aadd1ab1ae0a00f6068d4fa3e1d10 Mon Sep 17 00:00:00 2001 From: chenPengXu Date: Fri, 26 Oct 2018 14:15:29 +0800 Subject: [PATCH 53/91] add template_html_5 for fund part in html.go --- _examples/view/template_html_5/main.go | 31 +++++++++++++++++++ .../view/template_html_5/views/about.html | 14 +++++++++ .../view/template_html_5/views/home.html | 11 +++++++ .../view/template_html_5/views/layout.html | 11 +++++++ .../template_html_5/views/user/index.html | 10 ++++++ 5 files changed, 77 insertions(+) create mode 100644 _examples/view/template_html_5/main.go create mode 100644 _examples/view/template_html_5/views/about.html create mode 100644 _examples/view/template_html_5/views/home.html create mode 100644 _examples/view/template_html_5/views/layout.html create mode 100644 _examples/view/template_html_5/views/user/index.html diff --git a/_examples/view/template_html_5/main.go b/_examples/view/template_html_5/main.go new file mode 100644 index 0000000000..9fcc7fdb83 --- /dev/null +++ b/_examples/view/template_html_5/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "github.com/kataras/iris" +) + +func main() { + app := iris.New() + + app.RegisterView(iris.HTML("./views", ".html").Layout("layout.html")) + // TIP: append .Reload(true) to reload the templates on each request. + + app.Get("/home", func(ctx iris.Context) { + ctx.ViewData("title", "Home page"); + ctx.View("home.html") + // Note that: you can pass "layout" : "otherLayout.html" to bypass the config's Layout property + // or view.NoLayout to disable layout on this render action. + // third is an optional parameter + }) + + app.Get("/about", func(ctx iris.Context) { + ctx.View("about.html") + }) + + app.Get("/user/index", func(ctx iris.Context) { + ctx.View("user/index.html") + }) + + // http://localhost:8080 + app.Run(iris.Addr(":8080")) +} diff --git a/_examples/view/template_html_5/views/about.html b/_examples/view/template_html_5/views/about.html new file mode 100644 index 0000000000..b581a12a10 --- /dev/null +++ b/_examples/view/template_html_5/views/about.html @@ -0,0 +1,14 @@ +{{ define "about-head"}} + +{{ end }} + +{{ define "about-body"}} + extend body content in layout. +{{ end }} +
+ Hello about page +
\ No newline at end of file diff --git a/_examples/view/template_html_5/views/home.html b/_examples/view/template_html_5/views/home.html new file mode 100644 index 0000000000..365990090f --- /dev/null +++ b/_examples/view/template_html_5/views/home.html @@ -0,0 +1,11 @@ +{{ define "home-head"}} + {{.title}} + +{{ end }} +
+ Hello home page +
\ No newline at end of file diff --git a/_examples/view/template_html_5/views/layout.html b/_examples/view/template_html_5/views/layout.html new file mode 100644 index 0000000000..c374205f7e --- /dev/null +++ b/_examples/view/template_html_5/views/layout.html @@ -0,0 +1,11 @@ + + +{{ part "head" }} + + +

[layout] Body content is below...

+ {{ part "body" }} + + {{ yield }} + + diff --git a/_examples/view/template_html_5/views/user/index.html b/_examples/view/template_html_5/views/user/index.html new file mode 100644 index 0000000000..6c73fb2e4a --- /dev/null +++ b/_examples/view/template_html_5/views/user/index.html @@ -0,0 +1,10 @@ +{{ define "user/index-head"}} + +{{ end }} +
+ Hello user index page +
\ No newline at end of file From 0184e08b3fb2199551eb281b140a8fadaefdec19 Mon Sep 17 00:00:00 2001 From: chenPengXu Date: Sat, 27 Oct 2018 20:53:24 +0800 Subject: [PATCH 54/91] modify --- _examples/view/template_html_5/views/about.html | 1 + 1 file changed, 1 insertion(+) diff --git a/_examples/view/template_html_5/views/about.html b/_examples/view/template_html_5/views/about.html index b581a12a10..a264299265 100644 --- a/_examples/view/template_html_5/views/about.html +++ b/_examples/view/template_html_5/views/about.html @@ -1,4 +1,5 @@ {{ define "about-head"}} + about page