From 5d518d4440a3a9f7ef68b8dcab1a84db229b5164 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 22 Mar 2017 05:07:30 +0200 Subject: [PATCH] _future 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 --- DONATIONS.md | 13 +++-- README.md | 6 ++- _future/macros_test.go | 114 ++++++++++++++++++++++++++++------------ _future/param_parser.go | 30 ++++++++--- 4 files changed, 115 insertions(+), 48 deletions(-) diff --git a/DONATIONS.md b/DONATIONS.md index 115e6021..56e85886 100644 --- a/DONATIONS.md +++ b/DONATIONS.md @@ -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. -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¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) Some of the benefits are listed here: @@ -31,8 +34,6 @@ Some of the benefits are listed here: ## More about your donations - - [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 @@ -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 -*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. > *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 ♡ \ No newline at end of file diff --git a/README.md b/README.md index 21c45148..88eb730d 100644 --- a/README.md +++ b/README.md @@ -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 -ANYNOMOUS[*](https://github.com/kataras/iris#support) donated 336 EUR + 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 -[![](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¤cy_code=EUR&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted) > Please check your e-mail after your donation. diff --git a/_future/macros_test.go b/_future/macros_test.go index 40689063..4e28911d 100644 --- a/_future/macros_test.go +++ b/_future/macros_test.go @@ -12,6 +12,7 @@ import ( "reflect" "regexp" "strconv" + "strings" "testing" "gopkg.in/kataras/iris.v6" @@ -35,7 +36,8 @@ func fromRegexp(expr string) _macrofn { if err != nil { 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. @@ -59,7 +61,7 @@ func link(path string, mac _macros) iris.HandlerFunc { } paramValue := ctx.Param(paramName) if paramValue != "" { - valid := validator(paramValue, ctx) + valid := validator(paramValue) if !valid { // print("not valid for validator on paramValue= '" + paramValue + "' ctx.Pos = ") // println(ctx.Pos) // it should be always 0. @@ -92,9 +94,9 @@ func link(path string, mac _macros) iris.HandlerFunc { if hasFunc { prevEval := eval macroFuncEval := fi.eval(mi.Params) - eval = func(pvalue string, ctx *iris.Context) bool { - if prevEval(pvalue, ctx) { - return macroFuncEval(pvalue, ctx) + eval = func(pvalue string) bool { + if prevEval(pvalue) { + return macroFuncEval(pvalue) } 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 -type _macrofn func(pathParamValue string, ctx *iris.Context) bool +type _macrofn func(pathParamValue string) bool type _macrofunc struct { name string @@ -164,6 +166,7 @@ func addMacroFunc(macroName string, funcName string, v func([]string) _macrofn) const global_macro = "any" +// func(min int, max int) func(paramValue string)bool func macroFuncFrom(v interface{}) func(params []string) _macrofn { // this is executed once on boot time, not at serve time: vot := reflect.TypeOf(v) @@ -178,19 +181,32 @@ func macroFuncFrom(v interface{}) func(params []string) _macrofn { // check for accepting arguments for i := 0; i < numFields; 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() { - case reflect.Int: - val, err := strconv.Atoi(params[i]) - if err != nil { - panic("invalid first parameter: " + err.Error()) - } - args = append(args, reflect.ValueOf(val)) + // these can be transfered to another function with supported type conversions + // the dev can also be able to modify how a string converted to x kind of type, + // even custom type, i.e User{}, (I have to give an easy way to do hard things + // but also extensibility for devs that are experienced, + // like I did with the rest of the features). case reflect.String: + val = param + case reflect.Int: + val, err = strconv.Atoi(param) case reflect.Bool: + val, err = strconv.ParseBool(param) + default: 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) @@ -215,12 +231,19 @@ func macroFuncFrom(v interface{}) func(params []string) _macrofn { 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: - return func(paramValue string, _ *iris.Context) bool { - return vof(paramValue) + return func(paramValue string) bool { + return validator(paramValue) } // } @@ -229,7 +252,8 @@ func macroFuncFrom(v interface{}) func(params []string) _macrofn { func TestMacros(t *testing.T) { 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 { // // start: .Boot time, before .Listen // allowedParamsLen := 2 @@ -248,7 +272,7 @@ func TestMacros(t *testing.T) { // } // // end - // return func(paramValue string, _ *iris.Context) bool { + // return func(paramValue string) bool { // paramValueInt, err := strconv.Atoi(paramValue) // if err != nil { // return false @@ -259,6 +283,10 @@ func TestMacros(t *testing.T) { // 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, // so no performance cost(done) => 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 { - return func(paramValue string, _ *iris.Context) bool { + return func(paramValue string) bool { paramValueInt, err := strconv.Atoi(paramValue) if err != nil { return false @@ -288,21 +316,30 @@ func TestMacros(t *testing.T) { }) // "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 - // what macro is used, for example let's try the markdown(#hello), yes serve files without even call a function:) + // which are available to all other, we will need some functions to be 'globally' registered when don't care about. addMacro("any", fromRegexp(".*")) - addMacroFunc("any", "markdown", func(params []string) _macrofn { - if len(params) != 1 { - panic("markdown expected 1 arg") + addMacroFunc("any", "contains", macroFuncFrom(func(text string) _macrofn { + return func(paramValue string) bool { + 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] - // we don't care about path's parameter here - return func(_ string, ctx *iris.Context) bool { - ctx.Markdown(iris.StatusOK, contents) - return true + addMacro("string", fromRegexp("[a-zA-Z]+$")) + // this will 'override' the "any contains" + // when string macro is used: + addMacroFunc("string", "contains", macroFuncFrom(func(text string) _macrofn { + 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" app := iris.New() @@ -320,9 +357,18 @@ func TestMacros(t *testing.T) { ctx.ResponseWriter.WriteString(ctx.Path()) }) - path2 := "/markdown/{anything:any markdown(**hello**)}" + path2 := "/markdown/{file:any suffix(.md)}" 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) @@ -341,5 +387,7 @@ func TestMacros(t *testing.T) { // 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. // 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("

