pax_global_header00006660000000000000000000000064126541317040014515gustar00rootroot0000000000000052 comment=26c1cde6f1acba6ec0a54547d68ef575559348f1 macaron-1/000077500000000000000000000000001265413170400126375ustar00rootroot00000000000000macaron-1/.gitignore000066400000000000000000000000611265413170400146240ustar00rootroot00000000000000macaron.sublime-project macaron.sublime-workspacemacaron-1/.travis.yml000066400000000000000000000002001265413170400147400ustar00rootroot00000000000000sudo: false language: go go: - 1.3 - 1.4 - 1.5 script: go test -v -cover -race notifications: email: - u@gogs.io macaron-1/LICENSE000066400000000000000000000240411265413170400136450ustar00rootroot00000000000000Apache 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: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and 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 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. macaron-1/README.md000066400000000000000000000076161265413170400141300ustar00rootroot00000000000000Macaron [![Build Status](https://travis-ci.org/go-macaron/macaron.svg?branch=v1)](https://travis-ci.org/go-macaron/macaron) [![](http://gocover.io/_badge/github.com/go-macaron/macaron)](http://gocover.io/github.com/go-macaron/macaron) ======================= ![Macaron Logo](https://raw.githubusercontent.com/go-macaron/macaron/v1/macaronlogo.png) Package macaron is a high productive and modular web framework in Go. ##### Current version: 1.0.0 ## Getting Started The minimum requirement of Go is **1.3**. To install Macaron: go get gopkg.in/macaron.v1 The very basic usage of Macaron: ```go package main import "gopkg.in/macaron.v1" func main() { m := macaron.Classic() m.Get("/", func() string { return "Hello world!" }) m.Run() } ``` ## Features - Powerful routing with suburl. - Flexible routes combinations. - Unlimited nested group routers. - Directly integrate with existing services. - Dynamically change template files at runtime. - Allow to use in-memory template and static files. - Easy to plugin/unplugin features with modular design. - Handy dependency injection powered by [inject](https://github.com/codegangsta/inject). - Better router layer and less reflection make faster speed. ## Middlewares Middlewares allow you easily plugin/unplugin features for your Macaron applications. There are already many [middlewares](https://github.com/go-macaron) to simplify your work: - render - Go template engine - static - Serves static files - [gzip](https://github.com/go-macaron/gzip) - Gzip compression to all responses - [binding](https://github.com/go-macaron/binding) - Request data binding and validation - [i18n](https://github.com/go-macaron/i18n) - Internationalization and Localization - [cache](https://github.com/go-macaron/cache) - Cache manager - [session](https://github.com/go-macaron/session) - Session manager - [csrf](https://github.com/go-macaron/csrf) - Generates and validates csrf tokens - [captcha](https://github.com/go-macaron/captcha) - Captcha service - [pongo2](https://github.com/go-macaron/pongo2) - Pongo2 template engine support - [sockets](https://github.com/go-macaron/sockets) - WebSockets channels binding - [bindata](https://github.com/go-macaron/bindata) - Embed binary data as static and template files - [toolbox](https://github.com/go-macaron/toolbox) - Health check, pprof, profile and statistic services - [oauth2](https://github.com/go-macaron/oauth2) - OAuth 2.0 backend - [switcher](https://github.com/go-macaron/switcher) - Multiple-site support - [method](https://github.com/go-macaron/method) - HTTP method override - [permissions2](https://github.com/xyproto/permissions2) - Cookies, users and permissions - [renders](https://github.com/go-macaron/renders) - Beego-like render engine(Macaron has built-in template engine, this is another option) ## Use Cases - [Gogs](https://gogs.io): A painless self-hosted Git Service - [Peach](https://peachdocs.org): A modern web documentation server - [Go Walker](https://gowalker.org): Go online API documentation - [Switch](https://gopm.io): Gopm registry - [YouGam](http://yougam.com): Online Forum - [Critical Stack Intel](https://intel.criticalstack.com/): A 100% free intel marketplace from Critical Stack, Inc. ## Getting Help - [API Reference](https://gowalker.org/gopkg.in/macaron.v1) - [Documentation](https://go-macaron.com) - [FAQs](https://go-macaron.com/docs/faqs) - [![Join the chat at https://gitter.im/Unknwon/macaron](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-macaron/macaron?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Credits - Basic design of [Martini](https://github.com/go-martini/martini). - Logo is modified by [@insionng](https://github.com/insionng) based on [Tribal Dragon](http://xtremeyamazaki.deviantart.com/art/Tribal-Dragon-27005087). ## License This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. macaron-1/context.go000066400000000000000000000314461265413170400146620ustar00rootroot00000000000000// Copyright 2014 The Macaron Authors // // 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 macaron import ( "crypto/md5" "encoding/hex" "html/template" "io" "io/ioutil" "mime/multipart" "net/http" "net/url" "os" "path" "path/filepath" "reflect" "strconv" "strings" "time" "github.com/Unknwon/com" "github.com/go-macaron/inject" ) // Locale reprents a localization interface. type Locale interface { Language() string Tr(string, ...interface{}) string } // RequestBody represents a request body. type RequestBody struct { reader io.ReadCloser } // Bytes reads and returns content of request body in bytes. func (rb *RequestBody) Bytes() ([]byte, error) { return ioutil.ReadAll(rb.reader) } // String reads and returns content of request body in string. func (rb *RequestBody) String() (string, error) { data, err := rb.Bytes() return string(data), err } // ReadCloser returns a ReadCloser for request body. func (rb *RequestBody) ReadCloser() io.ReadCloser { return rb.reader } // Request represents an HTTP request received by a server or to be sent by a client. type Request struct { *http.Request } func (r *Request) Body() *RequestBody { return &RequestBody{r.Request.Body} } // Context represents the runtime context of current request of Macaron instance. // It is the integration of most frequently used middlewares and helper methods. type Context struct { inject.Injector handlers []Handler action Handler index int *Router Req Request Resp ResponseWriter params Params Render Locale Data map[string]interface{} } func (c *Context) handler() Handler { if c.index < len(c.handlers) { return c.handlers[c.index] } if c.index == len(c.handlers) { return c.action } panic("invalid index for context handler") } func (c *Context) Next() { c.index += 1 c.run() } func (c *Context) Written() bool { return c.Resp.Written() } func (c *Context) run() { for c.index <= len(c.handlers) { vals, err := c.Invoke(c.handler()) if err != nil { panic(err) } c.index += 1 // if the handler returned something, write it to the http response if len(vals) > 0 { ev := c.GetVal(reflect.TypeOf(ReturnHandler(nil))) handleReturn := ev.Interface().(ReturnHandler) handleReturn(c, vals) } if c.Written() { return } } } // RemoteAddr returns more real IP address. func (ctx *Context) RemoteAddr() string { addr := ctx.Req.Header.Get("X-Real-IP") if len(addr) == 0 { addr = ctx.Req.Header.Get("X-Forwarded-For") if addr == "" { addr = ctx.Req.RemoteAddr if i := strings.LastIndex(addr, ":"); i > -1 { addr = addr[:i] } } } return addr } func (ctx *Context) renderHTML(status int, setName, tplName string, data ...interface{}) { if len(data) <= 0 { ctx.Render.HTMLSet(status, setName, tplName, ctx.Data) } else if len(data) == 1 { ctx.Render.HTMLSet(status, setName, tplName, data[0]) } else { ctx.Render.HTMLSet(status, setName, tplName, data[0], data[1].(HTMLOptions)) } } // HTML calls Render.HTML but allows less arguments. func (ctx *Context) HTML(status int, name string, data ...interface{}) { ctx.renderHTML(status, _DEFAULT_TPL_SET_NAME, name, data...) } // HTML calls Render.HTMLSet but allows less arguments. func (ctx *Context) HTMLSet(status int, setName, tplName string, data ...interface{}) { ctx.renderHTML(status, setName, tplName, data...) } func (ctx *Context) Redirect(location string, status ...int) { code := http.StatusFound if len(status) == 1 { code = status[0] } http.Redirect(ctx.Resp, ctx.Req.Request, location, code) } // Maximum amount of memory to use when parsing a multipart form. // Set this to whatever value you prefer; default is 10 MB. var MaxMemory = int64(1024 * 1024 * 10) func (ctx *Context) parseForm() { if ctx.Req.Form != nil { return } contentType := ctx.Req.Header.Get(_CONTENT_TYPE) if (ctx.Req.Method == "POST" || ctx.Req.Method == "PUT") && len(contentType) > 0 && strings.Contains(contentType, "multipart/form-data") { ctx.Req.ParseMultipartForm(MaxMemory) } else { ctx.Req.ParseForm() } } // Query querys form parameter. func (ctx *Context) Query(name string) string { ctx.parseForm() return ctx.Req.Form.Get(name) } // QueryTrim querys and trims spaces form parameter. func (ctx *Context) QueryTrim(name string) string { return strings.TrimSpace(ctx.Query(name)) } // QueryStrings returns a list of results by given query name. func (ctx *Context) QueryStrings(name string) []string { ctx.parseForm() vals, ok := ctx.Req.Form[name] if !ok { return []string{} } return vals } // QueryEscape returns escapred query result. func (ctx *Context) QueryEscape(name string) string { return template.HTMLEscapeString(ctx.Query(name)) } // QueryInt returns query result in int type. func (ctx *Context) QueryInt(name string) int { return com.StrTo(ctx.Query(name)).MustInt() } // QueryInt64 returns query result in int64 type. func (ctx *Context) QueryInt64(name string) int64 { return com.StrTo(ctx.Query(name)).MustInt64() } // QueryFloat64 returns query result in float64 type. func (ctx *Context) QueryFloat64(name string) float64 { v, _ := strconv.ParseFloat(ctx.Query(name), 64) return v } // Params returns value of given param name. // e.g. ctx.Params(":uid") or ctx.Params("uid") func (ctx *Context) Params(name string) string { if len(name) == 0 { return "" } if len(name) > 1 && name[0] != ':' { name = ":" + name } return ctx.params[name] } // SetParams sets value of param with given name. func (ctx *Context) SetParams(name, val string) { if !strings.HasPrefix(name, ":") { name = ":" + name } ctx.params[name] = val } // ParamsEscape returns escapred params result. // e.g. ctx.ParamsEscape(":uname") func (ctx *Context) ParamsEscape(name string) string { return template.HTMLEscapeString(ctx.Params(name)) } // ParamsInt returns params result in int type. // e.g. ctx.ParamsInt(":uid") func (ctx *Context) ParamsInt(name string) int { return com.StrTo(ctx.Params(name)).MustInt() } // ParamsInt64 returns params result in int64 type. // e.g. ctx.ParamsInt64(":uid") func (ctx *Context) ParamsInt64(name string) int64 { return com.StrTo(ctx.Params(name)).MustInt64() } // ParamsFloat64 returns params result in int64 type. // e.g. ctx.ParamsFloat64(":uid") func (ctx *Context) ParamsFloat64(name string) float64 { v, _ := strconv.ParseFloat(ctx.Params(name), 64) return v } // GetFile returns information about user upload file by given form field name. func (ctx *Context) GetFile(name string) (multipart.File, *multipart.FileHeader, error) { return ctx.Req.FormFile(name) } // SaveToFile reads a file from request by field name and saves to given path. func (ctx *Context) SaveToFile(name, savePath string) error { fr, _, err := ctx.GetFile(name) if err != nil { return err } defer fr.Close() fw, err := os.OpenFile(savePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { return err } defer fw.Close() _, err = io.Copy(fw, fr) return err } // SetCookie sets given cookie value to response header. // FIXME: IE support? http://golanghome.com/post/620#reply2 func (ctx *Context) SetCookie(name string, value string, others ...interface{}) { cookie := http.Cookie{} cookie.Name = name cookie.Value = url.QueryEscape(value) if len(others) > 0 { switch v := others[0].(type) { case int: cookie.MaxAge = v case int64: cookie.MaxAge = int(v) case int32: cookie.MaxAge = int(v) } } cookie.Path = "/" if len(others) > 1 { if v, ok := others[1].(string); ok && len(v) > 0 { cookie.Path = v } } if len(others) > 2 { if v, ok := others[2].(string); ok && len(v) > 0 { cookie.Domain = v } } if len(others) > 3 { switch v := others[3].(type) { case bool: cookie.Secure = v default: if others[3] != nil { cookie.Secure = true } } } if len(others) > 4 { if v, ok := others[4].(bool); ok && v { cookie.HttpOnly = true } } ctx.Resp.Header().Add("Set-Cookie", cookie.String()) } // GetCookie returns given cookie value from request header. func (ctx *Context) GetCookie(name string) string { cookie, err := ctx.Req.Cookie(name) if err != nil { return "" } val, _ := url.QueryUnescape(cookie.Value) return val } // GetCookieInt returns cookie result in int type. func (ctx *Context) GetCookieInt(name string) int { return com.StrTo(ctx.GetCookie(name)).MustInt() } // GetCookieInt64 returns cookie result in int64 type. func (ctx *Context) GetCookieInt64(name string) int64 { return com.StrTo(ctx.GetCookie(name)).MustInt64() } // GetCookieFloat64 returns cookie result in float64 type. func (ctx *Context) GetCookieFloat64(name string) float64 { v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64) return v } var defaultCookieSecret string // SetDefaultCookieSecret sets global default secure cookie secret. func (m *Macaron) SetDefaultCookieSecret(secret string) { defaultCookieSecret = secret } // SetSecureCookie sets given cookie value to response header with default secret string. func (ctx *Context) SetSecureCookie(name, value string, others ...interface{}) { ctx.SetSuperSecureCookie(defaultCookieSecret, name, value, others...) } // GetSecureCookie returns given cookie value from request header with default secret string. func (ctx *Context) GetSecureCookie(key string) (string, bool) { return ctx.GetSuperSecureCookie(defaultCookieSecret, key) } // SetSuperSecureCookie sets given cookie value to response header with secret string. func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) { m := md5.Sum([]byte(secret)) secret = hex.EncodeToString(m[:]) text, err := com.AESEncrypt([]byte(secret), []byte(value)) if err != nil { panic("error encrypting cookie: " + err.Error()) } ctx.SetCookie(name, hex.EncodeToString(text), others...) } // GetSuperSecureCookie returns given cookie value from request header with secret string. func (ctx *Context) GetSuperSecureCookie(secret, key string) (string, bool) { val := ctx.GetCookie(key) if val == "" { return "", false } data, err := hex.DecodeString(val) if err != nil { return "", false } m := md5.Sum([]byte(secret)) secret = hex.EncodeToString(m[:]) text, err := com.AESDecrypt([]byte(secret), data) return string(text), err == nil } func (ctx *Context) setRawContentHeader() { ctx.Resp.Header().Set("Content-Description", "Raw content") ctx.Resp.Header().Set("Content-Type", "text/plain") ctx.Resp.Header().Set("Expires", "0") ctx.Resp.Header().Set("Cache-Control", "must-revalidate") ctx.Resp.Header().Set("Pragma", "public") } // ServeContent serves given content to response. func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) { modtime := time.Now() for _, p := range params { switch v := p.(type) { case time.Time: modtime = v } } ctx.setRawContentHeader() http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r) } // ServeFileContent serves given file as content to response. func (ctx *Context) ServeFileContent(file string, names ...string) { var name string if len(names) > 0 { name = names[0] } else { name = path.Base(file) } f, err := os.Open(file) if err != nil { if Env == PROD { http.Error(ctx.Resp, "Internal Server Error", 500) } else { http.Error(ctx.Resp, err.Error(), 500) } return } defer f.Close() ctx.setRawContentHeader() http.ServeContent(ctx.Resp, ctx.Req.Request, name, time.Now(), f) } // ServeFile serves given file to response. func (ctx *Context) ServeFile(file string, names ...string) { var name string if len(names) > 0 { name = names[0] } else { name = path.Base(file) } ctx.Resp.Header().Set("Content-Description", "File Transfer") ctx.Resp.Header().Set("Content-Type", "application/octet-stream") ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name) ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary") ctx.Resp.Header().Set("Expires", "0") ctx.Resp.Header().Set("Cache-Control", "must-revalidate") ctx.Resp.Header().Set("Pragma", "public") http.ServeFile(ctx.Resp, ctx.Req.Request, file) } // ChangeStaticPath changes static path from old to new one. func (ctx *Context) ChangeStaticPath(oldPath, newPath string) { if !filepath.IsAbs(oldPath) { oldPath = filepath.Join(Root, oldPath) } dir := statics.Get(oldPath) if dir != nil { statics.Delete(oldPath) if !filepath.IsAbs(newPath) { newPath = filepath.Join(Root, newPath) } *dir = http.Dir(newPath) statics.Set(dir) } } macaron-1/context_test.go000066400000000000000000000254601265413170400157200ustar00rootroot00000000000000// Copyright 2014 The Macaron Authors // // 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 macaron import ( "bytes" "io/ioutil" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" "github.com/Unknwon/com" . "github.com/smartystreets/goconvey/convey" ) func Test_Context(t *testing.T) { Convey("Do advanced encapsulation operations", t, func() { m := Classic() m.Use(Renderers(RenderOptions{ Directory: "fixtures/basic", }, "fixtures/basic2")) Convey("Get request body", func() { m.Get("/body1", func(ctx *Context) { data, err := ioutil.ReadAll(ctx.Req.Body().ReadCloser()) So(err, ShouldBeNil) So(string(data), ShouldEqual, "This is my request body") }) m.Get("/body2", func(ctx *Context) { data, err := ctx.Req.Body().Bytes() So(err, ShouldBeNil) So(string(data), ShouldEqual, "This is my request body") }) m.Get("/body3", func(ctx *Context) { data, err := ctx.Req.Body().String() So(err, ShouldBeNil) So(data, ShouldEqual, "This is my request body") }) for i := 1; i <= 3; i++ { resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/body"+com.ToStr(i), nil) req.Body = ioutil.NopCloser(bytes.NewBufferString("This is my request body")) So(err, ShouldBeNil) m.ServeHTTP(resp, req) } }) Convey("Get remote IP address", func() { m.Get("/remoteaddr", func(ctx *Context) string { return ctx.RemoteAddr() }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/remoteaddr", nil) req.RemoteAddr = "127.0.0.1:3333" So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "127.0.0.1") }) Convey("Render HTML", func() { Convey("Normal HTML", func() { m.Get("/html", func(ctx *Context) { ctx.HTML(304, "hello", "Unknwon") // 304 for logger test. }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/html", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "

Hello Unknwon

") }) Convey("HTML template set", func() { m.Get("/html2", func(ctx *Context) { ctx.Data["Name"] = "Unknwon" ctx.HTMLSet(200, "basic2", "hello2") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/html2", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "

Hello Unknwon

") }) Convey("With layout", func() { m.Get("/layout", func(ctx *Context) { ctx.HTML(200, "hello", "Unknwon", HTMLOptions{"layout"}) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/layout", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "head

Hello Unknwon

foot") }) }) Convey("Parse from and query", func() { m.Get("/query", func(ctx *Context) string { var buf bytes.Buffer buf.WriteString(ctx.QueryTrim("name") + " ") buf.WriteString(ctx.QueryEscape("name") + " ") buf.WriteString(com.ToStr(ctx.QueryInt("int")) + " ") buf.WriteString(com.ToStr(ctx.QueryInt64("int64")) + " ") buf.WriteString(com.ToStr(ctx.QueryFloat64("float64")) + " ") return buf.String() }) m.Get("/query2", func(ctx *Context) string { var buf bytes.Buffer buf.WriteString(strings.Join(ctx.QueryStrings("list"), ",") + " ") buf.WriteString(strings.Join(ctx.QueryStrings("404"), ",") + " ") return buf.String() }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/query?name=Unknwon&int=12&int64=123&float64=1.25", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "Unknwon Unknwon 12 123 1.25 ") resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/query2?list=item1&list=item2", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "item1,item2 ") }) Convey("URL parameter", func() { m.Get("/:name/:int/:int64/:float64", func(ctx *Context) string { var buf bytes.Buffer ctx.SetParams("name", ctx.Params("name")) buf.WriteString(ctx.Params("")) buf.WriteString(ctx.Params(":name") + " ") buf.WriteString(ctx.ParamsEscape(":name") + " ") buf.WriteString(com.ToStr(ctx.ParamsInt(":int")) + " ") buf.WriteString(com.ToStr(ctx.ParamsInt64(":int64")) + " ") buf.WriteString(com.ToStr(ctx.ParamsFloat64(":float64")) + " ") return buf.String() }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/user/1/13/1.24", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "user user 1 13 1.24 ") }) Convey("Get file", func() { m.Post("/getfile", func(ctx *Context) { ctx.Query("") ctx.GetFile("hi") }) resp := httptest.NewRecorder() req, err := http.NewRequest("POST", "/getfile", nil) So(err, ShouldBeNil) req.Header.Set("Content-Type", "multipart/form-data") m.ServeHTTP(resp, req) }) Convey("Set and get cookie", func() { m.Get("/set", func(ctx *Context) { ctx.SetCookie("user", "Unknwon", 1, "/", "localhost", true, true) ctx.SetCookie("user", "Unknwon", int32(1), "/", "localhost", 1) ctx.SetCookie("user", "Unknwon", int64(1)) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/set", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Header().Get("Set-Cookie"), ShouldEqual, "user=Unknwon; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure") m.Get("/get", func(ctx *Context) string { ctx.GetCookie("404") So(ctx.GetCookieInt("uid"), ShouldEqual, 1) So(ctx.GetCookieInt64("uid"), ShouldEqual, 1) So(ctx.GetCookieFloat64("balance"), ShouldEqual, 1.25) return ctx.GetCookie("user") }) resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/get", nil) So(err, ShouldBeNil) req.Header.Set("Cookie", "user=Unknwon; uid=1; balance=1.25") m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "Unknwon") }) Convey("Set and get secure cookie", func() { m.SetDefaultCookieSecret("macaron") m.Get("/set", func(ctx *Context) { ctx.SetSecureCookie("user", "Unknwon", 1) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/set", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) cookie := resp.Header().Get("Set-Cookie") m.Get("/get", func(ctx *Context) string { name, ok := ctx.GetSecureCookie("user") So(ok, ShouldBeTrue) return name }) resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/get", nil) So(err, ShouldBeNil) req.Header.Set("Cookie", cookie) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "Unknwon") }) Convey("Serve files", func() { m.Get("/file", func(ctx *Context) { ctx.ServeFile("fixtures/custom_funcs/index.tmpl") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/file", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}") m.Get("/file2", func(ctx *Context) { ctx.ServeFile("fixtures/custom_funcs/index.tmpl", "ok.tmpl") }) resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/file2", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}") }) Convey("Serve file content", func() { m.Get("/file", func(ctx *Context) { ctx.ServeFileContent("fixtures/custom_funcs/index.tmpl") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/file", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}") m.Get("/file2", func(ctx *Context) { ctx.ServeFileContent("fixtures/custom_funcs/index.tmpl", "ok.tmpl") }) resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/file2", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}") m.Get("/file3", func(ctx *Context) { ctx.ServeFileContent("404.tmpl") }) resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/file3", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "open 404.tmpl: no such file or directory\n") So(resp.Code, ShouldEqual, 500) }) Convey("Serve content", func() { m.Get("/content", func(ctx *Context) { ctx.ServeContent("content1", bytes.NewReader([]byte("Hello world!"))) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/content", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "Hello world!") m.Get("/content2", func(ctx *Context) { ctx.ServeContent("content1", bytes.NewReader([]byte("Hello world!")), time.Now()) }) resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/content2", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "Hello world!") }) }) } func Test_Context_Render(t *testing.T) { Convey("Invalid render", t, func() { defer func() { So(recover(), ShouldNotBeNil) }() m := New() m.Get("/", func(ctx *Context) { ctx.HTML(200, "hey") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) }) } func Test_Context_Redirect(t *testing.T) { Convey("Context with default redirect", t, func() { url, err := url.Parse("http://localhost/path/one") So(err, ShouldBeNil) resp := httptest.NewRecorder() req := http.Request{ Method: "GET", URL: url, } ctx := &Context{ Req: Request{&req}, Resp: NewResponseWriter(resp), Data: make(map[string]interface{}), } ctx.Redirect("two") So(resp.Code, ShouldEqual, http.StatusFound) So(resp.HeaderMap["Location"][0], ShouldEqual, "/path/two") }) Convey("Context with custom redirect", t, func() { url, err := url.Parse("http://localhost/path/one") So(err, ShouldBeNil) resp := httptest.NewRecorder() req := http.Request{ Method: "GET", URL: url, } ctx := &Context{ Req: Request{&req}, Resp: NewResponseWriter(resp), Data: make(map[string]interface{}), } ctx.Redirect("two", 307) So(resp.Code, ShouldEqual, http.StatusTemporaryRedirect) So(resp.HeaderMap["Location"][0], ShouldEqual, "/path/two") }) } macaron-1/fixtures/000077500000000000000000000000001265413170400145105ustar00rootroot00000000000000macaron-1/fixtures/basic/000077500000000000000000000000001265413170400155715ustar00rootroot00000000000000macaron-1/fixtures/basic/admin/000077500000000000000000000000001265413170400166615ustar00rootroot00000000000000macaron-1/fixtures/basic/admin/index.tmpl000066400000000000000000000000241265413170400206620ustar00rootroot00000000000000

Admin {{.}}

macaron-1/fixtures/basic/another_layout.tmpl000066400000000000000000000000431265413170400215210ustar00rootroot00000000000000another head{{ yield }}another footmacaron-1/fixtures/basic/content.tmpl000066400000000000000000000000201265413170400201310ustar00rootroot00000000000000

{{ . }}

macaron-1/fixtures/basic/current_layout.tmpl000066400000000000000000000000571265413170400215500ustar00rootroot00000000000000{{ current }} head{{ yield }}{{ current }} footmacaron-1/fixtures/basic/delims.tmpl000066400000000000000000000000261265413170400177420ustar00rootroot00000000000000

Hello {[{.}]}

macaron-1/fixtures/basic/hello.tmpl000066400000000000000000000000241265413170400175660ustar00rootroot00000000000000

Hello {{.}}

macaron-1/fixtures/basic/hypertext.html000066400000000000000000000000121265413170400205040ustar00rootroot00000000000000Hypertext!macaron-1/fixtures/basic/layout.tmpl000066400000000000000000000000231265413170400177770ustar00rootroot00000000000000head{{ yield }}footmacaron-1/fixtures/basic2/000077500000000000000000000000001265413170400156535ustar00rootroot00000000000000macaron-1/fixtures/basic2/hello.tmpl000066400000000000000000000000311265413170400176460ustar00rootroot00000000000000

What's up, {{.}}

macaron-1/fixtures/basic2/hello2.tmpl000066400000000000000000000000301265413170400177270ustar00rootroot00000000000000

Hello {{.Name}}

macaron-1/fixtures/custom_funcs/000077500000000000000000000000001265413170400172205ustar00rootroot00000000000000macaron-1/fixtures/custom_funcs/index.tmpl000066400000000000000000000000221265413170400212170ustar00rootroot00000000000000{{ myCustomFunc }}macaron-1/fixtures/symlink000077700000000000000000000000001265413170400171202basicustar00rootroot00000000000000macaron-1/logger.go000066400000000000000000000033641265413170400144530ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "fmt" "log" "net/http" "runtime" "time" ) var ColorLog = true func init() { ColorLog = runtime.GOOS != "windows" } // Logger returns a middleware handler that logs the request as it goes in and the response as it goes out. func Logger() Handler { return func(ctx *Context, log *log.Logger) { start := time.Now() log.Printf("Started %s %s for %s", ctx.Req.Method, ctx.Req.RequestURI, ctx.RemoteAddr()) rw := ctx.Resp.(ResponseWriter) ctx.Next() content := fmt.Sprintf("Completed %s %v %s in %v", ctx.Req.RequestURI, rw.Status(), http.StatusText(rw.Status()), time.Since(start)) if ColorLog { switch rw.Status() { case 200, 201, 202: content = fmt.Sprintf("\033[1;32m%s\033[0m", content) case 301, 302: content = fmt.Sprintf("\033[1;37m%s\033[0m", content) case 304: content = fmt.Sprintf("\033[1;33m%s\033[0m", content) case 401, 403: content = fmt.Sprintf("\033[4;31m%s\033[0m", content) case 404: content = fmt.Sprintf("\033[1;31m%s\033[0m", content) case 500: content = fmt.Sprintf("\033[1;36m%s\033[0m", content) } } log.Println(content) } } macaron-1/logger_test.go000066400000000000000000000035351265413170400155120ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "bytes" "log" "net/http" "net/http/httptest" "testing" "github.com/Unknwon/com" . "github.com/smartystreets/goconvey/convey" ) func Test_Logger(t *testing.T) { Convey("Global logger", t, func() { buf := bytes.NewBufferString("") m := New() m.Map(log.New(buf, "[Macaron] ", 0)) m.Use(Logger()) m.Use(func(res http.ResponseWriter) { res.WriteHeader(http.StatusNotFound) }) m.Get("/", func() {}) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://localhost:4000/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusNotFound) So(len(buf.String()), ShouldBeGreaterThan, 0) }) if ColorLog { Convey("Color console output", t, func() { m := Classic() m.Get("/:code:int", func(ctx *Context) (int, string) { return ctx.ParamsInt(":code"), "" }) // Just for testing if logger would capture. codes := []int{200, 201, 202, 301, 302, 304, 401, 403, 404, 500} for _, code := range codes { resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://localhost:4000/"+com.ToStr(code), nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, code) } }) } } macaron-1/macaron.go000066400000000000000000000162551265413170400146170ustar00rootroot00000000000000// +build go1.3 // Copyright 2014 The Macaron Authors // // 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 macaron is a high productive and modular web framework in Go. package macaron import ( "io" "log" "net/http" "os" "reflect" "strings" "github.com/Unknwon/com" "gopkg.in/ini.v1" "github.com/go-macaron/inject" ) const _VERSION = "1.0.0.0202" func Version() string { return _VERSION } // Handler can be any callable function. // Macaron attempts to inject services into the handler's argument list, // and panics if an argument could not be fullfilled via dependency injection. type Handler interface{} // validateHandler makes sure a handler is a callable function, // and panics if it is not. func validateHandler(h Handler) { if reflect.TypeOf(h).Kind() != reflect.Func { panic("Macaron handler must be a callable function") } } // validateHandlers makes sure handlers are callable functions, // and panics if any of them is not. func validateHandlers(handlers []Handler) { for _, h := range handlers { validateHandler(h) } } // Macaron represents the top level web application. // inject.Injector methods can be invoked to map services on a global level. type Macaron struct { inject.Injector befores []BeforeHandler handlers []Handler action Handler hasURLPrefix bool urlPrefix string // For suburl support. *Router logger *log.Logger } // NewWithLogger creates a bare bones Macaron instance. // Use this method if you want to have full control over the middleware that is used. // You can specify logger output writer with this function. func NewWithLogger(out io.Writer) *Macaron { m := &Macaron{ Injector: inject.New(), action: func() {}, Router: NewRouter(), logger: log.New(out, "[Macaron] ", 0), } m.Router.m = m m.Map(m.logger) m.Map(defaultReturnHandler()) m.NotFound(http.NotFound) m.InternalServerError(func(rw http.ResponseWriter, err error) { http.Error(rw, err.Error(), 500) }) return m } // New creates a bare bones Macaron instance. // Use this method if you want to have full control over the middleware that is used. func New() *Macaron { return NewWithLogger(os.Stdout) } // Classic creates a classic Macaron with some basic default middleware: // mocaron.Logger, mocaron.Recovery and mocaron.Static. func Classic() *Macaron { m := New() m.Use(Logger()) m.Use(Recovery()) m.Use(Static("public")) return m } // Handlers sets the entire middleware stack with the given Handlers. // This will clear any current middleware handlers, // and panics if any of the handlers is not a callable function func (m *Macaron) Handlers(handlers ...Handler) { m.handlers = make([]Handler, 0) for _, handler := range handlers { m.Use(handler) } } // Action sets the handler that will be called after all the middleware has been invoked. // This is set to macaron.Router in a macaron.Classic(). func (m *Macaron) Action(handler Handler) { validateHandler(handler) m.action = handler } // BeforeHandler represents a handler executes at beginning of every request. // Macaron stops future process when it returns true. type BeforeHandler func(rw http.ResponseWriter, req *http.Request) bool func (m *Macaron) Before(handler BeforeHandler) { m.befores = append(m.befores, handler) } // Use adds a middleware Handler to the stack, // and panics if the handler is not a callable func. // Middleware Handlers are invoked in the order that they are added. func (m *Macaron) Use(handler Handler) { validateHandler(handler) m.handlers = append(m.handlers, handler) } func (m *Macaron) createContext(rw http.ResponseWriter, req *http.Request) *Context { c := &Context{ Injector: inject.New(), handlers: m.handlers, action: m.action, index: 0, Router: m.Router, Req: Request{req}, Resp: NewResponseWriter(rw), Render: &dummyRender{rw}, Data: make(map[string]interface{}), } c.SetParent(m) c.Map(c) c.MapTo(c.Resp, (*http.ResponseWriter)(nil)) c.Map(req) return c } // ServeHTTP is the HTTP Entry point for a Macaron instance. // Useful if you want to control your own HTTP server. // Be aware that none of middleware will run without registering any router. func (m *Macaron) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if m.hasURLPrefix { req.URL.Path = strings.TrimPrefix(req.URL.Path, m.urlPrefix) } for _, h := range m.befores { if h(rw, req) { return } } m.Router.ServeHTTP(rw, req) } func GetDefaultListenInfo() (string, int) { host := os.Getenv("HOST") if len(host) == 0 { host = "0.0.0.0" } port := com.StrTo(os.Getenv("PORT")).MustInt() if port == 0 { port = 4000 } return host, port } // Run the http server. Listening on os.GetEnv("PORT") or 4000 by default. func (m *Macaron) Run(args ...interface{}) { host, port := GetDefaultListenInfo() if len(args) == 1 { switch arg := args[0].(type) { case string: host = arg case int: port = arg } } else if len(args) >= 2 { if arg, ok := args[0].(string); ok { host = arg } if arg, ok := args[1].(int); ok { port = arg } } addr := host + ":" + com.ToStr(port) logger := m.GetVal(reflect.TypeOf(m.logger)).Interface().(*log.Logger) logger.Printf("listening on %s (%s)\n", addr, Env) logger.Fatalln(http.ListenAndServe(addr, m)) } // SetURLPrefix sets URL prefix of router layer, so that it support suburl. func (m *Macaron) SetURLPrefix(prefix string) { m.urlPrefix = prefix m.hasURLPrefix = len(m.urlPrefix) > 0 } // ____ ____ .__ ___. .__ // \ \ / /____ _______|__|____ \_ |__ | | ____ ______ // \ Y /\__ \\_ __ \ \__ \ | __ \| | _/ __ \ / ___/ // \ / / __ \| | \/ |/ __ \| \_\ \ |_\ ___/ \___ \ // \___/ (____ /__| |__(____ /___ /____/\___ >____ > // \/ \/ \/ \/ \/ const ( DEV = "development" PROD = "production" TEST = "test" ) var ( // Env is the environment that Macaron is executing in. // The MACARON_ENV is read on initialization to set this variable. Env = DEV // Path of work directory. Root string // Flash applies to current request. FlashNow bool // Configuration convention object. cfg *ini.File ) func setENV(e string) { if len(e) > 0 { Env = e } } func init() { setENV(os.Getenv("MACARON_ENV")) var err error Root, err = os.Getwd() if err != nil { panic("error getting work directory: " + err.Error()) } } // SetConfig sets data sources for configuration. func SetConfig(source interface{}, others ...interface{}) (_ *ini.File, err error) { cfg, err = ini.Load(source, others...) return Config(), err } // Config returns configuration convention object. // It returns an empty object if there is no one available. func Config() *ini.File { if cfg == nil { return ini.Empty() } return cfg } macaron-1/macaron_test.go000066400000000000000000000117121265413170400156470ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "net/http" "net/http/httptest" "os" "testing" "time" . "github.com/smartystreets/goconvey/convey" ) func Test_Version(t *testing.T) { Convey("Get version", t, func() { So(Version(), ShouldEqual, _VERSION) }) } func Test_New(t *testing.T) { Convey("Initialize a new instance", t, func() { So(New(), ShouldNotBeNil) }) Convey("Just test that Run doesn't bomb", t, func() { go New().Run() time.Sleep(1 * time.Second) os.Setenv("PORT", "4001") go New().Run("0.0.0.0") go New().Run(4002) go New().Run("0.0.0.0", 4003) }) } func Test_Macaron_Before(t *testing.T) { Convey("Register before handlers", t, func() { m := New() m.Before(func(rw http.ResponseWriter, req *http.Request) bool { return false }) m.Before(func(rw http.ResponseWriter, req *http.Request) bool { return true }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) }) } func Test_Macaron_ServeHTTP(t *testing.T) { Convey("Serve HTTP requests", t, func() { result := "" m := New() m.Use(func(c *Context) { result += "foo" c.Next() result += "ban" }) m.Use(func(c *Context) { result += "bar" c.Next() result += "baz" }) m.Get("/", func() {}) m.Action(func(res http.ResponseWriter, req *http.Request) { result += "bat" res.WriteHeader(http.StatusBadRequest) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(result, ShouldEqual, "foobarbatbazban") So(resp.Code, ShouldEqual, http.StatusBadRequest) }) } func Test_Macaron_Handlers(t *testing.T) { Convey("Add custom handlers", t, func() { result := "" batman := func(c *Context) { result += "batman!" } m := New() m.Use(func(c *Context) { result += "foo" c.Next() result += "ban" }) m.Handlers( batman, batman, batman, ) Convey("Add not callable function", func() { defer func() { So(recover(), ShouldNotBeNil) }() m.Use("shit") }) m.Get("/", func() {}) m.Action(func(res http.ResponseWriter, req *http.Request) { result += "bat" res.WriteHeader(http.StatusBadRequest) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(result, ShouldEqual, "batman!batman!batman!bat") So(resp.Code, ShouldEqual, http.StatusBadRequest) }) } func Test_Macaron_EarlyWrite(t *testing.T) { Convey("Write early content to response", t, func() { result := "" m := New() m.Use(func(res http.ResponseWriter) { result += "foobar" res.Write([]byte("Hello world")) }) m.Use(func() { result += "bat" }) m.Get("/", func() {}) m.Action(func(res http.ResponseWriter) { result += "baz" res.WriteHeader(http.StatusBadRequest) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(result, ShouldEqual, "foobar") So(resp.Code, ShouldEqual, http.StatusOK) }) } func Test_Macaron_Written(t *testing.T) { Convey("Written sign", t, func() { resp := httptest.NewRecorder() m := New() m.Handlers(func(res http.ResponseWriter) { res.WriteHeader(http.StatusOK) }) ctx := m.createContext(resp, &http.Request{Method: "GET"}) So(ctx.Written(), ShouldBeFalse) ctx.run() So(ctx.Written(), ShouldBeTrue) }) } func Test_Macaron_Basic_NoRace(t *testing.T) { Convey("Make sure no race between requests", t, func() { m := New() handlers := []Handler{func() {}, func() {}} // Ensure append will not realloc to trigger the race condition m.handlers = handlers[:1] m.Get("/", func() {}) for i := 0; i < 2; i++ { go func() { req, _ := http.NewRequest("GET", "/", nil) resp := httptest.NewRecorder() m.ServeHTTP(resp, req) }() } }) } func Test_SetENV(t *testing.T) { Convey("Get and save environment variable", t, func() { tests := []struct { in string out string }{ {"", "development"}, {"not_development", "not_development"}, } for _, test := range tests { setENV(test.in) So(Env, ShouldEqual, test.out) } }) } func Test_Config(t *testing.T) { Convey("Set and get configuration object", t, func() { So(Config(), ShouldNotBeNil) cfg, err := SetConfig([]byte("")) So(err, ShouldBeNil) So(cfg, ShouldNotBeNil) }) } macaron-1/macaronlogo.png000066400000000000000000002555341265413170400156640ustar00rootroot00000000000000‰PNG  IHDR,,y}Žu AiCCPICC ProfileH –wTSهϽ7½Ð" %ôz Ò;HQ‰I€P†„&vDF)VdTÀG‡"cE ƒ‚b× òPÆÁQDEåÝŒk ï­5óÞšýÇYßÙç·×Ùgï}׺Pü‚ÂtX€4¡XîëÁ\ËÄ÷XÀáffGøDÔü½=™™¨HƳöî.€d»Û,¿P&sÖÿ‘"7C$ EÕ6<~&å”S³Å2ÿÊô•)2†12¡ ¢¬"ãįlö§æ+»É˜—&ä¡Yμ4žŒ»PÞš%ᣌ¡\˜%àg£|e½TIšå÷(ÓÓøœL0™_Ìç&¡l‰2Eî‰ò”Ä9¼r‹ù9hžx¦g䊉Ib¦טiåèÈfúñ³Sùb1+”ÃMáˆxLÏô´ Ž0€¯o–E%Ym™h‘í­ííYÖæhù¿Ùß~Sý=ÈzûUñ&ìÏžAŒžYßlì¬/½ö$Z›³¾•U´m@åá¬Oï ò´Þœó†l^’Äâ ' ‹ììlsŸk.+è7ûŸ‚oÊ¿†9÷™ËîûV;¦?#I3eE妧¦KDÌÌ —Ïdý÷ÿãÀ9iÍÉÃ,œŸÀñ…èUQè” „‰h»…Ø A1ØvƒjpÔzÐN‚6p\WÀ p €G@ †ÁK0Þi‚ð¢Aª¤™BÖZyCAP8ÅC‰’@ùÐ&¨*ƒª¡CP=ô#tº]ƒú Ð 4ý}„˜Óa ض€Ù°;GÂËàDxœÀÛáJ¸>·Âáð,…_“@ÈÑFXñDBX$!k‘"¤©Eš¤¹H‘q䇡a˜Æã‡YŒábVaÖbJ0Õ˜c˜VLæ6f3ù‚¥bÕ±¦X'¬?v 6›-ÄV``[°—±Øaì;ÇÀâp~¸\2n5®·׌»€ëà á&ñx¼*Þï‚Ásðb|!¾ ߯¿' Zk‚!– $l$Tçý„Â4Q¨Ot"†yÄ\b)±ŽØA¼I&N“I†$R$)™´TIj"]&=&½!“É:dGrY@^O®$Ÿ _%’?P”(&OJEBÙN9J¹@y@yC¥R ¨nÔXª˜ºZO½D}J}/G“3—ó—ãÉ­“«‘k•ë—{%O”×—w—_.Ÿ'_!Jþ¦ü¸QÁ@ÁS£°V¡Fá´Â=…IEš¢•bˆbšb‰bƒâ5ÅQ%¼’’·O©@é°Ò%¥!BÓ¥yÒ¸´M´:ÚeÚ0G7¤ûÓ“éÅôè½ô e%e[å(ååå³ÊRÂ0`ø3R¥Œ“Œ»Œó4æ¹ÏãÏÛ6¯i^ÿ¼)•ù*n*|•"•f••ªLUoÕÕªmªOÔ0j&jajÙjûÕ.«Ï§ÏwžÏ_4ÿäü‡ê°º‰z¸újõÃê=ꓚ¾U—4Æ5šnšÉšåšç4Ç´hZ µZåZçµ^0•™îÌTf%³‹9¡­®í§-Ñ>¤Ý«=­c¨³Xg£N³Î]’.[7A·\·SwBOK/X/_¯Qï¡>QŸ­Ÿ¤¿G¿[ÊÀÐ Ú`‹A›Á¨¡Š¡¿aža£ác#ª‘«Ñ*£Z£;Æ8c¶qŠñ>ã[&°‰I’IÉMSØÔÞT`ºÏ´Ï kæh&4«5»Ç¢°ÜYY¬FÖ 9Ã<È|£y›ù+ =‹X‹Ý_,í,S-ë,Y)YXm´ê°úÃÚÄšk]c}džjãc³Î¦Ýæµ­©-ßv¿í};š]°Ý»N»Ïöö"û&û1=‡x‡½÷Øtv(»„}Õëèá¸ÎñŒã'{'±ÓI§ßYÎ)ΠΣ ðÔ-rÑqá¸r‘.d.Œ_xp¡ÔUÛ•ãZëúÌM×çvÄmÄÝØ=Ùý¸û+K‘G‹Ç”§“çÏ ^ˆ—¯W‘W¯·’÷bïjï§>:>‰>>¾v¾«}/øaýývúÝó×ðçú×ûO8¬ è ¤FV> 2 uÃÁÁ»‚/Ò_$\ÔBüCv…< 5 ]ús.,4¬&ìy¸Ux~xw-bEDCÄ»HÈÒÈG‹KwFÉGÅEÕGME{E—EK—X,Y³äFŒZŒ ¦={$vr©÷ÒÝK‡ãìâ ãî.3\–³ìÚrµå©ËÏ®_ÁYq*ßÿ‰©åL®ô_¹wåד»‡û’çÆ+çñ]øeü‘—„²„ÑD—Ä]‰cI®IIãOAµàu²_òä©””£)3©Ñ©Íi„´ø´ÓB%aа+]3='½/Ã4£0CºÊiÕîU¢@Ñ‘L(sYf»˜ŽþLõHŒ$›%ƒY ³j²ÞgGeŸÊQÌæôäšänËÉóÉû~5f5wug¾vþ†üÁ5îk­…Ö®\Û¹Nw]Áºáõ¾ëm mHÙðËFËeßnŠÞÔQ Q°¾`h³ïæÆB¹BQá½-Î[lÅllíÝf³­jÛ—"^ÑõbËâŠâO%Ü’ëßY}WùÝÌö„í½¥ö¥ûwàvwÜÝéºóX™bY^ÙЮà]­åÌò¢ò·»Wì¾Va[q`id´2¨²½J¯jGÕ§ê¤êšæ½ê{·íÚÇÛ׿ßmÓÅ>¼È÷Pk­AmÅaÜá¬ÃÏë¢êº¿g_DíHñ‘ÏG…G¥ÇÂuÕ;Ô×7¨7”6’ƱãqÇoýàõC{«éP3£¹ø8!9ñâÇøïž <ÙyŠ}ªé'ýŸö¶ÐZŠZ¡ÖÜÖ‰¶¤6i{L{ßé€ÓÎ-?›ÿ|ôŒö™š³ÊgKϑΜ›9Ÿw~òBÆ…ñ‹‰‡:Wt>º´äÒ®°®ÞË—¯^ñ¹r©Û½ûüU—«g®9];}}½í†ýÖ»ž–_ì~iéµïm½ép³ý–ã­Ž¾}çú]û/Þöº}åŽÿ‹úî.¾{ÿ^Ü=é}ÞýÑ©^?Ìz8ýhýcìã¢' O*žª?­ýÕø×f©½ôì ×`ϳˆg†¸C/ÿ•ù¯OÃÏ©Ï+F´FêG­GÏŒùŒÝz±ôÅðËŒ—Óã…¿)þ¶÷•Ñ«Ÿ~wû½gbÉÄðkÑë™?JÞ¨¾9úÖömçdèäÓwi獵ŠÞ«¾?öý¡ûcôÇ‘éìOøO•Ÿ?w| üòx&mfæß÷„óû2:Y~ pHYs  šœÍiTXtXML:com.adobe.xmp 2014-10-13T04:52:43 Adobe Photoshop CC 2014 (Macintosh) 2014-10-13T16:41:06 1 72 2 72 1 1000 1000 ”çÚý@IDATxì}€UÅÕÿÜòzÛ·}—Ž4Áņ…EQ ¨°(6DÁ’Ø¢Ñ/‰ |_‰ýÅ bcm ‚‚ TT:,Ëö¾¯÷òÿ¹ï.oi¢¢ÙÅ;°ï¶©çÎüî9gΜaL 4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ(ÐÍ( t³újÕíÊH¥”þ4kŽ3™‹ê:sO…7–• Ã&MJuܙŔ8 '3g*÷aÏóŽˆÚ‰F…JÓ¨¡QààX*Å&••‰ŒMbÃòXºß”³¡%%©MŒ¥€K)á0€M  ø†âoSy9Ê)á5ÛXÂR¬Œ±²I,É^¼lgGäÓtÇ;"Û¦5ê;S %—X)€iX^žPg³ £O:)Y*‰CÌJ`®•–’AÅFÉ¢“D&K}BŸdYJ¢lä8“ã‰x8)¦äx ‹&Y<‘ÄåÕÍvß™”“<”²æ¥RÒòõëÅbßI©Í,5lÓ¬”ËE\𡚨ýºk °ºë›;,õN ®¸¥rÎ RY KK¶Q?ég¡ÙÂòL:©·^ÒòDQì! ,?)ˆfäd˜ÐË„€©”ŒûÈ_¤kˆ’d©$qGL !%$9ûƘñZ’‰¤WdB‘ZðWJ°–x2YŽ&+}¾@kÙç-Ì5çÕåqÆQŒîpãWÄsq|#v‹Îy$$@ýΜ `æDfÐë™,3fÀþ3˜ýq!™¬Aä-ÉdòËX<ùY«7¸áµq½w!qrÁµ›­*Iº\Ô-t' ¨]¨;ÕY«ë·QƒÜUΤ¡Ðí”–vÖ?{G¯âûi:I:C”¤ÓC£Ù–ÔÉ,  G,²x¬RJH‹ˆI@ä9Š&é"~‡×'£+eœ~[U3ŸS2TaOH_tðiÑÐÀk¥£¢|Q–édFp}z°eR,É’a@H¦¶\?ŽF4z‚Ÿ¼qQŸŠ=™#ƒTJœnÓµ  ¦W&iºìù÷ìZ]¶=?ߊq*—hÖ®“’üᵦO0ܤÏ%±2×ñÌb³ÅA¡XŠ…"apO bp ña°KDØDÌRfïȸà·Ó?Q:N ±H›1Ü“_Pte¦æ÷è§¼pÒq®ž¥o˜Žá2‰FpîŒê@3馺%ý¾L| þpE({ÿßퟰ†øÔÂæÍƒèˆÙOתY/—Æy©„ébÇýv”.VG­:¢ú¤2†= ´LqoöNÇô!–‘Fn<˜Q‚ÞØ/a2²P”±@8Äbñ8 È$©‹D…qRà½<OÔ#gª‘€#p9FT`BdŽu ÌS²¡D*òÑQAdÆQFAÊ$g*yò_~—R+£­<ñdOPR(×”  E£ºrC¢ ³MÌd(CÆM…5xº2O-ü¤®m奛•İ ƒØX±±¼¤$ÁÛª>ÐŽÿu ð>ù_¯…VïF—Ktœ)v©aóô3þ5²Ä¨“KS¢p>3XzÅtzæJ…ÂaRäÄE  ”Û1$áðŸô—jw <¢ÿÄûp]æøè1L$I$‰I¿t¿HINÉhúO)â$’Å£”0„¤‘'(LBΉ'ÇØ;ðüè’Ø<ÎêQ ª4’' ?‹ÇãП%‹’¸OÌåL8w†Téº+¨Çó¥ÈT.Á!²JAýÆç€‚’Io¬&ÓãN*äo@AË"áè³×7.g¿9žÌ+x ðÒDF•ÿýcú-ÿ÷+¢ÕàÛ)0 bËÞÜÔuïÕdµê®Ei|Ê`Ó™/7!Ž%)ƒ-6ohD§_wúŒs,(ƒ£Z„A&â$Ì âGÀÓ鸘˜@¼H”ECA(¸c~(ßë’±hs2k‹ rU"ö$BÁp<o bU(‘Æ>‹$¢Á0¬°R)_ 1™Lz ¨i À)#×›,£‘Å{êõ†\Á`0K:£M“½DI—+è 9˜=ì‘’d›ÎÁbiÛ¡a0Rþq€YŒX)®·£udÄÛáŒðŠƒÖú‚ñBs Òq¡Èíf#3$Pá°¿*OÍÇÌãk/œS°FMåZ¹RÞØ\‚™ÆÎ:Aõ¹vüi(îÁ?MaZ)߇)¡de¹t+K‡ý¥ÝΛ{&êôºk’¢tVÒê`ž`”BA O–H‘HÆUäD<>„y*i­ˆp’ ô=n&ÃPº}¡T<ÖDwÄ"¡ÍÁPpKÈ㮫orïüjå'^¶üÁä!óG2;îÆÂ¡Ç:zd÷59=Ìë`Ùh‚öõ†bÙh¶ˆy‰×Šs¢Vèå˜1$z Õ €uàPŒ?ÀCQZ¼pW2M‚ÃŒ™HˆÎ©Xd]<–x~]½ïÏJûQ{™‹¸ºr(êK uªâ0=ÐÂOB °~2¯B|Õ¥™Ð£€¢ñÅ&/ª”c7Ì€òü fqôðAiîñûiÀÅ Má6—Ü`‹™æ¥0Tù V~HŒ‚DûsÈuès zăÈó´A‚‹TÆ#‘¯¢ÁÀç^¯÷›ª•Û6ýï”Zë>Xí1àÌ´)²]:"éò°û¦ÃÚ,íÙ;4ç• dvÑ)”áç·šØúþö¹ƒŽ0Бíf0[‡ëÆcD½±¿ÁáÔ¥ÀFÁ……C`€&R€÷éX-Ž\Ê^ˆDR(âéì ³éÁÎùÝÍÉDl^K ùÔkc ¿QëÉÅE ¸Trü$G °~2§BT))S?uÍÒú‘6“ô+A”.‰[³õíþ F" b¡``Iê%å=â—ļޗŠÔ€M‚€iƒh4‚sÀ˜·-™Œ„·Gá}ÏšÚêÊõëï½|jéß»¦H€‘Œ/ jN•mÂæ™åJ±û¦½sùÞ×h ²w1aÒÐ2MÂ:FÔch Kö’*ï•»iÈŸæ<`Ðp³ÝyºÑl:]2˜†èÙ2Í2„#1‰D‰ù‚ªŒs^/*…ÿÐ/—Œ Ý1wÊu^F^vÚÌLòµÈïù‚ñ½p^Ñ"µ\ ¸TJüøÇ޾ý㥕ð-بnXQ¡É ûuRg<'¬3±6ʼn¸ qj&â0º€R°ƒ‹+–“x u^2aZLŒÅR­m±PøÓ€Ç³twŶ՟ÿ¶t#R‡yéœþ…EÇ%dX©€á®¦®YÒ‘åâ­0ç8ˆé†ýéÕ¡ >Þ•u¾Îd¡³gå1ˆÀ¡pàÞ^\‰OŠ<üS[1¯šÍål»™“`ÛB¾µáxòŸÿ™Ol#­×ßM4Àúñiü­%LÂò‘yÃõ+.ÂÚồzS‰_г6¯—T- ºp>[G<”òŸsT áf$‚YÂŒZÄÝÜ V{Z[ßZ´pÙ öêýÕ™•¡r‰cáËV6mJÁþˆž:T3£v—s âU¢9o’pk g÷^¸}Þ½Å\yÉYÙ……—êÍ–s Y¹¹Iˆ}Á8¯p+~¸3‘æG!jsj€‘PN6þ˜gËÄ,«U´Ó'#ìû4‰>ǫ̂¢×U"•@A_¾¿eOjíø½) Ö÷&ÝO8iÞ×ò»¤ Íl¬Åí¥i,pT8*ÊSQ§«çQd†`Æ,¾Ù cÑ–FOÄï_W½{ο:5ptáë^^ÎÄ ,ð¥ñÅÇŽZÈ $`±|$xuš‘F<ºè¬žýú]i²e]fÌ-Ê•?Ì^a÷ ³4Ê‚àE¯‰äuâ¸`Þ%æ8l¢5†Þ0úJ…7âZ|aOšÌP€+s5ÝÔÂw¦€Xß™dß/yÑTÄ?—<}åMwÃØóþ˜Ùi­ok#Óí88*²óäÐA9ç“ñ hÙE›Õ* Y‰´7íô¹ÛŸ?ï­¹¬ÌU¥ÖHûš«”øG€— à•9;ËØ%—ÎûÓUÖ,ç4ˆ‹Ãâ°È÷ùý)˜xA`çÀU—¢ WEEè¸âPxI…YY‚>䎦‰ÌÞØúöËa~êä&º¬´–Zø>ÐëûPí»¥f¬[×!þMYZ?ÎnJZr†Ö»½X€‹C÷ezšÂ!YPò q³úòÜ=©³;ÏL™í ¶jPb¥HÏ%ê`cUà‚,ƒ+Õ„Aå¢'½-•þpôÞÏïI3Š ýA÷ôI'Ø8÷K·´pˆÐë õ}¢‘R}Þ¤IнÐs»²fô5=$L7¶¡›·ùq=Y¤ÃŽŠç*2R Ñ.S;Ä 9b‘¶–›kv?²üæsæ#.ÿ:7Å‘Of}Ÿú, Õ}¿Šj5ô6Gü€Û×uöKÏïѧ߯ÎÜ1I“y0ƒ † f$à¸ð¹Ã… |7H¹ÛÞ¸ÃdÒåmAÿ뚢w~TÚ‹O„ÐR+M)¯v¨C;j€uhtúαøW4­T¿viõÕ&½þˆ)«Gmk>ÅB ‡¹—” RÄU¡³C "¦ì6‹dÄi¨µaMã®¼õ¼EjJÈ“ÀÄáN‡A,Úg6 ÞÆõÔ÷îá°ö §R½Z|þ%ïŽë½>òA‹^Y•iªpæ# G÷4ø7&gÁùq½—“†ù *ªb¢ò΀X¤˜‡vÜ–h {Ú#±Ø½Ï[ü =Ïì'J|í÷`Ðë`ÔùÏhЩ説svä_ÙË:[´dMhð™7‰àì€còVgþø‡â‡Åd’­ð@iiø²µ¾æÁ¥7žM¢iÛâ®”Ïwúþá8XÉ×Çe(…/\VwtO³~”A–GDOÖÉR±½À,nûºòƒOª§ì˜~Z ͸áïprxÜŽJm“+½]˜zMGn̪Þà½÷'«„×J©ÜµÇµtÉ“Ë/*îÓÿ÷Æœ‚Ó‚°÷iq8üeÀV‹¸-/ tŒ@¿eÑëå";fvî•;Ú£ÓW\Ú{'Ñ_Óm©/õàG °NŸïô4Ó`ðêåuÌ¢øDÈè(®mm‡ˆÇ—Ñ`m±WÔ£)âNv:l,ÖR×än¨ûóâ©§?…G|añI³×éÖß4Kz?PàtrS3{kîô¡9¥&½L~´ÎÖÙmÆU¤‡uwMå®GÞºdð¯©ÞÌßÛÅ083²PÏÔ‹}Ër*rŸà[‚ØÒßо ÒWJ+÷¬óÇþgõµ9=ûüÑ׳­ï„1i2"÷FA룀X¼þ´ÜôLegIæˆ×ŠÇo›3ºx.=ä“& ž@ûéD °:‘ãû_ìaíWÊ7,üÿD“õ¦†@”y#ÑÔ:.ó!{⪠ï YAè¶Ä”Óa—˜¿5jk~ì­i|4R-¸è§vÞAôê48^Þš;£wö-&½î—r–£ x$5axúƒk! âkåæÍ·,¾z8)#ŽÀ•iÃD7Œ“†ÎäJûý¹m>`Ò©+ìä|™å%ÖŽXð˜ÃnH“ Äi0PýÄ~ãÀ•!*:Æ¿úÅoì¹ùw ަò`Ö1Ë‹L ô@I ¸Ñd%ASFMÉðzÕ ¯íȰ ¿0­x°=Onoò…7ίôî"µ :8sð*å«mö®KfÔï{àJIåêæ¦,½pÌß,ù½&øaçà …cP¹Ë´ÜGiàa–‘{Î#ém•-¡ÈÕoŽë³f&<6©ïèûÖèM§Öx±™³øè½Y™}»à1ÿžô VäÞ]v~™:2ï;üt€Õìu¹·Ó÷I“#k¢‚U{ [/*u±¢Rg<Š;f9ÑÒºbö¹çRQ´Ç/ÖDÎ÷­ƒtþ‚ÊÞÅã1v‹þtøÞ:nq½d»E¢0xx›—ù–'!¿ÉU˜xÛyž ?’ÃdÂFzÎEi$¡SüP º‡p0hƒˆ'ôØx®‘*ðD[¢ñÄÂZìe€×Æt"ëüÄˤ “9 ‘ÙëO=hÍï95döúƒd»Åu[Tyjµ3 ›.(ʽ³í,åwÏyftÝŒ ÍX—Ò==\à+¨î?÷ ô‰Ÿ;¾cûU°0w{Ï‘æW–ì3*[h4§À=(ßP–XôIRb@´²H¢¿­±©¦êÎ÷g(³i6(£¯£*`áÆŒU_ÁpÍJ ƹ¾«îˆ2W4/=k9áÝê’^Ù–W$§³°¡-@nURä" Ê–WC –ká¿ÎýûgúìQöÎÅw\~p‹mÒ±Ž^X%”o3É'õº°uØ1àœÀe„9e䌶#€ŠÓ‚b8e§ÆQ|&,.•âx™D”«üßsìt†8„Ÿô9Å焦PaÌj› Ηm¨` ¾¬`.µ Þ}dþØâ(Nÿ´øMׇ9tG>ñÞÅÅý=&å÷mm÷³EÛ-Š ©”Œ÷‘Œ¡þ}ór%)زa»'våêËún>`?9ÌîÙuê,Ý¡ÂÿÕ:b¬¹°Ø‘â’ùÕÇåY¤½OUk{Ô®Jˆ\K»—l‹‘Zë·¼µfÕeìoÓ7»<ÆÎ¬i+>Ôëä3žÚÙÚ“]7¸–ÒrýÓ!‚¾ôê2ëÊîÂLäÃ䮯 Ç [ƒ[-âýöT\A~ °Å‹óÌøj“±ÄJ8 l„¤–Îi’çC>ƒïuÉ ZY)ÜSà„ 1©â~ˆ(@r^ž3x@@ƒCÇoǺ•æ¡”S%Ž_}Ư•›<)‡,!‰Âøl Ë•]p`Èœ6ÓÁ‡r4è[\íývÁ肯1•¥p´üâ0ÿL‚GŽy“T«ù¼ËÞÜü/[qŸ‰î@„œ0ò™DTtH·aˆˆEY½-îs{#‰ eãz­¢mÈJ'áã·¯>ð0W¸kgÇ;P×®b©é'f΄ GHN~·ê ³$½ÔœÐ‰mp¤dÂ~ |À`üsªLŠu§Í¦Ó…ÚRî†ê[æœÂ¬¼ì·žS°apï&f  kÞ¯_á,.U_U}EÙ˜Þ¯uˆv{§Ùë:³Œé4=`ÏÉ»¯Á€ÏódB[0ª/ç¥88ÑEš³â×ÊMêð©ÌÇ“[C[±C=‘ÑØo»Ù@)»Ù€«áœ"!xLb-A Êãž~N×™7•ûé_5JVO•ñŒ+å?Žég<W%°µ™l6à=Æ¢>wBLÆÉ™¹^ÅÒepÀາYÎv°¨rûÿ4—ð »©8ø£‚Úš6Oá¢Ý˜ç>º9»°øïI[ž¥ÕãÙÇüÞD8‘ŒY z]‘ çáÐ-/íýýfh3é|œsYúhÇÚWš«q¹\lÊ’ªß –?Wû"˜ö' \×a0ÐxÇS¬ÙÇìZav–.ÞZ»e󿝮ÿê¾Ë?.}¯vY,jFE74cÁïPa9ŽØˆg!Íkd¯ômiI×U¦(k…›×´¼e-È_Óì§jbk®C+^†bœ3 Ã3g ¬(Ô©­àšÐ\j1Z‹_R&SšŽÊá^Ç9=à-Nƒ"]SPA‡2á×JÎD 1Jà÷ASœÕ“SvšÉàÏÒ÷±û ‘c³È¡æ&Ÿ»®}¾§­yYucã¦oÞ_ÛÈ`~=¾ËuŠØ©#Ɇ ·Òc°Þ^0ôä\SágŒU0n¤Ú¹Ê¼Ž‡é¶t1(+¥Ô<¾Të)ö«ÇË'>ÿ…¢Â¾§4µ¹!,“ˆH[‘ž.ÅHŒD»ã¢ÔÛarêÒª^ÏŸßû÷.|03?F‡©zÝ& °¾åUQç@'!î]»¤úQÉê¼cGc+®ðWŸñ¡aK¤˜ÐëtRžÝÂõ;_{{â±· b;{l»Á$ Ãйîdжõð~à@F•.ŒÉþ³×9 Òô†[&ëE)0åÕ öÎ…¼ˆbÑ.Igâ-kZs³/¬lòc“ RW)ì*«%Nƒ!‡Žt[9á¿ø! ‹”óå‘À… ªŒSy’NÖmŸõ¦Zv&pñçÈQ&Ä¥ºì Z” Ý'/z,˜¥Í;·>;oá{°ÙwlW‹Øï+2—ÃÎÏÞäÏŽ–°âÇ e¥d2‚Åð\‘¾¥ìÿ±‘—–}õT~QßëÚ "ÂØ”{•%Þ‘@‹tp¤xÛÑêc ó7uYmŸçÏëq5Mü ˜·‘?nî`„¾œ ›ú^õ’¦¬i[ë[d D "44`iå¾Íb–-ÉkÛùÍ}ïN=ýojÖ§'lYE‘ö¶"ºW<_ûƒ„´áÂÑyöb¨ pß«Ä>¸¥è¯2DÆÌlÒœ WpV‹yÙcª›}1(«àb+ +„éÓŽ~M”ÜÔ¸ñèþS¥y<À9WnÓ5o E!<¤ºñºðý{è€dHÃãR<%O¨¤UžqŽ‹?Ç#Êæ öŠgJÀ‡Ì†Í5*7m¼{aéqSÑD‡òòr1¯9½a†R¡Ž_ÚТ9/ªÃÊG•üÔÛu¥h֯ĵR.w ¿5鸩ç?÷Ñúì‚âGõ¶,Éíó“Ï~ŒK¤ñ‘ÁG"•ÚZß}ÕõK«ÍÏßë²rèQK~†f`utãÎ'ÖÖ×]9 ,ªw\´³¡.CˆsÇPÂHSt,™ŠeÛm:}°µµºrÇukoË+Oš·Q_V:,ÚÇf½Á3h´•KelcI §KÜsE^ȉÃb:Q2Z‘h„éSÌÄ<¦c;n¿µŸ€Ù@WÚꦛß6ææp°°ÂÊ>Ÿ*ž¾è¸ÅO”8Á*[}Fiñ•OédY0Y`TFHª¦ç¹à%ÕL âÀpŒP‚ž!WâÔxæ ô)yð' .sa׿a&³õt£Ã1PŸe” ÄCahi 5‡’6§«’ÊTA‹Š&S«Ue;U‰¤%¬.Þ~÷øÿ“þg¢ˆÐb±Dª"oþ¦5ô)ê½™qsa"‚ÌQxæ]ë'°Š§gk^/fä„yž/,ì?¹©kiM¥¾Gd>ƒe_Ñ^ÙŽ¡×Ö<÷ÂÖsŸ†¹ËÏ´@ -¨èPd>´ÁrÝ1ÎÕ!½ãÄM­1³L‹G¨Ûg¥.±YqN–¨Û9wÁ•'\‡<’éqáJÏ,^»¬n‘_¶"~ yG/¼tðûiýÅ~šÇ¥‹vŸ—ít.­óGâù9N9ÞÚðÔKç÷¼…¬ÍÕ]v¨Þª%téâÝ÷,^ÐàÃì ‡r\t¥Q½Œ”&ìsòé%Þ#†3îóHÊNÊEw®“o î¹=Tw+E›e—ÂåPÄËi,9½ï€>ãΜñ–¼¼^I=vp `›(i #LüçƒÎ8÷…»ÊQ¹A\– ܞŪ§ô¦)‡„Ïã~è DŸ3zÔì7Ÿ-Óa^›©”zx~3ûÅE/ñ¨£çQw4º±esÂðMŸ||½wWí§6¸…Ȳí¡ ‘7•‘CFJíÔ_µ®Ô2œ °T mÁxþ[ñúó’í’Ñ–°g_ç,û嚉Ÿ·j _NDk!¡;¢ô]-^‹èEõZ8åw¶Um›™kÆ2#½^ dÅ3qÌ:ëªÚÜQŸlï{]Ëûìÿ}’Cb!Ѻ«µëpÖ'óýÎ|»U^™áW/®*èí#wXé$V4H‰<–„]¢˜g’™¯fûï–Üxö_yCa_Ã0eÍÏi ’ÈæÚe¼ö ýç­1ùh "1æ]4ï‚>¥E:ŠJ}¯SP¹«‹WžS`³¾ß÷ž°(ìB,º«b×/>˜vÚ&µ®¢ëÅï˜o¹ï´µ [öñÍ^?üÓž*¬Ô™¿àNçi@{x»¨tB Á¥0’½xòô”ê‘"“ݵÖC±žp6à©j{íñÓr® g Þp)F¹ÌüEšIXLJõÒû1â±pο˯è7hÐýÎÞECýAt4J>ĸë¸,õ¾BI…û¢{ôÁ àv¿6…ü,‘YЄݻšþ=¿Éÿ›öÒ£<P÷­3«ù_= ¦fÎ$Ù?5úÉ÷§g÷ü´Ÿ­0Ì@vòdÁÛH¯-KÆzçfé,qï—s>®8aæñH¶Óê’_šŸ´³€ P—¯\½x÷óVÄY‘õ:ÙÂPPÀŠ%¬rVm5Ûn&°¢Ë¿Ö*X!.í7Hi¦ŒïH™³ŽnE"¨¾ýßKtŸöÄa°Â=ÂQœ\£á÷1£ÄF­ KÆbk ¬èÕµ¬Ø<ý-÷ú¾èÌ=¾ÅëX©£/g„=Wü ?4¬!FahóBB’ðÉÖc…Õ ÙmÉfÇ‘þøËÒã¨Ç=ž)zƒ,bR@ %>uîTÈÞ#{ò5ï×ÝI…ºV*F¤ØsŠAHvD\¬ÅÀLs©ÓK^ùÏÈâáÛ?ùü~1à‰8³ V¤˜çô§ \M.]ÓMj³ú§^ÃG m·Û6U}³ó­_m}zÄ£OÖ‹×QמätÙµ”ñÔV8Òå·œû力-WØY˜ïêÍ!*„ (3A]QÕÒ ÈŽ®=µÿÔ¢¯Ê©u­†ýðÚ¨ïü‡çÔsàPG§/ÙU‹*žˆé³nÝÑØëuZ¬Êkè„RIxó,zÖZµãÖÓÏxR1Фɪ=œ„ $ç¾½{h/‹üYµ?a6™-Ì”ð]6®ÏÉ(*r /»*Þ•.Ù]šåÌ}­Î„‡–,ÈvÈžºª{߸xÀCôå$+ùr(iÙ¤µ¦[n°TtæY׿¥µÐ³Qcx{”ÁŒ6P3p©5$jÑv¾€Y‚d4AÜ ít>Qè$¢ÑZ)™€98ƒµFËÚXɉPØÜGÀ  hHÁ«€¨7É:1Kowð’ÍV4®.ÐúЃÙïNmu‘™E‡+ªØÁ&8r.jÂQ¼=bäi'ÿ'§oñV„\šMä:wU‡¥Ñ ú¨ðÿé#®1ÚSÙð¢å©«YÖûèA·y0-† Ly98¡u™ µ÷=Z Mˆ‹M4`€k€—†‹ø|Ëw¶û~ÿÞÅý?uÑ,î¬Y¨«[p jÿ*yjùÄìâ£ÊÜ)ZIòcF Í‰‹ÔÉN)vß÷ʸ~ënàœÙöwþ³,uú÷’ùÛÎÆZK+<1®PWÖÊ)’L²¬‚w÷æß-qö_÷ËYªª87aQååYVÇë;[¼q›™–éÚÊV-;.ejTey§—€/à<( iöêºåõ³óîÝÝ܆ÕûL4™Œ¢9êk›ójÙPöÌÜÏ;cnZ=y©äÌ?»¶Õ­ˆT”© X8‚Ã@Ý¥$– IFÒŸÕ×Uy[›ç¼·î‹y¯ÿ:³Ô¦a娢™ú|+Û´)Å ðåq”Œ3¢Up9‹ %Ø,Îe,M•â‰=û¦nÀ¢ÀX%]FŠw» âíü®gÒkò;ÛíuÌÀ; ùb0ôÎh1 Q¤P%:áÿÓ÷:0”txHÎ%˜Kä:l‚èkO¹}¡Û^Ýã ^·ŒÆêÚUîw€Ö“˯Êí=dns$ENZX4Ía7íµí˜gBß5oŒï?Wíï]¥?¤?;Àâæ˜>>û¥M{ØM×…ål8Ì2Óhƒž†æÔÁK‹Éâl»ä«Ùú×¥ÓFüýžK[{s*7pö›ÛŽîkµ|VbøOYe±¶®ú’¯?å5NçµGo3彪»ìÙyW·zÉJPÚÄŠr²u-•[ïŸÙÐ?+éž3Þ´z\¹˜•w*¡9ê*DàVWhd¿¸Å —áš„ù›jw474ýkñU'ÍA”VʇÚ1 Šÿ´Î®tºù¡ q$ ëó†bYQ¦å™$°šÎ3ûç›mz,1Á×0þ…ÃľH¨ù—§P=ö±ÊF½DÔKteZ¥g€Éä…ÛþÕsÈÀ[Z}Q >õOˆEï ×€¥—rn§p1³ 1£,é ¬fæmj|ô™Q…wQ >qÒÍ8­ÑO—ß–Ýï˜Çê=x„N—B§E}íLè¡£ìiJƼ±Ðé‹.=z½ v¼½ÝøçgX_íÛ®<ÈW- Ó &/f„} ˆÀ+(n„Dϼl)Ò´û©ÅW@öÛ©;tO/}å¼¾ ç#wÊ4¸¶Ýî_à4úªþoáäcþ°ßŽ‚¨z²¤úWvgöãÕîDbB"Ûa“Sžæ¯_Óû4” /Ÿ3tÓËg½/çžUƒ!U ¤ºR… d‘›³J,Ëf#­ áÖúú¿Î¿âÄ¿+éQ}èA66—¤ÒæxpÐ`#ó¾=} KºÒëÓi¤Ñ/}Õ§0ÛÙ;Ûj&ÉÒ l·7Îüò`ïЃ;ôƒ]iõ8` fâÉÛhVèPDÕa¬ñX|C½7¸þù;¶°§Ïó¨õ!q½ø¤“¼Ì Кº´baö€~6»ÉñfDÓ€µ/p)€µ7×EùÓÆD¯>ù6±¥ºîoÏÓã>‰‡ wÈ!N­G= ¨¯@´¹`îÿg-ìóûÚ6o4…Bƒûýâý8 }–\~¡>Z¿¼ÕsBãµÇ7uô×.Ú°C©uùŸGÀKfi‘eÊ;¯úDÛäÊæ6X±+Î÷hð E_áâÜl]¢u÷닦ü"=ÛCý¿Àù]³¤jIÜè³­±%Ô;/ÇÄ|M¾5~ÀYœ°ñèZåðèüª%»ï´:²©ñÁs„¥=¦ãC±¯7oùůF¨Ñ߸òæÅrvþ¹Õ᱘/Š%, úRèkPÐK¸mݵã¹y >Èž½o+•ÁÅú…‰}êO÷h0”ï™±ëˆòŸÏ‹§õÍ;Õf6§7èNeÝA§³ê¬2¦•õ/0ndQðNi†'©ù Õ•f%IWHì!9Џ1aíx}ÜÒî›;gLŸ%ˆ¬®@,–êøÐ0æ¼ñƒ†u¦Â‚þno8½¡ÅP9­ÌsÎÿï-&½`QÒÁaKmM÷¼0ºç?ö^A€œºnÀ;"Ò’>nîº'ýo®ks§AœÈ¡pZ!8ì‘åÐÙY`åËõ;‡7(ãÐuxàšQ‡øYUùXº âž¤ÞþÐVx^àæ |àï«,w}Ÿ-˜<ìL&š1X:ÑIýZMy·ê!(ÙïÙRß³Ãö![—LVUí<å“[ÏY¿÷L™ªë¢Œ¦.¯~Àh˾¯ªÍÇÁ ê2|øÅda–Ûjm¹{ñ”á*Å%Ï(Ÿñšœ]tÙî–¶˜‹™ùç3 TZóqpU2ó¶Æ++vL_qýÏSþ\ß6k XM©Lí!nÊ•6- <Ƽ¼qX¿âœ&³þbY¯;So³:“€ à$D‹¹îàì`MR4~ñ,â¢3 ¿AùrÐà" C} ]8Äà E²«eFX¯‰aä l FâeÛÜ‘9KÇîBLBå”:IrÚÓŒ>ù”áËLä¾°(ë•L%Ñèæ%*×ÔéiÅ‚ ü¹˜ŒØQUs֒˸ё»b kýô»½äÕo‹¹½.¨kn+!Z™¡´™4}­ø€Â|Y ·ü㵋Ü£öۮؤC©½»#>¨bÙ„·¶ž«—LË··…h«(÷Ð7ŸôVèüÐÍbàK–¨»bÁò%g²§ïªÏä†2‰¤*1Ç¿Sq±Ãæ\PÑäæœY¯‚\]¨¹úo ' ½O-“§Cçr)ÒâÛظbL‘éÙžÎîfl©­ƒý@¬8Ç©óÔT¼øæ¥C®¥t7¼_󆥠Çe•MmØ×.íÓŠ` XÐ\Äò6]´©¶zé'Ÿ_Qwÿ„µ.ˆõŸÙàLÉÊòÌŒûŸœÓÎë7Þn5]# %†,‹_'د@õ’s't¨÷$@âaUúuS àÆ/(áÿU@蚸CÜJõ¢{E0}4ÅMñw`ù§ ‘«jnֆɧJÓ+íüGáàþ¿nñFâ€A™²ÆU€)}äèˆüÕÁ«?¦ëK&ãÙ6›,ùZ¿zúì‚©:xÎ’êÖÕCÆÇÔ:¾lÓš¨9ï8r¹ ?ü2mÚÈ$ÀÌ! %|"ìT6aàëjÿíêíÛ_ý:º×þ ÷Ô—jfcöØ\ã7ua©È=¦È[(õ[pdj4Ä.i71bÃý¥ŸfúàîD•¥~b¥õêþý¿ŸÖ¯%ˆf›Íz Ö¼¹v݉쯥Íê Ëü¢]þÎÎqN‹ù?Q££°¦ÝCŽõ`á% èP±¢ÜOÕ†×.:ê”¶¬êISNáÍ»Û||†‹”êüáê ù1–çtèÂõUÛçüóɱlÑiŽÄ©ôìTó=”{‹}ãßÞujï˽Y‰>ËÑ7 „ôi3 ìtƒüP<0žÈEP²Jã}¯”›+O8J•Òƒˆ„*n6в€è©i¬nojyyÞßaÿ;y ›pg¢ä„ñ~î7J­ù®Â6}uÃz9¿` Ï¦iCîLQ-‡Ž™€EÔPÇÁý£ÿ1Ô¡‡Ó!{ë«ïyñüÞÿPAQ-²«U.¾èÞ}êi%k=‚9+ )÷†Kõ'½Ì;ÄÞæ”§Æï?ñÃ«Ž«Èì—]½™õKw©Ì[GÔ9—C0°R“ßÞù–W°NÀ7éM#¸˜BƒfY±Ó"xª·\S~󨹪ø±?J¨/úÊÅ»ŸN³¦ïjj‡HQ(†’>Þ^ãzûòa³ÔNŸ™Ï5ïÕþ|&0–—•¥ 6ìþò¥1—Œa쫦̲öWoõž*«×ßÝ}n‘Ã|—Þb¾PÎ2c—bhù#XQŒÁ .”ŒZª(Ä{‹RJ¯ÞWnó_^?%ï=ñ8H: ¨çYQðW·ywïÜ5ká”áÏà±—G:ÈÊ\øÚ×W8þ˜ÛƒØû“RîySæÊ…rL_SŽSx¦>§m°a ûjþ½bÝ0æçÅCª<~ªt™G*MNôÝ1}‡,iŠ@ súÀ-ƒÍZñ»Cv ¡µ¯Žïw¯|7k'Õm:r^$í¾œ*}së/c:ë„Ê–öœ íY#ˆ¦“HŸVB¸µöV.ˆTON\Ê>A«KæWœ-ˆºéUM­Ø+ ü7”! ¿;Þì ¼N‰ÞÏvRâ"Ÿ<)ô›¶¼fΞsçNøæö`+x#Œ=i(K¦Kp8½¡ë=E“µç”S¾Œ’”(ë=÷Sz×¼E^è#ùѼp7ÝWeCEñ­NWÂ!¹ØÆšOOÜýˆ+æïø_zD\v:J·9ì=w›F쯢ªþiô¼ÍÇ:dÝg»¼I8cLiH+ò ‰N§¬4”/¼ò¸Q<Ÿ=bÇ^ÙÒR¾ºW.ª,÷ Ö‘-^L'£ógÙm’9êYûÆø£ÎP¹+J\º¨¢Ôf0<28²k1‹#C %…ŒEF]‰‚œl9ÖR[÷ÕºOÇlv]ñÍ•ïVþÚYPüÝ­ô5ø+çÆK¥’Qé¬ò³V_¾<¶Õ×M`Už¡(ß«Ò0ŒŸ'‘•¹+m–pùÛ;GçÛÿjÊÉ%¼þH þ\à§ ‚ ü\)¹¥oゆÝðŒþÓ3%>ÅU¨éèNfà†L  FXž¿ñîê3«î=Í]k«MŒè‰éÂCãjÔ6Ÿ?çÓ‹ŸpÂÉ<°‚ç壪%ç 8à$ä¤ût4¡ÑLµ‡œ+bË2,å\on\ûLIa÷ä>2¸¥ _ýêmÁQ<¾¾µû‡2SÃ9 ÈT«¿C‡6‡F.¸|èj•–£;„#“ÃÂËKë>§N÷,ôL†(xd+44 È¿Íd– ¡ÖÆ…ËÞ¼Š^ nuæeï—W’ö@pù;;&$dËÈß—ìi&u=6 Å/|¥qxÀüµïí~Ênu¼Ö×eWÃv Óø0: ×É–SÐ6`r°nÇ’×ÞzsÕäÅ×Ú­ÖÔ´Âó øxZš“b0¦ây».T_¹åå±×ž¢VÄMÌÈîʰºláŽQ¿ZÓ´¤o¿ËÀªÎN´zÂêŒôŸ>É|Hwj:yòÇŽ>O2öƒ—ì®ò"Àã©< Ä” ýQúSÁƒž‘»Ú„d3°ËÏ9åߌ=dydD¯‹C ÅØpó¥öÐ;*¥¬q\zí)ï„ÚZ?¶Z „ å²(Wªí-‡üU[>X1½é‹On®ßðñ=ÞÊÍÏëkÛÌŽ¬½µ•tˆŒlÐèØmurs„°èŠã¦ŠÞ¦mYV«L#êïh;õ#ìϘJ4EefõÏ0èaùÇÎE6\Ý#`2ùÈ 3ž^/? ‰kâ†uŽá -ÍÜk( "¼4þ¥ÁW^tꬽ¶êFö¬«Ž”ìe¥«€i.F'È÷z’2¦áS¾h,²Ã¦·œdµZY´±‰Ö¨7·ßÇf™Ã,¹Çíhl&ìIÁ/æ¿h0R~JbžÝ,´Tmbñäã~EÔ¿pþöK°¹ç ÍXÕKÄ0Ý.r`¥ÊROŠ%Y<ßa‘#U[_ºï/ç1öAsÉA8+Ø©¸ úÖ¢g>ï3aPÑL³3çú8¦áš¼!„0é‘/Ý“9GÉToðN ,É3íðä ` S4 ¯À!<Ôéõ‚l…6pç2ˆà>èà$™ÅŠµÇ¸…Z¼LÓŠÀwÞ&ÉLÅ {:޾eÕU?9ò77ÍDþö|‘²ænW*Ëwh(Õß@IDATžÕ/ìõù^),*< _!Ô˜?Þot“l0Iå;sz÷8jPÓ«õ_ùÞKÊ6>•ЙFãÞ§ÍyåÔUºW€›£t¿p7ÔížZÐK·6€/Kö'ÔvžÑ{oõböüœ“‹“ÿx ®X' e|Ó“îÐØî÷R¾…ªê¬ÉØ×·gLIŸWxɾžZxÀ[Ã@[+ÇÛv?¶ôÚáw¤×ÒlØ~ƒª»ºðí# ¢nM;33{Ò³& -ï3àè™Õ[¿yléÕ'Ü1eqÕ-pœù¸_4Iõø§’à†…KPTÌDÂsd6”hÍ»·ß»ôúQa%¯n:m@~öêvÁ óÃü³—ü#Bõ¨òºf9ì²ènjñ?ÏŸÌ^ûÃ΃Í`º0òÓƒ[ºayͽV«é÷¢3ÛÒì r࣠‰Œw^*ƒwêÍésp*œ92õ\q±p³‹ÿ[?¸=Ë[›[ÖG¿Çë`Ýl³åXùyGeåå^âìÑó<ÑdžÁdR²>Š5UW/²Ú¬ÅÅÃàŽž%|œ à\—(&òÍ‚T¹©òæ¹ô›½¿‰ÂMªî•K«o´êÄœ/wÕ•}6í䊎õû·{Í7â‹„-'‡6{EÃöªøG'圗N8–HIJ°#·ìnÙòôȹ'ºRwÑâo²t纱ÂÇç5Ü>®¹#ÿnxÂ?XøÀž÷Ü'¿3õ皬O#M” —‹‚þ” ̱ñXè¼ù—Zžºýên» Ž,ÀâL‹ìò7¶®iŽ›G@Ï'à ¢c r{«l»:§¶u‹§KúЍj‚p £ÖäùEËmQ|¯lIÏ“»êf›$vâÚég½€MV_ÍŽ)˜ôb!|Ò°Xð@œXÉb¬#wo¿ùýi#fó²ÿ°øÆ¡ý>òéí½Ûü¸ú{êO¨¦ V ;0Ë!Ox݆oÎÝr÷˜µêŒÐÞuͼùÛÛFåe=bÈÍ;¦Õ…WÊן,8B¥8HQ>T°Mqp€²v¨Áú濽}þ†M[ŸúúÞKV)1ü{ü ÇœvÆð'm½ úa¦1nM%äÏ>øüŒÕ3NY{îì•× 6ô[ïüB·?…¬Év šr \õ>_èíu›O¨˜qê6•ÖjIêÇdâüm÷ÊÀÝ >4^º»±íá—YCñ®]RñoGÿ~7¶À¥„ÔŽ%;¤@PŒM°“)fLaå´Ñ,¹ÃÑT¶˜šëjÎzë²aò2±ð›íY¶DQ†¶Z‘ît̾òõʘ%¿¤©:TAà´¡¦`˜÷-,ÝÙúú†Ïc®Òo]Äé1Aõ¸ôÍm÷DuøfòÀeðž—DK2t²N2ŽñºšÓÑð()礷℞<8€¯*p¥í¾³u,‹Õ~qóYìÙùß`Ÿ¸Š¸É9e[c{I¥LJÍ9+ÚK̳¼†Ý;¦w€Õm‹íÓŽî·,brönõbzt&šÍá©0\ÀBÇf”ô!wìë _#°¢/ç>Û9Aÿ@¦ üþ%ÿ±M_Y7·Oÿ¾Ëb¶ÜcªÚñp,š$ýø&ÉQ@ÇHhÐÈ$îÊçd¶Ã ¢!Vûå7Ï=õð3'νhð•*Xp­hp“Iùƒs9Ô À løŸ‹Þ›}VáoUóû›Éô+Fíyÿ¦Q/xÒq'ü‘î»FÎìòxÐå+H„<”@/È6øìW¾î•г?Ô´xÈE¦˜fùÀ„%Qà´³XÀû_Þ{é—4ØÒÊùQ uzx\¶yd˜a¡?䤸§aãü‰Cÿ<îím§æÛlŸb{Õ>;š£Øî‹=‘„ÒN…ªg3±öº÷¯˜vú34uOϦ^4ìÙ”=(êဃ† q;q «[Þc¶sÓ×Wl¸{ìJ2 ¥öQZ5PýÉ£$™*\ú掱¿¼÷âÏ-…EWÕ"©6ŸŸ6¯ E?§AÇì8á@Eúq‹Ù$Úáþ¹}ÛÎU¯X5æµËŽÆÞøÝ–3(ÊË \'Z "_ùT''o[¼‹vXÃ;ﯹ&Ñî‹êŒÐi±TÕõš Æ^Þ=ûí¥—ê[}fƒ£F쯊ºÈ`ínÜÖ+ïÌiK«î¦øê‡‡ÎAo'Èâin™M®ýš½Ñp{¹}Šo¿mmó²Eó6Ô‡}ÞÍ&lV‹÷Í%O‹j..¹†1¯Gëüß|}ŸÍŽÍM¦AæiŸÿö‹çž·±w®Á°´“ñõn‚ê)ž tžÀn‚ùN‡èk¬~öÝ«OýóìTJG®Y¦,®|HÎ*¸¼F§ðŒÉ=Eð‘…ŠÒ”y]pZM¬¡ºúÎ5·_ðfæDÊ¥ð@À6ݰ¼ê‰>} ßXœªážÓzJ1¦T¤•qIôÀT7ùqOåaÀXS}ÛÆ5kf̹`@Éú{/^êÂŒ}¨®œãùvsƒÔããFXê]×¹Ûݯ¬ ½® ¾ø¸lŒ°QϹþëÚÊÊÿÁn˜?Ñ«¡ú` ÁÚ„Ö@Û³mb,ë­ eƋһVÍâ ôÆeWÍ 55VÙÍzc,žLT·†ã†ÂÜÑ·ýÏ„wS‘HTßþˆš·O —²‚¢{ÞŸzêß6¯ÛôP[0̵ lÜ‘Èf¸ß3Î|*éi\û=‰ÆJ Ò¯Â)bÜÃ,²M§{ˆhP6‰‡ÛÕèrD)Úi`]T¶ñܨ`¸º¦¥5…¯‡ Q‡Ó›Þ­¯2'ü¬¹µþvÜLpQðÛ¾¦xÙô¥B|bÝéz¸h¨Ú};ñ"wŲ¾-i²7BqÚr)Ó¿6J"]6”A±ö†oL>î&ºw–Ó”.ÜqÙî¼F¬øàu6܃Ċ¼˜È±Ŧíß<ðÞu§ü“À.ƒ;8+å ®_ËßÜ<òÖÕW}iíÑëÖ:x_o󉫂H¢(•ˆÀaA½À%hPŒY-&É’Œ 7¿òŸ[üÅògÃÄ“û\˜‘K·›nr(gå<î®Úæ9 f¤d³£J(+Þˆ±ùW ÿ—»¶ñK«³‰|ûz®[„hëó²Ì·Œ8öAЧz(¥sÙ.ëKwÐçŸGkÉD ­\ÛJÄ9ƒí¹¹ÇcroФ]¼í”œZ‹}Ïã’#·øš%»¯Y0qؽµM-ÐsôN\Ý;BBjÔ¬rÎ1íܵíN}¸=Œ‰ˆä J}„¤cx¸MÄtöó0Kzq˜3f+©tE€E&£ø€†·òÿmÁÒŒJê€üCB?±yÙ–ŒøùìŽ1kùL"Ä›o{!®Yé¯õßWæHNðÔU<¼î®óʧ )~7¨³çÖy|QìÊ`ìœ$tì’"]UMÕ/© ãìW¿>Új0Ín ÀÕô Ähd&…×ò“zê«_gÊðßRîÆ7=ôhÐ*ÍïVþ¦O¢ò°Å9hw³ëx“dL3¥RC§ ±¤ ‰ÉÜ,³.Ö\çÞ¸îÓ)¯Ž:…mügUZìRí×Ô ¾Ó±|Õ*>ð?xrÕgÞw:n;`/Eå]` ¬ãÖ\ßôOð…ºðü©ÎPIX6™²d;¯ÿúæ1šôžÔ ¨`ØÒæ]÷Eh…¯DÉ %£\¨V˜‡Ìfg„ÀÆÂXÀ)\Hù~z顯PšÌj‘Gđĸô›­(ÝxÿœkÇ.L°”&Ó/á5€¦² ÿ‘¹æé¹>T¡K—£A·,Õ s¼3"¢õô&Ÿ7ÝïÌÔIÁk³Xd)ÐZ¹têÉ3é ¨ëÛÞÆFاPœ³ r5µµ¯\8å„»¯XXñ§”){d|i³ÒÓ Ø; CÀ ŸE¼î}ö«sWó箕YýœŽE>Ùb óŒUˆlH‹ÿTHœ¬ØáyÁ_³}Õ›—}’&Ž Òð·Ësf¬¨Yè,îñ·Æ¨À`SK…ÿ~J]Ôc:{žuR‹Ù ³ po›·.xöÉg‡¯ºùÜW\SzF\•¢S2à)¾ÇË¥¤ÿè×mn_ Îðoá¹”+y=M~¹Þ.}nA¨¥­Ñ ']ñ•°E@ýIÌ‹›õ¬gQ.Ùù‡(=pÊÓ[ -¹úÄ¢>Ïv“ѶHàÊ{¤9‘~¼øéÜ^†Œ``ê %ì/‹ó¨.%º8BCy‰¢úX:í”á†ö ‡Õ¼²œ‰ú/}èÜ~<$Û]6ìØ{ˆ ôˆ]ˆ.x×Ý8`¯-oÞFk*)Ý_£H¬(ÆGSi0'f¬B¾ößáÒG6LÌ ‡"ÏÃ)lYñá§¥¼¾ñ‚Îò‡]õÍô’e¨Dh´u 4ú°»Ìümojk¸ÈA®>µßCI{~¿Øg‘­O‡´¤dçú5›Um­«-?„Ö³…¹È …:é’¨=(¾±eÔ-c‡}!ç_XÙîOÀ¦ˆä?.úòjdÖçÊ% u²tb¢µ¾î½wßüêø!ãÉž‹êå&`:›v>,ÊÖŽTÔ6ý­²¡y)/#­ƒâ¢D6Æi ï“h‡À¹2¢‰x OÊät¿ôÍ-cyÚò´Ø Q%=a ûýË(-^-Ø#4.ÝV^øž$'(äåˆUAX6˜Ͷ‚‰ûñÂò‘åÝ{ Pû¾-€vin5æmiº×ŠWމÒq*äÁ/ú¤Xßl‰¤tOÉÜ{RÃ÷§ËѦƒåþ¶6wÅç4 ëb®ä˜¤ÂÖò½¼ÁÖ86–ä¶LDi2…¢QNù[W®ºù¬W¨ ëÓ‹g©=¤ãÂKût†°âÛ/ÚñN;ÿGáGÉ{%'ìªüÑ8‹`ÄÀ›g‰Ñ—À9á©{}ç¯Çî ›—¿µåjÙ~#Kƒûƒë`ލÐ4Aë ÐK8ÌFK)¼~ùÅDo#ž€˜@«¬tW’^½hç¯Nç?ü’‘µÐn9° éi è©z É™dĽ 8L–²ëaÕÐ\ßÜÞÜòî˜SqÔkš…«bŒ^e£Û·nþ¤aCÔä:&5Ÿz\u͉Oò<ˆCR¬Ôùe9ü¶ã$ÙÞÞ¾0+Üs Ž¥»RÚ NÆ-’Ô#ß97—ÐÀ¡ÖP«þU^Î[êõWÙÃñ[Ñ|¤å-F:î ü*}‹ÃÎI·‡h f0ÉF³áxÄ^ZÂJÒš·=iÄ3âV]øX¸aùØ¿x%×YteCK+¹Y‚9 > %vÛŽÑÉœRœ¸¬;U £+Ñ£Ë!衇П:óˆ9òã1á7õí>þ•Pû-^ùe‘Mqs»ëˆ»ÂR+ÎEtîÙßR -[ (ÞÚrKÒuZ³×Ç ðì`m q­U'`Ìð(òÆZˆ ƒó鯀ÜÓb2ÿ³9”€r/È|lÈ@é óI2¥"¬r˦ÉÕ÷O\Gz+²ú&ýå‡éþg=zÿ£! ›#_0A`EÃ/Í6(G4œ7L%Ò)b ‘x‚aœ£÷°a7H=Šþ’ÊÉ»ƒåÝnîÙëá!C®¾û“ÖÆÉDª/À´£1tý} dF`Õ‰Þ%%%ª+¾ˆx=!ÚFW±¨¾dÒ.Ë{Ú¤4—EN©å«”´oÞñ)ÄBY¸OI7›"PÊ]e<à—ü},H.ÕéåcèîÐ%é;R+«·ºt¡ö°D YiºG¸¯ÃÑú6‹Æ…_Žž³þhÒ—&ΛŠ8,¡ÛVùH0ùùÎìp"ؘ€¾¸¸Go…í¹Ð#¥bÁg>¹}ÜÇDxUaMé)`À)ifÐJ”»Z™tA°PŒl|žÒ[:±áQ @H†ýîV•qrQî–œlw0DÛrapÒL l´À"ä@gÓTUqçG¿¾p‰ œ)<3¬k|ìãž7.¯þÜW|}E‹7ò0ÛIú9ÊY©ü¢Ó¨K?æqˆÐȳ&¦××co¿ª¦@¸þÐ0À ½kmm‡A»{{؇]0x¦“ð%‡Š©3XQ–®4ެ¿gLU*¯Ân÷’J“Òe&á40Ûg¶cÝRA6g¼i ®K+ãáÈFLx¡Ý(':M…9"‰òŸG#ZZ×—}(ïô@‡«Ù”e— 4óK¢á–ß]±-ó?žã°r'jã1 „(–.å,Ùj2ß×Ò= Üq!g½¸®(šnjhWDyÆ×‹ŒÑ›uáö@uå×VÿÝmT¿H—Ý1k:zAò¼)XL&‹Dêuz8ÄÒÓ¸LË4P_aP´|öˑգ^Ü08)讯mm·'Á 2ð ³ùdò€ÑÍÏÎÝu•Ï.»~Ä?1d²7"= í|<úÕ §ÝtL¯5 GÁq»àíA‚’Ž{pèœh•Ò\õÐ10÷ `%¢%áäÛÄÂl‹ÑöH‰ÚŠõ _­»ÿ™‡=ùÙ±G:ÿŠ“”åBû™ÃÚqÕÑ=Úì¥Ï風 ‚-:!QÏ‚'£a4{øý~i}ŠªÇ‡(¶Aß…øX[ñþ‚É'ÜÈSÁ^Æ5s&ÿâ_öö–‰ý ò±ÆÐÑ»¡Ä0ªhˬtþéAÈËR&.ø~“²ã8> iÜd0Š9بBhoô4}óõóå+–ñÂØÃçØ•½ó×-–?ëßX`¤X_ â)m„8 ;Єꔫ†ôU,‚Óï|SZUW{: OÃ)¡6?}TïeÞÆ=∠"lð½°ÇG˜5«£RüúHþMöÔošâßß#‰Èzâ²°6løë~£By],ÝNé®ê®N~îÓBè»onôùö,ÁA·#£8“Á ëB-íŸoÿ’ÏÒ•¯Rf¢¾ÁÁÅÑ×eÐìu¹‰¤0Ê ÿ)d9OŸèT4”ôGý»D!/ÀýtŒ L+ÛÇKÁ@ÈÚöõê׳ÀãoÛãU¬‚oËÜ¢Q1þèù}p¥+ð¥Ã¢.fïøƒÍ‘ó§F(÷ý´ F©4ðO~ü H…¥•Óô¥IåQÜ„ÍL­&“h¨n¬i¬aÁSÏÏf>YX¨r&•ƒ«C“ˆCä&ôì§ ÐWqÏ«¼ ø¡#ÿ¥sÚyÃf5ÇÍGiùîâ —S$¸³ l±Á;2&°­JbžçÊ‘GKŸ«÷Òñ¸8Îô줾2ÛªÄû9ýÒ;§ö¾ÃéOûÕ/m&[ÿ`(HþÄhÉ×e5¹aÿn®õâgal¥ÙêïcL|¸éÚí«®h=)`“&Ó5AÁæD[ãæŒ™A˜$‹í1és?Ýò—›ê¹‘(Ÿiú.¤Ç1t“ !2:0K>6!éòÂ~Øïˆ¤}"ù hዯKµp Œ>Hèë‹2ø†èÁþ2/ûíÒc®?@©/ã>µ#A3å+ŠwÝâ7åõ¼›<> dO†ÝO)•Š?*CÕ ÑÐåçøœ)ð…›v0o’²`Ón¨ ×mk|bÁÕ¸Ëj© u™á..aTˆòiÄS¹ÓùˆR ˜åtra·Ô¥Ù*!‰cb¢Â¨ÓŸØïïï÷ÙuϹ»]¨åFVBõ&B×'c"<¢rús ñ‡”þÑÿô?ç—( ¾ñ…ìÓGžPðÑˬ}ÒÐ¡ÝÆµè‡4—µJ0ñ?îtæ>â RбrºA,„±d'$;õŽTj:Ê»§9/÷\öÌ {¸‚§±¼…¹žƒ§á¦æ`VƒÅ@Nâ®Ìئ] µ·lÙ¹åq¢*³2¸‹dZš·3’døER"q0‚/PJÂLÔ:¡OÿžÙ¿UVVüNx:à_yd‘œÜÖ«Ž9áo†w+`_ŽE6½(/¨ºjÐ%¶¶„¢¯¼sÙt¯3G=¹!ÿ¬~Ysd{ö˜Êf7Ÿ¨‡Ó*¾ë mMð›F)ºÎä²H®'ñv § N©¼m¬ySåËo®Zýöį6R9äm¡âéÇØŒwÌÿŸ½ï¬ªÈþ¾¯—¼÷òÒ ¡7l "6‚ (VÄ ¢€Ø×º¶U×Uvwײv,¨ˆŠDÔEª¢ôZ(¤çåõþ¾ßoî½\Ðü?™äÝ2wîÌÜ33gÎ9s朾geÌÑs“ºK€i(?[WBH–ð¶)”à éHÉ(í0¤¸èÎÔ2ÀÅ¡ZòÄD/|Åq°šÕ)@‡®(î“>·ÚH4š°;Ò§·Ëï±M’¶£Z0&&—d„óUà>ÞÕ(gY‰[õYS01ƒv:“՜ﴶF74—Áx(ð;RiT*kÖØçÞpëS÷ج)mü€0!ÎFÅ䬭iôIÖÍȾŸ,þ×ì>gT îºGª¿%ŸÿS‹BðP;ö¸:¤µµo 6Äa¥Xlòe‡9ØDšÍªMú÷¼]ñôˆ]2u%T 6`iýšßæ>2zÎzâ\°-­Â@D±žˆ±fä‚•þ §Žìúì¥ÅëJmÖô®>?Hjh yÔ"ɼ´Ìë}:#„—1)„Ñ€Õhhk1éÏɶ†nºnò¦·Ç_Úé.$ƒ]õÎj@Åq™EÖÔ—)¹îxyq|ËQ¼Ç?evq(«êœÐ`öTl]µfåÊûJ"¢°'‡ª5%’¼…é™Y·Ùe¦Åj=¥ãw“j«êþóùÆúõ’«O­²b¦d¾¿“Œ1êŒU%â:tVðíÚUÒaµÍwÞ¢OOÕżX6›ø$Q5Lµj›™¬¦®ˆüB~€·ñD¤ÄTòÇÛò¿Œõš®êjz¦”3ÁÐò6Yt¦+Í!O/Ä¡¿?UT¨ë>}¼±y5Õ‘ö‚×Â’'ØÓR6rf¦§Äâ76Ï*+óÇÖ!v”¦=ƒÐ¸±>•ˆmE'Ä0U;x¸môÖïÙü6ó,„ÞN /)`•n–Ý]Xê>1 ÌZµÂk}0Œ|=‡*–Ù2mÝÖ5_Îqê+EŽëµÆVîdìlh "°sšmPCÀXU„>˜à‚üØçˆ!‡URMç–n1­ü´Ï÷Tœ;òô9 /|}Y×ãOø$nHI‰Ä¢ŠMw–̬ñ•@^~*•Ų¸¸ùt, ›¬5ÞzÚ±z曑g=…W‚¤–Ša†€fa„J‡Â_߽ݳ––y§ì© $3sZ .p¤¾»uþnMßÊ XÖʼnm~¨fÛý,b{]CTj»¹>öiéMšzÆ]7µü m2ê⢷›¸ B\WúÖhj&ûirÃ7`µOŠôýhÅùجü²ƒÖˆ_1ZàƒÈP J£’hd¦ƒá8>)U¨+^G$sÜÓÞcpVP+.WFiä„„g£Ñr1ÞzR–‰¦“ˆÌþÿ?¨ÜÇëŒ;ãÄþ÷[Ì–üP($܃¦°ø¡©õ$³E; ÆPþ 2¸²ÿ08‘ ø? ëʃŒ_Þ7”ПÝàõ%aAQÌÌìé\„uIOXñk·“²pùê‚“ ÔÀ ¥ÛòÚ ‡öuC*"Žeð‡ÕäÔ“u›WÞduÅ5“Ëþf¶:‹+£F{Š=DIòÀ!Ã,‡½‚øé±L c1ÐjwÉf†;{#÷ ÖUl‹ø¼«o*瘔VßñtCý³M!»™¡ü¿Ïàcj` VL£‹ÃM˜>VµsDzE‹úY=ŠGAR–²i8 ÀµÐ%ûËÇi7LÛ6Ö–žqóžjÜ@Ä’Ðrï‰i“Kj~0-§§6¿ÅHsË–Odµk3)½eËi0m<­e—NßdÚM–Þt‡`‘‘?Œ÷XµîÖá…[gíxWºí‹l +*¸ªv«ö"DŠ tòöc×´6uÛ›=Oê4Ã’f“ölØ0'PS5׊UTN8L+2‘øpM”(I«mÇgüŽšu²,ÚBxE$åV|á'q ,úu>ŸOÒ™-§_òÉêÞL§ö/^ÿi‚Bey_x VŠ…ÞKK±ˆ‰_@ °Â€ÓyÀ&F4Öã/¾bÕ ÂEUõù£`ôa*Z/éoð&…Šèì”ß@Ï zWnÉç©}KSæ ®Ô.ç »dÂêË,Fó³e;«%˜AÎâËÁH¢*3+óyÃÒ)ŸŒi7ïîc¯²yšÖžóøÆ_Ò %„ªŒ­Dy¨ë$'Ü ¢ÜAÏp:t)PÙôïÜøöê¹3ΘTÔíÜKNêÒføÔ­Ón˜±³:;'kh8ˆ…G|; ƒ|ä…zÒÀ, k²RLº†²5>¹¸ÃÉk½b&)ˆZU.%S>}bg¿·ü¤Û¯ê³ÚÞºÍÈê($i°¬ÅŠ'ê­ƒ{M0N¸=¾xeƒ/¶«!Ûˆ&+B±de\«óÇ¤Š î(ñ‰ ás|þ`¹(ÊRÐòÆ{†ŸSví7R0 ®r/âRô¦òr‘DH {ó"AO+ØÄ?ÿ›ÙW_vü¶´ŒtPø<äï÷òK‚›ÖØæáͳ‹¼ :ïëL ý1ɰ’€Ïø‰8P«hßhÜb—ÒÓì·ññí£ E2^ÿ™¹~ïîšòwµÁz¯Ñ`àVÇ€¨%áÆT….y#ÓÍQ6¡óú¿œ ÿˆZüJ™.yT‚›2 1ëúrŸ„ÍÎÜç";…é×X†Ó©·Çë§ÍÙãB@—d+sýÕN¨jÀ÷·´UÛÇê~M*eV-ÍÑò)WßöÌ÷÷·j’÷̺¡×EÒøÅŽkl™?F,é·T×E =!w' {„"epÊ×hnÜs,aûI27ͦ Uo_²aúû6¹†~ÏÊ]4aííí:v|[¶¥U5ŒBjŠÉø®|O8âŸEaa!n…éd;pCå¦5£§ÝØÛ…”R!U‰Œ0x+¶!‘"Éà ۠ÇÿKËÉ©„ÖMÁâ *H‹Uâ!À×ê"9™VSM醟\zü$ÒÿfËãÁ„4vã†5y§ŸÞsnÄ–f0ÂRiv‚ªöÌ®¬sÿkҕݦË9¢´ÃhÔÜEµ …ÊRŸñ|ÉG«º·:¾Ý²F ÎшˆRjƒ+ ¼”0™­Z£§n×Û·¿x‚´æ¹N.Ü pÙGËÎÉêÐ~ž‡‚ Bn™™6!&~%¡Ï(Éqh·œÙjF‹d 6ÆÖmÚÖsñ­½–ÿ†ÌîÏÔ1ÐoìÒCÖÜáUuuô ç,K𺚶v-<"EºOÑcõ©â€Önþ¡„«Cè–Ö¤yˆßà°£Pe€*ôCO­‹_ѰïFB ³\¢xx°e‹BE$sSãAÔæ„å ÅÖï“–”üœã;´;)û`ñ°q½CS]YÈŸ$Ô¬+0†|ï Þá˜È8¶ð/º¶[»`ûÝ©OMº¯Hþ¦²åxþ°„4Ð'F8÷½¶Æï0¡7Iý`6nƒ'mÔ¿nÞm½¿!K\…2»ø+Ue*^ÿŸˆÞy6¬€ÂP€Æˆ@rbvØÓÆ ˜Åe“J‡¦ZÌóv‡Mön/Ó‹ b à9[VL-Îê ¨}iÝ;Ëž!²êñÉÆÌ+&­=ëü÷wdª9··jÖMgýÞ_ΨٸâQK°¾>Ñ¢‹ᦒNìMØöKÃ* Î[ëY1N!‘ëAœZ>u%:àЛ{?iÎ/èkÑ’É[«k–=³}É‚ÛÃ;7/7úãéV³.Ía×Ûm6|+ê6».j¦p@Ú³rŇã/êØE:q\üÖÙ;¦ÛóZ -¯¦—7 CVJ^—~1kkÜxe´YNÞ9é:}|߈óWÝQ²g៕^'С‚´J‹×鉬z¿ÿãi)ŽÔ‘X…ÉÁÚíK ñûå{R•з]³J|cZ».2z½¹d€râ\ùù|O>4›âø²Î(+KjZäóK:–-úñ%ŸÏ[0p@·a|¬ö ^ÿY‚:^æÞ^8_.‚µÈ„ÁÁ„.L6ië}˜àÚ!9ÿ—RÂMùTÿùB³§°Š&Bogˆ8niÏ`\×½Ñï£ñ|-ÉÁÊÓa—㯣ù˜°¬Ê°µq ’ü/"ì÷ÑŠBÞr÷–ÊZ ZåBÓ²l½‰J¹Fã­W¯É3í®ž˜ä ûc0#lYˆªâ`!sò.VïΡÊÍÏÙóÑ¡“Ëþ‹ÁH‹ÓfuøG~³uE(šðéókß— i˜}ká3Òù·¾íž/²rÚt«ÇÚÜb¯úŒ§9¬zÉ]Õ¸`AÉ…ÛŸ½q­9@°N*RŒSõ3er]?îµùÒ2Òî ÃËOý¦Ò7§ ë9i€rDx§õ_ÇœØõ„Î}mÎôÓŒ&S[ÀÒ…JV–Ì[²t\ÍËw¬8nù©mZç~fÊËk·«Î†³ :ÊfñHdk˜… êŒ IõžÌ/[ 6‡3;PWV㮫&•%Qo vÝ¡!”d5]Ú¶x;™š® {üq|§¢Ö  ¾€@Ú‰®äc±ägßúåØeâd¶˜Ú%AaQõ?±é½ 1 ¤Å ˜‡’'ÎÌPŒ€¥)Ugè}Áû+tS†B+Oä<ð¯(3 ¼øS°—EÙg"ü(Å‘ÖK¬8…ïZ_(˜ˆÚ­OÍíÖTÁd:g)‘Aú»BªÙ#,•\ž/ j­˜ äz °/1åC•!Ï Ÿ1NÝÿÇ댄öë]f»Éüz¥ä Ø-²Bìà0x'þ¤ÕfìwóÝ7ÂLë&èkÂt¬Èå°q–â-¨¾ü ê »w}}m‡þwÓcæÌ‚'vÖºa!ÔŽ^oKM±Ÿÿ¨çŒ|ÌrçîºU·NvÒ|i昲I3Çœ3xҺﲲ Nû/Hv’RªÕ¢Ó5Â^ÖœÙçï|á–%ûs8ª~k׬"¡µ}j»‚¿<ÒòUk¯zè2¡ÃÄ•S©xnbQzºY¾]’–+ïžD~"ô»¤]»™;>6ÛíCCL×Ôù£:hR3hu­O?Å5›¶.ë•“Ô[ºV@òj(TL×k uë×L-)ÝøÚ¦QE”g‰ ÝŠŽW\ºé“œÛ¯=çSCvþÉ•@V BAV)Z¤|‡˜…¢á­ˆ‰»Àaó,žjt­ ™€À!Å“@žâV\±sˆG¼“ÈT'µÐÃ2’ÇãI9~îª?¨$úsžT‡ª=›¾Ì1ÚF[Œ¦¬H$B*‹Ò3áÃZY£‚»ÉY5…*TW€ý!dÝ!! ÈÏü1S¬˜¼/oY )¨+9n_IùªOFfþð·Kʸ2u(ûÔ¥ÙË.¼ò™°ÁÙ¥Ò/ÍÜ£dÌÊÑìòG’k«Üôв'l/áÿ•òÕ‘@jOòŽ]»Å$…|OôxevΚúÄX)…Œ*) =«êFo|S;ê³fŸŸå,¹dÂÊþ \Ü“>w¥Æ]YĦƒ>¨Îj”Ö,]x™Œ¬’†·O…é™ýé.PÜÿèöú ç-^p‘YG’ðX8…וêûdªãºÿnºè–Y;>íܹÍSAË¡5X [$ ¨6LÇz›ÃÔ)7í´ÒG.Û ·õ›F¨a•  têü{¥9 «o‘¿(G «¡o¤]ûeéuwŽ8o…!¿ ‘V @Y)Ý{b>âÇú‘tŠëy½{™¬zq½Uo4öätIµ-Ú„yˆüÔ6w|UÄã1`™- vRo2µbù8qXþyT(|/u¬Ô&#S0áÂôŒÌr9„IëA­cûæ猗%(QŒ·ß`ÍšÂR…ç'óÏ IÆö¾? wZ‚üHÌQm,J Š¹ÛŸÆŸÀT°L }Ï… n nÛQ]/Az®81¥x#4àÝ5&8gaŒ<P*I üqÑ ®Hq©c l“>EÎo—­™zeáÙÃ:«Ž¢&½VlöEz Ù :~é™hfŠÅ™¦ù– ÏŸ6ì”iÒs[ë.ºä‰ÜN¹ÿ‰…ƒÒîMå·­u +qQA“[kFá‰K²é"Îò*σ[\Ã*¬cT|7UÑä ç9 †‹#~ÿ,ÁÐÂb1·‡Mœ®Zƒî]Jj‹¸Ù $å—Âu²¤:ÁêáSù‰d¿$£Ik¶™{ãö¿Ñpx5öqvÀAo¹$un ‘“ßòÂ;Jv­w×Ö? ]^}ŠÍ>.¿zèÎ^h¬¹ë}BÈŽWE¼Œ&à€|$Ç„øACô „µ³z³ ýßyôxÁЮé™RÈ3åwÄ‘Y‰ ”“hKõZ~¢c1¬š˜ xßï–‰·oUáíŸ6¨Ü TO>3é7`-‹ ÿ±ë_X$MDŽ »6r>€ô ­¦”(“Òï´f°²º*ýLsyF6:ìíÅ:6º(˜¬èÀÖî¬Ú&–ÒU‹”^×uë’ÄniÛ³>ÍŒ6¹;yâÁµZRטï€×[øeÛG†"wt¥‹#§Ÿß+QÊ`P‹bßgà‰Co)à¶â¦#bɺÈiDBåT  Up¦qæ¶.º¹¯ìýø™ÞžÕÛ¶]óßNx„™ Á~@ž5(¸…ì± !È&“-òÙªkìVóÛvx¥]ád|'KE]c¬Êí‰à* ÕahðPÞÓz*$h¡«Ã>¡ C’Æ`:ñìÿÌl5éª×'†j+‰XðmD–üNPZØÎù[…/˜Ø…_uƒWäOºl ÂZË*6“QS·eãMÀSa2Qã ˆx–€q9 ‹S¥ß&öD*V-LVË¥°¾ƒá£L+(SG¼)Lj­ùPEVò•HMŠNôAžÿ*`ÿ ÐwØ_ð¥0ƒl·˜9ΰx„n–¶ÑÀ"P²OáësrÙ¿(jø=!ó»v8V(õuëjÉ9-ª1¶ñ‡Bì]ȽSÒš BF„íô’Ñ%¿ú-s8ƒ uæc±\ÎÁ)êÅfÁO´ŽˆÀõ/îUT&§’gj&Þû–ܼ|yá]à(%“L}0•š¡:Àö惯‡½vP/–pîžÂýmëþÚ¿XZýoY7ˆV©Ö!¸-‰Ûr¸òsü[s;Þ<}[IN‡NŸø,Îö!ìȆ+ ØH艤<?Jù± 2 uúk|zŸÛ­Çc `&‚¥DCZ¶¡e›‚+%é½z£»8Û‰HñòkÔµèµDPd¾Ð×dÁÊ ø óƒ}0MlÝ V>ò¦YàÔ†£!:¡M1­.¶mÛ!të¸XÀ/|sN7ðrgøý4œ"¿âä©ÂWÔñüÓxĉ‡rZ¨èÊ|¦;dÌ•O‘€ÿkCŒ6àj$ûH0IDuÖLSŠCPÙ\-ü=A÷»vXÖ[N­é†4ftp”a:¸²ƒZ]ÔW¹{Ǧ9Œ.‘æt@S†C®¬Ï{?œŽiVÂ@÷ù‰ y²{ã„ nб廦¨}nE’}îyÉä2ÃÈÅ ØÇ4©S‹ˆÏš²n4"o°ÚÅÁžð…`ÂÆêh}ù'÷QFî°Ì„÷Ç¥¦=Ô³ &)«{wá)çtl·$žÙ¢÷ì# @G:WMi5XùÄ*DSØ|,aw¡&ÍuMVšÃÜU¾qÜ¿^»O+àÇß&àKª&H§)¶ÛX—…‹–ü3Tµ+b2è`»‹hˆÿdwex6âZ‰Wc)£mü@]Íbé[V†fa‘E¡ ¤ñ×ÕN^tOÿÙÈO“Ö›Éùy¹—êÓ²´0sLoB ’)ÊÂ…øçA.å—ñÌA8ôf±ºWJܱÆ‘2©ÿBŸÅºX° T.œ?bSÙB¨ðÚŒ:¤Uyõå#pn¦‹Ê¢}Ä2;Lºô÷è•–’+Œ2 ØAm":gó³7Ô4=(ÂRmYÆ‹";=Ɉ¯à'”,€ý]¾Sâ”Î¯Ä ¤„'h8‘ƒQ"=lbiBlÝ£ CËýؼ‹gJ®b„1©ÔTeŠ™MFmÜ‘e±Q£(ê4¦žPŽ¢|6#1\]íöÊìö[åO¦d¤6úCÐt·š­·?¾kËz[,$e@¯ÂÄ„¤î”^õOmd÷²Åï~|Qûî=/°YoÐwˆP¸-XZJß’ZX_™3s;^3©ôÖŠ,óÕ×wذqž«Åç)pŸü³k>i€Ñõñô *ÅÞOŸ/¸' Ì É$ï}ß}³«ÛH€°„ ˆ Ý‚?È9¢¡i¼?”­ê¦M½°­L…Ztjf¢\4Ý+qM÷¸P:»H Ïè ø°‡2åmŽÎúÐW‚dÚlÏÎH; ÎNÝ*U-óE E¢D“ åRKÈí –¯}¿nÓòa+fO>¿tcé›LSÒGfay}8¡kQ‘¨öImZ>iiÙ:+„£¯«Ø¸{åâ¿ÿ÷óâ3>Ô¥û–¥‹û·–.л«=FOm ¾gÛ†ê5Ëßœ0©¸ç”½nFyV9™ýu6§jÔÇ  (õXHHqØB:ÝçE'Þå+ßZ™jµ¹}ˆu•ñ¸@'ò5ˆÚ ê(ܨٽ뛞Ìô3VoÙ¥E7´84”MÖì¬xr£ëÊ¥\l(d„+',¿Ò˜švœ/àÇZ#Vo™Ÿü¯”Çr‘P”Å3ÿ•4<1^<Ŭ•J‰Èæ{,üêja<žaL„Ùæ7Óм«F€9¥˜dhÞI8£UwVü4—£s×,W UjÈ ™ÎéS Ñx#>êša8ZmØëókç,Y¥5œöÛÉÂhºè. ÂÂb¯qéO9³;3Ztxzö¹/¨½]JæÀò‚>â…>D è1X,°C'«¥ã= ìd\‹õ8ƒ¡3­ŠüEåXM±'0žn³êLáF©qó¶WfNþìeiÊkÛ~Z~ío¡®’4“/|{ÁÉéÙÙ×ÔnÙR³»lÓ¨¹÷üù ˆå,xè *“~!|}©mºQúòE^RPŒØ–4»ÚjB«åôØÁ¸/$o4¥hÃÕ™/2då™SÛ¶l{Óôm+Þжûʥˮè~¦y¡Ý™¥'B¡ý{‚õØm!™Áaî¬Øõ¼$}Ó ÒÌ]ã×]И•â¬\¹zÚ7ן1Šñb±Áå"KbJMÏx4€EâD<Œ=Ir[à¨þ+ç}ï‚úY¼tRþ@T(tÿQJâ»›áA¯ìÞ¹eQ‹ö¶J³Á”+”HÑ—¹ŠK_·a½UoN„ú úß×dq°ü>¡YRX*_ ¡hßækTRt}²a`“)X½Ðjb+V<0x;ÁT\,Lõbš¤K^Š%â‘MN{ )™…”û·\KQîÕÌ8è­> 3’‰­þú†s6/×ÇW»ëo«i@h1-ëa1Õ‰<ÈŽ `ýñ\370gÂkŽÙ_S¹éǹ}gÞ|Î=DV$Ã)wì-:ÅoAV¬eW¥ó¤¦¥Þ\YQ±ãÓ‹;œdEŠMØÉ’°zÈ ²Ÿ/¬ü Èj®B„¶Ï‰¬Îxivw«-e¨:Y@ÁØß¨¡@‡¬ÞSüÑmºÕ®Yöòεæ566VžóÚì×<>dц¥?\.5TûLðâHe[C>ÉgÂí³ÛRt¾Ý勾½ñ¬Wñ\îìŽT’p-êÖoZýyÑIÔ¦æ„",¼{óòÊ «®‚°¿ô¼„Â)åbrþ ÌY–hÜ‹³8Êi”xñ­dÖÄ"{¾{ãÓ2æ]¼n4[íXP!ñ ûÅ-Z)¾€ní(†QÁˆÉHòCM…ÎYøJI¡Ä åw ÍÂB¦…|×+¼©Ÿî A~¥¬0±gsï L Óè|BˆË°ª¼ë -?ݽkó9-t禚-¹Ð8õíeìƒÑ^7ö—Êwì7kêdÆ{‰P‰H¨¹/ü’Ó™‘ö¤—ÂŒr‰¯˜@T<ó†'ù¥|/"åx\bdÁg¥A›p‡×JkÞ„«0A}Ë“—xãØ())áD; ѹf‹4pE7 SÝ9ŒGì-„`éÄs^™š5_£©!V¦6ôšÂ*¢sô«N-»v‘Ò> ý+¹sRØd«¥xØ?Ggî¡Èå’»ôºÇ‡–¶þpåf£Ö˜ë ‡`ñíÂ>ÎQ‰ NÊ}Ó5pìѪ©ÓnY ºeDVE¥IcqWMµ4nŬ”ËÐ`$ŒÑüŠÉ(•ê­ÆÔŽ;áù@V8ÈÐëL!wdÃò…—Y‘À)”´Ž` †Å D'r0Rlʆãý’„úƒ-ßQLíº´k9!šžï¬npG±>ÞV'é| ÒÆÕkFHÓž+g½áä=N¤ç‚öý( IÀ'yý”M™s[¶ß K¦ÒŠÃÞ20aÎÙ–b¨^»üÅ^šå6Ÿ€îÆÁƒ qàŸÃ3ŽØ^&W‘FL̰7ÿÉ‹# Ž<(å·$Œ¤–ƒKr}é¼|üª ­©µ?¤GîºEoç¿h ¹çã’ºœ@4ñH<Ä¢K´ÎÉ0ivlœýú‹CE¾EÐý‚á<…é¯Þ]“Œ†KàeïÉŒ6NCK˜ê ‰$”\áÕ¦*0ÿ‡ù÷ð]Ú†ÂI)Päöû™í³we]4~ÅW}±nx¾kb«³Ÿ›Òjà[sNíÔ¦Õí  “Qˆ›$ù l--Y¿ äú™·œ==T“ÖNV!PiËÈÉ>NmÛá¯;­3båSNJÑ o²I‚¹ÁfʈÙq°À¬±õ{Þõ¼qßf.ü›&*q´Ê PíC—íùKR©>ïÜí]Në±"¯cghÄ mˆ&¨vaH…"IÕÆÕ¯MrrŸ÷ øTú©&Ig¬…o|ÆÓ¶®0µê8t§ÛWÊÂ=L·,":µ@&p|›å°kk7¯?ëö¾/ó³6VÍkÕ½eÖœ–ÚœÄ{e&ç%d·Àó(ãêâ5.k~›K+êaÈ0™]ÚËJ¤#µ„t˜Î„´@´#)9RPÉ´þ…í‘pð{]œm‚Y•¡éé8¬hÔU×L™ÛD(Àp C–âV-ö,ÕF¸M¦“e|Å9HÄê7»[µÐtfêø=P~G"¾Y!,UôHç¸ÎbŽp³ˆà/„4+L0…ôkùñ%‡@†M”e3ŽýáΘ1ãÒÒŠÊH \pɸ‰ˆ…ƒL>Cñ1™a5i²uÁ€=XµFS¿uBeÙ’ çÜvÎ`iíd¬¸Qˆ½W.¤z©¨(_ð»ý´_%³ @V¸°p¯‹§Ú·iý!÷:‹¢JGñ@*ãW+§¯Ì˜9»pOM]Y4Å™µe@f¨‘œ°ÒÛµuÝÆs®þöú^w!­–, X±ƒ>ÔµŸ¯Ý鸶‹¢iy·×6D±‰z¸ÈTà ¦¸ªˆ¦;S îÍk~uÝiB ÎÏêÒ"뮼ã::añDÞ«rK®˜¢n±Ë>üa3¯Å¨Ý¨T$⢭ˆüD!=&³¸ÕlÔ:â!)EuçÌçZXððx…2j<Þ™c+&”LAèÅS,VMÜïY1÷¯¾c¹X4 5,ªºG}õ–MšD´›Ü…P´ÑÄãúI¯7uaj; »#ݬV UN‰„îÄ(¼&£“‚”§K ~Lá6š“Ñø·,ûÛ íâë]M}x¿ÀËùÜá˘TÎ8j[u£„ÌðÍʬÌQ¦ŒLÔ4¼§µKÁúM+§÷Ûþê#+~’1XËŸ«¸F¡.P O /ï8~ÕF³ÑÔÝ ëˆÕ¡òq‡Õª×V}¼[X”U1Ážü$ß?à†Ä‰RŸ¹å’tÒYÏN¼ÔžžuËb†±ht 4ÔYˆ"ôÚGª¯­ÿˆ% Ë”¿O òU¾-¸à‘!ŸM»µk*~@VŸ£~ÖÈJ;ø£%ƒoüvËéí:}LÍíT^ï‰ANÌ/[i%( DåŒIÑ »ÍÙµµ¢øš¿Ÿ/IK*I¡1¿œ/‡RÒ´‘·|`þ@(XTõŽS£ç¾6÷Ü–mZÏh4¥ê}¡(½ S4*2ŒÃŽƒ»¬ÝUÑ Ë–^Q÷æ]_a«ÕnÚ}G›FaJZòÖÕ¾îyå¶2æ;gsÅN]<æ†êœ„¾£ÖW×L{~ì§|¦šæõ±p (û ñ’ÕfômŽCÞ±¹!€Éo:ƒÖucòd~tåX¢Ã°°?>p ^Vž[Ò)ˆ½jZ . ¤1Rc-šXúªìã/ëM™•ì ïü±‹¯3ë̯fçö:oÂBG Yb†Ør€Ø&OF[f¤’Þª%óï9ÿ ‘oQ½Gÿ*E¤*Ù%c‘•@W#XØÁDªÝ¦Ó„jÖ.þÛÀ˜Ÿj MäÝLdo{Œc8³åyÚWvÄ;% z-㚫Ζ’šzƒÆî·]Ç6S=¦T©1ŒabiB†Ì™Ë0¨µ€ ܸ쇫W¸®%âã±v¥th¶woÝ H†zÊ3³€IZ…¡5 ¹ž ¡dé¸ö…£R"!…uÀàÂr…øþâ{t¦´—7׆¥=AOÌ Þ ‰þ(¬å™Ç0†anªÃ® lÙ´ú&d.(}eU,TR8 ¬µ@8 H=¤¨Õ¨×ÅÜîÙ‹R ò™ƒåó{?«uERðŠÊ¾h ,Þü䄜ÜÌ¡‹µHëÌÊ÷y|¸*õÒ²ÔÔÁ r h–Q¡Èõo°öCl϶=>ÿt€4éÉÒ¢‰ -Å@H=ÿ3½¼©˜‹ŽJNÌMÆãý×­ÚøåÄIƒ‰ Ùv%{¾–©'s„dTÜiK‘*Ê>^ñøÕ«\\@8F]íî¯\©ŠÙñ°o«Î˜ÞWuiØ®Ã!Ij:†Ô*¦ôÐÒY•˜óù•‚ñq³‘a5}h\×^2XÄ p ‡ƒà›á΋[tjwW­©d\1l³óŒ¤a'-|Ž™ðÌ×JzÁøÌ›k+àR Bl)€š‚h"9gñ¦ü:Y øÛ“ú¿¯zîÆ\©:["óC;t]'›s¥‚Þ¡m Wh(–-zhp  jZŽG)7ƒÒ#åHXõ{î™Ã¿\?)¿u«ÅÚ¼vy¬+¢ziÊzáLì-ís‰U:~‘•Ÿ€§|C=+èDsÒœÆPÅæ­ŸþO"+R9EE]⸦xÕK†ì‚«‰¬4ILP–‚#×N™Ž‘©m?­N%o0'eEêMä,Ê"ÄR”nÎ÷RXp¸•yct‰èg·Nwì(ßúøçWÜ£ YQßN Ÿ.‰ƒÈ5F«ÊãSgM{†Ñ¥CŠÅ»j’cç_€¢˜ýÃCƒkôÚÄÊ‘Á Y3ß„ä#Ôë(rê](NGëÐl(,õc]+Á-¢Á…èÀI³"Ûw<÷HƒH }""+Lɰ\ˆÕaMy¡2÷TÑ@Ô /5èï2E€é@0€ è²FoågBÀîÙ´röWÃzR©¶šH'\ÐŽü¥ã~¼ÙÞ¢í_¡«ý„ôx07cñ7 ÕëRóÚž·Ë;q4U“})+–Ã21'ÐöZPd³phòɈ¶ÿ^£!µ-(nR{©âQˆvIÒM—õR"ÛëixSÿØÆc²+€å°•°ðYn8‹m ÞE™SD_@OH„Z9©m}ØYÿ†š ÂR=ä`Éî·ÄÀ§ZôZ¨`…Pé7±›ßè"Y?z4z´&qÚës[Ú´úÓa¼Áh4]Ý6ôÚÞИth%)+,1ixà:“0±»,â« 7T „u(º]rŽ{*uhu8ºéLS2Žé¢Am(àûŽ©TÁåÞ7~¿+dôsQ~vѳi× ø¨Éî¸#–šcÞ‰ýyÑ ;Êj²À·™°n„–?ª)†÷¸Ã"nÌbÐëÓL:©¦té‡ßÞ|îõ|ƒå¥5Ø5ÜZÓï•9ý`“kÌðø0G¬ÁªŸ@€l’@êž@21nÀ”Wn› a äâK"2Ëdþ =z ÙÈiѵ<·×(·Å£¢.°:«>gœÜÁ­Fó–-û'Þû"á±Ã!A ðdBƒ6¹ÓŒ£| /ò»hš›ßž…s¥D‰ü䨛 Âꊰìd¡˜¦•+Q¬»¬ ú9eOðØ+–«épÂ=‘Þ[WŸ×¾ÓçtQZ÷òð#Áóôð¦2hL—ƒŽQÌ?î„2Áº%C–>6d#YPå-9Í~ê@‘“tn¡qÁ¹…9åt~’Râä,¢Æ¶ñýýÉÛö›ïŽT©*Âõ’w\––“ù’&£ m¥Û+jê£ð9@;0’Îíó&fÛY¼·èz"+X 88ÇEVÁ`zŒ;؈-`+žƒ:&YS%»3½¿üèwvs€MÁ´ÐL¿÷Ãׯù"³]‡¯Ím·TÕGi«Š ŸUÞ+¢$ZÀZÀN°‚MÏ䯀< ºQúDNª]ÝU¶aå¬i}‰¬ˆè‘BSx{lñƒ’»éœƒúÏÙ3p÷%œ¦Ê²)¹õ¨–-ˆÔòy©”Mã¾@0a´§u¹üýE#XרQ‘q÷sÊe3º)¨«»?ÜÓ¯dÕ¼Àj\S¢c‡ »ŒÄùÜ…+úŒŒ™0¤065N©¨«QdŠgG+4„¥|`ç&¦ÂÈ]Ë0”ÑVP€‚Ž wQQœâBànßãq)ÔЪG>­\c;kpw®ò1ÚDÿgϰ#¢‚`Pj™îÔåÀâeb+&ÃHKîòà:Ë[§¾ümg²2¾2d×/ƒ (óä—¾;ëÌ׿ßÂL¯PeÉh,ÑH%F–é‡:€NoÀ4p#RFÀŒ8Šò#R” |gÞeà {­Hæ´´ÙMÐíâV½WÎ2è ;„M\È¿€[š6¦­[·üƒ/¯é~êÆ—ÿòåB*¢ÈÊqoúˆKÎ-‰¥·<®Êã‡35ÙEY¢8ÑJÑû\«åíS'ùi€²`a¨ðtd猽üýÃÐî "cXš¥{"¥–ûêÁží÷…c‘¿„€*®Ñ$ãÕÚx à^ (‹}†} ^ãÐFRÞY§>#Øv×h«£šÂ=Zþ@“à ÏÂp& ó&€?™ý–ϱHH ‡#åŒÛ‡Åb‡æ~W<~娭ëKú¥ã~¬š£ïzÁÅPÏJ×ÚiÒ˜<哬ÑÚEíò²ô`m0^É*èþÔÁwMËŒÜoÚ?õUËoïæÖ üýÂg{M¥7Ë‘2¦è“5ó/yoÑ~oÌj‡zX´±À6á@Â"c0HÏ'žòÔ¤Ö¬³Ëuô‘ù3ˆí&Ê*ÙUŸ.5»MÇ¯ê ©Y[«ê¢Zàk¡CC$% J øâÌkåÇŒUü°‚ŠzR̳9¹ ª’ÕÛ·•-žW4í–sG"…_läb$•)ÌÁ\òïÌáãnË(8®¢ÖU‘•š·\ •¬‡üGV“„)ƒ\¶ú„¨·ÆŽ&j5)gAÛqþ,9èÃFt…3×þãV¥à£6@÷± |†ÃÈo8´# ‡°]VnB@†Òˆj¤P¼MF;¥.B Àíw@‚šgiÑá R ûn5f,>‰þKÈhà45£'b»ÈÏòJ‘\ÉØóÆc듉èæì´TÉKBË ™û—Ìw˜µÎhõš²%,¸À• ïîÝOªÞøq ° ‡CgÇÒÇšzoxwÈܾ[«VßýòôžÂœ0å$û ­â‰E°ÕTjlxfèêFOà ɌVgcÇÏgöÔœUƒ?YS®Oɸ¡6Ð1P ÁŒvGNv¦°HP¢ØãúYÝÔ­†,ëœãšÔnè¤Ò…†ÜvwnÖgƒ?7cBàÀ’ÿÊO¾çˆcmpAmvRTôúœí°é;špÕš^øêê“NYêºæsXq `m ¼úˆU;P™-™ÐiÄÍ—.Œ¦tÛY×Å©r±½eýòZEŒDT&³I  È^vEâ½éI¡aˆh½áHr{P“ˆeµë—ßµû§wh¿=ìmxRÔzïüt¤`z,ÅŠÚN ¡*`Âf^,S±]ÙB"&6áøÖ Õ§É¯=H³@X…]³‚|$WÒÌÄØ‰Nñﳄƒî[<¼Põžx-MRÅêµ»7ÝcT{»äfšáj[ëO`&V鱻߻áù¦cl°¬ÀÂû\׸mÅ`[¨rq¾9.’j ¡<·>»C~»xgñÉ5%KVŸà{`?¸?‘y”ÜÙ÷¡ª²U% Z›´Þ£±m š²7x“º–4)CàODµ¼b3¯Ã‰·fYS/MÍÎnµ¦º1rVËÌÞþ~4ä-£(£šz×…ØI¢á¶™¯:ß÷zï´ìü³ì&[ÇW›WÅxüHž¢¶€¹†ÚÂÑ%:*“žõòw¡Œš†÷ã½øü¹Ó¯;ï …  D a0ciV‹QßXá]µf…°üõÆ/÷>î§ú‡E¶L]¦òñ²ñæ¼¶÷n¬õ&`ꃊ˜‚”;û™<Š{q-ß¶$ª(«¢Ò9¢>©nÝÒW'ëѽ YQ{BõÂÞ%‚ý#²’î_€2ÿuü '® §ôÜT펅 ïÒ²‰õd'>ÐO´) ¦[û<§ò+¾ùöæsnâ‡÷¸e UDËžöÕ”ëOï±ëÇï†ÅwoZ“aHêl³F›ÑÂß*wÓ dÅÕ×C@V¢mIBÎÉw…ÇZTl'RiTˆ”867'F˜ñq(QGíÄAÝl´Üí´]´/KÁkR,ø›hEe9E,€Ò*‘ {Rì®/ydГ–‹‹Æ¶?åŠöûY0LÙš¯+Æ÷ˆÈ‚*k!,JUEà•I˜ÕQIqåÆeÿ8^o½$aÉ¸Ì %ºò:¨X€…§7ñ)M˜Ã&–Æ©HžŠmw×¾~ç&YµAÉí8YÙ" í•ã—}«Én×cuÌÝA3¼pø™IÚKQQ¨¬ æGhªÃ\‹^¯ÍÆê_¸bÓº5?~ç–7îŸÃª)Öbܯé’)V–§»rÜXÎû“©ÙÎÝž€jð@^%Ñl®@Œ|—× ´“øè²RmwÙª)Ón>—D[]2¦(¾ìí[¥ ß]ô”ÍblQ|m‘x6¾çŸ]—Ö¢à†ˆ3«OF~Ëëû¿õý4Àó3N4jYb¯æ/í¢I´Þ!6y3-‘–¢Ü¨¾zì|-† hzô0J9)Ém/7;ìcqÅ«ø…¿Ö ˜ì7…f…°À ÛIԠრ"JZ„[^!”©žCûXÙî:Õh)t×Ú¯‹ÇâEþlA=‰ëŸö lu³uÈ—¨€úÂÚp"µÛÚZo CGoRp$Œeyâ,ø$KæØíºhmÙë‹òÁ]S“¦W߃2(•þÊñËg&2[–í©rr(¬Pnâ#Å—áC‡°²ºÜ²Zô¶DHr¯_òÖ¬»ú?ˆ§>UNEÓ,ªEN°ÙÒ…o̹<3'ûÙdZþqU~Xq¨Ã&f 0y³²(â ¥nÜÕƒåƒ)è–ª–­|vÎý=Š“êÄRRU ¯@лªu—3úyéI;+ö\?ÿ¯ýÆ#~|úõOqÚi=GD’ú‘Ò_>ž‰FÞWºOéIc‘ “‹DêÐ…O¿à­y}SR̃˼/¸îÖliRÃØç½c—‡ì2Ô¤S9*Y…“:RñØvð\þ÷§ì xP12Ì­ÚhmÓ&áAP*âT ,*¥¹8#zr*L­ØÎ3J¬ fÝÃɃS³PÀì\?gûð‹ ÝøÍI¹m»U»!¿ *›ÉˆÅ ̲„"ÁpD—íHëŽ*š¬ÈÊ2°lñUâîp°+”ààñ˧%²Ún®ª‹Y™)Y?"nra8Š >C|1ìÅIJ½Ö½+X¾aÍ+žö)«"ØL é JæŒR„êgÙ¯þôµWLy××'ôRm'†Žª5B°Î2eJ˜ìösË?½^§Í² ïÙ²½tÅ’;7¼ø—¯Å[h ¼VíÝÏ]¼pQjnË:SvÛS ´º•ç½>û²ïîè;¥þƒ,žþÄฤn¶>é8©K«Û° ;gî…k"ï@`.}wþ ÖôÜ÷Lùí¤–e+Ú/“¤ÔS²šîØùàPWþ°8ãSkµ3“φïJîùµ<—ÿýi³@Xêg@›Á,xŒ:R òX#XHai"Ý(]âêð@8\Mcàìû[Y Eؽ£dÖ¸sz½ðí¿³¬i#4iPBõT×q13%#?N?#ùù9½†|ºzs§ñÙÙ–Œ•^‚5F®$ç0KßgP·ä])½ ï&PV"+¥×ÈÈJ¾ù9â"¶¢ú ±N‡>¶§lõä¯ÿ{½4é¹*UE6Se—\}¤XßÿÌê“[?6‘šÛf‹Û å(Ùü EFTê7 LÑP¸WÏê%aÏB,•+¯Ô°nÝ»³ïîÿ׊rQ-IÞ{&g6 ÍîÂåÇ®Í%W6ì®kȰjíšÜü‚ÉýßœsÉŒ¿ôùºÏßwžSZ] йN**òÇÛŽÌoÓ¡Ó#'—•Áípú¹rPllH:±Õ {”rÊž0&’ÍÛ"RÒú_óÙªù›·Wܵ•G’U—?àÿÿ#æ&¥a”²ß—°I XYá<6 ^ ¼e«¾œqGŸ»ñ ÂEþèÑ’²h ä÷“ƃl1Æ¡m‘”(Ôf“N}»Ž¿™PôÏ ®.~ ¥k—.e-Þžw÷”[Î}uÎ}êö÷ËZwÂ;±´æšzø-‚==´ãaB Ä£$Øv}5lù6SNNîÌ^/NéYr_Ÿ‰´R§ŸTðÏ~ƒõ¥‘À)p•<4eªK¶M‹ G NÍ aÊ@ 8„&@„T–p<{´ ð[ò¥LKr+IhêS¡Rس2$ðã£W>­f© @ÜþŒšPäLÕŒ·ûh¢}_ž~¾Å‘ùÏ-ÕnlDŽkhùŠ7›ê„—€£I¦CX…D0Ö,åÚL:ÏÆ%ïÍþëE¤p’….—¾äÖ[…=õ&duÓ+×ô;çËDz«S7Ô4Æ´”UÁô ‘†Š›DÈ[D('¹4)?£õQÔ$lz½§JªÚ¸ù‘ï¾ü9>¥Ìˆ—ÿƒ‚¬ÀÃ7ùpœC|¤¥ÑW5z"¶ÜV.}ïÖ»vW½šwb¯',:Ë+×LXy…ßÛøêä›{‡k”ƒßk•‘ß/nÍÀö¨¨A0y&Yà1 dÅ~"S>{éZ òHÂIv2ÏnÔ6lZñÒÜ.–‘Õ>Â{YþüOmß®ýÌFs–³Fý¬ÜÒƒ Ã\EJ*rD§£Æãàv#nª|ºI#ülÖ]ýnB>e%.û:¿Þ‘Q·ë{—?€ Ç–mÛÒÚ3l¡?ì¼'飤 aí,=£ÕI'ukj¨“Ýþä¿ÁEÛLé)Y3ÒLo]õöÓ}"úuÐS» zûú©þÆÚØŠW\Šú¸ñaö}¯8ýÉ⇜=Î~Κš~%"ßQŒ0•ÔGø\X‚­^˜Œ°i¿ .gþ{Rk»1µ£Ùb阚Á]Ù`f©ç¶šú¯7<¦y•«Ü\©>µ9äìT“N¨>-Tаw|‚š]6 ŽrhV ÿA=Tdy ˆˆñV~„Ž7>7s”õ?7™«÷(ÈÇ\‰‹ßžÿ¼”ZpævÝ3c»Í^dÅÙLP@ì,2GYV-´Yå§ZtÌ}à’ûD…öéð*²:õÙÏOi׶ݬCzêž@V?¡Þš¾„nØMqrü§ÀúhŠÞ¨—vo\ÿÀ~I„\h?TW"»ŒBåÖ•”ˆ³TX˜pAÍäÉêúØG'YÒ2«†×poMä©(ý !”Ò³òþüª]4nÌÈl{NV£Ï«óø´Õ^¼ƒCµÕ–9Ôšš;47½€ˆÎ×éì ~ï˜)7ôz”«vbQüfhž7þsŠÁ‘žùéÁ÷sq_ ¶ H¡©HVñˆ°Å% %×÷åoOK±¥‚˜°7,²u‰kõN/v±LÉé0Kž-«ç•ïÞò¨Ä¶£Ñ BfÐ9La2Ôh;ôAqMk  ‚aeF΂bÒ£šÂRùvC2áÃæY.…ãk…KþjÈF@0e%KNpt`ò‡æÊNÍÅ}^œÜ[gKÿÛ–7:\¡S@!u’:{Ä…Gì:LÁg,«ÞÍËß™{ÿ%÷ˆ·öAV¤zÞîsjôDׄníÛuú®Á‘º§Ñ³è~J½í RmjùŒ'ð¹W âñ8ídÅ«¶U¬þqÞ°ò·,±°o&Øœ}3á5Þ b’\? î­—¾9g9#ó5·1Íé…±An3Ú+ìT–®Áç‹¥eæt¹ä½®[³lx·³/øÖ¯3háÖ^RׇÂáDE(¼ Á £á}[n:L6F¢£¤¾ʵæmÙïcßÿí’§z<ý¥³GN¦úY\39:ß °áÛÉrš¾5o¤Þh¾1&Nõ’;“<°ŸV ŲÍI}º¯!²iõÖ¶=Ý«¢Bl¿ÃÕ<Â_¢Ú Ã>B» ²Ù•QÈ‚ã[Ø0Jzp±¿È®Y ¬¦Ziã>ƒ^UY­@ã4ûl¬T¡Õôæÿè˜b _ôpš##\-ö‚‡c~س‚½|²LåÈÔA$‚‚¸øº{ÑévCpdžïæÜ7PXDZäŠb¦Jõ<üQ‡ÎŽŸÝ`ÌtînôĬ°ë³/õ¦d,gbTöD^X¾ƒ'”c·êýÛ×Í™u{áÕxTMÝ*<'Ûò ÖÅ…osáYß·ôÂêá P³Ý¤çž5äEIoê%¥8ÛTã’Ûë§eTÈêX"Û[ø\íîÆ€”—âü×–ÏgädçÜŸ\j|0ŠF±íÎ^a™ïh€¶<‘ xtÕ•»GISÿUéÚ»ÕH ·ez9ËÀÜW'L-ð< ‹Ô¸ÇGœ÷ÊôKl¶ôÅ,wûcR•Ç'bõðrMGÉdkPªiI··®lÑ¥Û^Þ‹üÿHVPý|:2!g¡; `7šú€%¯q´ýMÑGé¢Y!¬xLòÁ’Éø& °'Qø‹µaBr;J`ùc²¥&>•[^:ð±pJn«JÀ­d‰µñ½8á³þFÕ8:*£¦q"–“–jHTo]ùÝ…ƒøŠ’«“Ȧh„àÛ|z×nßx¬9Ù»ÜQ dV{‘Õ¾@Uóß "+–c7›ôNˆ^ëKxqþC—=ˆ ªI@§ë€2Œ’Þ½Ù¬`x¢×ÛïuSCu•åwDÁó6z?ŒóIõAð€ zd‘)+¥ùÝjÀº¹6ǂΠý•O=øåç×v?ó̧&„ì-:¾nÏhið‚0EDÿ@¬paÛjI£Ñ(Síf江ڋxÞ§µ¤ÿý,`®È?V—’1rOP+mßU—) TF´©:IIhN«YçL6úv®ú~àÖ·þž:rø/{qÃÿ^¥ßžÃhùÕxBcÁvyŒj¦±‡]C ,•cúí…øMö˜f0!6R£ €ƒš´v8õQ³©ó‘ª©*2ö|vb9õÞŠºFÉË:‚%ˆƒVtAŠž"d›0-½¡qÆ´)CQ'œ_“°[3ÖRY×Ëß_<>êÌï´³Þ­ÈÅä/`^¤¤Ô ã4øA›9J•{°>±kéÜë¬îDZqy™¢&!çöËcÉܹ¢üiïrgéò²=¾´¾¦1²©ÆßQ߃¥V8-ÄB#ú?…¢øi• WëEx`U@_×qG^¯+>\›…Ûp¨p…ú‹~A²}- lÍ « w4Ë8ä¼ãM€Rô Ô'¢w 3[‡».Äþÿ24É|²³[¿è58¥@4“а¯¥ÀA X‰ÈýD~¸RÖ!jÊ×ß$}ùÏõœ÷•!UÃÐ$/}«äCf«ÁÛ€$Ìš¤ ¬ˆTd Ÿ_f-~ò-RÞr úÚòmËç|[¸ìéº/Øí!® ÃÒïV˜bÁ§ÍFwlZì…Ôá¸UeãØºB%C£M´°jµXµ¤Ö¼htµNiÁ)ë–ZO\Jk5lð‡?|³õƒÇ×ͽï‚kgÞ|Æ) >~å´7÷ìªXÿhvŠAÓŽFb)™önÇŸp3*ærD–<È™ó ,!YW\íEbŒ?ü™Õ(ñÖ…oÍKmyÑÒŠêˆT•Ô#¶Ø*Enækãi))] aÞR×µãÐðÕõ€”êáW刼Áe@'SvaJ’lpfßA”¼}ÿLšK(ÅB&]”½…³ €+€ JË4âç´g ϤowÃà(;” ®ýÙÿ¡X±ÌÝ'Ö÷¥©—Ç,éçîjhL4Yîd?Á¿Ê ’!â+h²'5Ñ–@$¡ëÞ]ñäð Êþ¸¦/(-°}žŸÒWŸ–ûÈ–:/H˜8d<"'yhªò0Àloþr™Tݤ­EÍÐl\1ë/½/F²ÝDŠI‘e9ÔvÐp1A4‹ÉÚ•ž‘HL'PPQW²U€F£AŸLåÝ´üK{AÇ B:«%Çz$ªÔ4H¤˜nS­'Ú!§ÝÀAc-Ú¾µlÄò§†¯÷ÎWËræþýªgÎsþEYXiõÀD…Ã`>ѯgÕþ¢ÎdŸg.ÿ?î®@Š"kwO÷ä™Í™  ˆÌá<==ô gΞã‰(‹NŒ˜³'&̧¨‹YrZrÞœgw'OÏÌÿ}ÕÝ»+É]\ÿ‚î®®®\¯Þ{õB·ÂÂs7ñ[còm“N·ó_Ž'ÛzÂô¯nµxó¯\^V£yaãžõ'°ân`f XLy9>¯b®7|¶Î†1ÔûéWZ¯£·1=œ]\“¤Ÿ˜ì¼V¿ã-Nº°1%³¯½ÖcU,Ùqø!ìå”D½E[°ñ`¦h¡úßQJ»>í«ÏÚjá†jÕšGqêWBÆKðq1*×–S‘¬|4\ÃÿÿmHê¾ü@ ÙS2ÇÖEÑd¢M`LéÓZÌ âZ:hÌ,Ì|<ƒD‹gx¼ÖDíæ sÆ1ÒÌ̵Œ!i)*8àšô”¼‚×jv!|j¥ð)Ra¾q¡´üŠgã‡+°Y’y^‡X»ð墛O¹ Ñ‘¶j=üº]†u‡w!p)sÃtå†ìi?Ìa7Ö¼¸6«Å³7Í›V<;çßg^yìôÙ/gw?àÂ-µõ0¿L•F½WÌ2AFZWWÔj=Sr‡õà)îþâϳÖoÞpç²Âóh,Ûxwª+í+Ÿš"%-ÖÁøÎ²0¦ ]´§‡É“žùn–òÌworÕQ“ ѱLZûS<îüGˆŒàöè‡>þ«5½ËÔ•8 €{,F®ÕŒ v@ÇÊâ¥,‡ã„}ÿýTÏé§ôÛ´ó¶z‹:bO¡saÀÚ#ó¶Uí}ÌMëïÀ†…D¹ì^ˆ¾Ëp¬&¢â¤Õoø­¼~ïûΰ ¡¸Æòš@JïÞ~y§E±½ ºXV À•EµØ{ Áó©Îð{Þ)¾Çä*ÒÉ*‹Ý™:¦–bwÔn Íì–Ù­7µ•TÓØÒºd¦ZµÊUï.zùçÆâ2H InâÔN;rÊÌ£,ÞŒë¶ÔúÈ3Ò­a°cѳZóæŸ1%šiÖð†E¯šÀŠ"¦¾ž²¿@ ¹˜²)±«jHAMÁ7ý0ùb® šØäVž<ôª*,6|ñùÛ§òÙßd÷έƒž~)°‘æa SÍr@˜.ô‰@M™õÕ¨H!0,æX,ú z؃I~ éc¼lÌ“Ãd<²¾¤!$¥fuäÄǾ„$ºü¸!­ßÒ¯m¿ûÕ=x_&ßpÿÞƒßö©éYUð ä1DF˜–õl ¬8Æ\ó´am½?Ñä‚N¶7ý.ŽGœ´)/ÒÍ+;O#Dh4¦„jóH–x$r⨠J”cÎ]ע߸}uß íj”"ê»õÆâw)Œ† (FÇbuu·Â«U4“`ýBdÅ>µYpÔ¬Äýqƒ`º›nÁv©¬ßø¨s0Ý98!i‚LJ©4=wM¨„>9,ÀÖx¢€éšóט¯øø§ Ô_cåG=ôÉß4gúàjx‰±B'ƒLeNdÎOÜé“\¿ˆ{šn5«#|«çÿt·è9o -Lüô¼î÷ûàI'¢i4£ ²Dd‹üY†‘7KiÉ_œÆs¼n«V¶jñ7cNºZd `Õ.æzk¶¹ãÎÏHøâ\L†–ª20'¯ÑQüÕâYæBêLXÅÄýû®üò£7F&J‹çxj\¹\nů‚ýQ g¨ K`ÕÜ›¾½é„ËYÎG3ˆòÀì.%—ˆ˜]Uɪu¦i€A ¨«ë#ðß”5ýÈɳF‘·(êÐ6á¶÷-LöSžžóTÔ™3l5Ìò¸!ȪŸrŠa?èf1ú¸Š[q"ꆟ•º`$ñKEöSE]|^E½¶¬¦N+®kÀµ^ûÏß#þûІø·õñ«ƒÉ ’}m³½o¥–vvÈšû”#½ë’Ÿøî‹ãþóå%(E¼p#Ãàþ®õbŠ)€e˜Ç æUDžä?Â%ÜÉ%+—L¾FßlÆsÒî™Ð9Ç­P5'!ÂP R„­å#Š-4 ×ò0@§æ3Ê[Ñuóß[a¤4R ñvwê¥q+à19:FÃÑzñ-À„sÀh5ŒxºÓ&E›êž¨xõî•\Pæ:¿Ç³èÀãþ伤;ëˆÊ¦fèäÉÀ®˜reÞz¦zÞ¢ ñ#Nè\Ð ´6U4Ïþðe „Å‚Ý ¼‘ìWY²qÅ•`R€þÀw£jQ÷a½Éÿ†41Æñ^,º_õõõÇQ·ô»Ë•ÊÕ³u§…jʼ6U`à £$-õ×ÕÖ½Ãï$H³÷N*ú§ŽqÊbÅ"¡µß¢“-……FoŠÔâ1ù4Ç´h5½´œ¼WaÌÁ+꣟¶&ns‡z«àK'(nM¦v¿zUµ/î†3]±ñ èoÑï|à‡Õ¼bá8 ð–lEV³ÁÄ…ém5/ ¼fA^+ñ9à?æâÏKD¨fM0XRÓ }[Z_T'ÙË¢)ÇkŽüNxâû…ç~,l~øS&Lô;~dÙšÖ *Ë–éuf[èÒ*OPWúT"îwóOgXMk°mV9^âÂö o.\²âJ¡e¨ç¨ýù4r¼¾ÐÅ«?éyAœä‡ÞûöИÅqrmS ³“z»ÍYat…è ù;­X¥þšÆy ¾}‚ͧi›–nÀnj.x` c|”ýN€¾6ÿ!3=_½wE<"§/,K<Ãe—üÕ¥“¤Ï_XM bæ×RÆ.Þ€œÞ‡Ö_tãqÇ]?ò@­¡â>8Ëå\—j+Ž–µÒ5O߬+9ÆM×j6Õ&¹”„ÔXWó:ó60&}jµ) ÀÃÆyF‘R_s,âÎérâ÷Þ!¾Ñ_Û¤ÖoyZJ¾ÕáSÞ¡ºÒ§®®òq=“ŠÓç.ûI9¹ùG<´ÿ…*·†X¼“Çy á ¶—æg|<žšÐiä÷‘êBÔ\Ü{@:ÝâgÇ@IDATÊ8ˆ°d e«h~\K®¬óiE[ê´Òk Ý•óåqÓ¿z ÅŠ s÷¦ sEQû阱ÞXóKºaÂÈjI”0cì„)lò `µ Im-wô{†W Ä À©„GIX¨«·Gze¯d ¾BjFÎ…{º¬Ë]é.G»b"PDgpâÆS\8“û? ¿uÿ&}¶¬2sê'g'Ý™W7Ay@XgÙù¢WHhÚf;ìQ7 ¬Qð s×£çM7¥‰|žìJþâL±8ÿvgf\±ÿ½Î"vE~Œ˜­ÀÊ,ˆfßà… S 6Sõ%kŸÒÛ²}Þ•#-ã†ø¶ÁŽ.€™  Ädg1øX/Î(«” Ä™JM囯ãu” g¬&Ý}ÀÕÀ–jç\?òèÆÕóß÷ætívìô÷®g!¦£(Š,X0C¼N‚-'¤âQID_âAÿ¾0CÍ‚-eåÏûê[B]ÉX°¿jÓÛ£õ¶DÒÒ3ÂŽ…¥1֢ΌìÁû ¥8ǯëLÖÌØÀßl²eå•5ûcðžH0ÊÑÃ:&%iZ9ÓãQze9“RV¼¡Â(ýÖ^»úEkõò©¶Ú•ãp‡µ¶x²½nåÓߺw3bÕkºXÃZŸ4·R–;Œ ¥ª)i‚šàÏh˜+¬œxÖ10bc.`눵|·¹6pv?æØG¿Ø%ÚÏùеÅmPøúÍr¿(€'L `ò°ƒ2XñØ&¤Ùã¡Xîñ¢v^€)»a‰ú6JJ*2 Ž,Oó!E Ö#ãrV—Ì#r7Iú„uÏgÙiߎ”FXІrذ¸ê*hjŽà¬‡¢äò?ç£þ#fïùióD¦Ç«H†ù˦]ý(È,£¥Øñ…­ƒÇ>70n±Vß ¯¦ˆÒp–Š|™^LpÆée"ï8œ¿*Éæ²¥s'ýóC&)…£ñ=ˆ-;AÝ}ßßrâ™ÝýÚMàã#öŒ €<ˆEIQÃm+àí¯ó0„€±]†¦æšXÙ¦Õ_1õVNkå-_,â°jƒ9ƒ|eAøÀšÕ €^"ÉÅ…YK 9ªóL¤(6£nWÎX lÆ :)æÎ;zueCÌkxܦÜÄ\ã)v»šî¶YT¸5K–ŃõŸ6V—­~òæEȯÊ(G—”>ÝÝÏß÷P·7ãL—Í{lØ“¡”44‹?˜:“„¡=E¢Ïèf&»xí8j^¥k Õn¶föì~Ö!÷}rÛÜ;OJg»Éü •’ƒïz9;–°ä‡`Tý"æ*çJÄIŒØ:ñ¸‡: À2ýþù|e¥nW·Z—jÏBg‹µ ²YŽB‰5iuÛâI'ùX«ÿÌ¢ &pvxRN(N)o„9$Yå”ãÒ1E¸K†ñGç‰ÈI P4ØüçÅÖGîfŸ¤ç÷<;æHS–tÉ…|™ ?Á˜j¸×£þÎmIH¡Æ:îÄÂ)E…N÷PØ É…/ ùùR²è#ÜíXµ­Ž¬Z{†aá>Íå”d_Õ랸¹°‚¨- ÕýRÖo[`E¦‹âÎ}g&¾ÌÜc§†®¶4‡ÂRºMÝïÐ;_Ø÷—û.]A’;ûíbËŒ³Dºÿ½Ó-žì»V‚oå‚ÈO£PôØìj ÚW5ÇÊkߨټøåu/L$¢àJK(„ØE*Lvˆiy¼¨Ü´þ剰z#ñïÉüÜ~pÁÀá“ûgv;asC0 L‡NM`å赫d"Ëí´¤ËÍ›ª–}z~´~S™¬¤8à GŸlw§tcáíVHk"v·«w\V2ÂèXýCŒßÌχƒ`Ó&æMáU^÷Tè4 G4tÝô±5N™½ÅªZ³"±ôÏÑzôM4—bÝœÖÎ ÈA*%ïã…Õô“}A(抋Íl;\ CH-z…¢ƒBPTU`(¯¹®òK¦6y4¼Çk0Û€±IVÏY>«RM@`m|KÕµÌü`HŒÒæ8«õ­üáë·˜Û¯ùŒØÁ OÀ—"‰ÃBjW)#að¯ˆ)-Ê>`@t>¤³êUFÒEE~ hyÂù+Ê™)õ`.ÈÞØ§Ð]àV¬€q}ÙÉDjn·Õ‘ž3_¬àO!äÛŽšòöoFÁ‹›8 ŠChC’ÚÒ&¢Õ@uP+_÷òOï<=MZ6{CKY1@EÄæŒ¶#<Ð$)LÀUT$ÉEsð:…ߤ¿€ñ#!5*ϯ׺¬Öƒ@X)Ó€EE ‘fH`©üv‚‰¥ìîÙÓù™äà‘“§¬©®Íarb³dü® ½øG}Õ`Cƒ ‘”t9`08¬XRôÉz¦6±RÞÓ>8Mîq÷kƒuÿ¦P€v¡Œ,tÔMdÔfþü’|)îq:Ôd}ù· ýg w~c11Ážº£[Ñb<ï´@L£n¶„bàv»$mËúîÛü®¨°cd¬¹ mnï ¤Ý#Å‚°?†C IÐyÄ"à}N‚ú½X+(Û¡ÒgÚ#Q»TÆ17¥L[IµD¤XÍšw~š=sœôÍkà$ÜÑÔÙ£‚¤d$6.1¨™€ÍÚô™ÉWfà·øï¸ ºBÌhñÄóït×ë9ûL^[BýˆLr²pmÀ4ÈdU J‰h„{ý¶âb).Ëj'öÊoÌB8ES`Œ@L)þ’Ùï°BrPŠo,ÿHÒŸ ÄCÿz÷ÿv&€%m˜Ý@F;Ô6bK°ßŸƒÓa±†ùƒŠÊHLʲ+ÜéL‰k±¬w·ìÁGèyÛÝi'T,Dƒík$°Ò1*Áç4Ô‹>9°›Y‘2[!Í}¿NäÒfr˜ Ï™ž9œ4)j‚EÝÅ•˜ÏèË–Î"\02¢,µ= a I`®ÔKÑzU÷Òo;€j2z&3æÈ°»^=ØæòVoŒ®ŸûÝux%dfÎü9ø›u7¤¢Ú“ª#àÁ¼X•ÌäadPªÃuÌ©½¯dsŒŽÙÒ÷[‰øÚpPóB1³ Õ£(Me% [VÞ²â?×£z: ¯<Æ_ׇ›2ß“$.b×#ôºñÑÜ^]{pØ]pâfÓ¢ßÿ¾›÷¾õa^ÈòÓ‚k¡¥09^*”å)Oœµ_¬ž¬®öi:fˆhÙ„"qÅ©¸RÓû"ëo³¥ì„4sT¼HÔŠ¥u(à`A¯?Ž!Aý„v¼ˆ@0PÔÛÈ–h šPF›·j/ãwgèTË[`„j¡¥ª5B¬ 2I€…`ä «»Ï!ã^é±&ÿawvÈÎ ŠÎØå@~é‡î, :ÙKÀd’n:L!T Aûyòã ~p,¼”ß»/·WÙp¢çðcuÄT‡K¢ 0Ø µ£`$••d°!ê+_'Hˆ=͇5ÞÅŸô†ÞbSKÍïþWwFŽ´iù¼kËÞ(\L d&1ˆŽ¬<áH–Ú1VÇñ4"H†¶Xè3ö¾*%,µþpÒ™’w¹¦:¥Øš¯¨öCÄ7ϲ۬¹°½®U®|óÇqgh6s6±Ým­v؈1Ûï¦gò»vïq¶bwŸŽíø€˜lË YpôÂñ†ËãM©ˆ v(¾ço õQ8KAJÌÿöÍ;†wÅ©YNw:N3ÔID< ì± Iãš™ï: þÞ€1…8AÝ×ð4[ì,œF qÚÂDT`s&/®ÃåtàƒN°L« áPÍj‹š±)V;ö ÎêŒÉ¸–Ð§×æJ€¸&yÕöþ±I €Jx@¶u§ß<… ^±±v˜XJ‰GÐà  ±Ýofü¯&ò5X.MVú‡€‰ò„‹'] :ŒÂ¯~#.¢ái’6V≚âé·–1ýžæC°Œ] £(¤Ùíö7 "áÐùëçξgåƒ?/€Ä¯ííÊž ô" GLz÷£~/YC¼DÏ‚P >‹jƒ€AÍNV(Å,Ûš Ï ËÇ.žrá}L=ôÊg¸ž 4ÍÝ w^3%Àˆä ¿÷C=é™7ÁÞè Q«7£*"Kp+ÕGBR0 QRB¤ëåvæ÷HO{ö¸Gg7û¦ãþ Œ,f°L„ªÈÃòÄðsžÎJ͸³:¨,Ž7Už(+呬ý‘D‘ D1Kc60¶}Áäï¥dî‘­¹Xp¹,ç ð}ÅóK`¸Sþm¯°iTn_õ÷B*Ã*fñý×m´Èñu.« h'æ º4s"F/TŠí½P›Ý^'3=äîWû`Zv c*m#Xâ?ã?æ9ïE /äGµ–¡À–­+f‹pãýY8ðÎÑM·"¥žÈ»%O#_¾ wð!±È¤ny·!5·.ë}/ŠO±&{×T”Ž[tï¹ Á [Û±0BOîô¤žµ¥Ð²-»@ï 'fÆþcŸÿ…•P9¤;j"/Õ£¦ÇªªWýr†Vüfôh…ºü̬±>TNð膌{±D ^uduû¹NÍýÇ ¿5ã§ÊÆø(sûá8†Ò¥,ˆ d+’5¥¼±¢’†xÈÙå~òÅjž¬*]µë_³E‚)VU¥~©(šæŠ)&ËY¬®z¼Y©\M6°Ïƒcª›æŒŒÅ¼¿ŒÔ.(iZùµà×Í„7¢d¿KI;ÀBçb­°Nq%_ì¶Á8·O &g@•ä‡!¶¤l=Œ­5ŽgÅlåsgæ°yÓ‡jÖ%×hfÿÑ>6SKÜësLDñ±h$ÜÚàT\ U²mŠaŒqÞ”ù0üáÃ8>rB·ñKñ½¸êù³ ½Þ™ãˆäM@¨3‰Ew3ËÎ(Àˆ®(ž|Þ÷«º\·xŠ„J²«:€­ òüðË2$›ë¬z`Wd;KaÿðGü3žÙx2ÖÒé®@Åúfºî‰güŒ/Ì™3[±*äÍ~4æ§ý¸G¿|0;Ÿb¿-ÿ‚Åõ iQU½ŒF’iP)P«Á„'ÈÄ&=A©u'†$Ý’T~ØR ;óGûÈìE…e½½ë¦ßºRŠkË]6;ÕvXuÈÝÉRH× é ª§ ,Õ.„rRV€o¸™ ×#¬y$ €y_¾éƒ—|×­Ž»PP>é\ /2Dûñè<;Ä1 ¢³9y¨Ú„cçxÂ2hÀ˜‡3D;IfýÉ7 ž§± EsÄüNRG-‹ù˜í¯HÍ­Êé:Æèbê'úzÇt°r# ÝÀ£O?í¤¸#­K=È*Xä  ¨Ø÷bp„Ž_÷t¯ÕÞ´yÝÏO9VZùíZaŠz” [ê °*ˆ,‡uÔä™ÇžüÄœŸãÞ®·­lR­´À@Ñ|*2ƒó.˜YÊ cx"żÇ=,P(IÛüÒšxÌ‘}þ“ßûm¨’ñ‰Êá€jL–Qþ ÿTpšóËÐ{¸¾F sÍê"l N…½ßƒš±î€0ˆÍ•ù¨Â±/ÀVt>ŸGîa•–ÁÐùÖþºàY2êûI‰B!˜Æú9ž ü÷G£I( çzÒz fÈÇâõÏÈg=qD¼U@Xq1QÑ6üç´ eœxWc2ë ŠØÓöDÖ ‘AÄJtWK~,¤mžfæL@(ÛɪF63×?‡é’Y”eÛå`~Xìžô}Ô֥џÑÏæX°„LÚwfeõmYþóm'!­üz3ùU†£ˆ–JA^ñ/ÿèÓœ9}g—iiC~(F‹%h!s±s<ĸp”x/Íú{}”¶øb’êL' ì$æfšÎNÌsY ­ ±‹Õš°Û™ïèbç}{Oc™öð{_Û/–´ô÷A€ ¢Å¬ ;xVX†MFšÛÈzµ7÷]O×ù;äUØœ%?\´Ò/ÜVî’$\Éxl‡{”õ'¹€»®£Ì0+`íK”Xl–XÆ å“#&³þ`ƈw Ìžm6¾×o¹0§_[ÁRÊònÂ÷ÿÓ=ÛôQ›`AB´àèû?¸ îÊ<¤¢)ãÑ]föØ$a3ßîÛ¼êç;N:Q5Ä¢ ~•ž+Vp!HC’˜n{qÈIÓ‹ļÝnYX’Öøšã;¤²äã´ùl1·Y¸ñJ¿ÕÓƒ]-eS“?RÓúqß—0\Ç‹6$´ðj¬Ôó¢M؈x¢i˜¾£ÁÄÚe[Æð˜š¢@H>bX 6\̧ _Z°¾¡r¹8!Ü+‚ÆhĶ“¿£-ÛíéÁÇ"º[$ù-Éè|·vŒÀGd1k6C )«G1ŽRÀ¼vú ÏGIê{¨*) 8ÖÀà &'Û&¦¨~Ã(ñl¸ƒÂªKRSRû°­æ„â}óXGBÌöŠZ²ÒŸž/´”Óš7Šƒõ| Ä,Zûü“`X¨(øDè4Ñnֻݼ+ rÚ=jBø’´9{»Mß3ã˜ð]Ó¼V§¿¼ô—_r¢jˆYßëE²؈ A’­K~óªåÌ¿/­‘~‚=+aYÇLˆ-¡8œ#Á2F‚d¦Œì ¡éLsÖ…Ù².æ=DR‚ŠU²6ˆ\TÏ9L!)PmHº4 ±Vì‡Ì…ÒùIM¶Ùìö”<&­°¢ÃýD‰~~‹u6§¤ÔIL2fiú„ÇnC\|ÙÆç¦T‰qó™é÷tè„ @H\ë=üíagΡk¨zÇâ–ƒ½ý±®œ,‚á·§{ê÷æá?fqàéÿL‡if(¿K_(Æ<•‹Ç¸i™¸<̓ìU»m]ì¶úç±H© È"raNzV-åå‰/ˆE$“a|˪½'óýs`X˜Tëc€Ñ0rÄx‘~ÔÔOo¸r{–64Ó†¾ÞWä‘g9+˜Úq+*þï-«òAê'¢D!Êz ;íÓ ¶Œno® :ÔâºÆXN1¸ ÿôqààs¬aqûŠ’Lw¹-4%Ó¤f÷ôTÅewÈÀæ¨1kŒ Òßð†Íápa¥_¯Ë'çé¼3$B'Ÿ˜‚)@Åͺºé—öþ‚?Hù+MR‡ëä v54„-Üðü-ká"FQ<³xFí±Ð¡ÁÞcµØ*cSp1Ö\ö½mÖl0)ÔèlgØJĬ0(”Ñc$?Ý[ ¿­ª¹KÉX Ü<â‡1Òâ^ŸÜz=žÏzîÌ8 áb£.%ڬ7O C͵Ë到ÐÉgfof¤ç'‘¯˜ZFy8äªØÍ•ùvÊ@ŒðxÐ]ÿ=·ë˜Wû À¥Ÿ,ÿfuIÊq¡½í…ƒ,®´{77ø!W”Ô~˜}…\ ÐHµÛ”´¸O*Yöã9ÑÙÏ _mÉ@æeòÑp ø–”Ù÷ž¥õZ²6ŽÃB¨ðÖMHÆqCKa`È`û$h몇+)gÅ*×8|kÞqúV½ân\÷qžÜÐÐ?Ë«P™j ”2‹.XIKcó^vägômë‘¡J ÊOŒp²õ3]¶`‡Û…`ò…Ó2¦Y\¾0uz!ÄÍ*áXXj‹¥X¸ñ=û9»PÊ®}Ò)T8ÖRñC7¬„Y¶)ŽQ9Ä£Ò ?ÜŒ&,ΙÎ\°¼ïì!‘°hbT-âý"n ã[6VÄS(!á.a±RhVÅá¨8âÇ}‹Ná¢Y3ŠåX¨ÚùXÎeæÛúÀ©­÷#ßqõ‡!À ªž¯8yÛR‹Ù Á´—•ž–ræ>Ýó¦±ÈÂñí(¸•”2»÷~ªÁ’&5Á9¢PnÓôðK+ç¹,R lÕ•%/ÝóOÛ2Ø[˜ë‡\˜yâ¾þ.ê*8çÇ-5ÐŽãÔCRV…¾%С«ŒŒæÁ,PïTÕâlÜ0»iõ§/wêÁ‹&Œ½`Â9-œð÷Ó=ídEñÃÝ\q)ÓaµP„” ‡cÄIÀ¼ •ˆJÐm·;ú3– ô}q¾‰qÔåõôˆÇ 0-±Žô”íÿ5Å’V× ! ¢²lËBžØlA:$%,Yöí… _Dªu/…N °ÐvÝBèW%ží½LS¬¤ÒÙi8ØYjª³ïuwfšu{©Ï~W1Ue›>!è¸ (N0|ðFLP}Ž"/yÏ4À¹¡šwzµï«ïëÊJ@Mƒó7…H ³jn´wJqØDÏYϧ%o–ODÞÈ€L‰{ìþ=.¸)[ÏO'_Å}'ýi™ª”‚^§¸åùá$_L½TWXCà»ã¦}öpÄ•sÈXä¤MÂ˜Ì ‘ºxÝÒ=r´jý ˦]ù,zÖ`Õr4«‹2@hô]]Ž?ï’9 ö‚á?Vøb^˜—Á„5¥â#x Œ¯Hûd¥ª©‘òU¾•ߎ^Tø·ã×={LJ¨J3† º´-…±Û°a˲þyK`Ӽѹ¶PÒ ÿŒ°“ŽÆÀlª€çØ·"Àl1DEIÐ}B+Ã3¸)fºv_QCœÁ¦Éö“BnkŒÌª Î/ð—¿•~@Èo6Èâv—ñ;vV€ÕÒ¤x´áS;¬`BÅ–õ¹ÅãU_8œˆ)ž‚´.ÃF0±‰Æ¶|ØÙn ÇNHUïOkr(Éj¹˜MœbøÅ|hš-Ï|§Xb`ÍZ=.W—>ÂzQ«ìKòlã:P³å9—ÖAö—ÈÔÈ_χӟÀÊ,;³%QX*Hï¾ÿQG²:¹/Ms½Ð>*hHº¥œ‚7°ÎÔÓãu{N"¸žòîerfÏ1«jü ¸©"Ù,ú\ô>VÏózT‹¯dùü¿_ϼ 'LàEäMûöÄ´ö¹ñÙAÇ8qq’5`neM,6à™@Œ&ndȰOu8-=`Õ/Q±túü»N¶î™[ßaf#á aÉ“ÀVœCRžB¨«Ÿ¼õpͦ›»¦À)‰Éžb=ñ²P €/€’€:³…M{¸­°ü)|!ÐÅ4ü®°¿åu„9a‚8Y<òÞ™‡Ædç>µ°úÊ 8!&Ч“âÑæO™w ¿™{!tZ€e“.uñ/ŠÜë„òú GÏᇱÅu:û©#ƒ²úu;Ep¸EÀÆ l&š xËgKø#Œg¾C€C¾DRSVGêi|6QwÞËhù’©W~"…|ó2\.`¨•7rÙêå€ ßR‘ןt0ßó˜×žäc«`»Ls½2RºUûš¥„ÕqÌ~×MêÁ…~›¼ d¨xØ¿_iOÍ{f]]qà ˜€›uá)Jà)ñF©jÍüë¥{ 2°’Ìgè˜éôìÛÿ“êDzÖâ_,.ù0Äé*5áô/ßëUs-1ÿÆEW-™rªŸL{\a·¬‡¤5@R¾hÔ1†ÅÓ.{TTýØ-Õc¡YdÎn4 ze—xâ sK½Ú¤kS}†Ã‚”l+o-‰nÕc¦‘º]—"UÒæþKÈã’ð…!Øaˆe¹ÑñJ¬¹¾aóò9Lh®SñÑ^øÙf€÷B™í+E ùÅ’_M„¿Hu‚Ìx9œ Km&`%ëIû^7åÏ@¶¨i±h™$!Úa‚&1ã9õi)Åä1ˆ'¤†)`Km3Œ„Ùœ§õ¸`l¾@Ý[i í¯Þô@Š|)°GÉ$y²$æoþa¯ç+)5Íp*lu:ä¶C‰µÜBm5ÐdȘ‡¯Z øVévühh5 í¹ a{úü~)nOÍÌî6ð/ühkÌp4lAÈ ºýÙÃ<]ú~¹9êR#‘8D ¥·ŸÝÂ~ÁQ}¼K†WŠÖ—>¸ñùqß ½aýÁ$ú÷ Ǥõ¼ Bóv]ï4Ùªl剙>jz†!øýÝ'3EM‰T”¬ýöÝÃW=ví ¡î„qÚZ×pÛ†ÂÒ«À¾¤`õÚ±îD3=)éÓ@¯*{Ž€O:ð–Ç»¢ú`C'bQHEÛôq…/mœ2]ûáU‹Ç%5.ÙÿZˆÐÉk‹=ÈÁx¼)ÉHQé+“ËÄæ³ÉA¶¥ó,TΔ5Ò ³œIÌÕå`p¥Ñ8'_DMÉIÏßÇÎÛNXÆu¦`’pVY[Mô‹D,¶‡ ‡¼i ¸ZßÁ"È·z´9jÏù6LÈã´]¤ÕÒ©W¾›h,ÿJºÜ¡é@UÏß(ƒå° ³,…²?ªi!G†-#¯ûí¬Õüw_L|0Aää~·¿vÓÀ±oä"o6´C@ËTÏï¹ß> Õ‘å‡`Fålî“YWS“€÷Ĭfžx㣃 zìó~•”¦–ùC­eê|+½ý$ßè£i^U®^ûý‰ÿËïMÌÁ$÷âÈ´¼¾_nŠº­ËZäKt‘·Cò;c}3 dÚ¸qÁ‚»N9¼ñ“§336<˜÷ΰ/1†«¦ß1f-öR'ìPù³Ÿó’“Ó|2¡˜þlÔti_·KŽ”ÜIxT–Õ†¼ÂÄ#<—=pñ¤xõêzd¤ª~(ðŠù‹xæb–g>è%es“_óÙròNyÒ»Hf㩘iu¨_¹à‰¥p3åý›l¹ýžXQ•@Å% nÕáy_†â›A7üg`BµYþ%ìsªpË•„ñ=›#5·/ê+B0tÔ}³îtô›¹1êRËx:0«8{„ý‹\ü°hA&»¥; YS銫Ö?çç&ßJV°muþí] úìóye2=Mc@Ë‚ëqÀIØÄ\q›ì›é²Ä+—?¹hÒyç"Z`hÜ8ô”»þ dÚÀpEÅEýáPR-‰õ]ÿõÌ¡ ?oþkÿýG~ߎÙÓé ƒ\Ù =Y3ÌÃx j †{g51Y ®¼ýO«ÞÜÆpa Œh³lqƒ/*E„í|ÛÝYž{â]‡'Ξ¨ÄÎò4O¿Š§^þƒ.ÎpÃ| iz ×::Uª…癸ì¸ùè‹·][ÊÎJÝCïÈ ÔK³¤ÅÖ¸„½/`ùœNh‹†y¯ÿéÀ â¸&90KeC£_‹ºr5ñ+XÛ¡ð“Ç«àÚj´ÀbæÞó÷‹,µë–ôÉL·Ât2õÛôŒÌòxÅGü#i˜ ûL«k|ZÀžsÈIÿùê{éô»ºÓ*D½‰5˜ìà*Ò -d—„1;ë Ó>}^ÉîýHq}®©`‚bDͲØ~ô†ã„id)-¿çYQG¦âi¦ÜNŽjÓéHÛñ|1KÎê=ei]\ª …ã^Xû0yMì_ÌZYMvOQS-ž°ê‘«f°ÎüZ¢î#ÏÍ:nØI_ÖÊ™9Kqhò¬8àa*ª*õÆÑ\¤té# §\Ä“E˜F@¿ÓKÏï3MÇ®²Ú'H.”H˜X%Ãì’ +ÁíwÖUn9[ZýêÆ„l½˜ÎxÉãÄŽD0ÖB k§¹ ì5aq_X¦î ~Ѓæ‚ךL¤:5ÚX²ä¾ g³i&ïw4s—>íô ­2O¿`ϼùMº0¢ñ{N-é„Ã`߉æq΀ÿˆ£${¤»,‘ÍK¦,~ð²B!À‰ö—ÁôsÚe³ë,Ùû.®mŠe[y¨÷‘ŽYÁE&Äá{§9ähé²û–ÍŠÓ¢”Ë7RÙ±ÁöR0åÅÂ5¥Ÿ«áz0»-”¶&”2a†^ñÌFŠf ȶ˜19æK!bB9#ÿqít~´µŠ ‡HÊ~{êµ~ýÒ>™ipï!ƒ0ƒ5³yêý(ògn< <–Ö4h›cÞlµ`¿™'<:û‹#ƽr Ëá¢æÉ=óÞXèÒQ÷¼vì‰ù™³Çß”ËYh´NMÄ!¤[ó„vo¬1ä(ÖÌ;öd9f0íz`Ö]š3«Ísq²¾ì´è;Gv¯›ÊiÌ-¯ÑÜ8D†„žÎÖ“Pµ Ô °"«bMöo<°eÉ“ §^vË€´7E…ý%I§¦ûð—ß7Ûr/ªnˆe+I+óѱ3øû‚ÐoϛٲhÒÒ/§ŠÈÄ‚¿ß?ï õ“ÓA7>Ú :‚½ Ô+a'°P-(^ñ²ªUó6°Þ ²=åªÆR@)”ŒJj…@`¦|Uñ‡›D‚vê'ª;ûŠfÙìТ¹Äì`»Ý¢Ú£¾d´¶ô5æ9sfû%DvãÇàOÄ.ý¹Áãgý·ÞZpv‰îÀ›àƇ…—ŒÀ·ýàT¸Â mòÓÄK–‘)h*§v® &(ç¿åàÉŸ.¨Ur‡T77ÇíJ[ÛEưàò«2%øŒ‘4 ƒ :4Ça‰W¯9ÿûÂsß “ÜÀFZšM e”¬ƒÆ¿û®š·ÏÑ¥Pü hšpŠ)5R·–¥ß|£u\+rï ¯%Ìi[,ðƒ¤E¾Ij¡ù0xãdÖ"§©6÷aI«óhøZ<¤QrK›|ÍIØPJxÐ. uñT—[ÉŠÕ®ùåŽã† ¸€‰M´Ôuë³î}.(ìÛmØ1ËÖ‡š¦ Xø€D1Uœ ÇaÁPX«ùñdG ›ç"Ï*Ý!…7-zpÉÔKï0ʲ€?&CU|6jêçŸ5»{œ4¿¢& ûê62º ­˜† g©½Ó!X²dü²‡.¿05™€·»X¡(³½Ãîyûšˆ·ç“K«šâ^ØxÇ `<ý×ÕÚ¸tḓCÒÐ;Ÿ;Rõöú~q=͇Ç%È€iÝR½jF¢ò£%…güµ=ýk®«a7?ÔMÊ>°xY³êµÄ…š=ÈK‘<ÁfÅ«g/pÚñíÉŸí±ð§À°Øú"i‚¨kÄ_õ‚'À.¢«êð( \š_õ!GöÕŒû£˜‚,{çAìæXæPÿ 6½ïÁ±Ÿ q±(¸Æôuf¬3ÎÌ/‰ýˆ?‡õÎìfxºWSòžévÑÄ>Vœôæ'¼XÔùOµ 'œ52²qÞÃÝš”ãñ(”¢3'§Y¾YÉ *¶dܲØÖ’†¸TfÉ>²ÙÓc\,µ×ñ´ž³cÞ^³é}Þiòô¸µ,™qÈ¢†D²¸Ö§Á¼†0í²BVZǬvÒ†í·l‡±¬_¹ã…#4‹cTYs@rB{šíA)2…6åxøg¦9´ð¿—jöœc×Õ5K–Ô(:˜þ%Õˆ/¬Þü%Ó˜óŒ÷Û ècΕþçÞ’³8/-k‚mØ›ãX±ÿ¨Ú•étYl±†µÅS/&ÿ ›ßH±æ¶›ß^ˆì” zína¾Çƒ¾S±R!z)&%”’ïáXT ª©i®‚ý.gæQíòû㢠òcÃK÷,³hþ9Ùä\ Ðݘ5C»Ø6þqÆŠ?3ÎHÃÉ•£å5 ± «Ë°‘÷ø”HýöÛ\¾\o­A”+d£”ͯÿdÁ¸¿ mšûp®Ô˜è™™¦ª*¤b‚¿¥—'êƒ2@»ArÁ8 —W8QL&q…÷—¤ ŒDå;,c±˜Õå•@O¥KcØ5Õ|Ï íôôŠÀjüxV@:æ¾îO¤t¿bYe]¿¡°Ì7ب1û†¢<üðIeœ ¬‹¬õÏ.5R][±èóc×<ñ¯çÈ0‚Ùз±Xuуã¦~6+êîz2<ÔÄR¬DvÈ”G@çïúi`ÙÒÉÀ¬îFxÅÍp·aVltCõ6;¼Y77Ë)Àêââ$ÃìTV-ÀþÀH±§N(iŒá+Š’ÊdŒÇ9ÔdxîºÇ®fšÄýv~Ìõ‘µÿðsCJDÂÀ^£ ¬.Ý‹ÿ¡æ×ñyTÇÜçs;¹í¨?Àj9J-~ðŸ¯Û#õ›ÒN…¼NP. zÕ)Å.¡Éîkúüó֜Όe™d[´®ô 68´±êÄ Ô—Ž8‰ÄÚ*ÒáÁŒ#† —PÖEXÔðÐrÙˆ)L&‹§P_s¿ZXdd–å×/›vÙ-¾ýïÁjå²™ùRS'‰ªÖ/É Ãza3àëD¬Ž@’0 /õ?”/âo8óʺF€ g¸p,¨Y¾dÚ•ÿc‹v¸S3LX¡þ£îûðádfŸ;V6'€²ðÇH£,±ûCÑß3¢7ZÊý‚¼Qïå£öÍòª¶ú5s¼ùàáeoÞÿ5Ûl´Ýäoz¬>¹ºœú}Y½–nI!YQò¡;mGöÍpÈ‘’%S—O»|ëŽz`aCØ}Á¬Û°;fW P=7TàËuÄ€ŸðÚlª#RÛX±þ›ç÷G‰2°l3ü©–TXåPÁŸ $C Ïfº„½wÝN¦.­"6…#ZPIÍ_YÈŘ»ˆÙàÎrÅd¨õòÇþõ¾¨[œëuÉ4oÂúµÌ2ܘ Ÿ˜ƒ"ŽñLÇ8H¤8q¶°*”«ïXèƒ)«j*ägm´0`Ó ˜ 1ßgÏ/Z<ùܳKæÌ¦”/~:+^ÛÐ'Ý¥æ¥x2¼Æh!¬_Q¤(Û¬‡¨k±ý?¾€ dš Œ_ÅH¦[A`O­A¦Ç °1ñÝ{“½ÆÌ¯h‚„°© ¡:öFp"Çe³t·™ÐAv!;¥I“b™Ž½]öGŠ2°žføs,ÔÚˆŠâÿ½l ×6{¬6òÄâNƒùkÙì Iq‹óÒææÂ4܉®-$®ÖXù84Th}°ò[.­ÕÑ:pâ¤AO*>aÛ)X(åIJJŸdKëúðð»ßº’j*˜lçm€7bÔ+Sû¿ Ýá5 Ÿ¹ýàØÆ_îö4mü¥‹¥9Þ;ݣ槦)v«–‚XãϨ¯Yoýª¿&&ä©Kzº*5”.[òàeϲÞf¢Kdz Y.~a›Z޽ÖCRf¯»U6>ƒ%{´‹ “ © ¼¶.©)–ÔhíjÙ{ ΄/Að3aÓ¢õÍJµfhuþÆUß]¼dê%<€6Yجtu™žiÇMýü›€3oÈÏ%µ1b§H'°Gò½€9&!Ñ”è—åVbeKŸ&Ïï àw7f…\a\P÷~äÄ÷îÚ²m€Þ#ÅVüPÌ–'9TŪ†j¢–HhñÓWUù Z Ëtà D77Å%ÉQÿ'•<º ðKæØ²ÊÛIÙìM(®)ϧRÖ˘!8(I:`Ð…{²ª•3˜Ç)Êж :€Å ´¯ûø•29Úü\Ž;ô Ù.!+ÞšÂ!­YÍÈÎ8Rw3åxÚ6¼3Ü–?rÅŠ¿r-•p‰eéóF-ð m…ÙŠyµ¾Ð6xyš–XÝ$9=žNÝÅJ…ú$Þþxƒ·ElŒ€Kì¤[–m(~äêI ÿvDÅ÷ï–Ü<{i$¥×Í «C ý’7I²S`P~-¡å{œª'T!ÍwáéöÔì…IÕ+ RØ cyý2œª­fåì…/LºfƆghb/…š÷ ‰CÏÍ5mÆwu¶.‡Ï«ôi*䬌þ•Â=4òâ}3=ŠV¶ì¹%S/»†ñT–=UebÀ¿èõIwþ˜%>ê&aüOîá±Ë˜ÏIJäQ“‘¬ÜÞ—o ØÒh¼ò{ÂG àYMuÆê¡òÕ°|ù7”ÊM« Ã'½{E@I۷ƈ;À#3±KðSÝÿ7Ö?_¸\è |×¶o·'ßþÞíÅìÞ g®˜ f|yñW:#upi/2­‰×ÇéùzÈEeç ƒ¯žÔE¸$o+¾{«³ë¹2HÜP°fÓ½Ö˜¤èÒà­À€âŸXTh[¢±ØC<Ÿ“¹&M¬nHhõqø„™@"°×O ÛæÐzI)0.IˆèDŧÏÌ_úð…‹ï=s¤+Þ¼Ú{Þô,Í·„ZFí~u%ÀÙ–ÌuÚ,Y‘²ò…Ÿ<ZÉë÷Ìã7Tó!ÉÇ{ïð;_8ô¸ûfÝvüC_übÍÛï“òdæ€*|ššÔ µM[Ls”¬b”ÊŒ×Ej–ÿx²VÃ\ôáa€÷þinGN¼Ö\÷ó­‹&s¼´áû5ìWÑf©d±MëzÙ}}9ûÒ¹u–œ‹*ˆYé$•Yöq0iö¬´ª5,~ðb¡ö¤«9í cÑÓ¡_ôqá7I…ð¡c_¾À’Òý±Ë!‹±ëŸ›iÍ’êË3ã•‹rÝá‡Ó•šÕ»±Ù‘YŠ.LªQNŽv¼ Ra¹"â{kÝKã~.$¯ogÀårƒèyÆÅiš’rGI3´(d $üv@W°bÙT¶é”ݦK $p›øNaò$ÜñÖC¡Ô>7o€ìw¹’@BGûd§«ÙñŠ'¿¿ë¯×™é;aà N²œr÷Ì/üžÞǯ¯÷i)ªEœˆµÔmúõ`OÛÄë_P.BŸ 0-CsÁÐñm| è®3ÿÍ·íè yÈM/¦.~ô’˜tàQ®']º¯5«Ëx¿5ëØE5þ$TwZ NKýÚܰf8 ‘ÒlªÔË©5Û•äFªØln„+u꫚¤¤‚¨ÉÓ`1S³§J 0ýHÈVÄ©4ïl懅Ôu¢>i«7T.]5ïÌÊ×ÇÆ×ç~¹,áÍØ´ié§k?{rLdÉ÷kÄgÊú¢•aJá†5äÚG‡eö=à½j)£ë²jèRÝÆX¤ü†7ÍIÛ/;ÕªÖ®ùrÉ”sNA4l·S7pæn=Ê)HTý„ò±¯^nMëòäú ÓêÀ«žz’ÚWŠßžòŸC¯˜öâ–hê J?,v‘Mž¸ÛªZ ð-ÈcÀœ`Zå~î¨ÜôÃ!^š8O˜ÆÙ À2Ç~ø¤Ym]Æ¡/tÛöÆÆ_‰±Ð;õ7¿Q<é¬ó‰q‹MLtìÿóë5ðÇ×§ý5 ŒÁuü_ z~U1l¥E¢p#*„ÞÄâ†K{‹|PŽ¦Ãæßù¢N+ýn,°.£o’=èØù%1·€K'’·z‡l5LÛ)ã}› ×"w`È,'âÈæàü‹Õ_þ¿oî8å,¼ ˜6£¶êpæÀO¥Á·½òL^AÁ Åá‹Ä’M·T\G`ÅCC£ ¶·|©G’ÁBÁS8‹Òá´"{¤û)bMDÒ /ÕÂ׉B±*)SÆŠ\f«‚CЂšÅ¦:7mÞðå‹§û¾_èÈõý׌½©)ø«·Ü½öÙÛ…Œ± \’˜–Âñã%bY‡Þõò鎌îïWÄ<ò( ë ¢•âG´:–Ú~ØàÔúµsO}<òˆýÖâoÛüöÝ'å¡WÎh1;3bò¬)–Ì^wV7E¥dSy}2Xÿ~SÉâçËf>úÓ‘ß¿;èèzï¢òhtHj .'{{Ù7©%AUKVH¬¬½qÍ Ë¸à²ßšßÄ®I‚ÉIOG÷C–AªÝ£iÈÅ8³@¯mSä^ŽH¢yó¼!¥»Ùý}оžÚQªí̼%í|ñæn1à¶Wï;a±,C]G¨•@¦¨&{RûÙwžr P~²¥Ù}¦v¢&™m9à–ç&ÈCî)®iÖÜ`4ëˆj£òæSëUÄ·>n LȨÆÜNKI ÍÏP`ºw]åÚ笜1f¡àWß7ÕRD.z%¥þ§exÖ¹Ó¬¹}.*X¥R_SÆ ¬P‰V_ÙÛ©MKEÌ÷€MÉ0@Ð0Æ_m@IDATìº6 ¿£•Sò§ NÅ~ˆãŸÈÓ hºÝªv¡=¯šµE‹'ÿã|¼.7U .y¨[ù‹·4#·?ÃJ‚Ž ™ “ÑÃ'ü÷:%¥àñu~U*ƒîe:0W’S,‡uäŽÐ ¬bß Õáß¼pá½?Q¾¶XžGø€g‘Òþc^‘•ñ²+«ËÐò’’ÕÁú’·Þ¼þ…†i ßóôÁ®œ~ó–7@ Bƒ¡c¤è’ⵤk•kqªV» *ojÖ²ÀóÌW|uËîýë@|Vù[Ø ‰-5åÓ—ª,¹­©®×Ü0FH6çI[S]ÍëŸ^~ÿ¹×˜éY§Îþ”<,³óÌÃâ©>nUW¤9œàeéII“CFEÝXߘ+é' cwààX[çšyt–«)—ÁÄ ßæ=a²· —‰hàS´…·"žïŒ?ñÆ•´r ô¹² ¼>V!e÷Íí?ø‡£îyó ¢úV–-ý@þÖšjÝwÞÅ›|ï¯ÙÁ-›å¦Ùv§&j¨i&Ìû°ˆ–r·º7ß±ljþ{ /”’‚¦*¯|¦”>«GÒŒùΤÎ_§^ð†¬v±†¥È†¹“¬ŽAýÊYO]O2)X• €EÔ]6É6>›xÄä÷T²ú=¾ ­2Š `Å1Êãä‡Ãˆ08«Í_ºÀŠÆ÷v#°]ÒôÁȺôï·¼rÃaû"ØÜ\ñŧ|ÒC7’ãV!ðfŠ«Ïþ’A´¥{·-“ D‡8Ö&9Ùé–«ÎOT­øåjO´&–í°©a(déÓʨ4¶ ¼øZ¼ã{ýüÇ XW?¶&èpÈÙýfŒºÿ£×gÞÕƒLX½~!Ð"I…Tõö-špƱ’…ä'jûg¸•T‡Ýi/– D!Æb’D5ÄU`NˆàÒú×* (Ú†Æ0¯òÌp»,ýÓíŠÛ·nqÕÂ/-~ìšq¢1èó4Q€6ãÔÓˆc‘ÂŽ»þœš>òþO¾Ð¼=oû¡¬9á‹Æ^ðŰ(™LN€Fœ:öNu[½þ-e‹?}ƒŽ,jÙE†¼žr—õa]è‘®¼?•¹8רh$\øÓ·G.¼ÿüÓ¤YS(D ¨òcŸÚI¾vÏÇGìyÇPå†nð¼w–WŠÕoºÇ›Û½^ƒ VS —h«Å·þÃÕÓ¯yŒy£Ý;ã³™B¢’’’;­2 š$èq`¶æFÃI{~ lŒ†j©úè¹b>`}1ïÎØ©ö O ì9ǽ?·Æ’;´ ÖxLËé V $m`^:œYn™òÃ=gÞEÇâä°¶\_0£´}¯yäJ{¯ÃžYÙ> ¸Pœ\&X@‹w8pâÝߊ ?OTAÙ ÎËP¼ÑªúPõ–K~~ð2:÷܆!¿iÔeà˜çnSÓò.Œ{rÓ›¢²D³¼Aª€x:F¸ã ˆ>§´<Ì›n‘LtLKuº`¤Isyi¸vóÔÕOÜð8’ »êm¬O0ßV¨#òÂã…o¿ä‚põÇäôø¾Ï–[°¨²ü /NÁ 2_“üñ{ížâQÓµê¦EïL9TZöÓ* •þ^³Æf•Ì~ëuý³c1«Ö=~Åóæ;^¹ž=˜áÙl£À‡Üú̩Μ}?ž_ i7¨ÉÆpbÙ7;ÍêlZ?{Ùýÿ8þЉÍ*Ó2ÿÓ½«¥®léÄ3!«úß"Í95üÞ÷n8»>´¢°tÆú< §¹ÝJ¾T¿qùÄÓ@žnXøët«bÎS4 ©Û4>¢ØÅúÁhà?ùvKRYYÛ$ÅÔ”;†ÞôøAVœP*e/%æNI“«ž3#^µæ¨—XBI %³Ð þ¡M¢yzÅ›–ñšï‰WéÿZÞᆓ;7<Î%•ù¥µ±Ío†=ßY#&½ûÓC!@V<ã4 “Wð»àXaù#—ß´xü_†h% '¦¶w·E¤¾PçÉñ¦(6–†AÕ!A¶DFvÛ?ºlBñ?”TV«MΦûvðLºÛcRj`sqtËÜqK&œ~ €±†Dßë?µX\9}ßA·¾âFjÿkX-0BV°ÅuEvß!E%‰ô‚Eu±Tþ赸-€ê×’ñ|·SM×j–Í9}w+Ôa„øu;]½.g>P’_ˆ•À¢¾<ÉDý¶±ûyÿîíÊèñÂæf  Ãwa½3½V§Ó:«¿ ¼qrn4¡Ž ‡<¥YjزìRdYO k’âÀ­~Ȉç˜î{uaOú›°®>Ø*Æ€´Ü Z e»PncÙD<DžX±i¿±2ÉŸ#p‘CØ(1àηgúÝ=ÿ¾¹Î^’Oõ’i)jWµá‡Æž<\o•`ˆrt®Ðº»Y}ë›pj¿£Ás€ÏÉÊ-OGbÚV¹Í0â¶ÍSÛDú½ñ›À>}#ðÂîË€¼TɬXØP¾áêÅÿ¹NÈL¥ÞG\ÊncgÌÖíÌóö<à4Õ™vüö—ìW\q;€0ÜV!eV°&%ÉáÆ¨E ¯‡õÌo6¿]6sÚwx Ô™ÿ»Jõ³é7Dz^ûÄ4Oê“‹g}}¢´ú8OĘ$+‚ LÙ1rÒûO'S»_´º6$UB=KxÈi3²¼¢%ó8tä–nö°Ô¸âë“6½1å» ³bÝ&LRææ‰Ý¡ÞÿØdíü¿gb:l#7Í"üô1éã…ÕÉì~ë@²ó]A lP%ªª7~öÌÑÍó?_=øÖ®²ä z:Ü\+EKŸ»þù¿µu~ünë€ ¯rrøäO>¬”sN[WÛ AŽ«…ÑAej¨é‘²9˧œ5Òøž#צ÷¶Îõ{î”XÆ®tGa1Ðk„ÒÅŸü»ËÁgŸâ±¹]á˜.æ@Þ 6usC“–^}ä‘Þ¹í‡ñŸ:ô™@ÅõE²+eî±o¸»¢K§üãoƒïzgNÿÌ×Ô5Âû‹n´°¥l´ZçÙ1hk éÈ(ñ¾%uË4$îEY©0^a ùSi}|ÿÌœƒ²ºÚ玘øÎ#sîþû= üœð£&aa ÝÃÄLfÕ ¸¢%ïýç[ÄðÏb|HßžCNÙWñfö·[Ý]-VðÚ-€^º¶'¼¢~xÕF¾ 5«¾[Øðí¬µø.Œ?ôE¾"™ÞÐÛ2súÙ‘T`½zõýÑïó½N`%¨,Cjm®Š ;ø–'{r»?°åöZPÞNL¦pà,B‡Ž-í%ð$?Ðk³Zº:c’ã’K¬ŒE/¥Q]ºˆ“IåãcÁÿ3€zšbsFÞc¦-n´pßXYžüч JN¿u0ÑÃñÊõ¸ýÕJ‹çœA`…äª'£ËÕœ@ÔnXpIÉ«w½eœà˜7sß6°Ÿ¬bÃïyóŸ!kæië*} òÅÈhG1B ž›Ô­!é+]¦ëJŽåQ¤Ëˆm›ãóÿ`è5Öúü}ŽšT=pÊšºX:Râž@Ë oÑJM½÷ ›ø¨øÊ6ç¶5䂨ë—Lþû_¼ûÝïû¤w-X_߃ËOá]$Êø°8!Ò ¿ÀØ/+àé½§YUuŸì¾cF=ðÙ©‘úÒÛ1ág!+lŒ(Ñ.ª\€D£G÷àèÑè~9Y2wÍjü™E·ç*HM]´"A‰êÂäxÊNÅ\z÷à¬ýF|wg:×/øéEæUZRpÑ =8'ÈØq göØ’ˆCZ_ÛsC'¢wBþK/[ïb¤4#,ŽÂ|gpó¢›×?{ËK»ëؾEŒâüÇRº»•A[гbùƒnžqt2¥ÀÞ\²ª†Ï¦I¶˜ÑQ“f}ä·åZV^`%' RS¬iZu¸fÅ×'Ö¿ÿ¨ÈçÀ;^?Ïât©^ùóVf½wÊ_uºêìØ ËoïwfM[×@ ܦªô_œöÆáøU•}+.}¥pnÛz±¾1°öÿŸ‚>ØíŒ}ïçzkþ°Š¦&Í—ˆ”þAH±{ZªÚM­û击N=ç’ÆwÍNÌIdpÔþûžyëõ¶ü´ÒÆ&ˆ–ÂCK}w0ŒlUKš­o¶zƒG©aÊ$Iò¹NÏ Ü°ä®+ Ö–Lœ;횯ÍD¤9BÕÇŒÃF¦F[F€œFlbÀhömk(žI,x´X¸EÅO&Á{á¢IòN~÷k—ÊÞüÇ#žgÃúy¯¯}è ɤ&ßá°»^=ßî͹7ìÈé½¢ºQjŽiñTØ.&¿²mà[ Î>„ˆ•ä€t›%V:ÿ®Óo˜b+½ü¶uôÞ$ßÏ(LÒ¿ÏÇfß“kŸºþ fsHá¬oa稦 ¿\³ú‘Ëžf¿1^`«8·>jÒ‡ùí',*«'ãUêž•+\V½iñ×g4ü„VÞ¿MÉìѧkQsCÝ6??æ9£Ø´_7–·6[¼>é“Ϫ䜓֒4d®øâ#ñLXœÍMÔn(ž|:íAlDnF­Ùt¾;ÖýÿUiðºžwû!Þ>£~Y¶÷ “„çú÷¬ŽXXRµ/ì“™¢Z›6-_öÜ…“j"ëÀëv1ô.ó/›vt,²Ô¾uw‘ÁûM k ןðö!wïGPZÞ’„:”Q[v ‡èýÓT8Ò˜{Ú†goÿØœcLÒ™ƒ9¦¹Ž®›ÙùûÝüâýZöÀ;ÖBwÌIï'˜Z¢ÁÀŒ#8$ 8Eóúc~™rÙ7&_¤Ã…í¥Ì6cÝÈmï~wV=ÖÂ_„ÿt£s[× Õ·CÜ’¶5Á0ÇÄ\€4´ªôJ÷‚l J¶¸®n|­ä—Ïgnúô¥ÊÖ/ÈÚ"ÙX$Nž…w nÔ“À1 íì@½ ¿{Æþ!Þõ}c÷îu}Üžv]³’æ,® $ú8b–Ƶ?ÿ½âµqïy÷«gÛÜ™—Æ÷‰¾¤[Z^qO M)8šL#2ÚÖDo;  `ÂÛ?ÛkUjVÎZ6õÂ3DªÝ„I˜c2øæ/MéèóኵoÙš–LRÒܶ¤^Y‘ðJi‘ÒEË&Ÿu0WnšÉ>—MXÐç O-Ý––ûÂ^¨vÏpKrõŠ–?tÉ%¨Ÿ†Õ±°VÌßdâÿº•Û>™uvÓ#C”œó6²5ÔoÓkD@Ab¢wVUz^^ùà·Å^·Í±sÅ´c6w® ·¯6@m%ÚÚÜùÞüz[ÞPe0IC›ŒgCö¤¿+PòÃë…C¤âŸê[xí+d¯§jsšÕí€;ÿûi$µÏÀuuM1øèH½´I´uÉËÑC˱Ã+1R’ZGÐ`$@é™â‘ynÔ׫‰ÈgÉhðû¦êò9 ŸCÞáG»Böèkóöû¿ö®0Š2ûS¶×ôF ¡&T 6Ô §^õ΂ êYîì{E "êÙ 6<Å ¢èygù{» ”„J¤—Ín²½Ìüov'Ù„r6³;å+o¾ùÍ{ï{eä¤IœÑùGYg™î×¥Y¶¸¼Ì…;,ú©®\'G?àÍ©Šˆ¶ámÛÔÓ¸ìPÞDBãÎPg‹Q´1}†‰Ed$ì˜t®Ê¯ÖÞ7íXì†`Ÿ93“M2‘“™µìßõ\öïS‘9Èá¬m`kZý±"§ ÄÖ^R1ïJ%¦Ô7/˜.8sŸ® YíUH]=(Íj²ÅÚX¤µê^„o¾†¡Jô½³ì)Àvgœ|ÏûßTÃqºÆÓ¡¸«­hÞ+¢ æ}¦ÜR]>÷/£ Þ4¼(¨ÒbÏg®zE?Ùª7~ÀTˆ†£Š¿®‘&ÒéM¥~X:‡Ãk¶ÜøŸåH6Z×[í@ªúEßœcoXøËõÛŠV?\Et žÂ8Ы›Š“»ÎïúÖsìôf& ׺"Ýdrlff& ‰Â:.RÑ{'EÖJÑ`MØÛ¾>×Jljœ§°†Íz“u/ê 8iBX–ôö ¢W»}Ì #h ñ..º‡Û g´±F¤0¬õ.‘{Ò K ä/U€*Mè%!TR¡‡’²i„aY”n× ­ë?YýsOÅ)Ȱj‡¤+öú+Ki}âï~°80XFÈ=ŠkŒ[£óRMº–²7Ö¤%ÎabŽC4û·ßõEÉé³újåh I©Vcú1úêùO‹¹…—6ÀR³Ù@ˆÃêfè@úÜÞ—Ä5ê¥ôd’~‹¸Ú !N é­8+‚«§›ŒÌªe³dÄõŠ! žÂ)žl(Åa®Š#Ì‹ Û!Ö ¡¡Oþ†ÄP;FPüÓ—(ž?οCü"'nå8}ßY!¿Ëe˜FP?(°yÖ¸îý²Ï;ç†Àºôér½zÆ\öÀ8}θUe¢á˜PnXv†À5¯_¾þþ³Ž9ê¶'ð–Œ'Ûùôá«]~i°ÍÄ[ÃMáH[Í•O^q7ú枺¸L_4µ0: Ã;Ù„öF/Xü© ï .:¹äõ˃–ü'¿¯S²éÄs4¢" B"F†gÂo²eÝce]pú|àP¿)ê|ì7îeGi|ô °¢›ßø¨ÃšÂ6é³Xw}ºÃ³MŒk«:å«{Ï]¦*-{ÙÖþ==émœÁ½·æ>Ž¿¦ª¶„j‘Oo’ë\¢o]·{¯@,Q ”€‹¸.úŽü}2À X¬T„°2ƒ½ý8ŠÊ‰®BÐ8Þ@gá¨Õ+zÀèA}Á$W¿«}¡ê)ég¦Ù$f™ ¤©[ÿDżKg༸&ÌDÔk~úVFœ-†8[\ôðYo.jÕ 8gcK{8ÏiÓ;„0 5m\TÿÅ+|ÊŒkúԳ›ÂÄH±†à o•-~ö&¶ýËÍ{Òzb5ÿwÝUÿzøOÅœÃW|çýc]Ò¾šŠ¨3TûCÙ½§†¶a{Q0®:Ù“®ç`JüÂK—[xû;kXjšÛO‘C§›‰š1“Á(Œ³…ƒÑº•‡|÷ä-eIú¢ƒ@Ð3Ъš¡¢sé¹Öý³ ‚à$ýØßŒñÛóŸ¥ ?bk›ân$Ám%{—£è1ð³Çž]^™|@á¼p!m <€´‰7®Ô‰p2„@(*§¤WöìÙŸÎú§SmôAVÊ!Q„ K°Þïß¾æŠÍ ï\¨œÖçú™.•ÁÁ7¾øw!kÔsä\ï#Lî¨[é«Y÷ÞÀ#-BJÞyKN†«+¬¾ú°hymý‡fmëÖŒ¼ð&[ZÆÐ‰¢!e¬¨3`p“Ïñ¢ r= ñ£UÀ’ZwCÝ÷«Ÿ¹n Æ Ö*J÷òr7»~«\Òèé×Nsâ·|æÌÌoäºT"1Ðý€­dF"Ùb‡¿á»7'¹>|±¬¯9N…ÖûáO|퇆~Î&Ô›šÞœ“MG¾SÙ1V Sè³ðwˆÁ†ö²·ýöhô·mOWföõØzöƒVã¤í"!(¢j’%:lcG^=ÿq]êàKÛyÛ†`u"‚Žê€qôÀ½ºã»89iwÒW…ÛRi‘¼_ݧTò1uŸrN·êU´Ýñ@\WÅ+Š!Ù§Q̶Bvmù¶²ôÍ‹#ß¾½:.ÚÏÆ…ÝðäZ{õ^|‹aº¡L™þ› î°g*il`îñ#×QÿcZZ†`Nt²œ:8Ûír±@ëö-ÑöºW«^-Y6rüA®ì.*f¼yšÄăZ# «žX.˜/„EO^dV‹…!ª48Wœõª< H•óÆÚWÎk^R²‰ú›<’‰tˆqõY],õˆÍð† {+UoÒaEœ“G§‘Eè»ó+ç_÷â÷2îÅHž;½¸¬ÿªê¦F]ùì,9w|IO}EÄDöãÈHÈøYrÓçŸßþÇc•Q&‰^?Ǩ»ÄÓ Î£n¿ö¤ºªÊ¯«^»5i²Æ»•¤£Éþã•ÅécŽ~<æ2¶Ö`íáR8ÉyˆWÝþÎ Ðùå§ŒrO+éÞ‡Þ´HF¨`nl>ffð×·›ªænžíƒ8¬„Jxoªþßç*â.'¹ø±›R¬Ö)þPd- ˜S3²2ÚR&rf‡±¥¹¹©¹¥iqÝúoÞcŸ>·å°›Ÿ;ÌèxQ”‰G„E‡ÎPÐ-þ k …‰Å†„H }ï‹Fým_EÃáMà@&½.Íf5¶§¤ÓéuÌçjzþ‹Y§PBŒHâL"®BÈcî~g™K7àÏëàðK:DÁP Ñ*˜Ð[‰ÍeO¯øüËK’ pãgõ¯¿{:ÃúרvÞ[«r+ oxåÝ€sØŸªZ=ú,ºáQ(Md\NªÎ©Y¸|æ_.Pªês±B©u÷HL8÷ŽºìÁƒÓóF-–ÌÙCe_“ÏU»á¤ Ï\û…Ê9&UÄaBr%q‹pÃðËŸ¸KŸ2èz¿1S äQø B)h'Ý•òI×wûÚ99:¿t;¼Ï¨\!5OÒ$¥Ÿ"ŽÊŒ$ ÙHkeDÚ+©£áµŠGÿ6§lQ:Ô¥è‹þQÓ2»êqC~TœTõôåË•rÂÔ9§ ÏÌ8Óîp ÀÔ7·¹?ßúŒâ ÐtØßïÈ6 žøÛ˜ÎrAH—ZЄ0”vñø(8{™7`ýÙ‡b¹6‹àŒ5¹¼ŸL«zó‰vìtvá¨s/LII¹.‹mj­Ùvüf/¼øYY7ÿ.rÌ]oÝÛapËuîlòÅ8•¨öV6Í¿íëòœ9»É­ªß˜0ìH‹^ ;« _íëšÌö¢[—®vò†Ô{<°S‰{¯ÓXDÌOŒ‹œ—"X|Õ~^2õƸ͟òÔî=kÐB%MªC®fš>uð3Rª}s³'˜Ÿ™jÌ’êÖ}çÉE¨’t;LÀℵ?5i>â䉃Ž8õBzÁ nØ2Á +F䦔@o¥_ÊØ”o}ñgWµõ–xÄ!P¡ÌÇ”LÔ ?Çl˜R˜¢mTÿn)ûü¾æ^XAçôä:h_ß”¸ÎjÔ•O½ng´^ Î 4W–×wHCù` \³î³¦±‡N1§æÍ¿‰ò†ãœqT·²†Ž«÷"­X)=̰€Š5Ïx¯`%§š \6ïvmûzÙÚ?^¨d¼ù×ü•J¸™pB©c®ùçs&³µ8â þããlœ|Çk3Ãö!s¾­ë(Ô4-wU´ «U„½UKÙÝ&{«m RŸ®”ö }{WË®æVïjéGg«sÊñ—sÄéŸ5ÊN§Ëˆ(S ^é AHîǽ?<ÇÆý[o)uæ?"%ézûÜõŽ:ItÔoÌcŽAWlj ³F0 7U±à„‡Îì«^ðå]g^˜˜ØÔ§ý¢V]¡`Ì'ßð‡œag˦´ƒšÃ‡tî;„Q:ºÃŒØaGïÆ³gS‹¸ÄM):£TD^M5˜èof\ õ½öMß>\ûîSŸRÕŠ>¯ìŒÎÉ{ÑÜî/麆Coã{ç±c¼ 5^½Ž«#;•Ò%AŸLˆ³Å3—/Èü @‚3³ÌSþ@šTêB5¦ˆj0ÙŠ0ÐúæÕ7V<~ñƒ˜_z¸(AN¤R„ eô=áŸyygÄÐá—=~#`LN±éÿË9‡>öM3 E¤¨ R”š´ˆ„è’Å`äóD_ijá“ãê–<´b'ܸÒRû³ÿgã@!Ué8túÌß›FóïÍ~&²Ô Á!M(¼aMÄs‡çX°Ê³ý²å%g>£ÚºìÃ!tZO1ó…‘”áç}_Ó‚ë®`…™"³Â@OÊAä–¶Š«¿¾ç‚'ºô\=z¦>l'žk9æÐßþÏÿô˜)uJ¥×dr…¢2ùਗ਼ûW*?w99vy`gµôb_r)Ö â˜3§QÃTp Þzó6½ÑT¾bAë_ù®³Æ.޹s×>ùÒÕŽ%ÿšErÇLnôøX8ªDƒ]z ÃV¼2(€*o’—“l®¡’Œ†×-ÉÒ@›… 4¿ïÞðåŒõ¯>@"­xè-‹fBqÝ#ÞÐ}qe*’Ü– åVQ¸è’ûgê2§¼¶pÑ(‡èºJ^Ajƒ€^‚)ÞÈT=ÞòÕß6=Ó ê|ß'´ÙÏ•ªtÜÏÍþüÍ©a’‡^üÈe†A?µ±òS¾·øÊaÂbZŠò"|õ¾šiËçœýºzݾú<øú^Îg>¹.sHVç ÇXMG€Ò—“yÜ+$ÙŠü5E©ˆîY_ö§Õ]ùžê,ÛÕ¯ÎåwqÒK?Ó‡ëj0ßßZdÄïÁ’u]ú¾ý¯ µ»6éZÒ$gê9ÙÁý,Ô^õ¶¼³ñ™/cw•Ò-R|O™- äA§ÒYÙ¿¯ÿt–yÔUÏÌç2FžÝ »Éˆ`)ƒ¬u ¤èö(t%ý›J ³Â½ã •JšqEiÎÁyƒ‚ùQtNCjÖ¨­?.¿¨òé+ÿ©Îšú²<â¶NbÖÁ¯¯rIÎ0Ì9Tƒ[j mÊ(L3 Rýš9ç]vç~“ ¨“û¡¨4ÝMpMt*¨G]õìƒH­uýú–ލAŽÁO-®”¦·d*)À!©˜lž­Ó¿ºïü×ö h©Ü²!:û?k^n¬aݪHëÖEúŒ‚‹á78šb>ñðDÄH΋եL‹Y(0v´×m(=|ë¢û×wã´ÔúF‘7qÚÌêüpr …"œÈ#8q×Cs Üe"âƒ/7Î.±ö5˦oYúÔkjÿ(IÃ6—WÎ`Rw[4õŒý°MZ1|vÉtˀ’˜5g¸Q×¼Á0ëGdä[—Õ‰I‰Nq ;µÁüÐ?ɼ ‰! 8i¼RSlÌ[½òÝŸ«D|ÀÅÊ)jÉÃo˜ÿ–2äà Ÿ‰wýHW÷ “èC+‚#3:¡qíÂòGÿv Z-:Ò³?´«e4ý³ë}Òk¿òÂu͂ьÂóáL5r’¨®¤hQF½^/”Še˜ö-ÓWÌ%ÐR\!úì-¯¾Mºá¥‹cç×®_¹¨òñ¿_Œþ‘á`Þè^|…¥< øð»‹Dôœ$Pº­|§UÌÝî–ê•¿©øç+§", ÒÁ“T%«6;#g<÷˜˜7ñêõMðù“ ù’(¨¾æqâîJòÙÃKvWÝn©¢R¾ÝÄ4ú+M[®úî‘Ë?ÞÅE÷nÇ÷Ñî¤Tœ…é¡è·-C/¼÷\CÊ€Ó9½e‚¬·Á¶Ê û}I¼{Äi!×<c>å‘cQ4‘2ôLqjÂFŽr0Îߌ¼Ø5Ïo~êŠkqqgˆd°âRó?(÷$þP«ù©%«|D`0¶mø÷úÏ¡ @¤àÛaAFÙßÿ$ÏÇ~<ŒŸÐõ¤›ZxÃKoSF¶¥Å1"z%M)ú¨ ehMprLï¯>óó9ç/îKv;QWlRɲwñ’>äû»NÍ¥QøÀ‡–n<ɇ¯†á—ø½õzØqu5y¹í:SÇæÏ×ß?ýDìõ±£·:ÆŸ}«ÎÉŸ½#?kºX}nô /NYLâ¥w-QÔ Cw¤ ÕC?í…íN$ø cè|ðÆßúúz¯Çóäæ§.¡4W;Ëa§Ë=ø¸ E{æé²15/*ZàHav+Ëb-±€»ñoßÞ{΋vÍ4üŠgžÒe:¿9ÈÃQ:D\šR˜¸€žÀ¥vˆ8M‡Ñ¨¤•‡jŒô5X¢W':Ûèñ…®íº¾ÇÁÝü¤k ¤$ZaÍCàÙÌbšÅÀl¸F¸òº·WÔÖ×Õ^±ý¥[ÞIˆ<=»¾›úü€«^}=®d§[ÅÁÅ2O 'ßþÒÔ¨%ï²v‘ëaƒÖYU±µÂªñà4‡ÎÒ±åÛòû§‡ú~©`EwæˆVˆ]JU[ÑM¯~tL‚ÝS:ïn 'VIBv…ó`aí­¾ç³ÙÓnW.Çõ»sPÝ-‘ãº8éÞìš‘_êëh>µqÑUÃÎ{¼ gHN T"5uÕk_Þô✲¤z²òÏžy´1eÀdÎ`. Ëú»Õä䂦šúš+ÝòúÎV‡†ž7çSnáã!ë@ë¶¶9‹Æ`…1Y9t)_hb„ ‚˜.Ö¨k;„šÙšiu!ŒBs ±™AlÞ9Ðè n• E´²J°³óp¨[QޏY›ÁÀtz|ˆ}ˆAò S†w½§¥î¥æ…×=‰½*Ò­’Ÿï‡A••2~êå…;PŠW_\Tކ…ò’©ª‹:Œ{?–Ð äà Ž_ž5K öwôm ¯9òþÞ%³p8ÃЃ•Õ·mcÙ}SBmÍP±¯ôç#ç®[Ö+™6]†uÎ17½¾<à,³¹yGN+™ÖÙÏspFß¶y¥wžvU£²ðÉUîáwåyÍýS‰Ùm`Çå)üˆÂãN¸î¹+LÃ&Ïó··4Ú‚|ØótÓ–•Ïnxù~ʤҳ tç Hq˜UOÑ]EvKŠsã†U• ï/hÆþÔ‘3þù˜˜–ŽOpÀvÈϼ/1,â xŠoŠûbFjúÁ¢ÇµvÎÉ#©î¡Þw®)kø-^S^ÆV7( ë#SEÄNЬ“b6ppub¤Ü¢ˆ3ŠÑ$*ÙÙÓ¬NBؾ±"Ä‘ç}õ›½aV!rR­.â¯{êV¹^½ç+2ËÓ˜Ôèô½ŸuÈj÷²¨:GÚyLÉPèšõMƒŸEñRéV8@@Š!à¬Fºfݽ§]Õ;{AQ]¿¤Ò“p¿¤±íÕX’ÞPécn~ý“€=ìfE§ÕÅi‘ÉYÝ`YZšíœ‘úKgþùL4èé¶R·W=ÀE ½ZÞï¯apâB–tdêeÃÓìÌi !ÖÔ2ö•†|•á`ã––Öííbóó~çkŒ’'¨O:ÆŠUnxú¢­GÍ~ë5Y°üaö k5óRð¿rÈûꊹýŒúÝÄ‚“θÌ`I-–ެœq½¡tc!Ø!¨;h| µÉm³k¥ç«Ÿ:ƒB"…kÓÒXJÁ„ .oTbŠs5éÃ1—ää"3j6‡ôö4É6ÀÙàî [%˜UÊ :‡- âa\lŠOx©Æ¸èØÜt1%Ò°fÛ NªþÏ’†Äʹu©ÏãNZÙÅ.¬fá:eŠ4óíU BÖÄ:O{꣘Ù(ò¶¸AeÄ‹`yÑvAŽùyI G¡×[Í|­õþ¯ßïGž|ÚÝ>kþ‘«ëš”žÀbœe[MH •ôÛ%Ê¡¥Á¦-mûfY£/sÜ ´…Å:kÚ!œÎ\À‹[€30+ 7SM2kª\ý\ųW_’“±ðšç߳ǜ¼Ýå#0B`ÄxôĤ’rÌ&~ˆØ¶mSék7ÙÇL™lpd_2e3w ÌÜ!ժѡDj{zãÓ^äÈâ0ا…YX¢`{ ­ŒE«EƒÙ }¼½­Õ}PG‡gUÕ ×Ý“ "UÐ{ZïâìïÝI/9ç±w¿»¾Ç­ªi‰ÁîŽìºã^ }à; ÎÊ UõƲœQŒ]u¿Î ãT Ñ@+;¡@hYG_·hi8m䉺C…i,ÿ(Oô4åahš]7@ôÔ+Îúî±k>/!¯øÙ³‰[R%¦´²‹] }ZÁÔk‡Y O\[0EÄ÷DZ&¾‘:a«‘ &ø@b ]Ð sšÍ,݆œ¥ÍÊËï›zÖ1w-½¡Ã4ø¯?Ô¶†õȘU‰œ@P`.ϼ„l›…ÙpNõ5s±Àrò¬õ5×4ø=M‘·?dLhå¨{û–­UìëÄéÈI—nø%߯Ïv›s’s/8( ®(ï‹ÉÒˆ4Ÿ#º7´¬þ¿¿—½öP L+nŒÎÓ$SZf ¾p”V9·‹Âÿ‘T ¼!È!¢*{`¯$B&:þƒ)63"ï‹¿¦ÂÛR¿í‘¦ŠMxV<ݦÐ!î» æ¼ú«ÅÐ_A¹>ñ’ûÆÛ{«QNZÑì†+Uƒ®øC ‰‹B Íž-ß•?0lµêM`E72N‹ø–þ¬]ëÒi±Q×.\Ë}:™FùXLˆ'}ˆ÷.åAŽæØ¬b¾É/é —}6çÜùt4 øöx(*{?ášgÿIývYkXngœÁW»2jp —¬Ùvx…àùÆ?ˆ "ÖìôþF_ µæÞªù3æ;gé>Óàs¿Gz'v‰ÌE6‰zÈ~€^„ b°'â݄ϵR|vŽé¢~hÓ£Íf“Ðó¹tÛ6nœ¾eÑÍߣóN|Ü=1èä+N°Œ:z¾ä2¤ñ×]ˆÍŽˆ§<)݇8íBž <Ms?/9ã\+>óæß³ NŒö#eÑ4L28taprÉSï¥Àš¡–á™ô°ô¿²¶aÛë¡×o=ñ3”ùÛ/9«d}Õä[œËìžÝ²š¶»;”1ÏŠF" ­š†á}3,Õ!Š® ÿWñbXØ›¹• [¿Ýh€õ¿n]×ê!}Õüù\Θ‹*]„ˆÈàl(Å‚RƒO Au:½xH&¸íüOãÊøpoED°ºaÁELjù[¼6\t·®›û—Ѭà LjÏ¿Lg´ ÆCw@©#äïXYùìŒçóÆ³ =cî{>CÎøUõmJð>\xBa>ä­ê¬ƒc¦4æMp7n&L¶×„z^<„A“‰ pX™É»Éíõs×w«×b4G³‡ŽÉ48SOñ†?´Fͬ¬¹=JAç”xä’BøNyV#Gô ºÖ@Sõ¹?Ì»JÍ 1äÌÛÔ9ÒljfÇ^íXäÁ͆X°<ànø°ö­ûkèÁ+.fHk_(M‡ýÙ¨{V¼§”Ú¾¹¼œ+-+ëJk?Ôïþª÷”:~ôm Î⬹ó$gZe‹ù2•ÖNå:->`!ï4™Ä\}Þ×ܸñ¹ë)š*ØË>Ë«¨T×ßþh€Õ»;%iÜ9ã·NΘð‡¥^ËÀLÊăÄ »l’T½–É`Çg@D Ö¿½ò¥9wT®lQÜpÊgïP@íŠ:¹Ç^õôÙá¬ñ/KvÆ×|;yËw~A猽jþÍÎÑ“ïklínp°Z2—?x™£H–”ºÌ¨ùaN=¬úþúMÏ]û°”m)ÒÔ©dŸ¸Ó ,|ÖÙ÷?š‘ê˜h·ÙŽä‹HQ¿>mXm _¤°¢•@XW( q•´"ðŠNqè³Yk°uÛš#7=ëê8v¿Å¢A¿U–ï~dÝŽ‚«R#Çb¿iÊ]oÍ ™s/\ï ³6¤e³$â¯+ïœ@/;ò ÌsØtöpcÀ·é«3·-þÇ¿n6-IüŠ -rieÏ) ÓŠNPV Úå‘°IzmxƈC7µºáoåwÂf†âjÃB EBÒ×5ayLVÞ)ÏŸ{°ì©ºu|HMªÀÔ³ùÒ²fš¿¬eý—«RR†2£ž´µTÔ«ç¹ë6ý7lÍòoŽZͱ0‚1A,E³B ¢FŽ·Y“˜a„±Î¶ï'°"íúJŽ#‡hÊ…nqjW\T„àXüþŒuÔåp+ç_rcc\/D}„‘W>ýè°ì¢Ë]³nlfeJxÊ8ˆÃ0­bú­mÞ€c@ŽÉj«9×ü „º)-—Š‹eŬXBbe°/ŠdYMº›L±ÈV¼s÷u+W¶ÿXYÙ®‡[^´xÆ!~ºþ•wSrý©fÅâaÛ—=±Y{5ìo÷^¨|ØónIN“L:Ù`5P ¶#°w`«·¾âžm‹îTV*Ñ<Ýg4°íu0ýγm¹£n“ŒÎ˜!…ÃâêdpYœMÇGs&1ÖºmSÅ‹ÃZVÖCÌãó«å(Aëâd-Sf¿9'¬O»vkPÏjɈLÈžO%¸Vc&Nä0Õ7¼µþÑ .ÀjOšg½¾o¿Ä 4Àú)w5Iù9ìÂû®Ðe=áÖ¥qõí>ˆˆÀ´kR&Vñ–ø±Y)\ŠÔÜ {o\~Ïy´LmŸŠ¥ŸA¡¬>è]}Ú˜k_ø! ?Zùô¥M-Ñ—-)¡ð¹Î±·/+¯Œ:sš8fv•¿îªÙ¼JóÖíï>¹ Ç‘'J)½«žó€“Lìty'_>Ù”^pŒ%3ït·>gÌVxCf˜tÜöoŸ*{æ:2[pýºÁJæ~v¾¸ò’Knö¨›ž;Uçp¿[Ì(ø±!Xù„(¨YmâÄVì«"™V‹. žG±¦ ³*Ÿ½æ.ºsÅ%%bé¯p%ƾ«B/­ì-JiY+?0ÝôÂ-O¶¬ú×1)í› ÀÒ‡"ž"BÒÛ“ …7¦Œ5&ØL­mh”ù,ÙçðEÇÍ}÷íC§ß2´t ò ¬ˆýÇéäL+)ÙŒµ®ûòãã=îæ©ž²"F"Õêæ`ý‚•»Èé­éúš·|`õ9Žyié[:¯7œ›üí"§<8Ø¡8ê›^¸~6V°š|QŽåÚVžšM«kpÜ…E¥ßøñk+ñûÆÉVùÇŸuÜÝo/ÓG/ݰüXçŠ š’ÀŠæV¥,Íòa¹ž®oèX÷Ñï°¢û åz©V;L£Äã´Ã~mG/) ê+p™eÔÕÏ>ÁeŒº !À!ôK€”ÕŠ®PˆÛòÃ?¹käQé!st‘¶{?uæÃ¸žl”ðìƒã¢ä¨]œ ÜŽzlÄŞǜ´°Âäˆþê•GV¼pÛWûBŒPësÉ#§ò¹c–6Èv–ª¸¾}÷¨†ÿ.üN=N}ÿ5•bpAŸÎšõ¡Â…ŠSJ^¿:ªsÎt!~hY“à/I–D”zÐGŸ $Gíp`È5CغiDÀK±»1qoI_E§k¥4ÀêAŸò“&¯úVÌ?»äÓ qúÌyi[ÛÚcð÷ƒ•'‚桚‰ÄÚÒÇåk^/ŽK·2[¤e«ìo¾ã³¹]„C$qSfÏJUe«*.vqN|ÑM¯¯l6š`‚;;Vûî7wŸùgâøJâPŸNúÑèðô¡EŸzM9F_Myuû†o.hxïÑÒd«mê÷¯¡Ð½.ž5K•÷ÐÑ7¿ðgÞžu_ŸY¸¾Ñ/`º3]<°jÁKvѬ÷ëÖ]¿eáÌgˆfê‹è×@¿½£X{K¹]]ŽH5Ä)CF_÷Ò|9uè ~Yá¶·[Hü qQŠÄ8)Ójóm<³D]Ë%wÍ=¥\ú5CÀuÆ’%ð9ë2GPÁ1÷”ë';G³ÜmÈa9ú0‹ÕþpÊó®ZÖG“Ÿæ‡Âäž{ßi©©W댖FWSý;Õ n ‡¬é×V4Þ¢ò©rII¨&_ÿäd$«½Ã/:OÜîXm;"XðñÌ9ä^C…^Lô5®ÊF\R mUßn]ñÆE¾oÞ]£pÎgœÁc —8+­ì†`í†8?åP2`\pÏ%†ìQsýà¶¶y:ÈíVÍœ r[t¸à4Œ ˱|øàå#Lq}ól›³ü«û+šØÅ³K¡Û˜¢ˆ j(JMo>éQsúÀñæ¨[n¨ª<}Ë›ßbÉâäO ôt¹§±u«¿ñ²MïרUÑjXÕMõÐ/m«„D.&P9ãÑCéCn ŠŽÓ›`\»©Í›4YF X«'PT #Pd9’d^”:l‚\U¬yã•ÏÝHâ¿ÊUi"àÎ °öP{uZw«ä¼Ñ3^xHNÍ?Ó…˜M>DDÆTàJ‘Òœç R\¦èg†Xûû’»öÁÏTR¡+]Q‘}†KgAï¯T¡Kýý%ÅÎÌ¡ÃáæQ¾iá­¥xlpÝÊ^uWŹªr<–¿pÓ~1;–Wt‰ bLžñèaúôüë‚‚õŒVÙÆ6´Â•RT`ª—Ýé¡¢’ÛRæ£if£.À[ÕVõAÕ7\ªk‰`LòSMT¯mþ4ÀúêƒÃÒ"Œ2•¥î!Óîœf8vnÄ1(¿y}È"´[HÜ{DíÑ `Föƒp‰ÃRì,Cð3“äý„\ó>™{Þ2œFÏe¡¼|‰@JéwWéc°"n Ø:üUÚV~ÿÿçØ\”Ñ™m™†xA0kÆ5AÎô»V,8lhë 3²¹#·?®»øo1ŽäÆŽíÚòÛ«Ýñ<Õ•à¾5®ŠˆÑË¢V/ ¶×§ƒÛ*)Z¬ê>l#.›w Ÿ2ð†€)[_çñÊH/Ž x¬Ûj¢bà‚ €K†:¬\†!ÆŒ1Ï>è~vó²é¯m[ËJÁþ›Ô÷u”»¯Œ•+&{Ý×_ã… nêÓYÅêŠQÁ<厗ϒ)xÛa­Ö³ ÑSá(®¸)ÑK&¨è¬þÅx80å!ë9Ð$síuOUk?«›ÀÂEÉl!£p–âšT¹uÊ ÏÒÎ 2ã‰>Ñ!Öv„¯ HÀH×r>ýIŽËÇÊÊKuÞ)"DO{ÍþÚus·¾6w)Õý¿Ý“Ú×¾î4ÀÚ"í³SHL\ 11aË3ôÌ™§Ý,Ys'¹c:Öàó)À…ØÞ`²È¿ëaQVŸ`ÓƒDJ©Î­,ÓˆpQw½N }$[ßÞúÔ…¥[Ú:£s²Øg•Î.å3à\Õ=U׿φw€TÌA'Å“N*£ãN2 Aÿ¬Sn{á(„<%&X~ämƒš3ý ¬IDATZÂ<ÛÒÛ]D§€9oDDVÒª1ÏzË´˜…Š‘ßQ_jÜxÿæ—f¾¤Ü&2ò=c LºLQzôënh€u ܾ«ECÏb>{ôµ1[ΡnÉ€E¢Æ$$0Uĸè摸H!tƒˆ£ŽŸœÍdâóhÂX톘·VüKA÷Ò­¯\°<™ó¢a“˜BÛ8€!WÞ/‚“¹’’Ùˆq¬‚-É¢5•1ûØ›ž;L°¥Ÿã¿ ÖÁíàjë¼A$É@ÒE!zèÉæ DkxWÉ!$s¦,Ž PGå­/‹4nxhã‹w¼‚S”…d±û´Ò‡Ы‰ù«Rl}’0aØ9³ÏÒe¸&fÍ:ÄÇYX“×Ç"±hTDaÄAöø8ÇE7‘žNÚâŠs]8î4ù«‘Ùù3HÞQ ¯`aï‡ÁÆõË¿œ÷fœÞ­¨…›)%IôÀ_T˜N-,D ¿rPÊø?ÿ–!æ¼QG •ÙIH“qlˆ3é`fVO1èAÀò1ªÜ®PYO¢'}àó‡\ I—Å +wpðôÖ¯¶T?¹qÁ-ä•àÇG[ý#"ìãB÷C+\ÝÒŸsùgÝú'Cîè˱ZuRИ‡,„06Èê%ÇÓÆÓ²z2x%V»ô]|–à®Àíê¸H™ ­`ÁŽÖm?|9ÿŽ*žÕn%®ËPæ ‰TEåår ±ÿÀ m—€k‚ãw˜¨ùŒÂâž:(Ú­”ɽy!oÔAÌd?"ÆŽŽpºñaÑfîÔ7úCð8è)€ôSD>¢! ”>@1d¯f1 ï‹^'¦šÌr1>àZjª~zËË3)²˜S]S¨ƒû©Ð½ÑÊIžÀŲ›>ÉYT|!³dœ5g¦·#bB«ßs )* „1¢œ‚ÑêÒuÑÍUÁ+Î ¢#Eå™NÇç"ÙDªIÇ,\˜‰oP䢕ÈSøL¬”î5íõ?V®|y>âùíb*¹€Y|Y!ã¶´ÍçÖÂl¹#虘Ä2%X zö.¶ˆE…8¤ø7ä¡Ï©ä†¦\,•3Õ D=´ÃvÌÉÓ²R‡M.ÐYSÆ2Ñ8)&ÆG%ÝȈÎj 0=À=ÂjA&G?`¥ ûãwRT9EPˆB·NSÍ&Î#9¯©ƒó·.óU}3Û¿æ¯P;‘ý4{*• ûaÛ9IöC[Z{G8p!"L@@5‡_ôЙ¢#oºdJ™Ô;•$¥ž 8&ÇT‘‘šSY&Ud¤2éb”L_¨‚ŽÈg"y©Ó¨Ãj˜Ät1?Ò\GZ.ZÍK‘ œÞÀB ‘¨w»aëúêÿ{}Œ‘}öW±}ÎÅv–;f ^ï$ëL#9^?"ÆéF@‹TaºŒ¨ˆ„1yBQÖDt(#¸O,Z`¹5®<§¸ˆÉ4¡ n†Q"zr¼Í`àSLfŠykºe}´£á•mï=ðF¨¦f“:X£R)±ÿ·`íšï}‹PÎOEPv Óœ¨D|êÕǘòÆMc–”ßÇ ©y~ÞÄ"32M#+Y°¥™ùÚÞGBÜ7¶.~࿸$D„#ý^ÉK8ŸHˆZµòsP€îŸVúJºÙq%ºŸ2윒Äô!Se£ýؘ15#ÈY;²+{Ca„Cz;,µ“؈'Y¹ïêSG[ÚA\eéATLˆFʃ‰¿Ø‰ëˆãEKfЉ̄-\S±VJL—F$™wE|ò€âÿÁx)žtšÎ‹*AÀPš@6jBH¨‚ŽE1/²š‘$NÑ)!l°’" 0X>±TFlBLJ·)ô>eÚbŠ&ÊㆀK¦¨’ÒŽ·êõ¼ÉeM‰«‡·—ÆZ·¿]ùÒLŠA¢°RnŠ|6»Bû¨‡´íÏ@õ¾þ MkMö(¨0õòŒ ó λëxsàÉÌh? ±Ø³±|Ïü‘D¦ GIŸ|‰À •ý"PÁ‹úF“CýÐo°1²ÆIô[ùK:báâ逛¢NC@“8>ЀX‰ ©¥Ò‰”Ú¨u|v¢fÊ`¤Êâ]Ršw ^}ï<ˆï¨}%¶‹Bqœiïí{-Zƒ'ÕÖÊÛWÄÚëþÓ¸ôÖÿóxXU¼&2öÔŒmUZhÛø”8Ðz¥õg/(P®‹ñÅ AåâÑÔ:Òó§Ý~„!mð œÑ>9&š c¦C˜A¬€ùÂ!„?ù)¸âÀÀjÀa«N*êV:Aý®ßø9ôµÅt^—øÙyR©RGœ…£ºè mé\哸ˆ–ŠrNÒ–ö8Ñâ‰ÃYÄJÁ’i Ä·Ü11¨`÷ò «æ“ªWï"åy=]¯\YLÁKˆI+Q›SjÛ„êý?@º£u£o(P¢€WiwE=UÍevüh{Ñ Gñ–ô#þPIg.úo@€9è… v‘9,LÖ\´ªˆ?<Á¸âpâÜR`Ô¾&0EýÙ'ÛLLˆµ¤˜H´SB—tXÒ3BT5÷ÄÕËaÆÛ#|Ô_ÅÂþï¢þÆ/üÕß}]÷ñÒ2\ÓÕB)`ž&òõÉmÛç•ìd^ìó6µöÃÊbˆh;ñ£^3>y„=ÿ°q‚-õPÞ`› ÆIÐçÀÈ’ñH-/ó,A'ƒ>)ÊÂØ^€­I´úT~Ýð‡Ò'Í«¤¯t^·Òu„Œ„R!XÂÂŒÎ#“Š¢t0 3Š"±%ý™§Â jq$š…¸hp³ö­y[¿ó׬þ¡îã×7âj5{RUÜ1œ,á¢tuB9Cûs S`w³é@î·Ö·½£ecá‹‹.ß™_ZcJîoÎbÊ=B0§Ž –Q²hÁ bZ”×e1^o`zؽ¢›ÂJƒ[{  `&¸Ìýî,Ä)?è/Am’§å=)ßIïE¹Y98%¥ê ‘ŽE`¯"ä"j†¥Tõm‘BÁ Ù×RlÚ²qû «Pk U\Jþ“JrZm…/™4ýò{ò¬é—Ð:ý“(°"XaOÇàž§ä\œ¥4:K°å Ñ™Sr ZÏ‘õ¦lŽ3¤c¬0I°ÃÁ)q¢ºsrÑ¢$ƒu…‚TÄŒóDÈE/X$I†šrÄ72£y°ßËÅÂnñ×c]¯A tÔ…ÛjªåÖíMÛ¾ø­àµ&êÀ¦{éT˜—•Ɉ‘®¶Õý$íW¿¥€XýöÖ퓎+"$|`ƾsãXVœÇü´H`dÂÇ’3:ß$YÓAÖëbz‹Ž78 0¨`p%J\(Ê…B’ „„ˆ'•ƒQÁïŠÖ¯¯"{'ßÈ/O‘±Ýe)•})+E›ð{,+”YB6c'. V~¡ÐëzcûxX4Oˆã¦"{sÌâ¾…È 3‹ø'Èo}U +™Í¸²Â%ŠS3R@i ùú,)B;0õ©û[=`õ·;vàö71—JÀ¥%u²¬l×s¬ˆÀ'QJh«}éÚO¿´¢Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 hÐ( Q@£€F4 ôW ü?AÕ–à­óIEND®B`‚macaron-1/recovery.go000066400000000000000000000104151265413170400150250ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "bytes" "fmt" "io/ioutil" "log" "net/http" "runtime" "github.com/go-macaron/inject" ) const ( panicHtml = ` PANIC: %s

PANIC

%s
%s
` ) var ( dunno = []byte("???") centerDot = []byte("·") dot = []byte(".") slash = []byte("/") ) // stack returns a nicely formated stack frame, skipping skip frames func stack(skip int) []byte { buf := new(bytes.Buffer) // the returned data // As we loop, we open files and read them. These variables record the currently // loaded file. var lines [][]byte var lastFile string for i := skip; ; i++ { // Skip the expected number of frames pc, file, line, ok := runtime.Caller(i) if !ok { break } // Print this much at least. If we can't find the source, it won't show. fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc) if file != lastFile { data, err := ioutil.ReadFile(file) if err != nil { continue } lines = bytes.Split(data, []byte{'\n'}) lastFile = file } fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line)) } return buf.Bytes() } // source returns a space-trimmed slice of the n'th line. func source(lines [][]byte, n int) []byte { n-- // in stack trace, lines are 1-indexed but our array is 0-indexed if n < 0 || n >= len(lines) { return dunno } return bytes.TrimSpace(lines[n]) } // function returns, if possible, the name of the function containing the PC. func function(pc uintptr) []byte { fn := runtime.FuncForPC(pc) if fn == nil { return dunno } name := []byte(fn.Name()) // The name includes the path name to the package, which is unnecessary // since the file name is already included. Plus, it has center dots. // That is, we see // runtime/debug.*T·ptrmethod // and want // *T.ptrmethod // Also the package path might contains dot (e.g. code.google.com/...), // so first eliminate the path prefix if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 { name = name[lastslash+1:] } if period := bytes.Index(name, dot); period >= 0 { name = name[period+1:] } name = bytes.Replace(name, centerDot, dot, -1) return name } // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one. // While Martini is in development mode, Recovery will also output the panic as HTML. func Recovery() Handler { return func(c *Context, log *log.Logger) { defer func() { if err := recover(); err != nil { stack := stack(3) log.Printf("PANIC: %s\n%s", err, stack) // Lookup the current responsewriter val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil))) res := val.Interface().(http.ResponseWriter) // respond with panic message while in development mode var body []byte if Env == DEV { res.Header().Set("Content-Type", "text/html") body = []byte(fmt.Sprintf(panicHtml, err, err, stack)) } res.WriteHeader(http.StatusInternalServerError) if nil != body { res.Write(body) } } }() c.Next() } } macaron-1/recovery_test.go000066400000000000000000000040541265413170400160660ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "bytes" "log" "net/http" "net/http/httptest" "testing" . "github.com/smartystreets/goconvey/convey" ) func Test_Recovery(t *testing.T) { Convey("Recovery from panic", t, func() { buf := bytes.NewBufferString("") setENV(DEV) m := New() m.Map(log.New(buf, "[Macaron] ", 0)) m.Use(func(res http.ResponseWriter, req *http.Request) { res.Header().Set("Content-Type", "unpredictable") }) m.Use(Recovery()) m.Use(func(res http.ResponseWriter, req *http.Request) { panic("here is a panic!") }) m.Get("/", func() {}) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusInternalServerError) So(resp.HeaderMap.Get("Content-Type"), ShouldEqual, "text/html") So(buf.String(), ShouldNotBeEmpty) }) Convey("Revocery panic to another response writer", t, func() { resp := httptest.NewRecorder() resp2 := httptest.NewRecorder() setENV(DEV) m := New() m.Use(Recovery()) m.Use(func(c *Context) { c.MapTo(resp2, (*http.ResponseWriter)(nil)) panic("here is a panic!") }) m.Get("/", func() {}) req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp2.Code, ShouldEqual, http.StatusInternalServerError) So(resp2.HeaderMap.Get("Content-Type"), ShouldEqual, "text/html") So(resp2.Body.Len(), ShouldBeGreaterThan, 0) }) } macaron-1/render.go000066400000000000000000000414111265413170400144460ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "bytes" "encoding/json" "encoding/xml" "fmt" "html/template" "io/ioutil" "net/http" "os" "path" "path/filepath" "strings" "sync" "time" "github.com/Unknwon/com" ) const ( _CONTENT_TYPE = "Content-Type" _CONTENT_LENGTH = "Content-Length" _CONTENT_BINARY = "application/octet-stream" _CONTENT_JSON = "application/json" _CONTENT_HTML = "text/html" _CONTENT_PLAIN = "text/plain" _CONTENT_XHTML = "application/xhtml+xml" _CONTENT_XML = "text/xml" _DEFAULT_CHARSET = "UTF-8" ) var ( // Provides a temporary buffer to execute templates into and catch errors. bufpool = sync.Pool{ New: func() interface{} { return new(bytes.Buffer) }, } // Included helper functions for use when rendering html helperFuncs = template.FuncMap{ "yield": func() (string, error) { return "", fmt.Errorf("yield called with no layout defined") }, "current": func() (string, error) { return "", nil }, } ) type ( // TemplateFile represents a interface of template file that has name and can be read. TemplateFile interface { Name() string Data() []byte Ext() string } // TemplateFileSystem represents a interface of template file system that able to list all files. TemplateFileSystem interface { ListFiles() []TemplateFile } // Delims represents a set of Left and Right delimiters for HTML template rendering Delims struct { // Left delimiter, defaults to {{ Left string // Right delimiter, defaults to }} Right string } // RenderOptions represents a struct for specifying configuration options for the Render middleware. RenderOptions struct { // Directory to load templates. Default is "templates". Directory string // Layout template name. Will not render a layout if "". Default is to "". Layout string // Extensions to parse template files from. Defaults are [".tmpl", ".html"]. Extensions []string // Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is []. Funcs []template.FuncMap // Delims sets the action delimiters to the specified strings in the Delims struct. Delims Delims // Appends the given charset to the Content-Type header. Default is "UTF-8". Charset string // Outputs human readable JSON. IndentJSON bool // Outputs human readable XML. IndentXML bool // Prefixes the JSON output with the given bytes. PrefixJSON []byte // Prefixes the XML output with the given bytes. PrefixXML []byte // Allows changing of output to XHTML instead of HTML. Default is "text/html" HTMLContentType string // TemplateFileSystem is the interface for supporting any implmentation of template file system. TemplateFileSystem } // HTMLOptions is a struct for overriding some rendering Options for specific HTML call HTMLOptions struct { // Layout template name. Overrides Options.Layout. Layout string } Render interface { http.ResponseWriter SetResponseWriter(http.ResponseWriter) JSON(int, interface{}) JSONString(interface{}) (string, error) RawData(int, []byte) // Serve content as binary PlainText(int, []byte) // Serve content as plain text HTML(int, string, interface{}, ...HTMLOptions) HTMLSet(int, string, string, interface{}, ...HTMLOptions) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) HTMLString(string, interface{}, ...HTMLOptions) (string, error) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) XML(int, interface{}) Error(int, ...string) Status(int) SetTemplatePath(string, string) HasTemplateSet(string) bool } ) // TplFile implements TemplateFile interface. type TplFile struct { name string data []byte ext string } // NewTplFile cerates new template file with given name and data. func NewTplFile(name string, data []byte, ext string) *TplFile { return &TplFile{name, data, ext} } func (f *TplFile) Name() string { return f.name } func (f *TplFile) Data() []byte { return f.data } func (f *TplFile) Ext() string { return f.ext } // TplFileSystem implements TemplateFileSystem interface. type TplFileSystem struct { files []TemplateFile } // NewTemplateFileSystem creates new template file system with given options. func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem { fs := TplFileSystem{} fs.files = make([]TemplateFile, 0, 10) dir, err := filepath.EvalSymlinks(opt.Directory) if err != nil { return fs } if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { r, err := filepath.Rel(dir, path) if err != nil { return err } ext := GetExt(r) for _, extension := range opt.Extensions { if ext == extension { var data []byte if !omitData { data, err = ioutil.ReadFile(path) if err != nil { return err } } name := filepath.ToSlash((r[0 : len(r)-len(ext)])) fs.files = append(fs.files, NewTplFile(name, data, ext)) break } } return nil }); err != nil { panic("NewTemplateFileSystem: " + err.Error()) } return fs } func (fs TplFileSystem) ListFiles() []TemplateFile { return fs.files } func PrepareCharset(charset string) string { if len(charset) != 0 { return "; charset=" + charset } return "; charset=" + _DEFAULT_CHARSET } func GetExt(s string) string { index := strings.Index(s, ".") if index == -1 { return "" } return s[index:] } func compile(opt RenderOptions) *template.Template { dir := opt.Directory t := template.New(dir) t.Delims(opt.Delims.Left, opt.Delims.Right) // Parse an initial template in case we don't have any. template.Must(t.Parse("Macaron")) if opt.TemplateFileSystem == nil { opt.TemplateFileSystem = NewTemplateFileSystem(opt, false) } for _, f := range opt.TemplateFileSystem.ListFiles() { tmpl := t.New(f.Name()) for _, funcs := range opt.Funcs { tmpl.Funcs(funcs) } // Bomb out if parse fails. We don't want any silent server starts. template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data()))) } return t } const ( _DEFAULT_TPL_SET_NAME = "DEFAULT" ) // templateSet represents a template set of type *template.Template. type templateSet struct { lock sync.RWMutex sets map[string]*template.Template dirs map[string]string } func newTemplateSet() *templateSet { return &templateSet{ sets: make(map[string]*template.Template), dirs: make(map[string]string), } } func (ts *templateSet) Set(name string, opt *RenderOptions) *template.Template { t := compile(*opt) ts.lock.Lock() defer ts.lock.Unlock() ts.sets[name] = t ts.dirs[name] = opt.Directory return t } func (ts *templateSet) Get(name string) *template.Template { ts.lock.RLock() defer ts.lock.RUnlock() return ts.sets[name] } func (ts *templateSet) GetDir(name string) string { ts.lock.RLock() defer ts.lock.RUnlock() return ts.dirs[name] } func prepareRenderOptions(options []RenderOptions) RenderOptions { var opt RenderOptions if len(options) > 0 { opt = options[0] } // Defaults. if len(opt.Directory) == 0 { opt.Directory = "templates" } if len(opt.Extensions) == 0 { opt.Extensions = []string{".tmpl", ".html"} } if len(opt.HTMLContentType) == 0 { opt.HTMLContentType = _CONTENT_HTML } return opt } func ParseTplSet(tplSet string) (tplName string, tplDir string) { tplSet = strings.TrimSpace(tplSet) if len(tplSet) == 0 { panic("empty template set argument") } infos := strings.Split(tplSet, ":") if len(infos) == 1 { tplDir = infos[0] tplName = path.Base(tplDir) } else { tplName = infos[0] tplDir = infos[1] } if !com.IsDir(tplDir) { panic("template set path does not exist or is not a directory") } return tplName, tplDir } func renderHandler(opt RenderOptions, tplSets []string) Handler { cs := PrepareCharset(opt.Charset) ts := newTemplateSet() ts.Set(_DEFAULT_TPL_SET_NAME, &opt) var tmpOpt RenderOptions for _, tplSet := range tplSets { tplName, tplDir := ParseTplSet(tplSet) tmpOpt = opt tmpOpt.Directory = tplDir ts.Set(tplName, &tmpOpt) } return func(ctx *Context) { r := &TplRender{ ResponseWriter: ctx.Resp, templateSet: ts, Opt: &opt, CompiledCharset: cs, } ctx.Data["TmplLoadTimes"] = func() string { if r.startTime.IsZero() { return "" } return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms" } ctx.Render = r ctx.MapTo(r, (*Render)(nil)) } } // Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain. // An single variadic macaron.RenderOptions struct can be optionally provided to configure // HTML rendering. The default directory for templates is "templates" and the default // file extension is ".tmpl" and ".html". // // If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the // MACARON_ENV environment variable to "production". func Renderer(options ...RenderOptions) Handler { return renderHandler(prepareRenderOptions(options), []string{}) } func Renderers(options RenderOptions, tplSets ...string) Handler { return renderHandler(prepareRenderOptions([]RenderOptions{options}), tplSets) } type TplRender struct { http.ResponseWriter *templateSet Opt *RenderOptions CompiledCharset string startTime time.Time } func (r *TplRender) SetResponseWriter(rw http.ResponseWriter) { r.ResponseWriter = rw } func (r *TplRender) JSON(status int, v interface{}) { var ( result []byte err error ) if r.Opt.IndentJSON { result, err = json.MarshalIndent(v, "", " ") } else { result, err = json.Marshal(v) } if err != nil { http.Error(r, err.Error(), 500) return } // json rendered fine, write out the result r.Header().Set(_CONTENT_TYPE, _CONTENT_JSON+r.CompiledCharset) r.WriteHeader(status) if len(r.Opt.PrefixJSON) > 0 { r.Write(r.Opt.PrefixJSON) } r.Write(result) } func (r *TplRender) JSONString(v interface{}) (string, error) { var result []byte var err error if r.Opt.IndentJSON { result, err = json.MarshalIndent(v, "", " ") } else { result, err = json.Marshal(v) } if err != nil { return "", err } return string(result), nil } func (r *TplRender) XML(status int, v interface{}) { var result []byte var err error if r.Opt.IndentXML { result, err = xml.MarshalIndent(v, "", " ") } else { result, err = xml.Marshal(v) } if err != nil { http.Error(r, err.Error(), 500) return } // XML rendered fine, write out the result r.Header().Set(_CONTENT_TYPE, _CONTENT_XML+r.CompiledCharset) r.WriteHeader(status) if len(r.Opt.PrefixXML) > 0 { r.Write(r.Opt.PrefixXML) } r.Write(result) } func (r *TplRender) data(status int, contentType string, v []byte) { if r.Header().Get(_CONTENT_TYPE) == "" { r.Header().Set(_CONTENT_TYPE, contentType) } r.WriteHeader(status) r.Write(v) } func (r *TplRender) RawData(status int, v []byte) { r.data(status, _CONTENT_BINARY, v) } func (r *TplRender) PlainText(status int, v []byte) { r.data(status, _CONTENT_PLAIN, v) } func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) { buf := bufpool.Get().(*bytes.Buffer) return buf, t.ExecuteTemplate(buf, name, data) } func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) { funcs := template.FuncMap{ "yield": func() (template.HTML, error) { buf, err := r.execute(t, tplName, data) // return safe html here since we are rendering our own template return template.HTML(buf.String()), err }, "current": func() (string, error) { return tplName, nil }, } t.Funcs(funcs) } func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) { t := r.templateSet.Get(setName) if Env == DEV { opt := *r.Opt opt.Directory = r.templateSet.GetDir(setName) t = r.templateSet.Set(setName, &opt) } if t == nil { return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName) } opt := r.prepareHTMLOptions(htmlOpt) if len(opt.Layout) > 0 { r.addYield(t, tplName, data) tplName = opt.Layout } out, err := r.execute(t, tplName, data) if err != nil { return nil, err } return out, nil } func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) { r.startTime = time.Now() out, err := r.renderBytes(setName, tplName, data, htmlOpt...) if err != nil { http.Error(r, err.Error(), http.StatusInternalServerError) return } r.Header().Set(_CONTENT_TYPE, r.Opt.HTMLContentType+r.CompiledCharset) r.WriteHeader(status) out.WriteTo(r) bufpool.Put(out) } func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) { r.renderHTML(status, _DEFAULT_TPL_SET_NAME, name, data, htmlOpt...) } func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) { r.renderHTML(status, setName, tplName, data, htmlOpt...) } func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) { out, err := r.renderBytes(setName, tplName, data, htmlOpt...) if err != nil { return []byte(""), err } return out.Bytes(), nil } func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) { return r.HTMLSetBytes(_DEFAULT_TPL_SET_NAME, name, data, htmlOpt...) } func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) { p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...) return string(p), err } func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) { p, err := r.HTMLBytes(name, data, htmlOpt...) return string(p), err } // Error writes the given HTTP status to the current ResponseWriter func (r *TplRender) Error(status int, message ...string) { r.WriteHeader(status) if len(message) > 0 { r.Write([]byte(message[0])) } } func (r *TplRender) Status(status int) { r.WriteHeader(status) } func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions { if len(htmlOpt) > 0 { return htmlOpt[0] } return HTMLOptions{ Layout: r.Opt.Layout, } } func (r *TplRender) SetTemplatePath(setName, dir string) { if len(setName) == 0 { setName = _DEFAULT_TPL_SET_NAME } opt := *r.Opt opt.Directory = dir r.templateSet.Set(setName, &opt) } func (r *TplRender) HasTemplateSet(name string) bool { return r.templateSet.Get(name) != nil } // dummyRender is used when user does not choose any real render to use. // This way, we can print out friendly message which asks them to register one, // instead of ugly and confusing 'nil pointer' panic. type dummyRender struct { http.ResponseWriter } func renderNotRegistered() { panic("middleware render hasn't been registered") } func (r *dummyRender) SetResponseWriter(http.ResponseWriter) { renderNotRegistered() } func (r *dummyRender) JSON(int, interface{}) { renderNotRegistered() } func (r *dummyRender) JSONString(interface{}) (string, error) { renderNotRegistered() return "", nil } func (r *dummyRender) RawData(int, []byte) { renderNotRegistered() } func (r *dummyRender) PlainText(int, []byte) { renderNotRegistered() } func (r *dummyRender) HTML(int, string, interface{}, ...HTMLOptions) { renderNotRegistered() } func (r *dummyRender) HTMLSet(int, string, string, interface{}, ...HTMLOptions) { renderNotRegistered() } func (r *dummyRender) HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error) { renderNotRegistered() return "", nil } func (r *dummyRender) HTMLString(string, interface{}, ...HTMLOptions) (string, error) { renderNotRegistered() return "", nil } func (r *dummyRender) HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error) { renderNotRegistered() return nil, nil } func (r *dummyRender) HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error) { renderNotRegistered() return nil, nil } func (r *dummyRender) XML(int, interface{}) { renderNotRegistered() } func (r *dummyRender) Error(int, ...string) { renderNotRegistered() } func (r *dummyRender) Status(int) { renderNotRegistered() } func (r *dummyRender) SetTemplatePath(string, string) { renderNotRegistered() } func (r *dummyRender) HasTemplateSet(string) bool { renderNotRegistered() return false } macaron-1/render_test.go000066400000000000000000000455751265413170400155240ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "encoding/xml" "html/template" "net/http" "net/http/httptest" "testing" "time" . "github.com/smartystreets/goconvey/convey" ) type Greeting struct { One string `json:"one"` Two string `json:"two"` } type GreetingXML struct { XMLName xml.Name `xml:"greeting"` One string `xml:"one,attr"` Two string `xml:"two,attr"` } func Test_Render_JSON(t *testing.T) { Convey("Render JSON", t, func() { m := Classic() m.Use(Renderer()) m.Get("/foobar", func(r Render) { r.JSON(300, Greeting{"hello", "world"}) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusMultipleChoices) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_JSON+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, `{"one":"hello","two":"world"}`) }) Convey("Render JSON with prefix", t, func() { m := Classic() prefix := ")]}',\n" m.Use(Renderer(RenderOptions{ PrefixJSON: []byte(prefix), })) m.Get("/foobar", func(r Render) { r.JSON(300, Greeting{"hello", "world"}) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusMultipleChoices) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_JSON+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, prefix+`{"one":"hello","two":"world"}`) }) Convey("Render Indented JSON", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ IndentJSON: true, })) m.Get("/foobar", func(r Render) { r.JSON(300, Greeting{"hello", "world"}) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusMultipleChoices) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_JSON+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, `{ "one": "hello", "two": "world" }`) }) Convey("Render JSON and return string", t, func() { m := Classic() m.Use(Renderer()) m.Get("/foobar", func(r Render) { result, err := r.JSONString(Greeting{"hello", "world"}) So(err, ShouldBeNil) So(result, ShouldEqual, `{"one":"hello","two":"world"}`) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) }) Convey("Render with charset JSON", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Charset: "foobar", })) m.Get("/foobar", func(r Render) { r.JSON(300, Greeting{"hello", "world"}) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusMultipleChoices) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_JSON+"; charset=foobar") So(resp.Body.String(), ShouldEqual, `{"one":"hello","two":"world"}`) }) } func Test_Render_XML(t *testing.T) { Convey("Render XML", t, func() { m := Classic() m.Use(Renderer()) m.Get("/foobar", func(r Render) { r.XML(300, GreetingXML{One: "hello", Two: "world"}) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusMultipleChoices) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_XML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, ``) }) Convey("Render XML with prefix", t, func() { m := Classic() prefix := ")]}',\n" m.Use(Renderer(RenderOptions{ PrefixXML: []byte(prefix), })) m.Get("/foobar", func(r Render) { r.XML(300, GreetingXML{One: "hello", Two: "world"}) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusMultipleChoices) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_XML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, prefix+``) }) Convey("Render Indented XML", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ IndentXML: true, })) m.Get("/foobar", func(r Render) { r.XML(300, GreetingXML{One: "hello", Two: "world"}) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusMultipleChoices) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_XML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, ``) }) } func Test_Render_HTML(t *testing.T) { Convey("Render HTML", t, func() { m := Classic() m.Use(Renderers(RenderOptions{ Directory: "fixtures/basic", }, "fixtures/basic2")) m.Get("/foobar", func(r Render) { r.SetResponseWriter(r.(*TplRender).ResponseWriter) r.HTML(200, "hello", "jeremy") r.SetTemplatePath("", "fixtures/basic2") }) m.Get("/foobar2", func(r Render) { if r.HasTemplateSet("basic2") { r.HTMLSet(200, "basic2", "hello", "jeremy") } }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, "

Hello jeremy

") resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/foobar2", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, "

What's up, jeremy

") Convey("Change render templates path", func() { resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, "

What's up, jeremy

") }) }) Convey("Render HTML and return string", t, func() { m := Classic() m.Use(Renderers(RenderOptions{ Directory: "fixtures/basic", }, "basic2:fixtures/basic2")) m.Get("/foobar", func(r Render) { result, err := r.HTMLString("hello", "jeremy") So(err, ShouldBeNil) So(result, ShouldEqual, "

Hello jeremy

") }) m.Get("/foobar2", func(r Render) { result, err := r.HTMLSetString("basic2", "hello", "jeremy") So(err, ShouldBeNil) So(result, ShouldEqual, "

What's up, jeremy

") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/foobar2", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) }) Convey("Render with nested HTML", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Directory: "fixtures/basic", })) m.Get("/foobar", func(r Render) { r.HTML(200, "admin/index", "jeremy") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, "

Admin jeremy

") }) Convey("Render bad HTML", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Directory: "fixtures/basic", })) m.Get("/foobar", func(r Render) { r.HTML(200, "nope", nil) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusInternalServerError) So(resp.Body.String(), ShouldEqual, "html/template: \"nope\" is undefined\n") }) Convey("Invalid template set", t, func() { Convey("Empty template set argument", func() { defer func() { So(recover(), ShouldNotBeNil) }() m := Classic() m.Use(Renderers(RenderOptions{ Directory: "fixtures/basic", }, "")) }) Convey("Bad template set path", func() { defer func() { So(recover(), ShouldNotBeNil) }() m := Classic() m.Use(Renderers(RenderOptions{ Directory: "fixtures/basic", }, "404")) }) }) } func Test_Render_XHTML(t *testing.T) { Convey("Render XHTML", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Directory: "fixtures/basic", HTMLContentType: _CONTENT_XHTML, })) m.Get("/foobar", func(r Render) { r.HTML(200, "hello", "jeremy") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_XHTML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, "

Hello jeremy

") }) } func Test_Render_Extensions(t *testing.T) { Convey("Render with extensions", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Directory: "fixtures/basic", Extensions: []string{".tmpl", ".html"}, })) m.Get("/foobar", func(r Render) { r.HTML(200, "hypertext", nil) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, "Hypertext!") }) } func Test_Render_Funcs(t *testing.T) { Convey("Render with functions", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Directory: "fixtures/custom_funcs", Funcs: []template.FuncMap{ { "myCustomFunc": func() string { return "My custom function" }, }, }, })) m.Get("/foobar", func(r Render) { r.HTML(200, "index", "jeremy") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "My custom function") }) } func Test_Render_Layout(t *testing.T) { Convey("Render with layout", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Directory: "fixtures/basic", Layout: "layout", })) m.Get("/foobar", func(r Render) { r.HTML(200, "content", "jeremy") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "head

jeremy

foot") }) Convey("Render with current layout", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Directory: "fixtures/basic", Layout: "current_layout", })) m.Get("/foobar", func(r Render) { r.HTML(200, "content", "jeremy") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "content head

jeremy

content foot") }) Convey("Render with override layout", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Directory: "fixtures/basic", Layout: "layout", })) m.Get("/foobar", func(r Render) { r.HTML(200, "content", "jeremy", HTMLOptions{ Layout: "another_layout", }) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, "another head

jeremy

another foot") }) } func Test_Render_Delimiters(t *testing.T) { Convey("Render with delimiters", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Delims: Delims{"{[{", "}]}"}, Directory: "fixtures/basic", })) m.Get("/foobar", func(r Render) { r.HTML(200, "delims", "jeremy") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_HTML+"; charset=UTF-8") So(resp.Body.String(), ShouldEqual, "

Hello jeremy

") }) } func Test_Render_BinaryData(t *testing.T) { Convey("Render binary data", t, func() { m := Classic() m.Use(Renderer()) m.Get("/foobar", func(r Render) { r.RawData(200, []byte("hello there")) }) m.Get("/foobar2", func(r Render) { r.PlainText(200, []byte("hello there")) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_BINARY) So(resp.Body.String(), ShouldEqual, "hello there") resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/foobar2", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, _CONTENT_PLAIN) So(resp.Body.String(), ShouldEqual, "hello there") }) Convey("Render binary data with mime type", t, func() { m := Classic() m.Use(Renderer()) m.Get("/foobar", func(r Render) { r.(*TplRender).ResponseWriter.Header().Set(_CONTENT_TYPE, "image/jpeg") r.RawData(200, []byte("..jpeg data..")) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get(_CONTENT_TYPE), ShouldEqual, "image/jpeg") So(resp.Body.String(), ShouldEqual, "..jpeg data..") }) } func Test_Render_Status(t *testing.T) { Convey("Render with status 204", t, func() { resp := httptest.NewRecorder() r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()} r.Status(204) So(resp.Code, ShouldEqual, http.StatusNoContent) }) Convey("Render with status 404", t, func() { resp := httptest.NewRecorder() r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()} r.Error(404) So(resp.Code, ShouldEqual, http.StatusNotFound) }) Convey("Render with status 500", t, func() { resp := httptest.NewRecorder() r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()} r.Error(500) So(resp.Code, ShouldEqual, http.StatusInternalServerError) }) } func Test_Render_NoRace(t *testing.T) { Convey("Make sure render has no race", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Directory: "fixtures/basic", })) m.Get("/foobar", func(r Render) { r.HTML(200, "hello", "world") }) done := make(chan bool) doreq := func() { resp := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/foobar", nil) m.ServeHTTP(resp, req) done <- true } // Run two requests to check there is no race condition go doreq() go doreq() <-done <-done }) } func Test_Render_Symlink(t *testing.T) { Convey("Render can follow symlinks", t, func() { m := Classic() m.Use(Renderer(RenderOptions{ Directory: "fixtures/symlink", })) m.Get("/foobar", func(r Render) { r.HTML(200, "hello", "world") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/foobar", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) }) } func Test_GetExt(t *testing.T) { Convey("Get extension", t, func() { So(GetExt("test"), ShouldBeBlank) So(GetExt("test.tmpl"), ShouldEqual, ".tmpl") So(GetExt("test.go.tmpl"), ShouldEqual, ".go.tmpl") }) } func Test_dummyRender(t *testing.T) { shouldPanic := func() { So(recover(), ShouldNotBeNil) } Convey("Use dummy render to gracefully handle panic", t, func() { m := New() performRequest := func(method, path string) { resp := httptest.NewRecorder() req, err := http.NewRequest(method, path, nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) } m.Get("/set_response_writer", func(ctx *Context) { defer shouldPanic() ctx.SetResponseWriter(nil) }) m.Get("/json", func(ctx *Context) { defer shouldPanic() ctx.JSON(0, nil) }) m.Get("/jsonstring", func(ctx *Context) { defer shouldPanic() ctx.JSONString(nil) }) m.Get("/rawdata", func(ctx *Context) { defer shouldPanic() ctx.RawData(0, nil) }) m.Get("/plaintext", func(ctx *Context) { defer shouldPanic() ctx.PlainText(0, nil) }) m.Get("/html", func(ctx *Context) { defer shouldPanic() ctx.Render.HTML(0, "", nil) }) m.Get("/htmlset", func(ctx *Context) { defer shouldPanic() ctx.Render.HTMLSet(0, "", "", nil) }) m.Get("/htmlsetstring", func(ctx *Context) { defer shouldPanic() ctx.Render.HTMLSetString("", "", nil) }) m.Get("/htmlstring", func(ctx *Context) { defer shouldPanic() ctx.Render.HTMLString("", nil) }) m.Get("/htmlsetbytes", func(ctx *Context) { defer shouldPanic() ctx.Render.HTMLSetBytes("", "", nil) }) m.Get("/htmlbytes", func(ctx *Context) { defer shouldPanic() ctx.Render.HTMLBytes("", nil) }) m.Get("/xml", func(ctx *Context) { defer shouldPanic() ctx.XML(0, nil) }) m.Get("/error", func(ctx *Context) { defer shouldPanic() ctx.Error(0) }) m.Get("/status", func(ctx *Context) { defer shouldPanic() ctx.Status(0) }) m.Get("/settemplatepath", func(ctx *Context) { defer shouldPanic() ctx.SetTemplatePath("", "") }) m.Get("/hastemplateset", func(ctx *Context) { defer shouldPanic() ctx.HasTemplateSet("") }) performRequest("GET", "/set_response_writer") performRequest("GET", "/json") performRequest("GET", "/jsonstring") performRequest("GET", "/rawdata") performRequest("GET", "/jsonstring") performRequest("GET", "/plaintext") performRequest("GET", "/html") performRequest("GET", "/htmlset") performRequest("GET", "/htmlsetstring") performRequest("GET", "/htmlstring") performRequest("GET", "/htmlsetbytes") performRequest("GET", "/htmlbytes") performRequest("GET", "/xml") performRequest("GET", "/error") performRequest("GET", "/status") performRequest("GET", "/settemplatepath") performRequest("GET", "/hastemplateset") }) } macaron-1/response_writer.go000066400000000000000000000061341265413170400164240ustar00rootroot00000000000000// Copyright 2013 Martini Authors // // 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 macaron import ( "bufio" "fmt" "net" "net/http" ) // ResponseWriter is a wrapper around http.ResponseWriter that provides extra information about // the response. It is recommended that middleware handlers use this construct to wrap a responsewriter // if the functionality calls for it. type ResponseWriter interface { http.ResponseWriter http.Flusher // Status returns the status code of the response or 0 if the response has not been written. Status() int // Written returns whether or not the ResponseWriter has been written. Written() bool // Size returns the size of the response body. Size() int // Before allows for a function to be called before the ResponseWriter has been written to. This is // useful for setting headers or any other operations that must happen before a response has been written. Before(BeforeFunc) } // BeforeFunc is a function that is called before the ResponseWriter has been written to. type BeforeFunc func(ResponseWriter) // NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter func NewResponseWriter(rw http.ResponseWriter) ResponseWriter { return &responseWriter{rw, 0, 0, nil} } type responseWriter struct { http.ResponseWriter status int size int beforeFuncs []BeforeFunc } func (rw *responseWriter) WriteHeader(s int) { rw.callBefore() rw.ResponseWriter.WriteHeader(s) rw.status = s } func (rw *responseWriter) Write(b []byte) (int, error) { if !rw.Written() { // The status will be StatusOK if WriteHeader has not been called yet rw.WriteHeader(http.StatusOK) } size, err := rw.ResponseWriter.Write(b) rw.size += size return size, err } func (rw *responseWriter) Status() int { return rw.status } func (rw *responseWriter) Size() int { return rw.size } func (rw *responseWriter) Written() bool { return rw.status != 0 } func (rw *responseWriter) Before(before BeforeFunc) { rw.beforeFuncs = append(rw.beforeFuncs, before) } func (rw *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { hijacker, ok := rw.ResponseWriter.(http.Hijacker) if !ok { return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface") } return hijacker.Hijack() } func (rw *responseWriter) CloseNotify() <-chan bool { return rw.ResponseWriter.(http.CloseNotifier).CloseNotify() } func (rw *responseWriter) callBefore() { for i := len(rw.beforeFuncs) - 1; i >= 0; i-- { rw.beforeFuncs[i](rw) } } func (rw *responseWriter) Flush() { flusher, ok := rw.ResponseWriter.(http.Flusher) if ok { flusher.Flush() } } macaron-1/response_writer_test.go000066400000000000000000000120161265413170400174570ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "bufio" "io" "net" "net/http" "net/http/httptest" "testing" "time" . "github.com/smartystreets/goconvey/convey" ) type closeNotifyingRecorder struct { *httptest.ResponseRecorder closed chan bool } func newCloseNotifyingRecorder() *closeNotifyingRecorder { return &closeNotifyingRecorder{ httptest.NewRecorder(), make(chan bool, 1), } } func (c *closeNotifyingRecorder) close() { c.closed <- true } func (c *closeNotifyingRecorder) CloseNotify() <-chan bool { return c.closed } type hijackableResponse struct { Hijacked bool } func newHijackableResponse() *hijackableResponse { return &hijackableResponse{} } func (h *hijackableResponse) Header() http.Header { return nil } func (h *hijackableResponse) Write(buf []byte) (int, error) { return 0, nil } func (h *hijackableResponse) WriteHeader(code int) {} func (h *hijackableResponse) Flush() {} func (h *hijackableResponse) Hijack() (net.Conn, *bufio.ReadWriter, error) { h.Hijacked = true return nil, nil, nil } func Test_ResponseWriter(t *testing.T) { Convey("Write string to response writer", t, func() { resp := httptest.NewRecorder() rw := NewResponseWriter(resp) rw.Write([]byte("Hello world")) So(resp.Code, ShouldEqual, rw.Status()) So(resp.Body.String(), ShouldEqual, "Hello world") So(rw.Status(), ShouldEqual, http.StatusOK) So(rw.Size(), ShouldEqual, 11) So(rw.Written(), ShouldBeTrue) }) Convey("Write strings to response writer", t, func() { resp := httptest.NewRecorder() rw := NewResponseWriter(resp) rw.Write([]byte("Hello world")) rw.Write([]byte("foo bar bat baz")) So(resp.Code, ShouldEqual, rw.Status()) So(resp.Body.String(), ShouldEqual, "Hello worldfoo bar bat baz") So(rw.Status(), ShouldEqual, http.StatusOK) So(rw.Size(), ShouldEqual, 26) So(rw.Written(), ShouldBeTrue) }) Convey("Write header to response writer", t, func() { resp := httptest.NewRecorder() rw := NewResponseWriter(resp) rw.WriteHeader(http.StatusNotFound) So(resp.Code, ShouldEqual, rw.Status()) So(resp.Body.String(), ShouldBeBlank) So(rw.Status(), ShouldEqual, http.StatusNotFound) So(rw.Size(), ShouldEqual, 0) }) Convey("Write before response write", t, func() { result := "" resp := httptest.NewRecorder() rw := NewResponseWriter(resp) rw.Before(func(ResponseWriter) { result += "foo" }) rw.Before(func(ResponseWriter) { result += "bar" }) rw.WriteHeader(http.StatusNotFound) So(resp.Code, ShouldEqual, rw.Status()) So(resp.Body.String(), ShouldBeBlank) So(rw.Status(), ShouldEqual, http.StatusNotFound) So(rw.Size(), ShouldEqual, 0) So(result, ShouldEqual, "barfoo") }) Convey("Response writer with Hijack", t, func() { hijackable := newHijackableResponse() rw := NewResponseWriter(hijackable) hijacker, ok := rw.(http.Hijacker) So(ok, ShouldBeTrue) _, _, err := hijacker.Hijack() So(err, ShouldBeNil) So(hijackable.Hijacked, ShouldBeTrue) }) Convey("Response writer with bad Hijack", t, func() { hijackable := new(http.ResponseWriter) rw := NewResponseWriter(*hijackable) hijacker, ok := rw.(http.Hijacker) So(ok, ShouldBeTrue) _, _, err := hijacker.Hijack() So(err, ShouldNotBeNil) }) Convey("Response writer with close notify", t, func() { resp := newCloseNotifyingRecorder() rw := NewResponseWriter(resp) closed := false notifier := rw.(http.CloseNotifier).CloseNotify() resp.close() select { case <-notifier: closed = true case <-time.After(time.Second): } So(closed, ShouldBeTrue) }) Convey("Response writer with flusher", t, func() { resp := httptest.NewRecorder() rw := NewResponseWriter(resp) _, ok := rw.(http.Flusher) So(ok, ShouldBeTrue) }) Convey("Response writer with flusher handler", t, func() { m := Classic() m.Get("/events", func(w http.ResponseWriter, r *http.Request) { f, ok := w.(http.Flusher) So(ok, ShouldBeTrue) w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") for i := 0; i < 2; i++ { time.Sleep(10 * time.Millisecond) io.WriteString(w, "data: Hello\n\n") f.Flush() } }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/events", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Body.String(), ShouldEqual, "data: Hello\n\ndata: Hello\n\n") }) } macaron-1/return_handler.go000066400000000000000000000041551265413170400162070ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "net/http" "reflect" "github.com/go-macaron/inject" ) // ReturnHandler is a service that Martini provides that is called // when a route handler returns something. The ReturnHandler is // responsible for writing to the ResponseWriter based on the values // that are passed into this function. type ReturnHandler func(*Context, []reflect.Value) func canDeref(val reflect.Value) bool { return val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr } func isError(val reflect.Value) bool { _, ok := val.Interface().(error) return ok } func isByteSlice(val reflect.Value) bool { return val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8 } func defaultReturnHandler() ReturnHandler { return func(ctx *Context, vals []reflect.Value) { rv := ctx.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil))) resp := rv.Interface().(http.ResponseWriter) var respVal reflect.Value if len(vals) > 1 && vals[0].Kind() == reflect.Int { resp.WriteHeader(int(vals[0].Int())) respVal = vals[1] } else if len(vals) > 0 { respVal = vals[0] if isError(respVal) { err := respVal.Interface().(error) if err != nil { ctx.internalServerError(ctx, err) } return } else if canDeref(respVal) { if respVal.IsNil() { return // Ignore nil error } } } if canDeref(respVal) { respVal = respVal.Elem() } if isByteSlice(respVal) { resp.Write(respVal.Bytes()) } else { resp.Write([]byte(respVal.String())) } } } macaron-1/return_handler_test.go000066400000000000000000000047711265413170400172520ustar00rootroot00000000000000// Copyright 2014 The Macaron Authors // // 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 macaron import ( "errors" "net/http" "net/http/httptest" "testing" . "github.com/smartystreets/goconvey/convey" ) func Test_Return_Handler(t *testing.T) { Convey("Return with status and body", t, func() { m := New() m.Get("/", func() (int, string) { return 418, "i'm a teapot" }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusTeapot) So(resp.Body.String(), ShouldEqual, "i'm a teapot") }) Convey("Return with error", t, func() { m := New() m.Get("/", func() error { return errors.New("what the hell!!!") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusInternalServerError) So(resp.Body.String(), ShouldEqual, "what the hell!!!\n") Convey("Return with nil error", func() { m := New() m.Get("/", func() error { return nil }, func() (int, string) { return 200, "Awesome" }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Body.String(), ShouldEqual, "Awesome") }) }) Convey("Return with pointer", t, func() { m := New() m.Get("/", func() *string { str := "hello world" return &str }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "hello world") }) Convey("Return with byte slice", t, func() { m := New() m.Get("/", func() []byte { return []byte("hello world") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "hello world") }) } macaron-1/router.go000066400000000000000000000224461265413170400145160ustar00rootroot00000000000000// Copyright 2014 The Macaron Authors // // 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 macaron import ( "net/http" "strings" "sync" ) var ( // Known HTTP methods. _HTTP_METHODS = map[string]bool{ "GET": true, "POST": true, "PUT": true, "DELETE": true, "PATCH": true, "OPTIONS": true, "HEAD": true, } ) // routeMap represents a thread-safe map for route tree. type routeMap struct { lock sync.RWMutex routes map[string]map[string]*Leaf } // NewRouteMap initializes and returns a new routeMap. func NewRouteMap() *routeMap { rm := &routeMap{ routes: make(map[string]map[string]*Leaf), } for m := range _HTTP_METHODS { rm.routes[m] = make(map[string]*Leaf) } return rm } // getLeaf returns Leaf object if a route has been registered. func (rm *routeMap) getLeaf(method, pattern string) *Leaf { rm.lock.RLock() defer rm.lock.RUnlock() return rm.routes[method][pattern] } // add adds new route to route tree map. func (rm *routeMap) add(method, pattern string, leaf *Leaf) { rm.lock.Lock() defer rm.lock.Unlock() rm.routes[method][pattern] = leaf } type group struct { pattern string handlers []Handler } // Router represents a Macaron router layer. type Router struct { m *Macaron autoHead bool routers map[string]*Tree *routeMap namedRoutes map[string]*Leaf groups []group notFound http.HandlerFunc internalServerError func(*Context, error) } func NewRouter() *Router { return &Router{ routers: make(map[string]*Tree), routeMap: NewRouteMap(), namedRoutes: make(map[string]*Leaf), } } // SetAutoHead sets the value who determines whether add HEAD method automatically // when GET method is added. Combo router will not be affected by this value. func (r *Router) SetAutoHead(v bool) { r.autoHead = v } type Params map[string]string // Handle is a function that can be registered to a route to handle HTTP requests. // Like http.HandlerFunc, but has a third parameter for the values of wildcards (variables). type Handle func(http.ResponseWriter, *http.Request, Params) // Route represents a wrapper of leaf route and upper level router. type Route struct { router *Router leaf *Leaf } // Name sets name of route. func (r *Route) Name(name string) { if len(name) == 0 { panic("route name cannot be empty") } else if r.router.namedRoutes[name] != nil { panic("route with given name already exists") } r.router.namedRoutes[name] = r.leaf } // handle adds new route to the router tree. func (r *Router) handle(method, pattern string, handle Handle) *Route { method = strings.ToUpper(method) var leaf *Leaf // Prevent duplicate routes. if leaf = r.getLeaf(method, pattern); leaf != nil { return &Route{r, leaf} } // Validate HTTP methods. if !_HTTP_METHODS[method] && method != "*" { panic("unknown HTTP method: " + method) } // Generate methods need register. methods := make(map[string]bool) if method == "*" { for m := range _HTTP_METHODS { methods[m] = true } } else { methods[method] = true } // Add to router tree. for m := range methods { if t, ok := r.routers[m]; ok { leaf = t.Add(pattern, handle) } else { t := NewTree() leaf = t.Add(pattern, handle) r.routers[m] = t } r.add(m, pattern, leaf) } return &Route{r, leaf} } // Handle registers a new request handle with the given pattern, method and handlers. func (r *Router) Handle(method string, pattern string, handlers []Handler) *Route { if len(r.groups) > 0 { groupPattern := "" h := make([]Handler, 0) for _, g := range r.groups { groupPattern += g.pattern h = append(h, g.handlers...) } pattern = groupPattern + pattern h = append(h, handlers...) handlers = h } validateHandlers(handlers) return r.handle(method, pattern, func(resp http.ResponseWriter, req *http.Request, params Params) { c := r.m.createContext(resp, req) c.params = params c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers)) c.handlers = append(c.handlers, r.m.handlers...) c.handlers = append(c.handlers, handlers...) c.run() }) } func (r *Router) Group(pattern string, fn func(), h ...Handler) { r.groups = append(r.groups, group{pattern, h}) fn() r.groups = r.groups[:len(r.groups)-1] } // Get is a shortcut for r.Handle("GET", pattern, handlers) func (r *Router) Get(pattern string, h ...Handler) (leaf *Route) { leaf = r.Handle("GET", pattern, h) if r.autoHead { r.Head(pattern, h...) } return leaf } // Patch is a shortcut for r.Handle("PATCH", pattern, handlers) func (r *Router) Patch(pattern string, h ...Handler) *Route { return r.Handle("PATCH", pattern, h) } // Post is a shortcut for r.Handle("POST", pattern, handlers) func (r *Router) Post(pattern string, h ...Handler) *Route { return r.Handle("POST", pattern, h) } // Put is a shortcut for r.Handle("PUT", pattern, handlers) func (r *Router) Put(pattern string, h ...Handler) *Route { return r.Handle("PUT", pattern, h) } // Delete is a shortcut for r.Handle("DELETE", pattern, handlers) func (r *Router) Delete(pattern string, h ...Handler) *Route { return r.Handle("DELETE", pattern, h) } // Options is a shortcut for r.Handle("OPTIONS", pattern, handlers) func (r *Router) Options(pattern string, h ...Handler) *Route { return r.Handle("OPTIONS", pattern, h) } // Head is a shortcut for r.Handle("HEAD", pattern, handlers) func (r *Router) Head(pattern string, h ...Handler) *Route { return r.Handle("HEAD", pattern, h) } // Any is a shortcut for r.Handle("*", pattern, handlers) func (r *Router) Any(pattern string, h ...Handler) *Route { return r.Handle("*", pattern, h) } // Route is a shortcut for same handlers but different HTTP methods. // // Example: // m.Route("/", "GET,POST", h) func (r *Router) Route(pattern, methods string, h ...Handler) (route *Route) { for _, m := range strings.Split(methods, ",") { route = r.Handle(strings.TrimSpace(m), pattern, h) } return route } // Combo returns a combo router. func (r *Router) Combo(pattern string, h ...Handler) *ComboRouter { return &ComboRouter{r, pattern, h, map[string]bool{}, nil} } // Configurable http.HandlerFunc which is called when no matching route is // found. If it is not set, http.NotFound is used. // Be sure to set 404 response code in your handler. func (r *Router) NotFound(handlers ...Handler) { validateHandlers(handlers) r.notFound = func(rw http.ResponseWriter, req *http.Request) { c := r.m.createContext(rw, req) c.handlers = append(r.m.handlers, handlers...) c.run() } } // Configurable handler which is called when route handler returns // error. If it is not set, default handler is used. // Be sure to set 500 response code in your handler. func (r *Router) InternalServerError(handlers ...Handler) { validateHandlers(handlers) r.internalServerError = func(c *Context, err error) { c.index = 0 c.handlers = handlers c.Map(err) c.run() } } func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if t, ok := r.routers[req.Method]; ok { h, p, ok := t.Match(req.URL.Path) if ok { if splat, ok := p["*0"]; ok { p["*"] = splat // Easy name. } h(rw, req, p) return } } r.notFound(rw, req) } // URLFor builds path part of URL by given pair values. func (r *Router) URLFor(name string, pairs ...string) string { leaf, ok := r.namedRoutes[name] if !ok { panic("route with given name does not exists: " + name) } return leaf.URLPath(pairs...) } // ComboRouter represents a combo router. type ComboRouter struct { router *Router pattern string handlers []Handler methods map[string]bool // Registered methods. lastRoute *Route } func (cr *ComboRouter) checkMethod(name string) { if cr.methods[name] { panic("method '" + name + "' has already been registered") } cr.methods[name] = true } func (cr *ComboRouter) route(fn func(string, ...Handler) *Route, method string, h ...Handler) *ComboRouter { cr.checkMethod(method) cr.lastRoute = fn(cr.pattern, append(cr.handlers, h...)...) return cr } func (cr *ComboRouter) Get(h ...Handler) *ComboRouter { return cr.route(cr.router.Get, "GET", h...) } func (cr *ComboRouter) Patch(h ...Handler) *ComboRouter { return cr.route(cr.router.Patch, "PATCH", h...) } func (cr *ComboRouter) Post(h ...Handler) *ComboRouter { return cr.route(cr.router.Post, "POST", h...) } func (cr *ComboRouter) Put(h ...Handler) *ComboRouter { return cr.route(cr.router.Put, "PUT", h...) } func (cr *ComboRouter) Delete(h ...Handler) *ComboRouter { return cr.route(cr.router.Delete, "DELETE", h...) } func (cr *ComboRouter) Options(h ...Handler) *ComboRouter { return cr.route(cr.router.Options, "OPTIONS", h...) } func (cr *ComboRouter) Head(h ...Handler) *ComboRouter { return cr.route(cr.router.Head, "HEAD", h...) } // Name sets name of ComboRouter route. func (cr *ComboRouter) Name(name string) { if cr.lastRoute == nil { panic("no corresponding route to be named") } cr.lastRoute.Name(name) } macaron-1/router_test.go000066400000000000000000000200751265413170400155510ustar00rootroot00000000000000// Copyright 2014 The Macaron Authors // // 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 macaron import ( "errors" "net/http" "net/http/httptest" "testing" . "github.com/smartystreets/goconvey/convey" ) func Test_Router_Handle(t *testing.T) { Convey("Register all HTTP methods routes", t, func() { m := New() m.Get("/get", func() string { return "GET" }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/get", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "GET") m.Patch("/patch", func() string { return "PATCH" }) resp = httptest.NewRecorder() req, err = http.NewRequest("PATCH", "/patch", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "PATCH") m.Post("/post", func() string { return "POST" }) resp = httptest.NewRecorder() req, err = http.NewRequest("POST", "/post", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "POST") m.Put("/put", func() string { return "PUT" }) resp = httptest.NewRecorder() req, err = http.NewRequest("PUT", "/put", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "PUT") m.Delete("/delete", func() string { return "DELETE" }) resp = httptest.NewRecorder() req, err = http.NewRequest("DELETE", "/delete", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "DELETE") m.Options("/options", func() string { return "OPTIONS" }) resp = httptest.NewRecorder() req, err = http.NewRequest("OPTIONS", "/options", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "OPTIONS") m.Head("/head", func() string { return "HEAD" }) resp = httptest.NewRecorder() req, err = http.NewRequest("HEAD", "/head", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "HEAD") m.Any("/any", func() string { return "ANY" }) resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "/any", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "ANY") m.Route("/route", "GET,POST", func() string { return "ROUTE" }) resp = httptest.NewRecorder() req, err = http.NewRequest("POST", "/route", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "ROUTE") }) Convey("Register with or without auto head", t, func() { Convey("Without auto head", func() { m := New() m.Get("/", func() string { return "GET" }) resp := httptest.NewRecorder() req, err := http.NewRequest("HEAD", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, 404) }) Convey("With auto head", func() { m := New() m.SetAutoHead(true) m.Get("/", func() string { return "GET" }) resp := httptest.NewRecorder() req, err := http.NewRequest("HEAD", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, 200) }) }) Convey("Register all HTTP methods routes with combo", t, func() { m := New() m.SetURLPrefix("/prefix") m.Use(Renderer()) m.Combo("/", func(ctx *Context) { ctx.Data["prefix"] = "Prefix_" }). Get(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "GET" }). Patch(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "PATCH" }). Post(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "POST" }). Put(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "PUT" }). Delete(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "DELETE" }). Options(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "OPTIONS" }). Head(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "HEAD" }) for name := range _HTTP_METHODS { resp := httptest.NewRecorder() req, err := http.NewRequest(name, "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "Prefix_"+name) } defer func() { So(recover(), ShouldNotBeNil) }() m.Combo("/").Get(func() {}).Get(nil) }) Convey("Register duplicated routes", t, func() { r := NewRouter() r.Get("/") r.Get("/") }) Convey("Register invalid HTTP method", t, func() { defer func() { So(recover(), ShouldNotBeNil) }() r := NewRouter() r.Handle("404", "/", nil) }) } func Test_Route_Name(t *testing.T) { Convey("Set route name", t, func() { m := New() m.Get("/", func() {}).Name("home") defer func() { So(recover(), ShouldNotBeNil) }() m.Get("/", func() {}).Name("home") }) Convey("Set combo router name", t, func() { m := New() m.Combo("/").Get(func() {}).Name("home") defer func() { So(recover(), ShouldNotBeNil) }() m.Combo("/").Name("home") }) } func Test_Router_URLFor(t *testing.T) { Convey("Build URL path", t, func() { m := New() m.Get("/user/:id", func() {}).Name("user_id") m.Get("/user/:id/:name", func() {}).Name("user_id_name") m.Get("cms_:id_:page.html", func() {}).Name("id_page") So(m.URLFor("user_id", "id", "12"), ShouldEqual, "/user/12") So(m.URLFor("user_id_name", "id", "12", "name", "unknwon"), ShouldEqual, "/user/12/unknwon") So(m.URLFor("id_page", "id", "12", "page", "profile"), ShouldEqual, "/cms_12_profile.html") Convey("Number of pair values does not match", func() { defer func() { So(recover(), ShouldNotBeNil) }() m.URLFor("user_id", "id") }) Convey("Empty pair value", func() { defer func() { So(recover(), ShouldNotBeNil) }() m.URLFor("user_id", "", "") }) Convey("Empty route name", func() { defer func() { So(recover(), ShouldNotBeNil) }() m.Get("/user/:id", func() {}).Name("") }) Convey("Invalid route name", func() { defer func() { So(recover(), ShouldNotBeNil) }() m.URLFor("404") }) }) } func Test_Router_Group(t *testing.T) { Convey("Register route group", t, func() { m := New() m.Group("/api", func() { m.Group("/v1", func() { m.Get("/list", func() string { return "Well done!" }) }) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/api/v1/list", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "Well done!") }) } func Test_Router_NotFound(t *testing.T) { Convey("Custom not found handler", t, func() { m := New() m.Get("/", func() {}) m.NotFound(func() string { return "Custom not found" }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/404", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "Custom not found") }) } func Test_Router_InternalServerError(t *testing.T) { Convey("Custom internal server error handler", t, func() { m := New() m.Get("/", func() error { return errors.New("Custom internal server error") }) m.InternalServerError(func(rw http.ResponseWriter, err error) { rw.WriteHeader(500) rw.Write([]byte(err.Error())) }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, 500) So(resp.Body.String(), ShouldEqual, "Custom internal server error") }) } func Test_Router_splat(t *testing.T) { Convey("Register router with glob", t, func() { m := New() m.Get("/*", func(ctx *Context) string { return ctx.Params("*") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/hahaha", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Body.String(), ShouldEqual, "hahaha") }) } macaron-1/static.go000066400000000000000000000117711265413170400144640ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "log" "net/http" "path" "path/filepath" "strings" "sync" ) // StaticOptions is a struct for specifying configuration options for the macaron.Static middleware. type StaticOptions struct { // Prefix is the optional prefix used to serve the static directory content Prefix string // SkipLogging will disable [Static] log messages when a static file is served. SkipLogging bool // IndexFile defines which file to serve as index if it exists. IndexFile string // Expires defines which user-defined function to use for producing a HTTP Expires Header // https://developers.google.com/speed/docs/insights/LeverageBrowserCaching Expires func() string // FileSystem is the interface for supporting any implmentation of file system. FileSystem http.FileSystem } // FIXME: to be deleted. type staticMap struct { lock sync.RWMutex data map[string]*http.Dir } func (sm *staticMap) Set(dir *http.Dir) { sm.lock.Lock() defer sm.lock.Unlock() sm.data[string(*dir)] = dir } func (sm *staticMap) Get(name string) *http.Dir { sm.lock.RLock() defer sm.lock.RUnlock() return sm.data[name] } func (sm *staticMap) Delete(name string) { sm.lock.Lock() defer sm.lock.Unlock() delete(sm.data, name) } var statics = staticMap{sync.RWMutex{}, map[string]*http.Dir{}} // staticFileSystem implements http.FileSystem interface. type staticFileSystem struct { dir *http.Dir } func newStaticFileSystem(directory string) staticFileSystem { if !filepath.IsAbs(directory) { directory = filepath.Join(Root, directory) } dir := http.Dir(directory) statics.Set(&dir) return staticFileSystem{&dir} } func (fs staticFileSystem) Open(name string) (http.File, error) { return fs.dir.Open(name) } func prepareStaticOption(dir string, opt StaticOptions) StaticOptions { // Defaults if len(opt.IndexFile) == 0 { opt.IndexFile = "index.html" } // Normalize the prefix if provided if opt.Prefix != "" { // Ensure we have a leading '/' if opt.Prefix[0] != '/' { opt.Prefix = "/" + opt.Prefix } // Remove any trailing '/' opt.Prefix = strings.TrimRight(opt.Prefix, "/") } if opt.FileSystem == nil { opt.FileSystem = newStaticFileSystem(dir) } return opt } func prepareStaticOptions(dir string, options []StaticOptions) StaticOptions { var opt StaticOptions if len(options) > 0 { opt = options[0] } return prepareStaticOption(dir, opt) } func staticHandler(ctx *Context, log *log.Logger, opt StaticOptions) bool { if ctx.Req.Method != "GET" && ctx.Req.Method != "HEAD" { return false } file := ctx.Req.URL.Path // if we have a prefix, filter requests by stripping the prefix if opt.Prefix != "" { if !strings.HasPrefix(file, opt.Prefix) { return false } file = file[len(opt.Prefix):] if file != "" && file[0] != '/' { return false } } f, err := opt.FileSystem.Open(file) if err != nil { return false } defer f.Close() fi, err := f.Stat() if err != nil { return true // File exists but fail to open. } // Try to serve index file if fi.IsDir() { // Redirect if missing trailing slash. if !strings.HasSuffix(ctx.Req.URL.Path, "/") { http.Redirect(ctx.Resp, ctx.Req.Request, ctx.Req.URL.Path+"/", http.StatusFound) return true } file = path.Join(file, opt.IndexFile) f, err = opt.FileSystem.Open(file) if err != nil { return false // Discard error. } defer f.Close() fi, err = f.Stat() if err != nil || fi.IsDir() { return true } } if !opt.SkipLogging { log.Println("[Static] Serving " + file) } // Add an Expires header to the static content if opt.Expires != nil { ctx.Resp.Header().Set("Expires", opt.Expires()) } http.ServeContent(ctx.Resp, ctx.Req.Request, file, fi.ModTime(), f) return true } // Static returns a middleware handler that serves static files in the given directory. func Static(directory string, staticOpt ...StaticOptions) Handler { opt := prepareStaticOptions(directory, staticOpt) return func(ctx *Context, log *log.Logger) { staticHandler(ctx, log, opt) } } // Statics registers multiple static middleware handlers all at once. func Statics(opt StaticOptions, dirs ...string) Handler { if len(dirs) == 0 { panic("no static directory is given") } opts := make([]StaticOptions, len(dirs)) for i := range dirs { opts[i] = prepareStaticOption(dirs[i], opt) } return func(ctx *Context, log *log.Logger) { for i := range opts { if staticHandler(ctx, log, opts[i]) { return } } } } macaron-1/static_test.go000066400000000000000000000160431265413170400155200ustar00rootroot00000000000000// Copyright 2013 Martini Authors // Copyright 2014 The Macaron Authors // // 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 macaron import ( "bytes" "io/ioutil" "net/http" "net/http/httptest" "os" "path" "strings" "testing" . "github.com/smartystreets/goconvey/convey" ) var currentRoot, _ = os.Getwd() func Test_Static(t *testing.T) { Convey("Serve static files", t, func() { m := New() m.Use(Static("./")) resp := httptest.NewRecorder() resp.Body = new(bytes.Buffer) req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get("Expires"), ShouldBeBlank) So(resp.Body.Len(), ShouldBeGreaterThan, 0) Convey("Change static path", func() { m.Get("/", func(ctx *Context) { ctx.ChangeStaticPath("./", "fixtures/basic2") }) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) resp = httptest.NewRecorder() resp.Body = new(bytes.Buffer) req, err = http.NewRequest("GET", "http://localhost:4000/hello.tmpl", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get("Expires"), ShouldBeBlank) So(resp.Body.Len(), ShouldBeGreaterThan, 0) }) }) Convey("Serve static files with local path", t, func() { Root = os.TempDir() f, err := ioutil.TempFile(Root, "static_content") So(err, ShouldBeNil) f.WriteString("Expected Content") f.Close() m := New() m.Use(Static(".")) resp := httptest.NewRecorder() resp.Body = new(bytes.Buffer) req, err := http.NewRequest("GET", "http://localhost:4000/"+path.Base(strings.Replace(f.Name(), "\\", "/", -1)), nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Header().Get("Expires"), ShouldBeBlank) So(resp.Body.String(), ShouldEqual, "Expected Content") }) Convey("Serve static files with head", t, func() { m := New() m.Use(Static(currentRoot)) resp := httptest.NewRecorder() resp.Body = new(bytes.Buffer) req, err := http.NewRequest("HEAD", "http://localhost:4000/macaron.go", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(resp.Body.Len(), ShouldEqual, 0) }) Convey("Serve static files as post", t, func() { m := New() m.Use(Static(currentRoot)) resp := httptest.NewRecorder() req, err := http.NewRequest("POST", "http://localhost:4000/macaron.go", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusNotFound) }) Convey("Serve static files with bad directory", t, func() { m := Classic() resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldNotEqual, http.StatusOK) }) } func Test_Static_Options(t *testing.T) { Convey("Serve static files with options logging", t, func() { var buf bytes.Buffer m := NewWithLogger(&buf) opt := StaticOptions{} m.Use(Static(currentRoot, opt)) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n") // Not disable logging. m.Handlers() buf.Reset() opt.SkipLogging = true m.Use(Static(currentRoot, opt)) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(buf.Len(), ShouldEqual, 0) }) Convey("Serve static files with options serve index", t, func() { var buf bytes.Buffer m := NewWithLogger(&buf) opt := StaticOptions{IndexFile: "macaron.go"} m.Use(Static(currentRoot, opt)) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://localhost:4000/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n") }) Convey("Serve static files with options prefix", t, func() { var buf bytes.Buffer m := NewWithLogger(&buf) opt := StaticOptions{Prefix: "public"} m.Use(Static(currentRoot, opt)) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://localhost:4000/public/macaron.go", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n") }) Convey("Serve static files with options expires", t, func() { var buf bytes.Buffer m := NewWithLogger(&buf) opt := StaticOptions{Expires: func() string { return "46" }} m.Use(Static(currentRoot, opt)) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Header().Get("Expires"), ShouldEqual, "46") }) } func Test_Static_Redirect(t *testing.T) { Convey("Serve static files with redirect", t, func() { m := New() m.Use(Static(currentRoot, StaticOptions{Prefix: "/public"})) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://localhost:4000/public", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusFound) So(resp.Header().Get("Location"), ShouldEqual, "/public/") }) } func Test_Statics(t *testing.T) { Convey("Serve multiple static routers", t, func() { Convey("Register empty directory", func() { defer func() { So(recover(), ShouldNotBeNil) }() m := New() m.Use(Statics(StaticOptions{})) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://localhost:4000/", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) }) Convey("Serve normally", func() { var buf bytes.Buffer m := NewWithLogger(&buf) m.Use(Statics(StaticOptions{}, currentRoot, currentRoot+"/fixtures/basic")) resp := httptest.NewRecorder() req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n") resp = httptest.NewRecorder() req, err = http.NewRequest("GET", "http://localhost:4000/admin/index.tmpl", nil) So(err, ShouldBeNil) m.ServeHTTP(resp, req) So(resp.Code, ShouldEqual, http.StatusOK) So(buf.String(), ShouldEndWith, "[Macaron] [Static] Serving /admin/index.tmpl\n") }) }) } macaron-1/tree.go000066400000000000000000000237161265413170400141360ustar00rootroot00000000000000// Copyright 2015 The Macaron Authors // // 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 macaron import ( "regexp" "strings" "github.com/Unknwon/com" ) type patternType int8 const ( _PATTERN_STATIC patternType = iota // /home _PATTERN_REGEXP // /:id([0-9]+) _PATTERN_PATH_EXT // /*.* _PATTERN_HOLDER // /:user _PATTERN_MATCH_ALL // /* ) // Leaf represents a leaf route information. type Leaf struct { parent *Tree typ patternType pattern string rawPattern string // Contains wildcard instead of regexp wildcards []string reg *regexp.Regexp optional bool handle Handle } var wildcardPattern = regexp.MustCompile(`:[a-zA-Z0-9]+`) func isSpecialRegexp(pattern, regStr string, pos []int) bool { return len(pattern) >= pos[1]+len(regStr) && pattern[pos[1]:pos[1]+len(regStr)] == regStr } // getNextWildcard tries to find next wildcard and update pattern with corresponding regexp. func getNextWildcard(pattern string) (wildcard, _ string) { pos := wildcardPattern.FindStringIndex(pattern) if pos == nil { return "", pattern } wildcard = pattern[pos[0]:pos[1]] // Reach last character or no regexp is given. if len(pattern) == pos[1] { return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1) } else if pattern[pos[1]] != '(' { switch { case isSpecialRegexp(pattern, ":int", pos): pattern = strings.Replace(pattern, ":int", "([0-9]+)", 1) case isSpecialRegexp(pattern, ":string", pos): pattern = strings.Replace(pattern, ":string", "([\\w]+)", 1) default: return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1) } } // Cut out placeholder directly. return wildcard, pattern[:pos[0]] + pattern[pos[1]:] } func getWildcards(pattern string) (string, []string) { wildcards := make([]string, 0, 2) // Keep getting next wildcard until nothing is left. var wildcard string for { wildcard, pattern = getNextWildcard(pattern) if len(wildcard) > 0 { wildcards = append(wildcards, wildcard) } else { break } } return pattern, wildcards } // getRawPattern removes all regexp but keeps wildcards for building URL path. func getRawPattern(rawPattern string) string { rawPattern = strings.Replace(rawPattern, ":int", "", -1) rawPattern = strings.Replace(rawPattern, ":string", "", -1) for { startIdx := strings.Index(rawPattern, "(") if startIdx == -1 { break } closeIdx := strings.Index(rawPattern, ")") if closeIdx > -1 { rawPattern = rawPattern[:startIdx] + rawPattern[closeIdx+1:] } } return rawPattern } func checkPattern(pattern string) (typ patternType, rawPattern string, wildcards []string, reg *regexp.Regexp) { pattern = strings.TrimLeft(pattern, "?") rawPattern = getRawPattern(pattern) if pattern == "*" { typ = _PATTERN_MATCH_ALL } else if pattern == "*.*" { typ = _PATTERN_PATH_EXT } else if strings.Contains(pattern, ":") { typ = _PATTERN_REGEXP pattern, wildcards = getWildcards(pattern) if pattern == "(.+)" { typ = _PATTERN_HOLDER } else { reg = regexp.MustCompile(pattern) } } return typ, rawPattern, wildcards, reg } func NewLeaf(parent *Tree, pattern string, handle Handle) *Leaf { typ, rawPattern, wildcards, reg := checkPattern(pattern) optional := false if len(pattern) > 0 && pattern[0] == '?' { optional = true } return &Leaf{parent, typ, pattern, rawPattern, wildcards, reg, optional, handle} } // URLPath build path part of URL by given pair values. func (l *Leaf) URLPath(pairs ...string) string { if len(pairs)%2 != 0 { panic("number of pairs does not match") } urlPath := l.rawPattern parent := l.parent for parent != nil { urlPath = parent.rawPattern + "/" + urlPath parent = parent.parent } for i := 0; i < len(pairs); i += 2 { if len(pairs[i]) == 0 { panic("pair value cannot be empty: " + com.ToStr(i)) } else if pairs[i][0] != ':' && pairs[i] != "*" && pairs[i] != "*.*" { pairs[i] = ":" + pairs[i] } urlPath = strings.Replace(urlPath, pairs[i], pairs[i+1], 1) } return urlPath } // Tree represents a router tree in Macaron. type Tree struct { parent *Tree typ patternType pattern string rawPattern string wildcards []string reg *regexp.Regexp subtrees []*Tree leaves []*Leaf } func NewSubtree(parent *Tree, pattern string) *Tree { typ, rawPattern, wildcards, reg := checkPattern(pattern) return &Tree{parent, typ, pattern, rawPattern, wildcards, reg, make([]*Tree, 0, 5), make([]*Leaf, 0, 5)} } func NewTree() *Tree { return NewSubtree(nil, "") } func (t *Tree) addLeaf(pattern string, handle Handle) *Leaf { for i := 0; i < len(t.leaves); i++ { if t.leaves[i].pattern == pattern { return t.leaves[i] } } leaf := NewLeaf(t, pattern, handle) // Add exact same leaf to grandparent/parent level without optional. if leaf.optional { parent := leaf.parent if parent.parent != nil { parent.parent.addLeaf(parent.pattern, handle) } else { parent.addLeaf("", handle) // Root tree can add as empty pattern. } } i := 0 for ; i < len(t.leaves); i++ { if leaf.typ < t.leaves[i].typ { break } } if i == len(t.leaves) { t.leaves = append(t.leaves, leaf) } else { t.leaves = append(t.leaves[:i], append([]*Leaf{leaf}, t.leaves[i:]...)...) } return leaf } func (t *Tree) addSubtree(segment, pattern string, handle Handle) *Leaf { for i := 0; i < len(t.subtrees); i++ { if t.subtrees[i].pattern == segment { return t.subtrees[i].addNextSegment(pattern, handle) } } subtree := NewSubtree(t, segment) i := 0 for ; i < len(t.subtrees); i++ { if subtree.typ < t.subtrees[i].typ { break } } if i == len(t.subtrees) { t.subtrees = append(t.subtrees, subtree) } else { t.subtrees = append(t.subtrees[:i], append([]*Tree{subtree}, t.subtrees[i:]...)...) } return subtree.addNextSegment(pattern, handle) } func (t *Tree) addNextSegment(pattern string, handle Handle) *Leaf { pattern = strings.TrimPrefix(pattern, "/") i := strings.Index(pattern, "/") if i == -1 { return t.addLeaf(pattern, handle) } return t.addSubtree(pattern[:i], pattern[i+1:], handle) } func (t *Tree) Add(pattern string, handle Handle) *Leaf { pattern = strings.TrimSuffix(pattern, "/") return t.addNextSegment(pattern, handle) } func (t *Tree) matchLeaf(globLevel int, url string, params Params) (Handle, bool) { for i := 0; i < len(t.leaves); i++ { switch t.leaves[i].typ { case _PATTERN_STATIC: if t.leaves[i].pattern == url { return t.leaves[i].handle, true } case _PATTERN_REGEXP: results := t.leaves[i].reg.FindStringSubmatch(url) // Number of results and wildcasrd should be exact same. if len(results)-1 != len(t.leaves[i].wildcards) { break } for j := 0; j < len(t.leaves[i].wildcards); j++ { params[t.leaves[i].wildcards[j]] = results[j+1] } return t.leaves[i].handle, true case _PATTERN_PATH_EXT: j := strings.LastIndex(url, ".") if j > -1 { params[":path"] = url[:j] params[":ext"] = url[j+1:] } else { params[":path"] = url } return t.leaves[i].handle, true case _PATTERN_HOLDER: params[t.leaves[i].wildcards[0]] = url return t.leaves[i].handle, true case _PATTERN_MATCH_ALL: params["*"] = url params["*"+com.ToStr(globLevel)] = url return t.leaves[i].handle, true } } return nil, false } func (t *Tree) matchSubtree(globLevel int, segment, url string, params Params) (Handle, bool) { for i := 0; i < len(t.subtrees); i++ { switch t.subtrees[i].typ { case _PATTERN_STATIC: if t.subtrees[i].pattern == segment { if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok { return handle, true } } case _PATTERN_REGEXP: results := t.subtrees[i].reg.FindStringSubmatch(segment) if len(results)-1 != len(t.subtrees[i].wildcards) { break } for j := 0; j < len(t.subtrees[i].wildcards); j++ { params[t.subtrees[i].wildcards[j]] = results[j+1] } if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok { return handle, true } case _PATTERN_HOLDER: if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok { params[t.subtrees[i].wildcards[0]] = segment return handle, true } case _PATTERN_MATCH_ALL: if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok { params["*"+com.ToStr(globLevel)] = segment return handle, true } } } if len(t.leaves) > 0 { leaf := t.leaves[len(t.leaves)-1] if leaf.typ == _PATTERN_PATH_EXT { url = segment + "/" + url j := strings.LastIndex(url, ".") if j > -1 { params[":path"] = url[:j] params[":ext"] = url[j+1:] } else { params[":path"] = url } return leaf.handle, true } else if leaf.typ == _PATTERN_MATCH_ALL { params["*"] = segment + "/" + url params["*"+com.ToStr(globLevel)] = segment + "/" + url return leaf.handle, true } } return nil, false } func (t *Tree) matchNextSegment(globLevel int, url string, params Params) (Handle, bool) { i := strings.Index(url, "/") if i == -1 { return t.matchLeaf(globLevel, url, params) } return t.matchSubtree(globLevel, url[:i], url[i+1:], params) } func (t *Tree) Match(url string) (Handle, Params, bool) { url = strings.TrimPrefix(url, "/") url = strings.TrimSuffix(url, "/") params := make(Params) handle, ok := t.matchNextSegment(0, url, params) return handle, params, ok } // MatchTest returns true if given URL is matched by given pattern. func MatchTest(pattern, url string) bool { t := NewTree() t.Add(pattern, nil) _, _, ok := t.Match(url) return ok } macaron-1/tree_test.go000066400000000000000000000166721265413170400152000ustar00rootroot00000000000000// Copyright 2015 The Macaron Authors // // 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 macaron import ( "strings" "testing" . "github.com/smartystreets/goconvey/convey" ) func Test_getWildcards(t *testing.T) { type result struct { pattern string wildcards string } cases := map[string]result{ "admin": result{"admin", ""}, ":id": result{"(.+)", ":id"}, ":id:int": result{"([0-9]+)", ":id"}, ":id([0-9]+)": result{"([0-9]+)", ":id"}, ":id([0-9]+)_:name": result{"([0-9]+)_(.+)", ":id :name"}, "cms_:id_:page.html": result{"cms_(.+)_(.+).html", ":id :page"}, "cms_:id:int_:page:string.html": result{"cms_([0-9]+)_([\\w]+).html", ":id :page"}, "*": result{"*", ""}, "*.*": result{"*.*", ""}, } Convey("Get wildcards", t, func() { for key, result := range cases { pattern, wildcards := getWildcards(key) So(pattern, ShouldEqual, result.pattern) So(strings.Join(wildcards, " "), ShouldEqual, result.wildcards) } }) } func Test_getRawPattern(t *testing.T) { cases := map[string]string{ "admin": "admin", ":id": ":id", ":id:int": ":id", ":id([0-9]+)": ":id", ":id([0-9]+)_:name": ":id_:name", "cms_:id_:page.html": "cms_:id_:page.html", "cms_:id:int_:page:string.html": "cms_:id_:page.html", "cms_:id([0-9]+)_:page([\\w]+).html": "cms_:id_:page.html", "*": "*", "*.*": "*.*", } Convey("Get raw pattern", t, func() { for k, v := range cases { So(getRawPattern(k), ShouldEqual, v) } }) } func Test_Tree_Match(t *testing.T) { Convey("Match route in tree", t, func() { Convey("Match static routes", func() { t := NewTree() So(t.Add("/", nil), ShouldNotBeNil) So(t.Add("/user", nil), ShouldNotBeNil) So(t.Add("/user/unknwon", nil), ShouldNotBeNil) So(t.Add("/user/unknwon/profile", nil), ShouldNotBeNil) So(t.Add("/", nil), ShouldNotBeNil) _, _, ok := t.Match("/") So(ok, ShouldBeTrue) _, _, ok = t.Match("/user") So(ok, ShouldBeTrue) _, _, ok = t.Match("/user/unknwon") So(ok, ShouldBeTrue) _, _, ok = t.Match("/user/unknwon/profile") So(ok, ShouldBeTrue) _, _, ok = t.Match("/404") So(ok, ShouldBeFalse) }) Convey("Match optional routes", func() { t := NewTree() So(t.Add("/?:user", nil), ShouldNotBeNil) So(t.Add("/user/?:name", nil), ShouldNotBeNil) So(t.Add("/user/list/?:page:int", nil), ShouldNotBeNil) _, params, ok := t.Match("/") So(ok, ShouldBeTrue) So(params[":user"], ShouldBeEmpty) _, params, ok = t.Match("/unknwon") So(ok, ShouldBeTrue) So(params[":user"], ShouldEqual, "unknwon") _, params, ok = t.Match("/user") So(ok, ShouldBeTrue) So(params[":name"], ShouldBeEmpty) _, params, ok = t.Match("/user/unknwon") So(ok, ShouldBeTrue) So(params[":name"], ShouldEqual, "unknwon") _, params, ok = t.Match("/user/list/") So(ok, ShouldBeTrue) So(params[":page"], ShouldBeEmpty) _, params, ok = t.Match("/user/list/123") So(ok, ShouldBeTrue) So(params[":page"], ShouldEqual, "123") }) Convey("Match with regexp", func() { t := NewTree() So(t.Add("/v1/:year:int/6/23", nil), ShouldNotBeNil) So(t.Add("/v2/2015/:month:int/23", nil), ShouldNotBeNil) So(t.Add("/v3/2015/6/:day:int", nil), ShouldNotBeNil) _, params, ok := t.Match("/v1/2015/6/23") So(ok, ShouldBeTrue) So(MatchTest("/v1/:year:int/6/23", "/v1/2015/6/23"), ShouldBeTrue) So(params[":year"], ShouldEqual, "2015") _, _, ok = t.Match("/v1/year/6/23") So(ok, ShouldBeFalse) So(MatchTest("/v1/:year:int/6/23", "/v1/year/6/23"), ShouldBeFalse) _, params, ok = t.Match("/v2/2015/6/23") So(ok, ShouldBeTrue) So(params[":month"], ShouldEqual, "6") _, _, ok = t.Match("/v2/2015/month/23") So(ok, ShouldBeFalse) _, params, ok = t.Match("/v3/2015/6/23") So(ok, ShouldBeTrue) So(params[":day"], ShouldEqual, "23") _, _, ok = t.Match("/v2/2015/6/day") So(ok, ShouldBeFalse) So(t.Add("/v1/shop/cms_:id(.+)_:page(.+).html", nil), ShouldNotBeNil) So(t.Add("/v1/:v/cms/aaa_:id(.+)_:page(.+).html", nil), ShouldNotBeNil) So(t.Add("/v1/:v/cms_:id(.+)_:page(.+).html", nil), ShouldNotBeNil) So(t.Add("/v1/:v(.+)_cms/ttt_:id(.+)_:page:string.html", nil), ShouldNotBeNil) _, params, ok = t.Match("/v1/shop/cms_123_1.html") So(ok, ShouldBeTrue) So(params[":id"], ShouldEqual, "123") So(params[":page"], ShouldEqual, "1") _, params, ok = t.Match("/v1/2/cms/aaa_124_2.html") So(ok, ShouldBeTrue) So(params[":v"], ShouldEqual, "2") So(params[":id"], ShouldEqual, "124") So(params[":page"], ShouldEqual, "2") _, params, ok = t.Match("/v1/3/cms_125_3.html") So(ok, ShouldBeTrue) So(params[":v"], ShouldEqual, "3") So(params[":id"], ShouldEqual, "125") So(params[":page"], ShouldEqual, "3") _, params, ok = t.Match("/v1/4_cms/ttt_126_4.html") So(ok, ShouldBeTrue) So(params[":v"], ShouldEqual, "4") So(params[":id"], ShouldEqual, "126") So(params[":page"], ShouldEqual, "4") }) Convey("Match with path and extension", func() { t := NewTree() So(t.Add("/*.*", nil), ShouldNotBeNil) So(t.Add("/docs/*.*", nil), ShouldNotBeNil) _, params, ok := t.Match("/profile.html") So(ok, ShouldBeTrue) So(params[":path"], ShouldEqual, "profile") So(params[":ext"], ShouldEqual, "html") _, params, ok = t.Match("/profile") So(ok, ShouldBeTrue) So(params[":path"], ShouldEqual, "profile") So(params[":ext"], ShouldBeEmpty) _, params, ok = t.Match("/docs/framework/manual.html") So(ok, ShouldBeTrue) So(params[":path"], ShouldEqual, "framework/manual") So(params[":ext"], ShouldEqual, "html") _, params, ok = t.Match("/docs/framework/manual") So(ok, ShouldBeTrue) So(params[":path"], ShouldEqual, "framework/manual") So(params[":ext"], ShouldBeEmpty) }) Convey("Match all", func() { t := NewTree() So(t.Add("/*", nil), ShouldNotBeNil) So(t.Add("/*/123", nil), ShouldNotBeNil) So(t.Add("/*/123/*", nil), ShouldNotBeNil) So(t.Add("/*/*/123", nil), ShouldNotBeNil) _, params, ok := t.Match("/1/2/3") So(ok, ShouldBeTrue) So(params["*0"], ShouldEqual, "1/2/3") _, params, ok = t.Match("/4/123") So(ok, ShouldBeTrue) So(params["*0"], ShouldEqual, "4") _, params, ok = t.Match("/5/123/6") So(ok, ShouldBeTrue) So(params["*0"], ShouldEqual, "5") So(params["*1"], ShouldEqual, "6") _, params, ok = t.Match("/7/8/123") So(ok, ShouldBeTrue) So(params["*0"], ShouldEqual, "7") So(params["*1"], ShouldEqual, "8") }) Convey("Complex tests", func() { t := NewTree() So(t.Add("/:username/:reponame/commit/*", nil), ShouldNotBeNil) _, params, ok := t.Match("/unknwon/com/commit/d855b6c9dea98c619925b7b112f3c4e64b17bfa8") So(ok, ShouldBeTrue) So(params["*"], ShouldEqual, "d855b6c9dea98c619925b7b112f3c4e64b17bfa8") }) }) }