Merge branch 'master' into processconflict

This commit is contained in:
kingeasternsun
2021-08-26 09:33:23 +08:00
committed by GitHub
18 changed files with 286 additions and 130 deletions

View File

@ -16,7 +16,7 @@ func main() {
}
defer conn.Close()
if err := conn.Send([]byte("GET / HTTP/1.1\n\n")); err != nil {
if err := conn.Send([]byte("GET / HTTP/1.1\r\n\r\n")); err != nil {
panic(err)
}
@ -30,13 +30,14 @@ func main() {
array := bytes.Split(data, []byte(": "))
// 获得页面内容长度
if contentLength == 0 && len(array) == 2 && bytes.EqualFold([]byte("Content-Length"), array[0]) {
contentLength = gconv.Int(array[1])
// http 以\r\n换行需要把\r也去掉
contentLength = gconv.Int(string(array[1][:len(array[1])-1]))
}
header = append(header, data...)
header = append(header, '\n')
}
// header读取完毕读取文本内容
if contentLength > 0 && len(data) == 0 {
// header读取完毕读取文本内容, 1为\r
if contentLength > 0 && len(data) == 1 {
content, _ = conn.Recv(contentLength)
break
}

View File

@ -38,10 +38,6 @@ func (c *Core) Ctx(ctx context.Context) DB {
if ctx == nil {
return c.db
}
// It is already set context in previous chaining operation.
if c.ctx != nil {
return c.db
}
ctx = context.WithValue(ctx, ctxStrictKeyName, 1)
// It makes a shallow copy of current db and changes its context for next chaining operation.
var (
@ -111,6 +107,7 @@ func (c *Core) Close(ctx context.Context) (err error) {
if err != nil {
return
}
delete(m, k)
}
}
})

View File

@ -27,10 +27,15 @@ func (c *Core) Query(sql string, args ...interface{}) (rows *sql.Rows, err error
func (c *Core) DoQuery(ctx context.Context, link Link, sql string, args ...interface{}) (rows *sql.Rows, err error) {
// Transaction checks.
if link == nil {
if link, err = c.SlaveLink(); err != nil {
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
// Firstly, check and retrieve transaction link from context.
link = &txLink{tx.tx}
} else if link, err = c.SlaveLink(); err != nil {
// Or else it creates one from master node.
return nil, err
}
} else if !link.IsTransaction() {
// If current link is not transaction link, it checks and retrieves transaction from context.
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
link = &txLink{tx.tx}
}
@ -84,10 +89,15 @@ func (c *Core) Exec(sql string, args ...interface{}) (result sql.Result, err err
func (c *Core) DoExec(ctx context.Context, link Link, sql string, args ...interface{}) (result sql.Result, err error) {
// Transaction checks.
if link == nil {
if link, err = c.MasterLink(); err != nil {
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
// Firstly, check and retrieve transaction link from context.
link = &txLink{tx.tx}
} else if link, err = c.MasterLink(); err != nil {
// Or else it creates one from master node.
return nil, err
}
} else if !link.IsTransaction() {
// If current link is not transaction link, it checks and retrieves transaction from context.
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
link = &txLink{tx.tx}
}
@ -170,11 +180,25 @@ func (c *Core) Prepare(sql string, execOnMaster ...bool) (*Stmt, error) {
// DoPrepare calls prepare function on given link object and returns the statement object.
func (c *Core) DoPrepare(ctx context.Context, link Link, sql string) (*Stmt, error) {
if link != nil && !link.IsTransaction() {
// Transaction checks.
if link == nil {
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
// Firstly, check and retrieve transaction link from context.
link = &txLink{tx.tx}
} else {
// Or else it creates one from master node.
var err error
if link, err = c.MasterLink(); err != nil {
return nil, err
}
}
} else if !link.IsTransaction() {
// If current link is not transaction link, it checks and retrieves transaction from context.
if tx := TXFromCtx(ctx, c.db.GetGroup()); tx != nil {
link = &txLink{tx.tx}
}
}
if c.GetConfig().PrepareTimeout > 0 {
// DO NOT USE cancel function in prepare statement.
ctx, _ = context.WithTimeout(ctx, c.GetConfig().PrepareTimeout)

View File

@ -9,6 +9,7 @@ package gdb_test
import (
"context"
"fmt"
"github.com/gogf/gf/os/gctx"
"testing"
"github.com/gogf/gf/database/gdb"
@ -1116,3 +1117,34 @@ func Test_Transaction_Nested_SavePoint_RollbackTo(t *testing.T) {
t.Assert(all[0]["id"], 1)
})
}
func Test_Transaction_Method(t *testing.T) {
table := createTable()
defer dropTable(table)
gtest.C(t, func(t *gtest.T) {
var err error
err = db.Transaction(gctx.New(), func(ctx context.Context, tx *gdb.TX) error {
_, err = db.Model(table).Ctx(ctx).Data(g.Map{
"id": 1,
"passport": "t1",
"password": "25d55ad283aa400af464c76d713c07ad",
"nickname": "T1",
"create_time": gtime.Now().String(),
}).Insert()
t.AssertNil(err)
_, err = db.Ctx(ctx).Exec(fmt.Sprintf(
"insert into %s(`passport`,`password`,`nickname`,`create_time`,`id`) "+
"VALUES('t2','25d55ad283aa400af464c76d713c07ad','T2','2021-08-25 21:53:00',2) ",
table))
t.AssertNil(err)
return gerror.New("rollback")
})
t.AssertNE(err, nil)
count, err := db.Model(table).Count()
t.AssertNil(err)
t.Assert(count, 0)
})
}

View File

@ -19,6 +19,7 @@ func Example_conversionNormalFormats() {
"array" : ["John", "Ming"]
}
}`
if j, err := gjson.DecodeToJson(data); err != nil {
panic(err)
} else {

View File

@ -7,8 +7,6 @@
// Package gcode provides universal error code definition and common error codes implements.
package gcode
import "fmt"
// Code is universal error code interface definition.
type Code interface {
// Code returns the integer number of current error code.
@ -22,13 +20,6 @@ type Code interface {
Detail() interface{}
}
// localCode is an implementer for interface Code for internal usage only.
type localCode struct {
code int // Error code, usually an integer.
message string // Brief message for this error code.
detail interface{} // As type of interface, it is mainly designed as an extension field for error code.
}
// ================================================================================================================
// Common error code definition.
// There are reserved internal error code by framework: code < 1000.
@ -52,7 +43,7 @@ var (
CodeSecurityReason = localCode{62, "Security Reason", nil} // Security Reason.
CodeServerBusy = localCode{63, "Server Is Busy", nil} // Server is busy, please try again later.
CodeUnknown = localCode{64, "Unknown Error", nil} // Unknown error.
CodeResourceNotExist = localCode{65, "Resource Not Exist", nil} // Resource does not exist.
CodeNotFound = localCode{65, "Not Found", nil} // Resource does not exist.
CodeInvalidRequest = localCode{66, "Invalid Request", nil} // Invalid request.
CodeBusinessValidationFailed = localCode{300, "Business Validation Failed", nil} // Business validation failed.
)
@ -66,30 +57,3 @@ func New(code int, message string, detail interface{}) Code {
detail: detail,
}
}
// Code returns the integer number of current error code.
func (c localCode) Code() int {
return c.code
}
// Message returns the brief message for current error code.
func (c localCode) Message() string {
return c.message
}
// Detail returns the detailed information of current error code,
// which is mainly designed as an extension field for error code.
func (c localCode) Detail() interface{} {
return c.detail
}
// String returns current error code as a string.
func (c localCode) String() string {
if c.detail != nil {
return fmt.Sprintf(`%d:%s %v`, c.code, c.message, c.detail)
}
if c.message != "" {
return fmt.Sprintf(`%d:%s`, c.code, c.message)
}
return fmt.Sprintf(`%d`, c.code)
}

View File

@ -0,0 +1,43 @@
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gcode
import "fmt"
// localCode is an implementer for interface Code for internal usage only.
type localCode struct {
code int // Error code, usually an integer.
message string // Brief message for this error code.
detail interface{} // As type of interface, it is mainly designed as an extension field for error code.
}
// Code returns the integer number of current error code.
func (c localCode) Code() int {
return c.code
}
// Message returns the brief message for current error code.
func (c localCode) Message() string {
return c.message
}
// Detail returns the detailed information of current error code,
// which is mainly designed as an extension field for error code.
func (c localCode) Detail() interface{} {
return c.detail
}
// String returns current error code as a string.
func (c localCode) String() string {
if c.detail != nil {
return fmt.Sprintf(`%d:%s %v`, c.code, c.message, c.detail)
}
if c.message != "" {
return fmt.Sprintf(`%d:%s`, c.code, c.message)
}
return fmt.Sprintf(`%d`, c.code)
}

View File

@ -0,0 +1,23 @@
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gcode_test
import (
"github.com/gogf/gf/errors/gcode"
"testing"
"github.com/gogf/gf/test/gtest"
)
func Test_Nil(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
c := gcode.New(1, "custom error", "detailed description")
t.Assert(c.Code(), 1)
t.Assert(c.Message(), "custom error")
t.Assert(c.Detail(), "detailed description")
})
}

7
go.mod
View File

@ -5,24 +5,17 @@ go 1.14
require (
github.com/BurntSushi/toml v0.3.1
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.12.0
github.com/fsnotify/fsnotify v1.4.9
github.com/gogf/mysql v1.6.1-0.20210603073548-16164ae25579
github.com/gomodule/redigo v1.8.5
github.com/gorilla/websocket v1.4.2
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-runewidth v0.0.10 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/olekukonko/tablewriter v0.0.5
go.opentelemetry.io/otel v1.0.0-RC2
go.opentelemetry.io/otel/oteltest v1.0.0-RC2
go.opentelemetry.io/otel/trace v1.0.0-RC2
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
golang.org/x/text v0.3.6
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)

24
go.sum
View File

@ -2,10 +2,8 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28 h1:LdXxtjzvZYhhUaonAaAKArG3pyC67kGL3YY+6hGG8G4=
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
@ -20,25 +18,16 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf h1:wIOAyJMMen0ELGiFzlmqxdcV1yGbkyHBAB6PolcNbLA=
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
@ -55,19 +44,16 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=

View File

@ -23,12 +23,12 @@ type (
Server struct {
name string // Unique name for instance management.
config ServerConfig // Configuration.
plugins []Plugin // Plugin array to extends server functionality.
plugins []Plugin // Plugin array to extend server functionality.
servers []*gracefulServer // Underlying http.Server array.
serverCount *gtype.Int // Underlying http.Server count.
closeChan chan struct{} // Used for underlying server closing event notification.
serveTree map[string]interface{} // The route map tree.
serveCache *gcache.Cache // Server cache for internal usage.
serveCache *gcache.Cache // Server caches for internal usage.
routesMap map[string][]registeredRouteItem // Route map mainly for route dumps and repeated route checks.
statusHandlerMap map[string][]HandlerFunc // Custom status handler map.
sessionManager *gsession.Manager // Session manager.
@ -142,7 +142,7 @@ var (
serverMapping = gmap.NewStrAnyMap(true)
// serverRunning marks the running server count.
// If there no successful server running or all servers shutdown, this value is 0.
// If there is no successful server running or all servers' shutdown, this value is 0.
serverRunning = gtype.NewInt()
// wsUpGrader is the default up-grader configuration for websocket.

View File

@ -33,6 +33,10 @@ type cronSchedule struct {
const (
// regular expression for cron pattern, which contains 6 parts of time units.
regexForCron = `^([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,]+)\s+([\-/\d\*\?,A-Za-z]+)\s+([\-/\d\*\?,A-Za-z]+)$`
patternItemTypeUnknown = iota
patternItemTypeWeek
patternItemTypeMonth
)
var (
@ -103,37 +107,37 @@ func newSchedule(pattern string) (*cronSchedule, error) {
pattern: pattern,
}
// Second.
if m, err := parseItem(match[1], 0, 59, false); err != nil {
if m, err := parsePatternItem(match[1], 0, 59, false); err != nil {
return nil, err
} else {
schedule.second = m
}
// Minute.
if m, err := parseItem(match[2], 0, 59, false); err != nil {
if m, err := parsePatternItem(match[2], 0, 59, false); err != nil {
return nil, err
} else {
schedule.minute = m
}
// Hour.
if m, err := parseItem(match[3], 0, 23, false); err != nil {
if m, err := parsePatternItem(match[3], 0, 23, false); err != nil {
return nil, err
} else {
schedule.hour = m
}
// Day.
if m, err := parseItem(match[4], 1, 31, true); err != nil {
if m, err := parsePatternItem(match[4], 1, 31, true); err != nil {
return nil, err
} else {
schedule.day = m
}
// Month.
if m, err := parseItem(match[5], 1, 12, false); err != nil {
if m, err := parsePatternItem(match[5], 1, 12, false); err != nil {
return nil, err
} else {
schedule.month = m
}
// Week.
if m, err := parseItem(match[6], 0, 6, true); err != nil {
if m, err := parsePatternItem(match[6], 0, 6, true); err != nil {
return nil, err
} else {
schedule.week = m
@ -144,55 +148,57 @@ func newSchedule(pattern string) (*cronSchedule, error) {
}
}
// parseItem parses every item in the pattern and returns the result as map.
func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) {
// parsePatternItem parses every item in the pattern and returns the result as map, which is used for indexing.
func parsePatternItem(item string, min int, max int, allowQuestionMark bool) (map[int]struct{}, error) {
m := make(map[int]struct{}, max-min+1)
if item == "*" || (allowQuestionMark && item == "?") {
for i := min; i <= max; i++ {
m[i] = struct{}{}
}
} else {
// Like: MON,FRI
for _, item := range strings.Split(item, ",") {
interval := 1
intervalArray := strings.Split(item, "/")
var (
interval = 1
intervalArray = strings.Split(item, "/")
)
if len(intervalArray) == 2 {
if i, err := strconv.Atoi(intervalArray[1]); err != nil {
if number, err := strconv.Atoi(intervalArray[1]); err != nil {
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item)
} else {
interval = i
interval = number
}
}
var (
rangeMin = min
rangeMax = max
fieldType = byte(0)
itemType = patternItemTypeUnknown
rangeArray = strings.Split(intervalArray[0], "-") // Like: 1-30, JAN-DEC
)
switch max {
case 6:
// It's checking week field.
fieldType = 'w'
itemType = patternItemTypeWeek
case 12:
// It's checking month field.
fieldType = 'm'
itemType = patternItemTypeMonth
}
// Eg: */5
if rangeArray[0] != "*" {
if i, err := parseItemValue(rangeArray[0], fieldType); err != nil {
if number, err := parsePatternItemValue(rangeArray[0], itemType); err != nil {
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item)
} else {
rangeMin = i
rangeMin = number
if len(intervalArray) == 1 {
rangeMax = i
rangeMax = number
}
}
}
if len(rangeArray) == 2 {
if i, err := parseItemValue(rangeArray[1], fieldType); err != nil {
if number, err := parsePatternItemValue(rangeArray[1], itemType); err != nil {
return nil, gerror.NewCodef(gcode.CodeInvalidParameter, `invalid pattern item: "%s"`, item)
} else {
rangeMax = i
rangeMax = number
}
}
for i := rangeMin; i <= rangeMax; i += interval {
@ -203,24 +209,24 @@ func parseItem(item string, min int, max int, allowQuestionMark bool) (map[int]s
return m, nil
}
// parseItemValue parses the field value to a number according to its field type.
func parseItemValue(value string, fieldType byte) (int, error) {
// parsePatternItemValue parses the field value to a number according to its field type.
func parsePatternItemValue(value string, itemType int) (int, error) {
if gregex.IsMatchString(`^\d+$`, value) {
// Pure number.
if i, err := strconv.Atoi(value); err == nil {
return i, nil
// It is pure number.
if number, err := strconv.Atoi(value); err == nil {
return number, nil
}
} else {
// Check if contains letter,
// Check if it contains letter,
// it converts the value to number according to predefined map.
switch fieldType {
case 'm':
if i, ok := monthMap[strings.ToLower(value)]; ok {
return i, nil
switch itemType {
case patternItemTypeWeek:
if number, ok := monthMap[strings.ToLower(value)]; ok {
return number, nil
}
case 'w':
if i, ok := weekMap[strings.ToLower(value)]; ok {
return i, nil
case patternItemTypeMonth:
if number, ok := weekMap[strings.ToLower(value)]; ok {
return number, nil
}
}
}

View File

@ -61,7 +61,7 @@ func ParseContent(ctx context.Context, content string, params ...Params) (string
}
// New returns a new view object.
// The parameter <path> specifies the template directory path to load template files.
// The parameter `path` specifies the template directory path to load template files.
func New(path ...string) *View {
view := &View{
paths: garray.NewStrArray(),
@ -143,6 +143,10 @@ func New(path ...string) *View {
"map": view.buildInFuncMap,
"maps": view.buildInFuncMaps,
"json": view.buildInFuncJson,
"plus": view.buildInFuncPlus,
"minus": view.buildInFuncMinus,
"times": view.buildInFuncTimes,
"divide": view.buildInFuncDivide,
})
return view

View File

@ -219,8 +219,53 @@ func (view *View) buildInFuncNl2Br(str interface{}) string {
}
// buildInFuncJson implements build-in template function: json ,
// which encodes and returns <value> as JSON string.
// which encodes and returns `value` as JSON string.
func (view *View) buildInFuncJson(value interface{}) (string, error) {
b, err := json.Marshal(value)
return gconv.UnsafeBytesToStr(b), err
}
// buildInFuncPlus implements build-in template function: plus ,
// which returns the result that pluses all `deltas` to `value`.
func (view *View) buildInFuncPlus(value interface{}, deltas ...interface{}) string {
result := gconv.Float64(value)
for _, v := range deltas {
result += gconv.Float64(v)
}
return gconv.String(result)
}
// buildInFuncMinus implements build-in template function: minus ,
// which returns the result that subtracts all `deltas` from `value`.
func (view *View) buildInFuncMinus(value interface{}, deltas ...interface{}) string {
result := gconv.Float64(value)
for _, v := range deltas {
result -= gconv.Float64(v)
}
return gconv.String(result)
}
// buildInFuncTimes implements build-in template function: times ,
// which returns the result that multiplies `value` by all of `values`.
func (view *View) buildInFuncTimes(value interface{}, values ...interface{}) string {
result := gconv.Float64(value)
for _, v := range values {
result *= gconv.Float64(v)
}
return gconv.String(result)
}
// buildInFuncDivide implements build-in template function: divide ,
// which returns the result that divides `value` by all of `values`.
func (view *View) buildInFuncDivide(value interface{}, values ...interface{}) string {
result := gconv.Float64(value)
for _, v := range values {
value2Float64 := gconv.Float64(v)
if value2Float64 == 0 {
// Invalid `value2`.
return "0"
}
result /= value2Float64
}
return gconv.String(result)
}

View File

@ -95,7 +95,7 @@ func (view *View) SetConfigWithMap(m map[string]interface{}) error {
}
// SetPath sets the template directory path for template file search.
// The parameter <path> can be absolute or relative path, but absolute path is suggested.
// The parameter `path` can be absolute or relative path, but absolute path is suggested.
func (view *View) SetPath(path string) error {
var (
isDir = false
@ -239,9 +239,9 @@ func (view *View) SetAutoEncode(enable bool) {
view.config.AutoEncode = enable
}
// BindFunc registers customized global template function named <name>
// with given function <function> to current view object.
// The <name> is the function name which can be called in template content.
// BindFunc registers customized global template function named `name`
// with given function `function` to current view object.
// The `name` is the function name which can be called in template content.
func (view *View) BindFunc(name string, function interface{}) {
view.funcMap[name] = function
// Clear global template object cache.

View File

@ -19,7 +19,7 @@ var (
)
// Instance returns an instance of View with default settings.
// The parameter <name> is the name for the instance.
// The parameter `name` is the name for the instance.
func Instance(name ...string) *View {
key := DefaultName
if len(name) > 0 && name[0] != "" {

View File

@ -53,7 +53,7 @@ var (
resourceTryFolders = []string{"template/", "template", "/template", "/template/"}
)
// Parse parses given template file <file> with given template variables <params>
// Parse parses given template file `file` with given template variables `params`
// and returns the parsed template content.
func (view *View) Parse(ctx context.Context, file string, params ...Params) (result string, err error) {
var tpl interface{}
@ -65,7 +65,7 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
content string
resource *gres.File
)
// Searching the absolute file path for <file>.
// Searching the absolute file path for `file`.
path, folder, resource, err = view.searchFile(file)
if err != nil {
return nil
@ -100,7 +100,7 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
if item.content == "" {
return "", nil
}
// Get the template object instance for <folder>.
// Get the template object instance for `folder`.
tpl, err = view.getTemplate(item.path, item.folder, fmt.Sprintf(`*%s`, gfile.Ext(item.path)))
if err != nil {
return "", err
@ -120,7 +120,7 @@ func (view *View) Parse(ctx context.Context, file string, params ...Params) (res
return "", err
}
// Note that the template variable assignment cannot change the value
// of the existing <params> or view.data because both variables are pointers.
// of the existing `params` or view.data because both variables are pointers.
// It needs to merge the values of the two maps into a new map.
variables := gutil.MapMergeCopy(params...)
if len(view.data) > 0 {
@ -154,7 +154,7 @@ func (view *View) ParseDefault(ctx context.Context, params ...Params) (result st
return view.Parse(ctx, view.config.DefaultFile, params...)
}
// ParseContent parses given template content <content> with template variables <params>
// ParseContent parses given template content `content` with template variables `params`
// and returns the parsed content in []byte.
func (view *View) ParseContent(ctx context.Context, content string, params ...Params) (string, error) {
// It's not necessary continuing parsing if template content is empty.
@ -188,7 +188,7 @@ func (view *View) ParseContent(ctx context.Context, content string, params ...Pa
return "", err
}
// Note that the template variable assignment cannot change the value
// of the existing <params> or view.data because both variables are pointers.
// of the existing `params` or view.data because both variables are pointers.
// It needs to merge the values of the two maps into a new map.
variables := gutil.MapMergeCopy(params...)
if len(view.data) > 0 {
@ -216,10 +216,10 @@ func (view *View) ParseContent(ctx context.Context, content string, params ...Pa
return result, nil
}
// getTemplate returns the template object associated with given template file <path>.
// getTemplate returns the template object associated with given template file `path`.
// It uses template cache to enhance performance, that is, it will return the same template object
// with the same given <path>. It will also automatically refresh the template cache
// if the template files under <path> changes (recursively).
// with the same given `path`. It will also automatically refresh the template cache
// if the template files under `path` changes (recursively).
func (view *View) getTemplate(filePath, folderPath, pattern string) (tpl interface{}, err error) {
// Key for template cache.
key := fmt.Sprintf("%s_%v", filePath, view.config.Delimiters)
@ -304,9 +304,9 @@ func (view *View) formatTemplateObjectCreatingError(filePath, tplName string, er
return nil
}
// searchFile returns the found absolute path for <file> and its template folder path.
// Note that, the returned <folder> is the template folder path, but not the folder of
// the returned template file <path>.
// searchFile returns the found absolute path for `file` and its template folder path.
// Note that, the returned `folder` is the template folder path, but not the folder of
// the returned template file `path`.
func (view *View) searchFile(file string) (path string, folder string, resource *gres.File, err error) {
// Firstly checking the resource manager.
if !gres.IsEmpty() {

View File

@ -9,6 +9,7 @@ package gview_test
import (
"context"
"github.com/gogf/gf/encoding/ghtml"
"github.com/gogf/gf/os/gctx"
"github.com/gogf/gf/os/gtime"
"github.com/gogf/gf/util/gconv"
"io/ioutil"
@ -426,3 +427,39 @@ func Test_BuildInFuncJson(t *testing.T) {
t.Assert(r, `{"name":"john"}`)
})
}
func Test_BuildInFuncPlus(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
r, err := v.ParseContent(gctx.New(), "{{plus 1 2 3}}")
t.Assert(err, nil)
t.Assert(r, `6`)
})
}
func Test_BuildInFuncMinus(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
r, err := v.ParseContent(gctx.New(), "{{minus 1 2 3}}")
t.Assert(err, nil)
t.Assert(r, `-4`)
})
}
func Test_BuildInFuncTimes(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
r, err := v.ParseContent(gctx.New(), "{{times 1 2 3 4}}")
t.Assert(err, nil)
t.Assert(r, `24`)
})
}
func Test_BuildInFuncDivide(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
v := gview.New()
r, err := v.ParseContent(gctx.New(), "{{divide 8 2 2}}")
t.Assert(err, nil)
t.Assert(r, `2`)
})
}