mirror of
https://github.com/astaxie/beego.git
synced 2024-11-25 22:11:29 +00:00
Merge branch 'develop' into config-logic
This commit is contained in:
commit
c59a029ce7
11
.travis.yml
11
.travis.yml
@ -1,8 +1,9 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.5.1
|
||||
|
||||
- 1.5.3
|
||||
- 1.4.3
|
||||
- 1.3.3
|
||||
services:
|
||||
- redis-server
|
||||
- mysql
|
||||
@ -24,7 +25,13 @@ install:
|
||||
- go get github.com/couchbase/go-couchbase
|
||||
- go get github.com/siddontang/ledisdb/config
|
||||
- go get github.com/siddontang/ledisdb/ledis
|
||||
- go get golang.org/x/tools/cmd/vet
|
||||
- go get github.com/golang/lint/golint
|
||||
before_script:
|
||||
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
|
||||
- sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi"
|
||||
- sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi"
|
||||
script:
|
||||
- go vet -x ./...
|
||||
- $HOME/gopath/bin/golint ./...
|
||||
- go test -v ./...
|
||||
|
2
cache/memcache/memcache_test.go
vendored
2
cache/memcache/memcache_test.go
vendored
@ -37,7 +37,7 @@ func TestMemcacheCache(t *testing.T) {
|
||||
t.Error("check err")
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
time.Sleep(11 * time.Second)
|
||||
|
||||
if bm.IsExist("astaxie") {
|
||||
t.Error("check err")
|
||||
|
28
config.go
28
config.go
@ -105,18 +105,25 @@ var (
|
||||
BConfig *Config
|
||||
// AppConfig is the instance of Config, store the config information from file
|
||||
AppConfig *beegoAppConfig
|
||||
// AppPath is the absolute path to the app
|
||||
AppPath string
|
||||
// AppConfigPath is the path to the config files
|
||||
AppConfigPath string
|
||||
// AppConfigProvider is the provider for the config, default is ini
|
||||
AppConfigProvider = "ini"
|
||||
// TemplateCache stores template caching
|
||||
TemplateCache map[string]*template.Template
|
||||
// GlobalSessions is the instance for the session manager
|
||||
GlobalSessions *session.Manager
|
||||
|
||||
// AppConfigPath is the path to the config files
|
||||
AppConfigPath string
|
||||
// AppConfigProvider is the provider for the config, default is ini
|
||||
AppConfigProvider = "ini"
|
||||
workPath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
workPath, _ = os.Getwd()
|
||||
workPath, _ = filepath.Abs(workPath)
|
||||
|
||||
BConfig = &Config{
|
||||
AppName: "beego",
|
||||
RunMode: DEV,
|
||||
@ -177,8 +184,7 @@ func init() {
|
||||
},
|
||||
}
|
||||
|
||||
AppConfigPath = getDefaultAppConfigPath()
|
||||
|
||||
AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
|
||||
if !utils.FileExists(AppConfigPath) {
|
||||
AppConfig = &beegoAppConfig{config.NewFakeConfig()}
|
||||
return
|
||||
@ -187,14 +193,12 @@ func init() {
|
||||
parseConfig(AppConfigPath)
|
||||
}
|
||||
|
||||
func getDefaultAppConfigPath() string {
|
||||
// default config path
|
||||
AppPath, _ := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
return filepath.Join(AppPath, "conf", "app.conf")
|
||||
}
|
||||
|
||||
// now only support ini, next will support json.
|
||||
func parseConfig(appConfigPath string) (err error) {
|
||||
if workPath != AppPath {
|
||||
os.Chdir(AppPath)
|
||||
}
|
||||
|
||||
AppConfig, err = newAppConfig(AppConfigProvider, appConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -106,3 +106,39 @@ func NewConfigData(adapterName string, data []byte) (Configer, error) {
|
||||
}
|
||||
return adapter.ParseData(data)
|
||||
}
|
||||
|
||||
// ParseBool returns the boolean value represented by the string.
|
||||
//
|
||||
// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On,
|
||||
// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off.
|
||||
// Any other value returns an error.
|
||||
func ParseBool(val interface{}) (value bool, err error) {
|
||||
if val != nil {
|
||||
switch v := val.(type) {
|
||||
case bool:
|
||||
return v, nil
|
||||
case string:
|
||||
switch v {
|
||||
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "Y", "y", "ON", "on", "On":
|
||||
return true, nil
|
||||
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "N", "n", "OFF", "off", "Off":
|
||||
return false, nil
|
||||
}
|
||||
case int8, int32, int64:
|
||||
strV := fmt.Sprintf("%s", v)
|
||||
if strV == "1" {
|
||||
return true, nil
|
||||
} else if strV == "0" {
|
||||
return false, nil
|
||||
}
|
||||
case float64:
|
||||
if v == 1 {
|
||||
return true, nil
|
||||
} else if v == 0 {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("parsing %q: invalid syntax", val)
|
||||
}
|
||||
return false, fmt.Errorf("parsing <nil>: invalid syntax")
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||
}
|
||||
|
||||
func (c *fakeConfigContainer) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.getData(key))
|
||||
return ParseBool(c.getData(key))
|
||||
}
|
||||
|
||||
func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||
|
@ -27,7 +27,6 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -97,9 +96,11 @@ func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
||||
}
|
||||
if bComment != nil {
|
||||
line = bytes.TrimLeft(line, string(bComment))
|
||||
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
|
||||
comment.Write(line)
|
||||
// Need append to a new line if multi-line comments.
|
||||
if comment.Len() > 0 {
|
||||
comment.WriteByte('\n')
|
||||
}
|
||||
comment.Write(line)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -194,7 +195,7 @@ type IniConfigContainer struct {
|
||||
|
||||
// Bool returns the boolean value for a given key.
|
||||
func (c *IniConfigContainer) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.getdata(key))
|
||||
return ParseBool(c.getdata(key))
|
||||
}
|
||||
|
||||
// DefaultBool returns the boolean value for a given key.
|
||||
@ -299,14 +300,35 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Get section or key comments. Fixed #1607
|
||||
getCommentStr := func(section, key string) string {
|
||||
comment, ok := "", false
|
||||
if len(key) == 0 {
|
||||
comment, ok = c.sectionComment[section]
|
||||
} else {
|
||||
comment, ok = c.keyComment[section+"."+key]
|
||||
}
|
||||
|
||||
if ok {
|
||||
// Empty comment
|
||||
if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
|
||||
return string(bNumComment)
|
||||
}
|
||||
prefix := string(bNumComment)
|
||||
// Add the line head character "#"
|
||||
return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
// Save default section at first place
|
||||
if dt, ok := c.data[defaultSection]; ok {
|
||||
for key, val := range dt {
|
||||
if key != " " {
|
||||
// Write key comments.
|
||||
if v, ok := c.keyComment[key]; ok {
|
||||
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
||||
if v := getCommentStr(defaultSection, key); len(v) > 0 {
|
||||
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -327,8 +349,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||
for section, dt := range c.data {
|
||||
if section != defaultSection {
|
||||
// Write section comments.
|
||||
if v, ok := c.sectionComment[section]; ok {
|
||||
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
||||
if v := getCommentStr(section, ""); len(v) > 0 {
|
||||
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -341,8 +363,8 @@ func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||
for key, val := range dt {
|
||||
if key != " " {
|
||||
// Write key comments.
|
||||
if v, ok := c.keyComment[key]; ok {
|
||||
if _, err = buf.WriteString(string(bNumComment) + v + lineBreak); err != nil {
|
||||
if v := getCommentStr(section, key); len(v) > 0 {
|
||||
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,17 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var inicontext = `
|
||||
func TestIni(t *testing.T) {
|
||||
|
||||
var (
|
||||
inicontext = `
|
||||
;comment one
|
||||
#comment two
|
||||
appname = beeapi
|
||||
@ -29,6 +35,13 @@ PI = 3.1415976
|
||||
runmode = "dev"
|
||||
autorender = false
|
||||
copyrequestbody = true
|
||||
session= on
|
||||
cookieon= off
|
||||
newreg = OFF
|
||||
needlogin = ON
|
||||
enableSession = Y
|
||||
enableCookie = N
|
||||
flag = 1
|
||||
[demo]
|
||||
key1="asta"
|
||||
key2 = "xie"
|
||||
@ -36,7 +49,31 @@ CaseInsensitive = true
|
||||
peers = one;two;three
|
||||
`
|
||||
|
||||
func TestIni(t *testing.T) {
|
||||
keyValue = map[string]interface{}{
|
||||
"appname": "beeapi",
|
||||
"httpport": 8080,
|
||||
"mysqlport": int64(3600),
|
||||
"pi": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"session": true,
|
||||
"cookieon": false,
|
||||
"newreg": false,
|
||||
"needlogin": true,
|
||||
"enableSession": true,
|
||||
"enableCookie": false,
|
||||
"flag": true,
|
||||
"demo::key1": "asta",
|
||||
"demo::key2": "xie",
|
||||
"demo::CaseInsensitive": true,
|
||||
"demo::peers": []string{"one", "two", "three"},
|
||||
"null": "",
|
||||
"demo2::key1": "",
|
||||
"error": "",
|
||||
}
|
||||
)
|
||||
|
||||
f, err := os.Create("testini.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -52,31 +89,31 @@ func TestIni(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if iniconf.String("appname") != "beeapi" {
|
||||
t.Fatal("appname not equal to beeapi")
|
||||
for k, v := range keyValue {
|
||||
var err error
|
||||
var value interface{}
|
||||
switch v.(type) {
|
||||
case int:
|
||||
value, err = iniconf.Int(k)
|
||||
case int64:
|
||||
value, err = iniconf.Int64(k)
|
||||
case float64:
|
||||
value, err = iniconf.Float(k)
|
||||
case bool:
|
||||
value, err = iniconf.Bool(k)
|
||||
case []string:
|
||||
value = iniconf.Strings(k)
|
||||
case string:
|
||||
value = iniconf.String(k)
|
||||
default:
|
||||
value, err = iniconf.DIY(k)
|
||||
}
|
||||
if port, err := iniconf.Int("httpport"); err != nil || port != 8080 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
if err != nil {
|
||||
t.Fatalf("get key %q value fail,err %s", k, err)
|
||||
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
|
||||
}
|
||||
if port, err := iniconf.Int64("mysqlport"); err != nil || port != 3600 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pi, err := iniconf.Float("PI"); err != nil || pi != 3.1415976 {
|
||||
t.Error(pi)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if iniconf.String("runmode") != "dev" {
|
||||
t.Fatal("runmode not equal to dev")
|
||||
}
|
||||
if v, err := iniconf.Bool("autorender"); err != nil || v != false {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := iniconf.Bool("copyrequestbody"); err != nil || v != true {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
|
||||
}
|
||||
if err = iniconf.Set("name", "astaxie"); err != nil {
|
||||
t.Fatal(err)
|
||||
@ -84,20 +121,63 @@ func TestIni(t *testing.T) {
|
||||
if iniconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
if iniconf.String("demo::key1") != "asta" {
|
||||
t.Fatal("get demo.key1 error")
|
||||
}
|
||||
if iniconf.String("demo::key2") != "xie" {
|
||||
t.Fatal("get demo.key2 error")
|
||||
}
|
||||
if v, err := iniconf.Bool("demo::caseinsensitive"); err != nil || v != true {
|
||||
t.Fatal("get demo.caseinsensitive error")
|
||||
|
||||
}
|
||||
|
||||
if data := iniconf.Strings("demo::peers"); len(data) != 3 {
|
||||
t.Fatal("get strings error", data)
|
||||
} else if data[0] != "one" {
|
||||
t.Fatal("get first params error not equat to one")
|
||||
func TestIniSave(t *testing.T) {
|
||||
|
||||
const (
|
||||
inicontext = `
|
||||
app = app
|
||||
;comment one
|
||||
#comment two
|
||||
# comment three
|
||||
appname = beeapi
|
||||
httpport = 8080
|
||||
# DB Info
|
||||
# enable db
|
||||
[dbinfo]
|
||||
# db type name
|
||||
# suport mysql,sqlserver
|
||||
name = mysql
|
||||
`
|
||||
|
||||
saveResult = `
|
||||
app=app
|
||||
#comment one
|
||||
#comment two
|
||||
# comment three
|
||||
appname=beeapi
|
||||
httpport=8080
|
||||
|
||||
# DB Info
|
||||
# enable db
|
||||
[dbinfo]
|
||||
# db type name
|
||||
# suport mysql,sqlserver
|
||||
name=mysql
|
||||
`
|
||||
)
|
||||
cfg, err := NewConfigData("ini", []byte(inicontext))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
name := "newIniConfig.ini"
|
||||
if err := cfg.SaveConfigFile(name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(name)
|
||||
|
||||
if data, err := ioutil.ReadFile(name); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
cfgData := string(data)
|
||||
datas := strings.Split(saveResult, "\n")
|
||||
for _, line := range datas {
|
||||
if strings.Contains(cfgData, line+"\n") == false {
|
||||
t.Fatalf("different after save ini config file. need contains %q", line)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
@ -70,12 +71,9 @@ type JSONConfigContainer struct {
|
||||
func (c *JSONConfigContainer) Bool(key string) (bool, error) {
|
||||
val := c.getData(key)
|
||||
if val != nil {
|
||||
if v, ok := val.(bool); ok {
|
||||
return v, nil
|
||||
return ParseBool(val)
|
||||
}
|
||||
return false, errors.New("not bool value")
|
||||
}
|
||||
return false, errors.New("not exist key:" + key)
|
||||
return false, fmt.Errorf("not exist key: %q", key)
|
||||
}
|
||||
|
||||
// DefaultBool return the bool value if has no error
|
||||
|
@ -15,34 +15,14 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var jsoncontext = `{
|
||||
"appname": "beeapi",
|
||||
"testnames": "foo;bar",
|
||||
"httpport": 8080,
|
||||
"mysqlport": 3600,
|
||||
"PI": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"database": {
|
||||
"host": "host",
|
||||
"port": "port",
|
||||
"database": "database",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"conns":{
|
||||
"maxconnection":12,
|
||||
"autoconnect":true,
|
||||
"connectioninfo":"info"
|
||||
}
|
||||
}
|
||||
}`
|
||||
func TestJsonStartsWithArray(t *testing.T) {
|
||||
|
||||
var jsoncontextwitharray = `[
|
||||
const jsoncontextwitharray = `[
|
||||
{
|
||||
"url": "user",
|
||||
"serviceAPI": "http://www.test.com/user"
|
||||
@ -52,8 +32,6 @@ var jsoncontextwitharray = `[
|
||||
"serviceAPI": "http://www.test.com/employee"
|
||||
}
|
||||
]`
|
||||
|
||||
func TestJsonStartsWithArray(t *testing.T) {
|
||||
f, err := os.Create("testjsonWithArray.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -90,6 +68,64 @@ func TestJsonStartsWithArray(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestJson(t *testing.T) {
|
||||
|
||||
var (
|
||||
jsoncontext = `{
|
||||
"appname": "beeapi",
|
||||
"testnames": "foo;bar",
|
||||
"httpport": 8080,
|
||||
"mysqlport": 3600,
|
||||
"PI": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"session": "on",
|
||||
"cookieon": "off",
|
||||
"newreg": "OFF",
|
||||
"needlogin": "ON",
|
||||
"enableSession": "Y",
|
||||
"enableCookie": "N",
|
||||
"flag": 1,
|
||||
"database": {
|
||||
"host": "host",
|
||||
"port": "port",
|
||||
"database": "database",
|
||||
"username": "username",
|
||||
"password": "password",
|
||||
"conns":{
|
||||
"maxconnection":12,
|
||||
"autoconnect":true,
|
||||
"connectioninfo":"info"
|
||||
}
|
||||
}
|
||||
}`
|
||||
keyValue = map[string]interface{}{
|
||||
"appname": "beeapi",
|
||||
"testnames": []string{"foo", "bar"},
|
||||
"httpport": 8080,
|
||||
"mysqlport": int64(3600),
|
||||
"PI": 3.1415976,
|
||||
"runmode": "dev",
|
||||
"autorender": false,
|
||||
"copyrequestbody": true,
|
||||
"session": true,
|
||||
"cookieon": false,
|
||||
"newreg": false,
|
||||
"needlogin": true,
|
||||
"enableSession": true,
|
||||
"enableCookie": false,
|
||||
"flag": true,
|
||||
"database::host": "host",
|
||||
"database::port": "port",
|
||||
"database::database": "database",
|
||||
"database::password": "password",
|
||||
"database::conns::maxconnection": 12,
|
||||
"database::conns::autoconnect": true,
|
||||
"database::conns::connectioninfo": "info",
|
||||
"unknown": "",
|
||||
}
|
||||
)
|
||||
|
||||
f, err := os.Create("testjson.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -105,37 +141,32 @@ func TestJson(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jsonconf.String("appname") != "beeapi" {
|
||||
t.Fatal("appname not equal to beeapi")
|
||||
|
||||
for k, v := range keyValue {
|
||||
var err error
|
||||
var value interface{}
|
||||
switch v.(type) {
|
||||
case int:
|
||||
value, err = jsonconf.Int(k)
|
||||
case int64:
|
||||
value, err = jsonconf.Int64(k)
|
||||
case float64:
|
||||
value, err = jsonconf.Float(k)
|
||||
case bool:
|
||||
value, err = jsonconf.Bool(k)
|
||||
case []string:
|
||||
value = jsonconf.Strings(k)
|
||||
case string:
|
||||
value = jsonconf.String(k)
|
||||
default:
|
||||
value, err = jsonconf.DIY(k)
|
||||
}
|
||||
if port, err := jsonconf.Int("httpport"); err != nil || port != 8080 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
if err != nil {
|
||||
t.Fatalf("get key %q value fatal,%v err %s", k, v, err)
|
||||
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
|
||||
}
|
||||
if port, err := jsonconf.Int64("mysqlport"); err != nil || port != 3600 {
|
||||
t.Error(port)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pi, err := jsonconf.Float("PI"); err != nil || pi != 3.1415976 {
|
||||
t.Error(pi)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if jsonconf.String("runmode") != "dev" {
|
||||
t.Fatal("runmode not equal to dev")
|
||||
}
|
||||
if v := jsonconf.Strings("unknown"); len(v) > 0 {
|
||||
t.Fatal("unknown strings, the length should be 0")
|
||||
}
|
||||
if v := jsonconf.Strings("testnames"); len(v) != 2 {
|
||||
t.Fatal("testnames length should be 2")
|
||||
}
|
||||
if v, err := jsonconf.Bool("autorender"); err != nil || v != false {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if v, err := jsonconf.Bool("copyrequestbody"); err != nil || v != true {
|
||||
t.Error(v)
|
||||
t.Fatal(err)
|
||||
|
||||
}
|
||||
if err = jsonconf.Set("name", "astaxie"); err != nil {
|
||||
t.Fatal(err)
|
||||
@ -143,15 +174,7 @@ func TestJson(t *testing.T) {
|
||||
if jsonconf.String("name") != "astaxie" {
|
||||
t.Fatal("get name error")
|
||||
}
|
||||
if jsonconf.String("database::host") != "host" {
|
||||
t.Fatal("get database::host error")
|
||||
}
|
||||
if jsonconf.String("database::conns::connectioninfo") != "info" {
|
||||
t.Fatal("get database::conns::connectioninfo error")
|
||||
}
|
||||
if maxconnection, err := jsonconf.Int("database::conns::maxconnection"); err != nil || maxconnection != 12 {
|
||||
t.Fatal("get database::conns::maxconnection error")
|
||||
}
|
||||
|
||||
if db, err := jsonconf.DIY("database"); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if m, ok := db.(map[string]interface{}); !ok {
|
||||
|
@ -92,7 +92,10 @@ type ConfigContainer struct {
|
||||
|
||||
// Bool returns the boolean value for a given key.
|
||||
func (c *ConfigContainer) Bool(key string) (bool, error) {
|
||||
return strconv.ParseBool(c.data[key].(string))
|
||||
if v, ok := c.data[key]; ok {
|
||||
return config.ParseBool(v)
|
||||
}
|
||||
return false, fmt.Errorf("not exist key: %q", key)
|
||||
}
|
||||
|
||||
// DefaultBool return the bool value if has no error
|
||||
|
@ -121,10 +121,10 @@ type ConfigContainer struct {
|
||||
|
||||
// Bool returns the boolean value for a given key.
|
||||
func (c *ConfigContainer) Bool(key string) (bool, error) {
|
||||
if v, ok := c.data[key].(bool); ok {
|
||||
return v, nil
|
||||
if v, ok := c.data[key]; ok {
|
||||
return config.ParseBool(v)
|
||||
}
|
||||
return false, errors.New("not bool value")
|
||||
return false, fmt.Errorf("not exist key: %q", key)
|
||||
}
|
||||
|
||||
// DefaultBool return the bool value if has no error
|
||||
|
2
hooks.go
2
hooks.go
@ -68,14 +68,12 @@ func registerSession() error {
|
||||
}
|
||||
|
||||
func registerTemplate() error {
|
||||
if BConfig.WebConfig.AutoRender {
|
||||
if err := BuildTemplate(BConfig.WebConfig.ViewsPath); err != nil {
|
||||
if BConfig.RunMode == DEV {
|
||||
Warn(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// connWriter implements LoggerInterface.
|
||||
@ -48,7 +49,7 @@ func (c *connWriter) Init(jsonconfig string) error {
|
||||
|
||||
// WriteMsg write message in connection.
|
||||
// if connection is down, try to re-connect.
|
||||
func (c *connWriter) WriteMsg(msg string, level int) error {
|
||||
func (c *connWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > c.Level {
|
||||
return nil
|
||||
}
|
||||
@ -62,6 +63,9 @@ func (c *connWriter) WriteMsg(msg string, level int) error {
|
||||
if c.ReconnectOnMsg {
|
||||
defer c.innerWriter.Close()
|
||||
}
|
||||
|
||||
msg = formatLogTime(when) + msg
|
||||
|
||||
c.lg.Println(msg)
|
||||
return nil
|
||||
}
|
||||
@ -94,7 +98,7 @@ func (c *connWriter) connect() error {
|
||||
}
|
||||
|
||||
c.innerWriter = conn
|
||||
c.lg = log.New(conn, "", log.Ldate|log.Ltime)
|
||||
c.lg = log.New(conn, "", 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// brush is a color join function
|
||||
@ -53,27 +54,28 @@ type consoleWriter struct {
|
||||
// NewConsole create ConsoleWriter returning as LoggerInterface.
|
||||
func NewConsole() Logger {
|
||||
cw := &consoleWriter{
|
||||
lg: log.New(os.Stdout, "", log.Ldate|log.Ltime),
|
||||
lg: log.New(os.Stdout, "", 0),
|
||||
Level: LevelDebug,
|
||||
}
|
||||
return cw
|
||||
}
|
||||
|
||||
// Init init console logger.
|
||||
// jsonconfig like '{"level":LevelTrace}'.
|
||||
func (c *consoleWriter) Init(jsonconfig string) error {
|
||||
if len(jsonconfig) == 0 {
|
||||
// jsonConfig like '{"level":LevelTrace}'.
|
||||
func (c *consoleWriter) Init(jsonConfig string) error {
|
||||
if len(jsonConfig) == 0 {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal([]byte(jsonconfig), c)
|
||||
return json.Unmarshal([]byte(jsonConfig), c)
|
||||
}
|
||||
|
||||
// WriteMsg write message in console.
|
||||
func (c *consoleWriter) WriteMsg(msg string, level int) error {
|
||||
func (c *consoleWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > c.Level {
|
||||
return nil
|
||||
}
|
||||
if goos := runtime.GOOS; goos == "windows" {
|
||||
msg = formatLogTime(when) + msg
|
||||
if runtime.GOOS == "windows" {
|
||||
c.lg.Println(msg)
|
||||
return nil
|
||||
}
|
||||
|
@ -48,16 +48,16 @@ func (el *esLogger) Init(jsonconfig string) error {
|
||||
}
|
||||
|
||||
// WriteMsg will write the msg and level into es
|
||||
func (el *esLogger) WriteMsg(msg string, level int) error {
|
||||
func (el *esLogger) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > el.Level {
|
||||
return nil
|
||||
}
|
||||
t := time.Now()
|
||||
|
||||
vals := make(map[string]interface{})
|
||||
vals["@timestamp"] = t.Format(time.RFC3339)
|
||||
vals["@timestamp"] = when.Format(time.RFC3339)
|
||||
vals["@msg"] = msg
|
||||
d := goes.Document{
|
||||
Index: fmt.Sprintf("%04d.%02d.%02d", t.Year(), t.Month(), t.Day()),
|
||||
Index: fmt.Sprintf("%04d.%02d.%02d", when.Year(), when.Month(), when.Day()),
|
||||
Type: "logs",
|
||||
Fields: vals,
|
||||
}
|
||||
|
51
logs/file.go
51
logs/file.go
@ -114,59 +114,20 @@ func (w *fileLogWriter) needRotate(size int, day int) bool {
|
||||
}
|
||||
|
||||
// WriteMsg write logger message into file.
|
||||
func (w *fileLogWriter) WriteMsg(msg string, level int) error {
|
||||
func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > w.Level {
|
||||
return nil
|
||||
}
|
||||
//2016/01/12 21:34:33
|
||||
now := time.Now()
|
||||
y, mo, d := now.Date()
|
||||
h, mi, s := now.Clock()
|
||||
//len(2006/01/02 15:03:04)==19
|
||||
var buf [20]byte
|
||||
t := 3
|
||||
for y >= 10 {
|
||||
p := y / 10
|
||||
buf[t] = byte('0' + y - p*10)
|
||||
y = p
|
||||
t--
|
||||
}
|
||||
buf[0] = byte('0' + y)
|
||||
buf[4] = '/'
|
||||
if mo > 9 {
|
||||
buf[5] = '1'
|
||||
buf[6] = byte('0' + mo - 9)
|
||||
} else {
|
||||
buf[5] = '0'
|
||||
buf[6] = byte('0' + mo)
|
||||
}
|
||||
buf[7] = '/'
|
||||
t = d / 10
|
||||
buf[8] = byte('0' + t)
|
||||
buf[9] = byte('0' + d - t*10)
|
||||
buf[10] = ' '
|
||||
t = h / 10
|
||||
buf[11] = byte('0' + t)
|
||||
buf[12] = byte('0' + h - t*10)
|
||||
buf[13] = ':'
|
||||
t = mi / 10
|
||||
buf[14] = byte('0' + t)
|
||||
buf[15] = byte('0' + mi - t*10)
|
||||
buf[16] = ':'
|
||||
t = s / 10
|
||||
buf[17] = byte('0' + t)
|
||||
buf[18] = byte('0' + s - t*10)
|
||||
buf[19] = ' '
|
||||
msg = string(buf[0:]) + msg + "\n"
|
||||
msg = formatLogTime(when) + msg + "\n"
|
||||
|
||||
if w.Rotate {
|
||||
d := when.Day()
|
||||
if w.needRotate(len(msg), d) {
|
||||
w.Lock()
|
||||
if w.needRotate(len(msg), d) {
|
||||
if err := w.doRotate(); err != nil {
|
||||
if err := w.doRotate(when); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err)
|
||||
}
|
||||
|
||||
}
|
||||
w.Unlock()
|
||||
}
|
||||
@ -236,7 +197,7 @@ func (w *fileLogWriter) lines() (int, error) {
|
||||
|
||||
// DoRotate means it need to write file in new file.
|
||||
// new file name like xx.2013-01-01.2.log
|
||||
func (w *fileLogWriter) doRotate() error {
|
||||
func (w *fileLogWriter) doRotate(logTime time.Time) error {
|
||||
_, err := os.Lstat(w.Filename)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -251,7 +212,7 @@ func (w *fileLogWriter) doRotate() error {
|
||||
suffix = ".log"
|
||||
}
|
||||
for ; err == nil && num <= 999; num++ {
|
||||
fName = filenameOnly + fmt.Sprintf(".%s.%03d%s", time.Now().Format("2006-01-02"), num, suffix)
|
||||
fName = filenameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, suffix)
|
||||
_, err = os.Lstat(fName)
|
||||
}
|
||||
// return error if the last file checked still existed
|
||||
|
58
logs/log.go
58
logs/log.go
@ -40,6 +40,7 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RFC5424 log message levels.
|
||||
@ -68,7 +69,7 @@ type loggerType func() Logger
|
||||
// Logger defines the behavior of a log provider.
|
||||
type Logger interface {
|
||||
Init(config string) error
|
||||
WriteMsg(msg string, level int) error
|
||||
WriteMsg(when time.Time, msg string, level int) error
|
||||
Destroy()
|
||||
Flush()
|
||||
}
|
||||
@ -108,6 +109,7 @@ type nameLogger struct {
|
||||
type logMsg struct {
|
||||
level int
|
||||
msg string
|
||||
when time.Time
|
||||
}
|
||||
|
||||
var logMsgPool *sync.Pool
|
||||
@ -173,9 +175,9 @@ func (bl *BeeLogger) DelLogger(adapterName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeToLoggers(msg string, level int) {
|
||||
func (bl *BeeLogger) writeToLoggers(when time.Time, msg string, level int) {
|
||||
for _, l := range bl.outputs {
|
||||
err := l.WriteMsg(msg, level)
|
||||
err := l.WriteMsg(when, msg, level)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "unable to WriteMsg to adapter:%v,error:%v\n", l.name, err)
|
||||
}
|
||||
@ -183,6 +185,7 @@ func (bl *BeeLogger) writeToLoggers(msg string, level int) {
|
||||
}
|
||||
|
||||
func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
|
||||
when := time.Now()
|
||||
if bl.enableFuncCallDepth {
|
||||
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
|
||||
if !ok {
|
||||
@ -196,9 +199,10 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string) error {
|
||||
lm := logMsgPool.Get().(*logMsg)
|
||||
lm.level = logLevel
|
||||
lm.msg = msg
|
||||
lm.when = when
|
||||
bl.msgChan <- lm
|
||||
} else {
|
||||
bl.writeToLoggers(msg, logLevel)
|
||||
bl.writeToLoggers(when, msg, logLevel)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -231,7 +235,7 @@ func (bl *BeeLogger) startLogger() {
|
||||
for {
|
||||
select {
|
||||
case bm := <-bl.msgChan:
|
||||
bl.writeToLoggers(bm.msg, bm.level)
|
||||
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||
logMsgPool.Put(bm)
|
||||
}
|
||||
}
|
||||
@ -351,7 +355,7 @@ func (bl *BeeLogger) Close() {
|
||||
for {
|
||||
if len(bl.msgChan) > 0 {
|
||||
bm := <-bl.msgChan
|
||||
bl.writeToLoggers(bm.msg, bm.level)
|
||||
bl.writeToLoggers(bm.when, bm.msg, bm.level)
|
||||
logMsgPool.Put(bm)
|
||||
continue
|
||||
}
|
||||
@ -363,3 +367,45 @@ func (bl *BeeLogger) Close() {
|
||||
}
|
||||
bl.outputs = nil
|
||||
}
|
||||
|
||||
func formatLogTime(when time.Time) string {
|
||||
y, mo, d := when.Date()
|
||||
h, mi, s := when.Clock()
|
||||
//len(2006/01/02 15:03:04)==19
|
||||
var buf [20]byte
|
||||
t := 3
|
||||
for y >= 10 {
|
||||
p := y / 10
|
||||
buf[t] = byte('0' + y - p*10)
|
||||
y = p
|
||||
t--
|
||||
}
|
||||
buf[0] = byte('0' + y)
|
||||
buf[4] = '/'
|
||||
if mo > 9 {
|
||||
buf[5] = '1'
|
||||
buf[6] = byte('0' + mo - 9)
|
||||
} else {
|
||||
buf[5] = '0'
|
||||
buf[6] = byte('0' + mo)
|
||||
}
|
||||
buf[7] = '/'
|
||||
t = d / 10
|
||||
buf[8] = byte('0' + t)
|
||||
buf[9] = byte('0' + d - t*10)
|
||||
buf[10] = ' '
|
||||
t = h / 10
|
||||
buf[11] = byte('0' + t)
|
||||
buf[12] = byte('0' + h - t*10)
|
||||
buf[13] = ':'
|
||||
t = mi / 10
|
||||
buf[14] = byte('0' + t)
|
||||
buf[15] = byte('0' + mi - t*10)
|
||||
buf[16] = ':'
|
||||
t = s / 10
|
||||
buf[17] = byte('0' + t)
|
||||
buf[18] = byte('0' + s - t*10)
|
||||
buf[19] = ' '
|
||||
|
||||
return string(buf[0:])
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ func (s *SMTPWriter) sendMail(hostAddressWithPort string, auth smtp.Auth, fromAd
|
||||
|
||||
// WriteMsg write message in smtp writer.
|
||||
// it will send an email with subject and only this message.
|
||||
func (s *SMTPWriter) WriteMsg(msg string, level int) error {
|
||||
func (s *SMTPWriter) WriteMsg(when time.Time, msg string, level int) error {
|
||||
if level > s.Level {
|
||||
return nil
|
||||
}
|
||||
@ -140,7 +140,7 @@ func (s *SMTPWriter) WriteMsg(msg string, level int) error {
|
||||
// and send the email all in one step.
|
||||
contentType := "Content-Type: text/plain" + "; charset=UTF-8"
|
||||
mailmsg := []byte("To: " + strings.Join(s.RecipientAddresses, ";") + "\r\nFrom: " + s.FromAddress + "<" + s.FromAddress +
|
||||
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", time.Now().Format("2006-01-02 15:04:05")) + msg)
|
||||
">\r\nSubject: " + s.Subject + "\r\n" + contentType + "\r\n\r\n" + fmt.Sprintf(".%s", when.Format("2006-01-02 15:04:05")) + msg)
|
||||
|
||||
return s.sendMail(s.Host, auth, s.FromAddress, s.RecipientAddresses, mailmsg)
|
||||
}
|
||||
|
@ -162,6 +162,12 @@ type QuerySeter interface {
|
||||
// qs.RelatedSel("profile").One(&user)
|
||||
// user.Profile.Age = 32
|
||||
RelatedSel(params ...interface{}) QuerySeter
|
||||
// Set Distinct
|
||||
// for example:
|
||||
// o.QueryTable("policy").Filter("Groups__Group__Users__User", user).
|
||||
// Distinct().
|
||||
// All(&permissions)
|
||||
Distinct() QuerySeter
|
||||
// return QuerySeter execution result number
|
||||
// for example:
|
||||
// num, err = qs.Filter("profile__age__gt", 28).Count()
|
||||
|
@ -130,7 +130,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat
|
||||
}
|
||||
|
||||
func genRouterCode() {
|
||||
os.Mkdir("routers", 0755)
|
||||
os.Mkdir(path.Join(AppPath, "routers"), 0755)
|
||||
Info("generate router from comments")
|
||||
var (
|
||||
globalinfo string
|
||||
@ -172,7 +172,7 @@ func genRouterCode() {
|
||||
}
|
||||
}
|
||||
if globalinfo != "" {
|
||||
f, err := os.Create(path.Join("routers", commentFilename))
|
||||
f, err := os.Create(path.Join(AppPath, "routers", commentFilename))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -182,7 +182,7 @@ func genRouterCode() {
|
||||
}
|
||||
|
||||
func compareFile(pkgRealpath string) bool {
|
||||
if !utils.FileExists(path.Join("routers", commentFilename)) {
|
||||
if !utils.FileExists(path.Join(AppPath, "routers", commentFilename)) {
|
||||
return true
|
||||
}
|
||||
if utils.FileExists(lastupdateFilename) {
|
||||
|
@ -36,6 +36,7 @@
|
||||
package cors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -215,6 +216,7 @@ func Allow(opts *Options) beego.FilterFunc {
|
||||
for key, value := range headers {
|
||||
ctx.Output.Header(key, value)
|
||||
}
|
||||
ctx.ResponseWriter.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
headers = opts.Header(origin)
|
||||
|
18
tree.go
18
tree.go
@ -141,7 +141,7 @@ func (t *Tree) addtree(segments []string, tree *Tree, wildcards []string, reg st
|
||||
regexpStr = "([^.]+).(.+)"
|
||||
params = params[1:]
|
||||
} else {
|
||||
for range params {
|
||||
for _ = range params {
|
||||
regexpStr = "([^/]+)/" + regexpStr
|
||||
}
|
||||
}
|
||||
@ -254,7 +254,7 @@ func (t *Tree) addseg(segments []string, route interface{}, wildcards []string,
|
||||
regexpStr = "/([^.]+).(.+)"
|
||||
params = params[1:]
|
||||
} else {
|
||||
for range params {
|
||||
for _ = range params {
|
||||
regexpStr = "/([^/]+)" + regexpStr
|
||||
}
|
||||
}
|
||||
@ -420,7 +420,11 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
|
||||
if len(strs) == 2 {
|
||||
ctx.Input.SetParam(":ext", strs[1])
|
||||
}
|
||||
if index > (len(wildcardValues) - 1) {
|
||||
ctx.Input.SetParam(":path", "")
|
||||
} else {
|
||||
ctx.Input.SetParam(":path", path.Join(path.Join(wildcardValues[index:len(wildcardValues)-1]...), strs[0]))
|
||||
}
|
||||
return true
|
||||
}
|
||||
// match :id
|
||||
@ -438,8 +442,10 @@ func (leaf *leafInfo) match(wildcardValues []string, ctx *context.Context) (ok b
|
||||
}
|
||||
matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...))
|
||||
for i, match := range matches[1:] {
|
||||
if i < len(leaf.wildcards) {
|
||||
ctx.Input.SetParam(leaf.wildcards[i], match)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -536,13 +542,19 @@ func splitSegment(key string) (bool, []string, string) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if v == ':' {
|
||||
// Escape Sequence '\'
|
||||
if i > 0 && key[i-1] == '\\' {
|
||||
out = append(out, v)
|
||||
} else if v == ':' {
|
||||
param = make([]rune, 0)
|
||||
start = true
|
||||
} else if v == '(' {
|
||||
startexp = true
|
||||
start = false
|
||||
if len(param) > 0 {
|
||||
params = append(params, ":"+string(param))
|
||||
param = make([]rune, 0)
|
||||
}
|
||||
paramsNum++
|
||||
expt = make([]rune, 0)
|
||||
expt = append(expt, '(')
|
||||
|
71
tree_test.go
71
tree_test.go
@ -15,6 +15,7 @@
|
||||
package beego
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
@ -57,6 +58,9 @@ func init() {
|
||||
"/dl/48/48/05ac66d9bda00a3acf948c43e306fc9a.jpg",
|
||||
map[string]string{":width": "48", ":height": "48", ":ext": "jpg", ":path": "05ac66d9bda00a3acf948c43e306fc9a"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(a)", map[string]string{":id": "123"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(b)", map[string]string{":id": "123"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id\\((a|b|c)\\)", "/v1/shop/123(c)", map[string]string{":id": "123"}})
|
||||
routers = append(routers, testinfo{"/:year:int/:month:int/:id/:endid", "/1111/111/aaa/aaa", map[string]string{":year": "1111", ":month": "111", ":id": "aaa", ":endid": "aaa"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id/:name", "/v1/shop/123/nike", map[string]string{":id": "123", ":name": "nike"}})
|
||||
routers = append(routers, testinfo{"/v1/shop/:id/account", "/v1/shop/123/account", map[string]string{":id": "123"}})
|
||||
@ -245,48 +249,31 @@ func TestSplitPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSplitSegment(t *testing.T) {
|
||||
b, w, r := splitSegment("admin")
|
||||
if b || len(w) != 0 || r != "" {
|
||||
t.Fatal("admin should return false, nil, ''")
|
||||
|
||||
items := map[string]struct {
|
||||
isReg bool
|
||||
params []string
|
||||
regStr string
|
||||
}{
|
||||
"admin": {false, nil, ""},
|
||||
"*": {true, []string{":splat"}, ""},
|
||||
"*.*": {true, []string{".", ":path", ":ext"}, ""},
|
||||
":id": {true, []string{":id"}, ""},
|
||||
"?:id": {true, []string{":", ":id"}, ""},
|
||||
":id:int": {true, []string{":id"}, "([0-9]+)"},
|
||||
":name:string": {true, []string{":name"}, `([\w]+)`},
|
||||
":id([0-9]+)": {true, []string{":id"}, `([0-9]+)`},
|
||||
":id([0-9]+)_:name": {true, []string{":id", ":name"}, `([0-9]+)_(.+)`},
|
||||
":id(.+)_cms.html": {true, []string{":id"}, `(.+)_cms.html`},
|
||||
"cms_:id(.+)_:page(.+).html": {true, []string{":id", ":page"}, `cms_(.+)_(.+).html`},
|
||||
`:app(a|b|c)`: {true, []string{":app"}, `(a|b|c)`},
|
||||
`:app\((a|b|c)\)`: {true, []string{":app"}, `(.+)\((a|b|c)\)`},
|
||||
}
|
||||
|
||||
for pattern, v := range items {
|
||||
b, w, r := splitSegment(pattern)
|
||||
if b != v.isReg || r != v.regStr || strings.Join(w, ",") != strings.Join(v.params, ",") {
|
||||
t.Fatalf("%s should return %t,%s,%q, got %t,%s,%q", pattern, v.isReg, v.params, v.regStr, b, w, r)
|
||||
}
|
||||
b, w, r = splitSegment("*")
|
||||
if !b || len(w) != 1 || w[0] != ":splat" || r != "" {
|
||||
t.Fatal("* should return true, [:splat], ''")
|
||||
}
|
||||
b, w, r = splitSegment("*.*")
|
||||
if !b || len(w) != 3 || w[1] != ":path" || w[2] != ":ext" || w[0] != "." || r != "" {
|
||||
t.Fatal("admin should return true,[. :path :ext], ''")
|
||||
}
|
||||
b, w, r = splitSegment(":id")
|
||||
if !b || len(w) != 1 || w[0] != ":id" || r != "" {
|
||||
t.Fatal(":id should return true, [:id], ''")
|
||||
}
|
||||
b, w, r = splitSegment("?:id")
|
||||
if !b || len(w) != 2 || w[0] != ":" || w[1] != ":id" || r != "" {
|
||||
t.Fatal("?:id should return true, [: :id], ''")
|
||||
}
|
||||
b, w, r = splitSegment(":id:int")
|
||||
if !b || len(w) != 1 || w[0] != ":id" || r != "([0-9]+)" {
|
||||
t.Fatal(":id:int should return true, [:id], '([0-9]+)'")
|
||||
}
|
||||
b, w, r = splitSegment(":name:string")
|
||||
if !b || len(w) != 1 || w[0] != ":name" || r != `([\w]+)` {
|
||||
t.Fatal(`:name:string should return true, [:name], '([\w]+)'`)
|
||||
}
|
||||
b, w, r = splitSegment(":id([0-9]+)")
|
||||
if !b || len(w) != 1 || w[0] != ":id" || r != `([0-9]+)` {
|
||||
t.Fatal(`:id([0-9]+) should return true, [:id], '([0-9]+)'`)
|
||||
}
|
||||
b, w, r = splitSegment(":id([0-9]+)_:name")
|
||||
if !b || len(w) != 2 || w[0] != ":id" || w[1] != ":name" || r != `([0-9]+)_(.+)` {
|
||||
t.Fatal(`:id([0-9]+)_:name should return true, [:id :name], '([0-9]+)_(.+)'`)
|
||||
}
|
||||
b, w, r = splitSegment(":id(.+)_cms.html")
|
||||
if !b || len(w) != 1 || w[0] != ":id" || r != `(.+)_cms.html` {
|
||||
t.Fatal(":id_cms.html should return true, [:id], '(.+)_cms.html'")
|
||||
}
|
||||
b, w, r = splitSegment("cms_:id(.+)_:page(.+).html")
|
||||
if !b || len(w) != 2 || w[0] != ":id" || w[1] != ":page" || r != `cms_(.+)_(.+).html` {
|
||||
t.Fatal(":id_cms.html should return true, [:id :page], cms_(.+)_(.+).html")
|
||||
}
|
||||
}
|
||||
|
@ -31,10 +31,13 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLineLength = 76
|
||||
|
||||
upperhex = "0123456789ABCDEF"
|
||||
)
|
||||
|
||||
// Email is the type used for email messages
|
||||
@ -74,9 +77,6 @@ func NewEMail(config string) *Email {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if e.From == "" {
|
||||
e.From = e.Username
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
@ -228,14 +228,21 @@ func (e *Email) Send() error {
|
||||
to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
|
||||
to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
|
||||
// Check to make sure there is at least one recipient and one "From" address
|
||||
if e.From == "" || len(to) == 0 {
|
||||
return errors.New("Must specify at least one From address and one To address")
|
||||
if len(to) == 0 {
|
||||
return errors.New("Must specify at least one To address")
|
||||
}
|
||||
from, err := mail.ParseAddress(e.From)
|
||||
|
||||
from, err := mail.ParseAddress(e.Username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.From = from.String()
|
||||
|
||||
if len(e.From) == 0 {
|
||||
e.From = e.Username
|
||||
}
|
||||
// use mail's RFC 2047 to encode any string
|
||||
e.Subject = qEncode("utf-8", e.Subject)
|
||||
|
||||
raw, err := e.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -342,3 +349,73 @@ func base64Wrap(w io.Writer, b []byte) {
|
||||
w.Write(out)
|
||||
}
|
||||
}
|
||||
|
||||
// Encode returns the encoded-word form of s. If s is ASCII without special
|
||||
// characters, it is returned unchanged. The provided charset is the IANA
|
||||
// charset name of s. It is case insensitive.
|
||||
// RFC 2047 encoded-word
|
||||
func qEncode(charset, s string) string {
|
||||
if !needsEncoding(s) {
|
||||
return s
|
||||
}
|
||||
return encodeWord(charset, s)
|
||||
}
|
||||
|
||||
func needsEncoding(s string) bool {
|
||||
for _, b := range s {
|
||||
if (b < ' ' || b > '~') && b != '\t' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// encodeWord encodes a string into an encoded-word.
|
||||
func encodeWord(charset, s string) string {
|
||||
buf := getBuffer()
|
||||
|
||||
buf.WriteString("=?")
|
||||
buf.WriteString(charset)
|
||||
buf.WriteByte('?')
|
||||
buf.WriteByte('q')
|
||||
buf.WriteByte('?')
|
||||
|
||||
enc := make([]byte, 3)
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case b == ' ':
|
||||
buf.WriteByte('_')
|
||||
case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
|
||||
buf.WriteByte(b)
|
||||
default:
|
||||
enc[0] = '='
|
||||
enc[1] = upperhex[b>>4]
|
||||
enc[2] = upperhex[b&0x0f]
|
||||
buf.Write(enc)
|
||||
}
|
||||
}
|
||||
buf.WriteString("?=")
|
||||
|
||||
es := buf.String()
|
||||
putBuffer(buf)
|
||||
return es
|
||||
}
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func putBuffer(buf *bytes.Buffer) {
|
||||
if buf.Len() > 1024 {
|
||||
return
|
||||
}
|
||||
buf.Reset()
|
||||
bufPool.Put(buf)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user