hello

\n") + e.GET("/markdown/something.md").Expect().Status(iris.StatusOK).ContentType("text/html", "utf-8").Body().Equal("

hello

\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 } diff --git a/_future/param_parser.go b/_future/param_parser.go index 4f34dcfe..0331d8cd 100644 --- a/_future/param_parser.go +++ b/_future/param_parser.go @@ -73,12 +73,15 @@ func ParseParam(source string) (*ParamTmpl, error) { // id:int min(1) max(5) // id:int range(1,5)!404 or !404, space doesn't matters on fail error code. 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. // 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 // 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++ { if source[i] == ParamNameSeperator { if i+1 >= len(source) { @@ -101,20 +104,26 @@ func ParseParam(source string) (*ParamTmpl, error) { cursor = i + 1 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 ... - if source[i] == FuncSeperator { + if !waitForFunc && source[i] == FuncSeperator { // take the left part: int if it's the first // space after the param name if t.Macro.Name == t.Expression { - hasFuncs = true t.Macro.Name = source[cursor:i] } // else we have one or more functions, skip. - + waitForFunc = true cursor = i + 1 continue } - - if hasFuncs && source[i] == FuncStart { + // if not inside a func body + // 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 funcName := source[cursor:i] t.Macro.Funcs = append(t.Macro.Funcs, MacroFuncTmpl{Name: funcName}) @@ -123,7 +132,8 @@ func ParseParam(source string) (*ParamTmpl, error) { continue } // 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 if len(t.Macro.Funcs) == 0 { 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 funcParamsStr := source[cursor:i] + println("param_parser.go:41: '" + funcParamsStr + "'") funcParams := strings.SplitN(funcParamsStr, string(FuncParamSeperator), -1) t.Macro.Funcs[len(t.Macro.Funcs)-1].Params = funcParams cursor = i + 1 + + insideFunc = false // ignore ')' until new '(' + waitForFunc = false // wait for the next space to not ignore '(' continue }