Fix some things on an experimental future feature.
Still dirty code ofc, it will take a long to be adopted, but new
developers can watch the backstage's step-by-step test-driven solutions.


Former-commit-id: 739260b8662bff7164ba75ef36c81a6b564d53cc
This commit is contained in:
Gerasimos (Makis) Maropoulos 2017-03-22 05:07:30 +02:00
parent 1ac6f08a97
commit 5d518d4440
4 changed files with 115 additions and 48 deletions

View File

@ -19,9 +19,12 @@ I am a realistic person. If things won't change for my daily life I will be forc
Iris is free and open source but developing it has taken thousands of hours of my time and a large part of my sanity. If you feel this web framework useful to you, it would go a great way to ensuring that I can afford to take the time to continue to develop it. Iris is free and open source but developing it has taken thousands of hours of my time and a large part of my sanity. If you feel this web framework useful to you, it would go a great way to ensuring that I can afford to take the time to continue to develop it.
Thanks for your gratitude and finance help ♡
[![](https://raw.githubusercontent.com/iris-contrib/website/gh-pages/assets/donate.png)](https://www.paypal.me/kataras/25eur) I spend all my time in the construction of Iris, therefore I have no income value.
Feel free to send **any** amount through paypal
[![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
Some of the benefits are listed here: Some of the benefits are listed here:
@ -31,8 +34,6 @@ Some of the benefits are listed here:
## More about your donations ## More about your donations
[Juan Sebastián Suárez Valencia](https://github.com/Juanses) donated 20 EUR at September 11 [Juan Sebastián Suárez Valencia](https://github.com/Juanses) donated 20 EUR at September 11
[Bob Lee](https://github.com/li3p) donated 20 EUR at September 16 [Bob Lee](https://github.com/li3p) donated 20 EUR at September 16
@ -53,10 +54,12 @@ Some of the benefits are listed here:
[Lex Tang](https://github.com/lexrus) donated 20 EUR at February 22 of 2017 [Lex Tang](https://github.com/lexrus) donated 20 EUR at February 22 of 2017
*ANYNOMOUS* donated 336 EUR *ANONYMOUS* donated 356 EUR, last anonymous donation at March 22 of 2017
> The names, shown at the [supporters](https://github.com/kataras/iris#heroes-) list, are sorted by **date** and **NOT by the amount** of the donation. > The names, shown at the [supporters](https://github.com/kataras/iris#heroes-) list, are sorted by **date** and **NOT by the amount** of the donation.
> *ANONYMOUS*: People who donate but don't want to be shown here. *ANONYMOUS* are listed as one group instead of an individual entry, in order to protect their exact date of their donation. > *ANONYMOUS*: People who donate but don't want to be shown here. *ANONYMOUS* are listed as one group instead of an individual entry, in order to protect their exact date of their donation.
# Thanks for your gratitude and finance help ♡

View File

@ -89,7 +89,9 @@ Legends [♡](https://github.com/kataras/iris#support)
[Lex Tang](https://github.com/lexrus) donated 20 EUR at February 22 of 2017 [Lex Tang](https://github.com/lexrus) donated 20 EUR at February 22 of 2017
ANYNOMOUS[*](https://github.com/kataras/iris#support) donated 336 EUR <!-- ANONYMOUS[*](https://github.com/kataras/iris#support) donated 356 EUR, last anonymous donation at 22 March of 2017
// No o need to show anonymous donations here, the full list is still shown at DONATIONS.md.
-->
Feature Overview Feature Overview
----------- -----------
@ -326,7 +328,7 @@ I spend all my time in the construction of Iris, therefore I have no income valu
Feel free to send **any** amount through paypal Feel free to send **any** amount through paypal
[![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.me/kataras/) [![](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=kataras2006%40hotmail%2ecom&lc=GR&item_name=Iris%20web%20framework&item_number=iriswebframeworkdonationid2016&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
> Please check your e-mail after your donation. > Please check your e-mail after your donation.

View File

@ -12,6 +12,7 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"testing" "testing"
"gopkg.in/kataras/iris.v6" "gopkg.in/kataras/iris.v6"
@ -35,7 +36,8 @@ func fromRegexp(expr string) _macrofn {
if err != nil { if err != nil {
panic(err) panic(err)
} }
return func(pathParamValue string, _ *iris.Context) bool { return r.MatchString(pathParamValue) }
return r.MatchString
} }
// link the path tmpl with macros, at .Boot time, before Listen. // link the path tmpl with macros, at .Boot time, before Listen.
@ -59,7 +61,7 @@ func link(path string, mac _macros) iris.HandlerFunc {
} }
paramValue := ctx.Param(paramName) paramValue := ctx.Param(paramName)
if paramValue != "" { if paramValue != "" {
valid := validator(paramValue, ctx) valid := validator(paramValue)
if !valid { if !valid {
// print("not valid for validator on paramValue= '" + paramValue + "' ctx.Pos = ") // print("not valid for validator on paramValue= '" + paramValue + "' ctx.Pos = ")
// println(ctx.Pos) // it should be always 0. // println(ctx.Pos) // it should be always 0.
@ -92,9 +94,9 @@ func link(path string, mac _macros) iris.HandlerFunc {
if hasFunc { if hasFunc {
prevEval := eval prevEval := eval
macroFuncEval := fi.eval(mi.Params) macroFuncEval := fi.eval(mi.Params)
eval = func(pvalue string, ctx *iris.Context) bool { eval = func(pvalue string) bool {
if prevEval(pvalue, ctx) { if prevEval(pvalue) {
return macroFuncEval(pvalue, ctx) return macroFuncEval(pvalue)
} }
return false return false
} }
@ -135,7 +137,7 @@ func testMacros(source string) error {
} }
// let's give the macro's funcs access to context, it will be great experimental to serve templates just with a path signature // let's give the macro's funcs access to context, it will be great experimental to serve templates just with a path signature
type _macrofn func(pathParamValue string, ctx *iris.Context) bool type _macrofn func(pathParamValue string) bool
type _macrofunc struct { type _macrofunc struct {
name string name string
@ -164,6 +166,7 @@ func addMacroFunc(macroName string, funcName string, v func([]string) _macrofn)
const global_macro = "any" const global_macro = "any"
// func(min int, max int) func(paramValue string)bool
func macroFuncFrom(v interface{}) func(params []string) _macrofn { func macroFuncFrom(v interface{}) func(params []string) _macrofn {
// this is executed once on boot time, not at serve time: // this is executed once on boot time, not at serve time:
vot := reflect.TypeOf(v) vot := reflect.TypeOf(v)
@ -178,19 +181,32 @@ func macroFuncFrom(v interface{}) func(params []string) _macrofn {
// check for accepting arguments // check for accepting arguments
for i := 0; i < numFields; i++ { for i := 0; i < numFields; i++ {
field := vot.In(i) field := vot.In(i)
param := params[i]
// if field.IsVariadic() {
// panic("variadic arguments are not supported") // or they will do ?
// }
var val interface{}
var err error
switch field.Kind() { switch field.Kind() {
case reflect.Int: // these can be transfered to another function with supported type conversions
val, err := strconv.Atoi(params[i]) // the dev can also be able to modify how a string converted to x kind of type,
if err != nil { // even custom type, i.e User{}, (I have to give an easy way to do hard things
panic("invalid first parameter: " + err.Error()) // but also extensibility for devs that are experienced,
} // like I did with the rest of the features).
args = append(args, reflect.ValueOf(val))
case reflect.String: case reflect.String:
val = param
case reflect.Int:
val, err = strconv.Atoi(param)
case reflect.Bool: case reflect.Bool:
val, err = strconv.ParseBool(param)
default: default:
panic("unsported type!") panic("unsported type!")
} }
if err != nil {
panic(err)
}
args = append(args, reflect.ValueOf(val))
} }
// check for the return type (only one ofc, which again is a function but it returns a boolean) // check for the return type (only one ofc, which again is a function but it returns a boolean)
@ -215,12 +231,19 @@ func macroFuncFrom(v interface{}) func(params []string) _macrofn {
panic("expecting this func to receive one arg") panic("expecting this func to receive one arg")
} }
vof := reflect.ValueOf(v).Call(args)[0].Interface().(func(string) bool) vofi := reflect.ValueOf(v).Call(args)[0].Interface()
var validator _macrofn
// check for typed and not typed
if _v, ok := vofi.(_macrofn); ok {
validator = _v
} else if _v, ok = vofi.(func(string) bool); ok {
validator = _v
}
// //
// this is executed when a route requested: // this is executed when a route requested:
return func(paramValue string, _ *iris.Context) bool { return func(paramValue string) bool {
return vof(paramValue) return validator(paramValue)
} }
// //
} }
@ -229,7 +252,8 @@ func macroFuncFrom(v interface{}) func(params []string) _macrofn {
func TestMacros(t *testing.T) { func TestMacros(t *testing.T) {
addMacro("int", fromRegexp("[1-9]+$")) addMacro("int", fromRegexp("[1-9]+$"))
// {id:int range(42,49)} // // {id:int range(42,49)}
// // "hard" manually way(it will not be included on the final feature(;)):
// addMacroFunc("int", "range", func(params []string) _macrofn { // addMacroFunc("int", "range", func(params []string) _macrofn {
// // start: .Boot time, before .Listen // // start: .Boot time, before .Listen
// allowedParamsLen := 2 // allowedParamsLen := 2
@ -248,7 +272,7 @@ func TestMacros(t *testing.T) {
// } // }
// // end // // end
// return func(paramValue string, _ *iris.Context) bool { // return func(paramValue string) bool {
// paramValueInt, err := strconv.Atoi(paramValue) // paramValueInt, err := strconv.Atoi(paramValue)
// if err != nil { // if err != nil {
// return false // return false
@ -259,6 +283,10 @@ func TestMacros(t *testing.T) {
// return false // return false
// } // }
// }) // })
//
// {id:int range(42,49)}
// easy way, same performance as the hard way, no cost while serving requests.
// ::
// result should be like that in the final feature implementation, using reflection BEFORE .Listen on .Boot time, // result should be like that in the final feature implementation, using reflection BEFORE .Listen on .Boot time,
// so no performance cost(done) => // so no performance cost(done) =>
addMacroFunc("int", "range", macroFuncFrom(func(min int, max int) func(string) bool { addMacroFunc("int", "range", macroFuncFrom(func(min int, max int) func(string) bool {
@ -275,7 +303,7 @@ func TestMacros(t *testing.T) {
})) }))
addMacroFunc("int", "even", func(params []string) _macrofn { addMacroFunc("int", "even", func(params []string) _macrofn {
return func(paramValue string, _ *iris.Context) bool { return func(paramValue string) bool {
paramValueInt, err := strconv.Atoi(paramValue) paramValueInt, err := strconv.Atoi(paramValue)
if err != nil { if err != nil {
return false return false
@ -288,21 +316,30 @@ func TestMacros(t *testing.T) {
}) })
// "any" will contain macros functions // "any" will contain macros functions
// which are available to all other, we will need some functions to be 'globally' registered when don't care about // which are available to all other, we will need some functions to be 'globally' registered when don't care about.
// what macro is used, for example let's try the markdown(#hello), yes serve files without even call a function:)
addMacro("any", fromRegexp(".*")) addMacro("any", fromRegexp(".*"))
addMacroFunc("any", "markdown", func(params []string) _macrofn { addMacroFunc("any", "contains", macroFuncFrom(func(text string) _macrofn {
if len(params) != 1 { return func(paramValue string) bool {
panic("markdown expected 1 arg") return strings.Contains(paramValue, text)
} }
}))
addMacroFunc("any", "suffix", macroFuncFrom(func(text string) _macrofn {
return func(paramValue string) bool {
return strings.HasSuffix(paramValue, text)
}
}))
contents := params[0] addMacro("string", fromRegexp("[a-zA-Z]+$"))
// we don't care about path's parameter here // this will 'override' the "any contains"
return func(_ string, ctx *iris.Context) bool { // when string macro is used:
ctx.Markdown(iris.StatusOK, contents) addMacroFunc("string", "contains", macroFuncFrom(func(text string) _macrofn {
return true return func(paramValue string) bool {
println("from string:contains instead of any:string")
println("'" + text + "' vs '" + paramValue + "'")
return strings.Contains(paramValue, text)
} }
}) }))
path := "/api/users/{id:int range(42,49) even() !600}/posts" path := "/api/users/{id:int range(42,49) even() !600}/posts"
app := iris.New() app := iris.New()
@ -320,9 +357,18 @@ func TestMacros(t *testing.T) {
ctx.ResponseWriter.WriteString(ctx.Path()) ctx.ResponseWriter.WriteString(ctx.Path())
}) })
path2 := "/markdown/{anything:any markdown(**hello**)}" path2 := "/markdown/{file:any suffix(.md)}"
hv2 := link(path2, all_macros) hv2 := link(path2, all_macros)
app.Get("/markdown/*anything", hv2) app.Get("/markdown/*file", hv2, func(ctx *iris.Context) {
ctx.Markdown(iris.StatusOK, "**hello**")
})
// contains a space(on tests)
path3 := "/hello/{fullname:string contains( )}"
hv3 := link(path3, all_macros)
app.Get("/hello/:fullname", hv3, func(ctx *iris.Context) {
ctx.Writef("hello %s", ctx.Param("fullname"))
})
e := httptest.New(app, t) e := httptest.New(app, t)
@ -341,5 +387,7 @@ func TestMacros(t *testing.T) {
// response with "path language" only no need of handler too. // response with "path language" only no need of handler too.
// As it goes I love the idea and users will embrace and built awesome things on top of it. // As it goes I love the idea and users will embrace and built awesome things on top of it.
// maybe I have to 'rename' the final feature on something like iris expression language and document it as much as I can, people will love that // maybe I have to 'rename' the final feature on something like iris expression language and document it as much as I can, people will love that
e.GET("/markdown/something").Expect().Status(iris.StatusOK).ContentType("text/html", "utf-8").Body().Equal("<p><strong>hello</strong></p>\n") e.GET("/markdown/something.md").Expect().Status(iris.StatusOK).ContentType("text/html", "utf-8").Body().Equal("<p><strong>hello</strong></p>\n")
e.GET("/hello/Makis Maropoulos").Expect().Status(iris.StatusOK).Body().Equal("hello Makis Maropoulos")
e.GET("/hello/MakisMaropoulos").Expect().Status(iris.StatusNotFound) // no space -> invalidate -> fail status code
} }

View File

@ -73,12 +73,15 @@ func ParseParam(source string) (*ParamTmpl, error) {
// id:int min(1) max(5) // id:int min(1) max(5)
// id:int range(1,5)!404 or !404, space doesn't matters on fail error code. // id:int range(1,5)!404 or !404, space doesn't matters on fail error code.
cursor := 0 cursor := 0
// hasFuncs setted to true when we validate that we have macro's functions // waitForFunc setted to true when we validate that we have macro's functions
// so we can check for parenthesis. // so we can check for parenthesis.
// We need that check because the user may add a regexp with parenthesis. // We need that check because the user may add a regexp with parenthesis.
// Although this will not be recommended, user is better to create a macro for its regexp // Although this will not be recommended, user is better to create a macro for its regexp
// in order to use it everywhere and reduce code duplication. // in order to use it everywhere and reduce code duplication.
hasFuncs := false waitForFunc := false
// when inside macro func we don't need to check for anything else, because it could
// break the tmpl, i.e FuncSeperator (space) if "contains( )".
insideFunc := false
for i := 0; i < len(source); i++ { for i := 0; i < len(source); i++ {
if source[i] == ParamNameSeperator { if source[i] == ParamNameSeperator {
if i+1 >= len(source) { if i+1 >= len(source) {
@ -101,20 +104,26 @@ func ParseParam(source string) (*ParamTmpl, error) {
cursor = i + 1 cursor = i + 1
continue continue
} }
// TODO: find a better way instead of introducing variables like waitForFunc, insideFunc,
// one way is to move the functions with the reverse order but this can fix the problem for now
// later it will introduce new bugs, we can find a better static way to check these things, tomorrow.
// int ... // int ...
if source[i] == FuncSeperator { if !waitForFunc && source[i] == FuncSeperator {
// take the left part: int if it's the first // take the left part: int if it's the first
// space after the param name // space after the param name
if t.Macro.Name == t.Expression { if t.Macro.Name == t.Expression {
hasFuncs = true
t.Macro.Name = source[cursor:i] t.Macro.Name = source[cursor:i]
} // else we have one or more functions, skip. } // else we have one or more functions, skip.
waitForFunc = true
cursor = i + 1 cursor = i + 1
continue continue
} }
// if not inside a func body
if hasFuncs && source[i] == FuncStart { // the cursor is a point which can receive a func
// starts with (
if !insideFunc && waitForFunc && source[i] == FuncStart {
insideFunc = true
// take the left part: range // take the left part: range
funcName := source[cursor:i] funcName := source[cursor:i]
t.Macro.Funcs = append(t.Macro.Funcs, MacroFuncTmpl{Name: funcName}) t.Macro.Funcs = append(t.Macro.Funcs, MacroFuncTmpl{Name: funcName})
@ -123,7 +132,8 @@ func ParseParam(source string) (*ParamTmpl, error) {
continue continue
} }
// 1,5) // 1,5)
if hasFuncs && source[i] == FuncEnd { // we are inside func and )
if insideFunc && source[i] == FuncEnd {
// check if we have end parenthesis but not start // check if we have end parenthesis but not start
if len(t.Macro.Funcs) == 0 { if len(t.Macro.Funcs) == 0 {
return nil, fmt.Errorf("missing start macro's '%s' function, on source '%s'", t.Macro.Name, source) return nil, fmt.Errorf("missing start macro's '%s' function, on source '%s'", t.Macro.Name, source)
@ -131,11 +141,15 @@ func ParseParam(source string) (*ParamTmpl, error) {
// take the left part, between Start and End: 1,5 // take the left part, between Start and End: 1,5
funcParamsStr := source[cursor:i] funcParamsStr := source[cursor:i]
println("param_parser.go:41: '" + funcParamsStr + "'")
funcParams := strings.SplitN(funcParamsStr, string(FuncParamSeperator), -1) funcParams := strings.SplitN(funcParamsStr, string(FuncParamSeperator), -1)
t.Macro.Funcs[len(t.Macro.Funcs)-1].Params = funcParams t.Macro.Funcs[len(t.Macro.Funcs)-1].Params = funcParams
cursor = i + 1 cursor = i + 1
insideFunc = false // ignore ')' until new '('
waitForFunc = false // wait for the next space to not ignore '('
continue continue
} }