diff --git a/_examples/view/embedding-templates-into-app/templates/hi.html b/_examples/view/embedding-templates-into-app/templates/hi.html index 3719b3e1..ae537135 100644 --- a/_examples/view/embedding-templates-into-app/templates/hi.html +++ b/_examples/view/embedding-templates-into-app/templates/hi.html @@ -1,8 +1,11 @@ + -{{.Title}} + {{.Title}} +

Hi {{.Name}} - + + \ No newline at end of file diff --git a/context/context.go b/context/context.go index 7c33059e..d8895dae 100644 --- a/context/context.go +++ b/context/context.go @@ -71,15 +71,15 @@ 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 params should implement. -// RequestValues is for communication between middleware, RequestParams cannot be changed, are setted at the routing -// time, stores the dynamic named parameters, can be empty if the route is static. +// 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 shouldn't be used as a local storage, context's values store -// is the local storage, not params. +// 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) } @@ -97,17 +97,38 @@ func (r RequestParams) Get(key string) string { return r.store.GetString(key) } -// GetInt returns the param's value as int, based on its 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. func (r RequestParams) GetInt(key string) (int, error) { return r.store.GetInt(key) } -// GetInt64 returns the user's value as int64, based on its key. +// GetInt64 returns the path paramete's value as int64, based on its key. func (r RequestParams) GetInt64(key string) (int64, error) { return r.store.GetInt64(key) } -// GetBool returns the user's value as bool, based on its key. +// GetFloat64 returns a path parameter's value based as float64 on its route's dynamic path key. +func (r RequestParams) GetFloat64(key string) (float64, error) { + return strconv.ParseFloat(r.Get(key), 64) +} + +// 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. @@ -115,11 +136,6 @@ func (r RequestParams) GetBool(key string) (bool, error) { return r.store.GetBool(key) } -// GetDecoded returns the url-query-decoded user's value based on its key. -func (r RequestParams) GetDecoded(key string) string { - return DecodeQuery(DecodeQuery(r.Get(key))) -} - // GetIntUnslashed same as Get but it removes the first slash if found. // Usage: Get an id from a wildcard path. // @@ -350,12 +366,24 @@ type Context interface { // URLParam returns the get parameter from a request , if any. URLParam(name string) string + // URLParamTrim returns the url query parameter with trailing white spaces removed from a request, + // returns an error if parse failed. + URLParamTrim(name string) string + // URLParamTrim returns the escaped url query parameter from a request, + // returns an error if parse failed. + URLParamEscape(name string) string // URLParamInt returns the url query parameter as int value from a request, // returns an error if parse failed. URLParamInt(name string) (int, error) // URLParamInt64 returns the url query parameter as int64 value from a request, // returns an error if parse failed. URLParamInt64(name string) (int64, error) + // URLParamInt64 returns the url query parameter as float64 value from a request, + // returns an error if parse failed. + URLParamFloat64(name string) (float64, error) + // URLParamBool returns the url query parameter as boolean value from a request, + // returns an error if parse failed. + URLParamBool(name string) (bool, error) // URLParams returns a map of GET query parameters separated by comma if more than one // it returns an empty map if nothing found. URLParams() map[string]string @@ -367,9 +395,27 @@ type Context interface { // // NOTE: A check for nil is necessary. FormValues() map[string][]string + // PostValue returns a form's only-post value by its name, // same as Request.PostFormValue. PostValue(name string) string + // PostValueTrim returns a form's only-post value without trailing spaces by its name. + PostValueTrim(name string) string + // PostValueEscape returns a form's only-post escaped value by its name. + PostValueEscape(name string) string + // PostValueInt returns a form's only-post value as int by its name. + PostValueInt(name string) (int, error) + // PostValueInt64 returns a form's only-post value as int64 by its name. + PostValueInt64(name string) (int64, error) + // PostValueFloat64 returns a form's only-post value as float64 by its name. + PostValueFloat64(name string) (float64, error) + // PostValue returns a form's only-post value as boolean by its name. + PostValueBool(name string) (bool, error) + // PostValues returns a form's only-post values. + // PostValues calls ParseMultipartForm and ParseForm if necessary and ignores + // any errors returned by these functions. + PostValues(name string) []string + // FormFile returns the first file for the provided form key. // FormFile calls ctx.Request.ParseMultipartForm and ParseForm if necessary. // @@ -1298,6 +1344,18 @@ func (ctx *context) URLParam(name string) string { return ctx.request.URL.Query().Get(name) } +// URLParamTrim returns the url query parameter with trailing white spaces removed from a request, +// returns an error if parse failed. +func (ctx *context) URLParamTrim(name string) string { + return strings.TrimSpace(ctx.URLParam(name)) +} + +// URLParamTrim returns the escaped url query parameter from a request, +// returns an error if parse failed. +func (ctx *context) URLParamEscape(name string) string { + return DecodeQuery(ctx.URLParam(name)) +} + // URLParamInt returns the url query parameter as int value from a request, // returns an error if parse failed. func (ctx *context) URLParamInt(name string) (int, error) { @@ -1310,6 +1368,18 @@ func (ctx *context) URLParamInt64(name string) (int64, error) { return strconv.ParseInt(ctx.URLParam(name), 10, 64) } +// URLParamInt64 returns the url query parameter as float64 value from a request, +// returns an error if parse failed. +func (ctx *context) URLParamFloat64(name string) (float64, error) { + return strconv.ParseFloat(ctx.URLParam(name), 64) +} + +// URLParamBool returns the url query parameter as boolean value from a request, +// returns an error if parse failed. +func (ctx *context) URLParamBool(name string) (bool, error) { + return strconv.ParseBool(ctx.URLParam(name)) +} + // URLParams returns a map of GET query parameters separated by comma if more than one // it returns an empty map if nothing found. func (ctx *context) URLParams() map[string]string { @@ -1358,6 +1428,57 @@ func (ctx *context) PostValue(name string) string { return ctx.request.PostFormValue(name) } +// PostValueTrim returns a form's only-post value without trailing spaces by its name. +func (ctx *context) PostValueTrim(name string) string { + return strings.TrimSpace(ctx.PostValue(name)) +} + +// PostValueEscape returns a form's only-post escaped value by its name. +func (ctx *context) PostValueEscape(name string) string { + return DecodeQuery(ctx.PostValue(name)) +} + +// PostValueInt returns a form's only-post value as int by its name. +func (ctx *context) PostValueInt(name string) (int, error) { + return strconv.Atoi(ctx.PostValue(name)) +} + +// PostValueInt64 returns a form's only-post value as int64 by its name. +func (ctx *context) PostValueInt64(name string) (int64, error) { + return strconv.ParseInt(ctx.PostValue(name), 10, 64) +} + +// PostValueFloat64 returns a form's only-post value as float64 by its name. +func (ctx *context) PostValueFloat64(name string) (float64, error) { + return strconv.ParseFloat(ctx.PostValue(name), 64) +} + +// PostValue returns a form's only-post value as boolean by its name. +func (ctx *context) PostValueBool(name string) (bool, error) { + return strconv.ParseBool(ctx.PostValue(name)) +} + +const ( + // DefaultMaxMemory is the default value + // for post values' max memory, defaults to + // 32MB. + // Can be also changed by the middleware `LimitRequestBodySize` + // or `context#SetMaxRequestBodySize`. + DefaultMaxMemory = 32 << 20 // 32 MB +) + +// PostValues returns a form's only-post values. +// PostValues calls ParseMultipartForm and ParseForm if necessary and ignores +// any errors returned by these functions. +func (ctx *context) PostValues(name string) []string { + r := ctx.request + if r.PostForm == nil { + r.ParseMultipartForm(DefaultMaxMemory) + } + + return r.PostForm[name] +} + // FormFile returns the first file for the provided form key. // FormFile calls ctx.request.ParseMultipartForm and ParseForm if necessary. // diff --git a/view/django.go b/view/django.go index d0f74d71..ad4e78ec 100644 --- a/view/django.go +++ b/view/django.go @@ -22,6 +22,26 @@ type ( Error pongo2.Error // FilterFunction conversion for pongo2.FilterFunction FilterFunction func(in *Value, param *Value) (out *Value, err *Error) + + // Parser conversion for pongo2.Parser + Parser pongo2.Parser + // Token conversion for pongo2.Token + Token pongo2.Token + // INodeTag conversion for pongo2.InodeTag + INodeTag pongo2.INodeTag + // TagParser the function signature of the tag's parser you will have + // to implement in order to create a new tag. + // + // 'doc' is providing access to the whole document while 'arguments' + // is providing access to the user's arguments to the tag: + // + // {% your_tag_name some "arguments" 123 %} + // + // start_token will be the *Token with the tag's name in it (here: your_tag_name). + // + // Please see the Parser documentation on how to use the parser. + // See `RegisterTag` for more information about writing a tag as well. + TagParser func(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) ) type tDjangoAssetLoader struct { @@ -119,19 +139,58 @@ func (s *DjangoEngine) AddFunc(funcName string, funcBody interface{}) { s.rmu.Unlock() } -// AddFilter adds a filter to the template. +// AddFilter registers a new filter. If there's already a filter with the same +// name, RegisterFilter will panic. You usually want to call this +// function in the filter's init() function: +// http://golang.org/doc/effective_go.html#init +// +// Same as `RegisterFilter`. func (s *DjangoEngine) AddFilter(filterName string, filterBody FilterFunction) *DjangoEngine { - s.rmu.Lock() - s.filters[filterName] = filterBody - s.rmu.Unlock() + return s.registerFilter(filterName, filterBody) +} + +// RegisterFilter registers a new filter. If there's already a filter with the same +// name, RegisterFilter will panic. You usually want to call this +// function in the filter's init() function: +// http://golang.org/doc/effective_go.html#init +// +// See http://www.florian-schlachter.de/post/pongo2/ for more about +// writing filters and tags. +func (s *DjangoEngine) RegisterFilter(filterName string, filterBody FilterFunction) *DjangoEngine { + return s.registerFilter(filterName, filterBody) +} + +func (s *DjangoEngine) registerFilter(filterName string, filterBody FilterFunction) *DjangoEngine { + fn := pongo2.FilterFunction(func(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { + theOut, theErr := filterBody((*Value)(in), (*Value)(param)) + return (*pongo2.Value)(theOut), (*pongo2.Error)(theErr) + }) + pongo2.RegisterFilter(filterName, fn) + return s } +// RegisterTag registers a new tag. You usually want to call this +// function in the tag's init() function: +// http://golang.org/doc/effective_go.html#init +// +// See http://www.florian-schlachter.de/post/pongo2/ for more about +// writing filters and tags. +func (s *DjangoEngine) RegisterTag(tagName string, parserFn TagParser) error { + fn := func(doc *pongo2.Parser, start *pongo2.Token, arguments *pongo2.Parser) (pongo2.INodeTag, *pongo2.Error) { + t, err := parserFn((*Parser)(doc), (*Token)(start), (*Parser)(arguments)) + return t, (*pongo2.Error)(err) + } + + return pongo2.RegisterTag(tagName, fn) +} + // Load parses the templates to the engine. // It's alos responsible to add the necessary global functions. // // Returns an error if something bad happens, user is responsible to catch it. func (s *DjangoEngine) Load() error { + if s.assetFn != nil && s.namesFn != nil { // embedded return s.loadAssets() @@ -147,22 +206,6 @@ func (s *DjangoEngine) Load() error { return s.loadDirectory() } -// this exists because of moving the pongo2 to the vendors without conflictitions if users -// wants to register pongo2 filters they can use this django.FilterFunc to do so. -func (s *DjangoEngine) convertFilters() map[string]pongo2.FilterFunction { - filters := make(map[string]pongo2.FilterFunction, len(s.filters)) - for k, v := range s.filters { - func(filterName string, filterFunc FilterFunction) { - fn := pongo2.FilterFunction(func(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) { - theOut, theErr := filterFunc((*Value)(in), (*Value)(param)) - return (*pongo2.Value)(theOut), (*pongo2.Error)(theErr) - }) - filters[filterName] = fn - }(k, v) - } - return filters -} - // LoadDirectory loads the templates from directory. func (s *DjangoEngine) loadDirectory() (templateErr error) { dir, extension := s.directory, s.extension @@ -175,12 +218,6 @@ func (s *DjangoEngine) loadDirectory() (templateErr error) { set := pongo2.NewSet("", fsLoader) set.Globals = getPongoContext(s.globals) - // set the filters - filters := s.convertFilters() - for filterName, filterFunc := range filters { - pongo2.RegisterFilter(filterName, filterFunc) - } - s.mu.Lock() defer s.mu.Unlock() @@ -234,12 +271,6 @@ func (s *DjangoEngine) loadAssets() error { set := pongo2.NewSet("", &tDjangoAssetLoader{baseDir: s.directory, assetGet: s.assetFn}) set.Globals = getPongoContext(s.globals) - // set the filters - filters := s.convertFilters() - for filterName, filterFunc := range filters { - pongo2.RegisterFilter(filterName, filterFunc) - } - if len(virtualDirectory) > 0 { if virtualDirectory[0] == '.' { // first check for .wrong virtualDirectory = virtualDirectory[1:]