mirror of
https://github.com/astaxie/beego.git
synced 2025-06-11 14:10:39 +00:00
Merge pull request #4173 from AllenX2018/fix-bug-queryRow
Fix issue 3866
This commit is contained in:
25
pkg/infrastructure/utils/caller.go
Normal file
25
pkg/infrastructure/utils/caller.go
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// GetFuncName get function name
|
||||
func GetFuncName(i interface{}) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
}
|
28
pkg/infrastructure/utils/caller_test.go
Normal file
28
pkg/infrastructure/utils/caller_test.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetFuncName(t *testing.T) {
|
||||
name := GetFuncName(TestGetFuncName)
|
||||
t.Log(name)
|
||||
if !strings.HasSuffix(name, ".TestGetFuncName") {
|
||||
t.Error("get func name error")
|
||||
}
|
||||
}
|
478
pkg/infrastructure/utils/debug.go
Normal file
478
pkg/infrastructure/utils/debug.go
Normal file
@ -0,0 +1,478 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
dunno = []byte("???")
|
||||
centerDot = []byte("·")
|
||||
dot = []byte(".")
|
||||
)
|
||||
|
||||
type pointerInfo struct {
|
||||
prev *pointerInfo
|
||||
n int
|
||||
addr uintptr
|
||||
pos int
|
||||
used []int
|
||||
}
|
||||
|
||||
// Display print the data in console
|
||||
func Display(data ...interface{}) {
|
||||
display(true, data...)
|
||||
}
|
||||
|
||||
// GetDisplayString return data print string
|
||||
func GetDisplayString(data ...interface{}) string {
|
||||
return display(false, data...)
|
||||
}
|
||||
|
||||
func display(displayed bool, data ...interface{}) string {
|
||||
var pc, file, line, ok = runtime.Caller(2)
|
||||
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
var buf = new(bytes.Buffer)
|
||||
|
||||
fmt.Fprintf(buf, "[Debug] at %s() [%s:%d]\n", function(pc), file, line)
|
||||
|
||||
fmt.Fprintf(buf, "\n[Variables]\n")
|
||||
|
||||
for i := 0; i < len(data); i += 2 {
|
||||
var output = fomateinfo(len(data[i].(string))+3, data[i+1])
|
||||
fmt.Fprintf(buf, "%s = %s", data[i], output)
|
||||
}
|
||||
|
||||
if displayed {
|
||||
log.Print(buf)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// return data dump and format bytes
|
||||
func fomateinfo(headlen int, data ...interface{}) []byte {
|
||||
var buf = new(bytes.Buffer)
|
||||
|
||||
if len(data) > 1 {
|
||||
fmt.Fprint(buf, " ")
|
||||
|
||||
fmt.Fprint(buf, "[")
|
||||
|
||||
fmt.Fprintln(buf)
|
||||
}
|
||||
|
||||
for k, v := range data {
|
||||
var buf2 = new(bytes.Buffer)
|
||||
var pointers *pointerInfo
|
||||
var interfaces = make([]reflect.Value, 0, 10)
|
||||
|
||||
printKeyValue(buf2, reflect.ValueOf(v), &pointers, &interfaces, nil, true, " ", 1)
|
||||
|
||||
if k < len(data)-1 {
|
||||
fmt.Fprint(buf2, ", ")
|
||||
}
|
||||
|
||||
fmt.Fprintln(buf2)
|
||||
|
||||
buf.Write(buf2.Bytes())
|
||||
}
|
||||
|
||||
if len(data) > 1 {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
fmt.Fprint(buf, " ")
|
||||
|
||||
fmt.Fprint(buf, "]")
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// check data is golang basic type
|
||||
func isSimpleType(val reflect.Value, kind reflect.Kind, pointers **pointerInfo, interfaces *[]reflect.Value) bool {
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Chan:
|
||||
return true
|
||||
case reflect.Invalid:
|
||||
return true
|
||||
case reflect.Interface:
|
||||
for _, in := range *interfaces {
|
||||
if reflect.DeepEqual(in, val) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case reflect.UnsafePointer:
|
||||
if val.IsNil() {
|
||||
return true
|
||||
}
|
||||
|
||||
var elem = val.Elem()
|
||||
|
||||
if isSimpleType(elem, elem.Kind(), pointers, interfaces) {
|
||||
return true
|
||||
}
|
||||
|
||||
var addr = val.Elem().UnsafeAddr()
|
||||
|
||||
for p := *pointers; p != nil; p = p.prev {
|
||||
if addr == p.addr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// dump value
|
||||
func printKeyValue(buf *bytes.Buffer, val reflect.Value, pointers **pointerInfo, interfaces *[]reflect.Value, structFilter func(string, string) bool, formatOutput bool, indent string, level int) {
|
||||
var t = val.Kind()
|
||||
|
||||
switch t {
|
||||
case reflect.Bool:
|
||||
fmt.Fprint(buf, val.Bool())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
fmt.Fprint(buf, val.Int())
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||
fmt.Fprint(buf, val.Uint())
|
||||
case reflect.Float32, reflect.Float64:
|
||||
fmt.Fprint(buf, val.Float())
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
fmt.Fprint(buf, val.Complex())
|
||||
case reflect.UnsafePointer:
|
||||
fmt.Fprintf(buf, "unsafe.Pointer(0x%X)", val.Pointer())
|
||||
case reflect.Ptr:
|
||||
if val.IsNil() {
|
||||
fmt.Fprint(buf, "nil")
|
||||
return
|
||||
}
|
||||
|
||||
var addr = val.Elem().UnsafeAddr()
|
||||
|
||||
for p := *pointers; p != nil; p = p.prev {
|
||||
if addr == p.addr {
|
||||
p.used = append(p.used, buf.Len())
|
||||
fmt.Fprintf(buf, "0x%X", addr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
*pointers = &pointerInfo{
|
||||
prev: *pointers,
|
||||
addr: addr,
|
||||
pos: buf.Len(),
|
||||
used: make([]int, 0),
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "&")
|
||||
|
||||
printKeyValue(buf, val.Elem(), pointers, interfaces, structFilter, formatOutput, indent, level)
|
||||
case reflect.String:
|
||||
fmt.Fprint(buf, "\"", val.String(), "\"")
|
||||
case reflect.Interface:
|
||||
var value = val.Elem()
|
||||
|
||||
if !value.IsValid() {
|
||||
fmt.Fprint(buf, "nil")
|
||||
} else {
|
||||
for _, in := range *interfaces {
|
||||
if reflect.DeepEqual(in, val) {
|
||||
fmt.Fprint(buf, "repeat")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
*interfaces = append(*interfaces, val)
|
||||
|
||||
printKeyValue(buf, value, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
}
|
||||
case reflect.Struct:
|
||||
var t = val.Type()
|
||||
|
||||
fmt.Fprint(buf, t)
|
||||
fmt.Fprint(buf, "{")
|
||||
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
if formatOutput {
|
||||
fmt.Fprintln(buf)
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
var name = t.Field(i).Name
|
||||
|
||||
if formatOutput {
|
||||
for ind := 0; ind < level; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, name)
|
||||
fmt.Fprint(buf, ": ")
|
||||
|
||||
if structFilter != nil && structFilter(t.String(), name) {
|
||||
fmt.Fprint(buf, "ignore")
|
||||
} else {
|
||||
printKeyValue(buf, val.Field(i), pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, ",")
|
||||
}
|
||||
|
||||
if formatOutput {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
for ind := 0; ind < level-1; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "}")
|
||||
case reflect.Array, reflect.Slice:
|
||||
fmt.Fprint(buf, val.Type())
|
||||
fmt.Fprint(buf, "{")
|
||||
|
||||
var allSimple = true
|
||||
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
var elem = val.Index(i)
|
||||
|
||||
var isSimple = isSimpleType(elem, elem.Kind(), pointers, interfaces)
|
||||
|
||||
if !isSimple {
|
||||
allSimple = false
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
fmt.Fprintln(buf)
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
for ind := 0; ind < level; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
printKeyValue(buf, elem, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
|
||||
if i != val.Len()-1 || !allSimple {
|
||||
fmt.Fprint(buf, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if formatOutput && !allSimple {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
for ind := 0; ind < level-1; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "}")
|
||||
case reflect.Map:
|
||||
var t = val.Type()
|
||||
var keys = val.MapKeys()
|
||||
|
||||
fmt.Fprint(buf, t)
|
||||
fmt.Fprint(buf, "{")
|
||||
|
||||
var allSimple = true
|
||||
|
||||
for i := 0; i < len(keys); i++ {
|
||||
var elem = val.MapIndex(keys[i])
|
||||
|
||||
var isSimple = isSimpleType(elem, elem.Kind(), pointers, interfaces)
|
||||
|
||||
if !isSimple {
|
||||
allSimple = false
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
fmt.Fprintln(buf)
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
if formatOutput && !isSimple {
|
||||
for ind := 0; ind <= level; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
}
|
||||
|
||||
printKeyValue(buf, keys[i], pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
fmt.Fprint(buf, ": ")
|
||||
printKeyValue(buf, elem, pointers, interfaces, structFilter, formatOutput, indent, level+1)
|
||||
|
||||
if i != val.Len()-1 || !allSimple {
|
||||
fmt.Fprint(buf, ",")
|
||||
}
|
||||
}
|
||||
|
||||
if formatOutput && !allSimple {
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
for ind := 0; ind < level-1; ind++ {
|
||||
fmt.Fprint(buf, indent)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(buf, " ")
|
||||
}
|
||||
|
||||
fmt.Fprint(buf, "}")
|
||||
case reflect.Chan:
|
||||
fmt.Fprint(buf, val.Type())
|
||||
case reflect.Invalid:
|
||||
fmt.Fprint(buf, "invalid")
|
||||
default:
|
||||
fmt.Fprint(buf, "unknow")
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPointerInfo dump pointer value
|
||||
func PrintPointerInfo(buf *bytes.Buffer, headlen int, pointers *pointerInfo) {
|
||||
var anyused = false
|
||||
var pointerNum = 0
|
||||
|
||||
for p := pointers; p != nil; p = p.prev {
|
||||
if len(p.used) > 0 {
|
||||
anyused = true
|
||||
}
|
||||
pointerNum++
|
||||
p.n = pointerNum
|
||||
}
|
||||
|
||||
if anyused {
|
||||
var pointerBufs = make([][]rune, pointerNum+1)
|
||||
|
||||
for i := 0; i < len(pointerBufs); i++ {
|
||||
var pointerBuf = make([]rune, buf.Len()+headlen)
|
||||
|
||||
for j := 0; j < len(pointerBuf); j++ {
|
||||
pointerBuf[j] = ' '
|
||||
}
|
||||
|
||||
pointerBufs[i] = pointerBuf
|
||||
}
|
||||
|
||||
for pn := 0; pn <= pointerNum; pn++ {
|
||||
for p := pointers; p != nil; p = p.prev {
|
||||
if len(p.used) > 0 && p.n >= pn {
|
||||
if pn == p.n {
|
||||
pointerBufs[pn][p.pos+headlen] = '└'
|
||||
|
||||
var maxpos = 0
|
||||
|
||||
for i, pos := range p.used {
|
||||
if i < len(p.used)-1 {
|
||||
pointerBufs[pn][pos+headlen] = '┴'
|
||||
} else {
|
||||
pointerBufs[pn][pos+headlen] = '┘'
|
||||
}
|
||||
|
||||
maxpos = pos
|
||||
}
|
||||
|
||||
for i := 0; i < maxpos-p.pos-1; i++ {
|
||||
if pointerBufs[pn][i+p.pos+headlen+1] == ' ' {
|
||||
pointerBufs[pn][i+p.pos+headlen+1] = '─'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pointerBufs[pn][p.pos+headlen] = '│'
|
||||
|
||||
for _, pos := range p.used {
|
||||
if pointerBufs[pn][pos+headlen] == ' ' {
|
||||
pointerBufs[pn][pos+headlen] = '│'
|
||||
} else {
|
||||
pointerBufs[pn][pos+headlen] = '┼'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(string(pointerBufs[pn]) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stack get stack bytes
|
||||
func Stack(skip int, indent string) []byte {
|
||||
var buf = new(bytes.Buffer)
|
||||
|
||||
for i := skip; ; i++ {
|
||||
var pc, file, line, ok = runtime.Caller(i)
|
||||
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
buf.WriteString(indent)
|
||||
|
||||
fmt.Fprintf(buf, "at %s() [%s:%d]\n", function(pc), file, line)
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// return the name of the function containing the PC if possible,
|
||||
func function(pc uintptr) []byte {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return dunno
|
||||
}
|
||||
name := []byte(fn.Name())
|
||||
// The name includes the path name to the package, which is unnecessary
|
||||
// since the file name is already included. Plus, it has center dots.
|
||||
// That is, we see
|
||||
// runtime/debug.*T·ptrmethod
|
||||
// and want
|
||||
// *T.ptrmethod
|
||||
if period := bytes.Index(name, dot); period >= 0 {
|
||||
name = name[period+1:]
|
||||
}
|
||||
name = bytes.Replace(name, centerDot, dot, -1)
|
||||
return name
|
||||
}
|
46
pkg/infrastructure/utils/debug_test.go
Normal file
46
pkg/infrastructure/utils/debug_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mytype struct {
|
||||
next *mytype
|
||||
prev *mytype
|
||||
}
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
Display("v1", 1, "v2", 2, "v3", 3)
|
||||
}
|
||||
|
||||
func TestPrintPoint(t *testing.T) {
|
||||
var v1 = new(mytype)
|
||||
var v2 = new(mytype)
|
||||
|
||||
v1.prev = nil
|
||||
v1.next = v2
|
||||
|
||||
v2.prev = v1
|
||||
v2.next = nil
|
||||
|
||||
Display("v1", v1, "v2", v2)
|
||||
}
|
||||
|
||||
func TestPrintString(t *testing.T) {
|
||||
str := GetDisplayString("v1", 1, "v2", 2)
|
||||
println(str)
|
||||
}
|
101
pkg/infrastructure/utils/file.go
Normal file
101
pkg/infrastructure/utils/file.go
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// SelfPath gets compiled executable file absolute path
|
||||
func SelfPath() string {
|
||||
path, _ := filepath.Abs(os.Args[0])
|
||||
return path
|
||||
}
|
||||
|
||||
// SelfDir gets compiled executable file directory
|
||||
func SelfDir() string {
|
||||
return filepath.Dir(SelfPath())
|
||||
}
|
||||
|
||||
// FileExists reports whether the named file or directory exists.
|
||||
func FileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SearchFile Search a file in paths.
|
||||
// this is often used in search config file in /etc ~/
|
||||
func SearchFile(filename string, paths ...string) (fullpath string, err error) {
|
||||
for _, path := range paths {
|
||||
if fullpath = filepath.Join(path, filename); FileExists(fullpath) {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = errors.New(fullpath + " not found in paths")
|
||||
return
|
||||
}
|
||||
|
||||
// GrepFile like command grep -E
|
||||
// for example: GrepFile(`^hello`, "hello.txt")
|
||||
// \n is striped while read
|
||||
func GrepFile(patten string, filename string) (lines []string, err error) {
|
||||
re, err := regexp.Compile(patten)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fd, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lines = make([]string, 0)
|
||||
reader := bufio.NewReader(fd)
|
||||
prefix := ""
|
||||
var isLongLine bool
|
||||
for {
|
||||
byteLine, isPrefix, er := reader.ReadLine()
|
||||
if er != nil && er != io.EOF {
|
||||
return nil, er
|
||||
}
|
||||
if er == io.EOF {
|
||||
break
|
||||
}
|
||||
line := string(byteLine)
|
||||
if isPrefix {
|
||||
prefix += line
|
||||
continue
|
||||
} else {
|
||||
isLongLine = true
|
||||
}
|
||||
|
||||
line = prefix + line
|
||||
if isLongLine {
|
||||
prefix = ""
|
||||
}
|
||||
if re.MatchString(line) {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
return lines, nil
|
||||
}
|
75
pkg/infrastructure/utils/file_test.go
Normal file
75
pkg/infrastructure/utils/file_test.go
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var noExistedFile = "/tmp/not_existed_file"
|
||||
|
||||
func TestSelfPath(t *testing.T) {
|
||||
path := SelfPath()
|
||||
if path == "" {
|
||||
t.Error("path cannot be empty")
|
||||
}
|
||||
t.Logf("SelfPath: %s", path)
|
||||
}
|
||||
|
||||
func TestSelfDir(t *testing.T) {
|
||||
dir := SelfDir()
|
||||
t.Logf("SelfDir: %s", dir)
|
||||
}
|
||||
|
||||
func TestFileExists(t *testing.T) {
|
||||
if !FileExists("./file.go") {
|
||||
t.Errorf("./file.go should exists, but it didn't")
|
||||
}
|
||||
|
||||
if FileExists(noExistedFile) {
|
||||
t.Errorf("Weird, how could this file exists: %s", noExistedFile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchFile(t *testing.T) {
|
||||
path, err := SearchFile(filepath.Base(SelfPath()), SelfDir())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(path)
|
||||
|
||||
_, err = SearchFile(noExistedFile, ".")
|
||||
if err == nil {
|
||||
t.Errorf("err shouldnt be nil, got path: %s", SelfDir())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGrepFile(t *testing.T) {
|
||||
_, err := GrepFile("", noExistedFile)
|
||||
if err == nil {
|
||||
t.Error("expect file-not-existed error, but got nothing")
|
||||
}
|
||||
|
||||
path := filepath.Join(".", "testdata", "grepe.test")
|
||||
lines, err := GrepFile(`^\s*[^#]+`, path)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(lines, []string{"hello", "world"}) {
|
||||
t.Errorf("expect [hello world], but receive %v", lines)
|
||||
}
|
||||
}
|
87
pkg/infrastructure/utils/kv.go
Normal file
87
pkg/infrastructure/utils/kv.go
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright 2020 beego-dev
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
type KV interface {
|
||||
GetKey() interface{}
|
||||
GetValue() interface{}
|
||||
}
|
||||
|
||||
// SimpleKV is common structure to store key-value pairs.
|
||||
// When you need something like Pair, you can use this
|
||||
type SimpleKV struct {
|
||||
Key interface{}
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
var _ KV = new(SimpleKV)
|
||||
|
||||
func (s *SimpleKV) GetKey() interface{} {
|
||||
return s.Key
|
||||
}
|
||||
|
||||
func (s *SimpleKV) GetValue() interface{} {
|
||||
return s.Value
|
||||
}
|
||||
|
||||
// KVs interface
|
||||
type KVs interface {
|
||||
GetValueOr(key interface{}, defValue interface{}) interface{}
|
||||
Contains(key interface{}) bool
|
||||
IfContains(key interface{}, action func(value interface{})) KVs
|
||||
}
|
||||
|
||||
// SimpleKVs will store SimpleKV collection as map
|
||||
type SimpleKVs struct {
|
||||
kvs map[interface{}]interface{}
|
||||
}
|
||||
|
||||
var _ KVs = new(SimpleKVs)
|
||||
|
||||
// GetValueOr returns the value for a given key, if non-existant
|
||||
// it returns defValue
|
||||
func (kvs *SimpleKVs) GetValueOr(key interface{}, defValue interface{}) interface{} {
|
||||
v, ok := kvs.kvs[key]
|
||||
if ok {
|
||||
return v
|
||||
}
|
||||
return defValue
|
||||
}
|
||||
|
||||
// Contains checks if a key exists
|
||||
func (kvs *SimpleKVs) Contains(key interface{}) bool {
|
||||
_, ok := kvs.kvs[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// IfContains invokes the action on a key if it exists
|
||||
func (kvs *SimpleKVs) IfContains(key interface{}, action func(value interface{})) KVs {
|
||||
v, ok := kvs.kvs[key]
|
||||
if ok {
|
||||
action(v)
|
||||
}
|
||||
return kvs
|
||||
}
|
||||
|
||||
// NewKVs creates the *KVs instance
|
||||
func NewKVs(kvs ...KV) KVs {
|
||||
res := &SimpleKVs{
|
||||
kvs: make(map[interface{}]interface{}, len(kvs)),
|
||||
}
|
||||
for _, kv := range kvs {
|
||||
res.kvs[kv.GetKey()] = kv.GetValue()
|
||||
}
|
||||
return res
|
||||
}
|
38
pkg/infrastructure/utils/kv_test.go
Normal file
38
pkg/infrastructure/utils/kv_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2020 beego-dev
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKVs(t *testing.T) {
|
||||
key := "my-key"
|
||||
kvs := NewKVs(&SimpleKV{
|
||||
Key: key,
|
||||
Value: 12,
|
||||
})
|
||||
|
||||
assert.True(t, kvs.Contains(key))
|
||||
|
||||
v := kvs.GetValueOr(key, 13)
|
||||
assert.Equal(t, 12, v)
|
||||
|
||||
v = kvs.GetValueOr(`key-not-exists`, 8546)
|
||||
assert.Equal(t, 8546, v)
|
||||
|
||||
}
|
424
pkg/infrastructure/utils/mail.go
Normal file
424
pkg/infrastructure/utils/mail.go
Normal file
@ -0,0 +1,424 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
maxLineLength = 76
|
||||
|
||||
upperhex = "0123456789ABCDEF"
|
||||
)
|
||||
|
||||
// Email is the type used for email messages
|
||||
type Email struct {
|
||||
Auth smtp.Auth
|
||||
Identity string `json:"identity"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
From string `json:"from"`
|
||||
To []string
|
||||
Bcc []string
|
||||
Cc []string
|
||||
Subject string
|
||||
Text string // Plaintext message (optional)
|
||||
HTML string // Html message (optional)
|
||||
Headers textproto.MIMEHeader
|
||||
Attachments []*Attachment
|
||||
ReadReceipt []string
|
||||
}
|
||||
|
||||
// Attachment is a struct representing an email attachment.
|
||||
// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
|
||||
type Attachment struct {
|
||||
Filename string
|
||||
Header textproto.MIMEHeader
|
||||
Content []byte
|
||||
}
|
||||
|
||||
// NewEMail create new Email struct with config json.
|
||||
// config json is followed from Email struct fields.
|
||||
func NewEMail(config string) *Email {
|
||||
e := new(Email)
|
||||
e.Headers = textproto.MIMEHeader{}
|
||||
err := json.Unmarshal([]byte(config), e)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Bytes Make all send information to byte
|
||||
func (e *Email) Bytes() ([]byte, error) {
|
||||
buff := &bytes.Buffer{}
|
||||
w := multipart.NewWriter(buff)
|
||||
// Set the appropriate headers (overwriting any conflicts)
|
||||
// Leave out Bcc (only included in envelope headers)
|
||||
e.Headers.Set("To", strings.Join(e.To, ","))
|
||||
if e.Cc != nil {
|
||||
e.Headers.Set("Cc", strings.Join(e.Cc, ","))
|
||||
}
|
||||
e.Headers.Set("From", e.From)
|
||||
e.Headers.Set("Subject", e.Subject)
|
||||
if len(e.ReadReceipt) != 0 {
|
||||
e.Headers.Set("Disposition-Notification-To", strings.Join(e.ReadReceipt, ","))
|
||||
}
|
||||
e.Headers.Set("MIME-Version", "1.0")
|
||||
|
||||
// Write the envelope headers (including any custom headers)
|
||||
if err := headerToBytes(buff, e.Headers); err != nil {
|
||||
return nil, fmt.Errorf("Failed to render message headers: %s", err)
|
||||
}
|
||||
|
||||
e.Headers.Set("Content-Type", fmt.Sprintf("multipart/mixed;\r\n boundary=%s\r\n", w.Boundary()))
|
||||
fmt.Fprintf(buff, "%s:", "Content-Type")
|
||||
fmt.Fprintf(buff, " %s\r\n", fmt.Sprintf("multipart/mixed;\r\n boundary=%s\r\n", w.Boundary()))
|
||||
|
||||
// Start the multipart/mixed part
|
||||
fmt.Fprintf(buff, "--%s\r\n", w.Boundary())
|
||||
header := textproto.MIMEHeader{}
|
||||
// Check to see if there is a Text or HTML field
|
||||
if e.Text != "" || e.HTML != "" {
|
||||
subWriter := multipart.NewWriter(buff)
|
||||
// Create the multipart alternative part
|
||||
header.Set("Content-Type", fmt.Sprintf("multipart/alternative;\r\n boundary=%s\r\n", subWriter.Boundary()))
|
||||
// Write the header
|
||||
if err := headerToBytes(buff, header); err != nil {
|
||||
return nil, fmt.Errorf("Failed to render multipart message headers: %s", err)
|
||||
}
|
||||
// Create the body sections
|
||||
if e.Text != "" {
|
||||
header.Set("Content-Type", fmt.Sprintf("text/plain; charset=UTF-8"))
|
||||
header.Set("Content-Transfer-Encoding", "quoted-printable")
|
||||
if _, err := subWriter.CreatePart(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write the text
|
||||
if err := quotePrintEncode(buff, e.Text); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if e.HTML != "" {
|
||||
header.Set("Content-Type", fmt.Sprintf("text/html; charset=UTF-8"))
|
||||
header.Set("Content-Transfer-Encoding", "quoted-printable")
|
||||
if _, err := subWriter.CreatePart(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write the text
|
||||
if err := quotePrintEncode(buff, e.HTML); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := subWriter.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// Create attachment part, if necessary
|
||||
for _, a := range e.Attachments {
|
||||
ap, err := w.CreatePart(a.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Write the base64Wrapped content to the part
|
||||
base64Wrap(ap, a.Content)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
// AttachFile Add attach file to the send mail
|
||||
func (e *Email) AttachFile(args ...string) (a *Attachment, err error) {
|
||||
if len(args) < 1 || len(args) > 2 { // change && to ||
|
||||
err = errors.New("Must specify a file name and number of parameters can not exceed at least two")
|
||||
return
|
||||
}
|
||||
filename := args[0]
|
||||
id := ""
|
||||
if len(args) > 1 {
|
||||
id = args[1]
|
||||
}
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
ct := mime.TypeByExtension(filepath.Ext(filename))
|
||||
basename := path.Base(filename)
|
||||
return e.Attach(f, basename, ct, id)
|
||||
}
|
||||
|
||||
// Attach is used to attach content from an io.Reader to the email.
|
||||
// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type.
|
||||
func (e *Email) Attach(r io.Reader, filename string, args ...string) (a *Attachment, err error) {
|
||||
if len(args) < 1 || len(args) > 2 { // change && to ||
|
||||
err = errors.New("Must specify the file type and number of parameters can not exceed at least two")
|
||||
return
|
||||
}
|
||||
c := args[0] //Content-Type
|
||||
id := ""
|
||||
if len(args) > 1 {
|
||||
id = args[1] //Content-ID
|
||||
}
|
||||
var buffer bytes.Buffer
|
||||
if _, err = io.Copy(&buffer, r); err != nil {
|
||||
return
|
||||
}
|
||||
at := &Attachment{
|
||||
Filename: filename,
|
||||
Header: textproto.MIMEHeader{},
|
||||
Content: buffer.Bytes(),
|
||||
}
|
||||
// Get the Content-Type to be used in the MIMEHeader
|
||||
if c != "" {
|
||||
at.Header.Set("Content-Type", c)
|
||||
} else {
|
||||
// If the Content-Type is blank, set the Content-Type to "application/octet-stream"
|
||||
at.Header.Set("Content-Type", "application/octet-stream")
|
||||
}
|
||||
if id != "" {
|
||||
at.Header.Set("Content-Disposition", fmt.Sprintf("inline;\r\n filename=\"%s\"", filename))
|
||||
at.Header.Set("Content-ID", fmt.Sprintf("<%s>", id))
|
||||
} else {
|
||||
at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
|
||||
}
|
||||
at.Header.Set("Content-Transfer-Encoding", "base64")
|
||||
e.Attachments = append(e.Attachments, at)
|
||||
return at, nil
|
||||
}
|
||||
|
||||
// Send will send out the mail
|
||||
func (e *Email) Send() error {
|
||||
if e.Auth == nil {
|
||||
e.Auth = smtp.PlainAuth(e.Identity, e.Username, e.Password, e.Host)
|
||||
}
|
||||
// Merge the To, Cc, and Bcc fields
|
||||
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 len(to) == 0 {
|
||||
return errors.New("Must specify at least one To address")
|
||||
}
|
||||
|
||||
// Use the username if no From is provided
|
||||
if len(e.From) == 0 {
|
||||
e.From = e.Username
|
||||
}
|
||||
|
||||
from, err := mail.ParseAddress(e.From)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
return smtp.SendMail(e.Host+":"+strconv.Itoa(e.Port), e.Auth, from.Address, to, raw)
|
||||
}
|
||||
|
||||
// quotePrintEncode writes the quoted-printable text to the IO Writer (according to RFC 2045)
|
||||
func quotePrintEncode(w io.Writer, s string) error {
|
||||
var buf [3]byte
|
||||
mc := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
c := s[i]
|
||||
// We're assuming Unix style text formats as input (LF line break), and
|
||||
// quoted-printble uses CRLF line breaks. (Literal CRs will become
|
||||
// "=0D", but probably shouldn't be there to begin with!)
|
||||
if c == '\n' {
|
||||
io.WriteString(w, "\r\n")
|
||||
mc = 0
|
||||
continue
|
||||
}
|
||||
|
||||
var nextOut []byte
|
||||
if isPrintable(c) {
|
||||
nextOut = append(buf[:0], c)
|
||||
} else {
|
||||
nextOut = buf[:]
|
||||
qpEscape(nextOut, c)
|
||||
}
|
||||
|
||||
// Add a soft line break if the next (encoded) byte would push this line
|
||||
// to or past the limit.
|
||||
if mc+len(nextOut) >= maxLineLength {
|
||||
if _, err := io.WriteString(w, "=\r\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
mc = 0
|
||||
}
|
||||
|
||||
if _, err := w.Write(nextOut); err != nil {
|
||||
return err
|
||||
}
|
||||
mc += len(nextOut)
|
||||
}
|
||||
// No trailing end-of-line?? Soft line break, then. TODO: is this sane?
|
||||
if mc > 0 {
|
||||
io.WriteString(w, "=\r\n")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isPrintable returns true if the rune given is "printable" according to RFC 2045, false otherwise
|
||||
func isPrintable(c byte) bool {
|
||||
return (c >= '!' && c <= '<') || (c >= '>' && c <= '~') || (c == ' ' || c == '\n' || c == '\t')
|
||||
}
|
||||
|
||||
// qpEscape is a helper function for quotePrintEncode which escapes a
|
||||
// non-printable byte. Expects len(dest) == 3.
|
||||
func qpEscape(dest []byte, c byte) {
|
||||
const nums = "0123456789ABCDEF"
|
||||
dest[0] = '='
|
||||
dest[1] = nums[(c&0xf0)>>4]
|
||||
dest[2] = nums[(c & 0xf)]
|
||||
}
|
||||
|
||||
// headerToBytes enumerates the key and values in the header, and writes the results to the IO Writer
|
||||
func headerToBytes(w io.Writer, t textproto.MIMEHeader) error {
|
||||
for k, v := range t {
|
||||
// Write the header key
|
||||
_, err := fmt.Fprintf(w, "%s:", k)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Write each value in the header
|
||||
for _, c := range v {
|
||||
_, err := fmt.Fprintf(w, " %s\r\n", c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// base64Wrap encodes the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
|
||||
// The output is then written to the specified io.Writer
|
||||
func base64Wrap(w io.Writer, b []byte) {
|
||||
// 57 raw bytes per 76-byte base64 line.
|
||||
const maxRaw = 57
|
||||
// Buffer for each line, including trailing CRLF.
|
||||
var buffer [maxLineLength + len("\r\n")]byte
|
||||
copy(buffer[maxLineLength:], "\r\n")
|
||||
// Process raw chunks until there's no longer enough to fill a line.
|
||||
for len(b) >= maxRaw {
|
||||
base64.StdEncoding.Encode(buffer[:], b[:maxRaw])
|
||||
w.Write(buffer[:])
|
||||
b = b[maxRaw:]
|
||||
}
|
||||
// Handle the last chunk of bytes.
|
||||
if len(b) > 0 {
|
||||
out := buffer[:base64.StdEncoding.EncodedLen(len(b))]
|
||||
base64.StdEncoding.Encode(out, b)
|
||||
out = append(out, "\r\n"...)
|
||||
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)
|
||||
}
|
41
pkg/infrastructure/utils/mail_test.go
Normal file
41
pkg/infrastructure/utils/mail_test.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestMail(t *testing.T) {
|
||||
config := `{"username":"astaxie@gmail.com","password":"astaxie","host":"smtp.gmail.com","port":587}`
|
||||
mail := NewEMail(config)
|
||||
if mail.Username != "astaxie@gmail.com" {
|
||||
t.Fatal("email parse get username error")
|
||||
}
|
||||
if mail.Password != "astaxie" {
|
||||
t.Fatal("email parse get password error")
|
||||
}
|
||||
if mail.Host != "smtp.gmail.com" {
|
||||
t.Fatal("email parse get host error")
|
||||
}
|
||||
if mail.Port != 587 {
|
||||
t.Fatal("email parse get port error")
|
||||
}
|
||||
mail.To = []string{"xiemengjun@gmail.com"}
|
||||
mail.From = "astaxie@gmail.com"
|
||||
mail.Subject = "hi, just from beego!"
|
||||
mail.Text = "Text Body is, of course, supported!"
|
||||
mail.HTML = "<h1>Fancy Html is supported, too!</h1>"
|
||||
mail.AttachFile("/Users/astaxie/github/beego/beego.go")
|
||||
mail.Send()
|
||||
}
|
58
pkg/infrastructure/utils/pagination/doc.go
Normal file
58
pkg/infrastructure/utils/pagination/doc.go
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Package pagination provides utilities to setup a paginator within the
|
||||
context of a http request.
|
||||
|
||||
Usage
|
||||
|
||||
In your beego.Controller:
|
||||
|
||||
package controllers
|
||||
|
||||
import "github.com/astaxie/beego/pkg/infrastructure/utils/pagination"
|
||||
|
||||
type PostsController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
func (this *PostsController) ListAllPosts() {
|
||||
// sets this.Data["paginator"] with the current offset (from the url query param)
|
||||
postsPerPage := 20
|
||||
paginator := pagination.SetPaginator(this.Ctx, postsPerPage, CountPosts())
|
||||
|
||||
// fetch the next 20 posts
|
||||
this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage)
|
||||
}
|
||||
|
||||
|
||||
In your view templates:
|
||||
|
||||
{{if .paginator.HasPages}}
|
||||
<ul class="pagination pagination">
|
||||
{{if .paginator.HasPrev}}
|
||||
<li><a href="{{.paginator.PageLinkFirst}}">{{ i18n .Lang "paginator.first_page"}}</a></li>
|
||||
<li><a href="{{.paginator.PageLinkPrev}}">«</a></li>
|
||||
{{else}}
|
||||
<li class="disabled"><a>{{ i18n .Lang "paginator.first_page"}}</a></li>
|
||||
<li class="disabled"><a>«</a></li>
|
||||
{{end}}
|
||||
{{range $index, $page := .paginator.Pages}}
|
||||
<li{{if $.paginator.IsActive .}} class="active"{{end}}>
|
||||
<a href="{{$.paginator.PageLink $page}}">{{$page}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
{{if .paginator.HasNext}}
|
||||
<li><a href="{{.paginator.PageLinkNext}}">»</a></li>
|
||||
<li><a href="{{.paginator.PageLinkLast}}">{{ i18n .Lang "paginator.last_page"}}</a></li>
|
||||
{{else}}
|
||||
<li class="disabled"><a>»</a></li>
|
||||
<li class="disabled"><a>{{ i18n .Lang "paginator.last_page"}}</a></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
See also
|
||||
|
||||
http://beego.me/docs/mvc/view/page.md
|
||||
|
||||
*/
|
||||
package pagination
|
189
pkg/infrastructure/utils/pagination/paginator.go
Normal file
189
pkg/infrastructure/utils/pagination/paginator.go
Normal file
@ -0,0 +1,189 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pagination
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Paginator within the state of a http request.
|
||||
type Paginator struct {
|
||||
Request *http.Request
|
||||
PerPageNums int
|
||||
MaxPages int
|
||||
|
||||
nums int64
|
||||
pageRange []int
|
||||
pageNums int
|
||||
page int
|
||||
}
|
||||
|
||||
// PageNums Returns the total number of pages.
|
||||
func (p *Paginator) PageNums() int {
|
||||
if p.pageNums != 0 {
|
||||
return p.pageNums
|
||||
}
|
||||
pageNums := math.Ceil(float64(p.nums) / float64(p.PerPageNums))
|
||||
if p.MaxPages > 0 {
|
||||
pageNums = math.Min(pageNums, float64(p.MaxPages))
|
||||
}
|
||||
p.pageNums = int(pageNums)
|
||||
return p.pageNums
|
||||
}
|
||||
|
||||
// Nums Returns the total number of items (e.g. from doing SQL count).
|
||||
func (p *Paginator) Nums() int64 {
|
||||
return p.nums
|
||||
}
|
||||
|
||||
// SetNums Sets the total number of items.
|
||||
func (p *Paginator) SetNums(nums interface{}) {
|
||||
p.nums, _ = toInt64(nums)
|
||||
}
|
||||
|
||||
// Page Returns the current page.
|
||||
func (p *Paginator) Page() int {
|
||||
if p.page != 0 {
|
||||
return p.page
|
||||
}
|
||||
if p.Request.Form == nil {
|
||||
p.Request.ParseForm()
|
||||
}
|
||||
p.page, _ = strconv.Atoi(p.Request.Form.Get("p"))
|
||||
if p.page > p.PageNums() {
|
||||
p.page = p.PageNums()
|
||||
}
|
||||
if p.page <= 0 {
|
||||
p.page = 1
|
||||
}
|
||||
return p.page
|
||||
}
|
||||
|
||||
// Pages Returns a list of all pages.
|
||||
//
|
||||
// Usage (in a view template):
|
||||
//
|
||||
// {{range $index, $page := .paginator.Pages}}
|
||||
// <li{{if $.paginator.IsActive .}} class="active"{{end}}>
|
||||
// <a href="{{$.paginator.PageLink $page}}">{{$page}}</a>
|
||||
// </li>
|
||||
// {{end}}
|
||||
func (p *Paginator) Pages() []int {
|
||||
if p.pageRange == nil && p.nums > 0 {
|
||||
var pages []int
|
||||
pageNums := p.PageNums()
|
||||
page := p.Page()
|
||||
switch {
|
||||
case page >= pageNums-4 && pageNums > 9:
|
||||
start := pageNums - 9 + 1
|
||||
pages = make([]int, 9)
|
||||
for i := range pages {
|
||||
pages[i] = start + i
|
||||
}
|
||||
case page >= 5 && pageNums > 9:
|
||||
start := page - 5 + 1
|
||||
pages = make([]int, int(math.Min(9, float64(page+4+1))))
|
||||
for i := range pages {
|
||||
pages[i] = start + i
|
||||
}
|
||||
default:
|
||||
pages = make([]int, int(math.Min(9, float64(pageNums))))
|
||||
for i := range pages {
|
||||
pages[i] = i + 1
|
||||
}
|
||||
}
|
||||
p.pageRange = pages
|
||||
}
|
||||
return p.pageRange
|
||||
}
|
||||
|
||||
// PageLink Returns URL for a given page index.
|
||||
func (p *Paginator) PageLink(page int) string {
|
||||
link, _ := url.ParseRequestURI(p.Request.URL.String())
|
||||
values := link.Query()
|
||||
if page == 1 {
|
||||
values.Del("p")
|
||||
} else {
|
||||
values.Set("p", strconv.Itoa(page))
|
||||
}
|
||||
link.RawQuery = values.Encode()
|
||||
return link.String()
|
||||
}
|
||||
|
||||
// PageLinkPrev Returns URL to the previous page.
|
||||
func (p *Paginator) PageLinkPrev() (link string) {
|
||||
if p.HasPrev() {
|
||||
link = p.PageLink(p.Page() - 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PageLinkNext Returns URL to the next page.
|
||||
func (p *Paginator) PageLinkNext() (link string) {
|
||||
if p.HasNext() {
|
||||
link = p.PageLink(p.Page() + 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PageLinkFirst Returns URL to the first page.
|
||||
func (p *Paginator) PageLinkFirst() (link string) {
|
||||
return p.PageLink(1)
|
||||
}
|
||||
|
||||
// PageLinkLast Returns URL to the last page.
|
||||
func (p *Paginator) PageLinkLast() (link string) {
|
||||
return p.PageLink(p.PageNums())
|
||||
}
|
||||
|
||||
// HasPrev Returns true if the current page has a predecessor.
|
||||
func (p *Paginator) HasPrev() bool {
|
||||
return p.Page() > 1
|
||||
}
|
||||
|
||||
// HasNext Returns true if the current page has a successor.
|
||||
func (p *Paginator) HasNext() bool {
|
||||
return p.Page() < p.PageNums()
|
||||
}
|
||||
|
||||
// IsActive Returns true if the given page index points to the current page.
|
||||
func (p *Paginator) IsActive(page int) bool {
|
||||
return p.Page() == page
|
||||
}
|
||||
|
||||
// Offset Returns the current offset.
|
||||
func (p *Paginator) Offset() int {
|
||||
return (p.Page() - 1) * p.PerPageNums
|
||||
}
|
||||
|
||||
// HasPages Returns true if there is more than one page.
|
||||
func (p *Paginator) HasPages() bool {
|
||||
return p.PageNums() > 1
|
||||
}
|
||||
|
||||
// NewPaginator Instantiates a paginator struct for the current http request.
|
||||
func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator {
|
||||
p := Paginator{}
|
||||
p.Request = req
|
||||
if per <= 0 {
|
||||
per = 10
|
||||
}
|
||||
p.PerPageNums = per
|
||||
p.SetNums(nums)
|
||||
return &p
|
||||
}
|
34
pkg/infrastructure/utils/pagination/utils.go
Normal file
34
pkg/infrastructure/utils/pagination/utils.go
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pagination
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ToInt64 convert any numeric value to int64
|
||||
func toInt64(value interface{}) (d int64, err error) {
|
||||
val := reflect.ValueOf(value)
|
||||
switch value.(type) {
|
||||
case int, int8, int16, int32, int64:
|
||||
d = val.Int()
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
d = int64(val.Uint())
|
||||
default:
|
||||
err = fmt.Errorf("ToInt64 need numeric not `%T`", value)
|
||||
}
|
||||
return
|
||||
}
|
44
pkg/infrastructure/utils/rand.go
Normal file
44
pkg/infrastructure/utils/rand.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
r "math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
var alphaNum = []byte(`0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz`)
|
||||
|
||||
// RandomCreateBytes generate random []byte by specify chars.
|
||||
func RandomCreateBytes(n int, alphabets ...byte) []byte {
|
||||
if len(alphabets) == 0 {
|
||||
alphabets = alphaNum
|
||||
}
|
||||
var bytes = make([]byte, n)
|
||||
var randBy bool
|
||||
if num, err := rand.Read(bytes); num != n || err != nil {
|
||||
r.Seed(time.Now().UnixNano())
|
||||
randBy = true
|
||||
}
|
||||
for i, b := range bytes {
|
||||
if randBy {
|
||||
bytes[i] = alphabets[r.Intn(len(alphabets))]
|
||||
} else {
|
||||
bytes[i] = alphabets[b%byte(len(alphabets))]
|
||||
}
|
||||
}
|
||||
return bytes
|
||||
}
|
33
pkg/infrastructure/utils/rand_test.go
Normal file
33
pkg/infrastructure/utils/rand_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2016 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRand_01(t *testing.T) {
|
||||
bs0 := RandomCreateBytes(16)
|
||||
bs1 := RandomCreateBytes(16)
|
||||
|
||||
t.Log(string(bs0), string(bs1))
|
||||
if string(bs0) == string(bs1) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
bs0 = RandomCreateBytes(4, []byte(`a`)...)
|
||||
|
||||
if string(bs0) != "aaaa" {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
91
pkg/infrastructure/utils/safemap.go
Normal file
91
pkg/infrastructure/utils/safemap.go
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// BeeMap is a map with lock
|
||||
type BeeMap struct {
|
||||
lock *sync.RWMutex
|
||||
bm map[interface{}]interface{}
|
||||
}
|
||||
|
||||
// NewBeeMap return new safemap
|
||||
func NewBeeMap() *BeeMap {
|
||||
return &BeeMap{
|
||||
lock: new(sync.RWMutex),
|
||||
bm: make(map[interface{}]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Get from maps return the k's value
|
||||
func (m *BeeMap) Get(k interface{}) interface{} {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
if val, ok := m.bm[k]; ok {
|
||||
return val
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set Maps the given key and value. Returns false
|
||||
// if the key is already in the map and changes nothing.
|
||||
func (m *BeeMap) Set(k interface{}, v interface{}) bool {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
if val, ok := m.bm[k]; !ok {
|
||||
m.bm[k] = v
|
||||
} else if val != v {
|
||||
m.bm[k] = v
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Check Returns true if k is exist in the map.
|
||||
func (m *BeeMap) Check(k interface{}) bool {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
_, ok := m.bm[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Delete the given key and value.
|
||||
func (m *BeeMap) Delete(k interface{}) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
delete(m.bm, k)
|
||||
}
|
||||
|
||||
// Items returns all items in safemap.
|
||||
func (m *BeeMap) Items() map[interface{}]interface{} {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
r := make(map[interface{}]interface{})
|
||||
for k, v := range m.bm {
|
||||
r[k] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Count returns the number of items within the map.
|
||||
func (m *BeeMap) Count() int {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
return len(m.bm)
|
||||
}
|
89
pkg/infrastructure/utils/safemap_test.go
Normal file
89
pkg/infrastructure/utils/safemap_test.go
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
var safeMap *BeeMap
|
||||
|
||||
func TestNewBeeMap(t *testing.T) {
|
||||
safeMap = NewBeeMap()
|
||||
if safeMap == nil {
|
||||
t.Fatal("expected to return non-nil BeeMap", "got", safeMap)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
safeMap = NewBeeMap()
|
||||
if ok := safeMap.Set("astaxie", 1); !ok {
|
||||
t.Error("expected", true, "got", false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReSet(t *testing.T) {
|
||||
safeMap := NewBeeMap()
|
||||
if ok := safeMap.Set("astaxie", 1); !ok {
|
||||
t.Error("expected", true, "got", false)
|
||||
}
|
||||
// set diff value
|
||||
if ok := safeMap.Set("astaxie", -1); !ok {
|
||||
t.Error("expected", true, "got", false)
|
||||
}
|
||||
|
||||
// set same value
|
||||
if ok := safeMap.Set("astaxie", -1); ok {
|
||||
t.Error("expected", false, "got", true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
if exists := safeMap.Check("astaxie"); !exists {
|
||||
t.Error("expected", true, "got", false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
if val := safeMap.Get("astaxie"); val.(int) != 1 {
|
||||
t.Error("expected value", 1, "got", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
safeMap.Delete("astaxie")
|
||||
if exists := safeMap.Check("astaxie"); exists {
|
||||
t.Error("expected element to be deleted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestItems(t *testing.T) {
|
||||
safeMap := NewBeeMap()
|
||||
safeMap.Set("astaxie", "hello")
|
||||
for k, v := range safeMap.Items() {
|
||||
key := k.(string)
|
||||
value := v.(string)
|
||||
if key != "astaxie" {
|
||||
t.Error("expected the key should be astaxie")
|
||||
}
|
||||
if value != "hello" {
|
||||
t.Error("expected the value should be hello")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCount(t *testing.T) {
|
||||
if count := safeMap.Count(); count != 0 {
|
||||
t.Error("expected count to be", 0, "got", count)
|
||||
}
|
||||
}
|
170
pkg/infrastructure/utils/slice.go
Normal file
170
pkg/infrastructure/utils/slice.go
Normal file
@ -0,0 +1,170 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
type reducetype func(interface{}) interface{}
|
||||
type filtertype func(interface{}) bool
|
||||
|
||||
// InSlice checks given string in string slice or not.
|
||||
func InSlice(v string, sl []string) bool {
|
||||
for _, vv := range sl {
|
||||
if vv == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InSliceIface checks given interface in interface slice.
|
||||
func InSliceIface(v interface{}, sl []interface{}) bool {
|
||||
for _, vv := range sl {
|
||||
if vv == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SliceRandList generate an int slice from min to max.
|
||||
func SliceRandList(min, max int) []int {
|
||||
if max < min {
|
||||
min, max = max, min
|
||||
}
|
||||
length := max - min + 1
|
||||
t0 := time.Now()
|
||||
rand.Seed(int64(t0.Nanosecond()))
|
||||
list := rand.Perm(length)
|
||||
for index := range list {
|
||||
list[index] += min
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// SliceMerge merges interface slices to one slice.
|
||||
func SliceMerge(slice1, slice2 []interface{}) (c []interface{}) {
|
||||
c = append(slice1, slice2...)
|
||||
return
|
||||
}
|
||||
|
||||
// SliceReduce generates a new slice after parsing every value by reduce function
|
||||
func SliceReduce(slice []interface{}, a reducetype) (dslice []interface{}) {
|
||||
for _, v := range slice {
|
||||
dslice = append(dslice, a(v))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceRand returns random one from slice.
|
||||
func SliceRand(a []interface{}) (b interface{}) {
|
||||
randnum := rand.Intn(len(a))
|
||||
b = a[randnum]
|
||||
return
|
||||
}
|
||||
|
||||
// SliceSum sums all values in int64 slice.
|
||||
func SliceSum(intslice []int64) (sum int64) {
|
||||
for _, v := range intslice {
|
||||
sum += v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceFilter generates a new slice after filter function.
|
||||
func SliceFilter(slice []interface{}, a filtertype) (ftslice []interface{}) {
|
||||
for _, v := range slice {
|
||||
if a(v) {
|
||||
ftslice = append(ftslice, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceDiff returns diff slice of slice1 - slice2.
|
||||
func SliceDiff(slice1, slice2 []interface{}) (diffslice []interface{}) {
|
||||
for _, v := range slice1 {
|
||||
if !InSliceIface(v, slice2) {
|
||||
diffslice = append(diffslice, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceIntersect returns slice that are present in all the slice1 and slice2.
|
||||
func SliceIntersect(slice1, slice2 []interface{}) (diffslice []interface{}) {
|
||||
for _, v := range slice1 {
|
||||
if InSliceIface(v, slice2) {
|
||||
diffslice = append(diffslice, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceChunk separates one slice to some sized slice.
|
||||
func SliceChunk(slice []interface{}, size int) (chunkslice [][]interface{}) {
|
||||
if size >= len(slice) {
|
||||
chunkslice = append(chunkslice, slice)
|
||||
return
|
||||
}
|
||||
end := size
|
||||
for i := 0; i <= (len(slice) - size); i += size {
|
||||
chunkslice = append(chunkslice, slice[i:end])
|
||||
end += size
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceRange generates a new slice from begin to end with step duration of int64 number.
|
||||
func SliceRange(start, end, step int64) (intslice []int64) {
|
||||
for i := start; i <= end; i += step {
|
||||
intslice = append(intslice, i)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SlicePad prepends size number of val into slice.
|
||||
func SlicePad(slice []interface{}, size int, val interface{}) []interface{} {
|
||||
if size <= len(slice) {
|
||||
return slice
|
||||
}
|
||||
for i := 0; i < (size - len(slice)); i++ {
|
||||
slice = append(slice, val)
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
// SliceUnique cleans repeated values in slice.
|
||||
func SliceUnique(slice []interface{}) (uniqueslice []interface{}) {
|
||||
for _, v := range slice {
|
||||
if !InSliceIface(v, uniqueslice) {
|
||||
uniqueslice = append(uniqueslice, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SliceShuffle shuffles a slice.
|
||||
func SliceShuffle(slice []interface{}) []interface{} {
|
||||
for i := 0; i < len(slice); i++ {
|
||||
a := rand.Intn(len(slice))
|
||||
b := rand.Intn(len(slice))
|
||||
slice[a], slice[b] = slice[b], slice[a]
|
||||
}
|
||||
return slice
|
||||
}
|
29
pkg/infrastructure/utils/slice_test.go
Normal file
29
pkg/infrastructure/utils/slice_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2014 beego Author. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInSlice(t *testing.T) {
|
||||
sl := []string{"A", "b"}
|
||||
if !InSlice("A", sl) {
|
||||
t.Error("should be true")
|
||||
}
|
||||
if InSlice("B", sl) {
|
||||
t.Error("should be false")
|
||||
}
|
||||
}
|
7
pkg/infrastructure/utils/testdata/grepe.test
vendored
Normal file
7
pkg/infrastructure/utils/testdata/grepe.test
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
# empty lines
|
||||
|
||||
|
||||
|
||||
hello
|
||||
# comment
|
||||
world
|
48
pkg/infrastructure/utils/time.go
Normal file
48
pkg/infrastructure/utils/time.go
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2020
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// short string format
|
||||
func ToShortTimeFormat(d time.Duration) string {
|
||||
|
||||
u := uint64(d)
|
||||
if u < uint64(time.Second) {
|
||||
switch {
|
||||
case u == 0:
|
||||
return "0"
|
||||
case u < uint64(time.Microsecond):
|
||||
return fmt.Sprintf("%.2fns", float64(u))
|
||||
case u < uint64(time.Millisecond):
|
||||
return fmt.Sprintf("%.2fus", float64(u)/1000)
|
||||
default:
|
||||
return fmt.Sprintf("%.2fms", float64(u)/1000/1000)
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case u < uint64(time.Minute):
|
||||
return fmt.Sprintf("%.2fs", float64(u)/1000/1000/1000)
|
||||
case u < uint64(time.Hour):
|
||||
return fmt.Sprintf("%.2fm", float64(u)/1000/1000/1000/60)
|
||||
default:
|
||||
return fmt.Sprintf("%.2fh", float64(u)/1000/1000/1000/60/60)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
89
pkg/infrastructure/utils/utils.go
Normal file
89
pkg/infrastructure/utils/utils.go
Normal file
@ -0,0 +1,89 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetGOPATHs returns all paths in GOPATH variable.
|
||||
func GetGOPATHs() []string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
if gopath == "" && compareGoVersion(runtime.Version(), "go1.8") >= 0 {
|
||||
gopath = defaultGOPATH()
|
||||
}
|
||||
return filepath.SplitList(gopath)
|
||||
}
|
||||
|
||||
func compareGoVersion(a, b string) int {
|
||||
reg := regexp.MustCompile("^\\d*")
|
||||
|
||||
a = strings.TrimPrefix(a, "go")
|
||||
b = strings.TrimPrefix(b, "go")
|
||||
|
||||
versionsA := strings.Split(a, ".")
|
||||
versionsB := strings.Split(b, ".")
|
||||
|
||||
for i := 0; i < len(versionsA) && i < len(versionsB); i++ {
|
||||
versionA := versionsA[i]
|
||||
versionB := versionsB[i]
|
||||
|
||||
vA, err := strconv.Atoi(versionA)
|
||||
if err != nil {
|
||||
str := reg.FindString(versionA)
|
||||
if str != "" {
|
||||
vA, _ = strconv.Atoi(str)
|
||||
} else {
|
||||
vA = -1
|
||||
}
|
||||
}
|
||||
|
||||
vB, err := strconv.Atoi(versionB)
|
||||
if err != nil {
|
||||
str := reg.FindString(versionB)
|
||||
if str != "" {
|
||||
vB, _ = strconv.Atoi(str)
|
||||
} else {
|
||||
vB = -1
|
||||
}
|
||||
}
|
||||
|
||||
if vA > vB {
|
||||
// vA = 12, vB = 8
|
||||
return 1
|
||||
} else if vA < vB {
|
||||
// vA = 6, vB = 8
|
||||
return -1
|
||||
} else if vA == -1 {
|
||||
// vA = rc1, vB = rc3
|
||||
return strings.Compare(versionA, versionB)
|
||||
}
|
||||
|
||||
// vA = vB = 8
|
||||
continue
|
||||
}
|
||||
|
||||
if len(versionsA) > len(versionsB) {
|
||||
return 1
|
||||
} else if len(versionsA) == len(versionsB) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func defaultGOPATH() string {
|
||||
env := "HOME"
|
||||
if runtime.GOOS == "windows" {
|
||||
env = "USERPROFILE"
|
||||
} else if runtime.GOOS == "plan9" {
|
||||
env = "home"
|
||||
}
|
||||
if home := os.Getenv(env); home != "" {
|
||||
return filepath.Join(home, "go")
|
||||
}
|
||||
return ""
|
||||
}
|
36
pkg/infrastructure/utils/utils_test.go
Normal file
36
pkg/infrastructure/utils/utils_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareGoVersion(t *testing.T) {
|
||||
targetVersion := "go1.8"
|
||||
if compareGoVersion("go1.12.4", targetVersion) != 1 {
|
||||
t.Error("should be 1")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.8.7", targetVersion) != 1 {
|
||||
t.Error("should be 1")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.8", targetVersion) != 0 {
|
||||
t.Error("should be 0")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.7.6", targetVersion) != -1 {
|
||||
t.Error("should be -1")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.12.1rc1", targetVersion) != 1 {
|
||||
t.Error("should be 1")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.8rc1", targetVersion) != 0 {
|
||||
t.Error("should be 0")
|
||||
}
|
||||
|
||||
if compareGoVersion("go1.7rc1", targetVersion) != -1 {
|
||||
t.Error("should be -1")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user