├── .gitignore ├── HttpRouterLicense ├── LICENSE ├── README.md ├── _fixture ├── bindata_favicon.go ├── favicon.ico ├── index.html ├── syso │ ├── favicon.syso │ └── syso.go └── syso_windows.go ├── apihandler.go ├── apimiddleware.go ├── app.go ├── binder.go ├── config.go ├── config ├── config.go ├── fake.go ├── ini.go ├── ini_test.go ├── json.go ├── json_test.go ├── xml │ ├── x2j │ │ ├── LICENSE │ │ ├── README │ │ ├── atomFeedString.xml │ │ ├── examples │ │ │ ├── README │ │ │ ├── books.go │ │ │ ├── getmetrics.go │ │ │ ├── gonuts1.go │ │ │ ├── gonuts2.go │ │ │ └── gonuts3.go │ │ ├── goofy_test.go │ │ ├── pathTestString.xml │ │ ├── reader2j.go │ │ ├── reader2j_test.go │ │ ├── songTextString.xml │ │ ├── x2j.go │ │ ├── x2j_bulk.go │ │ ├── x2j_test.go │ │ ├── x2j_test.xml │ │ ├── x2j_valuesFrom.go │ │ ├── x2jpath_test.go │ │ ├── x2junmarshal_test.go │ │ ├── x2m_bulk.go │ │ └── x2m_bulk.xml │ ├── xml.go │ └── xml_test.go └── yaml │ ├── goyaml2 │ ├── .gitignore │ ├── README.md │ ├── conv.go │ ├── conv_test.go │ ├── errors.go │ ├── reader.go │ ├── reader_test.go │ ├── types.go │ └── writer.go │ ├── yaml.go │ └── yaml_test.go ├── context.go ├── filecache.go ├── group.go ├── helper.go ├── initialize.go ├── lessgo.go ├── logs ├── color │ ├── color.go │ ├── colorable_others.go │ ├── colorable_windows.go │ ├── isatty_appengine.go │ ├── isatty_bsd.go │ ├── isatty_linux.go │ ├── isatty_solaris.go │ └── isatty_windows.go ├── log.go └── logs │ ├── README.md │ ├── conn.go │ ├── conn_test.go │ ├── console.go │ ├── console_test.go │ ├── es │ └── es.go │ ├── file.go │ ├── file_test.go │ ├── log.go │ ├── logger.go │ ├── multifile.go │ ├── multifile_test.go │ ├── smtp.go │ ├── smtp_test.go │ └── utils.go ├── markdown ├── LICENSE.txt ├── README.md ├── block.go ├── block_test.go ├── github.css.go ├── github.go ├── html.go ├── inline.go ├── inline_test.go ├── latex.go ├── markdown.go ├── ref_test.go └── smartypants.go ├── mime.go ├── path.go ├── path_test.go ├── pongo2 ├── context.go ├── doc.go ├── error.go ├── filters.go ├── filters_builtin.go ├── helpers.go ├── lexer.go ├── nodes.go ├── nodes_html.go ├── nodes_wrapper.go ├── parser.go ├── parser_document.go ├── parser_expression.go ├── pongo2.go ├── tags.go ├── tags_autoescape.go ├── tags_block.go ├── tags_comment.go ├── tags_cycle.go ├── tags_extends.go ├── tags_filter.go ├── tags_firstof.go ├── tags_for.go ├── tags_if.go ├── tags_ifchanged.go ├── tags_ifequal.go ├── tags_ifnotequal.go ├── tags_import.go ├── tags_include.go ├── tags_lorem.go ├── tags_macro.go ├── tags_now.go ├── tags_set.go ├── tags_spaceless.go ├── tags_ssi.go ├── tags_templatetag.go ├── tags_widthratio.go ├── tags_with.go ├── template.go ├── template_loader.go ├── template_sets.go ├── value.go └── variable.go ├── render.go ├── response.go ├── router.go ├── router_test.go ├── session ├── README.md ├── couchbase │ └── sess_couchbase.go ├── ledis │ └── ledis_session.go ├── memcache │ └── sess_memcache.go ├── mysql │ └── sess_mysql.go ├── postgres │ └── sess_postgresql.go ├── redis │ └── sess_redis.go ├── sess_cookie.go ├── sess_cookie_test.go ├── sess_file.go ├── sess_mem.go ├── sess_mem_test.go ├── sess_test.go ├── sess_utils.go └── session.go ├── tree.go ├── tree_test.go ├── utils ├── caller.go ├── caller_test.go ├── debug.go ├── debug_test.go ├── file.go ├── file_test.go ├── hash.go ├── mail.go ├── mail_test.go ├── name.go ├── name_test.go ├── new.go ├── rand.go ├── safemap.go ├── safemap_test.go ├── slice.go ├── slice_test.go └── string2bytes.go ├── vendor └── github.com │ └── facebookgo │ ├── clock │ ├── LICENSE │ ├── README.md │ ├── clock.go │ └── clock_test.go │ ├── grace │ ├── .travis.yml │ ├── gracedemo │ │ └── demo.go │ ├── gracehttp │ │ ├── http.go │ │ ├── http_darwin.go │ │ ├── http_freebsd.go │ │ ├── http_linux.go │ │ ├── http_test.go │ │ ├── http_windows.go │ │ └── testbin_test.go │ ├── gracenet │ │ ├── net.go │ │ └── net_test.go │ ├── license │ ├── patents │ └── readme.md │ ├── httpdown │ ├── .travis.yml │ ├── httpdown.go │ ├── httpdown_example │ │ └── main.go │ ├── httpdown_test.go │ ├── license │ ├── patents │ └── readme.md │ └── stats │ ├── .travis.yml │ ├── aggregation.go │ ├── aggregation_test.go │ ├── counter.go │ ├── counter_test.go │ ├── license │ ├── patents │ ├── readme.md │ ├── stats.go │ ├── stats_test.go │ └── stopper.go ├── virtrouter.go └── websocket ├── client.go ├── hybi.go ├── server.go └── websocket.go /.gitignore: -------------------------------------------------------------------------------- 1 | /*.rar 2 | -------------------------------------------------------------------------------- /HttpRouterLicense: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Julien Schmidt. All rights reserved. 2 | 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * The names of the contributors may not be used to endorse or promote 12 | products derived from this software without specific prior written 13 | permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL JULIEN SCHMIDT BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Henrylee2cn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /_fixture/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andeya/lessgo/b9b2f5c9be14d2f6007c1ee2a1ce4654562be7bb/_fixture/favicon.ico -------------------------------------------------------------------------------- /_fixture/syso/favicon.syso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andeya/lessgo/b9b2f5c9be14d2f6007c1ee2a1ce4654562be7bb/_fixture/syso/favicon.syso -------------------------------------------------------------------------------- /_fixture/syso/syso.go: -------------------------------------------------------------------------------- 1 | package syso 2 | -------------------------------------------------------------------------------- /_fixture/syso_windows.go: -------------------------------------------------------------------------------- 1 | package _fixture 2 | 3 | import ( 4 | _ "github.com/henrylee2cn/lessgo/_fixture/syso" 5 | ) 6 | -------------------------------------------------------------------------------- /config/fake.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package config 16 | 17 | import ( 18 | "errors" 19 | "strconv" 20 | "strings" 21 | ) 22 | 23 | type fakeConfigContainer struct { 24 | data map[string]string 25 | } 26 | 27 | func (c *fakeConfigContainer) getData(key string) string { 28 | return c.data[strings.ToLower(key)] 29 | } 30 | 31 | func (c *fakeConfigContainer) Set(key, val string) error { 32 | c.data[strings.ToLower(key)] = val 33 | return nil 34 | } 35 | 36 | func (c *fakeConfigContainer) String(key string) string { 37 | return c.getData(key) 38 | } 39 | 40 | func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string { 41 | v := c.getData(key) 42 | if v == "" { 43 | return defaultval 44 | } 45 | return v 46 | } 47 | 48 | func (c *fakeConfigContainer) Strings(key string) []string { 49 | v := c.getData(key) 50 | if v == "" { 51 | return nil 52 | } 53 | return strings.Split(v, ";") 54 | } 55 | 56 | func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string { 57 | v := c.Strings(key) 58 | if v == nil { 59 | return defaultval 60 | } 61 | return v 62 | } 63 | 64 | func (c *fakeConfigContainer) Int(key string) (int, error) { 65 | return strconv.Atoi(c.getData(key)) 66 | } 67 | 68 | func (c *fakeConfigContainer) DefaultInt(key string, defaultval int) int { 69 | v, err := c.Int(key) 70 | if err != nil { 71 | return defaultval 72 | } 73 | return v 74 | } 75 | 76 | func (c *fakeConfigContainer) Int64(key string) (int64, error) { 77 | return strconv.ParseInt(c.getData(key), 10, 64) 78 | } 79 | 80 | func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 { 81 | v, err := c.Int64(key) 82 | if err != nil { 83 | return defaultval 84 | } 85 | return v 86 | } 87 | 88 | func (c *fakeConfigContainer) Bool(key string) (bool, error) { 89 | return ParseBool(c.getData(key)) 90 | } 91 | 92 | func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool { 93 | v, err := c.Bool(key) 94 | if err != nil { 95 | return defaultval 96 | } 97 | return v 98 | } 99 | 100 | func (c *fakeConfigContainer) Float(key string) (float64, error) { 101 | return strconv.ParseFloat(c.getData(key), 64) 102 | } 103 | 104 | func (c *fakeConfigContainer) DefaultFloat(key string, defaultval float64) float64 { 105 | v, err := c.Float(key) 106 | if err != nil { 107 | return defaultval 108 | } 109 | return v 110 | } 111 | 112 | func (c *fakeConfigContainer) DIY(key string) (interface{}, error) { 113 | if v, ok := c.data[strings.ToLower(key)]; ok { 114 | return v, nil 115 | } 116 | return nil, errors.New("key not find") 117 | } 118 | 119 | func (c *fakeConfigContainer) GetSection(section string) (map[string]string, error) { 120 | return nil, errors.New("not implement in the fakeConfigContainer") 121 | } 122 | 123 | func (c *fakeConfigContainer) SaveConfigFile(filename string) error { 124 | return errors.New("not implement in the fakeConfigContainer") 125 | } 126 | 127 | var _ Configer = new(fakeConfigContainer) 128 | 129 | // NewFakeConfig return a fake Congiger 130 | func NewFakeConfig() Configer { 131 | return &fakeConfigContainer{ 132 | data: make(map[string]string), 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /config/xml/x2j/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2013 Charles Banning . 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /config/xml/x2j/atomFeedString.xml: -------------------------------------------------------------------------------- 1 | 2 | Code Review - My issueshttp://codereview.appspot.com/rietveld<>rietveld: an attempt at pubsubhubbub 3 | 2009-10-04T01:35:58+00:00email-address-removedurn:md5:134d9179c41f806be79b3a5f7877d19a 4 | An attempt at adding pubsubhubbub support to Rietveld. 5 | http://code.google.com/p/pubsubhubbub 6 | http://code.google.com/p/rietveld/issues/detail?id=155 7 | 8 | The server side of the protocol is trivial: 9 | 1. add a &lt;link rel=&quot;hub&quot; href=&quot;hub-server&quot;&gt; tag to all 10 | feeds that will be pubsubhubbubbed. 11 | 2. every time one of those feeds changes, tell the hub 12 | with a simple POST request. 13 | 14 | I have tested this by adding debug prints to a local hub 15 | server and checking that the server got the right publish 16 | requests. 17 | 18 | I can&#39;t quite get the server to work, but I think the bug 19 | is not in my code. I think that the server expects to be 20 | able to grab the feed and see the feed&#39;s actual URL in 21 | the link rel=&quot;self&quot;, but the default value for that drops 22 | the :port from the URL, and I cannot for the life of me 23 | figure out how to get the Atom generator deep inside 24 | django not to do that, or even where it is doing that, 25 | or even what code is running to generate the Atom feed. 26 | (I thought I knew but I added some assert False statements 27 | and it kept running!) 28 | 29 | Ignoring that particular problem, I would appreciate 30 | feedback on the right way to get the two values at 31 | the top of feeds.py marked NOTE(rsc). 32 | 33 | 34 | rietveld: correct tab handling 35 | 2009-10-03T23:02:17+00:00email-address-removedurn:md5:0a2a4f19bb815101f0ba2904aed7c35a 36 | This fixes the buggy tab rendering that can be seen at 37 | http://codereview.appspot.com/116075/diff/1/2 38 | 39 | The fundamental problem was that the tab code was 40 | not being told what column the text began in, so it 41 | didn&#39;t know where to put the tab stops. Another problem 42 | was that some of the code assumed that string byte 43 | offsets were the same as column offsets, which is only 44 | true if there are no tabs. 45 | 46 | In the process of fixing this, I cleaned up the arguments 47 | to Fold and ExpandTabs and renamed them Break and 48 | _ExpandTabs so that I could be sure that I found all the 49 | call sites. I also wanted to verify that ExpandTabs was 50 | not being used from outside intra_region_diff.py. 51 | 52 | 53 | ` 54 | 55 | -------------------------------------------------------------------------------- /config/xml/x2j/examples/books.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/henrylee2cn/tconfig/xml/x2j" 6 | ) 7 | 8 | var doc = ` 9 | 10 | 11 | William H. Gaddis 12 | The Recognitions 13 | One of the great seminal American novels of the 20th century. 14 | 15 | 16 | Austin Tappan Wright 17 | Islandia 18 | An example of earlier 20th century American utopian fiction. 19 | 20 | 21 | John Hawkes 22 | The Beetle Leg 23 | A lyrical novel about the construction of Ft. Peck Dam in Montana. 24 | 25 | 26 | T.E. Porter 27 | King's Day 28 | A magical novella. 29 | 30 | 31 | ` 32 | 33 | func main() { 34 | fmt.Println(doc) 35 | 36 | v, _ := x2j.ValuesFromTagPath(doc, "books") 37 | fmt.Println("path: books; len(v):", len(v)) 38 | fmt.Printf("%+v\n\n", v) 39 | 40 | v, _ = x2j.ValuesFromTagPath(doc, "books.book") 41 | fmt.Println("path: books.book; len(v):", len(v)) 42 | fmt.Printf("%+v\n\n", v) 43 | 44 | v, _ = x2j.ValuesFromTagPath(doc, "books.*") 45 | fmt.Println("path: books.*; len(v):", len(v)) 46 | fmt.Printf("%+v\n\n", v) 47 | 48 | v, _ = x2j.ValuesFromTagPath(doc, "books.*.title") 49 | fmt.Println("path: books.*.title len(v):", len(v)) 50 | fmt.Printf("%+v\n\n", v) 51 | 52 | v, _ = x2j.ValuesFromTagPath(doc, "books.*.*") 53 | fmt.Println("path: books.*.*; len(v):", len(v)) 54 | fmt.Printf("%+v\n\n", v) 55 | } 56 | -------------------------------------------------------------------------------- /config/xml/x2j/examples/getmetrics.go: -------------------------------------------------------------------------------- 1 | // getmetrics.go - transform Eclipse Metrics (v3) XML report into CSV files for each metric 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "github.com/henrylee2cn/tconfig/xml/x2j" 9 | "os" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | var file string 15 | flag.StringVar(&file, "file", "", "file to process") 16 | flag.Parse() 17 | 18 | fh, fherr := os.Open(file) 19 | if fherr != nil { 20 | fmt.Println("fherr:", fherr.Error()) 21 | return 22 | } 23 | defer fh.Close() 24 | fs, _ := fh.Stat() 25 | fmt.Println(time.Now().String(), "... File Opened:", file) 26 | 27 | b := make([]byte, fs.Size()) 28 | n, frerr := fh.Read(b) 29 | if frerr != nil { 30 | fmt.Println("frerr:", frerr.Error()) 31 | return 32 | } 33 | if int64(n) != fs.Size() { 34 | fmt.Println("n:", n, "fs.Size():", fs.Size()) 35 | return 36 | } 37 | fmt.Println(time.Now().String(), "... File Read - size:", fs.Size()) 38 | 39 | m := make(map[string]interface{}, 0) 40 | merr := x2j.Unmarshal(b, &m) 41 | if merr != nil { 42 | fmt.Println("merr:", merr.Error()) 43 | return 44 | } 45 | fmt.Println(time.Now().String(), "... XML Unmarshaled - len:", len(m)) 46 | 47 | metricVals := x2j.ValuesFromKeyPath(m, "Metrics.Metric", true) 48 | fmt.Println(time.Now().String(), "... ValuesFromKeyPath - len:", len(metricVals)) 49 | 50 | for _, v := range metricVals { 51 | aMetricVal := v.(map[string]interface{}) 52 | id := aMetricVal["-id"].(string) 53 | desc := aMetricVal["-description"].(string) 54 | mf, mferr := os.OpenFile(id+".csv", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0666) 55 | if mferr != nil { 56 | fmt.Println("mferr:", mferr.Error()) 57 | return 58 | } 59 | fmt.Print(time.Now().String(), " id: ", id, " desc: ", desc) 60 | mf.WriteString(id + "," + desc + "\n") 61 | for key, val := range aMetricVal { 62 | switch key { 63 | case "Values": 64 | // extract the list of "Value" from map 65 | values := val.(map[string]interface{})["Value"].([]interface{}) 66 | fmt.Println(" len(Values):", len(values)) 67 | var gotKeys bool 68 | for _, vval := range values { 69 | valueEntry := vval.(map[string]interface{}) 70 | // extract keys as column header on first pass 71 | if !gotKeys { 72 | // print out the keys 73 | var gotFirstKey bool 74 | for kk, _ := range valueEntry { 75 | if gotFirstKey { 76 | mf.WriteString(",") 77 | } else { 78 | gotFirstKey = true 79 | } 80 | // strip prepended hyphen 81 | mf.WriteString(kk[1:]) 82 | } 83 | mf.WriteString("\n") 84 | gotKeys = true 85 | } 86 | // print out values 87 | var gotFirstVal bool 88 | for _, vv := range valueEntry { 89 | if gotFirstVal { 90 | mf.WriteString(",") 91 | } else { 92 | gotFirstVal = true 93 | } 94 | mf.WriteString(vv.(string)) 95 | } 96 | mf.WriteString("\n") 97 | } 98 | case "Value": 99 | vv := val.(map[string]interface{}) 100 | fmt.Println(" len(Value):", len(vv)) 101 | mf.WriteString("value\n" + vv["-value"].(string) + "\n") 102 | } 103 | } 104 | mf.Close() 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /config/xml/x2j/examples/gonuts1.go: -------------------------------------------------------------------------------- 1 | // https://groups.google.com/forum/#!searchin/golang-nuts/idnet$20netid/golang-nuts/guM3ZHHqSF0/K1pBpMqQSSwJ 2 | // http://play.golang.org/p/BFFDxphKYK 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "github.com/henrylee2cn/tconfig/xml/x2j" 9 | ) 10 | 11 | // demo how to compensate for irregular tag labels in data 12 | // "netid" vs. "idnet" 13 | var doc1 = ` 14 | 15 | 16 | 17 | no 18 | default:text 19 | default:word 20 | 21 | 22 | ` 23 | var doc2 = ` 24 | 25 | 26 | 27 | yes 28 | default:text 29 | default:word 30 | 31 | 32 | ` 33 | 34 | func main() { 35 | var docs = []string{doc1, doc2} 36 | 37 | for n, doc := range docs { 38 | fmt.Println("\nTestValuesFromTagPath2(), iteration:", n, "\n", doc) 39 | 40 | m, _ := x2j.DocToMap(doc) 41 | fmt.Println("map:", x2j.WriteMap(m)) 42 | 43 | v, _ := x2j.ValuesFromTagPath(doc, "data.*") 44 | fmt.Println("\npath == data.*: len(v):", len(v)) 45 | for key, val := range v { 46 | fmt.Println(key, ":", val) 47 | } 48 | mm := v[0] 49 | for key, val := range mm.(map[string]interface{}) { 50 | fmt.Println(key, ":", val) 51 | } 52 | 53 | v, _ = x2j.ValuesFromTagPath(doc, "data.*.*") 54 | fmt.Println("\npath == data.*.*: len(v):", len(v)) 55 | for key, val := range v { 56 | fmt.Println(key, ":", val) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /config/xml/x2j/examples/gonuts3.go: -------------------------------------------------------------------------------- 1 | // https://groups.google.com/forum/#!topic/golang-nuts/cok6xasvI3w 2 | // retrieve 'src' values from 'image' tags 3 | 4 | package main 5 | 6 | import ( 7 | "fmt" 8 | "github.com/henrylee2cn/tconfig/xml/x2j" 9 | ) 10 | 11 | var doc = ` 12 | 13 | 14 | 15 | 16 | something else 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ` 25 | 26 | func main() { 27 | // get all image tag values - []interface{} 28 | images, err := x2j.ValuesForTag(doc, "image") 29 | if err != nil { 30 | fmt.Println("error parsing doc:", err.Error()) 31 | return 32 | } 33 | 34 | sources := make([]string, 0) 35 | for _, v := range images { 36 | // ValuesForKey requires a map[string]interface{} value 37 | // as a starting point ... thinks its a doc 38 | m := make(map[string]interface{}, 1) 39 | m["dummy"] = v 40 | ss := x2j.ValuesForKey(m, "-src") 41 | for _, s := range ss { 42 | sources = append(sources, s.(string)) 43 | } 44 | } 45 | 46 | for _, src := range sources { 47 | fmt.Println(src) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /config/xml/x2j/goofy_test.go: -------------------------------------------------------------------------------- 1 | package x2j 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestGoofy(t *testing.T) { 10 | var doc = `` 11 | type goofy struct { 12 | S string 13 | Sp *string 14 | } 15 | g := new(goofy) 16 | g.S = "Now is the time for" 17 | tmp := "all good men to come to" 18 | g.Sp = &tmp 19 | 20 | m, err := DocToMap(doc) 21 | if err != nil { 22 | fmt.Println("err:", err.Error()) 23 | return 24 | } 25 | 26 | m["goofyVal"] = interface{}(g) 27 | m["byteVal"] = interface{}([]byte(`the aid of their country`)) 28 | m["nilVal"] = interface{}(nil) 29 | 30 | fmt.Println("\nTestGoofy ... MapToDoc:", m) 31 | var v []byte 32 | v, err = json.Marshal(m) 33 | if err != nil { 34 | fmt.Println("err:", err.Error()) 35 | } 36 | fmt.Println("v:", string(v)) 37 | 38 | type goofier struct { 39 | G *goofy 40 | B []byte 41 | N *string 42 | } 43 | gg := new(goofier) 44 | gg.G = g 45 | gg.B = []byte(`the tree of freedom must periodically be`) 46 | gg.N = nil 47 | m["goofierVal"] = interface{}(gg) 48 | 49 | fmt.Println("\nTestGoofier ... MapToDoc:", m) 50 | v, err = json.Marshal(m) 51 | if err != nil { 52 | fmt.Println("err:", err.Error()) 53 | } 54 | fmt.Println("v:", string(v)) 55 | } 56 | -------------------------------------------------------------------------------- /config/xml/x2j/pathTestString.xml: -------------------------------------------------------------------------------- 1 | 2 | 1 3 | 4 | 5 | A 6 | 7 | 8 | B 9 | 10 | 11 | C 12 | D 13 | 14 | <_> 15 | E 16 | 17 | 18 | 2 19 | 20 | 21 | -------------------------------------------------------------------------------- /config/xml/x2j/reader2j.go: -------------------------------------------------------------------------------- 1 | // io.Reader --> map[string]interface{} or JSON string 2 | // nothing magic - just implements generic Go case 3 | 4 | package x2j 5 | 6 | import ( 7 | "encoding/json" 8 | "encoding/xml" 9 | "io" 10 | ) 11 | 12 | // ToTree() - parse a XML io.Reader to a tree of Nodes 13 | func ToTree(rdr io.Reader) (*Node, error) { 14 | p := xml.NewDecoder(rdr) 15 | p.CharsetReader = X2jCharsetReader 16 | n, perr := xmlToTree("", nil, p) 17 | if perr != nil { 18 | return nil, perr 19 | } 20 | 21 | return n, nil 22 | } 23 | 24 | // ToMap() - parse a XML io.Reader to a map[string]interface{} 25 | func ToMap(rdr io.Reader, recast ...bool) (map[string]interface{}, error) { 26 | var r bool 27 | if len(recast) == 1 { 28 | r = recast[0] 29 | } 30 | n, err := ToTree(rdr) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | m := make(map[string]interface{}) 36 | m[n.key] = n.treeToMap(r) 37 | 38 | return m, nil 39 | } 40 | 41 | // ToJson() - parse a XML io.Reader to a JSON string 42 | func ToJson(rdr io.Reader, recast ...bool) (string, error) { 43 | var r bool 44 | if len(recast) == 1 { 45 | r = recast[0] 46 | } 47 | m, merr := ToMap(rdr, r) 48 | if m == nil || merr != nil { 49 | return "", merr 50 | } 51 | 52 | b, berr := json.Marshal(m) 53 | if berr != nil { 54 | return "", berr 55 | } 56 | 57 | return string(b), nil 58 | } 59 | 60 | // ToJsonIndent - the pretty form of ReaderToJson 61 | func ToJsonIndent(rdr io.Reader, recast ...bool) (string, error) { 62 | var r bool 63 | if len(recast) == 1 { 64 | r = recast[0] 65 | } 66 | m, merr := ToMap(rdr, r) 67 | if m == nil || merr != nil { 68 | return "", merr 69 | } 70 | 71 | b, berr := json.MarshalIndent(m, "", " ") 72 | if berr != nil { 73 | return "", berr 74 | } 75 | 76 | // NOTE: don't have to worry about safe JSON marshaling with json.Marshal, since '<' and '>" are reservedin XML. 77 | return string(b), nil 78 | } 79 | 80 | // ReaderValuesFromTagPath - io.Reader version of ValuesFromTagPath() 81 | func ReaderValuesFromTagPath(rdr io.Reader, path string, getAttrs ...bool) ([]interface{}, error) { 82 | var a bool 83 | if len(getAttrs) == 1 { 84 | a = getAttrs[0] 85 | } 86 | m, err := ToMap(rdr) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | return ValuesFromKeyPath(m, path, a), nil 92 | } 93 | 94 | // ReaderValuesForTag - io.Reader version of ValuesForTag() 95 | func ReaderValuesForTag(rdr io.Reader, tag string) ([]interface{}, error) { 96 | m, err := ToMap(rdr) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | return ValuesForKey(m, tag), nil 102 | } 103 | -------------------------------------------------------------------------------- /config/xml/x2j/reader2j_test.go: -------------------------------------------------------------------------------- 1 | package x2j 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | var doc = `barworld` 10 | 11 | func TestToTree(t *testing.T) { 12 | fmt.Println("\nToTree - Read doc:", doc) 13 | rdr := bytes.NewBufferString(doc) 14 | n, err := ToTree(rdr) 15 | if err != nil { 16 | fmt.Println("err:", err.Error()) 17 | } 18 | fmt.Println(n.WriteTree()) 19 | } 20 | 21 | func TestToMap(t *testing.T) { 22 | fmt.Println("\nToMap - Read doc:", doc) 23 | rdr := bytes.NewBufferString(doc) 24 | m, err := ToMap(rdr) 25 | if err != nil { 26 | fmt.Println("err:", err.Error()) 27 | } 28 | fmt.Println(WriteMap(m)) 29 | } 30 | 31 | func TestToJson(t *testing.T) { 32 | fmt.Println("\nToJson - Read doc:", doc) 33 | rdr := bytes.NewBufferString(doc) 34 | s, err := ToJson(rdr) 35 | if err != nil { 36 | fmt.Println("err:", err.Error()) 37 | } 38 | fmt.Println("json:", s) 39 | } 40 | 41 | func TestToJsonIndent(t *testing.T) { 42 | fmt.Println("\nToJsonIndent - Read doc:", doc) 43 | rdr := bytes.NewBufferString(doc) 44 | s, err := ToJsonIndent(rdr) 45 | if err != nil { 46 | fmt.Println("err:", err.Error()) 47 | } 48 | fmt.Println("json:", s) 49 | } 50 | 51 | func TestBulkParser(t *testing.T) { 52 | s := doc + `an` + doc 53 | fmt.Println("\nBulkParser (with error) - Read doc:", s) 54 | rdr := bytes.NewBufferString(s) 55 | err := XmlMsgsFromReader(rdr, phandler, ehandler) 56 | if err != nil { 57 | fmt.Println("reader terminated:", err.Error()) 58 | } 59 | } 60 | 61 | func phandler(m map[string]interface{}) bool { 62 | fmt.Println("phandler m:", m) 63 | return true 64 | } 65 | 66 | func ehandler(err error) bool { 67 | fmt.Println("ehandler err:", err.Error()) 68 | return true 69 | } 70 | 71 | func TestBulkParserToJson(t *testing.T) { 72 | s := doc + `an` + doc 73 | fmt.Println("\nBulkParser (with error) - Read doc:", s) 74 | rdr := bytes.NewBufferString(s) 75 | err := XmlMsgsFromReaderAsJson(rdr, phandlerj, ehandler) 76 | if err != nil { 77 | fmt.Println("reader terminated:", err.Error()) 78 | } 79 | } 80 | 81 | func phandlerj(s string) bool { 82 | fmt.Println("phandlerj s:", s) 83 | return true 84 | } 85 | -------------------------------------------------------------------------------- /config/xml/x2j/songTextString.xml: -------------------------------------------------------------------------------- 1 | 2 | help me! 3 | 4 | 5 | 6 | Henry was a renegade 7 | Didn't like to play it safe 8 | One component at a time 9 | There's got to be a better way 10 | Oh, people came from miles around 11 | Searching for a steady job 12 | Welcome to the Motor Town 13 | Booming like an atom bomb 14 | 15 | 16 | Oh, Henry was the end of the story 17 | Then everything went wrong 18 | And we'll return it to its former glory 19 | But it just takes so long 20 | 21 | 22 | 23 | It's going to take a long time 24 | It's going to take it, but we'll make it one day 25 | It's going to take a long time 26 | It's going to take it, but we'll make it one day 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /config/xml/x2j/x2j_test.xml: -------------------------------------------------------------------------------- 1 | 2 | help me! 3 | 4 | 5 | 6 | Henry was a renegade 7 | Didn't like to play it safe 8 | One component at a time 9 | There's got to be a better way 10 | Oh, people came from miles around 11 | Searching for a steady job 12 | Welcome to the Motor Town 13 | Booming like an atom bomb 14 | 15 | 16 | Oh, Henry was the end of the story 17 | Then everything went wrong 18 | And we'll return it to its former glory 19 | But it just takes so long 20 | 21 | 22 | 23 | It's going to take a long time 24 | It's going to take it, but we'll make it one day 25 | It's going to take a long time 26 | It's going to take it, but we'll make it one day 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /config/xml/x2j/x2junmarshal_test.go: -------------------------------------------------------------------------------- 1 | package x2j 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | func TestUnmarshal(t *testing.T) { 10 | var doc = []byte(` Mayer Hawthorne A Long Time Henry was a renegade Didn't like to play it safe `) 11 | 12 | fmt.Println("\nUnmarshal test ... *map[string]interface{}, *string") 13 | m := make(map[string]interface{}, 0) 14 | err := Unmarshal(doc, &m) 15 | if err != nil { 16 | fmt.Println("err:", err.Error()) 17 | } 18 | fmt.Println("m:", m) 19 | 20 | var s string 21 | err = Unmarshal(doc, &s) 22 | if err != nil { 23 | fmt.Println("err:", err.Error()) 24 | } 25 | fmt.Println("s:", s) 26 | } 27 | 28 | func TestStructValue(t *testing.T) { 29 | var doc = []byte(`clbanning
unknown
`) 30 | type Info struct { 31 | XMLName xml.Name `xml:"info"` 32 | Name string `xml:"name"` 33 | Address string `xml:"address"` 34 | } 35 | 36 | var myInfo Info 37 | 38 | fmt.Println("\nUnmarshal test ... struct:", string(doc)) 39 | err := Unmarshal(doc, &myInfo) 40 | if err != nil { 41 | fmt.Println("err:", err.Error()) 42 | } else { 43 | fmt.Printf("myInfo: %+v\n", myInfo) 44 | } 45 | } 46 | 47 | func TestMapValue(t *testing.T) { 48 | var doc = ` Mayer Hawthorne A Long Time Henry was a renegade Didn't like to play it safe ` 49 | 50 | fmt.Println("\nTestMapValue of doc.song.verse w/ len(attrs) == 0.") 51 | fmt.Println("doc:", doc) 52 | v, err := DocValue(doc, "doc.song.verse") 53 | if err != nil { 54 | fmt.Println("err:", err.Error()) 55 | } 56 | fmt.Println("result:", v) 57 | } 58 | -------------------------------------------------------------------------------- /config/xml/x2j/x2m_bulk.xml: -------------------------------------------------------------------------------- 1 | 2 | help me! 3 | 4 | 5 | 6 | Henry was a renegade 7 | Didn't like to play it safe 8 | One component at a time 9 | There's got to be a better way 10 | Oh, people came from miles around 11 | Searching for a steady job 12 | Welcome to the Motor Town 13 | Booming like an atom bomb 14 | 15 | 16 | Oh, Henry was the end of the story 17 | Then everything went wrong 18 | And we'll return it to its former glory 19 | But it just takes so long 20 | 21 | 22 | 23 | It's going to take a long time 24 | It's going to take it, but we'll make it one day 25 | It's going to take a long time 26 | It's going to take it, but we'll make it one day 27 | 28 | 29 | 30 | 31 | help me! 32 | 33 | 34 | 35 | Henry was a renegade 36 | Didn't like to play it safe 37 | One component at a time 38 | There's got to be a better way 39 | Oh, people came from miles around 40 | Searching for a steady job 41 | Welcome to the Motor Town 42 | Booming like an atom bomb 43 | 44 | 45 | 46 | 47 | 48 | help me! 49 | 50 | 51 | It's going to take a long time 52 | It's going to take it, but we'll make it one day 53 | It's going to take a long time 54 | It's going to take it, but we'll make it one day 55 | 56 | 57 | 58 | 59 | help me! 60 | 61 | 62 | It's going to take a long time 63 | It's going to take it, but we'll make it one day 64 | It's going to take a long time 65 | It's going to take it, but we'll make it one day 66 | 67 | 68 | -------------------------------------------------------------------------------- /config/xml/xml_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package xml 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/henrylee2cn/lessgo/config" 22 | ) 23 | 24 | //xml parse should incluce in tags 25 | var xmlcontext = ` 26 | 27 | lessgoapi 28 | 8080 29 | 3600 30 | 3.1415976 31 | dev 32 | false 33 | true 34 | 35 | ` 36 | 37 | func TestXML(t *testing.T) { 38 | f, err := os.Create("testxml.conf") 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | _, err = f.WriteString(xmlcontext) 43 | if err != nil { 44 | f.Close() 45 | t.Fatal(err) 46 | } 47 | f.Close() 48 | defer os.Remove("testxml.conf") 49 | xmlconf, err := config.NewConfig("xml", "testxml.conf") 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | if xmlconf.String("appname") != "lessgoapi" { 54 | t.Fatal("appname not equal to lessgoapi") 55 | } 56 | if port, err := xmlconf.Int("httpport"); err != nil || port != 8080 { 57 | t.Error(port) 58 | t.Fatal(err) 59 | } 60 | if port, err := xmlconf.Int64("mysqlport"); err != nil || port != 3600 { 61 | t.Error(port) 62 | t.Fatal(err) 63 | } 64 | if pi, err := xmlconf.Float("PI"); err != nil || pi != 3.1415976 { 65 | t.Error(pi) 66 | t.Fatal(err) 67 | } 68 | if xmlconf.String("runmode") != "dev" { 69 | t.Fatal("runmode not equal to dev") 70 | } 71 | if v, err := xmlconf.Bool("autorender"); err != nil || v != false { 72 | t.Error(v) 73 | t.Fatal(err) 74 | } 75 | if v, err := xmlconf.Bool("copyrequestbody"); err != nil || v != true { 76 | t.Error(v) 77 | t.Fatal(err) 78 | } 79 | if err = xmlconf.Set("name", "astaxie"); err != nil { 80 | t.Fatal(err) 81 | } 82 | if xmlconf.String("name") != "astaxie" { 83 | t.Fatal("get name error") 84 | } 85 | if xmlconf.Strings("emptystrings") != nil { 86 | t.Fatal("get emtpy strings error") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /config/yaml/goyaml2/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /config/yaml/goyaml2/README.md: -------------------------------------------------------------------------------- 1 | goyaml2 2 | ======= 3 | 4 | YAML for Golang -------------------------------------------------------------------------------- /config/yaml/goyaml2/conv.go: -------------------------------------------------------------------------------- 1 | package goyaml2 2 | 3 | import ( 4 | "regexp" 5 | "strconv" 6 | //"time" 7 | ) 8 | 9 | var ( 10 | RE_INT, _ = regexp.Compile("^[0-9,]+$") 11 | RE_FLOAT, _ = regexp.Compile("^[0-9]+[.][0-9]+$") 12 | RE_DATE, _ = regexp.Compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}$") 13 | RE_TIME, _ = regexp.Compile("^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$") 14 | ) 15 | 16 | func string2Val(str string) interface{} { 17 | tmp := []byte(str) 18 | switch { 19 | case str == "false": 20 | return false 21 | case str == "true": 22 | return true 23 | case RE_INT.Match(tmp): 24 | // TODO check err 25 | _int, _ := strconv.ParseInt(str, 10, 64) 26 | return _int 27 | case RE_FLOAT.Match(tmp): 28 | _float, _ := strconv.ParseFloat(str, 64) 29 | return _float 30 | //TODO support time or Not? 31 | /* 32 | case RE_DATE.Match(tmp): 33 | _date, _ := time.Parse("2006-01-02", str) 34 | return _date 35 | case RE_TIME.Match(tmp): 36 | _time, _ := time.Parse("2006-01-02 03:04:05", str) 37 | return _time 38 | */ 39 | } 40 | return str 41 | } 42 | -------------------------------------------------------------------------------- /config/yaml/goyaml2/conv_test.go: -------------------------------------------------------------------------------- 1 | package goyaml2 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestConv(*testing.T) { 9 | strs := []string{"true", "false", "100", "3.14", "2012-05-30", "2011-09-19 14:33:04"} 10 | for _, str := range strs { 11 | log.Println("++>>>", str, string2Val(str)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /config/yaml/goyaml2/errors.go: -------------------------------------------------------------------------------- 1 | package goyaml2 2 | 3 | // Copyright 2011 The Go Authors. All rights reserved. 4 | // Use of this source code is governed by a BSD-style 5 | // license that can be found in the LICENSE file. 6 | // And Modify By Wendal (wendal1985@gmail.com) 7 | 8 | // Package errors implements functions to manipulate errors. 9 | 10 | import ( 11 | "runtime/debug" 12 | ) 13 | 14 | var AddStack = true 15 | 16 | // New returns an error that formats as the given text. 17 | func New(text string) error { 18 | if AddStack { 19 | text += "\n" + string(debug.Stack()) 20 | } 21 | return &errorString{text} 22 | } 23 | 24 | // errorString is a trivial implementation of error. 25 | type errorString struct { 26 | s string 27 | } 28 | 29 | func (e *errorString) Error() string { 30 | return e.s 31 | } 32 | -------------------------------------------------------------------------------- /config/yaml/goyaml2/reader_test.go: -------------------------------------------------------------------------------- 1 | package goyaml2 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log" 7 | "testing" 8 | ) 9 | 10 | func init() { 11 | log.SetFlags(log.LstdFlags | log.Lshortfile) 12 | } 13 | 14 | func TestSpileToken(*testing.T) { 15 | strs := []string{"name : wendal", 16 | "skill : [c,c++,java,lua]", 17 | "study: { ppp:123 , vv : 23 }", 18 | "study: { ppp: 123 , vv : 23 }", 19 | "study : { ppp : 123 , vv : 23 } ", 20 | "a:b", 21 | "a:[1,2,3,4,5,6,7,8]"} 22 | 23 | for _, str := range strs { 24 | tokens := splitToken(str) 25 | data, _ := json.Marshal(tokens) 26 | log.Println(str, string(data)) 27 | } 28 | } 29 | 30 | func TestAsMapKeyValue(*testing.T) { 31 | strs := []string{"name : wendal", 32 | "skill : [c,c++,java,lua]", 33 | "study: { ppp:123 , vv : 23 }", 34 | "study: { ppp: 123 , vv : 23 }", 35 | "study : { ppp : 123 , vv : 23 } ", 36 | "a:b", 37 | "a:[1,2,3,4,5,6,7,8]", 38 | "abc : {abc:abc, ccc:zzz,sdfsadfasf:asfasfasfasfasfasfasfasdf}"} 39 | yaml := &yamlReader{} 40 | for _, str := range strs { 41 | key, value, err := yaml.asMapKeyValue(str) 42 | log.Println(key, value, err) 43 | } 44 | } 45 | 46 | func TestReadList(*testing.T) { 47 | strs := []string{` 48 | - name 49 | - age 50 | `, ` 51 | name : wendal 52 | age : 27 53 | skill : [c, c++, java] 54 | study : 55 | gdut : 2004 56 | jnu : 2008 57 | `, ` 58 | tags : 59 | - java 60 | - qq 61 | - golang 62 | catalog : 63 | - Jobs 64 | `, ` 65 | --- 66 | comments: true 67 | date: 2012-01-06 22:20:11 68 | layout: post 69 | slug: nutdao%e9%85%8d%e7%bd%ae%e5%a4%9a%e6%95%b0%e6%8d%ae%e6%ba%90 70 | title: NutDao配置多数据源 71 | permalink: '/356.html' 72 | wordpress_id: 356 73 | categories: 74 | - Java 75 | tags: 76 | - el 77 | - io 78 | - Nutz 79 | - 连接池 80 | - 配置 81 | ---`, ` 82 | layout : disqus 83 | disqus : 84 | short_name : wendalblog 85 | livefyre : 86 | site_id : 123 87 | intensedebate : 88 | account : 123abc 89 | facebook : 90 | appid : 123 91 | num_posts: 5 92 | width: 580 93 | colorscheme: light 94 | `, `linenums : false`, ` 95 | javascripts: 96 | [processing-1.4.1.min.js] 97 | 98 | layout: processing 99 | 100 | `, ` 101 | javascripts: 102 | [processing-1.4.1.min.js] 103 | 104 | layout: processing 105 | 106 | `} 107 | 108 | for _, str := range strs { 109 | log.Println("!!>>> ", str) 110 | obj, err := Read(bytes.NewBufferString(str)) 111 | log.Println("*********", str, obj, err, "*******") 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /config/yaml/goyaml2/types.go: -------------------------------------------------------------------------------- 1 | package goyaml2 2 | 3 | const ( 4 | N_Map = iota 5 | N_List 6 | N_String 7 | ) 8 | 9 | type Node interface { 10 | Type() int 11 | } 12 | 13 | type MapNode map[string]interface{} 14 | 15 | type ListNode []interface{} 16 | 17 | type StringNode string 18 | 19 | func (m *MapNode) Type() int { 20 | return N_Map 21 | } 22 | 23 | func (l *ListNode) Type() int { 24 | return N_List 25 | } 26 | 27 | func (s *StringNode) Type() int { 28 | return N_String 29 | } 30 | -------------------------------------------------------------------------------- /config/yaml/goyaml2/writer.go: -------------------------------------------------------------------------------- 1 | package goyaml2 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | func Write(w io.Writer, v interface{}) error { 8 | return nil 9 | } 10 | -------------------------------------------------------------------------------- /config/yaml/yaml_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package yaml 16 | 17 | import ( 18 | "os" 19 | "testing" 20 | 21 | "github.com/henrylee2cn/lessgo/config" 22 | ) 23 | 24 | var yamlcontext = ` 25 | "appname": lessgoapi 26 | "httpport": 8080 27 | "mysqlport": 3600 28 | "PI": 3.1415976 29 | "runmode": dev 30 | "autorender": false 31 | "copyrequestbody": true 32 | ` 33 | 34 | func TestYaml(t *testing.T) { 35 | f, err := os.Create("testyaml.conf") 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | _, err = f.WriteString(yamlcontext) 40 | if err != nil { 41 | f.Close() 42 | t.Fatal(err) 43 | } 44 | f.Close() 45 | defer os.Remove("testyaml.conf") 46 | yamlconf, err := config.NewConfig("yaml", "testyaml.conf") 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | if yamlconf.String("appname") != "lessgoapi" { 51 | t.Fatal("appname not equal to lessgoapi") 52 | } 53 | if port, err := yamlconf.Int("httpport"); err != nil || port != 8080 { 54 | t.Error(port) 55 | t.Fatal(err) 56 | } 57 | if port, err := yamlconf.Int64("mysqlport"); err != nil || port != 3600 { 58 | t.Error(port) 59 | t.Fatal(err) 60 | } 61 | if pi, err := yamlconf.Float("PI"); err != nil || pi != 3.1415976 { 62 | t.Error(pi) 63 | t.Fatal(err) 64 | } 65 | if yamlconf.String("runmode") != "dev" { 66 | t.Fatal("runmode not equal to dev") 67 | } 68 | if v, err := yamlconf.Bool("autorender"); err != nil || v != false { 69 | t.Error(v) 70 | t.Fatal(err) 71 | } 72 | if v, err := yamlconf.Bool("copyrequestbody"); err != nil || v != true { 73 | t.Error(v) 74 | t.Fatal(err) 75 | } 76 | if err = yamlconf.Set("name", "astaxie"); err != nil { 77 | t.Fatal(err) 78 | } 79 | if yamlconf.String("name") != "astaxie" { 80 | t.Fatal("get name error") 81 | } 82 | 83 | if yamlconf.Strings("emptystrings") != nil { 84 | t.Fatal("get emtpy strings error") 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /group.go: -------------------------------------------------------------------------------- 1 | package lessgo 2 | 3 | type ( 4 | // Group is a set of sub-routes for a specified route. It can be used for inner 5 | // routes that share a common middlware or functionality that should be separate 6 | // from the parent app instance while still inheriting from it. 7 | Group struct { 8 | prefix string 9 | chainNodes []MiddlewareFunc 10 | app *App 11 | } 12 | ) 13 | 14 | // Group creates a new sub-group with prefix and optional sub-group-level middleware. 15 | func (g *Group) group(prefix string, m ...MiddlewareFunc) *Group { 16 | m = append(g.chainNodes, m...) 17 | return g.app.group(joinpath(g.prefix, prefix), m...) 18 | } 19 | 20 | // Use implements `App#Use()` for sub-routes within the Group. 21 | func (g *Group) use(m ...MiddlewareFunc) { 22 | g.chainNodes = append(g.chainNodes, m...) 23 | } 24 | 25 | // match implements `App#match()` for sub-routes within the Group. 26 | func (g *Group) match(methods []string, path string, handler HandlerFunc, middleware ...MiddlewareFunc) { 27 | for _, method := range methods { 28 | g.add(method, path, handler, middleware...) 29 | } 30 | } 31 | 32 | func (g *Group) add(methods, path string, handler HandlerFunc, middleware ...MiddlewareFunc) { 33 | path = joinpath(g.prefix, path) 34 | middleware = append(g.chainNodes, middleware...) 35 | switch methods { 36 | case WS: 37 | g.app.webSocket(path, handler, middleware...) 38 | default: 39 | g.app.add(methods, path, handler, middleware...) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /helper.go: -------------------------------------------------------------------------------- 1 | package lessgo 2 | -------------------------------------------------------------------------------- /initialize.go: -------------------------------------------------------------------------------- 1 | package lessgo 2 | 3 | import ( 4 | "encoding/json" 5 | "mime" 6 | "path/filepath" 7 | "time" 8 | 9 | "github.com/henrylee2cn/lessgo/session" 10 | ) 11 | 12 | func newLessgo() *Lessgo { 13 | registerMime() 14 | 15 | l := &Lessgo{ 16 | App: app, 17 | config: Config, 18 | home: "/", 19 | serverEnable: true, 20 | apiHandlers: []*ApiHandler{}, 21 | apiMiddlewares: []*ApiMiddleware{}, 22 | virtBefore: []*MiddlewareConfig{}, 23 | virtAfter: []*MiddlewareConfig{}, 24 | virtStatics: []*VirtStatic{}, 25 | virtFiles: []*VirtFile{}, 26 | virtRouter: newRootVirtRouter(), 27 | } 28 | 29 | // 初始化全局日志 30 | Log.SetMsgChan(Config.Log.AsyncChan) 31 | Log.SetLevel(Config.Log.Level) 32 | 33 | // 设置运行模式 34 | l.App.SetDebug(Config.Debug) 35 | 36 | // 设置静态资源缓存 37 | l.App.setMemoryCache(NewMemoryCache( 38 | Config.FileCache.SingleFileAllowMB*MB, 39 | Config.FileCache.MaxCapMB*MB, 40 | time.Duration(Config.FileCache.CacheSecond)*time.Second), 41 | ) 42 | 43 | // 设置渲染接口 44 | l.App.SetRenderer(NewPongo2Render(!Config.Debug)) 45 | 46 | // 设置上传文件允许的最大尺寸 47 | MaxMemory = Config.MaxMemoryMB * MB 48 | 49 | // 初始化sessions管理实例 50 | sessions, err := newSessions() 51 | if err != nil { 52 | Log.Error("Failed to create sessions: %v.", err) 53 | } 54 | if sessions == nil { 55 | Log.Sys("Session is disable.") 56 | } else { 57 | go sessions.GC() 58 | l.App.setSessions(sessions) 59 | Log.Sys("Session is enable.") 60 | } 61 | 62 | return l 63 | } 64 | 65 | func registerMime() { 66 | for k, v := range mimemaps { 67 | mime.AddExtensionType(k, v) 68 | } 69 | } 70 | 71 | func newSessions() (sessions *session.Manager, err error) { 72 | if !Config.Session.SessionOn { 73 | return 74 | } 75 | conf := map[string]interface{}{ 76 | "cookieName": Config.Session.SessionName, 77 | "gclifetime": Config.Session.SessionGCMaxLifetime, 78 | "providerConfig": filepath.ToSlash(Config.Session.SessionProviderConfig), 79 | "secure": Config.Listen.EnableTLS, 80 | "enableSetCookie": Config.Session.SessionAutoSetCookie, 81 | "domain": Config.Session.SessionDomain, 82 | "cookieLifeTime": Config.Session.SessionCookieLifeTime, 83 | "enableSidInHttpHeader": Config.Session.EnableSidInHttpHeader, 84 | "sessionNameInHttpHeader": Config.Session.SessionNameInHttpHeader, 85 | "enableSidInUrlQuery": Config.Session.EnableSidInUrlQuery, 86 | } 87 | confBytes, _ := json.Marshal(conf) 88 | return session.NewManager(Config.Session.SessionProvider, string(confBytes)) 89 | } 90 | 91 | // 添加系统预设的路由操作前的中间件 92 | func registerBefore() { 93 | PreUse( 94 | &MiddlewareConfig{Name: "检查服务器是否启用"}, 95 | &MiddlewareConfig{Name: "检查是否为访问主页"}, 96 | &MiddlewareConfig{Name: "系统运行日志打印"}, 97 | ) 98 | if Config.CrossDomain { 99 | BeforeUse(&MiddlewareConfig{Name: "设置允许跨域"}) 100 | } 101 | } 102 | 103 | // 添加系统预设的路由操作后的中间件 104 | func registerAfter() { 105 | 106 | } 107 | 108 | // 添加系统预设的静态目录虚拟路由 109 | func registerStatics() { 110 | Static("/uploads", UPLOADS_DIR, AutoHTMLSuffix) 111 | Static("/static", STATIC_DIR, FilterTemplate, AutoHTMLSuffix) 112 | Static("/biz", BIZ_VIEW_DIR, FilterTemplate, AutoHTMLSuffix) 113 | Static("/sys", SYS_VIEW_DIR, FilterTemplate, AutoHTMLSuffix) 114 | } 115 | 116 | // 添加系统预设的静态文件虚拟路由 117 | func registerFiles() { 118 | File("/favicon.ico", IMG_DIR+"/favicon.ico") 119 | } 120 | -------------------------------------------------------------------------------- /logs/color/colorable_others.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package color 4 | 5 | import ( 6 | "io" 7 | "os" 8 | ) 9 | 10 | func NewColorable(file *os.File) io.Writer { 11 | if file == nil { 12 | panic("nil passed instead of *os.File to NewColorable()") 13 | } 14 | 15 | return file 16 | } 17 | 18 | func NewColorableStdout() io.Writer { 19 | return os.Stdout 20 | } 21 | 22 | func NewColorableStderr() io.Writer { 23 | return os.Stderr 24 | } 25 | -------------------------------------------------------------------------------- /logs/color/isatty_appengine.go: -------------------------------------------------------------------------------- 1 | // +build appengine 2 | 3 | package color 4 | 5 | // IsTerminal returns true if the file descriptor is terminal which 6 | // is always false on on appengine classic which is a sandboxed PaaS. 7 | func IsTerminal(fd uintptr) bool { 8 | return false 9 | } 10 | -------------------------------------------------------------------------------- /logs/color/isatty_bsd.go: -------------------------------------------------------------------------------- 1 | // +build darwin freebsd openbsd netbsd 2 | // +build !appengine 3 | 4 | package color 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | const ioctlReadTermios = syscall.TIOCGETA 12 | 13 | // IsTerminal return true if the file descriptor is terminal. 14 | func IsTerminal(fd uintptr) bool { 15 | var termios syscall.Termios 16 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) 17 | return err == 0 18 | } 19 | -------------------------------------------------------------------------------- /logs/color/isatty_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | // +build !appengine 3 | 4 | package color 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | const ioctlReadTermios = syscall.TCGETS 12 | 13 | // IsTerminal return true if the file descriptor is terminal. 14 | func IsTerminal(fd uintptr) bool { 15 | var termios syscall.Termios 16 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) 17 | return err == 0 18 | } 19 | -------------------------------------------------------------------------------- /logs/color/isatty_solaris.go: -------------------------------------------------------------------------------- 1 | // +build solaris 2 | // +build !appengine 3 | 4 | package color 5 | 6 | import ( 7 | "golang.org/x/sys/unix" 8 | ) 9 | 10 | // IsTerminal returns true if the given file descriptor is a terminal. 11 | // see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c 12 | func IsTerminal(fd uintptr) bool { 13 | var termio unix.Termio 14 | err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) 15 | return err == nil 16 | } 17 | -------------------------------------------------------------------------------- /logs/color/isatty_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | // +build !appengine 3 | 4 | package color 5 | 6 | import ( 7 | "syscall" 8 | "unsafe" 9 | ) 10 | 11 | var kernel32 = syscall.NewLazyDLL("kernel32.dll") 12 | var procGetConsoleMode = kernel32.NewProc("GetConsoleMode") 13 | 14 | // IsTerminal return true if the file descriptor is terminal. 15 | func IsTerminal(fd uintptr) bool { 16 | var st uint32 17 | r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) 18 | return r != 0 && e == 0 19 | } 20 | -------------------------------------------------------------------------------- /logs/log.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "github.com/henrylee2cn/lessgo/logs/logs" 5 | ) 6 | 7 | type ( 8 | Logger interface { 9 | SetMsgChan(channelLen int64) 10 | // SetLevel Set log message level. 11 | // If message level (such as LevelDebug) is higher than logger level (such as LevelWarning), 12 | // log providers will not even be sent the message. 13 | SetLevel(l int) 14 | // EnableFuncCallDepth enable log funcCallDepth 15 | EnableFuncCallDepth(b bool) 16 | // AddAdapter provides a given logger adapter into Logger with config string. 17 | // config need to be correct JSON as string: {"interval":360}. 18 | AddAdapter(adaptername string, config string) error 19 | 20 | Write(p []byte) (n int, err error) 21 | Sys(format string, v ...interface{}) 22 | Fatal(format string, v ...interface{}) 23 | Error(format string, v ...interface{}) 24 | Warn(format string, v ...interface{}) 25 | Info(format string, v ...interface{}) 26 | Debug(format string, v ...interface{}) 27 | } 28 | 29 | TgLogger struct { 30 | *logs.BeeLogger 31 | } 32 | ) 33 | 34 | // Log levels to control the logging output. 35 | const ( 36 | DEBUG = iota 37 | INFO 38 | WARN 39 | ERROR 40 | FATAL 41 | OFF 42 | ) 43 | 44 | func NewLogger(channelLen int64) Logger { 45 | tl := &TgLogger{logs.NewLogger(channelLen)} 46 | tl.BeeLogger.SetLogFuncCallDepth(3) 47 | return tl 48 | } 49 | 50 | func (t *TgLogger) SetLevel(l int) { 51 | t.BeeLogger.SetLevel(ExchangeLevel(l)) 52 | } 53 | 54 | func ExchangeLevel(l int) int { 55 | switch l { 56 | case DEBUG: 57 | return logs.LevelDebug 58 | case INFO: 59 | return logs.LevelInformational 60 | case WARN: 61 | return logs.LevelWarning 62 | case ERROR: 63 | return logs.LevelError 64 | case FATAL: 65 | return logs.LevelFatal 66 | case OFF: 67 | return logs.LevelEmergency - 1 68 | } 69 | return logs.LevelError 70 | } 71 | -------------------------------------------------------------------------------- /logs/logs/README.md: -------------------------------------------------------------------------------- 1 | ## logs 2 | logs is a Go logs manager. It can use many logs adapters. The repo is inspired by `database/sql` . 3 | 4 | 5 | ## How to install? 6 | 7 | go get github.com/astaxie/beego/logs 8 | 9 | 10 | ## What adapters are supported? 11 | 12 | As of now this logs support console, file,smtp and conn. 13 | 14 | 15 | ## How to use it? 16 | 17 | First you must import it 18 | 19 | import ( 20 | "github.com/astaxie/beego/logs" 21 | ) 22 | 23 | Then init a Log (example with console adapter) 24 | 25 | log := NewLogger(10000) 26 | log.SetLogger("console", "") 27 | 28 | > the first params stand for how many channel 29 | 30 | Use it like this: 31 | 32 | log.Trace("trace") 33 | log.Info("info") 34 | log.Warn("warning") 35 | log.Debug("debug") 36 | log.Critical("critical") 37 | 38 | 39 | ## File adapter 40 | 41 | Configure file adapter like this: 42 | 43 | log := NewLogger(10000) 44 | log.SetLogger("file", `{"filename":"test.log"}`) 45 | 46 | 47 | ## Conn adapter 48 | 49 | Configure like this: 50 | 51 | log := NewLogger(1000) 52 | log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) 53 | log.Info("info") 54 | 55 | 56 | ## Smtp adapter 57 | 58 | Configure like this: 59 | 60 | log := NewLogger(10000) 61 | log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) 62 | log.Critical("sendmail critical") 63 | time.Sleep(time.Second * 30) 64 | -------------------------------------------------------------------------------- /logs/logs/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "encoding/json" 19 | "io" 20 | "net" 21 | ) 22 | 23 | // connWriter implements LoggerInterface. 24 | // it writes messages in keep-live tcp connection. 25 | type connWriter struct { 26 | lg *logWriter 27 | innerWriter io.WriteCloser 28 | ReconnectOnMsg bool `json:"reconnectOnMsg"` 29 | Reconnect bool `json:"reconnect"` 30 | Net string `json:"net"` 31 | Addr string `json:"addr"` 32 | Level int `json:"level"` 33 | } 34 | 35 | // NewConn create new ConnWrite returning as LoggerInterface. 36 | func NewConn() Logger { 37 | conn := new(connWriter) 38 | conn.Level = LevelDebug 39 | return conn 40 | } 41 | 42 | // Init init connection writer with json config. 43 | // json config only need key "level". 44 | func (c *connWriter) Init(jsonConfig string) error { 45 | return json.Unmarshal([]byte(jsonConfig), c) 46 | } 47 | 48 | // WriteMsg write message in connection. 49 | // if connection is down, try to re-connect. 50 | func (c *connWriter) WriteMsg(lm logMsg) error { 51 | if lm.level > c.Level { 52 | return nil 53 | } 54 | if c.needToConnectOnMsg() { 55 | err := c.connect() 56 | if err != nil { 57 | return err 58 | } 59 | } 60 | 61 | if c.ReconnectOnMsg { 62 | defer c.innerWriter.Close() 63 | } 64 | 65 | c.lg.println(&lm) 66 | return nil 67 | } 68 | 69 | // Flush implementing method. empty. 70 | func (c *connWriter) Flush() { 71 | 72 | } 73 | 74 | // Destroy destroy connection writer and close tcp listener. 75 | func (c *connWriter) Destroy() { 76 | if c.innerWriter != nil { 77 | c.innerWriter.Close() 78 | } 79 | } 80 | 81 | func (c *connWriter) connect() error { 82 | if c.innerWriter != nil { 83 | c.innerWriter.Close() 84 | c.innerWriter = nil 85 | } 86 | 87 | conn, err := net.Dial(c.Net, c.Addr) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | if tcpConn, ok := conn.(*net.TCPConn); ok { 93 | tcpConn.SetKeepAlive(true) 94 | } 95 | 96 | c.innerWriter = conn 97 | c.lg = newLogWriter(conn) 98 | return nil 99 | } 100 | 101 | func (c *connWriter) needToConnectOnMsg() bool { 102 | if c.Reconnect { 103 | c.Reconnect = false 104 | return true 105 | } 106 | 107 | if c.innerWriter == nil { 108 | return true 109 | } 110 | 111 | return c.ReconnectOnMsg 112 | } 113 | 114 | func init() { 115 | Register("conn", NewConn) 116 | } 117 | -------------------------------------------------------------------------------- /logs/logs/conn_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestConn(t *testing.T) { 22 | log := NewLogger(1000) 23 | log.AddAdapter("conn", `{"net":"tcp","addr":":7020"}`) 24 | log.Info("informational") 25 | } 26 | -------------------------------------------------------------------------------- /logs/logs/console.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "encoding/json" 19 | "runtime" 20 | 21 | "github.com/henrylee2cn/lessgo/logs/color" 22 | ) 23 | 24 | type ( 25 | // // brush is a color join function 26 | brush func(msg interface{}, styles ...string) string 27 | // consoleWriter implements LoggerInterface and writes messages to terminal. 28 | consoleWriter struct { 29 | lg *logWriter 30 | Level int `json:"level"` 31 | Colorful bool `json:"color"` //this filed is useful only when system's terminal supports color 32 | } 33 | ) 34 | 35 | var ( 36 | colors = []brush{ 37 | color.White, // LevelSystem white 38 | color.RedBg, // Fatal redbg 39 | color.White, // Emergency white 40 | color.Cyan, // Alert cyan 41 | color.Magenta, // Critical magenta 42 | color.Red, // Error red 43 | color.Yellow, // Warning yellow 44 | color.Blue, // Notice blue 45 | color.Green, // Informational green 46 | color.Green, // Debug green 47 | } 48 | ) 49 | var out = newLogWriter(color.NewColorableStdout()) 50 | 51 | // NewConsole create ConsoleWriter returning as LoggerInterface. 52 | func NewConsole() Logger { 53 | cw := &consoleWriter{ 54 | lg: out, 55 | Level: LevelDebug, 56 | Colorful: true, 57 | } 58 | if runtime.GOOS == "linux" { 59 | cw.Colorful = false 60 | } 61 | return cw 62 | } 63 | 64 | // Init init console logger. 65 | // jsonConfig like '{"level":LevelTrace}'. 66 | func (c *consoleWriter) Init(jsonConfig string) error { 67 | if len(jsonConfig) == 0 { 68 | return nil 69 | } 70 | err := json.Unmarshal([]byte(jsonConfig), c) 71 | return err 72 | } 73 | 74 | // WriteMsg write message in console. 75 | func (c *consoleWriter) WriteMsg(lm logMsg) error { 76 | if lm.level > c.Level { 77 | return nil 78 | } 79 | if c.Colorful { 80 | lm.line = color.Dim(lm.line) 81 | lm.prefix = colors[lm.level](lm.prefix) 82 | } 83 | c.lg.println(&lm) 84 | return nil 85 | } 86 | 87 | // Destroy implementing method. empty. 88 | func (c *consoleWriter) Destroy() { 89 | 90 | } 91 | 92 | // Flush implementing method. empty. 93 | func (c *consoleWriter) Flush() { 94 | 95 | } 96 | 97 | func init() { 98 | Register("console", NewConsole) 99 | } 100 | -------------------------------------------------------------------------------- /logs/logs/console_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | // Try each log level in decreasing order of priority. 22 | func testConsoleCalls(bl *BeeLogger) { 23 | bl.Emergency("emergency") 24 | bl.Alert("alert") 25 | bl.Critical("critical") 26 | bl.Error("error") 27 | bl.Warn("warning") 28 | bl.Notice("notice") 29 | bl.Info("informational") 30 | bl.Debug("debug") 31 | } 32 | 33 | // Test console logging by visually comparing the lines being output with and 34 | // without a log level specification. 35 | func TestConsole(t *testing.T) { 36 | log1 := NewLogger(10000) 37 | log1.EnableFuncCallDepth(true) 38 | log1.AddAdapter("console", "") 39 | testConsoleCalls(log1) 40 | 41 | log2 := NewLogger(100) 42 | log2.AddAdapter("console", `{"level":3}`) 43 | testConsoleCalls(log2) 44 | } 45 | 46 | // Test console without color 47 | func TestConsoleNoColor(t *testing.T) { 48 | log := NewLogger(0) 49 | log.AddAdapter("console", `{"color":false}`) 50 | testConsoleCalls(log) 51 | } 52 | -------------------------------------------------------------------------------- /logs/logs/es/es.go: -------------------------------------------------------------------------------- 1 | package es 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "net/url" 9 | "time" 10 | 11 | "github.com/belogik/goes" 12 | 13 | "github.com/henrylee2cn/lessgo/logs/logs" 14 | ) 15 | 16 | // NewES return a LoggerInterface 17 | func NewES() logs.Logger { 18 | cw := &esLogger{ 19 | Level: logs.LevelDebug, 20 | } 21 | return cw 22 | } 23 | 24 | type esLogger struct { 25 | *goes.Connection 26 | DSN string `json:"dsn"` 27 | Level int `json:"level"` 28 | } 29 | 30 | // {"dsn":"http://localhost:9200/","level":1} 31 | func (el *esLogger) Init(jsonconfig string) error { 32 | err := json.Unmarshal([]byte(jsonconfig), el) 33 | if err != nil { 34 | return err 35 | } 36 | if el.DSN == "" { 37 | return errors.New("empty dsn") 38 | } else if u, err := url.Parse(el.DSN); err != nil { 39 | return err 40 | } else if u.Path == "" { 41 | return errors.New("missing prefix") 42 | } else if host, port, err := net.SplitHostPort(u.Host); err != nil { 43 | return err 44 | } else { 45 | conn := goes.NewConnection(host, port) 46 | el.Connection = conn 47 | } 48 | return nil 49 | } 50 | 51 | // WriteMsg will write the msg and level into es 52 | func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error { 53 | if level > el.Level { 54 | return nil 55 | } 56 | 57 | vals := make(map[string]interface{}) 58 | vals["@timestamp"] = when.Format(time.RFC3339) 59 | vals["@msg"] = msg 60 | d := goes.Document{ 61 | Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()), 62 | Type: "logs", 63 | Fields: vals, 64 | } 65 | _, err := el.Index(d, nil) 66 | return err 67 | } 68 | 69 | // Destroy is a empty method 70 | func (el *esLogger) Destroy() { 71 | 72 | } 73 | 74 | // Flush is a empty method 75 | func (el *esLogger) Flush() { 76 | 77 | } 78 | 79 | func init() { 80 | logs.Register("es", NewES) 81 | } 82 | -------------------------------------------------------------------------------- /logs/logs/logger.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "io" 19 | "sync" 20 | "time" 21 | ) 22 | 23 | type logWriter struct { 24 | sync.Mutex 25 | writer io.Writer 26 | } 27 | 28 | func newLogWriter(wr io.Writer) *logWriter { 29 | return &logWriter{writer: wr} 30 | } 31 | 32 | func (lg *logWriter) println(lm *logMsg) { 33 | lg.Lock() 34 | h, _ := formatTimeHeader(lm.when) 35 | b := append(h, (lm.line + lm.prefix + " " + lm.msg)...) 36 | if b[len(b)-1] != '\n' { 37 | b = append(b, '\n') 38 | } 39 | lg.writer.Write(b) 40 | lg.Unlock() 41 | } 42 | 43 | const ( 44 | y1 = `0123456789` 45 | y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789` 46 | y3 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999` 47 | y4 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789` 48 | mo1 = `000000000111` 49 | mo2 = `123456789012` 50 | d1 = `0000000001111111111222222222233` 51 | d2 = `1234567890123456789012345678901` 52 | h1 = `000000000011111111112222` 53 | h2 = `012345678901234567890123` 54 | mi1 = `000000000011111111112222222222333333333344444444445555555555` 55 | mi2 = `012345678901234567890123456789012345678901234567890123456789` 56 | s1 = `000000000011111111112222222222333333333344444444445555555555` 57 | s2 = `012345678901234567890123456789012345678901234567890123456789` 58 | ) 59 | 60 | func formatTimeHeader(when time.Time) ([]byte, int) { 61 | y, mo, d := when.Date() 62 | h, mi, s := when.Clock() 63 | //len("2006/01/02 15:04:05 ")==20 64 | var buf [20]byte 65 | 66 | buf[0] = y1[y/1000%10] 67 | buf[1] = y2[y/100] 68 | buf[2] = y3[y-y/100*100] 69 | buf[3] = y4[y-y/100*100] 70 | buf[4] = '/' 71 | buf[5] = mo1[mo-1] 72 | buf[6] = mo2[mo-1] 73 | buf[7] = '/' 74 | buf[8] = d1[d-1] 75 | buf[9] = d2[d-1] 76 | buf[10] = ' ' 77 | buf[11] = h1[h] 78 | buf[12] = h2[h] 79 | buf[13] = ':' 80 | buf[14] = mi1[mi] 81 | buf[15] = mi2[mi] 82 | buf[16] = ':' 83 | buf[17] = s1[s] 84 | buf[18] = s2[s] 85 | buf[19] = ' ' 86 | 87 | return buf[0:], d 88 | } 89 | -------------------------------------------------------------------------------- /logs/logs/multifile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "encoding/json" 19 | ) 20 | 21 | // A filesLogWriter manages several fileLogWriter 22 | // filesLogWriter will write logs to the file in json configuration and write the same level log to correspond file 23 | // means if the file name in configuration is project.log filesLogWriter will create project.error.log/project.debug.log 24 | // and write the error-level logs to project.error.log and write the debug-level logs to project.debug.log 25 | // the rotate attribute also acts like fileLogWriter 26 | type multiFileLogWriter struct { 27 | writers [LevelDebug + 1 + 1]*fileLogWriter // the last one for fullLogWriter 28 | fullLogWriter *fileLogWriter 29 | Separate []string `json:"separate"` 30 | } 31 | 32 | var levelNames = [...]string{"emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"} 33 | 34 | // Init file logger with json config. 35 | // jsonConfig like: 36 | // { 37 | // "filename":"logs/beego.log", 38 | // "maxLines":0, 39 | // "maxsize":0, 40 | // "daily":true, 41 | // "maxDays":15, 42 | // "rotate":true, 43 | // "perm":0600, 44 | // "separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"], 45 | // } 46 | 47 | func (f *multiFileLogWriter) Init(config string) error { 48 | writer := newFileWriter().(*fileLogWriter) 49 | err := writer.Init(config) 50 | if err != nil { 51 | return err 52 | } 53 | f.fullLogWriter = writer 54 | f.writers[LevelDebug+1] = writer 55 | 56 | //unmarshal "separate" field to f.Separate 57 | json.Unmarshal([]byte(config), f) 58 | 59 | jsonMap := map[string]interface{}{} 60 | json.Unmarshal([]byte(config), &jsonMap) 61 | 62 | for i := LevelEmergency; i < LevelDebug+1; i++ { 63 | for _, v := range f.Separate { 64 | if v == levelNames[i] { 65 | jsonMap["filename"] = f.fullLogWriter.fileNameOnly + "." + levelNames[i] + f.fullLogWriter.suffix 66 | jsonMap["level"] = i 67 | bs, _ := json.Marshal(jsonMap) 68 | writer = newFileWriter().(*fileLogWriter) 69 | writer.Init(Bytes2String(bs)) 70 | f.writers[i] = writer 71 | } 72 | } 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func (f *multiFileLogWriter) Destroy() { 79 | for i := 0; i < len(f.writers); i++ { 80 | if f.writers[i] != nil { 81 | f.writers[i].Destroy() 82 | } 83 | } 84 | } 85 | 86 | func (f *multiFileLogWriter) WriteMsg(lm logMsg) error { 87 | if f.fullLogWriter != nil { 88 | f.fullLogWriter.WriteMsg(lm) 89 | } 90 | for i := 0; i < len(f.writers)-1; i++ { 91 | if f.writers[i] != nil { 92 | if lm.level == f.writers[i].Level { 93 | f.writers[i].WriteMsg(lm) 94 | } 95 | } 96 | } 97 | return nil 98 | } 99 | 100 | func (f *multiFileLogWriter) Flush() { 101 | for i := 0; i < len(f.writers); i++ { 102 | if f.writers[i] != nil { 103 | f.writers[i].Flush() 104 | } 105 | } 106 | } 107 | 108 | // newFilesWriter create a FileLogWriter returning as LoggerInterface. 109 | func newFilesWriter() Logger { 110 | return &multiFileLogWriter{} 111 | } 112 | 113 | func init() { 114 | Register("multifile", newFilesWriter) 115 | } 116 | -------------------------------------------------------------------------------- /logs/logs/multifile_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "bufio" 19 | "os" 20 | "strconv" 21 | "strings" 22 | "testing" 23 | ) 24 | 25 | func TestFiles_1(t *testing.T) { 26 | log := NewLogger(10000) 27 | log.AddAdapter("multifile", `{"filename":"test.log","separate":["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]}`) 28 | log.Debug("debug") 29 | log.Info("info") 30 | log.Notice("notice") 31 | log.Warn("warning") 32 | log.Error("error") 33 | log.Alert("alert") 34 | log.Critical("critical") 35 | log.Emergency("emergency") 36 | fns := []string{""} 37 | fns = append(fns, levelNames[0:]...) 38 | name := "test" 39 | suffix := ".log" 40 | for _, fn := range fns { 41 | 42 | file := name + suffix 43 | if fn != "" { 44 | file = name + "." + fn + suffix 45 | } 46 | f, err := os.Open(file) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | b := bufio.NewReader(f) 51 | lineNum := 0 52 | lastLine := "" 53 | for { 54 | line, _, err := b.ReadLine() 55 | if err != nil { 56 | break 57 | } 58 | if len(line) > 0 { 59 | lastLine = string(line) 60 | lineNum++ 61 | } 62 | } 63 | var expected = 1 64 | if fn == "" { 65 | expected = LevelDebug + 1 66 | } 67 | if lineNum != expected { 68 | t.Fatal(file, "has", lineNum, "lines not "+strconv.Itoa(expected)+" lines") 69 | } 70 | if lineNum == 1 { 71 | if !strings.Contains(lastLine, fn) { 72 | t.Fatal(file + " " + lastLine + " not contains the log msg " + fn) 73 | } 74 | } 75 | os.Remove(file) 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /logs/logs/smtp_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package logs 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestSmtp(t *testing.T) { 23 | log := NewLogger(10000) 24 | log.AddAdapter("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) 25 | log.Critical("sendmail critical") 26 | time.Sleep(time.Second * 30) 27 | } 28 | -------------------------------------------------------------------------------- /logs/logs/utils.go: -------------------------------------------------------------------------------- 1 | package logs 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | // Bytes2String直接转换底层指针,两者指向的相同的内存,改一个另外一个也会变。 8 | // 效率是string([]byte{})的百倍以上,且转换量越大效率优势越明显。 9 | func Bytes2String(b []byte) string { 10 | return *(*string)(unsafe.Pointer(&b)) 11 | } 12 | 13 | // String2Bytes直接转换底层指针,两者指向的相同的内存,改一个另外一个也会变。 14 | // 效率是string([]byte{})的百倍以上,且转换量越大效率优势越明显。 15 | // 转换之后若没做其他操作直接改变里面的字符,则程序会崩溃。 16 | // 如 b:=String2bytes("xxx"); b[1]='d'; 程序将panic。 17 | func String2Bytes(s string) []byte { 18 | x := (*[2]uintptr)(unsafe.Pointer(&s)) 19 | h := [3]uintptr{x[0], x[1], x[1]} 20 | return *(*[]byte)(unsafe.Pointer(&h)) 21 | } 22 | -------------------------------------------------------------------------------- /markdown/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Blackfriday is distributed under the Simplified BSD License: 2 | 3 | > Copyright © 2011 Russ Ross 4 | > All rights reserved. 5 | > 6 | > Redistribution and use in source and binary forms, with or without 7 | > modification, are permitted provided that the following conditions 8 | > are met: 9 | > 10 | > 1. Redistributions of source code must retain the above copyright 11 | > notice, this list of conditions and the following disclaimer. 12 | > 13 | > 2. Redistributions in binary form must reproduce the above 14 | > copyright notice, this list of conditions and the following 15 | > disclaimer in the documentation and/or other materials provided with 16 | > the distribution. 17 | > 18 | > THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | > "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | > LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | > FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | > COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | > INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | > BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | > LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | > CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | > LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | > ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | > POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /markdown/github.go: -------------------------------------------------------------------------------- 1 | package markdown 2 | 3 | import ( 4 | "io" 5 | "text/template" 6 | ) 7 | 8 | const tpl = ` 9 | 10 | 11 | 14 | 15 | 16 | 17 |
18 | {{.body}} 19 |
20 | 21 | 22 | 23 | ` 24 | 25 | const ( 26 | githubCommonHTMLFlags = 0 | 27 | HTML_USE_XHTML | 28 | HTML_USE_SMARTYPANTS | 29 | HTML_SMARTYPANTS_FRACTIONS | 30 | HTML_SMARTYPANTS_LATEX_DASHES 31 | 32 | githubCommonExtensions = 0 | 33 | EXTENSION_NO_INTRA_EMPHASIS | 34 | EXTENSION_TABLES | 35 | EXTENSION_FENCED_CODE | 36 | EXTENSION_AUTOLINK | 37 | EXTENSION_STRIKETHROUGH | 38 | EXTENSION_SPACE_HEADERS | 39 | EXTENSION_HEADER_IDS | 40 | EXTENSION_BACKSLASH_LINE_BREAK | 41 | EXTENSION_DEFINITION_LISTS 42 | ) 43 | 44 | func GithubMarkdown(in []byte, out io.Writer, hasCatalog bool) error { 45 | flg := githubCommonHTMLFlags 46 | if hasCatalog { 47 | flg |= HTML_TOC 48 | } 49 | render := HtmlRenderer(flg, "", css) 50 | body := MarkdownOptions(in, render, Options{ 51 | Extensions: githubCommonExtensions, 52 | }) 53 | m := map[string]interface{}{ 54 | "css": css, 55 | "body": string(body), 56 | } 57 | return template.Must(template.New("markdown").Parse(tpl)).Execute(out, m) 58 | } 59 | -------------------------------------------------------------------------------- /markdown/ref_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Blackfriday Markdown Processor 3 | // Available at http://github.com/russross/blackfriday 4 | // 5 | // Copyright © 2011 Russ Ross . 6 | // Distributed under the Simplified BSD License. 7 | // See README.md for details. 8 | // 9 | 10 | // 11 | // Markdown 1.0.3 reference tests 12 | // 13 | 14 | package markdown 15 | 16 | import ( 17 | "io/ioutil" 18 | "path/filepath" 19 | "testing" 20 | ) 21 | 22 | func runMarkdownReference(input string, flag int) string { 23 | renderer := HtmlRenderer(0, "", "") 24 | return string(Markdown([]byte(input), renderer, flag)) 25 | } 26 | 27 | func doTestsReference(t *testing.T, files []string, flag int) { 28 | // catch and report panics 29 | var candidate string 30 | defer func() { 31 | if err := recover(); err != nil { 32 | t.Errorf("\npanic while processing [%#v]\n", candidate) 33 | } 34 | }() 35 | 36 | for _, basename := range files { 37 | filename := filepath.Join("testdata", basename+".text") 38 | inputBytes, err := ioutil.ReadFile(filename) 39 | if err != nil { 40 | t.Errorf("Couldn't open '%s', error: %v\n", filename, err) 41 | continue 42 | } 43 | input := string(inputBytes) 44 | 45 | filename = filepath.Join("testdata", basename+".html") 46 | expectedBytes, err := ioutil.ReadFile(filename) 47 | if err != nil { 48 | t.Errorf("Couldn't open '%s', error: %v\n", filename, err) 49 | continue 50 | } 51 | expected := string(expectedBytes) 52 | 53 | // fmt.Fprintf(os.Stderr, "processing %s ...", filename) 54 | actual := string(runMarkdownReference(input, flag)) 55 | if actual != expected { 56 | t.Errorf("\n [%#v]\nExpected[%#v]\nActual [%#v]", 57 | basename+".text", expected, actual) 58 | } 59 | // fmt.Fprintf(os.Stderr, " ok\n") 60 | 61 | // now test every prefix of every input to check for 62 | // bounds checking 63 | if !testing.Short() { 64 | start, max := 0, len(input) 65 | for end := start + 1; end <= max; end++ { 66 | candidate = input[start:end] 67 | // fmt.Fprintf(os.Stderr, " %s %d:%d/%d\n", filename, start, end, max) 68 | _ = runMarkdownReference(candidate, flag) 69 | } 70 | } 71 | } 72 | } 73 | 74 | func TestReference(t *testing.T) { 75 | files := []string{ 76 | "Amps and angle encoding", 77 | "Auto links", 78 | "Backslash escapes", 79 | "Blockquotes with code blocks", 80 | "Code Blocks", 81 | "Code Spans", 82 | "Hard-wrapped paragraphs with list-like lines", 83 | "Horizontal rules", 84 | "Inline HTML (Advanced)", 85 | "Inline HTML (Simple)", 86 | "Inline HTML comments", 87 | "Links, inline style", 88 | "Links, reference style", 89 | "Links, shortcut references", 90 | "Literal quotes in titles", 91 | "Markdown Documentation - Basics", 92 | "Markdown Documentation - Syntax", 93 | "Nested blockquotes", 94 | "Ordered and unordered lists", 95 | "Strong and em together", 96 | "Tabs", 97 | "Tidyness", 98 | } 99 | doTestsReference(t, files, 0) 100 | } 101 | 102 | func TestReference_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) { 103 | files := []string{ 104 | "Amps and angle encoding", 105 | "Auto links", 106 | "Backslash escapes", 107 | "Blockquotes with code blocks", 108 | "Code Blocks", 109 | "Code Spans", 110 | "Hard-wrapped paragraphs with list-like lines no empty line before block", 111 | "Horizontal rules", 112 | "Inline HTML (Advanced)", 113 | "Inline HTML (Simple)", 114 | "Inline HTML comments", 115 | "Links, inline style", 116 | "Links, reference style", 117 | "Links, shortcut references", 118 | "Literal quotes in titles", 119 | "Markdown Documentation - Basics", 120 | "Markdown Documentation - Syntax", 121 | "Nested blockquotes", 122 | "Ordered and unordered lists", 123 | "Strong and em together", 124 | "Tabs", 125 | "Tidyness", 126 | } 127 | doTestsReference(t, files, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK) 128 | } 129 | -------------------------------------------------------------------------------- /path.go: -------------------------------------------------------------------------------- 1 | package lessgo 2 | 3 | import ( 4 | "github.com/henrylee2cn/lessgo/utils" 5 | ) 6 | 7 | // CleanPath is the URL version of path.Clean, it returns a canonical URL path 8 | // for p, eliminating . and .. elements. 9 | // 10 | // The following rules are applied iteratively until no further processing can 11 | // be done: 12 | // 1. Replace multiple slashes with a single slash. 13 | // 2. Eliminate each . path name element (the current directory). 14 | // 3. Eliminate each inner .. path name element (the parent directory) 15 | // along with the non-.. element that precedes it. 16 | // 4. Eliminate .. elements that begin a rooted path: 17 | // that is, replace "/.." by "/" at the beginning of a path. 18 | // 19 | // If the result of this process is an empty string, "/" is returned 20 | func CleanPath(p string) string { 21 | // Turn empty string into "/" 22 | if p == "" { 23 | return "/" 24 | } 25 | 26 | n := len(p) 27 | var buf []byte 28 | 29 | // Invariants: 30 | // reading from path; r is index of next byte to process. 31 | // writing to buf; w is index of next byte to write. 32 | 33 | // path must start with '/' 34 | r := 1 35 | w := 1 36 | 37 | if p[0] != '/' { 38 | r = 0 39 | buf = make([]byte, n+1) 40 | buf[0] = '/' 41 | } 42 | 43 | trailing := n > 2 && p[n-1] == '/' 44 | 45 | // A bit more clunky without a 'lazybuf' like the path package, but the loop 46 | // gets completely inlined (bufApp). So in contrast to the path package this 47 | // loop has no expensive function calls (except 1x make) 48 | 49 | for r < n { 50 | switch { 51 | case p[r] == '/': 52 | // empty path element, trailing slash is added after the end 53 | r++ 54 | 55 | case p[r] == '.' && r+1 == n: 56 | trailing = true 57 | r++ 58 | 59 | case p[r] == '.' && p[r+1] == '/': 60 | // . element 61 | r++ 62 | 63 | case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'): 64 | // .. element: remove to last / 65 | r += 2 66 | 67 | if w > 1 { 68 | // can backtrack 69 | w-- 70 | 71 | if buf == nil { 72 | for w > 1 && p[w] != '/' { 73 | w-- 74 | } 75 | } else { 76 | for w > 1 && buf[w] != '/' { 77 | w-- 78 | } 79 | } 80 | } 81 | 82 | default: 83 | // real path element. 84 | // add slash if needed 85 | if w > 1 { 86 | bufApp(&buf, p, w, '/') 87 | w++ 88 | } 89 | 90 | // copy element 91 | for r < n && p[r] != '/' { 92 | bufApp(&buf, p, w, p[r]) 93 | w++ 94 | r++ 95 | } 96 | } 97 | } 98 | 99 | // re-append trailing slash 100 | if trailing && w > 1 { 101 | bufApp(&buf, p, w, '/') 102 | w++ 103 | } 104 | 105 | if buf == nil { 106 | return p[:w] 107 | } 108 | return utils.Bytes2String(buf[:w]) 109 | } 110 | 111 | // internal helper to lazily create a buffer if necessary 112 | func bufApp(buf *[]byte, s string, w int, c byte) { 113 | if *buf == nil { 114 | if s[w] == c { 115 | return 116 | } 117 | 118 | *buf = make([]byte, len(s)) 119 | copy(*buf, s[:w]) 120 | } 121 | (*buf)[w] = c 122 | } 123 | -------------------------------------------------------------------------------- /path_test.go: -------------------------------------------------------------------------------- 1 | package lessgo 2 | 3 | import ( 4 | "runtime" 5 | "testing" 6 | ) 7 | 8 | var cleanTests = []struct { 9 | path, result string 10 | }{ 11 | // Already clean 12 | {"/", "/"}, 13 | {"/abc", "/abc"}, 14 | {"/a/b/c", "/a/b/c"}, 15 | {"/abc/", "/abc/"}, 16 | {"/a/b/c/", "/a/b/c/"}, 17 | 18 | // missing root 19 | {"", "/"}, 20 | {"abc", "/abc"}, 21 | {"abc/def", "/abc/def"}, 22 | {"a/b/c", "/a/b/c"}, 23 | 24 | // Remove doubled slash 25 | {"//", "/"}, 26 | {"/abc//", "/abc/"}, 27 | {"/abc/def//", "/abc/def/"}, 28 | {"/a/b/c//", "/a/b/c/"}, 29 | {"/abc//def//ghi", "/abc/def/ghi"}, 30 | {"//abc", "/abc"}, 31 | {"///abc", "/abc"}, 32 | {"//abc//", "/abc/"}, 33 | 34 | // Remove . elements 35 | {".", "/"}, 36 | {"./", "/"}, 37 | {"/abc/./def", "/abc/def"}, 38 | {"/./abc/def", "/abc/def"}, 39 | {"/abc/.", "/abc/"}, 40 | 41 | // Remove .. elements 42 | {"..", "/"}, 43 | {"../", "/"}, 44 | {"../../", "/"}, 45 | {"../..", "/"}, 46 | {"../../abc", "/abc"}, 47 | {"/abc/def/ghi/../jkl", "/abc/def/jkl"}, 48 | {"/abc/def/../ghi/../jkl", "/abc/jkl"}, 49 | {"/abc/def/..", "/abc"}, 50 | {"/abc/def/../..", "/"}, 51 | {"/abc/def/../../..", "/"}, 52 | {"/abc/def/../../..", "/"}, 53 | {"/abc/def/../../../ghi/jkl/../../../mno", "/mno"}, 54 | 55 | // Combinations 56 | {"abc/./../def", "/def"}, 57 | {"abc//./../def", "/def"}, 58 | {"abc/../../././../def", "/def"}, 59 | } 60 | 61 | func TestPathClean(t *testing.T) { 62 | for _, test := range cleanTests { 63 | if s := CleanPath(test.path); s != test.result { 64 | t.Errorf("CleanPath(%q) = %q, want %q", test.path, s, test.result) 65 | } 66 | if s := CleanPath(test.result); s != test.result { 67 | t.Errorf("CleanPath(%q) = %q, want %q", test.result, s, test.result) 68 | } 69 | } 70 | } 71 | 72 | func TestPathCleanMallocs(t *testing.T) { 73 | if testing.Short() { 74 | t.Skip("skipping malloc count in short mode") 75 | } 76 | if runtime.GOMAXPROCS(0) > 1 { 77 | t.Log("skipping AllocsPerRun checks; GOMAXPROCS>1") 78 | return 79 | } 80 | 81 | for _, test := range cleanTests { 82 | allocs := testing.AllocsPerRun(100, func() { CleanPath(test.result) }) 83 | if allocs > 0 { 84 | t.Errorf("CleanPath(%q): %v allocs, want zero", test.result, allocs) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /pongo2/context.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$") 9 | 10 | // A Context type provides constants, variables, instances or functions to a template. 11 | // 12 | // pongo2 automatically provides meta-information or functions through the "pongo2"-key. 13 | // Currently, context["pongo2"] contains the following keys: 14 | // 1. version: returns the version string 15 | // 16 | // Template examples for accessing items from your context: 17 | // {{ myconstant }} 18 | // {{ myfunc("test", 42) }} 19 | // {{ user.name }} 20 | // {{ pongo2.version }} 21 | type Context map[string]interface{} 22 | 23 | func (c Context) checkForValidIdentifiers() *Error { 24 | for k, v := range c { 25 | if !reIdentifiers.MatchString(k) { 26 | return &Error{ 27 | Sender: "checkForValidIdentifiers", 28 | ErrorMsg: fmt.Sprintf("Context-key '%s' (value: '%+v') is not a valid identifier.", k, v), 29 | } 30 | } 31 | } 32 | return nil 33 | } 34 | 35 | // Update updates this context with the key/value-pairs from another context. 36 | func (c Context) Update(other Context) Context { 37 | for k, v := range other { 38 | c[k] = v 39 | } 40 | return c 41 | } 42 | 43 | // ExecutionContext contains all data important for the current rendering state. 44 | // 45 | // If you're writing a custom tag, your tag's Execute()-function will 46 | // have access to the ExecutionContext. This struct stores anything 47 | // about the current rendering process's Context including 48 | // the Context provided by the user (field Public). 49 | // You can safely use the Private context to provide data to the user's 50 | // template (like a 'forloop'-information). The Shared-context is used 51 | // to share data between tags. All ExecutionContexts share this context. 52 | // 53 | // Please be careful when accessing the Public data. 54 | // PLEASE DO NOT MODIFY THE PUBLIC CONTEXT (read-only). 55 | // 56 | // To create your own execution context within tags, use the 57 | // NewChildExecutionContext(parent) function. 58 | type ExecutionContext struct { 59 | template *Template 60 | 61 | Autoescape bool 62 | Public Context 63 | Private Context 64 | Shared Context 65 | } 66 | 67 | var pongo2MetaContext = Context{ 68 | "version": Version, 69 | } 70 | 71 | func newExecutionContext(tpl *Template, ctx Context) *ExecutionContext { 72 | privateCtx := make(Context) 73 | 74 | // Make the pongo2-related funcs/vars available to the context 75 | privateCtx["pongo2"] = pongo2MetaContext 76 | 77 | return &ExecutionContext{ 78 | template: tpl, 79 | 80 | Public: ctx, 81 | Private: privateCtx, 82 | Autoescape: true, 83 | } 84 | } 85 | 86 | func NewChildExecutionContext(parent *ExecutionContext) *ExecutionContext { 87 | newctx := &ExecutionContext{ 88 | template: parent.template, 89 | 90 | Public: parent.Public, 91 | Private: make(Context), 92 | Autoescape: parent.Autoescape, 93 | } 94 | newctx.Shared = parent.Shared 95 | 96 | // Copy all existing private items 97 | newctx.Private.Update(parent.Private) 98 | 99 | return newctx 100 | } 101 | 102 | func (ctx *ExecutionContext) Error(msg string, token *Token) *Error { 103 | filename := ctx.template.name 104 | var line, col int 105 | if token != nil { 106 | // No tokens available 107 | // TODO: Add location (from where?) 108 | filename = token.Filename 109 | line = token.Line 110 | col = token.Col 111 | } 112 | return &Error{ 113 | Template: ctx.template, 114 | Filename: filename, 115 | Line: line, 116 | Column: col, 117 | Token: token, 118 | Sender: "execution", 119 | ErrorMsg: msg, 120 | } 121 | } 122 | 123 | func (ctx *ExecutionContext) Logf(format string, args ...interface{}) { 124 | ctx.template.set.logf(format, args...) 125 | } 126 | -------------------------------------------------------------------------------- /pongo2/doc.go: -------------------------------------------------------------------------------- 1 | // A Django-syntax like template-engine 2 | // 3 | // Blog posts about pongo2 (including introduction and migration): 4 | // https://www.florian-schlachter.de/?tag=pongo2 5 | // 6 | // Complete documentation on the template language: 7 | // https://docs.djangoproject.com/en/dev/topics/templates/ 8 | // 9 | // Try out pongo2 live in the pongo2 playground: 10 | // https://www.florian-schlachter.de/pongo2/ 11 | // 12 | // Make sure to read README.md in the repository as well. 13 | // 14 | // A tiny example with template strings: 15 | // 16 | // (Snippet on playground: https://www.florian-schlachter.de/pongo2/?id=1206546277) 17 | // 18 | // // Compile the template first (i. e. creating the AST) 19 | // tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!") 20 | // if err != nil { 21 | // panic(err) 22 | // } 23 | // // Now you can render the template with the given 24 | // // pongo2.Context how often you want to. 25 | // out, err := tpl.Execute(pongo2.Context{"name": "fred"}) 26 | // if err != nil { 27 | // panic(err) 28 | // } 29 | // fmt.Println(out) // Output: Hello Fred! 30 | // 31 | package pongo2 32 | -------------------------------------------------------------------------------- /pongo2/error.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | // The Error type is being used to address an error during lexing, parsing or 10 | // execution. If you want to return an error object (for example in your own 11 | // tag or filter) fill this object with as much information as you have. 12 | // Make sure "Sender" is always given (if you're returning an error within 13 | // a filter, make Sender equals 'filter:yourfilter'; same goes for tags: 'tag:mytag'). 14 | // It's okay if you only fill in ErrorMsg if you don't have any other details at hand. 15 | type Error struct { 16 | Template *Template 17 | Filename string 18 | Line int 19 | Column int 20 | Token *Token 21 | Sender string 22 | ErrorMsg string 23 | } 24 | 25 | func (e *Error) updateFromTokenIfNeeded(template *Template, t *Token) *Error { 26 | if e.Template == nil { 27 | e.Template = template 28 | } 29 | 30 | if e.Token == nil { 31 | e.Token = t 32 | if e.Line <= 0 { 33 | e.Line = t.Line 34 | e.Column = t.Col 35 | } 36 | } 37 | 38 | return e 39 | } 40 | 41 | // Returns a nice formatted error string. 42 | func (e *Error) Error() string { 43 | s := "[Error" 44 | if e.Sender != "" { 45 | s += " (where: " + e.Sender + ")" 46 | } 47 | if e.Filename != "" { 48 | s += " in " + e.Filename 49 | } 50 | if e.Line > 0 { 51 | s += fmt.Sprintf(" | Line %d Col %d", e.Line, e.Column) 52 | if e.Token != nil { 53 | s += fmt.Sprintf(" near '%s'", e.Token.Val) 54 | } 55 | } 56 | s += "] " 57 | s += e.ErrorMsg 58 | return s 59 | } 60 | 61 | // RawLine returns the affected line from the original template, if available. 62 | func (e *Error) RawLine() (line string, available bool) { 63 | if e.Line <= 0 || e.Filename == "" { 64 | return "", false 65 | } 66 | 67 | filename := e.Filename 68 | if e.Template != nil { 69 | filename = e.Template.set.resolveFilename(e.Template, e.Filename) 70 | } 71 | file, err := os.Open(filename) 72 | if err != nil { 73 | panic(err) 74 | } 75 | defer func() { 76 | err := file.Close() 77 | if err != nil { 78 | panic(err) 79 | } 80 | }() 81 | 82 | scanner := bufio.NewScanner(file) 83 | l := 0 84 | for scanner.Scan() { 85 | l++ 86 | if l == e.Line { 87 | return scanner.Text(), true 88 | } 89 | } 90 | return "", false 91 | } 92 | -------------------------------------------------------------------------------- /pongo2/filters.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type FilterFunction func(in *Value, param *Value) (out *Value, err *Error) 8 | 9 | var filters map[string]FilterFunction 10 | 11 | func init() { 12 | filters = make(map[string]FilterFunction) 13 | } 14 | 15 | // Registers a new filter. If there's already a filter with the same 16 | // name, RegisterFilter will panic. You usually want to call this 17 | // function in the filter's init() function: 18 | // http://golang.org/doc/effective_go.html#init 19 | // 20 | // See http://www.florian-schlachter.de/post/pongo2/ for more about 21 | // writing filters and tags. 22 | func RegisterFilter(name string, fn FilterFunction) { 23 | _, existing := filters[name] 24 | if existing { 25 | panic(fmt.Sprintf("Filter with name '%s' is already registered.", name)) 26 | } 27 | filters[name] = fn 28 | } 29 | 30 | // Replaces an already registered filter with a new implementation. Use this 31 | // function with caution since it allows you to change existing filter behaviour. 32 | func ReplaceFilter(name string, fn FilterFunction) { 33 | _, existing := filters[name] 34 | if !existing { 35 | panic(fmt.Sprintf("Filter with name '%s' does not exist (therefore cannot be overridden).", name)) 36 | } 37 | filters[name] = fn 38 | } 39 | 40 | // Like ApplyFilter, but panics on an error 41 | func MustApplyFilter(name string, value *Value, param *Value) *Value { 42 | val, err := ApplyFilter(name, value, param) 43 | if err != nil { 44 | panic(err) 45 | } 46 | return val 47 | } 48 | 49 | // Applies a filter to a given value using the given parameters. Returns a *pongo2.Value or an error. 50 | func ApplyFilter(name string, value *Value, param *Value) (*Value, *Error) { 51 | fn, existing := filters[name] 52 | if !existing { 53 | return nil, &Error{ 54 | Sender: "applyfilter", 55 | ErrorMsg: fmt.Sprintf("Filter with name '%s' not found.", name), 56 | } 57 | } 58 | 59 | // Make sure param is a *Value 60 | if param == nil { 61 | param = AsValue(nil) 62 | } 63 | 64 | return fn(value, param) 65 | } 66 | 67 | type filterCall struct { 68 | token *Token 69 | 70 | name string 71 | parameter IEvaluator 72 | 73 | filterFunc FilterFunction 74 | } 75 | 76 | func (fc *filterCall) Execute(v *Value, ctx *ExecutionContext) (*Value, *Error) { 77 | var param *Value 78 | var err *Error 79 | 80 | if fc.parameter != nil { 81 | param, err = fc.parameter.Evaluate(ctx) 82 | if err != nil { 83 | return nil, err 84 | } 85 | } else { 86 | param = AsValue(nil) 87 | } 88 | 89 | filteredValue, err := fc.filterFunc(v, param) 90 | if err != nil { 91 | return nil, err.updateFromTokenIfNeeded(ctx.template, fc.token) 92 | } 93 | return filteredValue, nil 94 | } 95 | 96 | // Filter = IDENT | IDENT ":" FilterArg | IDENT "|" Filter 97 | func (p *Parser) parseFilter() (*filterCall, *Error) { 98 | identToken := p.MatchType(TokenIdentifier) 99 | 100 | // Check filter ident 101 | if identToken == nil { 102 | return nil, p.Error("Filter name must be an identifier.", nil) 103 | } 104 | 105 | filter := &filterCall{ 106 | token: identToken, 107 | name: identToken.Val, 108 | } 109 | 110 | // Get the appropriate filter function and bind it 111 | filterFn, exists := filters[identToken.Val] 112 | if !exists { 113 | return nil, p.Error(fmt.Sprintf("Filter '%s' does not exist.", identToken.Val), identToken) 114 | } 115 | 116 | filter.filterFunc = filterFn 117 | 118 | // Check for filter-argument (2 tokens needed: ':' ARG) 119 | if p.Match(TokenSymbol, ":") != nil { 120 | if p.Peek(TokenSymbol, "}}") != nil { 121 | return nil, p.Error("Filter parameter required after ':'.", nil) 122 | } 123 | 124 | // Get filter argument expression 125 | v, err := p.parseVariableOrLiteral() 126 | if err != nil { 127 | return nil, err 128 | } 129 | filter.parameter = v 130 | } 131 | 132 | return filter, nil 133 | } 134 | -------------------------------------------------------------------------------- /pongo2/helpers.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | func max(a, b int) int { 4 | if a > b { 5 | return a 6 | } 7 | return b 8 | } 9 | 10 | func min(a, b int) int { 11 | if a < b { 12 | return a 13 | } 14 | return b 15 | } 16 | -------------------------------------------------------------------------------- /pongo2/nodes.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | // The root document 4 | type nodeDocument struct { 5 | Nodes []INode 6 | } 7 | 8 | func (doc *nodeDocument) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 9 | for _, n := range doc.Nodes { 10 | err := n.Execute(ctx, writer) 11 | if err != nil { 12 | return err 13 | } 14 | } 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /pongo2/nodes_html.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type nodeHTML struct { 4 | token *Token 5 | } 6 | 7 | func (n *nodeHTML) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 8 | writer.WriteString(n.token.Val) 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pongo2/nodes_wrapper.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type NodeWrapper struct { 4 | Endtag string 5 | nodes []INode 6 | } 7 | 8 | func (wrapper *NodeWrapper) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 9 | for _, n := range wrapper.nodes { 10 | err := n.Execute(ctx, writer) 11 | if err != nil { 12 | return err 13 | } 14 | } 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /pongo2/parser_document.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | // Doc = { ( Filter | Tag | HTML ) } 4 | func (p *Parser) parseDocElement() (INode, *Error) { 5 | t := p.Current() 6 | 7 | switch t.Typ { 8 | case TokenHTML: 9 | p.Consume() // consume HTML element 10 | return &nodeHTML{token: t}, nil 11 | case TokenSymbol: 12 | switch t.Val { 13 | case "{{": 14 | // parse variable 15 | variable, err := p.parseVariableElement() 16 | if err != nil { 17 | return nil, err 18 | } 19 | return variable, nil 20 | case "{%": 21 | // parse tag 22 | tag, err := p.parseTagElement() 23 | if err != nil { 24 | return nil, err 25 | } 26 | return tag, nil 27 | } 28 | } 29 | return nil, p.Error("Unexpected token (only HTML/tags/filters in templates allowed)", t) 30 | } 31 | 32 | func (tpl *Template) parse() *Error { 33 | tpl.parser = newParser(tpl.name, tpl.tokens, tpl) 34 | doc, err := tpl.parser.parseDocument() 35 | if err != nil { 36 | return err 37 | } 38 | tpl.root = doc 39 | return nil 40 | } 41 | 42 | func (p *Parser) parseDocument() (*nodeDocument, *Error) { 43 | doc := &nodeDocument{} 44 | 45 | for p.Remaining() > 0 { 46 | node, err := p.parseDocElement() 47 | if err != nil { 48 | return nil, err 49 | } 50 | doc.Nodes = append(doc.Nodes, node) 51 | } 52 | 53 | return doc, nil 54 | } 55 | -------------------------------------------------------------------------------- /pongo2/pongo2.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | // Version string 4 | const Version = "dev" 5 | 6 | // Must panics, if a Template couldn't successfully parsed. This is how you 7 | // would use it: 8 | // var baseTemplate = pongo2.Must(pongo2.FromFile("templates/base.html")) 9 | func Must(tpl *Template, err error) *Template { 10 | if err != nil { 11 | panic(err) 12 | } 13 | return tpl 14 | } 15 | -------------------------------------------------------------------------------- /pongo2/tags_autoescape.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagAutoescapeNode struct { 4 | wrapper *NodeWrapper 5 | autoescape bool 6 | } 7 | 8 | func (node *tagAutoescapeNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 9 | old := ctx.Autoescape 10 | ctx.Autoescape = node.autoescape 11 | 12 | err := node.wrapper.Execute(ctx, writer) 13 | if err != nil { 14 | return err 15 | } 16 | 17 | ctx.Autoescape = old 18 | 19 | return nil 20 | } 21 | 22 | func tagAutoescapeParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 23 | autoescapeNode := &tagAutoescapeNode{} 24 | 25 | wrapper, _, err := doc.WrapUntilTag("endautoescape") 26 | if err != nil { 27 | return nil, err 28 | } 29 | autoescapeNode.wrapper = wrapper 30 | 31 | modeToken := arguments.MatchType(TokenIdentifier) 32 | if modeToken == nil { 33 | return nil, arguments.Error("A mode is required for autoescape-tag.", nil) 34 | } 35 | if modeToken.Val == "on" { 36 | autoescapeNode.autoescape = true 37 | } else if modeToken.Val == "off" { 38 | autoescapeNode.autoescape = false 39 | } else { 40 | return nil, arguments.Error("Only 'on' or 'off' is valid as an autoescape-mode.", nil) 41 | } 42 | 43 | if arguments.Remaining() > 0 { 44 | return nil, arguments.Error("Malformed autoescape-tag arguments.", nil) 45 | } 46 | 47 | return autoescapeNode, nil 48 | } 49 | 50 | func init() { 51 | RegisterTag("autoescape", tagAutoescapeParser) 52 | } 53 | -------------------------------------------------------------------------------- /pongo2/tags_block.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type tagBlockNode struct { 8 | name string 9 | } 10 | 11 | func (node *tagBlockNode) getBlockWrapperByName(tpl *Template) *NodeWrapper { 12 | var t *NodeWrapper 13 | if tpl.child != nil { 14 | // First ask the child for the block 15 | t = node.getBlockWrapperByName(tpl.child) 16 | } 17 | if t == nil { 18 | // Child has no block, lets look up here at parent 19 | t = tpl.blocks[node.name] 20 | } 21 | return t 22 | } 23 | 24 | func (node *tagBlockNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 25 | tpl := ctx.template 26 | if tpl == nil { 27 | panic("internal error: tpl == nil") 28 | } 29 | // Determine the block to execute 30 | blockWrapper := node.getBlockWrapperByName(tpl) 31 | if blockWrapper == nil { 32 | // fmt.Printf("could not find: %s\n", node.name) 33 | return ctx.Error("internal error: block_wrapper == nil in tagBlockNode.Execute()", nil) 34 | } 35 | err := blockWrapper.Execute(ctx, writer) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | // TODO: Add support for {{ block.super }} 41 | 42 | return nil 43 | } 44 | 45 | func tagBlockParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 46 | if arguments.Count() == 0 { 47 | return nil, arguments.Error("Tag 'block' requires an identifier.", nil) 48 | } 49 | 50 | nameToken := arguments.MatchType(TokenIdentifier) 51 | if nameToken == nil { 52 | return nil, arguments.Error("First argument for tag 'block' must be an identifier.", nil) 53 | } 54 | 55 | if arguments.Remaining() != 0 { 56 | return nil, arguments.Error("Tag 'block' takes exactly 1 argument (an identifier).", nil) 57 | } 58 | 59 | wrapper, endtagargs, err := doc.WrapUntilTag("endblock") 60 | if err != nil { 61 | return nil, err 62 | } 63 | if endtagargs.Remaining() > 0 { 64 | endtagnameToken := endtagargs.MatchType(TokenIdentifier) 65 | if endtagnameToken != nil { 66 | if endtagnameToken.Val != nameToken.Val { 67 | return nil, endtagargs.Error(fmt.Sprintf("Name for 'endblock' must equal to 'block'-tag's name ('%s' != '%s').", 68 | nameToken.Val, endtagnameToken.Val), nil) 69 | } 70 | } 71 | 72 | if endtagnameToken == nil || endtagargs.Remaining() > 0 { 73 | return nil, endtagargs.Error("Either no or only one argument (identifier) allowed for 'endblock'.", nil) 74 | } 75 | } 76 | 77 | tpl := doc.template 78 | if tpl == nil { 79 | panic("internal error: tpl == nil") 80 | } 81 | _, hasBlock := tpl.blocks[nameToken.Val] 82 | if !hasBlock { 83 | tpl.blocks[nameToken.Val] = wrapper 84 | } else { 85 | return nil, arguments.Error(fmt.Sprintf("Block named '%s' already defined", nameToken.Val), nil) 86 | } 87 | 88 | return &tagBlockNode{name: nameToken.Val}, nil 89 | } 90 | 91 | func init() { 92 | RegisterTag("block", tagBlockParser) 93 | } 94 | -------------------------------------------------------------------------------- /pongo2/tags_comment.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagCommentNode struct{} 4 | 5 | func (node *tagCommentNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 6 | return nil 7 | } 8 | 9 | func tagCommentParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 10 | commentNode := &tagCommentNode{} 11 | 12 | // TODO: Process the endtag's arguments (see django 'comment'-tag documentation) 13 | _, _, err := doc.WrapUntilTag("endcomment") 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | if arguments.Count() != 0 { 19 | return nil, arguments.Error("Tag 'comment' does not take any argument.", nil) 20 | } 21 | 22 | return commentNode, nil 23 | } 24 | 25 | func init() { 26 | RegisterTag("comment", tagCommentParser) 27 | } 28 | -------------------------------------------------------------------------------- /pongo2/tags_cycle.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagCycleValue struct { 4 | node *tagCycleNode 5 | value *Value 6 | } 7 | 8 | type tagCycleNode struct { 9 | position *Token 10 | args []IEvaluator 11 | idx int 12 | asName string 13 | silent bool 14 | } 15 | 16 | func (cv *tagCycleValue) String() string { 17 | return cv.value.String() 18 | } 19 | 20 | func (node *tagCycleNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 21 | item := node.args[node.idx%len(node.args)] 22 | node.idx++ 23 | 24 | val, err := item.Evaluate(ctx) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | if t, ok := val.Interface().(*tagCycleValue); ok { 30 | // {% cycle "test1" "test2" 31 | // {% cycle cycleitem %} 32 | 33 | // Update the cycle value with next value 34 | item := t.node.args[t.node.idx%len(t.node.args)] 35 | t.node.idx++ 36 | 37 | val, err := item.Evaluate(ctx) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | t.value = val 43 | 44 | if !t.node.silent { 45 | writer.WriteString(val.String()) 46 | } 47 | } else { 48 | // Regular call 49 | 50 | cycleValue := &tagCycleValue{ 51 | node: node, 52 | value: val, 53 | } 54 | 55 | if node.asName != "" { 56 | ctx.Private[node.asName] = cycleValue 57 | } 58 | if !node.silent { 59 | writer.WriteString(val.String()) 60 | } 61 | } 62 | 63 | return nil 64 | } 65 | 66 | // HINT: We're not supporting the old comma-seperated list of expresions argument-style 67 | func tagCycleParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 68 | cycleNode := &tagCycleNode{ 69 | position: start, 70 | } 71 | 72 | for arguments.Remaining() > 0 { 73 | node, err := arguments.ParseExpression() 74 | if err != nil { 75 | return nil, err 76 | } 77 | cycleNode.args = append(cycleNode.args, node) 78 | 79 | if arguments.MatchOne(TokenKeyword, "as") != nil { 80 | // as 81 | 82 | nameToken := arguments.MatchType(TokenIdentifier) 83 | if nameToken == nil { 84 | return nil, arguments.Error("Name (identifier) expected after 'as'.", nil) 85 | } 86 | cycleNode.asName = nameToken.Val 87 | 88 | if arguments.MatchOne(TokenIdentifier, "silent") != nil { 89 | cycleNode.silent = true 90 | } 91 | 92 | // Now we're finished 93 | break 94 | } 95 | } 96 | 97 | if arguments.Remaining() > 0 { 98 | return nil, arguments.Error("Malformed cycle-tag.", nil) 99 | } 100 | 101 | return cycleNode, nil 102 | } 103 | 104 | func init() { 105 | RegisterTag("cycle", tagCycleParser) 106 | } 107 | -------------------------------------------------------------------------------- /pongo2/tags_extends.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagExtendsNode struct { 4 | filename string 5 | } 6 | 7 | func (node *tagExtendsNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 8 | return nil 9 | } 10 | 11 | func tagExtendsParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 12 | extendsNode := &tagExtendsNode{} 13 | 14 | if doc.template.level > 1 { 15 | return nil, arguments.Error("The 'extends' tag can only defined on root level.", start) 16 | } 17 | 18 | if doc.template.parent != nil { 19 | // Already one parent 20 | return nil, arguments.Error("This template has already one parent.", start) 21 | } 22 | 23 | if filenameToken := arguments.MatchType(TokenString); filenameToken != nil { 24 | // prepared, static template 25 | 26 | // Get parent's filename 27 | parentFilename := doc.template.set.resolveFilename(doc.template, filenameToken.Val) 28 | 29 | // Parse the parent 30 | parentTemplate, err := doc.template.set.FromFile(parentFilename) 31 | if err != nil { 32 | return nil, err.(*Error) 33 | } 34 | 35 | // Keep track of things 36 | parentTemplate.child = doc.template 37 | doc.template.parent = parentTemplate 38 | extendsNode.filename = parentFilename 39 | } else { 40 | return nil, arguments.Error("Tag 'extends' requires a template filename as string.", nil) 41 | } 42 | 43 | if arguments.Remaining() > 0 { 44 | return nil, arguments.Error("Tag 'extends' does only take 1 argument.", nil) 45 | } 46 | 47 | return extendsNode, nil 48 | } 49 | 50 | func init() { 51 | RegisterTag("extends", tagExtendsParser) 52 | } 53 | -------------------------------------------------------------------------------- /pongo2/tags_filter.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | type nodeFilterCall struct { 8 | name string 9 | paramExpr IEvaluator 10 | } 11 | 12 | type tagFilterNode struct { 13 | position *Token 14 | bodyWrapper *NodeWrapper 15 | filterChain []*nodeFilterCall 16 | } 17 | 18 | func (node *tagFilterNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 19 | temp := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB size 20 | 21 | err := node.bodyWrapper.Execute(ctx, temp) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | value := AsValue(temp.String()) 27 | 28 | for _, call := range node.filterChain { 29 | var param *Value 30 | if call.paramExpr != nil { 31 | param, err = call.paramExpr.Evaluate(ctx) 32 | if err != nil { 33 | return err 34 | } 35 | } else { 36 | param = AsValue(nil) 37 | } 38 | value, err = ApplyFilter(call.name, value, param) 39 | if err != nil { 40 | return ctx.Error(err.Error(), node.position) 41 | } 42 | } 43 | 44 | writer.WriteString(value.String()) 45 | 46 | return nil 47 | } 48 | 49 | func tagFilterParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 50 | filterNode := &tagFilterNode{ 51 | position: start, 52 | } 53 | 54 | wrapper, _, err := doc.WrapUntilTag("endfilter") 55 | if err != nil { 56 | return nil, err 57 | } 58 | filterNode.bodyWrapper = wrapper 59 | 60 | for arguments.Remaining() > 0 { 61 | filterCall := &nodeFilterCall{} 62 | 63 | nameToken := arguments.MatchType(TokenIdentifier) 64 | if nameToken == nil { 65 | return nil, arguments.Error("Expected a filter name (identifier).", nil) 66 | } 67 | filterCall.name = nameToken.Val 68 | 69 | if arguments.MatchOne(TokenSymbol, ":") != nil { 70 | // Filter parameter 71 | // NOTICE: we can't use ParseExpression() here, because it would parse the next filter "|..." as well in the argument list 72 | expr, err := arguments.parseVariableOrLiteral() 73 | if err != nil { 74 | return nil, err 75 | } 76 | filterCall.paramExpr = expr 77 | } 78 | 79 | filterNode.filterChain = append(filterNode.filterChain, filterCall) 80 | 81 | if arguments.MatchOne(TokenSymbol, "|") == nil { 82 | break 83 | } 84 | } 85 | 86 | if arguments.Remaining() > 0 { 87 | return nil, arguments.Error("Malformed filter-tag arguments.", nil) 88 | } 89 | 90 | return filterNode, nil 91 | } 92 | 93 | func init() { 94 | RegisterTag("filter", tagFilterParser) 95 | } 96 | -------------------------------------------------------------------------------- /pongo2/tags_firstof.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagFirstofNode struct { 4 | position *Token 5 | args []IEvaluator 6 | } 7 | 8 | func (node *tagFirstofNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 9 | for _, arg := range node.args { 10 | val, err := arg.Evaluate(ctx) 11 | if err != nil { 12 | return err 13 | } 14 | 15 | if val.IsTrue() { 16 | if ctx.Autoescape && !arg.FilterApplied("safe") { 17 | val, err = ApplyFilter("escape", val, nil) 18 | if err != nil { 19 | return err 20 | } 21 | } 22 | 23 | writer.WriteString(val.String()) 24 | return nil 25 | } 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func tagFirstofParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 32 | firstofNode := &tagFirstofNode{ 33 | position: start, 34 | } 35 | 36 | for arguments.Remaining() > 0 { 37 | node, err := arguments.ParseExpression() 38 | if err != nil { 39 | return nil, err 40 | } 41 | firstofNode.args = append(firstofNode.args, node) 42 | } 43 | 44 | return firstofNode, nil 45 | } 46 | 47 | func init() { 48 | RegisterTag("firstof", tagFirstofParser) 49 | } 50 | -------------------------------------------------------------------------------- /pongo2/tags_if.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagIfNode struct { 4 | conditions []IEvaluator 5 | wrappers []*NodeWrapper 6 | } 7 | 8 | func (node *tagIfNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 9 | for i, condition := range node.conditions { 10 | result, err := condition.Evaluate(ctx) 11 | if err != nil { 12 | return err 13 | } 14 | 15 | if result.IsTrue() { 16 | return node.wrappers[i].Execute(ctx, writer) 17 | } 18 | // Last condition? 19 | if len(node.conditions) == i+1 && len(node.wrappers) > i+1 { 20 | return node.wrappers[i+1].Execute(ctx, writer) 21 | } 22 | } 23 | return nil 24 | } 25 | 26 | func tagIfParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 27 | ifNode := &tagIfNode{} 28 | 29 | // Parse first and main IF condition 30 | condition, err := arguments.ParseExpression() 31 | if err != nil { 32 | return nil, err 33 | } 34 | ifNode.conditions = append(ifNode.conditions, condition) 35 | 36 | if arguments.Remaining() > 0 { 37 | return nil, arguments.Error("If-condition is malformed.", nil) 38 | } 39 | 40 | // Check the rest 41 | for { 42 | wrapper, tagArgs, err := doc.WrapUntilTag("elif", "else", "endif") 43 | if err != nil { 44 | return nil, err 45 | } 46 | ifNode.wrappers = append(ifNode.wrappers, wrapper) 47 | 48 | if wrapper.Endtag == "elif" { 49 | // elif can take a condition 50 | condition, err = tagArgs.ParseExpression() 51 | if err != nil { 52 | return nil, err 53 | } 54 | ifNode.conditions = append(ifNode.conditions, condition) 55 | 56 | if tagArgs.Remaining() > 0 { 57 | return nil, tagArgs.Error("Elif-condition is malformed.", nil) 58 | } 59 | } else { 60 | if tagArgs.Count() > 0 { 61 | // else/endif can't take any conditions 62 | return nil, tagArgs.Error("Arguments not allowed here.", nil) 63 | } 64 | } 65 | 66 | if wrapper.Endtag == "endif" { 67 | break 68 | } 69 | } 70 | 71 | return ifNode, nil 72 | } 73 | 74 | func init() { 75 | RegisterTag("if", tagIfParser) 76 | } 77 | -------------------------------------------------------------------------------- /pongo2/tags_ifchanged.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | type tagIfchangedNode struct { 8 | watchedExpr []IEvaluator 9 | lastValues []*Value 10 | lastContent []byte 11 | thenWrapper *NodeWrapper 12 | elseWrapper *NodeWrapper 13 | } 14 | 15 | func (node *tagIfchangedNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 16 | if len(node.watchedExpr) == 0 { 17 | // Check against own rendered body 18 | 19 | buf := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB 20 | err := node.thenWrapper.Execute(ctx, buf) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | bufBytes := buf.Bytes() 26 | if !bytes.Equal(node.lastContent, bufBytes) { 27 | // Rendered content changed, output it 28 | writer.Write(bufBytes) 29 | node.lastContent = bufBytes 30 | } 31 | } else { 32 | nowValues := make([]*Value, 0, len(node.watchedExpr)) 33 | for _, expr := range node.watchedExpr { 34 | val, err := expr.Evaluate(ctx) 35 | if err != nil { 36 | return err 37 | } 38 | nowValues = append(nowValues, val) 39 | } 40 | 41 | // Compare old to new values now 42 | changed := len(node.lastValues) == 0 43 | 44 | for idx, oldVal := range node.lastValues { 45 | if !oldVal.EqualValueTo(nowValues[idx]) { 46 | changed = true 47 | break // we can stop here because ONE value changed 48 | } 49 | } 50 | 51 | node.lastValues = nowValues 52 | 53 | if changed { 54 | // Render thenWrapper 55 | err := node.thenWrapper.Execute(ctx, writer) 56 | if err != nil { 57 | return err 58 | } 59 | } else { 60 | // Render elseWrapper 61 | err := node.elseWrapper.Execute(ctx, writer) 62 | if err != nil { 63 | return err 64 | } 65 | } 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func tagIfchangedParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 72 | ifchangedNode := &tagIfchangedNode{} 73 | 74 | for arguments.Remaining() > 0 { 75 | // Parse condition 76 | expr, err := arguments.ParseExpression() 77 | if err != nil { 78 | return nil, err 79 | } 80 | ifchangedNode.watchedExpr = append(ifchangedNode.watchedExpr, expr) 81 | } 82 | 83 | if arguments.Remaining() > 0 { 84 | return nil, arguments.Error("Ifchanged-arguments are malformed.", nil) 85 | } 86 | 87 | // Wrap then/else-blocks 88 | wrapper, endargs, err := doc.WrapUntilTag("else", "endifchanged") 89 | if err != nil { 90 | return nil, err 91 | } 92 | ifchangedNode.thenWrapper = wrapper 93 | 94 | if endargs.Count() > 0 { 95 | return nil, endargs.Error("Arguments not allowed here.", nil) 96 | } 97 | 98 | if wrapper.Endtag == "else" { 99 | // if there's an else in the if-statement, we need the else-Block as well 100 | wrapper, endargs, err = doc.WrapUntilTag("endifchanged") 101 | if err != nil { 102 | return nil, err 103 | } 104 | ifchangedNode.elseWrapper = wrapper 105 | 106 | if endargs.Count() > 0 { 107 | return nil, endargs.Error("Arguments not allowed here.", nil) 108 | } 109 | } 110 | 111 | return ifchangedNode, nil 112 | } 113 | 114 | func init() { 115 | RegisterTag("ifchanged", tagIfchangedParser) 116 | } 117 | -------------------------------------------------------------------------------- /pongo2/tags_ifequal.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagIfEqualNode struct { 4 | var1, var2 IEvaluator 5 | thenWrapper *NodeWrapper 6 | elseWrapper *NodeWrapper 7 | } 8 | 9 | func (node *tagIfEqualNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 10 | r1, err := node.var1.Evaluate(ctx) 11 | if err != nil { 12 | return err 13 | } 14 | r2, err := node.var2.Evaluate(ctx) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | result := r1.EqualValueTo(r2) 20 | 21 | if result { 22 | return node.thenWrapper.Execute(ctx, writer) 23 | } 24 | if node.elseWrapper != nil { 25 | return node.elseWrapper.Execute(ctx, writer) 26 | } 27 | return nil 28 | } 29 | 30 | func tagIfEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 31 | ifequalNode := &tagIfEqualNode{} 32 | 33 | // Parse two expressions 34 | var1, err := arguments.ParseExpression() 35 | if err != nil { 36 | return nil, err 37 | } 38 | var2, err := arguments.ParseExpression() 39 | if err != nil { 40 | return nil, err 41 | } 42 | ifequalNode.var1 = var1 43 | ifequalNode.var2 = var2 44 | 45 | if arguments.Remaining() > 0 { 46 | return nil, arguments.Error("ifequal only takes 2 arguments.", nil) 47 | } 48 | 49 | // Wrap then/else-blocks 50 | wrapper, endargs, err := doc.WrapUntilTag("else", "endifequal") 51 | if err != nil { 52 | return nil, err 53 | } 54 | ifequalNode.thenWrapper = wrapper 55 | 56 | if endargs.Count() > 0 { 57 | return nil, endargs.Error("Arguments not allowed here.", nil) 58 | } 59 | 60 | if wrapper.Endtag == "else" { 61 | // if there's an else in the if-statement, we need the else-Block as well 62 | wrapper, endargs, err = doc.WrapUntilTag("endifequal") 63 | if err != nil { 64 | return nil, err 65 | } 66 | ifequalNode.elseWrapper = wrapper 67 | 68 | if endargs.Count() > 0 { 69 | return nil, endargs.Error("Arguments not allowed here.", nil) 70 | } 71 | } 72 | 73 | return ifequalNode, nil 74 | } 75 | 76 | func init() { 77 | RegisterTag("ifequal", tagIfEqualParser) 78 | } 79 | -------------------------------------------------------------------------------- /pongo2/tags_ifnotequal.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagIfNotEqualNode struct { 4 | var1, var2 IEvaluator 5 | thenWrapper *NodeWrapper 6 | elseWrapper *NodeWrapper 7 | } 8 | 9 | func (node *tagIfNotEqualNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 10 | r1, err := node.var1.Evaluate(ctx) 11 | if err != nil { 12 | return err 13 | } 14 | r2, err := node.var2.Evaluate(ctx) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | result := !r1.EqualValueTo(r2) 20 | 21 | if result { 22 | return node.thenWrapper.Execute(ctx, writer) 23 | } 24 | if node.elseWrapper != nil { 25 | return node.elseWrapper.Execute(ctx, writer) 26 | } 27 | return nil 28 | } 29 | 30 | func tagIfNotEqualParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 31 | ifnotequalNode := &tagIfNotEqualNode{} 32 | 33 | // Parse two expressions 34 | var1, err := arguments.ParseExpression() 35 | if err != nil { 36 | return nil, err 37 | } 38 | var2, err := arguments.ParseExpression() 39 | if err != nil { 40 | return nil, err 41 | } 42 | ifnotequalNode.var1 = var1 43 | ifnotequalNode.var2 = var2 44 | 45 | if arguments.Remaining() > 0 { 46 | return nil, arguments.Error("ifequal only takes 2 arguments.", nil) 47 | } 48 | 49 | // Wrap then/else-blocks 50 | wrapper, endargs, err := doc.WrapUntilTag("else", "endifnotequal") 51 | if err != nil { 52 | return nil, err 53 | } 54 | ifnotequalNode.thenWrapper = wrapper 55 | 56 | if endargs.Count() > 0 { 57 | return nil, endargs.Error("Arguments not allowed here.", nil) 58 | } 59 | 60 | if wrapper.Endtag == "else" { 61 | // if there's an else in the if-statement, we need the else-Block as well 62 | wrapper, endargs, err = doc.WrapUntilTag("endifnotequal") 63 | if err != nil { 64 | return nil, err 65 | } 66 | ifnotequalNode.elseWrapper = wrapper 67 | 68 | if endargs.Count() > 0 { 69 | return nil, endargs.Error("Arguments not allowed here.", nil) 70 | } 71 | } 72 | 73 | return ifnotequalNode, nil 74 | } 75 | 76 | func init() { 77 | RegisterTag("ifnotequal", tagIfNotEqualParser) 78 | } 79 | -------------------------------------------------------------------------------- /pongo2/tags_import.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type tagImportNode struct { 8 | position *Token 9 | filename string 10 | macros map[string]*tagMacroNode // alias/name -> macro instance 11 | } 12 | 13 | func (node *tagImportNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 14 | for name, macro := range node.macros { 15 | func(name string, macro *tagMacroNode) { 16 | ctx.Private[name] = func(args ...*Value) *Value { 17 | return macro.call(ctx, args...) 18 | } 19 | }(name, macro) 20 | } 21 | return nil 22 | } 23 | 24 | func tagImportParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 25 | importNode := &tagImportNode{ 26 | position: start, 27 | macros: make(map[string]*tagMacroNode), 28 | } 29 | 30 | filenameToken := arguments.MatchType(TokenString) 31 | if filenameToken == nil { 32 | return nil, arguments.Error("Import-tag needs a filename as string.", nil) 33 | } 34 | 35 | importNode.filename = doc.template.set.resolveFilename(doc.template, filenameToken.Val) 36 | 37 | if arguments.Remaining() == 0 { 38 | return nil, arguments.Error("You must at least specify one macro to import.", nil) 39 | } 40 | 41 | // Compile the given template 42 | tpl, err := doc.template.set.FromFile(importNode.filename) 43 | if err != nil { 44 | return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, start) 45 | } 46 | 47 | for arguments.Remaining() > 0 { 48 | macroNameToken := arguments.MatchType(TokenIdentifier) 49 | if macroNameToken == nil { 50 | return nil, arguments.Error("Expected macro name (identifier).", nil) 51 | } 52 | 53 | asName := macroNameToken.Val 54 | if arguments.Match(TokenKeyword, "as") != nil { 55 | aliasToken := arguments.MatchType(TokenIdentifier) 56 | if aliasToken == nil { 57 | return nil, arguments.Error("Expected macro alias name (identifier).", nil) 58 | } 59 | asName = aliasToken.Val 60 | } 61 | 62 | macroInstance, has := tpl.exportedMacros[macroNameToken.Val] 63 | if !has { 64 | return nil, arguments.Error(fmt.Sprintf("Macro '%s' not found (or not exported) in '%s'.", macroNameToken.Val, 65 | importNode.filename), macroNameToken) 66 | } 67 | 68 | importNode.macros[asName] = macroInstance 69 | 70 | if arguments.Remaining() == 0 { 71 | break 72 | } 73 | 74 | if arguments.Match(TokenSymbol, ",") == nil { 75 | return nil, arguments.Error("Expected ','.", nil) 76 | } 77 | } 78 | 79 | return importNode, nil 80 | } 81 | 82 | func init() { 83 | RegisterTag("import", tagImportParser) 84 | } 85 | -------------------------------------------------------------------------------- /pongo2/tags_now.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type tagNowNode struct { 8 | position *Token 9 | format string 10 | fake bool 11 | } 12 | 13 | func (node *tagNowNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 14 | var t time.Time 15 | if node.fake { 16 | t = time.Date(2014, time.February, 05, 18, 31, 45, 00, time.UTC) 17 | } else { 18 | t = time.Now() 19 | } 20 | 21 | writer.WriteString(t.Format(node.format)) 22 | 23 | return nil 24 | } 25 | 26 | func tagNowParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 27 | nowNode := &tagNowNode{ 28 | position: start, 29 | } 30 | 31 | formatToken := arguments.MatchType(TokenString) 32 | if formatToken == nil { 33 | return nil, arguments.Error("Expected a format string.", nil) 34 | } 35 | nowNode.format = formatToken.Val 36 | 37 | if arguments.MatchOne(TokenIdentifier, "fake") != nil { 38 | nowNode.fake = true 39 | } 40 | 41 | if arguments.Remaining() > 0 { 42 | return nil, arguments.Error("Malformed now-tag arguments.", nil) 43 | } 44 | 45 | return nowNode, nil 46 | } 47 | 48 | func init() { 49 | RegisterTag("now", tagNowParser) 50 | } 51 | -------------------------------------------------------------------------------- /pongo2/tags_set.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagSetNode struct { 4 | name string 5 | expression IEvaluator 6 | } 7 | 8 | func (node *tagSetNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 9 | // Evaluate expression 10 | value, err := node.expression.Evaluate(ctx) 11 | if err != nil { 12 | return err 13 | } 14 | 15 | ctx.Private[node.name] = value 16 | return nil 17 | } 18 | 19 | func tagSetParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 20 | node := &tagSetNode{} 21 | 22 | // Parse variable name 23 | typeToken := arguments.MatchType(TokenIdentifier) 24 | if typeToken == nil { 25 | return nil, arguments.Error("Expected an identifier.", nil) 26 | } 27 | node.name = typeToken.Val 28 | 29 | if arguments.Match(TokenSymbol, "=") == nil { 30 | return nil, arguments.Error("Expected '='.", nil) 31 | } 32 | 33 | // Variable expression 34 | keyExpression, err := arguments.ParseExpression() 35 | if err != nil { 36 | return nil, err 37 | } 38 | node.expression = keyExpression 39 | 40 | // Remaining arguments 41 | if arguments.Remaining() > 0 { 42 | return nil, arguments.Error("Malformed 'set'-tag arguments.", nil) 43 | } 44 | 45 | return node, nil 46 | } 47 | 48 | func init() { 49 | RegisterTag("set", tagSetParser) 50 | } 51 | -------------------------------------------------------------------------------- /pongo2/tags_spaceless.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "bytes" 5 | "regexp" 6 | ) 7 | 8 | type tagSpacelessNode struct { 9 | wrapper *NodeWrapper 10 | } 11 | 12 | var tagSpacelessRegexp = regexp.MustCompile(`(?U:(<.*>))([\t\n\v\f\r ]+)(?U:(<.*>))`) 13 | 14 | func (node *tagSpacelessNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 15 | b := bytes.NewBuffer(make([]byte, 0, 1024)) // 1 KiB 16 | 17 | err := node.wrapper.Execute(ctx, b) 18 | if err != nil { 19 | return err 20 | } 21 | 22 | s := b.String() 23 | // Repeat this recursively 24 | changed := true 25 | for changed { 26 | s2 := tagSpacelessRegexp.ReplaceAllString(s, "$1$3") 27 | changed = s != s2 28 | s = s2 29 | } 30 | 31 | writer.WriteString(s) 32 | 33 | return nil 34 | } 35 | 36 | func tagSpacelessParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 37 | spacelessNode := &tagSpacelessNode{} 38 | 39 | wrapper, _, err := doc.WrapUntilTag("endspaceless") 40 | if err != nil { 41 | return nil, err 42 | } 43 | spacelessNode.wrapper = wrapper 44 | 45 | if arguments.Remaining() > 0 { 46 | return nil, arguments.Error("Malformed spaceless-tag arguments.", nil) 47 | } 48 | 49 | return spacelessNode, nil 50 | } 51 | 52 | func init() { 53 | RegisterTag("spaceless", tagSpacelessParser) 54 | } 55 | -------------------------------------------------------------------------------- /pongo2/tags_ssi.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "io/ioutil" 5 | ) 6 | 7 | type tagSSINode struct { 8 | filename string 9 | content string 10 | template *Template 11 | } 12 | 13 | func (node *tagSSINode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 14 | if node.template != nil { 15 | // Execute the template within the current context 16 | includeCtx := make(Context) 17 | includeCtx.Update(ctx.Public) 18 | includeCtx.Update(ctx.Private) 19 | 20 | err := node.template.execute(includeCtx, writer) 21 | if err != nil { 22 | return err.(*Error) 23 | } 24 | } else { 25 | // Just print out the content 26 | writer.WriteString(node.content) 27 | } 28 | return nil 29 | } 30 | 31 | func tagSSIParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 32 | SSINode := &tagSSINode{} 33 | 34 | if fileToken := arguments.MatchType(TokenString); fileToken != nil { 35 | SSINode.filename = fileToken.Val 36 | 37 | if arguments.Match(TokenIdentifier, "parsed") != nil { 38 | // parsed 39 | temporaryTpl, err := doc.template.set.FromFile(doc.template.set.resolveFilename(doc.template, fileToken.Val)) 40 | if err != nil { 41 | return nil, err.(*Error).updateFromTokenIfNeeded(doc.template, fileToken) 42 | } 43 | SSINode.template = temporaryTpl 44 | } else { 45 | // plaintext 46 | buf, err := ioutil.ReadFile(doc.template.set.resolveFilename(doc.template, fileToken.Val)) 47 | if err != nil { 48 | return nil, (&Error{ 49 | Sender: "tag:ssi", 50 | ErrorMsg: err.Error(), 51 | }).updateFromTokenIfNeeded(doc.template, fileToken) 52 | } 53 | SSINode.content = string(buf) 54 | } 55 | } else { 56 | return nil, arguments.Error("First argument must be a string.", nil) 57 | } 58 | 59 | if arguments.Remaining() > 0 { 60 | return nil, arguments.Error("Malformed SSI-tag argument.", nil) 61 | } 62 | 63 | return SSINode, nil 64 | } 65 | 66 | func init() { 67 | RegisterTag("ssi", tagSSIParser) 68 | } 69 | -------------------------------------------------------------------------------- /pongo2/tags_templatetag.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagTemplateTagNode struct { 4 | content string 5 | } 6 | 7 | var templateTagMapping = map[string]string{ 8 | "openblock": "{%", 9 | "closeblock": "%}", 10 | "openvariable": "{{", 11 | "closevariable": "}}", 12 | "openbrace": "{", 13 | "closebrace": "}", 14 | "opencomment": "{#", 15 | "closecomment": "#}", 16 | } 17 | 18 | func (node *tagTemplateTagNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 19 | writer.WriteString(node.content) 20 | return nil 21 | } 22 | 23 | func tagTemplateTagParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 24 | ttNode := &tagTemplateTagNode{} 25 | 26 | if argToken := arguments.MatchType(TokenIdentifier); argToken != nil { 27 | output, found := templateTagMapping[argToken.Val] 28 | if !found { 29 | return nil, arguments.Error("Argument not found", argToken) 30 | } 31 | ttNode.content = output 32 | } else { 33 | return nil, arguments.Error("Identifier expected.", nil) 34 | } 35 | 36 | if arguments.Remaining() > 0 { 37 | return nil, arguments.Error("Malformed templatetag-tag argument.", nil) 38 | } 39 | 40 | return ttNode, nil 41 | } 42 | 43 | func init() { 44 | RegisterTag("templatetag", tagTemplateTagParser) 45 | } 46 | -------------------------------------------------------------------------------- /pongo2/tags_widthratio.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | type tagWidthratioNode struct { 9 | position *Token 10 | current, max IEvaluator 11 | width IEvaluator 12 | ctxName string 13 | } 14 | 15 | func (node *tagWidthratioNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 16 | current, err := node.current.Evaluate(ctx) 17 | if err != nil { 18 | return err 19 | } 20 | 21 | max, err := node.max.Evaluate(ctx) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | width, err := node.width.Evaluate(ctx) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | value := int(math.Ceil(current.Float()/max.Float()*width.Float() + 0.5)) 32 | 33 | if node.ctxName == "" { 34 | writer.WriteString(fmt.Sprintf("%d", value)) 35 | } else { 36 | ctx.Private[node.ctxName] = value 37 | } 38 | 39 | return nil 40 | } 41 | 42 | func tagWidthratioParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 43 | widthratioNode := &tagWidthratioNode{ 44 | position: start, 45 | } 46 | 47 | current, err := arguments.ParseExpression() 48 | if err != nil { 49 | return nil, err 50 | } 51 | widthratioNode.current = current 52 | 53 | max, err := arguments.ParseExpression() 54 | if err != nil { 55 | return nil, err 56 | } 57 | widthratioNode.max = max 58 | 59 | width, err := arguments.ParseExpression() 60 | if err != nil { 61 | return nil, err 62 | } 63 | widthratioNode.width = width 64 | 65 | if arguments.MatchOne(TokenKeyword, "as") != nil { 66 | // Name follows 67 | nameToken := arguments.MatchType(TokenIdentifier) 68 | if nameToken == nil { 69 | return nil, arguments.Error("Expected name (identifier).", nil) 70 | } 71 | widthratioNode.ctxName = nameToken.Val 72 | } 73 | 74 | if arguments.Remaining() > 0 { 75 | return nil, arguments.Error("Malformed widthratio-tag arguments.", nil) 76 | } 77 | 78 | return widthratioNode, nil 79 | } 80 | 81 | func init() { 82 | RegisterTag("widthratio", tagWidthratioParser) 83 | } 84 | -------------------------------------------------------------------------------- /pongo2/tags_with.go: -------------------------------------------------------------------------------- 1 | package pongo2 2 | 3 | type tagWithNode struct { 4 | withPairs map[string]IEvaluator 5 | wrapper *NodeWrapper 6 | } 7 | 8 | func (node *tagWithNode) Execute(ctx *ExecutionContext, writer TemplateWriter) *Error { 9 | //new context for block 10 | withctx := NewChildExecutionContext(ctx) 11 | 12 | // Put all custom with-pairs into the context 13 | for key, value := range node.withPairs { 14 | val, err := value.Evaluate(ctx) 15 | if err != nil { 16 | return err 17 | } 18 | withctx.Private[key] = val 19 | } 20 | 21 | return node.wrapper.Execute(withctx, writer) 22 | } 23 | 24 | func tagWithParser(doc *Parser, start *Token, arguments *Parser) (INodeTag, *Error) { 25 | withNode := &tagWithNode{ 26 | withPairs: make(map[string]IEvaluator), 27 | } 28 | 29 | if arguments.Count() == 0 { 30 | return nil, arguments.Error("Tag 'with' requires at least one argument.", nil) 31 | } 32 | 33 | wrapper, endargs, err := doc.WrapUntilTag("endwith") 34 | if err != nil { 35 | return nil, err 36 | } 37 | withNode.wrapper = wrapper 38 | 39 | if endargs.Count() > 0 { 40 | return nil, endargs.Error("Arguments not allowed here.", nil) 41 | } 42 | 43 | // Scan through all arguments to see which style the user uses (old or new style). 44 | // If we find any "as" keyword we will enforce old style; otherwise we will use new style. 45 | oldStyle := false // by default we're using the new_style 46 | for i := 0; i < arguments.Count(); i++ { 47 | if arguments.PeekN(i, TokenKeyword, "as") != nil { 48 | oldStyle = true 49 | break 50 | } 51 | } 52 | 53 | for arguments.Remaining() > 0 { 54 | if oldStyle { 55 | valueExpr, err := arguments.ParseExpression() 56 | if err != nil { 57 | return nil, err 58 | } 59 | if arguments.Match(TokenKeyword, "as") == nil { 60 | return nil, arguments.Error("Expected 'as' keyword.", nil) 61 | } 62 | keyToken := arguments.MatchType(TokenIdentifier) 63 | if keyToken == nil { 64 | return nil, arguments.Error("Expected an identifier", nil) 65 | } 66 | withNode.withPairs[keyToken.Val] = valueExpr 67 | } else { 68 | keyToken := arguments.MatchType(TokenIdentifier) 69 | if keyToken == nil { 70 | return nil, arguments.Error("Expected an identifier", nil) 71 | } 72 | if arguments.Match(TokenSymbol, "=") == nil { 73 | return nil, arguments.Error("Expected '='.", nil) 74 | } 75 | valueExpr, err := arguments.ParseExpression() 76 | if err != nil { 77 | return nil, err 78 | } 79 | withNode.withPairs[keyToken.Val] = valueExpr 80 | } 81 | } 82 | 83 | return withNode, nil 84 | } 85 | 86 | func init() { 87 | RegisterTag("with", tagWithParser) 88 | } 89 | -------------------------------------------------------------------------------- /render.go: -------------------------------------------------------------------------------- 1 | package lessgo 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "io" 7 | "sync" 8 | "time" 9 | 10 | "github.com/henrylee2cn/lessgo/pongo2" 11 | ) 12 | 13 | type ( 14 | Tpl struct { 15 | template *pongo2.Template 16 | modTime time.Time 17 | } 18 | // Pongo2Render is a custom lessgo template renderer using Pongo2. 19 | Pongo2Render struct { 20 | set *pongo2.TemplateSet 21 | caching bool // false=disable caching, true=enable caching 22 | tplCache map[string]*Tpl 23 | tplContext pongo2.Context // Context hold globle func for tpl 24 | sync.RWMutex 25 | } 26 | ) 27 | 28 | // New creates a new Pongo2Render instance with custom Options. 29 | func NewPongo2Render(caching bool) *Pongo2Render { 30 | return &Pongo2Render{ 31 | set: pongo2.NewSet("lessgo", pongo2.DefaultLoader), 32 | caching: caching, 33 | tplCache: make(map[string]*Tpl), 34 | tplContext: make(pongo2.Context), 35 | } 36 | } 37 | 38 | func (p *Pongo2Render) TemplateVariable(name string, v interface{}) { 39 | switch d := v.(type) { 40 | case func(in *pongo2.Value, param *pongo2.Value) (out *pongo2.Value, err *pongo2.Error): 41 | pongo2.RegisterFilter(name, d) 42 | case pongo2.FilterFunction: 43 | pongo2.RegisterFilter(name, d) 44 | default: 45 | p.tplContext[name] = d 46 | } 47 | } 48 | 49 | // Render should render the template to the io.Writer. 50 | func (p *Pongo2Render) Render(w io.Writer, filename string, data interface{}, c *Context) error { 51 | var data2 = pongo2.Context{} 52 | 53 | if data == nil { 54 | data2 = p.tplContext 55 | 56 | } else { 57 | switch d := data.(type) { 58 | case pongo2.Context: 59 | data2 = d 60 | case map[string]interface{}: 61 | data2 = pongo2.Context(d) 62 | default: 63 | b, _ := json.Marshal(data) 64 | json.Unmarshal(b, &data2) 65 | } 66 | 67 | for k, v := range p.tplContext { 68 | if _, ok := data2[k]; !ok { 69 | data2[k] = v 70 | } 71 | } 72 | } 73 | 74 | var template *pongo2.Template 75 | 76 | if p.caching { 77 | template = pongo2.Must(p.FromCache(filename)) 78 | } else { 79 | template = pongo2.Must(p.set.FromFile(filename)) 80 | } 81 | return template.ExecuteWriter(data2, w) 82 | } 83 | 84 | func (p *Pongo2Render) FromCache(fname string) (*pongo2.Template, error) { 85 | //从文件系统缓存中获取文件信息 86 | fbytes, finfo, exist := lessgo.App.MemoryCache().GetCacheFile(fname) 87 | 88 | // 文件已不存在 89 | if !exist { 90 | // 移除模板中缓存 91 | p.Lock() 92 | _, has := p.tplCache[fname] 93 | if has { 94 | delete(p.tplCache, fname) 95 | } 96 | p.Unlock() 97 | // 返回错误 98 | return nil, errors.New(fname + "is not found.") 99 | } 100 | 101 | // 查看模板缓存 102 | p.RLock() 103 | tpl, has := p.tplCache[fname] 104 | p.RUnlock() 105 | 106 | // 存在模板缓存且文件未更新时,直接读模板缓存 107 | if has && p.tplCache[fname].modTime.Equal(finfo.ModTime()) { 108 | return tpl.template, nil 109 | } 110 | 111 | // 缓存模板不存在或文件已更新时,均新建缓存模板 112 | p.Lock() 113 | defer p.Unlock() 114 | 115 | // 创建新模板并缓存 116 | newtpl, err := p.set.FromBytes(fname, fbytes) 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | p.tplCache[fname] = &Tpl{template: newtpl, modTime: finfo.ModTime()} 122 | return newtpl, nil 123 | } 124 | -------------------------------------------------------------------------------- /session/sess_cookie_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "strings" 21 | "testing" 22 | ) 23 | 24 | func TestCookie(t *testing.T) { 25 | config := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}` 26 | globalSessions, err := NewManager("cookie", config) 27 | if err != nil { 28 | t.Fatal("init cookie session err", err) 29 | } 30 | r, _ := http.NewRequest("GET", "/", nil) 31 | w := httptest.NewRecorder() 32 | sess, err := globalSessions.SessionStart(w, r) 33 | if err != nil { 34 | t.Fatal("set error,", err) 35 | } 36 | sess.Set("username", "astaxie") 37 | if username := sess.Get("username"); username != "astaxie" { 38 | t.Fatal("get username error") 39 | } 40 | sess.SessionRelease(w) 41 | if cookiestr := w.Header().Get("Set-Cookie"); cookiestr == "" { 42 | t.Fatal("setcookie error") 43 | } else { 44 | parts := strings.Split(strings.TrimSpace(cookiestr), ";") 45 | for k, v := range parts { 46 | nameval := strings.Split(v, "=") 47 | if k == 0 && nameval[0] != "gosessionid" { 48 | t.Fatal("error") 49 | } 50 | } 51 | } 52 | } 53 | 54 | func TestDestorySessionCookie(t *testing.T) { 55 | config := `{"cookieName":"gosessionid","enableSetCookie":true,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}` 56 | globalSessions, err := NewManager("cookie", config) 57 | if err != nil { 58 | t.Fatal("init cookie session err", err) 59 | } 60 | 61 | r, _ := http.NewRequest("GET", "/", nil) 62 | w := httptest.NewRecorder() 63 | session, err := globalSessions.SessionStart(w, r) 64 | if err != nil { 65 | t.Fatal("session start err,", err) 66 | } 67 | 68 | // request again ,will get same sesssion id . 69 | r1, _ := http.NewRequest("GET", "/", nil) 70 | r1.Header.Set("Cookie", w.Header().Get("Set-Cookie")) 71 | w = httptest.NewRecorder() 72 | newSession, err := globalSessions.SessionStart(w, r1) 73 | if err != nil { 74 | t.Fatal("session start err,", err) 75 | } 76 | if newSession.SessionID() != session.SessionID() { 77 | t.Fatal("get cookie session id is not the same again.") 78 | } 79 | 80 | // After destroy session , will get a new session id . 81 | globalSessions.SessionDestroy(w, r1) 82 | r2, _ := http.NewRequest("GET", "/", nil) 83 | r2.Header.Set("Cookie", w.Header().Get("Set-Cookie")) 84 | 85 | w = httptest.NewRecorder() 86 | newSession, err = globalSessions.SessionStart(w, r2) 87 | if err != nil { 88 | t.Fatal("session start error") 89 | } 90 | if newSession.SessionID() == session.SessionID() { 91 | t.Fatal("after destroy session and reqeust again ,get cookie session id is same.") 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /session/sess_mem_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "strings" 21 | "testing" 22 | ) 23 | 24 | func TestMem(t *testing.T) { 25 | globalSessions, _ := NewManager("memory", `{"cookieName":"gosessionid","gclifetime":10}`) 26 | go globalSessions.GC() 27 | r, _ := http.NewRequest("GET", "/", nil) 28 | w := httptest.NewRecorder() 29 | sess, err := globalSessions.SessionStart(w, r) 30 | if err != nil { 31 | t.Fatal("set error,", err) 32 | } 33 | defer sess.SessionRelease(w) 34 | sess.Set("username", "astaxie") 35 | if username := sess.Get("username"); username != "astaxie" { 36 | t.Fatal("get username error") 37 | } 38 | if cookiestr := w.Header().Get("Set-Cookie"); cookiestr == "" { 39 | t.Fatal("setcookie error") 40 | } else { 41 | parts := strings.Split(strings.TrimSpace(cookiestr), ";") 42 | for k, v := range parts { 43 | nameval := strings.Split(v, "=") 44 | if k == 0 && nameval[0] != "gosessionid" { 45 | t.Fatal("error") 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /utils/caller.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "reflect" 19 | "runtime" 20 | ) 21 | 22 | // GetFuncName get function name 23 | func GetFuncName(i interface{}) string { 24 | return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() 25 | } 26 | -------------------------------------------------------------------------------- /utils/caller_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "strings" 19 | "testing" 20 | ) 21 | 22 | func TestGetFuncName(t *testing.T) { 23 | name := GetFuncName(TestGetFuncName) 24 | t.Log(name) 25 | if !strings.HasSuffix(name, ".TestGetFuncName") { 26 | t.Error("get func name error") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /utils/debug_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | type mytype struct { 22 | next *mytype 23 | prev *mytype 24 | } 25 | 26 | func TestPrint(t *testing.T) { 27 | Display("v1", 1, "v2", 2, "v3", 3) 28 | } 29 | 30 | func TestPrintPoint(t *testing.T) { 31 | var v1 = new(mytype) 32 | var v2 = new(mytype) 33 | 34 | v1.prev = nil 35 | v1.next = v2 36 | 37 | v2.prev = v1 38 | v2.next = nil 39 | 40 | Display("v1", v1, "v2", v2) 41 | } 42 | 43 | func TestPrintString(t *testing.T) { 44 | str := GetDisplayString("v1", 1, "v2", 2) 45 | println(str) 46 | } 47 | -------------------------------------------------------------------------------- /utils/file_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "path/filepath" 19 | "reflect" 20 | "testing" 21 | ) 22 | 23 | var noExistedFile = "/tmp/not_existed_file" 24 | 25 | func TestSelfPath(t *testing.T) { 26 | path := SelfPath() 27 | if path == "" { 28 | t.Error("path cannot be empty") 29 | } 30 | t.Logf("SelfPath: %s", path) 31 | } 32 | 33 | func TestSelfDir(t *testing.T) { 34 | dir := SelfDir() 35 | t.Logf("SelfDir: %s", dir) 36 | } 37 | 38 | func TestSelfChdir(t *testing.T) { 39 | SelfChdir() 40 | path, err := filepath.Abs("a") 41 | t.Logf("SelfChdir: %s %v", path, err) 42 | } 43 | 44 | func TestFileExists(t *testing.T) { 45 | if !FileExists("./file.go") { 46 | t.Errorf("./file.go should exists, but it didn't") 47 | } 48 | 49 | if FileExists(noExistedFile) { 50 | t.Errorf("Wierd, how could this file exists: %s", noExistedFile) 51 | } 52 | } 53 | 54 | func TestSearchFile(t *testing.T) { 55 | path, err := SearchFile(filepath.Base(SelfPath()), SelfDir()) 56 | if err != nil { 57 | t.Error(err) 58 | } 59 | t.Log(path) 60 | 61 | path, err = SearchFile(noExistedFile, ".") 62 | if err == nil { 63 | t.Errorf("err shouldnot be nil, got path: %s", SelfDir()) 64 | } 65 | } 66 | 67 | func TestGrepFile(t *testing.T) { 68 | _, err := GrepFile("", noExistedFile) 69 | if err == nil { 70 | t.Error("expect file-not-existed error, but got nothing") 71 | } 72 | 73 | path := filepath.Join(".", "testdata", "grepe.test") 74 | lines, err := GrepFile(`^\s*[^#]+`, path) 75 | if err != nil { 76 | t.Error(err) 77 | } 78 | if !reflect.DeepEqual(lines, []string{"hello", "world"}) { 79 | t.Errorf("expect [hello world], but receive %v", lines) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /utils/hash.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "hash/crc32" 9 | "hash/fnv" 10 | "strconv" 11 | ) 12 | 13 | //string to hash 14 | func MakeHash(s string) string { 15 | const IEEE = 0xedb88320 16 | var IEEETable = crc32.MakeTable(IEEE) 17 | hash := fmt.Sprintf("%x", crc32.Checksum([]byte(s), IEEETable)) 18 | return hash 19 | } 20 | 21 | func HashString(encode string) uint64 { 22 | hash := fnv.New64() 23 | hash.Write([]byte(encode)) 24 | return hash.Sum64() 25 | } 26 | 27 | // 制作特征值方法一 28 | func MakeUnique(obj interface{}) string { 29 | baseString, _ := json.Marshal(obj) 30 | return strconv.FormatUint(HashString(string(baseString)), 10) 31 | } 32 | 33 | // 制作特征值方法二 34 | func MakeMd5(obj interface{}, length ...int) string { 35 | h := md5.New() 36 | baseString, _ := json.Marshal(obj) 37 | h.Write([]byte(baseString)) 38 | s := hex.EncodeToString(h.Sum(nil)) 39 | if len(length) == 0 { 40 | return s 41 | } 42 | if length[0] > 32 { 43 | length[0] = 32 44 | } 45 | return s[:length[0]] 46 | } 47 | -------------------------------------------------------------------------------- /utils/mail_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import "testing" 18 | 19 | func TestMail(t *testing.T) { 20 | config := `{"username":"astaxie@gmail.com","password":"astaxie","host":"smtp.gmail.com","port":587}` 21 | mail := NewEMail(config) 22 | if mail.Username != "astaxie@gmail.com" { 23 | t.Fatal("email parse get username error") 24 | } 25 | if mail.Password != "astaxie" { 26 | t.Fatal("email parse get password error") 27 | } 28 | if mail.Host != "smtp.gmail.com" { 29 | t.Fatal("email parse get host error") 30 | } 31 | if mail.Port != 587 { 32 | t.Fatal("email parse get port error") 33 | } 34 | mail.To = []string{"xiemengjun@gmail.com"} 35 | mail.From = "astaxie@gmail.com" 36 | mail.Subject = "hi, just from beego!" 37 | mail.Text = "Text Body is, of course, supported!" 38 | mail.HTML = "

Fancy Html is supported, too!

" 39 | mail.AttachFile("/Users/astaxie/github/beego/beego.go") 40 | mail.Send() 41 | } 42 | -------------------------------------------------------------------------------- /utils/name.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "reflect" 5 | "runtime" 6 | "strings" 7 | ) 8 | 9 | // snake string, XxYy to xx_yy 10 | func SnakeString(s string) string { 11 | data := make([]byte, 0, len(s)*2) 12 | j := false 13 | num := len(s) 14 | for i := 0; i < num; i++ { 15 | d := s[i] 16 | if i > 0 && d >= 'A' && d <= 'Z' && j { 17 | data = append(data, '_') 18 | } 19 | if d != '_' { 20 | j = true 21 | } 22 | data = append(data, d) 23 | } 24 | return strings.ToLower(string(data[:])) 25 | } 26 | 27 | // camel string, xx_yy to XxYy 28 | func CamelString(s string) string { 29 | data := make([]byte, 0, len(s)) 30 | j := false 31 | k := false 32 | num := len(s) - 1 33 | for i := 0; i <= num; i++ { 34 | d := s[i] 35 | if k == false && d >= 'A' && d <= 'Z' { 36 | k = true 37 | } 38 | if d >= 'a' && d <= 'z' && (j || k == false) { 39 | d = d - 32 40 | j = false 41 | k = true 42 | } 43 | if k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' { 44 | j = true 45 | continue 46 | } 47 | data = append(data, d) 48 | } 49 | return string(data[:]) 50 | } 51 | 52 | // 获取对象的类型名称 53 | func ObjectName(i interface{}) string { 54 | v := reflect.ValueOf(i) 55 | t := v.Type() 56 | if t.Kind() == reflect.Func { 57 | return runtime.FuncForPC(v.Pointer()).Name() 58 | } 59 | return t.String() 60 | } 61 | -------------------------------------------------------------------------------- /utils/name_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // snake string, XxYy to xx_yy 8 | func TestSnakeString(t *testing.T) { 9 | t.Log(SnakeString("IndexHandle")) 10 | } 11 | 12 | // camel string, xx_yy to XxYy 13 | func TestCamelString(t *testing.T) { 14 | t.Log(CamelString("index_handle")) 15 | } 16 | -------------------------------------------------------------------------------- /utils/new.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // 复制一个对象,返回指针类型的空对象 8 | func NewObjectPtr(i interface{}) interface{} { 9 | t := reflect.TypeOf(i) 10 | if t.Kind() == reflect.Ptr { 11 | t = t.Elem() 12 | } 13 | return reflect.New(t).Interface() 14 | } 15 | -------------------------------------------------------------------------------- /utils/rand.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "crypto/rand" 19 | r "math/rand" 20 | "time" 21 | ) 22 | 23 | // RandomCreateBytes generate random []byte by specify chars. 24 | func RandomCreateBytes(n int, alphabets ...byte) []byte { 25 | const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 26 | var bytes = make([]byte, n) 27 | var randby bool 28 | if num, err := rand.Read(bytes); num != n || err != nil { 29 | r.Seed(time.Now().UnixNano()) 30 | randby = true 31 | } 32 | for i, b := range bytes { 33 | if len(alphabets) == 0 { 34 | if randby { 35 | bytes[i] = alphanum[r.Intn(len(alphanum))] 36 | } else { 37 | bytes[i] = alphanum[b%byte(len(alphanum))] 38 | } 39 | } else { 40 | if randby { 41 | bytes[i] = alphabets[r.Intn(len(alphabets))] 42 | } else { 43 | bytes[i] = alphabets[b%byte(len(alphabets))] 44 | } 45 | } 46 | } 47 | return bytes 48 | } 49 | -------------------------------------------------------------------------------- /utils/safemap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "sync" 19 | ) 20 | 21 | // BeeMap is a map with lock 22 | type BeeMap struct { 23 | lock *sync.RWMutex 24 | bm map[interface{}]interface{} 25 | } 26 | 27 | // NewBeeMap return new safemap 28 | func NewBeeMap() *BeeMap { 29 | return &BeeMap{ 30 | lock: new(sync.RWMutex), 31 | bm: make(map[interface{}]interface{}), 32 | } 33 | } 34 | 35 | // Get from maps return the k's value 36 | func (m *BeeMap) Get(k interface{}) interface{} { 37 | m.lock.RLock() 38 | defer m.lock.RUnlock() 39 | if val, ok := m.bm[k]; ok { 40 | return val 41 | } 42 | return nil 43 | } 44 | 45 | // Set Maps the given key and value. Returns false 46 | // if the key is already in the map and changes nothing. 47 | func (m *BeeMap) Set(k interface{}, v interface{}) bool { 48 | m.lock.Lock() 49 | defer m.lock.Unlock() 50 | if val, ok := m.bm[k]; !ok { 51 | m.bm[k] = v 52 | } else if val != v { 53 | m.bm[k] = v 54 | } else { 55 | return false 56 | } 57 | return true 58 | } 59 | 60 | // Check Returns true if k is exist in the map. 61 | func (m *BeeMap) Check(k interface{}) bool { 62 | m.lock.RLock() 63 | defer m.lock.RUnlock() 64 | if _, ok := m.bm[k]; !ok { 65 | return false 66 | } 67 | return true 68 | } 69 | 70 | // Delete the given key and value. 71 | func (m *BeeMap) Delete(k interface{}) { 72 | m.lock.Lock() 73 | defer m.lock.Unlock() 74 | delete(m.bm, k) 75 | } 76 | 77 | // Items returns all items in safemap. 78 | func (m *BeeMap) Items() map[interface{}]interface{} { 79 | m.lock.RLock() 80 | defer m.lock.RUnlock() 81 | r := make(map[interface{}]interface{}) 82 | for k, v := range m.bm { 83 | r[k] = v 84 | } 85 | return r 86 | } 87 | -------------------------------------------------------------------------------- /utils/safemap_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func Test_beemap(t *testing.T) { 22 | bm := NewBeeMap() 23 | if !bm.Set("astaxie", 1) { 24 | t.Error("set Error") 25 | } 26 | if !bm.Check("astaxie") { 27 | t.Error("check err") 28 | } 29 | 30 | if v := bm.Get("astaxie"); v.(int) != 1 { 31 | t.Error("get err") 32 | } 33 | 34 | bm.Delete("astaxie") 35 | if bm.Check("astaxie") { 36 | t.Error("delete err") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /utils/slice_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 beego Author. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package utils 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestInSlice(t *testing.T) { 22 | sl := []string{"A", "b"} 23 | if !InSlice("A", sl) { 24 | t.Error("should be true") 25 | } 26 | if InSlice("B", sl) { 27 | t.Error("should be false") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /utils/string2bytes.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | // Bytes2String直接转换底层指针,两者指向的相同的内存,改一个另外一个也会变。 8 | // 效率是string([]byte{})的百倍以上,且转换量越大效率优势越明显。 9 | func Bytes2String(b []byte) string { 10 | return *(*string)(unsafe.Pointer(&b)) 11 | } 12 | 13 | // String2Bytes直接转换底层指针,两者指向的相同的内存,改一个另外一个也会变。 14 | // 效率是string([]byte{})的百倍以上,且转换量越大效率优势越明显。 15 | // 转换之后若没做其他操作直接改变里面的字符,则程序会崩溃。 16 | // 如 b:=String2bytes("xxx"); b[1]='d'; 程序将panic。 17 | func String2Bytes(s string) []byte { 18 | x := (*[2]uintptr)(unsafe.Pointer(&s)) 19 | h := [3]uintptr{x[0], x[1], x[1]} 20 | return *(*[]byte)(unsafe.Pointer(&h)) 21 | } 22 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/clock/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ben Johnson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/clock/README.md: -------------------------------------------------------------------------------- 1 | clock [![Build Status](https://drone.io/github.com/benbjohnson/clock/status.png)](https://drone.io/github.com/benbjohnson/clock/latest) [![Coverage Status](https://coveralls.io/repos/benbjohnson/clock/badge.png?branch=master)](https://coveralls.io/r/benbjohnson/clock?branch=master) [![GoDoc](https://godoc.org/github.com/benbjohnson/clock?status.png)](https://godoc.org/github.com/benbjohnson/clock) ![Project status](http://img.shields.io/status/experimental.png?color=red) 2 | ===== 3 | 4 | Clock is a small library for mocking time in Go. It provides an interface 5 | around the standard library's [`time`][time] package so that the application 6 | can use the realtime clock while tests can use the mock clock. 7 | 8 | [time]: http://golang.org/pkg/time/ 9 | 10 | 11 | ## Usage 12 | 13 | ### Realtime Clock 14 | 15 | Your application can maintain a `Clock` variable that will allow realtime and 16 | mock clocks to be interchangable. For example, if you had an `Application` type: 17 | 18 | ```go 19 | import "github.com/benbjohnson/clock" 20 | 21 | type Application struct { 22 | Clock clock.Clock 23 | } 24 | ``` 25 | 26 | You could initialize it to use the realtime clock like this: 27 | 28 | ```go 29 | var app Application 30 | app.Clock = clock.New() 31 | ... 32 | ``` 33 | 34 | Then all timers and time-related functionality should be performed from the 35 | `Clock` variable. 36 | 37 | 38 | ### Mocking time 39 | 40 | In your tests, you will want to use a `Mock` clock: 41 | 42 | ```go 43 | import ( 44 | "testing" 45 | 46 | "github.com/benbjohnson/clock" 47 | ) 48 | 49 | func TestApplication_DoSomething(t *testing.T) { 50 | mock := clock.NewMock() 51 | app := Application{Clock: mock} 52 | ... 53 | } 54 | ``` 55 | 56 | Now that you've initialized your application to use the mock clock, you can 57 | adjust the time programmatically. The mock clock always starts from the Unix 58 | epoch (midnight, Jan 1, 1970 UTC). 59 | 60 | 61 | ### Controlling time 62 | 63 | The mock clock provides the same functions that the standard library's `time` 64 | package provides. For example, to find the current time, you use the `Now()` 65 | function: 66 | 67 | ```go 68 | mock := clock.NewMock() 69 | 70 | // Find the current time. 71 | mock.Now().UTC() // 1970-01-01 00:00:00 +0000 UTC 72 | 73 | // Move the clock forward. 74 | mock.Add(2 * time.Hour) 75 | 76 | // Check the time again. It's 2 hours later! 77 | mock.Now().UTC() // 1970-01-01 02:00:00 +0000 UTC 78 | ``` 79 | 80 | Timers and Tickers are also controlled by this same mock clock. They will only 81 | execute when the clock is moved forward: 82 | 83 | ``` 84 | mock := clock.NewMock() 85 | count := 0 86 | 87 | // Kick off a timer to increment every 1 mock second. 88 | go func() { 89 | ticker := clock.Ticker(1 * time.Second) 90 | for { 91 | <-ticker.C 92 | count++ 93 | } 94 | }() 95 | runtime.Gosched() 96 | 97 | // Move the clock forward 10 second. 98 | mock.Add(10 * time.Second) 99 | 100 | // This prints 10. 101 | fmt.Println(count) 102 | ``` 103 | 104 | 105 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/grace/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.7 5 | 6 | before_install: 7 | - go get -v github.com/golang/lint/golint 8 | 9 | install: 10 | - go install -race -v std 11 | - go get -race -t -v ./... 12 | - go install -race -v ./... 13 | 14 | script: 15 | - go vet ./... 16 | - $HOME/gopath/bin/golint . 17 | - go test -cpu=2 -race -v ./... 18 | - go test -cpu=2 -covermode=atomic ./... 19 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/grace/gracedemo/demo.go: -------------------------------------------------------------------------------- 1 | // Command gracedemo implements a demo server showing how to gracefully 2 | // terminate an HTTP server using grace. 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "fmt" 8 | "net/http" 9 | "os" 10 | "time" 11 | 12 | "github.com/facebookgo/grace/gracehttp" 13 | ) 14 | 15 | var ( 16 | address0 = flag.String("a0", ":48567", "Zero address to bind to.") 17 | address1 = flag.String("a1", ":48568", "First address to bind to.") 18 | address2 = flag.String("a2", ":48569", "Second address to bind to.") 19 | now = time.Now() 20 | ) 21 | 22 | func main() { 23 | flag.Parse() 24 | gracehttp.Serve( 25 | &http.Server{Addr: *address0, Handler: newHandler("Zero ")}, 26 | &http.Server{Addr: *address1, Handler: newHandler("First ")}, 27 | &http.Server{Addr: *address2, Handler: newHandler("Second")}, 28 | ) 29 | } 30 | 31 | func newHandler(name string) http.Handler { 32 | mux := http.NewServeMux() 33 | mux.HandleFunc("/sleep/", func(w http.ResponseWriter, r *http.Request) { 34 | duration, err := time.ParseDuration(r.FormValue("duration")) 35 | if err != nil { 36 | http.Error(w, err.Error(), 400) 37 | return 38 | } 39 | time.Sleep(duration) 40 | fmt.Fprintf( 41 | w, 42 | "%s started at %s slept for %d nanoseconds from pid %d.\n", 43 | name, 44 | now, 45 | duration.Nanoseconds(), 46 | os.Getpid(), 47 | ) 48 | }) 49 | return mux 50 | } 51 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/grace/gracehttp/http.go: -------------------------------------------------------------------------------- 1 | // Package gracehttp provides easy to use graceful restart 2 | // functionality for HTTP server. 3 | // modified by henrylee 2016.10.29 4 | package gracehttp 5 | 6 | import ( 7 | "bytes" 8 | "crypto/tls" 9 | "flag" 10 | "fmt" 11 | "net" 12 | "net/http" 13 | "os" 14 | "sync" 15 | 16 | "github.com/facebookgo/grace/gracenet" 17 | "github.com/facebookgo/httpdown" 18 | ) 19 | 20 | var ( 21 | verbose = flag.Bool("gracehttp.log", true, "Enable logging.") 22 | didInherit = os.Getenv("LISTEN_FDS") != "" 23 | ppid = os.Getppid() 24 | ) 25 | 26 | // An app contains one or more servers and associated configuration. 27 | type app struct { 28 | servers []*http.Server 29 | http *httpdown.HTTP 30 | net *gracenet.Net 31 | listeners []net.Listener 32 | sds []httpdown.Server 33 | errors chan error 34 | terminateFunc func() error 35 | } 36 | 37 | func newApp(servers []*http.Server) *app { 38 | return &app{ 39 | servers: servers, 40 | http: &httpdown.HTTP{}, 41 | net: &gracenet.Net{}, 42 | listeners: make([]net.Listener, 0, len(servers)), 43 | sds: make([]httpdown.Server, 0, len(servers)), 44 | 45 | // 2x num servers for possible Close or Stop errors + 1 for possible 46 | // StartProcess error + 1 for possible terminateFunc error. 47 | errors: make(chan error, 2+(len(servers)*2)), 48 | } 49 | } 50 | 51 | func (a *app) listen() error { 52 | for _, s := range a.servers { 53 | // TODO: default addresses 54 | l, err := a.net.Listen("tcp", s.Addr) 55 | if err != nil { 56 | return err 57 | } 58 | if s.TLSConfig != nil { 59 | l = tls.NewListener(l, s.TLSConfig) 60 | } 61 | a.listeners = append(a.listeners, l) 62 | } 63 | return nil 64 | } 65 | 66 | func (a *app) serve() { 67 | for i, s := range a.servers { 68 | a.sds = append(a.sds, a.http.Serve(s, a.listeners[i])) 69 | } 70 | } 71 | 72 | func (a *app) wait() { 73 | var wg sync.WaitGroup 74 | wg.Add(len(a.sds) * 2) // Wait & Stop 75 | go a.signalHandler(&wg) 76 | for _, s := range a.sds { 77 | go func(s httpdown.Server) { 78 | defer wg.Done() 79 | if err := s.Wait(); err != nil { 80 | a.errors <- err 81 | } 82 | }(s) 83 | } 84 | wg.Wait() 85 | } 86 | 87 | func (a *app) term(wg *sync.WaitGroup) { 88 | for _, s := range a.sds { 89 | go func(s httpdown.Server) { 90 | defer wg.Done() 91 | if err := s.Stop(); err != nil { 92 | a.errors <- err 93 | } 94 | }(s) 95 | } 96 | } 97 | 98 | // Serve will serve the given http.Servers and will monitor for signals 99 | // allowing for graceful termination (SIGTERM) or restart (SIGUSR2). 100 | func Serve(servers ...*http.Server) error { 101 | return ServeWithTerminateFunc(nil, servers...) 102 | } 103 | 104 | // Used for pretty printing addresses. 105 | func pprintAddr(listeners []net.Listener) []byte { 106 | var out bytes.Buffer 107 | for i, l := range listeners { 108 | if i != 0 { 109 | fmt.Fprint(&out, ", ") 110 | } 111 | fmt.Fprint(&out, l.Addr()) 112 | } 113 | return out.Bytes() 114 | } 115 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/grace/gracehttp/http_darwin.go: -------------------------------------------------------------------------------- 1 | // Package gracehttp provides easy to use graceful restart 2 | // functionality for HTTP server. 3 | // modified by henrylee 2016.10.29 4 | 5 | package gracehttp 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "net/http" 11 | "os" 12 | "os/signal" 13 | "sync" 14 | "syscall" 15 | ) 16 | 17 | func (a *app) signalHandler(wg *sync.WaitGroup) { 18 | ch := make(chan os.Signal, 10) 19 | signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2) 20 | for { 21 | sig := <-ch 22 | switch sig { 23 | case syscall.SIGINT, syscall.SIGTERM: 24 | // this ensures a subsequent INT/TERM will trigger standard go behaviour of 25 | // terminating. 26 | signal.Stop(ch) 27 | if err := a.terminateFunc(); err != nil { 28 | a.errors <- err 29 | } 30 | a.term(wg) 31 | return 32 | case syscall.SIGUSR2: 33 | if err := a.terminateFunc(); err != nil { 34 | signal.Stop(ch) 35 | a.term(wg) 36 | return 37 | } 38 | // we only return here if there's an error, otherwise the new process 39 | // will send us a TERM when it's ready to trigger the actual shutdown. 40 | if _, err := a.net.StartProcess(); err != nil { 41 | a.errors <- err 42 | } 43 | } 44 | } 45 | } 46 | 47 | // ServeWithTerminateFunc will serve the given http.Servers and will monitor for signals 48 | // allowing for graceful termination (SIGTERM) or restart (SIGUSR2). 49 | func ServeWithTerminateFunc(terminateFunc func() error, servers ...*http.Server) error { 50 | a := newApp(servers) 51 | if terminateFunc == nil { 52 | a.terminateFunc = func() error { 53 | return nil 54 | } 55 | } else { 56 | a.terminateFunc = terminateFunc 57 | } 58 | 59 | // Acquire Listeners 60 | if err := a.listen(); err != nil { 61 | return err 62 | } 63 | 64 | // Some useful logging. 65 | if *verbose { 66 | if didInherit { 67 | if ppid == 1 { 68 | log.Printf("Listening on init activated %s", pprintAddr(a.listeners)) 69 | } else { 70 | const msg = "Graceful handoff of %s with new pid %d and old pid %d" 71 | log.Printf(msg, pprintAddr(a.listeners), os.Getpid(), ppid) 72 | } 73 | } else { 74 | const msg = "Serving %s with pid %d" 75 | log.Printf(msg, pprintAddr(a.listeners), os.Getpid()) 76 | } 77 | } 78 | 79 | // Start serving. 80 | a.serve() 81 | 82 | // Close the parent if we inherited and it wasn't init that started us. 83 | if didInherit && ppid != 1 { 84 | if err := syscall.Kill(ppid, syscall.SIGTERM); err != nil { 85 | return fmt.Errorf("failed to close parent: %s", err) 86 | } 87 | } 88 | 89 | waitdone := make(chan struct{}) 90 | go func() { 91 | defer close(waitdone) 92 | a.wait() 93 | }() 94 | 95 | select { 96 | case err := <-a.errors: 97 | if err == nil { 98 | panic("unexpected nil error") 99 | } 100 | return err 101 | case <-waitdone: 102 | if *verbose { 103 | log.Printf("Exiting pid %d.", os.Getpid()) 104 | } 105 | return nil 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/grace/gracehttp/http_freebsd.go: -------------------------------------------------------------------------------- 1 | // Package gracehttp provides easy to use graceful restart 2 | // functionality for HTTP server. 3 | // modified by henrylee 2016.10.29 4 | 5 | package gracehttp 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "net/http" 11 | "os" 12 | "os/signal" 13 | "sync" 14 | "syscall" 15 | ) 16 | 17 | func (a *app) signalHandler(wg *sync.WaitGroup) { 18 | ch := make(chan os.Signal, 10) 19 | signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2) 20 | for { 21 | sig := <-ch 22 | switch sig { 23 | case syscall.SIGINT, syscall.SIGTERM: 24 | // this ensures a subsequent INT/TERM will trigger standard go behaviour of 25 | // terminating. 26 | signal.Stop(ch) 27 | if err := a.terminateFunc(); err != nil { 28 | a.errors <- err 29 | } 30 | a.term(wg) 31 | return 32 | case syscall.SIGUSR2: 33 | if err := a.terminateFunc(); err != nil { 34 | signal.Stop(ch) 35 | a.term(wg) 36 | return 37 | } 38 | // we only return here if there's an error, otherwise the new process 39 | // will send us a TERM when it's ready to trigger the actual shutdown. 40 | if _, err := a.net.StartProcess(); err != nil { 41 | a.errors <- err 42 | } 43 | } 44 | } 45 | } 46 | 47 | // ServeWithTerminateFunc will serve the given http.Servers and will monitor for signals 48 | // allowing for graceful termination (SIGTERM) or restart (SIGUSR2). 49 | func ServeWithTerminateFunc(terminateFunc func() error, servers ...*http.Server) error { 50 | a := newApp(servers) 51 | if terminateFunc == nil { 52 | a.terminateFunc = func() error { 53 | return nil 54 | } 55 | } else { 56 | a.terminateFunc = terminateFunc 57 | } 58 | 59 | // Acquire Listeners 60 | if err := a.listen(); err != nil { 61 | return err 62 | } 63 | 64 | // Some useful logging. 65 | if *verbose { 66 | if didInherit { 67 | if ppid == 1 { 68 | log.Printf("Listening on init activated %s", pprintAddr(a.listeners)) 69 | } else { 70 | const msg = "Graceful handoff of %s with new pid %d and old pid %d" 71 | log.Printf(msg, pprintAddr(a.listeners), os.Getpid(), ppid) 72 | } 73 | } else { 74 | const msg = "Serving %s with pid %d" 75 | log.Printf(msg, pprintAddr(a.listeners), os.Getpid()) 76 | } 77 | } 78 | 79 | // Start serving. 80 | a.serve() 81 | 82 | // Close the parent if we inherited and it wasn't init that started us. 83 | if didInherit && ppid != 1 { 84 | if err := syscall.Kill(ppid, syscall.SIGTERM); err != nil { 85 | return fmt.Errorf("failed to close parent: %s", err) 86 | } 87 | } 88 | 89 | waitdone := make(chan struct{}) 90 | go func() { 91 | defer close(waitdone) 92 | a.wait() 93 | }() 94 | 95 | select { 96 | case err := <-a.errors: 97 | if err == nil { 98 | panic("unexpected nil error") 99 | } 100 | return err 101 | case <-waitdone: 102 | if *verbose { 103 | log.Printf("Exiting pid %d.", os.Getpid()) 104 | } 105 | return nil 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/grace/gracehttp/http_linux.go: -------------------------------------------------------------------------------- 1 | // Package gracehttp provides easy to use graceful restart 2 | // functionality for HTTP server. 3 | // modified by henrylee 2016.10.29 4 | 5 | package gracehttp 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "net/http" 11 | "os" 12 | "os/signal" 13 | "sync" 14 | "syscall" 15 | ) 16 | 17 | func (a *app) signalHandler(wg *sync.WaitGroup) { 18 | ch := make(chan os.Signal, 10) 19 | signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2) 20 | for { 21 | sig := <-ch 22 | switch sig { 23 | case syscall.SIGINT, syscall.SIGTERM: 24 | // this ensures a subsequent INT/TERM will trigger standard go behaviour of 25 | // terminating. 26 | signal.Stop(ch) 27 | if err := a.terminateFunc(); err != nil { 28 | a.errors <- err 29 | } 30 | a.term(wg) 31 | return 32 | case syscall.SIGUSR2: 33 | if err := a.terminateFunc(); err != nil { 34 | signal.Stop(ch) 35 | a.term(wg) 36 | return 37 | } 38 | // we only return here if there's an error, otherwise the new process 39 | // will send us a TERM when it's ready to trigger the actual shutdown. 40 | if _, err := a.net.StartProcess(); err != nil { 41 | a.errors <- err 42 | } 43 | } 44 | } 45 | } 46 | 47 | // ServeWithTerminateFunc will serve the given http.Servers and will monitor for signals 48 | // allowing for graceful termination (SIGTERM) or restart (SIGUSR2). 49 | func ServeWithTerminateFunc(terminateFunc func() error, servers ...*http.Server) error { 50 | a := newApp(servers) 51 | if terminateFunc == nil { 52 | a.terminateFunc = func() error { 53 | return nil 54 | } 55 | } else { 56 | a.terminateFunc = terminateFunc 57 | } 58 | 59 | // Acquire Listeners 60 | if err := a.listen(); err != nil { 61 | return err 62 | } 63 | 64 | // Some useful logging. 65 | if *verbose { 66 | if didInherit { 67 | if ppid == 1 { 68 | log.Printf("Listening on init activated %s", pprintAddr(a.listeners)) 69 | } else { 70 | const msg = "Graceful handoff of %s with new pid %d and old pid %d" 71 | log.Printf(msg, pprintAddr(a.listeners), os.Getpid(), ppid) 72 | } 73 | } else { 74 | const msg = "Serving %s with pid %d" 75 | log.Printf(msg, pprintAddr(a.listeners), os.Getpid()) 76 | } 77 | } 78 | 79 | // Start serving. 80 | a.serve() 81 | 82 | // Close the parent if we inherited and it wasn't init that started us. 83 | if didInherit && ppid != 1 { 84 | if err := syscall.Kill(ppid, syscall.SIGTERM); err != nil { 85 | return fmt.Errorf("failed to close parent: %s", err) 86 | } 87 | } 88 | 89 | waitdone := make(chan struct{}) 90 | go func() { 91 | defer close(waitdone) 92 | a.wait() 93 | }() 94 | 95 | select { 96 | case err := <-a.errors: 97 | if err == nil { 98 | panic("unexpected nil error") 99 | } 100 | return err 101 | case <-waitdone: 102 | if *verbose { 103 | log.Printf("Exiting pid %d.", os.Getpid()) 104 | } 105 | return nil 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/grace/gracehttp/http_windows.go: -------------------------------------------------------------------------------- 1 | // Package gracehttp provides easy to use graceful restart 2 | // functionality for HTTP server. 3 | // modified by henrylee 2016.10.29 4 | 5 | package gracehttp 6 | 7 | import ( 8 | "fmt" 9 | "log" 10 | "net/http" 11 | "os" 12 | "os/exec" 13 | "os/signal" 14 | "strconv" 15 | "sync" 16 | "syscall" 17 | ) 18 | 19 | func (a *app) signalHandler(wg *sync.WaitGroup) { 20 | ch := make(chan os.Signal, 10) 21 | signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) 22 | for { 23 | sig := <-ch 24 | switch sig { 25 | case syscall.SIGINT, syscall.SIGTERM: 26 | // this ensures a subsequent INT/TERM will trigger standard go behaviour of 27 | // terminating. 28 | signal.Stop(ch) 29 | if err := a.terminateFunc(); err != nil { 30 | a.errors <- err 31 | } 32 | a.term(wg) 33 | return 34 | } 35 | } 36 | } 37 | 38 | // ServeWithTerminateFunc will serve the given http.Servers and will monitor for signals 39 | // allowing for graceful termination (SIGINT). 40 | func ServeWithTerminateFunc(terminateFunc func() error, servers ...*http.Server) error { 41 | a := newApp(servers) 42 | if terminateFunc == nil { 43 | a.terminateFunc = func() error { 44 | return nil 45 | } 46 | } else { 47 | a.terminateFunc = terminateFunc 48 | } 49 | 50 | // Acquire Listeners 51 | if err := a.listen(); err != nil { 52 | return err 53 | } 54 | 55 | // Some useful logging. 56 | if *verbose { 57 | if didInherit { 58 | if ppid == 1 { 59 | log.Printf("Listening on init activated %s", pprintAddr(a.listeners)) 60 | } else { 61 | const msg = "Graceful handoff of %s with new pid %d and old pid %d" 62 | log.Printf(msg, pprintAddr(a.listeners), os.Getpid(), ppid) 63 | } 64 | } else { 65 | const msg = "Serving %s with pid %d" 66 | log.Printf(msg, pprintAddr(a.listeners), os.Getpid()) 67 | } 68 | } 69 | 70 | // Start serving. 71 | a.serve() 72 | 73 | // Close the parent if we inherited and it wasn't init that started us. 74 | if didInherit && ppid != 1 { 75 | c := exec.Command("TASKKILL", "/PID", strconv.Itoa(ppid)) 76 | err := c.Run() 77 | if err != nil { 78 | return fmt.Errorf("failed to close parent: %s", err) 79 | } 80 | } 81 | 82 | waitdone := make(chan struct{}) 83 | go func() { 84 | defer close(waitdone) 85 | a.wait() 86 | }() 87 | 88 | select { 89 | case err := <-a.errors: 90 | if err == nil { 91 | panic("unexpected nil error") 92 | } 93 | return err 94 | case <-waitdone: 95 | if *verbose { 96 | log.Printf("Exiting pid %d.", os.Getpid()) 97 | } 98 | return nil 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/grace/license: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For grace software 4 | 5 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/grace/patents: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the grace software distributed by Facebook, Inc. 4 | 5 | Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software 6 | ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary 8 | Claims, to make, have made, use, sell, offer to sell, import, and otherwise 9 | transfer the Software. For avoidance of doubt, no license is granted under 10 | Facebook’s rights in any patent claims that are infringed by (i) modifications 11 | to the Software made by you or any third party or (ii) the Software in 12 | combination with any software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Facebook or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Facebook or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating 21 | to the Software. Notwithstanding the foregoing, if Facebook or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Facebook that is 29 | necessarily infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. 34 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/grace/readme.md: -------------------------------------------------------------------------------- 1 | grace [![Build Status](https://secure.travis-ci.org/facebookgo/grace.png)](https://travis-ci.org/facebookgo/grace) 2 | ===== 3 | 4 | Package grace provides a library that makes it easy to build socket 5 | based servers that can be gracefully terminated & restarted (that is, 6 | without dropping any connections). 7 | 8 | It provides a convenient API for HTTP servers including support for TLS, 9 | especially if you need to listen on multiple ports (for example a secondary 10 | internal only admin server). Additionally it is implemented using the same API 11 | as systemd providing [socket 12 | activation](http://0pointer.de/blog/projects/socket-activation.html) 13 | compatibility to also provide lazy activation of the server. 14 | 15 | 16 | Usage 17 | ----- 18 | 19 | Demo HTTP Server with graceful termination and restart: 20 | https://github.com/facebookgo/grace/blob/master/gracedemo/demo.go 21 | 22 | 1. Install the demo application 23 | 24 | go get github.com/facebookgo/grace/gracedemo 25 | 26 | 1. Start it in the first terminal 27 | 28 | gracedemo 29 | 30 | This will output something like: 31 | 32 | 2013/03/25 19:07:33 Serving [::]:48567, [::]:48568, [::]:48569 with pid 14642. 33 | 34 | 1. In a second terminal start a slow HTTP request 35 | 36 | curl 'http://localhost:48567/sleep/?duration=20s' 37 | 38 | 1. In a third terminal trigger a graceful server restart (using the pid from your output): 39 | 40 | kill -USR2 14642 41 | 42 | 1. Trigger another shorter request that finishes before the earlier request: 43 | 44 | curl 'http://localhost:48567/sleep/?duration=0s' 45 | 46 | 47 | If done quickly enough, this shows the second quick request will be served by 48 | the new process (as indicated by the PID) while the slow first request will be 49 | served by the first server. It shows how the active connection was gracefully 50 | served before the server was shutdown. It is also showing that at one point 51 | both the new as well as the old server was running at the same time. 52 | 53 | 54 | Documentation 55 | ------------- 56 | 57 | `http.Server` graceful termination and restart: 58 | https://godoc.org/github.com/facebookgo/grace/gracehttp 59 | 60 | `net.Listener` graceful termination and restart: 61 | https://godoc.org/github.com/facebookgo/grace/gracenet 62 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/httpdown/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.6 5 | 6 | before_install: 7 | - go get -v golang.org/x/tools/cmd/vet 8 | - go get -v golang.org/x/tools/cmd/cover 9 | - go get -v github.com/golang/lint/golint 10 | 11 | install: 12 | - go install -race -v std 13 | - go get -race -t -v ./... 14 | - go install -race -v ./... 15 | 16 | script: 17 | - go vet ./... 18 | - $HOME/gopath/bin/golint . 19 | - go test -cpu=2 -race -v ./... 20 | - go test -cpu=2 -covermode=atomic -coverprofile=coverage.txt ./ 21 | 22 | after_success: 23 | - bash <(curl -s https://codecov.io/bash) 24 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/httpdown/httpdown_example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "time" 9 | 10 | "github.com/facebookgo/httpdown" 11 | ) 12 | 13 | func handler(w http.ResponseWriter, r *http.Request) { 14 | duration, err := time.ParseDuration(r.FormValue("duration")) 15 | if err != nil { 16 | http.Error(w, err.Error(), 400) 17 | return 18 | } 19 | fmt.Fprintf(w, "going to sleep %s with pid %d\n", duration, os.Getpid()) 20 | w.(http.Flusher).Flush() 21 | time.Sleep(duration) 22 | fmt.Fprintf(w, "slept %s with pid %d\n", duration, os.Getpid()) 23 | } 24 | 25 | func main() { 26 | server := &http.Server{ 27 | Addr: "127.0.0.1:8080", 28 | Handler: http.HandlerFunc(handler), 29 | } 30 | hd := &httpdown.HTTP{ 31 | StopTimeout: 10 * time.Second, 32 | KillTimeout: 1 * time.Second, 33 | } 34 | 35 | flag.StringVar(&server.Addr, "addr", server.Addr, "http address") 36 | flag.DurationVar(&hd.StopTimeout, "stop-timeout", hd.StopTimeout, "stop timeout") 37 | flag.DurationVar(&hd.KillTimeout, "kill-timeout", hd.KillTimeout, "kill timeout") 38 | flag.Parse() 39 | 40 | if err := httpdown.ListenAndServe(server, hd); err != nil { 41 | panic(err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/httpdown/license: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For httpdown software 4 | 5 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/httpdown/patents: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the httpdown software distributed by Facebook, Inc. 4 | 5 | Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software 6 | ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary 8 | Claims, to make, have made, use, sell, offer to sell, import, and otherwise 9 | transfer the Software. For avoidance of doubt, no license is granted under 10 | Facebook’s rights in any patent claims that are infringed by (i) modifications 11 | to the Software made by you or any third party or (ii) the Software in 12 | combination with any software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Facebook or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Facebook or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating 21 | to the Software. Notwithstanding the foregoing, if Facebook or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Facebook that is 29 | necessarily infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. 34 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/httpdown/readme.md: -------------------------------------------------------------------------------- 1 | httpdown [![Build Status](https://secure.travis-ci.org/facebookgo/httpdown.png)](https://travis-ci.org/facebookgo/httpdown) 2 | ======== 3 | 4 | Documentation: https://godoc.org/github.com/facebookgo/httpdown 5 | 6 | Package httpdown provides a library that makes it easy to build a HTTP server 7 | that can be shutdown gracefully (that is, without dropping any connections). 8 | 9 | If you want graceful restart and not just graceful shutdown, look at the 10 | [grace](https://github.com/facebookgo/grace) package which uses this package 11 | underneath but also provides graceful restart. 12 | 13 | Usage 14 | ----- 15 | 16 | Demo HTTP Server with graceful termination: 17 | https://github.com/facebookgo/httpdown/blob/master/httpdown_example/main.go 18 | 19 | 1. Install the demo application 20 | 21 | go get github.com/facebookgo/httpdown/httpdown_example 22 | 23 | 1. Start it in the first terminal 24 | 25 | httpdown_example 26 | 27 | This will output something like: 28 | 29 | 2014/11/18 21:57:50 serving on http://127.0.0.1:8080/ with pid 17 30 | 31 | 1. In a second terminal start a slow HTTP request 32 | 33 | curl 'http://localhost:8080/?duration=20s' 34 | 35 | 1. In a third terminal trigger a graceful shutdown (using the pid from your output): 36 | 37 | kill -TERM 17 38 | 39 | This will demonstrate that the slow request was served before the server was 40 | shutdown. You could also have used `Ctrl-C` instead of `kill` as the example 41 | application triggers graceful shutdown on TERM or INT signals. 42 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/stats/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5 5 | 6 | before_install: 7 | - go get -v golang.org/x/tools/cmd/vet 8 | - go get -v golang.org/x/tools/cmd/cover 9 | - go get -v github.com/golang/lint/golint 10 | 11 | install: 12 | - go install -race -v std 13 | - go get -race -t -v ./... 14 | - go install -race -v ./... 15 | 16 | script: 17 | - go vet ./... 18 | - $HOME/gopath/bin/golint . 19 | - go test -cpu=2 -race -v ./... 20 | - go test -cpu=2 -covermode=atomic ./... 21 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/stats/aggregation.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import "sort" 4 | 5 | // Average returns the average value 6 | func Average(values []float64) float64 { 7 | if len(values) == 0 { 8 | return 0 9 | } 10 | 11 | var val float64 12 | for _, point := range values { 13 | val += point 14 | } 15 | return val / float64(len(values)) 16 | } 17 | 18 | // Sum returns the sum of all the given values 19 | func Sum(values []float64) float64 { 20 | var val float64 21 | for _, point := range values { 22 | val += point 23 | } 24 | return val 25 | } 26 | 27 | // Percentiles returns a map containing the asked for percentiles 28 | func Percentiles(values []float64, percentiles map[string]float64) map[string]float64 { 29 | sort.Float64s(values) 30 | results := map[string]float64{} 31 | for label, p := range percentiles { 32 | results[label] = values[int(float64(len(values))*p)] 33 | } 34 | return results 35 | } 36 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/stats/aggregation_test.go: -------------------------------------------------------------------------------- 1 | package stats_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/facebookgo/ensure" 7 | "github.com/facebookgo/stats" 8 | ) 9 | 10 | func TestAverage(t *testing.T) { 11 | t.Parallel() 12 | ensure.DeepEqual(t, stats.Average([]float64{}), 0.0) 13 | ensure.DeepEqual(t, stats.Average([]float64{1}), 1.0) 14 | ensure.DeepEqual(t, stats.Average([]float64{1, 2}), 1.5) 15 | ensure.DeepEqual(t, stats.Average([]float64{1, 2, 3}), 2.0) 16 | } 17 | 18 | func TestSum(t *testing.T) { 19 | t.Parallel() 20 | ensure.DeepEqual(t, stats.Sum([]float64{}), 0.0) 21 | ensure.DeepEqual(t, stats.Sum([]float64{1}), 1.0) 22 | ensure.DeepEqual(t, stats.Sum([]float64{1, 2}), 3.0) 23 | ensure.DeepEqual(t, stats.Sum([]float64{1, 2, 3}), 6.0) 24 | } 25 | 26 | func TestPercentiles(t *testing.T) { 27 | t.Parallel() 28 | percentiles := map[string]float64{ 29 | "p50": 0.5, 30 | "p90": 0.9, 31 | } 32 | input := []float64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 33 | expected := map[string]float64{ 34 | "p50": 5, 35 | "p90": 9, 36 | } 37 | ensure.DeepEqual(t, stats.Percentiles(input, percentiles), expected) 38 | } 39 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/stats/counter.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import "fmt" 4 | 5 | // Type is the type of aggregation of apply 6 | type Type int 7 | 8 | const ( 9 | AggregateAvg Type = iota 10 | AggregateSum 11 | AggregateHistogram 12 | ) 13 | 14 | var ( 15 | // HistogramPercentiles is used to determine which percentiles to return for 16 | // SimpleCounter.Aggregate 17 | HistogramPercentiles = map[string]float64{ 18 | "p50": 0.5, 19 | "p95": 0.95, 20 | "p99": 0.99, 21 | } 22 | 23 | // MinSamplesForPercentiles is used by SimpleCounter.Aggregate to determine 24 | // what the minimum number of samples is required for percentile analysis 25 | MinSamplesForPercentiles = 10 26 | ) 27 | 28 | // Aggregates can be used to merge counters together. This is not goroutine safe 29 | type Aggregates map[string]Counter 30 | 31 | // Add adds the counter for aggregation. This is not goroutine safe 32 | func (a Aggregates) Add(c Counter) error { 33 | key := c.FullKey() 34 | if counter, ok := a[key]; ok { 35 | if counter.GetType() != c.GetType() { 36 | return fmt.Errorf("stats: mismatched aggregation type for: %s", key) 37 | } 38 | counter.AddValues(c.GetValues()...) 39 | } else { 40 | a[key] = c 41 | } 42 | return nil 43 | } 44 | 45 | // Counter is the interface used by Aggregates to merge counters together 46 | type Counter interface { 47 | // FullKey is used to uniquely identify the counter 48 | FullKey() string 49 | 50 | // AddValues adds values for aggregation 51 | AddValues(...float64) 52 | 53 | // GetValues returns the values for aggregation 54 | GetValues() []float64 55 | 56 | // GetType returns the type of aggregation to apply 57 | GetType() Type 58 | } 59 | 60 | // SimpleCounter is a basic implementation of the Counter interface 61 | type SimpleCounter struct { 62 | Key string 63 | Values []float64 64 | Type Type 65 | } 66 | 67 | // FullKey is part of the Counter interace 68 | func (s *SimpleCounter) FullKey() string { 69 | return s.Key 70 | } 71 | 72 | // GetValues is part of the Counter interface 73 | func (s *SimpleCounter) GetValues() []float64 { 74 | return s.Values 75 | } 76 | 77 | // AddValues is part of the Counter interface 78 | func (s *SimpleCounter) AddValues(vs ...float64) { 79 | s.Values = append(s.Values, vs...) 80 | } 81 | 82 | // GetType is part of the Counter interface 83 | func (s *SimpleCounter) GetType() Type { 84 | return s.Type 85 | } 86 | 87 | // Aggregate aggregates the provided values appropriately, returning a map 88 | // from key to value. If AggregateHistogram is specified, the map will contain 89 | // the relevant percentiles as specified by HistogramPercentiles 90 | func (s *SimpleCounter) Aggregate() map[string]float64 { 91 | switch s.Type { 92 | case AggregateAvg: 93 | return map[string]float64{ 94 | s.Key: Average(s.Values), 95 | } 96 | case AggregateSum: 97 | return map[string]float64{ 98 | s.Key: Sum(s.Values), 99 | } 100 | case AggregateHistogram: 101 | histogram := map[string]float64{ 102 | s.Key: Average(s.Values), 103 | } 104 | if len(s.Values) > MinSamplesForPercentiles { 105 | for k, v := range Percentiles(s.Values, HistogramPercentiles) { 106 | histogram[fmt.Sprintf("%s.%s", s.Key, k)] = v 107 | } 108 | } 109 | return histogram 110 | } 111 | panic("stats: unsupported aggregation type") 112 | } 113 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/stats/counter_test.go: -------------------------------------------------------------------------------- 1 | package stats_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/facebookgo/ensure" 7 | "github.com/facebookgo/stats" 8 | ) 9 | 10 | func TestSimpleCounterAggregation(t *testing.T) { 11 | t.Parallel() 12 | 13 | a := stats.Aggregates{} 14 | a.Add(&stats.SimpleCounter{ 15 | Key: "foo.avg", 16 | Values: []float64{1}, 17 | Type: stats.AggregateAvg, 18 | }) 19 | a.Add(&stats.SimpleCounter{ 20 | Key: "foo.sum", 21 | Values: []float64{1}, 22 | Type: stats.AggregateSum, 23 | }) 24 | a.Add(&stats.SimpleCounter{ 25 | Key: "foo.time", 26 | Values: []float64{0, 1, 2, 3, 4}, 27 | Type: stats.AggregateHistogram, 28 | }) 29 | a.Add(&stats.SimpleCounter{ 30 | Key: "foo.sum", 31 | Values: []float64{2}, 32 | Type: stats.AggregateSum, 33 | }) 34 | a.Add(&stats.SimpleCounter{ 35 | Key: "foo.time", 36 | Values: []float64{5, 6, 7, 8, 9, 10}, 37 | Type: stats.AggregateHistogram, 38 | }) 39 | a.Add(&stats.SimpleCounter{ 40 | Key: "foo.avg", 41 | Values: []float64{2}, 42 | Type: stats.AggregateAvg, 43 | }) 44 | 45 | all := map[string]float64{} 46 | for _, counter := range a { 47 | for key, value := range counter.(*stats.SimpleCounter).Aggregate() { 48 | all[key] = value 49 | } 50 | } 51 | 52 | ensure.DeepEqual(t, all, map[string]float64{ 53 | "foo.avg": 1.5, 54 | "foo.sum": 3.0, 55 | "foo.time": 5.0, 56 | "foo.time.p50": 5.0, 57 | "foo.time.p95": 10.0, 58 | "foo.time.p99": 10.0, 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/stats/license: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For stats software 4 | 5 | Copyright (c) 2015, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/stats/patents: -------------------------------------------------------------------------------- 1 | Additional Grant of Patent Rights Version 2 2 | 3 | "Software" means the stats software distributed by Facebook, Inc. 4 | 5 | Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software 6 | ("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable 7 | (subject to the termination provision below) license under any Necessary 8 | Claims, to make, have made, use, sell, offer to sell, import, and otherwise 9 | transfer the Software. For avoidance of doubt, no license is granted under 10 | Facebook’s rights in any patent claims that are infringed by (i) modifications 11 | to the Software made by you or any third party or (ii) the Software in 12 | combination with any software or other technology. 13 | 14 | The license granted hereunder will terminate, automatically and without notice, 15 | if you (or any of your subsidiaries, corporate affiliates or agents) initiate 16 | directly or indirectly, or take a direct financial interest in, any Patent 17 | Assertion: (i) against Facebook or any of its subsidiaries or corporate 18 | affiliates, (ii) against any party if such Patent Assertion arises in whole or 19 | in part from any software, technology, product or service of Facebook or any of 20 | its subsidiaries or corporate affiliates, or (iii) against any party relating 21 | to the Software. Notwithstanding the foregoing, if Facebook or any of its 22 | subsidiaries or corporate affiliates files a lawsuit alleging patent 23 | infringement against you in the first instance, and you respond by filing a 24 | patent infringement counterclaim in that lawsuit against that party that is 25 | unrelated to the Software, the license granted hereunder will not terminate 26 | under section (i) of this paragraph due to such counterclaim. 27 | 28 | A "Necessary Claim" is a claim of a patent owned by Facebook that is 29 | necessarily infringed by the Software standing alone. 30 | 31 | A "Patent Assertion" is any lawsuit or other action alleging direct, indirect, 32 | or contributory infringement or inducement to infringe any patent, including a 33 | cross-claim or counterclaim. 34 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/stats/readme.md: -------------------------------------------------------------------------------- 1 | stats [![Build Status](https://secure.travis-ci.org/facebookgo/stats.png)](https://travis-ci.org/facebookgo/stats) 2 | ===== 3 | 4 | Documentation: https://godoc.org/github.com/facebookgo/stats 5 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/stats/stats_test.go: -------------------------------------------------------------------------------- 1 | package stats_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/facebookgo/ensure" 7 | "github.com/facebookgo/stats" 8 | ) 9 | 10 | // Ensure calling End works even when a BumpTimeHook isn't provided. 11 | func TestHookClientBumpTime(t *testing.T) { 12 | (&stats.HookClient{}).BumpTime("foo").End() 13 | } 14 | 15 | func TestPrefixClient(t *testing.T) { 16 | const ( 17 | prefix1 = "prefix1" 18 | prefix2 = "prefix2" 19 | avgKey = "avg" 20 | avgVal = float64(1) 21 | sumKey = "sum" 22 | sumVal = float64(2) 23 | histogramKey = "histogram" 24 | histogramVal = float64(3) 25 | timeKey = "time" 26 | ) 27 | 28 | var keys []string 29 | hc := &stats.HookClient{ 30 | BumpAvgHook: func(key string, val float64) { 31 | keys = append(keys, key) 32 | ensure.DeepEqual(t, val, avgVal) 33 | }, 34 | BumpSumHook: func(key string, val float64) { 35 | keys = append(keys, key) 36 | ensure.DeepEqual(t, val, sumVal) 37 | }, 38 | BumpHistogramHook: func(key string, val float64) { 39 | keys = append(keys, key) 40 | ensure.DeepEqual(t, val, histogramVal) 41 | }, 42 | BumpTimeHook: func(key string) interface { 43 | End() 44 | } { 45 | return multiEnderTest{ 46 | EndHook: func() { 47 | keys = append(keys, key) 48 | }, 49 | } 50 | }, 51 | } 52 | 53 | pc := stats.PrefixClient([]string{prefix1, prefix2}, hc) 54 | pc.BumpAvg(avgKey, avgVal) 55 | pc.BumpSum(sumKey, sumVal) 56 | pc.BumpHistogram(histogramKey, histogramVal) 57 | pc.BumpTime(timeKey).End() 58 | 59 | ensure.SameElements(t, keys, []string{ 60 | prefix1 + avgKey, 61 | prefix1 + sumKey, 62 | prefix1 + histogramKey, 63 | prefix1 + timeKey, 64 | prefix2 + avgKey, 65 | prefix2 + sumKey, 66 | prefix2 + histogramKey, 67 | prefix2 + timeKey, 68 | }) 69 | } 70 | 71 | type multiEnderTest struct { 72 | EndHook func() 73 | } 74 | 75 | func (e multiEnderTest) End() { 76 | e.EndHook() 77 | } 78 | -------------------------------------------------------------------------------- /vendor/github.com/facebookgo/stats/stopper.go: -------------------------------------------------------------------------------- 1 | package stats 2 | 3 | import "time" 4 | 5 | // Stopper calls Client.BumpSum and Client.BumpHistogram when End'ed 6 | type Stopper struct { 7 | Key string 8 | Start time.Time 9 | Client Client 10 | } 11 | 12 | // End the Stopper 13 | func (s *Stopper) End() { 14 | since := time.Since(s.Start).Seconds() * 1000.0 15 | s.Client.BumpSum(s.Key+".total", since) 16 | s.Client.BumpHistogram(s.Key, since) 17 | } 18 | -------------------------------------------------------------------------------- /websocket/client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package websocket 6 | 7 | import ( 8 | "bufio" 9 | "crypto/tls" 10 | "io" 11 | "net" 12 | "net/http" 13 | "net/url" 14 | ) 15 | 16 | // DialError is an error that occurs while dialling a websocket server. 17 | type DialError struct { 18 | *Config 19 | Err error 20 | } 21 | 22 | func (e *DialError) Error() string { 23 | return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error() 24 | } 25 | 26 | // NewConfig creates a new WebSocket config for client connection. 27 | func NewConfig(server, origin string) (*Config, error) { 28 | var err error 29 | config := new(Config) 30 | config.Version = ProtocolVersionHybi13 31 | config.Location, err = url.ParseRequestURI(server) 32 | if err != nil { 33 | return nil, err 34 | } 35 | config.Origin, err = url.ParseRequestURI(origin) 36 | if err != nil { 37 | return nil, err 38 | } 39 | config.Header = http.Header(make(map[string][]string)) 40 | return config, nil 41 | } 42 | 43 | // NewClient creates a new WebSocket client connection over rwc. 44 | func NewClient(config *Config, rwc io.ReadWriteCloser) (*Conn, error) { 45 | br := bufio.NewReader(rwc) 46 | bw := bufio.NewWriter(rwc) 47 | err := hybiClientHandshake(config, br, bw) 48 | if err != nil { 49 | return nil, err 50 | } 51 | buf := bufio.NewReadWriter(br, bw) 52 | return newHybiClientConn(config, buf, rwc), nil 53 | } 54 | 55 | // Dial opens a new client connection to a WebSocket. 56 | func Dial(url_, protocol, origin string) (*Conn, error) { 57 | config, err := NewConfig(url_, origin) 58 | if err != nil { 59 | return nil, err 60 | } 61 | if protocol != "" { 62 | config.Protocol = []string{protocol} 63 | } 64 | return DialConfig(config) 65 | } 66 | 67 | // DialConfig opens a new client connection to a WebSocket with a config. 68 | func DialConfig(config *Config) (*Conn, error) { 69 | var client net.Conn 70 | if config.Location == nil { 71 | return nil, &DialError{config, ErrBadWebSocketLocation} 72 | } 73 | if config.Origin == nil { 74 | return nil, &DialError{config, ErrBadWebSocketOrigin} 75 | } 76 | var ( 77 | ws *Conn 78 | err error 79 | ) 80 | 81 | switch config.Location.Scheme { 82 | case "ws": 83 | client, err = net.Dial("tcp", config.Location.Host) 84 | 85 | case "wss": 86 | client, err = tls.Dial("tcp", config.Location.Host, config.TlsConfig) 87 | 88 | default: 89 | err = ErrBadScheme 90 | } 91 | if err != nil { 92 | goto Error 93 | } 94 | 95 | ws, err = NewClient(config, client) 96 | if err != nil { 97 | goto Error 98 | } 99 | return ws, nil 100 | 101 | Error: 102 | return nil, &DialError{config, err} 103 | } 104 | --------------------------------------------------------------------------------