mirror of
https://gitee.com/johng/gf
synced 2026-06-07 18:26:02 +08:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e6c2e790d | |||
| 34c761e9db | |||
| 87e3813636 | |||
| 361ff0315c | |||
| 2bb227d058 | |||
| 99dc69e839 | |||
| c9537af062 | |||
| 7ae03729f3 | |||
| ea7e2ec5ec | |||
| 237c58f2b0 | |||
| efa23e4a1d | |||
| c109cee7ef | |||
| e111d39c54 | |||
| 66306464e1 | |||
| dd34ac1722 | |||
| 34cb222b33 | |||
| d39ef156de | |||
| f464dc7fb8 | |||
| d29b27a5df | |||
| aadc6aa504 | |||
| e8c3dfa13e | |||
| 9eea93cc6e | |||
| 308cb55b6b | |||
| 75ada78f8f | |||
| ecd86e3a12 | |||
| c1aa5eb717 | |||
| d2fed1198b | |||
| a9f9261dbd | |||
| 161e0d6e97 | |||
| 3efe511f42 | |||
| 5d04c2e50a | |||
| 7b26b7ea4c | |||
| 9d1063c6b2 | |||
| 5ff7632d32 | |||
| e6fb41504c | |||
| a800f731dd | |||
| f1a9fbb74e | |||
| cf81a73526 | |||
| 65036fffe8 | |||
| a69934a7e3 | |||
| 9400457bf2 | |||
| 5060329721 | |||
| 07ab1d60e8 | |||
| 7377a82e19 | |||
| f82e3ac808 | |||
| 1a6cd1de04 | |||
| 90e6f685b7 | |||
| 0fc825dac1 | |||
| dbb27efe3e | |||
| 2d3d2e783e | |||
| 6ae1defa35 | |||
| 16a4a5ba46 | |||
| 8300885ab6 | |||
| b489eed4ef | |||
| 053a3c1a53 | |||
| 468c315087 | |||
| b3d5fc149e | |||
| 43886511b9 | |||
| fd63a2209b | |||
| a26ec37f59 | |||
| 779ad93bcb | |||
| 1ec0219473 | |||
| 388d5954cb | |||
| 20977558cc |
@ -76,6 +76,7 @@ func main() {
|
||||
- [pibigstar](https://github.com/pibigstar)
|
||||
- [qq1054000800](https://gitee.com/qq1054000800)
|
||||
- [qq976739120](https://github.com/qq976739120)
|
||||
- [touzijiao](https://github.com/touzijiao)
|
||||
- [wenzi1](https://gitee.com/wenzi1)
|
||||
- [wxkj001](https://github.com/wxkj001)
|
||||
- [ymrjqyy](https://gitee.com/ymrjqyy)
|
||||
|
||||
@ -94,6 +94,7 @@ func main() {
|
||||
- [pibigstar](https://github.com/pibigstar)
|
||||
- [qq1054000800](https://gitee.com/qq1054000800)
|
||||
- [qq976739120](https://github.com/qq976739120)
|
||||
- [touzijiao](https://github.com/touzijiao)
|
||||
- [wenzi1](https://gitee.com/wenzi1)
|
||||
- [wxkj001](https://github.com/wxkj001)
|
||||
- [ymrjqyy](https://gitee.com/ymrjqyy)
|
||||
|
||||
@ -8,7 +8,8 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
"math"
|
||||
@ -16,24 +17,20 @@ import (
|
||||
)
|
||||
|
||||
type IntArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
array []int // 底层数组
|
||||
mu *rwmutex.RWMutex
|
||||
array []int
|
||||
}
|
||||
|
||||
// Create an empty array.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个空的数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// NewIntArray creates and returns an empty array.
|
||||
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntArray(unsafe...bool) *IntArray {
|
||||
return NewIntArraySize(0, 0, unsafe...)
|
||||
}
|
||||
|
||||
// Create an array with given size and cap.
|
||||
// NewIntArraySize create and returns an array with given size and cap.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个指定大小的数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewIntArraySize(size int, cap int, unsafe...bool) *IntArray {
|
||||
return &IntArray{
|
||||
mu : rwmutex.New(unsafe...),
|
||||
@ -41,11 +38,9 @@ func NewIntArraySize(size int, cap int, unsafe...bool) *IntArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array with given slice <array>.
|
||||
// NewIntArrayFrom creates and returns an array with given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice变量创建数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewIntArrayFrom(array []int, unsafe...bool) *IntArray {
|
||||
return &IntArray{
|
||||
mu : rwmutex.New(unsafe...),
|
||||
@ -53,11 +48,9 @@ func NewIntArrayFrom(array []int, unsafe...bool) *IntArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array from a copy of given slice <array>.
|
||||
// NewIntArrayFromCopy creates and returns an array from a copy of given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice拷贝创建数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewIntArrayFromCopy(array []int, unsafe...bool) *IntArray {
|
||||
newArray := make([]int, len(array))
|
||||
copy(newArray, array)
|
||||
@ -67,9 +60,8 @@ func NewIntArrayFromCopy(array []int, unsafe...bool) *IntArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Get value by index.
|
||||
//
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *IntArray) Get(index int) int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -77,9 +69,7 @@ func (a *IntArray) Get(index int) int {
|
||||
return value
|
||||
}
|
||||
|
||||
// Set value by index.
|
||||
//
|
||||
// 设置指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Set sets value to specified index.
|
||||
func (a *IntArray) Set(index int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -87,9 +77,7 @@ func (a *IntArray) Set(index int, value int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Set the underlying slice array with the given <array> param.
|
||||
//
|
||||
// 设置底层数组变量.
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *IntArray) SetArray(array []int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -97,9 +85,7 @@ func (a *IntArray) SetArray(array []int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Replace the array items by given <array> from the beginning of array.
|
||||
//
|
||||
// 使用指定数组替换到对应的索引元素值.
|
||||
// Replace replaces the array items by given <array> from the beginning of array.
|
||||
func (a *IntArray) Replace(array []int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -113,9 +99,7 @@ func (a *IntArray) Replace(array []int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Calculate the sum of values in an array.
|
||||
//
|
||||
// 对数组中的元素项求和。
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *IntArray) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -125,11 +109,9 @@ func (a *IntArray) Sum() (sum int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Sort the array in increasing order.
|
||||
// Sort sorts the array in increasing order.
|
||||
// The param <reverse> controls whether sort
|
||||
// in increasing order(default) or decreasing order
|
||||
//
|
||||
// 将数组排序(默认从低到高).
|
||||
func (a *IntArray) Sort(reverse...bool) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -146,9 +128,7 @@ func (a *IntArray) Sort(reverse...bool) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Sort the array by custom function <less>.
|
||||
//
|
||||
// 使用自定义的排序函数将数组重新排序.
|
||||
// SortFunc sorts the array by custom function <less>.
|
||||
func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -158,9 +138,7 @@ func (a *IntArray) SortFunc(less func(v1, v2 int) bool) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Insert the <value> to the front of <index>.
|
||||
//
|
||||
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界。
|
||||
// InsertBefore inserts the <value> to the front of <index>.
|
||||
func (a *IntArray) InsertBefore(index int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -170,9 +148,7 @@ func (a *IntArray) InsertBefore(index int, value int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Insert the <value> to the back of <index>.
|
||||
//
|
||||
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界。
|
||||
// InsertAfter inserts the <value> to the back of <index>.
|
||||
func (a *IntArray) InsertAfter(index int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -182,13 +158,11 @@ func (a *IntArray) InsertAfter(index int, value int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Remove an item by index.
|
||||
//
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Remove removes an item by index.
|
||||
func (a *IntArray) Remove(index int) int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
// Determine array boundaries when deleting to improve deletion efficiency.
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
@ -198,15 +172,15 @@ func (a *IntArray) Remove(index int) int {
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// Push new items to the beginning of array.
|
||||
//
|
||||
// 将数据项添加到数组的最左端(索引为0)。
|
||||
// PushLeft pushes one or multiple items to the beginning of array.
|
||||
func (a *IntArray) PushLeft(value...int) *IntArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(value, a.array...)
|
||||
@ -214,9 +188,8 @@ func (a *IntArray) PushLeft(value...int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Push new items to the end of array.
|
||||
//
|
||||
// 将数据项添加到数组的最右端(索引为length - 1), 等于: Append。
|
||||
// PushRight pushes one or multiple items to the end of array.
|
||||
// It equals to Append.
|
||||
func (a *IntArray) PushRight(value...int) *IntArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
@ -224,9 +197,7 @@ func (a *IntArray) PushRight(value...int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Pop an item from the beginning of array.
|
||||
//
|
||||
// 将最左端(索引为0)的数据项移出数组,并返回该数据项。
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *IntArray) PopLeft() int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -235,9 +206,7 @@ func (a *IntArray) PopLeft() int {
|
||||
return value
|
||||
}
|
||||
|
||||
// Pop an item from the end of array.
|
||||
//
|
||||
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项。
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *IntArray) PopRight() int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -247,16 +216,12 @@ func (a *IntArray) PopRight() int {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRand picks an random item out of array.
|
||||
//
|
||||
// 随机将一个数据项移出数组,并返回该数据项。
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *IntArray) PopRand() int {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands picks <size> items out of array.
|
||||
//
|
||||
// 随机将size个数据项移出数组,并返回该数据项。
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *IntArray) PopRands(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -272,9 +237,7 @@ func (a *IntArray) PopRands(size int) []int {
|
||||
return array
|
||||
}
|
||||
|
||||
// Pop <size> items from the beginning of array.
|
||||
//
|
||||
// 将最左端(首部)的size个数据项移出数组,并返回该数据项。
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *IntArray) PopLefts(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -287,9 +250,7 @@ func (a *IntArray) PopLefts(size int) []int {
|
||||
return value
|
||||
}
|
||||
|
||||
// Pop <size> items from the end of array.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *IntArray) PopRights(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -302,11 +263,9 @@ func (a *IntArray) PopRights(size int) []int {
|
||||
return value
|
||||
}
|
||||
|
||||
// Get items by range, returns array[start:end].
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
func (a *IntArray) Range(start, end int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -333,8 +292,6 @@ func (a *IntArray) Range(start, end int) []int {
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
//
|
||||
// 追加数据项, 等于: PushRight。
|
||||
func (a *IntArray) Append(value...int) *IntArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
@ -342,9 +299,7 @@ func (a *IntArray) Append(value...int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Get the length of array.
|
||||
//
|
||||
// 数组长度。
|
||||
// Len returns the length of array.
|
||||
func (a *IntArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
@ -352,11 +307,9 @@ func (a *IntArray) Len() int {
|
||||
return length
|
||||
}
|
||||
|
||||
// Get the underlying data of array.
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 返回原始数据数组.
|
||||
func (a *IntArray) Slice() []int {
|
||||
array := ([]int)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
@ -370,9 +323,7 @@ func (a *IntArray) Slice() []int {
|
||||
return array
|
||||
}
|
||||
|
||||
// Return a new array, which is a copy of current array.
|
||||
//
|
||||
// 克隆当前数组,返回当前数组的一个拷贝。
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *IntArray) Clone() (newArray *IntArray) {
|
||||
a.mu.RLock()
|
||||
array := make([]int, len(a.array))
|
||||
@ -381,9 +332,7 @@ func (a *IntArray) Clone() (newArray *IntArray) {
|
||||
return NewIntArrayFrom(array, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Clear array.
|
||||
//
|
||||
// 清空数据数组。
|
||||
// Clear deletes all items of current array.
|
||||
func (a *IntArray) Clear() *IntArray {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
@ -393,17 +342,13 @@ func (a *IntArray) Clear() *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Check whether a value exists in the array.
|
||||
//
|
||||
// 查找指定数值是否存在。
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *IntArray) Contains(value int) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
|
||||
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
|
||||
//
|
||||
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1。
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *IntArray) Search(value int) int {
|
||||
if len(a.array) == 0 {
|
||||
return -1
|
||||
@ -421,9 +366,7 @@ func (a *IntArray) Search(value int) int {
|
||||
return result
|
||||
}
|
||||
|
||||
// Unique the array, clear repeated values.
|
||||
//
|
||||
// 清理数组中重复的元素项。
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *IntArray) Unique() *IntArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array) - 1; i++ {
|
||||
@ -437,9 +380,7 @@ func (a *IntArray) Unique() *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock writing by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁修改操作。
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -447,9 +388,7 @@ func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock reading by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁读取操作。
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -457,11 +396,10 @@ func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge two arrays. The parameter <array> can be any garray type or slice type.
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more variable types.
|
||||
//
|
||||
// 合并两个数组, 支持任意的garray数组类型及slice类型.
|
||||
// but Merge supports more parameter types.
|
||||
func (a *IntArray) Merge(array interface{}) *IntArray {
|
||||
switch v := array.(type) {
|
||||
case *Array: a.Append(gconv.Ints(v.Slice())...)
|
||||
@ -476,10 +414,8 @@ func (a *IntArray) Merge(array interface{}) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Fills an array with num entries of the value of the value parameter,
|
||||
// keys starting at the startIndex parameter.
|
||||
//
|
||||
// 用value参数的值将数组填充num个条目,位置由startIndex参数指定的开始。
|
||||
// Fill fills an array with num entries of the value <value>,
|
||||
// keys starting at the <startIndex> parameter.
|
||||
func (a *IntArray) Fill(startIndex int, num int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -496,10 +432,9 @@ func (a *IntArray) Fill(startIndex int, num int, value int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunks an array into arrays with size elements.
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
//
|
||||
// 将一个数组分割成多个数组,其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
|
||||
func (a *IntArray) Chunk(size int) [][]int {
|
||||
if size < 1 {
|
||||
return nil
|
||||
@ -520,14 +455,10 @@ func (a *IntArray) Chunk(size int) [][]int {
|
||||
return n
|
||||
}
|
||||
|
||||
// Pad array to the specified length with a value.
|
||||
// Pad pads array to the specified length with <value>.
|
||||
// If size is positive then the array is padded on the right, or negative on the left.
|
||||
// If the absolute value of size is less than or equal to the length of the array
|
||||
// If the absolute value of <size> is less than or equal to the length of the array
|
||||
// then no padding takes place.
|
||||
//
|
||||
// 返回数组的一个拷贝,并用value将其填补到size指定的长度。
|
||||
// 如果size为正数,则填补到数组的右侧,如果为负数则从左侧开始填补。
|
||||
// 如果size的绝对值小于或等于数组的长度则没有任何填补。
|
||||
func (a *IntArray) Pad(size int, value int) *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -551,12 +482,9 @@ func (a *IntArray) Pad(size int, value int) *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Extract a slice of the array(If in concurrent safe usage,
|
||||
// it returns a copy of the slice; else a pointer).
|
||||
// It returns the sequence of elements from the array array as specified
|
||||
// by the offset and length parameters.
|
||||
//
|
||||
// 返回根据offset和size参数所指定的数组中的一段序列。
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
func (a *IntArray) SubSlice(offset, size int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -575,18 +503,14 @@ func (a *IntArray) SubSlice(offset, size int) []int {
|
||||
}
|
||||
}
|
||||
|
||||
// Rand gets one random entry from array.
|
||||
//
|
||||
// 从数组中随机获得1个元素项(不删除)。
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *IntArray) Rand() int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands gets one or more random entries from array(a copy).
|
||||
//
|
||||
// 从数组中随机拷贝size个元素项,构成slice返回。
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *IntArray) Rands(size int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -603,9 +527,7 @@ func (a *IntArray) Rands(size int) []int {
|
||||
return n
|
||||
}
|
||||
|
||||
// Randomly shuffles the array.
|
||||
//
|
||||
// 随机打乱当前数组。
|
||||
// Shuffle randomly shuffles the array.
|
||||
func (a *IntArray) Shuffle() *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -615,9 +537,7 @@ func (a *IntArray) Shuffle() *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Make array with elements in reverse order.
|
||||
//
|
||||
// 将当前数组反转。
|
||||
// Reverse makes array with elements in reverse order.
|
||||
func (a *IntArray) Reverse() *IntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -627,9 +547,7 @@ func (a *IntArray) Reverse() *IntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Join array elements with a string.
|
||||
//
|
||||
// 使用glue字符串串连当前数组的元素项,构造成新的字符串返回。
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *IntArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -641,4 +559,22 @@ func (a *IntArray) Join(glue string) string {
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *IntArray) CountValues() map[int]int {
|
||||
m := make(map[int]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *IntArray) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
@ -17,15 +17,13 @@ import (
|
||||
)
|
||||
|
||||
type Array struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
array []interface{} // 底层数组
|
||||
mu *rwmutex.RWMutex
|
||||
array []interface{}
|
||||
}
|
||||
|
||||
// Create an empty array.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个空的数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// New creates and returns an empty array.
|
||||
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func New(unsafe...bool) *Array {
|
||||
return NewArraySize(0, 0, unsafe...)
|
||||
}
|
||||
@ -35,11 +33,9 @@ func NewArray(unsafe...bool) *Array {
|
||||
return NewArraySize(0, 0, unsafe...)
|
||||
}
|
||||
|
||||
// Create an array with given size and cap.
|
||||
// NewArraySize create and returns an array with given size and cap.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个指定大小的数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewArraySize(size int, cap int, unsafe...bool) *Array {
|
||||
return &Array{
|
||||
mu : rwmutex.New(unsafe...),
|
||||
@ -57,11 +53,9 @@ func NewFromCopy(array []interface{}, unsafe...bool) *Array {
|
||||
return NewArrayFromCopy(array, unsafe...)
|
||||
}
|
||||
|
||||
// Create an array with given slice <array>.
|
||||
// NewArrayFrom creates and returns an array with given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice变量创建数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewArrayFrom(array []interface{}, unsafe...bool) *Array {
|
||||
return &Array{
|
||||
mu : rwmutex.New(unsafe...),
|
||||
@ -69,11 +63,9 @@ func NewArrayFrom(array []interface{}, unsafe...bool) *Array {
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array from a copy of given slice <array>.
|
||||
// NewArrayFromCopy creates and returns an array from a copy of given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice拷贝创建数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewArrayFromCopy(array []interface{}, unsafe...bool) *Array {
|
||||
newArray := make([]interface{}, len(array))
|
||||
copy(newArray, array)
|
||||
@ -83,9 +75,8 @@ func NewArrayFromCopy(array []interface{}, unsafe...bool) *Array {
|
||||
}
|
||||
}
|
||||
|
||||
// Get value by index.
|
||||
//
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *Array) Get(index int) interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -93,9 +84,7 @@ func (a *Array) Get(index int) interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// Set value by index.
|
||||
//
|
||||
// 设置指定索引的数据项, 调用方注意判断数组边界
|
||||
// Set sets value to specified index.
|
||||
func (a *Array) Set(index int, value interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -103,9 +92,7 @@ func (a *Array) Set(index int, value interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Set the underlying slice array with the given <array> param.
|
||||
//
|
||||
// 设置底层数组变量.
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *Array) SetArray(array []interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -113,9 +100,7 @@ func (a *Array) SetArray(array []interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Replace the array items by given <array> from the beginning of array.
|
||||
//
|
||||
// 使用指定数组替换到对应的索引元素值.
|
||||
// Replace replaces the array items by given <array> from the beginning of array.
|
||||
func (a *Array) Replace(array []interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -129,9 +114,7 @@ func (a *Array) Replace(array []interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Calculate the sum of values in an array.
|
||||
//
|
||||
// 对数组中的元素项求和(将元素值转换为int类型后叠加)。
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *Array) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -141,9 +124,7 @@ func (a *Array) Sum() (sum int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Sort the array by custom function <less>.
|
||||
//
|
||||
// 使用自定义的排序函数将数组重新排序.
|
||||
// SortFunc sorts the array by custom function <less>.
|
||||
func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -153,9 +134,7 @@ func (a *Array) SortFunc(less func(v1, v2 interface{}) bool) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Insert the <value> to the front of <index>.
|
||||
//
|
||||
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界。
|
||||
// InsertBefore inserts the <value> to the front of <index>.
|
||||
func (a *Array) InsertBefore(index int, value interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -165,9 +144,7 @@ func (a *Array) InsertBefore(index int, value interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Insert the <value> to the back of <index>.
|
||||
//
|
||||
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界。
|
||||
// InsertAfter inserts the <value> to the back of <index>.
|
||||
func (a *Array) InsertAfter(index int, value interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -177,13 +154,11 @@ func (a *Array) InsertAfter(index int, value interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Remove an item by index.
|
||||
//
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Remove removes an item by index.
|
||||
func (a *Array) Remove(index int) interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
// Determine array boundaries when deleting to improve deletion efficiency。
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
@ -193,15 +168,15 @@ func (a *Array) Remove(index int) interface{} {
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// Push new items to the beginning of array.
|
||||
//
|
||||
// 将数据项添加到数组的最左端(索引为0)。
|
||||
// PushLeft pushes one or multiple items to the beginning of array.
|
||||
func (a *Array) PushLeft(value...interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
a.array = append(value, a.array...)
|
||||
@ -209,9 +184,8 @@ func (a *Array) PushLeft(value...interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Push new items to the end of array.
|
||||
//
|
||||
// 将数据项添加到数组的最右端(索引为length - 1), 等于: Append。
|
||||
// PushRight pushes one or multiple items to the end of array.
|
||||
// It equals to Append.
|
||||
func (a *Array) PushRight(value...interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
@ -219,16 +193,12 @@ func (a *Array) PushRight(value...interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// PopRand picks an random item out of array.
|
||||
//
|
||||
// 随机将一个数据项移出数组,并返回该数据项。
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *Array) PopRand() interface{} {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands picks <size> items out of array.
|
||||
//
|
||||
// 随机将size个数据项移出数组,并返回该数据项。
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *Array) PopRands(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -244,9 +214,7 @@ func (a *Array) PopRands(size int) []interface{} {
|
||||
return array
|
||||
}
|
||||
|
||||
// Pop an item from the beginning of array.
|
||||
//
|
||||
// 将最左端(索引为0)的数据项移出数组,并返回该数据项。
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *Array) PopLeft() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -255,9 +223,7 @@ func (a *Array) PopLeft() interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// Pop an item from the end of array.
|
||||
//
|
||||
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项。
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *Array) PopRight() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -267,9 +233,7 @@ func (a *Array) PopRight() interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// Pop <size> items from the beginning of array.
|
||||
//
|
||||
// 将最左端(首部)的size个数据项移出数组,并返回该数据项
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *Array) PopLefts(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -282,9 +246,7 @@ func (a *Array) PopLefts(size int) []interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// Pop <size> items from the end of array.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *Array) PopRights(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -297,11 +259,9 @@ func (a *Array) PopRights(size int) []interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// Get items by range, returns array[start:end].
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
func (a *Array) Range(start, end int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -328,16 +288,12 @@ func (a *Array) Range(start, end int) []interface{} {
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
//
|
||||
// 追加数据项, 等于: PushRight。
|
||||
func (a *Array) Append(value...interface{}) *Array {
|
||||
a.PushRight(value...)
|
||||
return a
|
||||
}
|
||||
|
||||
// Get the length of array.
|
||||
//
|
||||
// 数组长度。
|
||||
// Len returns the length of array.
|
||||
func (a *Array) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
@ -345,11 +301,9 @@ func (a *Array) Len() int {
|
||||
return length
|
||||
}
|
||||
|
||||
// Get the underlying data of array.
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 返回原始数据数组.
|
||||
func (a *Array) Slice() []interface{} {
|
||||
array := ([]interface{})(nil)
|
||||
if a.mu.IsSafe() {
|
||||
@ -363,9 +317,7 @@ func (a *Array) Slice() []interface{} {
|
||||
return array
|
||||
}
|
||||
|
||||
// Return a new array, which is a copy of current array.
|
||||
//
|
||||
// 克隆当前数组,返回当前数组的一个拷贝。
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *Array) Clone() (newArray *Array) {
|
||||
a.mu.RLock()
|
||||
array := make([]interface{}, len(a.array))
|
||||
@ -374,9 +326,7 @@ func (a *Array) Clone() (newArray *Array) {
|
||||
return NewArrayFrom(array, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Clear array.
|
||||
//
|
||||
// 清空数据数组
|
||||
// Clear deletes all items of current array.
|
||||
func (a *Array) Clear() *Array {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
@ -386,16 +336,13 @@ func (a *Array) Clear() *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Check whether a value exists in the array.
|
||||
//
|
||||
// 查找指定数值是否存在
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *Array) Contains(value interface{}) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
|
||||
//
|
||||
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *Array) Search(value interface{}) int {
|
||||
if len(a.array) == 0 {
|
||||
return -1
|
||||
@ -413,9 +360,7 @@ func (a *Array) Search(value interface{}) int {
|
||||
return result
|
||||
}
|
||||
|
||||
// Unique the array, clear repeated values.
|
||||
//
|
||||
// 清理数组中重复的元素项
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *Array) Unique() *Array {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array) - 1; i++ {
|
||||
@ -429,9 +374,7 @@ func (a *Array) Unique() *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock writing by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *Array) LockFunc(f func(array []interface{})) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -439,9 +382,7 @@ func (a *Array) LockFunc(f func(array []interface{})) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock reading by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁读取操作
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *Array) RLockFunc(f func(array []interface{})) *Array {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -449,11 +390,10 @@ func (a *Array) RLockFunc(f func(array []interface{})) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge two arrays. The parameter <array> can be any garray type or slice type.
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more variable types.
|
||||
//
|
||||
// 合并两个数组, 支持任意的garray数组类型及slice类型.
|
||||
// but Merge supports more parameter types.
|
||||
func (a *Array) Merge(array interface{}) *Array {
|
||||
switch v := array.(type) {
|
||||
case *Array: a.Append(gconv.Interfaces(v.Slice())...)
|
||||
@ -468,10 +408,8 @@ func (a *Array) Merge(array interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Fills an array with num entries of the value of the value parameter,
|
||||
// keys starting at the start_index parameter.
|
||||
//
|
||||
// 用value参数的值将数组填充num个条目,位置由startIndex参数指定的开始。
|
||||
// Fill fills an array with num entries of the value <value>,
|
||||
// keys starting at the <startIndex> parameter.
|
||||
func (a *Array) Fill(startIndex int, num int, value interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -488,10 +426,9 @@ func (a *Array) Fill(startIndex int, num int, value interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunks an array into arrays with size elements.
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
//
|
||||
// 将一个数组分割成多个数组,其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
|
||||
func (a *Array) Chunk(size int) [][]interface{} {
|
||||
if size < 1 {
|
||||
return nil
|
||||
@ -512,15 +449,10 @@ func (a *Array) Chunk(size int) [][]interface{} {
|
||||
return n
|
||||
}
|
||||
|
||||
// Pad array to the specified length with a value.
|
||||
// If size is positive then the array is padded on the right,
|
||||
// if it's negative then on the left.
|
||||
// If the absolute value of size is less than or equal to the length of the array
|
||||
// Pad pads array to the specified length with <value>.
|
||||
// If size is positive then the array is padded on the right, or negative on the left.
|
||||
// If the absolute value of <size> is less than or equal to the length of the array
|
||||
// then no padding takes place.
|
||||
//
|
||||
// 返回数组的一个拷贝,并用value将其填补到size指定的长度。
|
||||
// 如果size为正数,则填补到数组的右侧,如果为负数则从左侧开始填补。
|
||||
// 如果size的绝对值小于或等于数组的长度则没有任何填补。
|
||||
func (a *Array) Pad(size int, val interface{}) *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -544,10 +476,9 @@ func (a *Array) Pad(size int, val interface{}) *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Extract a slice of the array(If in concurrent safe usage, it returns a copy of the slice; else a pointer).
|
||||
// It returns the sequence of elements from the array array as specified by the offset and length parameters.
|
||||
//
|
||||
// 返回根据offset和size参数所指定的数组中的一段序列。
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
func (a *Array) SubSlice(offset, size int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -566,18 +497,14 @@ func (a *Array) SubSlice(offset, size int) []interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
// Rand gets one random entry from array.
|
||||
//
|
||||
// 从数组中随机获得1个元素项(不删除)。
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *Array) Rand() interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands gets one or more random entries from array(a copy).
|
||||
//
|
||||
// 从数组中随机拷贝size个元素项,构成slice返回。
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *Array) Rands(size int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -594,9 +521,7 @@ func (a *Array) Rands(size int) []interface{} {
|
||||
return n
|
||||
}
|
||||
|
||||
// Randomly shuffles the array.
|
||||
//
|
||||
// 随机打乱当前数组。
|
||||
// Shuffle randomly shuffles the array.
|
||||
func (a *Array) Shuffle() *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -606,9 +531,7 @@ func (a *Array) Shuffle() *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Make array with elements in reverse order.
|
||||
//
|
||||
// 将当前数组反转。
|
||||
// Reverse makes array with elements in reverse order.
|
||||
func (a *Array) Reverse() *Array {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -618,9 +541,7 @@ func (a *Array) Reverse() *Array {
|
||||
return a
|
||||
}
|
||||
|
||||
// Join array elements with a string.
|
||||
//
|
||||
// 使用glue字符串串连当前数组的元素项,构造成新的字符串返回。
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *Array) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -634,9 +555,7 @@ func (a *Array) Join(glue string) string {
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Counts all the values of an array.
|
||||
//
|
||||
// 统计数组中所有的值出现的次数.
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *Array) CountValues() map[interface{}]int {
|
||||
m := make(map[interface{}]int)
|
||||
a.mu.RLock()
|
||||
@ -648,8 +567,6 @@ func (a *Array) CountValues() map[interface{}]int {
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
//
|
||||
// 将当前数组转换为字符串返回。
|
||||
func (a *Array) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
|
||||
@ -8,7 +8,8 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
"math"
|
||||
@ -17,24 +18,20 @@ import (
|
||||
)
|
||||
|
||||
type StringArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
array []string // 底层数组
|
||||
mu *rwmutex.RWMutex
|
||||
array []string
|
||||
}
|
||||
|
||||
// Create an empty array.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个空的数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// NewStringArray creates and returns an empty array.
|
||||
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStringArray(unsafe...bool) *StringArray {
|
||||
return NewStringArraySize(0, 0, unsafe...)
|
||||
}
|
||||
|
||||
// Create an array with given size and cap.
|
||||
// NewStringArraySize create and returns an array with given size and cap.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个指定大小的数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewStringArraySize(size int, cap int, unsafe...bool) *StringArray {
|
||||
return &StringArray{
|
||||
mu : rwmutex.New(unsafe...),
|
||||
@ -42,11 +39,9 @@ func NewStringArraySize(size int, cap int, unsafe...bool) *StringArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array with given slice <array>.
|
||||
// NewStringArrayFrom creates and returns an array with given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice变量创建数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewStringArrayFrom(array []string, unsafe...bool) *StringArray {
|
||||
return &StringArray {
|
||||
mu : rwmutex.New(unsafe...),
|
||||
@ -54,11 +49,9 @@ func NewStringArrayFrom(array []string, unsafe...bool) *StringArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array from a copy of given slice <array>.
|
||||
// NewStringArrayFromCopy creates and returns an array from a copy of given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice拷贝创建数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewStringArrayFromCopy(array []string, unsafe...bool) *StringArray {
|
||||
newArray := make([]string, len(array))
|
||||
copy(newArray, array)
|
||||
@ -68,9 +61,8 @@ func NewStringArrayFromCopy(array []string, unsafe...bool) *StringArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Get value by index.
|
||||
//
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *StringArray) Get(index int) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -78,9 +70,7 @@ func (a *StringArray) Get(index int) string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Set value by index.
|
||||
//
|
||||
// 设置指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Set sets value to specified index.
|
||||
func (a *StringArray) Set(index int, value string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -88,9 +78,7 @@ func (a *StringArray) Set(index int, value string) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Set the underlying slice array with the given <array> param.
|
||||
//
|
||||
// 设置底层数组变量.
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *StringArray) SetArray(array []string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -98,9 +86,7 @@ func (a *StringArray) SetArray(array []string) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Replace the array items by given <array> from the beginning of array.
|
||||
//
|
||||
// 使用指定数组替换到对应的索引元素值.
|
||||
// Replace replaces the array items by given <array> from the beginning of array.
|
||||
func (a *StringArray) Replace(array []string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -114,9 +100,7 @@ func (a *StringArray) Replace(array []string) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Calculate the sum of values in an array.
|
||||
//
|
||||
// 对数组中的元素项求和(将元素值转换为int类型后叠加)。
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *StringArray) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -126,11 +110,9 @@ func (a *StringArray) Sum() (sum int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Sort the array in increasing order.
|
||||
// Sort sorts the array in increasing order.
|
||||
// The param <reverse> controls whether sort
|
||||
// in increasing order(default) or decreasing order
|
||||
//
|
||||
// 将数组排序(默认从低到高).
|
||||
func (a *StringArray) Sort(reverse...bool) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -147,9 +129,7 @@ func (a *StringArray) Sort(reverse...bool) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Sort the array by custom function <less>.
|
||||
//
|
||||
// 使用自定义的排序函数将数组重新排序.
|
||||
// SortFunc sorts the array by custom function <less>.
|
||||
func (a *StringArray) SortFunc(less func(v1, v2 string) bool) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -159,9 +139,7 @@ func (a *StringArray) SortFunc(less func(v1, v2 string) bool) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Insert the <value> to the front of <index>.
|
||||
//
|
||||
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界。
|
||||
// InsertBefore inserts the <value> to the front of <index>.
|
||||
func (a *StringArray) InsertBefore(index int, value string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -171,9 +149,7 @@ func (a *StringArray) InsertBefore(index int, value string) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Insert the <value> to the back of <index>.
|
||||
//
|
||||
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界。
|
||||
// InsertAfter inserts the <value> to the back of <index>.
|
||||
func (a *StringArray) InsertAfter(index int, value string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -183,13 +159,11 @@ func (a *StringArray) InsertAfter(index int, value string) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Remove an item by index.
|
||||
//
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Remove removes an item by index.
|
||||
func (a *StringArray) Remove(index int) string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
// Determine array boundaries when deleting to improve deletion efficiency。
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
@ -199,15 +173,15 @@ func (a *StringArray) Remove(index int) string {
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// Push new items to the beginning of array.
|
||||
//
|
||||
// 将数据项添加到数组的最左端(索引为0)。
|
||||
// PushLeft pushes one or multiple items to the beginning of array.
|
||||
func (a *StringArray) PushLeft(value...string) *StringArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(value, a.array...)
|
||||
@ -215,9 +189,8 @@ func (a *StringArray) PushLeft(value...string) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Push new items to the end of array.
|
||||
//
|
||||
// 将数据项添加到数组的最右端(索引为length - 1), 等于: Append。
|
||||
// PushRight pushes one or multiple items to the end of array.
|
||||
// It equals to Append.
|
||||
func (a *StringArray) PushRight(value...string) *StringArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
@ -225,9 +198,7 @@ func (a *StringArray) PushRight(value...string) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Pop an item from the beginning of array.
|
||||
//
|
||||
// 将最左端(索引为0)的数据项移出数组,并返回该数据项。
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *StringArray) PopLeft() string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -236,9 +207,7 @@ func (a *StringArray) PopLeft() string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Pop an item from the end of array.
|
||||
//
|
||||
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项。
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *StringArray) PopRight() string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -248,16 +217,12 @@ func (a *StringArray) PopRight() string {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRand picks an random item out of array.
|
||||
//
|
||||
// 随机将一个数据项移出数组,并返回该数据项。
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *StringArray) PopRand() string {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands picks <size> items out of array.
|
||||
//
|
||||
// 随机将size个数据项移出数组,并返回该数据项。
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *StringArray) PopRands(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -273,9 +238,7 @@ func (a *StringArray) PopRands(size int) []string {
|
||||
return array
|
||||
}
|
||||
|
||||
// Pop <size> items from the beginning of array.
|
||||
//
|
||||
// 将最左端(首部)的size个数据项移出数组,并返回该数据项
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *StringArray) PopLefts(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -288,9 +251,7 @@ func (a *StringArray) PopLefts(size int) []string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Pop <size> items from the end of array.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *StringArray) PopRights(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -303,11 +264,9 @@ func (a *StringArray) PopRights(size int) []string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Get items by range, returns array[start:end].
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
func (a *StringArray) Range(start, end int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -334,8 +293,6 @@ func (a *StringArray) Range(start, end int) []string {
|
||||
}
|
||||
|
||||
// See PushRight.
|
||||
//
|
||||
// 追加数据项, 等于: PushRight。
|
||||
func (a *StringArray) Append(value...string) *StringArray {
|
||||
a.mu.Lock()
|
||||
a.array = append(a.array, value...)
|
||||
@ -343,9 +300,7 @@ func (a *StringArray) Append(value...string) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Get the length of array.
|
||||
//
|
||||
// 数组长度。
|
||||
// Len returns the length of array.
|
||||
func (a *StringArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
@ -353,11 +308,9 @@ func (a *StringArray) Len() int {
|
||||
return length
|
||||
}
|
||||
|
||||
// Get the underlying data of array.
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 返回原始数据数组.
|
||||
func (a *StringArray) Slice() []string {
|
||||
array := ([]string)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
@ -371,9 +324,7 @@ func (a *StringArray) Slice() []string {
|
||||
return array
|
||||
}
|
||||
|
||||
// Return a new array, which is a copy of current array.
|
||||
//
|
||||
// 克隆当前数组,返回当前数组的一个拷贝。
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *StringArray) Clone() (newArray *StringArray) {
|
||||
a.mu.RLock()
|
||||
array := make([]string, len(a.array))
|
||||
@ -382,9 +333,7 @@ func (a *StringArray) Clone() (newArray *StringArray) {
|
||||
return NewStringArrayFrom(array, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Clear array.
|
||||
//
|
||||
// 清空数据数组。
|
||||
// Clear deletes all items of current array.
|
||||
func (a *StringArray) Clear() *StringArray {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
@ -394,16 +343,13 @@ func (a *StringArray) Clear() *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Check whether a value exists in the array.
|
||||
//
|
||||
// 查找指定数值是否存在。
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *StringArray) Contains(value string) bool {
|
||||
return a.Search(value) != -1
|
||||
}
|
||||
|
||||
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
|
||||
//
|
||||
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1。
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *StringArray) Search(value string) int {
|
||||
if len(a.array) == 0 {
|
||||
return -1
|
||||
@ -420,9 +366,7 @@ func (a *StringArray) Search(value string) int {
|
||||
return result
|
||||
}
|
||||
|
||||
// Unique the array, clear repeated values.
|
||||
//
|
||||
// 清理数组中重复的元素项。
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *StringArray) Unique() *StringArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array) - 1; i++ {
|
||||
@ -436,9 +380,7 @@ func (a *StringArray) Unique() *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock writing by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁修改操作。
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *StringArray) LockFunc(f func(array []string)) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -446,9 +388,7 @@ func (a *StringArray) LockFunc(f func(array []string)) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock reading by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁读取操作。
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *StringArray) RLockFunc(f func(array []string)) *StringArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -456,11 +396,10 @@ func (a *StringArray) RLockFunc(f func(array []string)) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge two arrays. The parameter <array> can be any garray type or slice type.
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more variable types.
|
||||
//
|
||||
// 合并两个数组, 支持任意的garray数组类型及slice类型.
|
||||
// but Merge supports more parameter types.
|
||||
func (a *StringArray) Merge(array interface{}) *StringArray {
|
||||
switch v := array.(type) {
|
||||
case *Array: a.Append(gconv.Strings(v.Slice())...)
|
||||
@ -475,10 +414,8 @@ func (a *StringArray) Merge(array interface{}) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Fills an array with num entries of the value of the value parameter,
|
||||
// keys starting at the start_index parameter.
|
||||
//
|
||||
// 用value参数的值将数组填充num个条目,位置由startIndex参数指定的开始。
|
||||
// Fill fills an array with num entries of the value <value>,
|
||||
// keys starting at the <startIndex> parameter.
|
||||
func (a *StringArray) Fill(startIndex int, num int, value string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -495,10 +432,9 @@ func (a *StringArray) Fill(startIndex int, num int, value string) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunks an array into arrays with size elements.
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
//
|
||||
// 将一个数组分割成多个数组,其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
|
||||
func (a *StringArray) Chunk(size int) [][]string {
|
||||
if size < 1 {
|
||||
return nil
|
||||
@ -519,15 +455,10 @@ func (a *StringArray) Chunk(size int) [][]string {
|
||||
return n
|
||||
}
|
||||
|
||||
// Pad array to the specified length with a value.
|
||||
// If size is positive then the array is padded on the right,
|
||||
// if it's negative then on the left.
|
||||
// If the absolute value of size is less than or equal to the length of the array
|
||||
// Pad pads array to the specified length with <value>.
|
||||
// If size is positive then the array is padded on the right, or negative on the left.
|
||||
// If the absolute value of <size> is less than or equal to the length of the array
|
||||
// then no padding takes place.
|
||||
//
|
||||
// 返回数组的一个拷贝,并用value将其填补到size指定的长度。
|
||||
// 如果size为正数,则填补到数组的右侧,如果为负数则从左侧开始填补。
|
||||
// 如果size的绝对值小于或等于数组的长度则没有任何填补。
|
||||
func (a *StringArray) Pad(size int, value string) *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -551,12 +482,9 @@ func (a *StringArray) Pad(size int, value string) *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Extract a slice of the array(If in concurrent safe usage,
|
||||
// it returns a copy of the slice; else a pointer).
|
||||
// It returns the sequence of elements from the array array as specified
|
||||
// by the offset and length parameters.
|
||||
//
|
||||
// 返回根据offset和size参数所指定的数组中的一段序列。
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
func (a *StringArray) SubSlice(offset, size int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -575,18 +503,14 @@ func (a *StringArray) SubSlice(offset, size int) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// Rand gets one random entry from array.
|
||||
//
|
||||
// 从数组中随机获得1个元素项(不删除)。
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *StringArray) Rand() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands gets one or more random entries from array(a copy).
|
||||
//
|
||||
// 从数组中随机拷贝size个元素项,构成slice返回。
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *StringArray) Rands(size int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -603,9 +527,7 @@ func (a *StringArray) Rands(size int) []string {
|
||||
return n
|
||||
}
|
||||
|
||||
// Randomly shuffles the array.
|
||||
//
|
||||
// 随机打乱当前数组。
|
||||
// Shuffle randomly shuffles the array.
|
||||
func (a *StringArray) Shuffle() *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -615,9 +537,7 @@ func (a *StringArray) Shuffle() *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Make array with elements in reverse order.
|
||||
//
|
||||
// 将当前数组反转。
|
||||
// Reverse makes array with elements in reverse order.
|
||||
func (a *StringArray) Reverse() *StringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -627,9 +547,7 @@ func (a *StringArray) Reverse() *StringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Join array elements with a string.
|
||||
//
|
||||
// 使用glue字符串串连当前数组的元素项,构造成新的字符串返回。
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *StringArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -643,3 +561,20 @@ func (a *StringArray) Join(glue string) string {
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *StringArray) CountValues() map[string]int {
|
||||
m := make(map[string]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *StringArray) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
|
||||
@ -8,7 +8,8 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
@ -16,28 +17,24 @@ import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// 默认按照从小到大进行排序
|
||||
// It's using increasing order in default.
|
||||
type SortedIntArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
array []int // 底层数组
|
||||
unique *gtype.Bool // 是否要求不能重复(默认false)
|
||||
compareFunc func(v1, v2 int) int // 比较函数,返回值 -1: v1 < v2;0: v1 == v2;1: v1 > v2
|
||||
mu *rwmutex.RWMutex
|
||||
array []int
|
||||
unique *gtype.Bool // Whether enable unique feature(false)
|
||||
compareFunc func(v1, v2 int) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
|
||||
}
|
||||
|
||||
// Create an empty sorted array.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个空的排序数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// NewSortedIntArray creates and returns an empty sorted array.
|
||||
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedIntArray(unsafe...bool) *SortedIntArray {
|
||||
return NewSortedIntArraySize(0, unsafe...)
|
||||
}
|
||||
|
||||
// Create a sorted array with given size and cap.
|
||||
// NewSortedIntArraySize create and returns an sorted array with given size and cap.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个指定大小的排序数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewSortedIntArraySize(cap int, unsafe...bool) *SortedIntArray {
|
||||
return &SortedIntArray {
|
||||
mu : rwmutex.New(unsafe...),
|
||||
@ -55,11 +52,9 @@ func NewSortedIntArraySize(cap int, unsafe...bool) *SortedIntArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array with given slice <array>.
|
||||
// NewIntArrayFrom creates and returns an sorted array with given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice变量创建排序数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewSortedIntArrayFrom(array []int, unsafe...bool) *SortedIntArray {
|
||||
a := NewSortedIntArraySize(0, unsafe...)
|
||||
a.array = array
|
||||
@ -67,11 +62,9 @@ func NewSortedIntArrayFrom(array []int, unsafe...bool) *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Create an array from a copy of given slice <array>.
|
||||
// NewSortedIntArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice拷贝创建数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewSortedIntArrayFromCopy(array []int, unsafe...bool) *SortedIntArray {
|
||||
newArray := make([]int, len(array))
|
||||
copy(newArray, array)
|
||||
@ -81,9 +74,7 @@ func NewSortedIntArrayFromCopy(array []int, unsafe...bool) *SortedIntArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Set the underlying slice array with the given <array> param.
|
||||
//
|
||||
// 设置底层数组变量.
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -92,9 +83,9 @@ func (a *SortedIntArray) SetArray(array []int) *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Sort the array in increasing order.
|
||||
//
|
||||
// 将数组排序(默认从低到高).
|
||||
// Sort sorts the array in increasing order.
|
||||
// The param <reverse> controls whether sort
|
||||
// in increasing order(default) or decreasing order.
|
||||
func (a *SortedIntArray) Sort() *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -102,9 +93,7 @@ func (a *SortedIntArray) Sort() *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// And values to sorted array, the array always keeps sorted.
|
||||
//
|
||||
// 添加数据项.
|
||||
// Add adds one or multiple values to sorted array, the array always keeps sorted.
|
||||
func (a *SortedIntArray) Add(values...int) *SortedIntArray {
|
||||
if len(values) == 0 {
|
||||
return a
|
||||
@ -120,7 +109,6 @@ func (a *SortedIntArray) Add(values...int) *SortedIntArray {
|
||||
a.array = append(a.array, value)
|
||||
continue
|
||||
}
|
||||
// 加到指定索引后面
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
@ -131,9 +119,8 @@ func (a *SortedIntArray) Add(values...int) *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Get value by index.
|
||||
//
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *SortedIntArray) Get(index int) int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -141,13 +128,11 @@ func (a *SortedIntArray) Get(index int) int {
|
||||
return value
|
||||
}
|
||||
|
||||
// Remove an item by index.
|
||||
//
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Remove removes an item by index.
|
||||
func (a *SortedIntArray) Remove(index int) int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
// Determine array boundaries when deleting to improve deletion efficiency.
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
@ -157,15 +142,15 @@ func (a *SortedIntArray) Remove(index int) int {
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// Push new items to the beginning of array.
|
||||
//
|
||||
// 将数据项添加到数组的最左端(索引为0)。
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *SortedIntArray) PopLeft() int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -174,9 +159,7 @@ func (a *SortedIntArray) PopLeft() int {
|
||||
return value
|
||||
}
|
||||
|
||||
// Push new items to the end of array.
|
||||
//
|
||||
// 将数据项添加到数组的最右端(索引为length - 1)。
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *SortedIntArray) PopRight() int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -186,16 +169,12 @@ func (a *SortedIntArray) PopRight() int {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRand picks an random item out of array.
|
||||
//
|
||||
// 随机将一个数据项移出数组,并返回该数据项。
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *SortedIntArray) PopRand() int {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands picks <size> items out of array.
|
||||
//
|
||||
// 随机将size个数据项移出数组,并返回该数据项。
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *SortedIntArray) PopRands(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -211,9 +190,7 @@ func (a *SortedIntArray) PopRands(size int) []int {
|
||||
return array
|
||||
}
|
||||
|
||||
// Pop <size> items from the beginning of array.
|
||||
//
|
||||
// 将最左端(首部)的size个数据项移出数组,并返回该数据项
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *SortedIntArray) PopLefts(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -226,9 +203,7 @@ func (a *SortedIntArray) PopLefts(size int) []int {
|
||||
return value
|
||||
}
|
||||
|
||||
// Pop <size> items from the end of array.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *SortedIntArray) PopRights(size int) []int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -241,11 +216,9 @@ func (a *SortedIntArray) PopRights(size int) []int {
|
||||
return value
|
||||
}
|
||||
|
||||
// Get items by range, returns array[start:end].
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
func (a *SortedIntArray) Range(start, end int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -271,9 +244,7 @@ func (a *SortedIntArray) Range(start, end int) []int {
|
||||
return array
|
||||
}
|
||||
|
||||
// Get the length of array.
|
||||
//
|
||||
// 数组长度。
|
||||
// Len returns the length of array.
|
||||
func (a *SortedIntArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
@ -281,9 +252,7 @@ func (a *SortedIntArray) Len() int {
|
||||
return length
|
||||
}
|
||||
|
||||
// Calculate the sum of values in an array.
|
||||
//
|
||||
// 对数组中的元素项求和。
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *SortedIntArray) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -293,11 +262,9 @@ func (a *SortedIntArray) Sum() (sum int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the underlying data of array.
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 返回原始数据数组.
|
||||
func (a *SortedIntArray) Slice() []int {
|
||||
array := ([]int)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
@ -311,24 +278,19 @@ func (a *SortedIntArray) Slice() []int {
|
||||
return array
|
||||
}
|
||||
|
||||
// Check whether a value exists in the array.
|
||||
//
|
||||
// 查找指定数值是否存在。
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *SortedIntArray) Contains(value int) bool {
|
||||
return a.Search(value) == 0
|
||||
}
|
||||
|
||||
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
|
||||
//
|
||||
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1。
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *SortedIntArray) Search(value int) (index int) {
|
||||
index, _ = a.binSearch(value, true)
|
||||
return
|
||||
}
|
||||
|
||||
// Binary search.
|
||||
//
|
||||
// 二分查找.
|
||||
func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) {
|
||||
if len(a.array) == 0 {
|
||||
return -1, -2
|
||||
@ -354,11 +316,9 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
|
||||
return mid, cmp
|
||||
}
|
||||
|
||||
// Set unique mark to the array,
|
||||
// SetUnique sets unique mark to the array,
|
||||
// which means it does not contain any repeated items.
|
||||
// It also do unique check, remove all repeated items.
|
||||
//
|
||||
// 设置是否允许数组唯一.
|
||||
func (a *SortedIntArray) SetUnique(unique bool) *SortedIntArray {
|
||||
oldUnique := a.unique.Val()
|
||||
a.unique.Set(unique)
|
||||
@ -368,9 +328,7 @@ func (a *SortedIntArray) SetUnique(unique bool) *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Do unique check, remove all repeated items.
|
||||
//
|
||||
// 清理数组中重复的元素项.
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *SortedIntArray) Unique() *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
i := 0
|
||||
@ -388,9 +346,7 @@ func (a *SortedIntArray) Unique() *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Return a new array, which is a copy of current array.
|
||||
//
|
||||
// 克隆当前数组,返回当前数组的一个拷贝。
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *SortedIntArray) Clone() (newArray *SortedIntArray) {
|
||||
a.mu.RLock()
|
||||
array := make([]int, len(a.array))
|
||||
@ -399,9 +355,7 @@ func (a *SortedIntArray) Clone() (newArray *SortedIntArray) {
|
||||
return NewSortedIntArrayFrom(array, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Clear array.
|
||||
//
|
||||
// 清空数据数组。
|
||||
// Clear deletes all items of current array.
|
||||
func (a *SortedIntArray) Clear() *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
@ -411,9 +365,7 @@ func (a *SortedIntArray) Clear() *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock writing by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁修改操作。
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -421,9 +373,7 @@ func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock reading by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁读取操作。
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -431,11 +381,10 @@ func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge two arrays. The parameter <array> can be any garray type or slice type.
|
||||
// The difference between Merge and Add is Add supports only specified slice type,
|
||||
// but Merge supports more variable types.
|
||||
//
|
||||
// 合并两个数组, 支持任意的garray数组类型及slice类型.
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
|
||||
switch v := array.(type) {
|
||||
case *Array: a.Add(gconv.Ints(v.Slice())...)
|
||||
@ -450,10 +399,9 @@ func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunks an array into arrays with size elements.
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
//
|
||||
// 将一个数组分割成多个数组,其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
|
||||
func (a *SortedIntArray) Chunk(size int) [][]int {
|
||||
if size < 1 {
|
||||
return nil
|
||||
@ -474,12 +422,9 @@ func (a *SortedIntArray) Chunk(size int) [][]int {
|
||||
return n
|
||||
}
|
||||
|
||||
// Extract a slice of the array(If in concurrent safe usage,
|
||||
// it returns a copy of the slice; else a pointer).
|
||||
// It returns the sequence of elements from the array array as specified
|
||||
// by the offset and length parameters.
|
||||
//
|
||||
// 返回根据offset和size参数所指定的数组中的一段序列。
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
func (a *SortedIntArray) SubSlice(offset, size int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -498,18 +443,14 @@ func (a *SortedIntArray) SubSlice(offset, size int) []int {
|
||||
}
|
||||
}
|
||||
|
||||
// Rand gets one random entry from array.
|
||||
//
|
||||
// 从数组中随机获得1个元素项(不删除)。
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *SortedIntArray) Rand() int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands gets one or more random entries from array(a copy).
|
||||
//
|
||||
// 从数组中随机拷贝size个元素项,构成slice返回。
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *SortedIntArray) Rands(size int) []int {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -526,9 +467,7 @@ func (a *SortedIntArray) Rands(size int) []int {
|
||||
return n
|
||||
}
|
||||
|
||||
// Join array elements with a string.
|
||||
//
|
||||
// 使用glue字符串串连当前数组的元素项,构造成新的字符串返回。
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *SortedIntArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -540,4 +479,22 @@ func (a *SortedIntArray) Join(glue string) string {
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *SortedIntArray) CountValues() map[int]int {
|
||||
m := make(map[int]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *SortedIntArray) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
@ -8,7 +8,8 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
@ -16,36 +17,27 @@ import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// 默认按照从小到大进行排序
|
||||
// It's using increasing order in default.
|
||||
type SortedArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
array []interface{} // 底层数组
|
||||
unique *gtype.Bool // 是否要求不能重复
|
||||
compareFunc func(v1, v2 interface{}) int // 比较函数,返回值 -1: v1 < v2;0: v1 == v2;1: v1 > v2
|
||||
mu *rwmutex.RWMutex
|
||||
array []interface{}
|
||||
unique *gtype.Bool // Whether enable unique feature(false)
|
||||
compareFunc func(v1, v2 interface{}) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
|
||||
}
|
||||
|
||||
// Create an empty sorted array.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
// NewSortedArray creates and returns an empty sorted array.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety, which is false in default.
|
||||
// The param <compareFunc> used to compare values to sort in array,
|
||||
// if it returns value < 0, means v1 < v2;
|
||||
// if it returns value = 0, means v1 = v2;
|
||||
// if it returns value > 0, means v1 > v2;
|
||||
//
|
||||
// 创建一个空的排序数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// 参数compareFunc用于指定排序方法:
|
||||
// 如果返回值 < 0, 表示 v1 < v2;
|
||||
// 如果返回值 = 0, 表示 v1 = v2;
|
||||
// 如果返回值 > 0, 表示 v1 > v2;
|
||||
func NewSortedArray(compareFunc func(v1, v2 interface{}) int, unsafe...bool) *SortedArray {
|
||||
return NewSortedArraySize(0, compareFunc, unsafe...)
|
||||
}
|
||||
|
||||
// Create a sorted array with given size and cap.
|
||||
// NewSortedArraySize create and returns an sorted array with given size and cap.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个指定大小的排序数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewSortedArraySize(cap int, compareFunc func(v1, v2 interface{}) int, unsafe...bool) *SortedArray {
|
||||
return &SortedArray{
|
||||
mu : rwmutex.New(unsafe...),
|
||||
@ -55,11 +47,9 @@ func NewSortedArraySize(cap int, compareFunc func(v1, v2 interface{}) int, unsaf
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array with given slice <array>.
|
||||
// NewSortedArrayFrom creates and returns an sorted array with given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice变量创建排序数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewSortedArrayFrom(array []interface{}, compareFunc func(v1, v2 interface{}) int, unsafe...bool) *SortedArray {
|
||||
a := NewSortedArraySize(0, compareFunc, unsafe...)
|
||||
a.array = array
|
||||
@ -69,11 +59,9 @@ func NewSortedArrayFrom(array []interface{}, compareFunc func(v1, v2 interface{}
|
||||
return a
|
||||
}
|
||||
|
||||
// Create an array from a copy of given slice <array>.
|
||||
// NewSortedArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice拷贝创建数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewSortedArrayFromCopy(array []interface{}, unsafe...bool) *SortedArray {
|
||||
newArray := make([]interface{}, len(array))
|
||||
copy(newArray, array)
|
||||
@ -83,9 +71,7 @@ func NewSortedArrayFromCopy(array []interface{}, unsafe...bool) *SortedArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Set the underlying slice array with the given <array> param.
|
||||
//
|
||||
// 设置底层数组变量.
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *SortedArray) SetArray(array []interface{}) *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -96,9 +82,9 @@ func (a *SortedArray) SetArray(array []interface{}) *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Sort the array by comparing function.
|
||||
//
|
||||
// 将数组按照比较方法进行排序.
|
||||
// Sort sorts the array in increasing order.
|
||||
// The param <reverse> controls whether sort
|
||||
// in increasing order(default) or decreasing order
|
||||
func (a *SortedArray) Sort() *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -108,9 +94,7 @@ func (a *SortedArray) Sort() *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// And values to sorted array, the array always keeps sorted.
|
||||
//
|
||||
// 添加数据项.
|
||||
// Add adds one or multiple values to sorted array, the array always keeps sorted.
|
||||
func (a *SortedArray) Add(values...interface{}) *SortedArray {
|
||||
if len(values) == 0 {
|
||||
return a
|
||||
@ -126,7 +110,6 @@ func (a *SortedArray) Add(values...interface{}) *SortedArray {
|
||||
a.array = append(a.array, value)
|
||||
continue
|
||||
}
|
||||
// 加到指定索引后面
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
@ -137,9 +120,8 @@ func (a *SortedArray) Add(values...interface{}) *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Get value by index.
|
||||
//
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *SortedArray) Get(index int) interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -147,13 +129,11 @@ func (a *SortedArray) Get(index int) interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// Remove an item by index.
|
||||
//
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Remove removes an item by index.
|
||||
func (a *SortedArray) Remove(index int) interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
// Determine array boundaries when deleting to improve deletion efficiency.
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
@ -163,15 +143,15 @@ func (a *SortedArray) Remove(index int) interface{} {
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// Push new items to the beginning of array.
|
||||
//
|
||||
// 将数据项添加到数组的最左端(索引为0)。
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *SortedArray) PopLeft() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -180,9 +160,7 @@ func (a *SortedArray) PopLeft() interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// Push new items to the end of array.
|
||||
//
|
||||
// 将数据项添加到数组的最右端(索引为length - 1)。
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *SortedArray) PopRight() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -192,16 +170,12 @@ func (a *SortedArray) PopRight() interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRand picks an random item out of array.
|
||||
//
|
||||
// 随机将一个数据项移出数组,并返回该数据项。
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *SortedArray) PopRand() interface{} {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands picks <size> items out of array.
|
||||
//
|
||||
// 随机将size个数据项移出数组,并返回该数据项。
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *SortedArray) PopRands(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -217,9 +191,7 @@ func (a *SortedArray) PopRands(size int) []interface{} {
|
||||
return array
|
||||
}
|
||||
|
||||
// Pop <size> items from the beginning of array.
|
||||
//
|
||||
// 将最左端(首部)的size个数据项移出数组,并返回该数据项
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *SortedArray) PopLefts(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -232,9 +204,7 @@ func (a *SortedArray) PopLefts(size int) []interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// Pop <size> items from the end of array.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *SortedArray) PopRights(size int) []interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -247,11 +217,9 @@ func (a *SortedArray) PopRights(size int) []interface{} {
|
||||
return value
|
||||
}
|
||||
|
||||
// Get items by range, returns array[start:end].
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
func (a *SortedArray) Range(start, end int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -277,9 +245,7 @@ func (a *SortedArray) Range(start, end int) []interface{} {
|
||||
return array
|
||||
}
|
||||
|
||||
// Calculate the sum of values in an array.
|
||||
//
|
||||
// 对数组中的元素项求和(将元素值转换为int类型后叠加)。
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *SortedArray) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -289,9 +255,7 @@ func (a *SortedArray) Sum() (sum int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the length of array.
|
||||
//
|
||||
// 数组长度。
|
||||
// Len returns the length of array.
|
||||
func (a *SortedArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
@ -299,11 +263,9 @@ func (a *SortedArray) Len() int {
|
||||
return length
|
||||
}
|
||||
|
||||
// Get the underlying data of array.
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 返回原始数据数组.
|
||||
func (a *SortedArray) Slice() []interface{} {
|
||||
array := ([]interface{})(nil)
|
||||
if a.mu.IsSafe() {
|
||||
@ -317,25 +279,19 @@ func (a *SortedArray) Slice() []interface{} {
|
||||
return array
|
||||
}
|
||||
|
||||
// Check whether a value exists in the array.
|
||||
//
|
||||
// 查找指定数值是否存在。
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *SortedArray) Contains(value interface{}) bool {
|
||||
return a.Search(value) == 0
|
||||
}
|
||||
|
||||
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
|
||||
//
|
||||
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1。
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *SortedArray) Search(value interface{}) (index int) {
|
||||
index, _ = a.binSearch(value, true)
|
||||
return
|
||||
}
|
||||
|
||||
// Binary search.
|
||||
//
|
||||
// 二分查找。查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
|
||||
// 返回值: 最后比较位置, 比较结果。
|
||||
func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result int) {
|
||||
if len(a.array) == 0 {
|
||||
return -1, -2
|
||||
@ -361,11 +317,9 @@ func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result
|
||||
return mid, cmp
|
||||
}
|
||||
|
||||
// Set unique mark to the array,
|
||||
// SetUnique sets unique mark to the array,
|
||||
// which means it does not contain any repeated items.
|
||||
// It also do unique check, remove all repeated items.
|
||||
//
|
||||
// 设置是否允许数组唯一.
|
||||
func (a *SortedArray) SetUnique(unique bool) *SortedArray {
|
||||
oldUnique := a.unique.Val()
|
||||
a.unique.Set(unique)
|
||||
@ -375,9 +329,7 @@ func (a *SortedArray) SetUnique(unique bool) *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Do unique check, remove all repeated items.
|
||||
//
|
||||
// 清理数组中重复的元素项.
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *SortedArray) Unique() *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -395,9 +347,7 @@ func (a *SortedArray) Unique() *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Return a new array, which is a copy of current array.
|
||||
//
|
||||
// 克隆当前数组,返回当前数组的一个拷贝。
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *SortedArray) Clone() (newArray *SortedArray) {
|
||||
a.mu.RLock()
|
||||
array := make([]interface{}, len(a.array))
|
||||
@ -406,9 +356,7 @@ func (a *SortedArray) Clone() (newArray *SortedArray) {
|
||||
return NewSortedArrayFrom(array, a.compareFunc, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Clear array.
|
||||
//
|
||||
// 清空数据数组。
|
||||
// Clear deletes all items of current array.
|
||||
func (a *SortedArray) Clear() *SortedArray {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
@ -418,9 +366,7 @@ func (a *SortedArray) Clear() *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock writing by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁修改操作。
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -428,9 +374,7 @@ func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock reading by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁读取操作。
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -438,11 +382,10 @@ func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge two arrays. The parameter <array> can be any garray type or slice type.
|
||||
// The difference between Merge and Add is Add supports only specified slice type,
|
||||
// but Merge supports more variable types.
|
||||
//
|
||||
// 合并两个数组, 支持任意的garray数组类型及slice类型.
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *SortedArray) Merge(array interface{}) *SortedArray {
|
||||
switch v := array.(type) {
|
||||
case *Array: a.Add(gconv.Interfaces(v.Slice())...)
|
||||
@ -457,10 +400,9 @@ func (a *SortedArray) Merge(array interface{}) *SortedArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunks an array into arrays with size elements.
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
//
|
||||
// 将一个数组分割成多个数组,其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
|
||||
func (a *SortedArray) Chunk(size int) [][]interface{} {
|
||||
if size < 1 {
|
||||
return nil
|
||||
@ -481,12 +423,9 @@ func (a *SortedArray) Chunk(size int) [][]interface{} {
|
||||
return n
|
||||
}
|
||||
|
||||
// Extract a slice of the array(If in concurrent safe usage,
|
||||
// it returns a copy of the slice; else a pointer).
|
||||
// It returns the sequence of elements from the array array as specified
|
||||
// by the offset and length parameters.
|
||||
//
|
||||
// 返回根据offset和size参数所指定的数组中的一段序列。
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
func (a *SortedArray) SubSlice(offset, size int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -505,18 +444,14 @@ func (a *SortedArray) SubSlice(offset, size int) []interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
// Rand gets one random entry from array.
|
||||
//
|
||||
// 从数组中随机获得1个元素项(不删除)。
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *SortedArray) Rand() interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands gets one or more random entries from array(a copy).
|
||||
//
|
||||
// 从数组中随机拷贝size个元素项,构成slice返回。
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *SortedArray) Rands(size int) []interface{} {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -533,9 +468,7 @@ func (a *SortedArray) Rands(size int) []interface{} {
|
||||
return n
|
||||
}
|
||||
|
||||
// Join array elements with a string.
|
||||
//
|
||||
// 使用glue字符串串连当前数组的元素项,构造成新的字符串返回。
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *SortedArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -547,4 +480,22 @@ func (a *SortedArray) Join(glue string) string {
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *SortedArray) CountValues() map[interface{}]int {
|
||||
m := make(map[interface{}]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *SortedArray) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
@ -8,7 +8,8 @@ package garray
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
@ -17,28 +18,24 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 默认按照从小到大进行排序
|
||||
// It's using increasing order in default.
|
||||
type SortedStringArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
array []string // 底层数组
|
||||
unique *gtype.Bool // 是否要求不能重复
|
||||
compareFunc func(v1, v2 string) int // 比较函数,返回值 -1: v1 < v2;0: v1 == v2;1: v1 > v2
|
||||
mu *rwmutex.RWMutex
|
||||
array []string
|
||||
unique *gtype.Bool // Whether enable unique feature(false)
|
||||
compareFunc func(v1, v2 string) int // Comparison function(it returns -1: v1 < v2; 0: v1 == v2; 1: v1 > v2)
|
||||
}
|
||||
|
||||
// Create an empty sorted array.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个空的排序数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// NewSortedStringArray creates and returns an empty sorted array.
|
||||
// The param <unsafe> used to specify whether using array in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewSortedStringArray(unsafe...bool) *SortedStringArray {
|
||||
return NewSortedStringArraySize(0, unsafe...)
|
||||
}
|
||||
|
||||
// Create a sorted array with given size and cap.
|
||||
// NewSortedStringArraySize create and returns an sorted array with given size and cap.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个指定大小的排序数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewSortedStringArraySize(cap int, unsafe...bool) *SortedStringArray {
|
||||
return &SortedStringArray {
|
||||
mu : rwmutex.New(unsafe...),
|
||||
@ -50,11 +47,9 @@ func NewSortedStringArraySize(cap int, unsafe...bool) *SortedStringArray {
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array with given slice <array>.
|
||||
// NewSortedStringArrayFrom creates and returns an sorted array with given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice变量创建排序数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewSortedStringArrayFrom(array []string, unsafe...bool) *SortedStringArray {
|
||||
a := NewSortedStringArraySize(0, unsafe...)
|
||||
a.array = array
|
||||
@ -62,11 +57,9 @@ func NewSortedStringArrayFrom(array []string, unsafe...bool) *SortedStringArray
|
||||
return a
|
||||
}
|
||||
|
||||
// Create an array from a copy of given slice <array>.
|
||||
// NewSortedStringArrayFromCopy creates and returns an sorted array from a copy of given slice <array>.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 通过给定的slice拷贝创建数组对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// which is false in default.
|
||||
func NewSortedStringArrayFromCopy(array []string, unsafe...bool) *SortedStringArray {
|
||||
newArray := make([]string, len(array))
|
||||
copy(newArray, array)
|
||||
@ -76,9 +69,7 @@ func NewSortedStringArrayFromCopy(array []string, unsafe...bool) *SortedStringAr
|
||||
}
|
||||
}
|
||||
|
||||
// Set the underlying slice array with the given <array> param.
|
||||
//
|
||||
// 设置底层数组变量.
|
||||
// SetArray sets the underlying slice array with the given <array>.
|
||||
func (a *SortedStringArray) SetArray(array []string) *SortedStringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -87,9 +78,9 @@ func (a *SortedStringArray) SetArray(array []string) *SortedStringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Sort the array in increasing order.
|
||||
//
|
||||
// 将数组排序(默认从低到高).
|
||||
// Sort sorts the array in increasing order.
|
||||
// The param <reverse> controls whether sort
|
||||
// in increasing order(default) or decreasing order.
|
||||
func (a *SortedStringArray) Sort() *SortedStringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -97,9 +88,7 @@ func (a *SortedStringArray) Sort() *SortedStringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// And values to sorted array, the array always keeps sorted.
|
||||
//
|
||||
// 添加数据项.
|
||||
// Add adds one or multiple values to sorted array, the array always keeps sorted.
|
||||
func (a *SortedStringArray) Add(values...string) *SortedStringArray {
|
||||
if len(values) == 0 {
|
||||
return a
|
||||
@ -115,7 +104,6 @@ func (a *SortedStringArray) Add(values...string) *SortedStringArray {
|
||||
a.array = append(a.array, value)
|
||||
continue
|
||||
}
|
||||
// 加到指定索引后面
|
||||
if cmp > 0 {
|
||||
index++
|
||||
}
|
||||
@ -126,9 +114,8 @@ func (a *SortedStringArray) Add(values...string) *SortedStringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Get value by index.
|
||||
//
|
||||
// 获取指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Get returns the value of the specified index,
|
||||
// the caller should notice the boundary of the array.
|
||||
func (a *SortedStringArray) Get(index int) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -136,13 +123,11 @@ func (a *SortedStringArray) Get(index int) string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Remove an item by index.
|
||||
//
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界。
|
||||
// Remove removes an item by index.
|
||||
func (a *SortedStringArray) Remove(index int) string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
// Determine array boundaries when deleting to improve deletion efficiency.
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
@ -152,15 +137,15 @@ func (a *SortedStringArray) Remove(index int) string {
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
// If it is a non-boundary delete,
|
||||
// it will involve the creation of an array,
|
||||
// then the deletion is less efficient.
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// Push new items to the beginning of array.
|
||||
//
|
||||
// 将数据项添加到数组的最左端(索引为0)。
|
||||
// PopLeft pops and returns an item from the beginning of array.
|
||||
func (a *SortedStringArray) PopLeft() string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -169,9 +154,7 @@ func (a *SortedStringArray) PopLeft() string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Push new items to the end of array.
|
||||
//
|
||||
// 将数据项添加到数组的最右端(索引为length - 1)。
|
||||
// PopRight pops and returns an item from the end of array.
|
||||
func (a *SortedStringArray) PopRight() string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -181,16 +164,12 @@ func (a *SortedStringArray) PopRight() string {
|
||||
return value
|
||||
}
|
||||
|
||||
// PopRand picks an random item out of array.
|
||||
//
|
||||
// 随机将一个数据项移出数组,并返回该数据项。
|
||||
// PopRand randomly pops and return an item out of array.
|
||||
func (a *SortedStringArray) PopRand() string {
|
||||
return a.Remove(grand.Intn(len(a.array)))
|
||||
}
|
||||
|
||||
// PopRands picks <size> items out of array.
|
||||
//
|
||||
// 随机将size个数据项移出数组,并返回该数据项。
|
||||
// PopRands randomly pops and returns <size> items out of array.
|
||||
func (a *SortedStringArray) PopRands(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -206,9 +185,7 @@ func (a *SortedStringArray) PopRands(size int) []string {
|
||||
return array
|
||||
}
|
||||
|
||||
// Pop <size> items from the beginning of array.
|
||||
//
|
||||
// 将最左端(首部)的size个数据项移出数组,并返回该数据项
|
||||
// PopLefts pops and returns <size> items from the beginning of array.
|
||||
func (a *SortedStringArray) PopLefts(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -221,9 +198,7 @@ func (a *SortedStringArray) PopLefts(size int) []string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Pop <size> items from the end of array.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
// PopRights pops and returns <size> items from the end of array.
|
||||
func (a *SortedStringArray) PopRights(size int) []string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -236,11 +211,9 @@ func (a *SortedStringArray) PopRights(size int) []string {
|
||||
return value
|
||||
}
|
||||
|
||||
// Get items by range, returns array[start:end].
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Range picks and returns items by range, like array[start:end].
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 将最右端(尾部)的size个数据项移出数组,并返回该数据项
|
||||
func (a *SortedStringArray) Range(start, end int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -266,9 +239,7 @@ func (a *SortedStringArray) Range(start, end int) []string {
|
||||
return array
|
||||
}
|
||||
|
||||
// Calculate the sum of values in an array.
|
||||
//
|
||||
// 对数组中的元素项求和(将元素值转换为int类型后叠加)。
|
||||
// Sum returns the sum of values in an array.
|
||||
func (a *SortedStringArray) Sum() (sum int) {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -278,9 +249,7 @@ func (a *SortedStringArray) Sum() (sum int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the length of array.
|
||||
//
|
||||
// 数组长度。
|
||||
// Len returns the length of array.
|
||||
func (a *SortedStringArray) Len() int {
|
||||
a.mu.RLock()
|
||||
length := len(a.array)
|
||||
@ -288,11 +257,9 @@ func (a *SortedStringArray) Len() int {
|
||||
return length
|
||||
}
|
||||
|
||||
// Get the underlying data of array.
|
||||
// Be aware that, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// Slice returns the underlying data of array.
|
||||
// Notice, if in concurrent-safe usage, it returns a copy of slice;
|
||||
// else a pointer to the underlying data.
|
||||
//
|
||||
// 返回原始数据数组.
|
||||
func (a *SortedStringArray) Slice() []string {
|
||||
array := ([]string)(nil)
|
||||
if a.mu.IsSafe() {
|
||||
@ -306,24 +273,19 @@ func (a *SortedStringArray) Slice() []string {
|
||||
return array
|
||||
}
|
||||
|
||||
// Check whether a value exists in the array.
|
||||
//
|
||||
// 查找指定数值是否存在。
|
||||
// Contains checks whether a value exists in the array.
|
||||
func (a *SortedStringArray) Contains(value string) bool {
|
||||
return a.Search(value) == 0
|
||||
}
|
||||
|
||||
// Search array by <value>, returns the index of <value>, returns -1 if not exists.
|
||||
//
|
||||
// 查找指定数值的索引位置,返回索引位置,如果查找不到则返回-1。
|
||||
// Search searches array by <value>, returns the index of <value>,
|
||||
// or returns -1 if not exists.
|
||||
func (a *SortedStringArray) Search(value string) (index int) {
|
||||
index, _ = a.binSearch(value, true)
|
||||
return
|
||||
}
|
||||
|
||||
// Binary search.
|
||||
//
|
||||
// 二分查找.
|
||||
func (a *SortedStringArray) binSearch(value string, lock bool) (index int, result int) {
|
||||
if len(a.array) == 0 {
|
||||
return -1, -2
|
||||
@ -349,11 +311,9 @@ func (a *SortedStringArray) binSearch(value string, lock bool) (index int, resul
|
||||
return mid, cmp
|
||||
}
|
||||
|
||||
// Set unique mark to the array,
|
||||
// SetUnique sets unique mark to the array,
|
||||
// which means it does not contain any repeated items.
|
||||
// It also do unique check, remove all repeated items.
|
||||
//
|
||||
// 设置是否允许数组唯一.
|
||||
func (a *SortedStringArray) SetUnique(unique bool) *SortedStringArray {
|
||||
oldUnique := a.unique.Val()
|
||||
a.unique.Set(unique)
|
||||
@ -363,9 +323,7 @@ func (a *SortedStringArray) SetUnique(unique bool) *SortedStringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Do unique check, remove all repeated items.
|
||||
//
|
||||
// 清理数组中重复的元素项.
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *SortedStringArray) Unique() *SortedStringArray {
|
||||
a.mu.Lock()
|
||||
i := 0
|
||||
@ -383,9 +341,7 @@ func (a *SortedStringArray) Unique() *SortedStringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Return a new array, which is a copy of current array.
|
||||
//
|
||||
// 克隆当前数组,返回当前数组的一个拷贝。
|
||||
// Clone returns a new array, which is a copy of current array.
|
||||
func (a *SortedStringArray) Clone() (newArray *SortedStringArray) {
|
||||
a.mu.RLock()
|
||||
array := make([]string, len(a.array))
|
||||
@ -394,9 +350,7 @@ func (a *SortedStringArray) Clone() (newArray *SortedStringArray) {
|
||||
return NewSortedStringArrayFrom(array, !a.mu.IsSafe())
|
||||
}
|
||||
|
||||
// Clear array.
|
||||
//
|
||||
// 清空数据数组。
|
||||
// Clear deletes all items of current array.
|
||||
func (a *SortedStringArray) Clear() *SortedStringArray {
|
||||
a.mu.Lock()
|
||||
if len(a.array) > 0 {
|
||||
@ -406,9 +360,7 @@ func (a *SortedStringArray) Clear() *SortedStringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock writing by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁修改操作。
|
||||
// LockFunc locks writing by callback function <f>.
|
||||
func (a *SortedStringArray) LockFunc(f func(array []string)) *SortedStringArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
@ -416,9 +368,7 @@ func (a *SortedStringArray) LockFunc(f func(array []string)) *SortedStringArray
|
||||
return a
|
||||
}
|
||||
|
||||
// Lock reading by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁读取操作。
|
||||
// RLockFunc locks reading by callback function <f>.
|
||||
func (a *SortedStringArray) RLockFunc(f func(array []string)) *SortedStringArray {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -426,11 +376,10 @@ func (a *SortedStringArray) RLockFunc(f func(array []string)) *SortedStringArray
|
||||
return a
|
||||
}
|
||||
|
||||
// Merge two arrays. The parameter <array> can be any garray type or slice type.
|
||||
// The difference between Merge and Add is Add supports only specified slice type,
|
||||
// but Merge supports more variable types.
|
||||
//
|
||||
// 合并两个数组, 支持任意的garray数组类型及slice类型.
|
||||
// Merge merges <array> into current array.
|
||||
// The parameter <array> can be any garray or slice type.
|
||||
// The difference between Merge and Append is Append supports only specified slice type,
|
||||
// but Merge supports more parameter types.
|
||||
func (a *SortedStringArray) Merge(array interface{}) *SortedStringArray {
|
||||
switch v := array.(type) {
|
||||
case *Array: a.Add(gconv.Strings(v.Slice())...)
|
||||
@ -445,10 +394,9 @@ func (a *SortedStringArray) Merge(array interface{}) *SortedStringArray {
|
||||
return a
|
||||
}
|
||||
|
||||
// Chunks an array into arrays with size elements.
|
||||
// Chunk splits an array into multiple arrays,
|
||||
// the size of each array is determined by <size>.
|
||||
// The last chunk may contain less than size elements.
|
||||
//
|
||||
// 将一个数组分割成多个数组,其中每个数组的单元数目由size决定。最后一个数组的单元数目可能会少于size个。
|
||||
func (a *SortedStringArray) Chunk(size int) [][]string {
|
||||
if size < 1 {
|
||||
return nil
|
||||
@ -469,12 +417,9 @@ func (a *SortedStringArray) Chunk(size int) [][]string {
|
||||
return n
|
||||
}
|
||||
|
||||
// Extract a slice of the array(If in concurrent safe usage,
|
||||
// it returns a copy of the slice; else a pointer).
|
||||
// It returns the sequence of elements from the array array as specified
|
||||
// by the offset and length parameters.
|
||||
//
|
||||
// 返回根据offset和size参数所指定的数组中的一段序列。
|
||||
// SubSlice returns a slice of elements from the array as specified
|
||||
// by the <offset> and <size> parameters.
|
||||
// If in concurrent safe usage, it returns a copy of the slice; else a pointer.
|
||||
func (a *SortedStringArray) SubSlice(offset, size int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -493,18 +438,14 @@ func (a *SortedStringArray) SubSlice(offset, size int) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// Rand gets one random entry from array.
|
||||
//
|
||||
// 从数组中随机获得1个元素项(不删除)。
|
||||
// Rand randomly returns one item from array(no deleting).
|
||||
func (a *SortedStringArray) Rand() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return a.array[grand.Intn(len(a.array))]
|
||||
}
|
||||
|
||||
// Rands gets one or more random entries from array(a copy).
|
||||
//
|
||||
// 从数组中随机拷贝size个元素项,构成slice返回。
|
||||
// Rands randomly returns <size> items from array(no deleting).
|
||||
func (a *SortedStringArray) Rands(size int) []string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -521,9 +462,7 @@ func (a *SortedStringArray) Rands(size int) []string {
|
||||
return n
|
||||
}
|
||||
|
||||
// Join array elements with a string.
|
||||
//
|
||||
// 使用glue字符串串连当前数组的元素项,构造成新的字符串返回。
|
||||
// Join joins array elements with a string <glue>.
|
||||
func (a *SortedStringArray) Join(glue string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
@ -535,4 +474,22 @@ func (a *SortedStringArray) Join(glue string) string {
|
||||
}
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// CountValues counts the number of occurrences of all values in the array.
|
||||
func (a *SortedStringArray) CountValues() map[string]int {
|
||||
m := make(map[string]int)
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
for _, v := range a.array {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// String returns current array as a string.
|
||||
func (a *SortedStringArray) String() string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
return fmt.Sprint(a.array)
|
||||
}
|
||||
@ -289,6 +289,10 @@ func (l *List) RemoveAll() {
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
func (l *List) Clear() {
|
||||
l.RemoveAll()
|
||||
}
|
||||
|
||||
// 读锁操作
|
||||
func (l *List) RLockFunc(f func(list *list.List)) {
|
||||
l.mu.RLock()
|
||||
|
||||
@ -51,18 +51,54 @@ func Test_IntBoolMap_Set_Fun(t *testing.T) {
|
||||
gtest.Assert(m.Get(1), true)
|
||||
gtest.Assert(m.Get(2), true)
|
||||
gtest.Assert(m.SetIfNotExistFunc(1, getBool), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc(4, getBool), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(2, getBool), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(3, getBool), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_IntBoolMap_Batch(t *testing.T) {
|
||||
m := gmap.NewIntBoolMap()
|
||||
|
||||
m.BatchSet(map[int]bool{1: true, 2: false, 3: true})
|
||||
m.Iterator(intBoolCallBack)
|
||||
gtest.Assert(m.Map(), map[int]bool{1: true, 2: false, 3: true})
|
||||
m.BatchRemove([]int{1, 2})
|
||||
gtest.Assert(m.Map(), map[int]bool{3: true})
|
||||
}
|
||||
func Test_IntBoolMap_Iterator(t *testing.T){
|
||||
expect := map[int]bool{1: true, 2: false}
|
||||
m := gmap.NewIntBoolMapFrom(expect)
|
||||
m.Iterator(func(k int, v bool) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k int, v bool) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k int, v bool) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_IntBoolMap_Lock(t *testing.T){
|
||||
expect := map[int]bool{1: true, 2: false}
|
||||
m := gmap.NewIntBoolMapFrom(expect)
|
||||
m.LockFunc(func(m map[int]bool) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[int]bool) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func Test_IntBoolMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
|
||||
@ -55,7 +55,11 @@ func Test_IntIntMap_Set_Fun(t *testing.T) {
|
||||
gtest.Assert(m.Get(1), 123)
|
||||
gtest.Assert(m.Get(2), 123)
|
||||
gtest.Assert(m.SetIfNotExistFunc(1, getInt), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc(3, getInt), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(2, getInt), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(4, getInt), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_IntIntMap_Batch(t *testing.T) {
|
||||
@ -68,6 +72,39 @@ func Test_IntIntMap_Batch(t *testing.T) {
|
||||
gtest.Assert(m.Map(), map[int]int{3: 3})
|
||||
}
|
||||
|
||||
func Test_IntIntMap_Iterator(t *testing.T){
|
||||
expect := map[int]int{1: 1, 2: 2}
|
||||
m := gmap.NewIntIntMapFrom(expect)
|
||||
m.Iterator(func(k int, v int) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k int, v int) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k int, v int) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_IntIntMap_Lock(t *testing.T){
|
||||
expect := map[int]int{1: 1, 2: 2}
|
||||
m := gmap.NewIntIntMapFrom(expect)
|
||||
m.LockFunc(func(m map[int]int) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[int]int) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
|
||||
}
|
||||
func Test_IntIntMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewIntIntMapFrom(map[int]int{1: 1, 2: 2})
|
||||
|
||||
@ -54,20 +54,57 @@ func Test_IntInterfaceMap_Set_Fun(t *testing.T) {
|
||||
m.GetOrSetFuncLock(2, getInterface)
|
||||
gtest.Assert(m.Get(1), 123)
|
||||
gtest.Assert(m.Get(2), 123)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFunc(1, getInterface), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc(3, getInterface), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(2, getInterface), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(4, getInterface), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_IntInterfaceMap_Batch(t *testing.T) {
|
||||
m := gmap.NewIntInterfaceMap()
|
||||
|
||||
m.BatchSet(map[int]interface{}{1: 1, 2: "2", 3: 3})
|
||||
m.Iterator(intInterfaceCallBack)
|
||||
gtest.Assert(m.Map(), map[int]interface{}{1: 1, 2: "2", 3: 3})
|
||||
m.BatchRemove([]int{1, 2})
|
||||
gtest.Assert(m.Map(), map[int]interface{}{3: 3})
|
||||
}
|
||||
func Test_IntInterfaceMap_Iterator(t *testing.T){
|
||||
expect := map[int]interface{}{1: 1, 2: "2"}
|
||||
m := gmap.NewIntInterfaceMapFrom(expect)
|
||||
m.Iterator(func(k int, v interface{}) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k int, v interface{}) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k int, v interface{}) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, "2")
|
||||
gtest.Assert(j, 1)
|
||||
|
||||
|
||||
}
|
||||
|
||||
func Test_IntInterfaceMap_Lock(t *testing.T){
|
||||
expect := map[int]interface{}{1: 1, 2: "2"}
|
||||
m := gmap.NewIntInterfaceMapFrom(expect)
|
||||
m.LockFunc(func(m map[int]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[int]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
func Test_IntInterfaceMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewIntInterfaceMapFrom(map[int]interface{}{1: 1, 2: "2"})
|
||||
|
||||
@ -60,19 +60,55 @@ func Test_IntStringMap_Set_Fun(t *testing.T) {
|
||||
gtest.Assert(m.Get(1), "z")
|
||||
gtest.Assert(m.Get(2), "z")
|
||||
gtest.Assert(m.SetIfNotExistFunc(1, getString), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc(3, getString), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(2, getString), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock(4, getString), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_IntStringMap_Batch(t *testing.T) {
|
||||
m := gmap.NewIntStringMap()
|
||||
|
||||
m.BatchSet(map[int]string{1: "a", 2: "b", 3: "c"})
|
||||
m.Iterator(intStringCallBack)
|
||||
gtest.Assert(m.Map(), map[int]string{1: "a", 2: "b",3: "c"})
|
||||
m.BatchRemove([]int{1, 2})
|
||||
gtest.Assert(m.Map(), map[int]interface{}{3: "c"})
|
||||
}
|
||||
func Test_IntStringMap_Iterator(t *testing.T){
|
||||
expect := map[int]string{1: "a", 2: "b"}
|
||||
m := gmap.NewIntStringMapFrom(expect)
|
||||
m.Iterator(func(k int, v string) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k int, v string) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k int, v string) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_IntStringMap_Lock(t *testing.T){
|
||||
|
||||
expect := map[int]string{1: "a", 2: "b", 3: "c"}
|
||||
m := gmap.NewIntStringMapFrom(expect)
|
||||
m.LockFunc(func(m map[int]string) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[int]string) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
|
||||
}
|
||||
func Test_IntStringMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewIntStringMapFrom(map[int]string{1: "a", 2: "b", 3: "c"})
|
||||
|
||||
@ -12,6 +12,7 @@ func getValue() interface{} {
|
||||
func callBack(k interface{}, v interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func Test_Map_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gmap.New()
|
||||
@ -63,11 +64,44 @@ func Test_Map_Set_Fun(t *testing.T) {
|
||||
func Test_Map_Batch(t *testing.T) {
|
||||
m := gmap.New()
|
||||
m.BatchSet(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
m.Iterator(callBack)
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
|
||||
m.BatchRemove([]interface{}{"key1", 1})
|
||||
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
|
||||
}
|
||||
func Test_Map_Iterator(t *testing.T){
|
||||
expect :=map[interface{}]interface{}{1: 1, "key1": "val1"}
|
||||
|
||||
m := gmap.NewFrom(expect)
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k interface{}, v interface{}) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_Map_Lock(t *testing.T){
|
||||
expect :=map[interface{}]interface{}{1: 1, "key1": "val1"}
|
||||
|
||||
m := gmap.NewFrom(expect)
|
||||
m.LockFunc(func(m map[interface{}]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[interface{}]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Map_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
|
||||
@ -6,8 +6,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func StringBoolCallBack( string, bool) bool {
|
||||
func StringBoolCallBack(string, bool) bool {
|
||||
return true
|
||||
}
|
||||
func Test_StringBoolMap_Basic(t *testing.T) {
|
||||
@ -49,19 +48,57 @@ func Test_StringBoolMap_Set_Fun(t *testing.T) {
|
||||
gtest.Assert(m.Get("a"), true)
|
||||
gtest.Assert(m.Get("b"), true)
|
||||
gtest.Assert(m.SetIfNotExistFunc("a", getBool), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc("c", getBool), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("b", getBool), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("d", getBool), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_StringBoolMap_Batch(t *testing.T) {
|
||||
m := gmap.NewStringBoolMap()
|
||||
|
||||
m.BatchSet(map[string]bool{"a": true, "b": false, "c": true})
|
||||
m.Iterator(StringBoolCallBack)
|
||||
gtest.Assert(m.Map(), map[string]bool{"a": true, "b": false, "c": true})
|
||||
m.BatchRemove([]string{"a", "b"})
|
||||
gtest.Assert(m.Map(), map[string]bool{"c": true})
|
||||
}
|
||||
|
||||
func Test_StringBoolMap_Iterator(t *testing.T) {
|
||||
expect := map[string]bool{"a": true, "b": false}
|
||||
m := gmap.NewStringBoolMapFrom(expect)
|
||||
m.Iterator(func(k string, v bool) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k string, v bool) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k string, v bool) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
|
||||
}
|
||||
|
||||
func Test_StringBoolMap_Lock(t *testing.T) {
|
||||
expect := map[string]bool{"a": true, "b": false}
|
||||
|
||||
m := gmap.NewStringBoolMapFrom(expect)
|
||||
m.LockFunc(func(m map[string]bool) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[string]bool) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StringBoolMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewStringBoolMapFrom(map[string]bool{"a": true, "b": false})
|
||||
|
||||
@ -55,18 +55,55 @@ func Test_StringIntMap_Set_Fun(t *testing.T) {
|
||||
gtest.Assert(m.Get("a"), 123)
|
||||
gtest.Assert(m.Get("b"), 123)
|
||||
gtest.Assert(m.SetIfNotExistFunc("a", getInt), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc("c", getInt), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("b", getInt), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("d", getInt), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_StringIntMap_Batch(t *testing.T) {
|
||||
m := gmap.NewStringIntMap()
|
||||
|
||||
m.BatchSet(map[string]int{"a": 1, "b": 2, "c": 3})
|
||||
m.Iterator(stringIntCallBack)
|
||||
gtest.Assert(m.Map(), map[string]int{"a": 1, "b": 2, "c": 3})
|
||||
m.BatchRemove([]string{"a", "b"})
|
||||
gtest.Assert(m.Map(), map[string]int{"c": 3})
|
||||
}
|
||||
func Test_StringIntMap_Iterator(t *testing.T) {
|
||||
expect := map[string]int{"a": 1, "b": 2}
|
||||
m := gmap.NewStringIntMapFrom(expect)
|
||||
m.Iterator(func(k string, v int) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k string, v int) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k string, v int) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
|
||||
}
|
||||
|
||||
func Test_StringIntMap_Lock(t *testing.T) {
|
||||
expect := map[string]int{"a": 1, "b": 2}
|
||||
|
||||
m := gmap.NewStringIntMapFrom(expect)
|
||||
m.LockFunc(func(m map[string]int) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[string]int) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StringIntMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
|
||||
@ -53,19 +53,55 @@ func Test_StringInterfaceMap_Set_Fun(t *testing.T) {
|
||||
gtest.Assert(m.Get("a"), 123)
|
||||
gtest.Assert(m.Get("b"), 123)
|
||||
gtest.Assert(m.SetIfNotExistFunc("a", getInterface), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc("c", getInterface), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("b", getInterface), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("d", getInterface), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_StringInterfaceMap_Batch(t *testing.T) {
|
||||
m := gmap.NewStringInterfaceMap()
|
||||
|
||||
m.BatchSet(map[string]interface{}{"a": 1, "b": "2", "c": 3})
|
||||
m.Iterator(stringInterfaceCallBack)
|
||||
gtest.Assert(m.Map(), map[string]interface{}{"a": 1, "b": "2", "c": 3})
|
||||
m.BatchRemove([]string{"a", "b"})
|
||||
gtest.Assert(m.Map(), map[string]interface{}{"c": 3})
|
||||
}
|
||||
|
||||
func Test_StringInterfaceMap_Iterator(t *testing.T) {
|
||||
expect := map[string]interface{}{"a": true, "b": false}
|
||||
m := gmap.NewStringInterfaceMapFrom(expect)
|
||||
m.Iterator(func(k string, v interface{}) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k string, v interface{}) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k string, v interface{}) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_StringInterfaceMap_Lock(t *testing.T) {
|
||||
expect := map[string]interface{}{"a": true, "b": false}
|
||||
|
||||
m := gmap.NewStringInterfaceMapFrom(expect)
|
||||
m.LockFunc(func(m map[string]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[string]interface{}) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
func Test_StringInterfaceMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewStringInterfaceMapFrom(map[string]interface{}{"a": 1, "b": "2"})
|
||||
|
||||
@ -54,19 +54,54 @@ func Test_StringStringMap_Set_Fun(t *testing.T) {
|
||||
gtest.Assert(m.Get("a"), "z")
|
||||
gtest.Assert(m.Get("b"), "z")
|
||||
gtest.Assert(m.SetIfNotExistFunc("a", getString), false)
|
||||
gtest.Assert(m.SetIfNotExistFunc("c", getString), true)
|
||||
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("b", getString), false)
|
||||
gtest.Assert(m.SetIfNotExistFuncLock("d", getString), true)
|
||||
|
||||
}
|
||||
|
||||
func Test_StringStringMap_Batch(t *testing.T) {
|
||||
m := gmap.NewStringStringMap()
|
||||
|
||||
m.BatchSet(map[string]string{"a": "a", "b": "b", "c": "c"})
|
||||
m.Iterator(stringStringCallBack)
|
||||
gtest.Assert(m.Map(), map[string]string{"a": "a", "b": "b", "c": "c"})
|
||||
m.BatchRemove([]string{"a", "b"})
|
||||
gtest.Assert(m.Map(), map[string]string{"c": "c"})
|
||||
}
|
||||
func Test_StringStringMap_Iterator(t *testing.T) {
|
||||
expect := map[string]string{"a": "a", "b": "b"}
|
||||
m := gmap.NewStringStringMapFrom(expect)
|
||||
m.Iterator(func(k string, v string) bool {
|
||||
gtest.Assert(expect[k], v)
|
||||
return true
|
||||
})
|
||||
// 断言返回值对遍历控制
|
||||
i := 0
|
||||
j := 0
|
||||
m.Iterator(func(k string, v string) bool {
|
||||
i++
|
||||
return true
|
||||
})
|
||||
m.Iterator(func(k string, v string) bool {
|
||||
j++
|
||||
return false
|
||||
})
|
||||
gtest.Assert(i, 2)
|
||||
gtest.Assert(j, 1)
|
||||
}
|
||||
|
||||
func Test_StringStringMap_Lock(t *testing.T) {
|
||||
expect := map[string]string{"a": "a", "b": "b"}
|
||||
|
||||
m := gmap.NewStringStringMapFrom(expect)
|
||||
m.LockFunc(func(m map[string]string) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
m.RLockFunc(func(m map[string]string) {
|
||||
gtest.Assert(m, expect)
|
||||
})
|
||||
}
|
||||
func Test_StringStringMap_Clone(t *testing.T) {
|
||||
//clone 方法是深克隆
|
||||
m := gmap.NewStringStringMapFrom(map[string]string{"a": "a", "b": "b", "c": "c"})
|
||||
|
||||
@ -4,14 +4,18 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gqueue provides a dynamic/static concurrent-safe(alternative) queue.
|
||||
// Package gqueue provides a dynamic/static concurrent-safe queue.
|
||||
//
|
||||
// 并发安全动态队列.
|
||||
// Features:
|
||||
//
|
||||
// 1. FIFO queue(data -> list -> chan);
|
||||
//
|
||||
// 2. Fast creation and initialization;
|
||||
//
|
||||
// 3. Support dynamic queue size(unlimited queue size);
|
||||
//
|
||||
// 4. Blocking when reading data from queue;
|
||||
//
|
||||
// 特点:
|
||||
// 1. 动态队列初始化速度快;
|
||||
// 2. 动态的队列大小(不限大小);
|
||||
// 3. 取数据时如果队列为空那么会阻塞等待;
|
||||
package gqueue
|
||||
|
||||
import (
|
||||
@ -19,27 +23,21 @@ import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// 1、这是一个先进先出的队列(chan <-- list);
|
||||
//
|
||||
// 2、当创建Queue对象时限定大小,那么等同于一个同步的chan并发安全队列;
|
||||
//
|
||||
// 3、不限制大小时,list链表用以存储数据,临时chan负责为客户端读取数据,当从chan获取数据时,list往chan中不停补充数据;
|
||||
//
|
||||
// 4、由于功能主体是chan,那么操作仍然像chan那样具有阻塞效果;
|
||||
type Queue struct {
|
||||
limit int // 队列限制大小
|
||||
list *glist.List // 底层数据链表
|
||||
events chan struct{} // 写入事件通知
|
||||
closed chan struct{} // 队列关闭通知
|
||||
C chan interface{} // 队列数据读取
|
||||
limit int // Limit for queue size.
|
||||
list *glist.List // Underlying list structure for data maintaining.
|
||||
events chan struct{} // Events for data writing.
|
||||
closed chan struct{} // Events for queue closing.
|
||||
C chan interface{} // Underlying channel for data reading.
|
||||
}
|
||||
|
||||
const (
|
||||
// 动态队列缓冲区大小
|
||||
// Size for queue buffer.
|
||||
gDEFAULT_QUEUE_SIZE = 10000
|
||||
)
|
||||
|
||||
// 队列大小为非必须参数,默认不限制
|
||||
// New returns a queue object.
|
||||
// Param <limit> is optional and it is not limited by default.
|
||||
func New(limit...int) *Queue {
|
||||
q := &Queue {
|
||||
closed : make(chan struct{}, 0),
|
||||
@ -56,7 +54,8 @@ func New(limit...int) *Queue {
|
||||
return q
|
||||
}
|
||||
|
||||
// 异步list->chan同步队列
|
||||
// startAsyncLoop starts an asynchronous goroutine,
|
||||
// which handles the data synchronization from list <q.list> to channel <q.C>.
|
||||
func (q *Queue) startAsyncLoop() {
|
||||
for {
|
||||
select {
|
||||
@ -84,7 +83,8 @@ func (q *Queue) startAsyncLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
// 将数据压入队列, 队尾
|
||||
// Push pushes the data <v> into the queue.
|
||||
// Note that it would panics if the Push method is called after the queue is closed.
|
||||
func (q *Queue) Push(v interface{}) {
|
||||
if q.limit > 0 {
|
||||
q.C <- v
|
||||
@ -94,19 +94,22 @@ func (q *Queue) Push(v interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// 从队头先进先出地从队列取出一项数据
|
||||
// Pop pops an item from the queue in FIFO way.
|
||||
// Note that it would return nil immediately if the Pop method is called after the queue is closed.
|
||||
func (q *Queue) Pop() interface{} {
|
||||
return <- q.C
|
||||
}
|
||||
|
||||
// 关闭队列(通知所有通过Pop*阻塞的协程退出)
|
||||
// Close closes the queue.
|
||||
// Notice: It would notify all goroutines exit immediately,
|
||||
// which are blocked reading by Pop method).
|
||||
func (q *Queue) Close() {
|
||||
close(q.C)
|
||||
close(q.events)
|
||||
close(q.closed)
|
||||
}
|
||||
|
||||
// 获取当前队列大小
|
||||
// Size returns the length of the queue.
|
||||
func (q *Queue) Size() int {
|
||||
return len(q.C) + q.list.Len()
|
||||
}
|
||||
|
||||
@ -18,18 +18,14 @@ type Set struct {
|
||||
m map[interface{}]struct{}
|
||||
}
|
||||
|
||||
// Create a set, which contains un-repeated items.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个空的集合对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// New create and returns a new set, which contains un-repeated items.
|
||||
// The param <unsafe> used to specify whether using set in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func New(unsafe...bool) *Set {
|
||||
return NewSet(unsafe...)
|
||||
}
|
||||
|
||||
// See New.
|
||||
//
|
||||
// 同New.
|
||||
func NewSet(unsafe...bool) *Set {
|
||||
return &Set{
|
||||
m : make(map[interface{}]struct{}),
|
||||
@ -37,10 +33,21 @@ func NewSet(unsafe...bool) *Set {
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate the set by given callback <f>,
|
||||
// NewFrom returns a new set from <items>.
|
||||
// Parameter <items> can be either a variable of any type, or a slice.
|
||||
func NewFrom(items interface{}, unsafe...bool) *Set {
|
||||
m := make(map[interface{}]struct{})
|
||||
for _, v := range gconv.Interfaces(items) {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return &Set{
|
||||
m : m,
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the set with given callback function <f>,
|
||||
// if <f> returns true then continue iterating; or false to stop.
|
||||
//
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历。
|
||||
func (set *Set) Iterator(f func (v interface{}) bool) *Set {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
@ -52,9 +59,7 @@ func (set *Set) Iterator(f func (v interface{}) bool) *Set {
|
||||
return set
|
||||
}
|
||||
|
||||
// Add one or multiple items to the set.
|
||||
//
|
||||
// 添加元素项到集合中(支持多个).
|
||||
// Add adds one or multiple items to the set.
|
||||
func (set *Set) Add(item...interface{}) *Set {
|
||||
set.mu.Lock()
|
||||
for _, v := range item {
|
||||
@ -64,9 +69,7 @@ func (set *Set) Add(item...interface{}) *Set {
|
||||
return set
|
||||
}
|
||||
|
||||
// Check whether the set contains <item>.
|
||||
//
|
||||
// 键是否存在.
|
||||
// Contains checks whether the set contains <item>.
|
||||
func (set *Set) Contains(item interface{}) bool {
|
||||
set.mu.RLock()
|
||||
_, exists := set.m[item]
|
||||
@ -74,9 +77,7 @@ func (set *Set) Contains(item interface{}) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
// Remove <item> from set.
|
||||
//
|
||||
// 删除元素项。
|
||||
// Remove deletes <item> from set.
|
||||
func (set *Set) Remove(item interface{}) *Set {
|
||||
set.mu.Lock()
|
||||
delete(set.m, item)
|
||||
@ -84,9 +85,7 @@ func (set *Set) Remove(item interface{}) *Set {
|
||||
return set
|
||||
}
|
||||
|
||||
// Get size of the set.
|
||||
//
|
||||
// 获得集合大小。
|
||||
// Size returns the size of the set.
|
||||
func (set *Set) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.m)
|
||||
@ -94,9 +93,7 @@ func (set *Set) Size() int {
|
||||
return l
|
||||
}
|
||||
|
||||
// Clear the set.
|
||||
//
|
||||
// 清空集合。
|
||||
// Clear deletes all items of the set.
|
||||
func (set *Set) Clear() *Set {
|
||||
set.mu.Lock()
|
||||
set.m = make(map[interface{}]struct{})
|
||||
@ -104,9 +101,7 @@ func (set *Set) Clear() *Set {
|
||||
return set
|
||||
}
|
||||
|
||||
// Get the copy of items from set as slice.
|
||||
//
|
||||
// 获得集合元素项列表.
|
||||
// Slice returns the a of items of the set as slice.
|
||||
func (set *Set) Slice() []interface{} {
|
||||
set.mu.RLock()
|
||||
i := 0
|
||||
@ -119,23 +114,17 @@ func (set *Set) Slice() []interface{} {
|
||||
return ret
|
||||
}
|
||||
|
||||
// Join set items with a string.
|
||||
//
|
||||
// 使用glue字符串串连当前集合的元素项,构造成新的字符串返回。
|
||||
// Join joins items with a string <glue>.
|
||||
func (set *Set) Join(glue string) string {
|
||||
return strings.Join(gconv.Strings(set.Slice()), ",")
|
||||
}
|
||||
|
||||
// Return set items as a string, which are joined by char ','.
|
||||
//
|
||||
// 使用glue字符串串连当前集合的元素项,构造成新的字符串返回。
|
||||
// String returns items as a string, which are joined by char ','.
|
||||
func (set *Set) String() string {
|
||||
return set.Join(",")
|
||||
}
|
||||
|
||||
// Lock writing by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁修改操作。
|
||||
// LockFunc locks writing with callback function <f>.
|
||||
func (set *Set) LockFunc(f func(m map[interface{}]struct{})) *Set {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
@ -143,9 +132,7 @@ func (set *Set) LockFunc(f func(m map[interface{}]struct{})) *Set {
|
||||
return set
|
||||
}
|
||||
|
||||
// Lock reading by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁读取操作。
|
||||
// RLockFunc locks reading with callback function <f>.
|
||||
func (set *Set) RLockFunc(f func(m map[interface{}]struct{})) *Set {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
@ -153,9 +140,7 @@ func (set *Set) RLockFunc(f func(m map[interface{}]struct{})) *Set {
|
||||
return set
|
||||
}
|
||||
|
||||
// Check whether the two sets equal.
|
||||
//
|
||||
// 判断两个集合是否相等.
|
||||
// Equal checks whether the two sets equal.
|
||||
func (set *Set) Equal(other *Set) bool {
|
||||
if set == other {
|
||||
return true
|
||||
@ -175,9 +160,7 @@ func (set *Set) Equal(other *Set) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check whether the current set is sub-set of <other>.
|
||||
//
|
||||
// 判断当前集合是否为other集合的子集.
|
||||
// IsSubsetOf checks whether the current set is a sub-set of <other>.
|
||||
func (set *Set) IsSubsetOf(other *Set) bool {
|
||||
if set == other {
|
||||
return true
|
||||
@ -194,10 +177,8 @@ func (set *Set) IsSubsetOf(other *Set) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns a new set which is the union of <set> and <other>.
|
||||
// Which means, all the items in <newSet> is in <set> or in <other>.
|
||||
//
|
||||
// 并集, 返回新的集合:属于set或属于others的元素为元素的集合.
|
||||
// Union returns a new set which is the union of <set> and <others>.
|
||||
// Which means, all the items in <newSet> are in <set> or in <others>.
|
||||
func (set *Set) Union(others ... *Set) (newSet *Set) {
|
||||
newSet = NewSet(true)
|
||||
set.mu.RLock()
|
||||
@ -222,10 +203,8 @@ func (set *Set) Union(others ... *Set) (newSet *Set) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a new set which is the difference set from <set> to <other>.
|
||||
// Which means, all the items in <newSet> is in <set> and not in <other>.
|
||||
//
|
||||
// 差集, 返回新的集合: 属于set且不属于others的元素为元素的集合.
|
||||
// Diff returns a new set which is the difference set from <set> to <others>.
|
||||
// Which means, all the items in <newSet> are in <set> but not in <others>.
|
||||
func (set *Set) Diff(others...*Set) (newSet *Set) {
|
||||
newSet = NewSet(true)
|
||||
set.mu.RLock()
|
||||
@ -245,10 +224,8 @@ func (set *Set) Diff(others...*Set) (newSet *Set) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a new set which is the intersection from <set> to <other>.
|
||||
// Which means, all the items in <newSet> is in <set> and also in <other>.
|
||||
//
|
||||
// 交集, 返回新的集合: 属于set且属于others的元素为元素的集合.
|
||||
// Intersect returns a new set which is the intersection from <set> to <others>.
|
||||
// Which means, all the items in <newSet> are in <set> and also in <others>.
|
||||
func (set *Set) Intersect(others...*Set) (newSet *Set) {
|
||||
newSet = NewSet(true)
|
||||
set.mu.RLock()
|
||||
@ -269,11 +246,11 @@ func (set *Set) Intersect(others...*Set) (newSet *Set) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a new set which is the complement from <set> to <full>.
|
||||
// Which means, all the items in <newSet> is in <full> and not in <set>.
|
||||
// Complement returns a new set which is the complement from <set> to <full>.
|
||||
// Which means, all the items in <newSet> are in <full> and not in <set>.
|
||||
//
|
||||
// 补集, 返回新的集合: (前提: set应当为full的子集)属于全集full不属于集合set的元素组成的集合.
|
||||
// 如果给定的full集合不是set的全集时,返回full与set的差集.
|
||||
// It returns the difference between <full> and <set>
|
||||
// if the given set <full> is not the full set of <set>.
|
||||
func (set *Set) Complement(full *Set) (newSet *Set) {
|
||||
newSet = NewSet(true)
|
||||
set.mu.RLock()
|
||||
@ -288,4 +265,63 @@ func (set *Set) Complement(full *Set) (newSet *Set) {
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Merge adds items from <others> sets into <set>.
|
||||
func (set *Set) Merge(others ... *Set) *Set {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range other.m {
|
||||
set.m[k] = v
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// Sum sums items.
|
||||
// Note: The items should be converted to int type,
|
||||
// or you'd get a result that you unexpected.
|
||||
func (set *Set) Sum() (sum int) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
sum += gconv.Int(k)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops randomly pops an item from set.
|
||||
func (set *Set) Pop(size int) interface{} {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
return k
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pops randomly pops <size> items from set.
|
||||
func (set *Set) Pops(size int) []interface{} {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if size > len(set.m) {
|
||||
size = len(set.m)
|
||||
}
|
||||
index := 0
|
||||
array := make([]interface{}, size)
|
||||
for k, _ := range set.m {
|
||||
array[index] = k
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
@ -18,11 +18,9 @@ type IntSet struct {
|
||||
m map[int]struct{}
|
||||
}
|
||||
|
||||
// Create a set, which contains un-repeated items.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个空的集合对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// New create and returns a new set, which contains un-repeated items.
|
||||
// The param <unsafe> used to specify whether using set in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewIntSet(unsafe...bool) *IntSet {
|
||||
return &IntSet{
|
||||
m : make(map[int]struct{}),
|
||||
@ -30,10 +28,20 @@ func NewIntSet(unsafe...bool) *IntSet {
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate the set by given callback <f>,
|
||||
// NewIntSetFrom returns a new set from <items>.
|
||||
func NewIntSetFrom(items []int, unsafe...bool) *IntSet {
|
||||
m := make(map[int]struct{})
|
||||
for _, v := range items {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return &IntSet{
|
||||
m : m,
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the set with given callback function <f>,
|
||||
// if <f> returns true then continue iterating; or false to stop.
|
||||
//
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历。
|
||||
func (set *IntSet) Iterator(f func (v int) bool) *IntSet {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
@ -45,9 +53,7 @@ func (set *IntSet) Iterator(f func (v int) bool) *IntSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Add one or multiple items to the set.
|
||||
//
|
||||
// 添加元素项到集合中(支持多个).
|
||||
// Add adds one or multiple items to the set.
|
||||
func (set *IntSet) Add(item...int) *IntSet {
|
||||
set.mu.Lock()
|
||||
for _, v := range item {
|
||||
@ -57,9 +63,7 @@ func (set *IntSet) Add(item...int) *IntSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Check whether the set contains <item>.
|
||||
//
|
||||
// 键是否存在.
|
||||
// Contains checks whether the set contains <item>.
|
||||
func (set *IntSet) Contains(item int) bool {
|
||||
set.mu.RLock()
|
||||
_, exists := set.m[item]
|
||||
@ -67,9 +71,7 @@ func (set *IntSet) Contains(item int) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
// Remove <item> from set.
|
||||
//
|
||||
// 删除元素项。
|
||||
// Remove deletes <item> from set.
|
||||
func (set *IntSet) Remove(item int) *IntSet {
|
||||
set.mu.Lock()
|
||||
delete(set.m, item)
|
||||
@ -77,9 +79,7 @@ func (set *IntSet) Remove(item int) *IntSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Get size of the set.
|
||||
//
|
||||
// 获得集合大小。
|
||||
// Size returns the size of the set.
|
||||
func (set *IntSet) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.m)
|
||||
@ -87,9 +87,7 @@ func (set *IntSet) Size() int {
|
||||
return l
|
||||
}
|
||||
|
||||
// Clear the set.
|
||||
//
|
||||
// 清空集合。
|
||||
// Clear deletes all items of the set.
|
||||
func (set *IntSet) Clear() *IntSet {
|
||||
set.mu.Lock()
|
||||
set.m = make(map[int]struct{})
|
||||
@ -97,9 +95,7 @@ func (set *IntSet) Clear() *IntSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Get the copy of items from set as slice.
|
||||
//
|
||||
// 获得集合元素项列表.
|
||||
// Slice returns the a of items of the set as slice.
|
||||
func (set *IntSet) Slice() []int {
|
||||
set.mu.RLock()
|
||||
ret := make([]int, len(set.m))
|
||||
@ -112,23 +108,17 @@ func (set *IntSet) Slice() []int {
|
||||
return ret
|
||||
}
|
||||
|
||||
// Join set items with a string.
|
||||
//
|
||||
// 使用glue字符串串连当前集合的元素项,构造成新的字符串返回。
|
||||
// Join joins items with a string <glue>.
|
||||
func (set *IntSet) Join(glue string) string {
|
||||
return strings.Join(gconv.Strings(set.Slice()), ",")
|
||||
}
|
||||
|
||||
// Return set items as a string, which are joined by char ','.
|
||||
//
|
||||
// 使用glue字符串串连当前集合的元素项,构造成新的字符串返回。
|
||||
// String returns items as a string, which are joined by char ','.
|
||||
func (set *IntSet) String() string {
|
||||
return set.Join(",")
|
||||
}
|
||||
|
||||
// Lock writing by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁修改操作。
|
||||
// LockFunc locks writing with callback function <f>.
|
||||
func (set *IntSet) LockFunc(f func(m map[int]struct{})) *IntSet {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
@ -136,9 +126,7 @@ func (set *IntSet) LockFunc(f func(m map[int]struct{})) *IntSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Lock reading by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁读取操作。
|
||||
// RLockFunc locks reading with callback function <f>.
|
||||
func (set *IntSet) RLockFunc(f func(m map[int]struct{})) *IntSet {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
@ -146,9 +134,7 @@ func (set *IntSet) RLockFunc(f func(m map[int]struct{})) *IntSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Check whether the two sets equal.
|
||||
//
|
||||
// 判断两个集合是否相等.
|
||||
// Equal checks whether the two sets equal.
|
||||
func (set *IntSet) Equal(other *IntSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
@ -168,9 +154,7 @@ func (set *IntSet) Equal(other *IntSet) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check whether the current set is sub-set of <other>.
|
||||
//
|
||||
// 判断当前集合是否为other集合的子集.
|
||||
// IsSubsetOf checks whether the current set is a sub-set of <other>.
|
||||
func (set *IntSet) IsSubsetOf(other *IntSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
@ -187,10 +171,8 @@ func (set *IntSet) IsSubsetOf(other *IntSet) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns a new set which is the union of <set> and <other>.
|
||||
// Which means, all the items in <newSet> is in <set> or in <other>.
|
||||
//
|
||||
// 并集, 返回新的集合:属于set或属于others的元素为元素的集合.
|
||||
// Union returns a new set which is the union of <set> and <other>.
|
||||
// Which means, all the items in <newSet> are in <set> or in <other>.
|
||||
func (set *IntSet) Union(others ... *IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet(true)
|
||||
set.mu.RLock()
|
||||
@ -215,10 +197,8 @@ func (set *IntSet) Union(others ... *IntSet) (newSet *IntSet) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a new set which is the difference set from <set> to <other>.
|
||||
// Which means, all the items in <newSet> is in <set> and not in <other>.
|
||||
//
|
||||
// 差集, 返回新的集合: 属于set且不属于others的元素为元素的集合.
|
||||
// Diff returns a new set which is the difference set from <set> to <other>.
|
||||
// Which means, all the items in <newSet> are in <set> but not in <other>.
|
||||
func (set *IntSet) Diff(others...*IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet(true)
|
||||
set.mu.RLock()
|
||||
@ -238,10 +218,8 @@ func (set *IntSet) Diff(others...*IntSet) (newSet *IntSet) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a new set which is the intersection from <set> to <other>.
|
||||
// Which means, all the items in <newSet> is in <set> and also in <other>.
|
||||
//
|
||||
// 交集, 返回新的集合: 属于set且属于others的元素为元素的集合.
|
||||
// Intersect returns a new set which is the intersection from <set> to <other>.
|
||||
// Which means, all the items in <newSet> are in <set> and also in <other>.
|
||||
func (set *IntSet) Intersect(others...*IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet(true)
|
||||
set.mu.RLock()
|
||||
@ -262,11 +240,11 @@ func (set *IntSet) Intersect(others...*IntSet) (newSet *IntSet) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a new set which is the complement from <set> to <full>.
|
||||
// Which means, all the items in <newSet> is in <full> and not in <set>.
|
||||
// Complement returns a new set which is the complement from <set> to <full>.
|
||||
// Which means, all the items in <newSet> are in <full> and not in <set>.
|
||||
//
|
||||
// 补集, 返回新的集合: (前提: set应当为full的子集)属于全集full不属于集合set的元素组成的集合.
|
||||
// 如果给定的full集合不是set的全集时,返回full与set的差集.
|
||||
// It returns the difference between <full> and <set>
|
||||
// if the given set <full> is not the full set of <set>.
|
||||
func (set *IntSet) Complement(full *IntSet) (newSet *IntSet) {
|
||||
newSet = NewIntSet(true)
|
||||
set.mu.RLock()
|
||||
@ -282,3 +260,62 @@ func (set *IntSet) Complement(full *IntSet) (newSet *IntSet) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Merge adds items from <others> sets into <set>.
|
||||
func (set *IntSet) Merge(others ... *IntSet) *IntSet {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range other.m {
|
||||
set.m[k] = v
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// Sum sums items.
|
||||
// Note: The items should be converted to int type,
|
||||
// or you'd get a result that you unexpected.
|
||||
func (set *IntSet) Sum() (sum int) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
sum += k
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops randomly pops an item from set.
|
||||
func (set *IntSet) Pop(size int) int {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
return k
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Pops randomly pops <size> items from set.
|
||||
func (set *IntSet) Pops(size int) []int {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if size > len(set.m) {
|
||||
size = len(set.m)
|
||||
}
|
||||
index := 0
|
||||
array := make([]int, size)
|
||||
for k, _ := range set.m {
|
||||
array[index] = k
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
@ -9,7 +9,8 @@ package gset
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/rwmutex"
|
||||
"strings"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StringSet struct {
|
||||
@ -17,11 +18,9 @@ type StringSet struct {
|
||||
m map[string]struct{}
|
||||
}
|
||||
|
||||
// Create a set, which contains un-repeated items.
|
||||
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
|
||||
// which is false in default, means concurrent-safe in default.
|
||||
//
|
||||
// 创建一个空的集合对象,参数unsafe用于指定是否用于非并发安全场景,默认为false,表示并发安全。
|
||||
// New create and returns a new set, which contains un-repeated items.
|
||||
// The param <unsafe> used to specify whether using set in un-concurrent-safety,
|
||||
// which is false in default.
|
||||
func NewStringSet(unsafe...bool) *StringSet {
|
||||
return &StringSet {
|
||||
m : make(map[string]struct{}),
|
||||
@ -29,10 +28,20 @@ func NewStringSet(unsafe...bool) *StringSet {
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate the set by given callback <f>,
|
||||
// NewStringSetFrom returns a new set from <items>.
|
||||
func NewStringSetFrom(items []string, unsafe...bool) *StringSet {
|
||||
m := make(map[string]struct{})
|
||||
for _, v := range items {
|
||||
m[v] = struct{}{}
|
||||
}
|
||||
return &StringSet{
|
||||
m : m,
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator iterates the set with given callback function <f>,
|
||||
// if <f> returns true then continue iterating; or false to stop.
|
||||
//
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历。
|
||||
func (set *StringSet) Iterator(f func (v string) bool) *StringSet {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
@ -44,9 +53,7 @@ func (set *StringSet) Iterator(f func (v string) bool) *StringSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Add one or multiple items to the set.
|
||||
//
|
||||
// 添加元素项到集合中(支持多个).
|
||||
// Add adds one or multiple items to the set.
|
||||
func (set *StringSet) Add(item...string) *StringSet {
|
||||
set.mu.Lock()
|
||||
for _, v := range item {
|
||||
@ -56,9 +63,7 @@ func (set *StringSet) Add(item...string) *StringSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Check whether the set contains <item>.
|
||||
//
|
||||
// 键是否存在.
|
||||
// Contains checks whether the set contains <item>.
|
||||
func (set *StringSet) Contains(item string) bool {
|
||||
set.mu.RLock()
|
||||
_, exists := set.m[item]
|
||||
@ -66,9 +71,7 @@ func (set *StringSet) Contains(item string) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
// Remove <item> from set.
|
||||
//
|
||||
// 删除元素项。
|
||||
// Remove deletes <item> from set.
|
||||
func (set *StringSet) Remove(item string) *StringSet {
|
||||
set.mu.Lock()
|
||||
delete(set.m, item)
|
||||
@ -76,9 +79,7 @@ func (set *StringSet) Remove(item string) *StringSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Get size of the set.
|
||||
//
|
||||
// 获得集合大小。
|
||||
// Size returns the size of the set.
|
||||
func (set *StringSet) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.m)
|
||||
@ -86,9 +87,7 @@ func (set *StringSet) Size() int {
|
||||
return l
|
||||
}
|
||||
|
||||
// Clear the set.
|
||||
//
|
||||
// 清空集合。
|
||||
// Clear deletes all items of the set.
|
||||
func (set *StringSet) Clear() *StringSet {
|
||||
set.mu.Lock()
|
||||
set.m = make(map[string]struct{})
|
||||
@ -96,9 +95,7 @@ func (set *StringSet) Clear() *StringSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Get the copy of items from set as slice.
|
||||
//
|
||||
// 获得集合元素项列表.
|
||||
// Slice returns the a of items of the set as slice.
|
||||
func (set *StringSet) Slice() []string {
|
||||
set.mu.RLock()
|
||||
ret := make([]string, len(set.m))
|
||||
@ -112,23 +109,17 @@ func (set *StringSet) Slice() []string {
|
||||
return ret
|
||||
}
|
||||
|
||||
// Join set items with a string.
|
||||
//
|
||||
// 使用glue字符串串连当前集合的元素项,构造成新的字符串返回。
|
||||
// Join joins items with a string <glue>.
|
||||
func (set *StringSet) Join(glue string) string {
|
||||
return strings.Join(set.Slice(), ",")
|
||||
}
|
||||
|
||||
// Return set items as a string, which are joined by char ','.
|
||||
//
|
||||
// 使用glue字符串串连当前集合的元素项,构造成新的字符串返回。
|
||||
// String returns items as a string, which are joined by char ','.
|
||||
func (set *StringSet) String() string {
|
||||
return set.Join(",")
|
||||
}
|
||||
|
||||
// Lock writing by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁修改操作。
|
||||
// LockFunc locks writing with callback function <f>.
|
||||
func (set *StringSet) LockFunc(f func(m map[string]struct{})) *StringSet {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
@ -136,9 +127,7 @@ func (set *StringSet) LockFunc(f func(m map[string]struct{})) *StringSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Lock reading by callback function f.
|
||||
//
|
||||
// 使用自定义方法执行加锁读取操作。
|
||||
// RLockFunc locks reading with callback function <f>.
|
||||
func (set *StringSet) RLockFunc(f func(m map[string]struct{})) *StringSet {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
@ -146,9 +135,7 @@ func (set *StringSet) RLockFunc(f func(m map[string]struct{})) *StringSet {
|
||||
return set
|
||||
}
|
||||
|
||||
// Check whether the two sets equal.
|
||||
//
|
||||
// 判断两个集合是否相等.
|
||||
// Equal checks whether the two sets equal.
|
||||
func (set *StringSet) Equal(other *StringSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
@ -168,9 +155,7 @@ func (set *StringSet) Equal(other *StringSet) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check whether the current set is sub-set of <other>.
|
||||
//
|
||||
// 判断当前集合是否为other集合的子集.
|
||||
// IsSubsetOf checks whether the current set is a sub-set of <other>.
|
||||
func (set *StringSet) IsSubsetOf(other *StringSet) bool {
|
||||
if set == other {
|
||||
return true
|
||||
@ -187,10 +172,8 @@ func (set *StringSet) IsSubsetOf(other *StringSet) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Returns a new set which is the union of <set> and <other>.
|
||||
// Which means, all the items in <newSet> is in <set> or in <other>.
|
||||
//
|
||||
// 并集, 返回新的集合:属于set或属于others的元素为元素的集合.
|
||||
// Union returns a new set which is the union of <set> and <other>.
|
||||
// Which means, all the items in <newSet> are in <set> or in <other>.
|
||||
func (set *StringSet) Union(others ... *StringSet) (newSet *StringSet) {
|
||||
newSet = NewStringSet(true)
|
||||
set.mu.RLock()
|
||||
@ -215,10 +198,8 @@ func (set *StringSet) Union(others ... *StringSet) (newSet *StringSet) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a new set which is the difference set from <set> to <other>.
|
||||
// Which means, all the items in <newSet> is in <set> and not in <other>.
|
||||
//
|
||||
// 差集, 返回新的集合: 属于set且不属于others的元素为元素的集合.
|
||||
// Diff returns a new set which is the difference set from <set> to <other>.
|
||||
// Which means, all the items in <newSet> are in <set> but not in <other>.
|
||||
func (set *StringSet) Diff(others...*StringSet) (newSet *StringSet) {
|
||||
newSet = NewStringSet(true)
|
||||
set.mu.RLock()
|
||||
@ -238,10 +219,8 @@ func (set *StringSet) Diff(others...*StringSet) (newSet *StringSet) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a new set which is the intersection from <set> to <other>.
|
||||
// Which means, all the items in <newSet> is in <set> and also in <other>.
|
||||
//
|
||||
// 交集, 返回新的集合: 属于set且属于others的元素为元素的集合.
|
||||
// Intersect returns a new set which is the intersection from <set> to <other>.
|
||||
// Which means, all the items in <newSet> are in <set> and also in <other>.
|
||||
func (set *StringSet) Intersect(others...*StringSet) (newSet *StringSet) {
|
||||
newSet = NewStringSet(true)
|
||||
set.mu.RLock()
|
||||
@ -262,11 +241,11 @@ func (set *StringSet) Intersect(others...*StringSet) (newSet *StringSet) {
|
||||
return
|
||||
}
|
||||
|
||||
// Returns a new set which is the complement from <set> to <full>.
|
||||
// Which means, all the items in <newSet> is in <full> and not in <set>.
|
||||
// Complement returns a new set which is the complement from <set> to <full>.
|
||||
// Which means, all the items in <newSet> are in <full> and not in <set>.
|
||||
//
|
||||
// 补集, 返回新的集合: (前提: set应当为full的子集)属于全集full不属于集合set的元素组成的集合.
|
||||
// 如果给定的full集合不是set的全集时,返回full与set的差集.
|
||||
// It returns the difference between <full> and <set>
|
||||
// if the given set <full> is not the full set of <set>.
|
||||
func (set *StringSet) Complement(full *StringSet) (newSet *StringSet) {
|
||||
newSet = NewStringSet(true)
|
||||
set.mu.RLock()
|
||||
@ -282,3 +261,62 @@ func (set *StringSet) Complement(full *StringSet) (newSet *StringSet) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Merge adds items from <others> sets into <set>.
|
||||
func (set *StringSet) Merge(others ... *StringSet) *StringSet {
|
||||
set.mu.Lock()
|
||||
defer set.mu.Unlock()
|
||||
for _, other := range others {
|
||||
if set != other {
|
||||
other.mu.RLock()
|
||||
}
|
||||
for k, v := range other.m {
|
||||
set.m[k] = v
|
||||
}
|
||||
if set != other {
|
||||
other.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// Sum sums items.
|
||||
// Note: The items should be converted to int type,
|
||||
// or you'd get a result that you unexpected.
|
||||
func (set *StringSet) Sum() (sum int) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
sum += gconv.Int(k)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pops randomly pops an item from set.
|
||||
func (set *StringSet) Pop(size int) string {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
return k
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Pops randomly pops <size> items from set.
|
||||
func (set *StringSet) Pops(size int) []string {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
if size > len(set.m) {
|
||||
size = len(set.m)
|
||||
}
|
||||
index := 0
|
||||
array := make([]string, size)
|
||||
for k, _ := range set.m {
|
||||
array[index] = k
|
||||
index++
|
||||
if index == size {
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
265
g/container/gvar/gvar_test.go
Normal file
265
g/container/gvar/gvar_test.go
Normal file
@ -0,0 +1,265 @@
|
||||
package gvar_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
)
|
||||
|
||||
func TestReadOnly(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
obj := gvar.New(nil, true)
|
||||
var result string
|
||||
|
||||
switch obj.ReadOnly().(type) {
|
||||
case gvar.VarRead:
|
||||
result = "yes"
|
||||
default:
|
||||
result = "no"
|
||||
}
|
||||
|
||||
gtest.Assert(result, "yes")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
objOne := gvar.New("old", true)
|
||||
objOneOld, _ := objOne.Set("new").(string)
|
||||
gtest.Assert(objOneOld, "old")
|
||||
|
||||
objTwo := gvar.New("old", false)
|
||||
objTwoOld, _ := objTwo.Set("new").(string)
|
||||
gtest.Assert(objTwoOld, "old")
|
||||
})
|
||||
}
|
||||
|
||||
func TestVal(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
objOne := gvar.New(1, true)
|
||||
objOneOld, _ := objOne.Val().(int)
|
||||
gtest.Assert(objOneOld, 1)
|
||||
|
||||
objTwo := gvar.New(1, false)
|
||||
objTwoOld, _ := objTwo.Val().(int)
|
||||
gtest.Assert(objTwoOld, 1)
|
||||
})
|
||||
}
|
||||
func TestInterface(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
objOne := gvar.New(1, true)
|
||||
objOneOld, _ := objOne.Interface().(int)
|
||||
gtest.Assert(objOneOld, 1)
|
||||
|
||||
objTwo := gvar.New(1, false)
|
||||
objTwoOld, _ := objTwo.Interface().(int)
|
||||
gtest.Assert(objTwoOld, 1)
|
||||
})
|
||||
}
|
||||
func TestIsNil(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
objOne := gvar.New(nil, true)
|
||||
gtest.Assert(objOne.IsNil(), true)
|
||||
|
||||
objTwo := gvar.New("noNil", false)
|
||||
gtest.Assert(objTwo.IsNil(), false)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestBytes(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
x := int32(1)
|
||||
bytesBuffer := bytes.NewBuffer([]byte{})
|
||||
binary.Write(bytesBuffer, binary.BigEndian, x)
|
||||
|
||||
objOne := gvar.New(bytesBuffer.Bytes(), true)
|
||||
|
||||
bBuf := bytes.NewBuffer(objOne.Bytes())
|
||||
var y int32
|
||||
binary.Read(bBuf, binary.BigEndian, &y)
|
||||
|
||||
gtest.Assert(x, y)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var str string = "hello"
|
||||
objOne := gvar.New(str, true)
|
||||
gtest.Assert(objOne.String(), str)
|
||||
|
||||
})
|
||||
}
|
||||
func TestBool(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var ok bool = true
|
||||
objOne := gvar.New(ok, true)
|
||||
gtest.Assert(objOne.Bool(), ok)
|
||||
|
||||
ok = false
|
||||
objTwo := gvar.New(ok, true)
|
||||
gtest.Assert(objTwo.Bool(), ok)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num int = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Int(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt8(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num int8 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Int8(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt16(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num int16 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Int16(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt32(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num int32 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Int32(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt64(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num int64 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Int64(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUint(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num uint = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Uint(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUint8(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num uint8 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Uint8(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUint16(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num uint16 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Uint16(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUint32(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num uint32 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Uint32(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestUint64(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num uint64 = 1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Uint64(), num)
|
||||
|
||||
})
|
||||
}
|
||||
func TestFloat32(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num float32 = 1.1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Float32(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestFloat64(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var num float64 = 1.1
|
||||
objOne := gvar.New(num, true)
|
||||
gtest.Assert(objOne.Float64(), num)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestInts(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var arr = []int{1, 2, 3, 4, 5}
|
||||
objOne := gvar.New(arr, true)
|
||||
gtest.Assert(objOne.Ints()[0], arr[0])
|
||||
})
|
||||
}
|
||||
func TestFloats(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var arr = []float64{1, 2, 3, 4, 5}
|
||||
objOne := gvar.New(arr, true)
|
||||
gtest.Assert(objOne.Floats()[0], arr[0])
|
||||
})
|
||||
}
|
||||
func TestStrings(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var arr = []string{"hello", "world"}
|
||||
objOne := gvar.New(arr, true)
|
||||
gtest.Assert(objOne.Strings()[0], arr[0])
|
||||
})
|
||||
}
|
||||
|
||||
func TestTime(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
var timeUnix int64 = 1556242660
|
||||
objOne := gvar.New(timeUnix, true)
|
||||
gtest.Assert(objOne.Time().Unix(), timeUnix)
|
||||
})
|
||||
}
|
||||
|
||||
type StTest struct {
|
||||
Test int
|
||||
}
|
||||
|
||||
func TestStruct(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
Kv := make(map[string]int, 1)
|
||||
Kv["Test"] = 100
|
||||
|
||||
testObj := &StTest{}
|
||||
|
||||
objOne := gvar.New(Kv, true)
|
||||
|
||||
objOne.Struct(testObj)
|
||||
|
||||
gtest.Assert(testObj.Test, Kv["Test"])
|
||||
})
|
||||
}
|
||||
@ -61,7 +61,10 @@ func Decrypt(cipherText []byte, key []byte, iv...[]byte) ([]byte, error) {
|
||||
blockModel := cipher.NewCBCDecrypter(block, ivValue)
|
||||
plainText := make([]byte, len(cipherText))
|
||||
blockModel.CryptBlocks(plainText, cipherText)
|
||||
plainText = PKCS5UnPadding(plainText)
|
||||
plainText, e := PKCS5UnPadding(plainText, blockSize)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return plainText, nil
|
||||
}
|
||||
@ -72,8 +75,27 @@ func PKCS5Padding(src []byte, blockSize int) []byte {
|
||||
return append(src, padtext...)
|
||||
}
|
||||
|
||||
func PKCS5UnPadding(src []byte) []byte {
|
||||
func PKCS5UnPadding(src []byte, blockSize int) ([]byte, error) {
|
||||
length := len(src)
|
||||
if blockSize <= 0 {
|
||||
return nil, errors.New("invalid blocklen")
|
||||
}
|
||||
|
||||
if length%blockSize != 0 || length == 0 {
|
||||
return nil, errors.New("invalid data len")
|
||||
}
|
||||
|
||||
unpadding := int(src[length - 1])
|
||||
return src[:(length - unpadding)]
|
||||
}
|
||||
if unpadding > blockSize || unpadding == 0 {
|
||||
return nil, errors.New("invalid padding")
|
||||
}
|
||||
|
||||
padding := src[length - unpadding:]
|
||||
for i := 0; i < unpadding; i++ {
|
||||
if padding[i] != byte(unpadding) {
|
||||
return nil, errors.New("invalid padding")
|
||||
}
|
||||
}
|
||||
|
||||
return src[:(length - unpadding)], nil
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ var (
|
||||
key_16 = []byte("1234567891234567")
|
||||
key_24 = []byte("123456789123456789123456")
|
||||
key_32 = []byte("12345678912345678912345678912345")
|
||||
keys = []byte("12345678912345678912345678912346")
|
||||
)
|
||||
|
||||
func TestEncrypt(t *testing.T) {
|
||||
@ -58,5 +59,9 @@ func TestDecrypt(t *testing.T) {
|
||||
decrypt, err = gaes.Decrypt(encrypt, key_32, iv)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(string(decrypt), string(content))
|
||||
|
||||
encrypt, err = gaes.Encrypt(content, key_32, iv)
|
||||
decrypt, err = gaes.Decrypt(encrypt, keys, iv)
|
||||
gtest.Assert(err, "invalid padding")
|
||||
})
|
||||
}
|
||||
|
||||
@ -20,7 +20,6 @@ import (
|
||||
"github.com/gogf/gf/g/container/gvar"
|
||||
"github.com/gogf/gf/g/os/gcache"
|
||||
"github.com/gogf/gf/g/util/grand"
|
||||
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
@ -15,7 +15,6 @@ import (
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -13,7 +13,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
|
||||
@ -8,8 +8,9 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"database/sql"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/gogf/gf/third/github.com/gf-third/mysql"
|
||||
)
|
||||
|
||||
// 数据库链接对象
|
||||
@ -26,7 +27,7 @@ func (db *dbMysql) Open (config *ConfigNode) (*sql.DB, error) {
|
||||
source = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset)
|
||||
}
|
||||
if db, err := sql.Open("mysql", source); err == nil {
|
||||
if db, err := sql.Open("gf-mysql", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
return nil, err
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
|
||||
51
g/encoding/gbase64/gbase64_test.go
Normal file
51
g/encoding/gbase64/gbase64_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gbase64_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/encoding/gbase64"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testpair struct {
|
||||
decoded, encoded string
|
||||
}
|
||||
|
||||
var pairs = []testpair{
|
||||
// RFC 3548 examples
|
||||
{"\x14\xfb\x9c\x03\xd9\x7e", "FPucA9l+"},
|
||||
{"\x14\xfb\x9c\x03\xd9", "FPucA9k="},
|
||||
{"\x14\xfb\x9c\x03", "FPucAw=="},
|
||||
|
||||
// RFC 4648 examples
|
||||
{"", ""},
|
||||
{"f", "Zg=="},
|
||||
{"fo", "Zm8="},
|
||||
{"foo", "Zm9v"},
|
||||
{"foob", "Zm9vYg=="},
|
||||
{"fooba", "Zm9vYmE="},
|
||||
{"foobar", "Zm9vYmFy"},
|
||||
|
||||
// Wikipedia examples
|
||||
{"sure.", "c3VyZS4="},
|
||||
{"sure", "c3VyZQ=="},
|
||||
{"sur", "c3Vy"},
|
||||
{"su", "c3U="},
|
||||
{"leasure.", "bGVhc3VyZS4="},
|
||||
{"easure.", "ZWFzdXJlLg=="},
|
||||
{"asure.", "YXN1cmUu"},
|
||||
{"sure.", "c3VyZS4="},
|
||||
}
|
||||
|
||||
func TestBase64(t *testing.T) {
|
||||
for k := range pairs{
|
||||
gtest.Assert(gbase64.Encode(pairs[k].decoded), pairs[k].encoded)
|
||||
|
||||
e, _ := gbase64.Decode(pairs[k].encoded)
|
||||
gtest.Assert(e, pairs[k].decoded)
|
||||
}
|
||||
}
|
||||
@ -5,13 +5,15 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gbinary provides useful API for handling binary/bytes data.
|
||||
//
|
||||
// 注意gbinary模块统一使用LittleEndian进行编码。
|
||||
package gbinary
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
// 二进制位(0|1)
|
||||
@ -20,304 +22,308 @@ type Bit int8
|
||||
// 针对基本类型进行二进制打包,支持的基本数据类型包括:int/8/16/32/64、uint/8/16/32/64、float32/64、bool、string、[]byte
|
||||
// 其他未知类型使用 fmt.Sprintf("%v", value) 转换为字符串之后处理
|
||||
func Encode(vs ...interface{}) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
for i := 0; i < len(vs); i++ {
|
||||
switch value := vs[i].(type) {
|
||||
case int: buf.Write(EncodeInt(value))
|
||||
case int8: buf.Write(EncodeInt8(value))
|
||||
case int16: buf.Write(EncodeInt16(value))
|
||||
case int32: buf.Write(EncodeInt32(value))
|
||||
case int64: buf.Write(EncodeInt64(value))
|
||||
case uint: buf.Write(EncodeUint(value))
|
||||
case uint8: buf.Write(EncodeUint8(value))
|
||||
case uint16: buf.Write(EncodeUint16(value))
|
||||
case uint32: buf.Write(EncodeUint32(value))
|
||||
case uint64: buf.Write(EncodeUint64(value))
|
||||
case bool: buf.Write(EncodeBool(value))
|
||||
case string: buf.Write(EncodeString(value))
|
||||
case []byte: buf.Write(value)
|
||||
case float32: buf.Write(EncodeFloat32(value))
|
||||
case float64: buf.Write(EncodeFloat64(value))
|
||||
default:
|
||||
if err := binary.Write(buf, binary.LittleEndian, value); err != nil {
|
||||
buf.Write(EncodeString(fmt.Sprintf("%v", value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
buf := new(bytes.Buffer)
|
||||
for i := 0; i < len(vs); i++ {
|
||||
if vs[i] == nil {
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
switch value := vs[i].(type) {
|
||||
case int: buf.Write(EncodeInt(value))
|
||||
case int8: buf.Write(EncodeInt8(value))
|
||||
case int16: buf.Write(EncodeInt16(value))
|
||||
case int32: buf.Write(EncodeInt32(value))
|
||||
case int64: buf.Write(EncodeInt64(value))
|
||||
case uint: buf.Write(EncodeUint(value))
|
||||
case uint8: buf.Write(EncodeUint8(value))
|
||||
case uint16: buf.Write(EncodeUint16(value))
|
||||
case uint32: buf.Write(EncodeUint32(value))
|
||||
case uint64: buf.Write(EncodeUint64(value))
|
||||
case bool: buf.Write(EncodeBool(value))
|
||||
case string: buf.Write(EncodeString(value))
|
||||
case []byte: buf.Write(value)
|
||||
case float32: buf.Write(EncodeFloat32(value))
|
||||
case float64: buf.Write(EncodeFloat64(value))
|
||||
default:
|
||||
if err := binary.Write(buf, binary.LittleEndian, value); err != nil {
|
||||
buf.Write(EncodeString(fmt.Sprintf("%v", value)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// 将变量转换为二进制[]byte,并指定固定的[]byte长度返回,长度单位为字节(byte);
|
||||
// 如果转换的二进制长度超过指定长度,那么进行截断处理
|
||||
func EncodeByLength(length int, vs ...interface{}) []byte {
|
||||
b := Encode(vs...)
|
||||
if len(b) < length {
|
||||
b = append(b, make([]byte, length - len(b))...)
|
||||
} else if len(b) > length {
|
||||
b = b[0 : length]
|
||||
}
|
||||
return b
|
||||
b := Encode(vs...)
|
||||
if len(b) < length {
|
||||
b = append(b, make([]byte, length - len(b))...)
|
||||
} else if len(b) > length {
|
||||
b = b[0 : length]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// 整形二进制解包,注意第二个及其后参数为字长确定的整形变量的指针地址,以便确定解析的[]byte长度,
|
||||
// 例如:int8/16/32/64、uint8/16/32/64、float32/64等等
|
||||
func Decode(b []byte, vs ...interface{}) error {
|
||||
buf := bytes.NewBuffer(b)
|
||||
for i := 0; i < len(vs); i++ {
|
||||
err := binary.Read(buf, binary.LittleEndian, vs[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
buf := bytes.NewBuffer(b)
|
||||
for i := 0; i < len(vs); i++ {
|
||||
err := binary.Read(buf, binary.LittleEndian, vs[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func EncodeString(s string) []byte {
|
||||
return []byte(s)
|
||||
return []byte(s)
|
||||
}
|
||||
|
||||
func DecodeToString(b []byte) string {
|
||||
return string(b)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func EncodeBool(b bool) []byte {
|
||||
if b == true {
|
||||
return []byte{1}
|
||||
} else {
|
||||
return []byte{0}
|
||||
}
|
||||
if b == true {
|
||||
return []byte{1}
|
||||
} else {
|
||||
return []byte{0}
|
||||
}
|
||||
}
|
||||
|
||||
// 自动识别int类型长度,转换为[]byte
|
||||
func EncodeInt(i int) []byte {
|
||||
if i <= math.MaxInt8 {
|
||||
return EncodeInt8(int8(i))
|
||||
} else if i <= math.MaxInt16 {
|
||||
return EncodeInt16(int16(i))
|
||||
} else if i <= math.MaxInt32 {
|
||||
return EncodeInt32(int32(i))
|
||||
} else {
|
||||
return EncodeInt64(int64(i))
|
||||
}
|
||||
if i <= math.MaxInt8 {
|
||||
return EncodeInt8(int8(i))
|
||||
} else if i <= math.MaxInt16 {
|
||||
return EncodeInt16(int16(i))
|
||||
} else if i <= math.MaxInt32 {
|
||||
return EncodeInt32(int32(i))
|
||||
} else {
|
||||
return EncodeInt64(int64(i))
|
||||
}
|
||||
}
|
||||
|
||||
// 自动识别uint类型长度,转换为[]byte
|
||||
func EncodeUint(i uint) []byte {
|
||||
if i <= math.MaxUint8 {
|
||||
return EncodeUint8(uint8(i))
|
||||
} else if i <= math.MaxUint16 {
|
||||
return EncodeUint16(uint16(i))
|
||||
} else if i <= math.MaxUint32 {
|
||||
return EncodeUint32(uint32(i))
|
||||
} else {
|
||||
return EncodeUint64(uint64(i))
|
||||
}
|
||||
if i <= math.MaxUint8 {
|
||||
return EncodeUint8(uint8(i))
|
||||
} else if i <= math.MaxUint16 {
|
||||
return EncodeUint16(uint16(i))
|
||||
} else if i <= math.MaxUint32 {
|
||||
return EncodeUint32(uint32(i))
|
||||
} else {
|
||||
return EncodeUint64(uint64(i))
|
||||
}
|
||||
}
|
||||
|
||||
func EncodeInt8(i int8) []byte {
|
||||
return []byte{byte(i)}
|
||||
return []byte{byte(i)}
|
||||
}
|
||||
|
||||
func EncodeUint8(i uint8) []byte {
|
||||
return []byte{byte(i)}
|
||||
return []byte{byte(i)}
|
||||
}
|
||||
|
||||
func EncodeInt16(i int16) []byte {
|
||||
bytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(bytes, uint16(i))
|
||||
return bytes
|
||||
bytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(bytes, uint16(i))
|
||||
return bytes
|
||||
}
|
||||
|
||||
func EncodeUint16(i uint16) []byte {
|
||||
bytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(bytes, i)
|
||||
return bytes
|
||||
bytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(bytes, i)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func EncodeInt32(i int32) []byte {
|
||||
bytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bytes, uint32(i))
|
||||
return bytes
|
||||
bytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bytes, uint32(i))
|
||||
return bytes
|
||||
}
|
||||
|
||||
func EncodeUint32(i uint32) []byte {
|
||||
bytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bytes, i)
|
||||
return bytes
|
||||
bytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bytes, i)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func EncodeInt64(i int64) []byte {
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, uint64(i))
|
||||
return bytes
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, uint64(i))
|
||||
return bytes
|
||||
}
|
||||
|
||||
func EncodeUint64(i uint64) []byte {
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, i)
|
||||
return bytes
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, i)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func EncodeFloat32(f float32) []byte {
|
||||
bits := math.Float32bits(f)
|
||||
bytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bytes, bits)
|
||||
return bytes
|
||||
bits := math.Float32bits(f)
|
||||
bytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(bytes, bits)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func EncodeFloat64(f float64) []byte {
|
||||
bits := math.Float64bits(f)
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, bits)
|
||||
return bytes
|
||||
bits := math.Float64bits(f)
|
||||
bytes := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(bytes, bits)
|
||||
return bytes
|
||||
}
|
||||
|
||||
// 当b位数不够时,进行高位补0
|
||||
func fillUpSize(b []byte, l int) []byte {
|
||||
if len(b) >= l {
|
||||
return b
|
||||
}
|
||||
c := make([]byte, 0)
|
||||
c = append(c, b...)
|
||||
for i := 0; i < l - len(b); i++ {
|
||||
c = append(c, 0x00)
|
||||
}
|
||||
return c
|
||||
if len(b) >= l {
|
||||
return b
|
||||
}
|
||||
c := make([]byte, 0)
|
||||
c = append(c, b...)
|
||||
for i := 0; i < l - len(b); i++ {
|
||||
c = append(c, 0x00)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// 将二进制解析为int类型,根据[]byte的长度进行自动转换.
|
||||
// 注意内部使用的是uint*,使用int会造成位丢失。
|
||||
func DecodeToInt(b []byte) int {
|
||||
if len(b) < 2 {
|
||||
return int(DecodeToUint8(b))
|
||||
} else if len(b) < 3 {
|
||||
return int(DecodeToUint16(b))
|
||||
} else if len(b) < 5 {
|
||||
return int(DecodeToUint32(b))
|
||||
} else {
|
||||
return int(DecodeToUint64(b))
|
||||
}
|
||||
if len(b) < 2 {
|
||||
return int(DecodeToUint8(b))
|
||||
} else if len(b) < 3 {
|
||||
return int(DecodeToUint16(b))
|
||||
} else if len(b) < 5 {
|
||||
return int(DecodeToUint32(b))
|
||||
} else {
|
||||
return int(DecodeToUint64(b))
|
||||
}
|
||||
}
|
||||
|
||||
// 将二进制解析为uint类型,根据[]byte的长度进行自动转换
|
||||
func DecodeToUint(b []byte) uint {
|
||||
if len(b) < 2 {
|
||||
return uint(DecodeToUint8(b))
|
||||
} else if len(b) < 3 {
|
||||
return uint(DecodeToUint16(b))
|
||||
} else if len(b) < 5 {
|
||||
return uint(DecodeToUint32(b))
|
||||
} else {
|
||||
return uint(DecodeToUint64(b))
|
||||
}
|
||||
if len(b) < 2 {
|
||||
return uint(DecodeToUint8(b))
|
||||
} else if len(b) < 3 {
|
||||
return uint(DecodeToUint16(b))
|
||||
} else if len(b) < 5 {
|
||||
return uint(DecodeToUint32(b))
|
||||
} else {
|
||||
return uint(DecodeToUint64(b))
|
||||
}
|
||||
}
|
||||
|
||||
// 将二进制解析为bool类型,识别标准是判断二进制中数值是否都为0,或者为空
|
||||
func DecodeToBool(b []byte) bool {
|
||||
if len(b) == 0 {
|
||||
return false
|
||||
}
|
||||
if bytes.Compare(b, make([]byte, len(b))) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
if len(b) == 0 {
|
||||
return false
|
||||
}
|
||||
if bytes.Compare(b, make([]byte, len(b))) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func DecodeToInt8(b []byte) int8 {
|
||||
return int8(b[0])
|
||||
return int8(b[0])
|
||||
}
|
||||
|
||||
func DecodeToUint8(b []byte) uint8 {
|
||||
return uint8(b[0])
|
||||
return uint8(b[0])
|
||||
}
|
||||
|
||||
func DecodeToInt16(b []byte) int16 {
|
||||
return int16(binary.LittleEndian.Uint16(fillUpSize(b, 2)))
|
||||
return int16(binary.LittleEndian.Uint16(fillUpSize(b, 2)))
|
||||
}
|
||||
|
||||
func DecodeToUint16(b []byte) uint16 {
|
||||
return binary.LittleEndian.Uint16(fillUpSize(b, 2))
|
||||
return binary.LittleEndian.Uint16(fillUpSize(b, 2))
|
||||
}
|
||||
|
||||
func DecodeToInt32(b []byte) int32 {
|
||||
return int32(binary.LittleEndian.Uint32(fillUpSize(b, 4)))
|
||||
return int32(binary.LittleEndian.Uint32(fillUpSize(b, 4)))
|
||||
}
|
||||
|
||||
func DecodeToUint32(b []byte) uint32 {
|
||||
return binary.LittleEndian.Uint32(fillUpSize(b, 4))
|
||||
return binary.LittleEndian.Uint32(fillUpSize(b, 4))
|
||||
}
|
||||
|
||||
func DecodeToInt64(b []byte) int64 {
|
||||
return int64(binary.LittleEndian.Uint64(fillUpSize(b, 8)))
|
||||
return int64(binary.LittleEndian.Uint64(fillUpSize(b, 8)))
|
||||
}
|
||||
|
||||
func DecodeToUint64(b []byte) uint64 {
|
||||
return binary.LittleEndian.Uint64(fillUpSize(b, 8))
|
||||
return binary.LittleEndian.Uint64(fillUpSize(b, 8))
|
||||
}
|
||||
|
||||
func DecodeToFloat32(b []byte) float32 {
|
||||
return math.Float32frombits(binary.LittleEndian.Uint32(fillUpSize(b, 4)))
|
||||
return math.Float32frombits(binary.LittleEndian.Uint32(fillUpSize(b, 4)))
|
||||
}
|
||||
|
||||
func DecodeToFloat64(b []byte) float64 {
|
||||
return math.Float64frombits(binary.LittleEndian.Uint64(fillUpSize(b, 8)))
|
||||
return math.Float64frombits(binary.LittleEndian.Uint64(fillUpSize(b, 8)))
|
||||
}
|
||||
|
||||
// 默认编码
|
||||
func EncodeBits(bits []Bit, i int, l int) []Bit {
|
||||
return EncodeBitsWithUint(bits, uint(i), l)
|
||||
return EncodeBitsWithUint(bits, uint(i), l)
|
||||
}
|
||||
|
||||
// 将ui按位合并到bits数组中,并占length长度位(注意:uis数组中存放的是二进制的0|1数字)
|
||||
func EncodeBitsWithUint(bits []Bit, ui uint, l int) []Bit {
|
||||
a := make([]Bit, l)
|
||||
for i := l - 1; i >= 0; i-- {
|
||||
a[i] = Bit(ui & 1)
|
||||
ui >>= 1
|
||||
}
|
||||
if bits != nil {
|
||||
return append(bits, a...)
|
||||
} else {
|
||||
return a
|
||||
}
|
||||
a := make([]Bit, l)
|
||||
for i := l - 1; i >= 0; i-- {
|
||||
a[i] = Bit(ui & 1)
|
||||
ui >>= 1
|
||||
}
|
||||
if bits != nil {
|
||||
return append(bits, a...)
|
||||
} else {
|
||||
return a
|
||||
}
|
||||
}
|
||||
// 将bits转换为[]byte,从左至右进行编码,不足1 byte按0往末尾补充
|
||||
func EncodeBitsToBytes(bits []Bit) []byte {
|
||||
if len(bits)%8 != 0 {
|
||||
for i := 0; i < len(bits)%8; i++ {
|
||||
bits = append(bits, 0)
|
||||
}
|
||||
}
|
||||
b := make([]byte, 0)
|
||||
for i := 0; i < len(bits); i += 8 {
|
||||
b = append(b, byte(DecodeBitsToUint(bits[i : i + 8])))
|
||||
}
|
||||
return b
|
||||
if len(bits)%8 != 0 {
|
||||
for i := 0; i < len(bits)%8; i++ {
|
||||
bits = append(bits, 0)
|
||||
}
|
||||
}
|
||||
b := make([]byte, 0)
|
||||
for i := 0; i < len(bits); i += 8 {
|
||||
b = append(b, byte(DecodeBitsToUint(bits[i : i + 8])))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// 解析为int
|
||||
func DecodeBits(bits []Bit) int {
|
||||
v := int(0)
|
||||
for _, i := range bits {
|
||||
v = v << 1 | int(i)
|
||||
}
|
||||
return v
|
||||
v := int(0)
|
||||
for _, i := range bits {
|
||||
v = v << 1 | int(i)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// 解析为uint
|
||||
func DecodeBitsToUint(bits []Bit) uint {
|
||||
v := uint(0)
|
||||
for _, i := range bits {
|
||||
v = v << 1 | uint(i)
|
||||
}
|
||||
return v
|
||||
v := uint(0)
|
||||
for _, i := range bits {
|
||||
v = v << 1 | uint(i)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// 解析[]byte为字位数组[]uint8
|
||||
func DecodeBytesToBits(bs []byte) []Bit {
|
||||
bits := make([]Bit, 0)
|
||||
for _, b := range bs {
|
||||
bits = EncodeBitsWithUint(bits, uint(b), 8)
|
||||
}
|
||||
return bits
|
||||
bits := make([]Bit, 0)
|
||||
for _, b := range bs {
|
||||
bits = EncodeBitsWithUint(bits, uint(b), 8)
|
||||
}
|
||||
return bits
|
||||
}
|
||||
131
g/encoding/gbinary/gbinary_test.go
Normal file
131
g/encoding/gbinary/gbinary_test.go
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gbinary_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/encoding/gbinary"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testData = map[string]interface{}{
|
||||
//"nil": nil,
|
||||
"int": int(123),
|
||||
"int8": int8(-99),
|
||||
"int8.max": math.MaxInt8,
|
||||
"int16": int16(123),
|
||||
"int16.max": math.MaxInt16,
|
||||
"int32": int32(-199),
|
||||
"int32.max": math.MaxInt32,
|
||||
"int64": int64(123),
|
||||
"uint": uint(123),
|
||||
"uint8": uint8(123),
|
||||
"uint8.max": math.MaxUint8,
|
||||
"uint16": uint16(9999),
|
||||
"uint16.max": math.MaxUint16,
|
||||
"uint32": uint32(123),
|
||||
"uint64": uint64(123),
|
||||
"bool.true": true,
|
||||
"bool.false": false,
|
||||
"string": "hehe haha",
|
||||
"byte": []byte("hehe haha"),
|
||||
"float32": float32(123.456),
|
||||
"float32.max": math.MaxFloat32,
|
||||
"float64": float64(123.456),
|
||||
}
|
||||
|
||||
func TestEncodeAndDecode(t *testing.T) {
|
||||
for k, v := range testData {
|
||||
ve := gbinary.Encode(v)
|
||||
ve1 := gbinary.EncodeByLength(len(ve), v)
|
||||
|
||||
//t.Logf("%s:%v, encoded:%v\n", k, v, ve)
|
||||
switch v.(type) {
|
||||
case int:
|
||||
gtest.Assert(gbinary.DecodeToInt(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToInt(ve1), v)
|
||||
case int8:
|
||||
gtest.Assert(gbinary.DecodeToInt8(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToInt8(ve1), v)
|
||||
case int16:
|
||||
gtest.Assert(gbinary.DecodeToInt16(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToInt16(ve1), v)
|
||||
case int32:
|
||||
gtest.Assert(gbinary.DecodeToInt32(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToInt32(ve1), v)
|
||||
case int64:
|
||||
gtest.Assert(gbinary.DecodeToInt64(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToInt64(ve1), v)
|
||||
case uint:
|
||||
gtest.Assert(gbinary.DecodeToUint(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToUint(ve1), v)
|
||||
case uint8:
|
||||
gtest.Assert(gbinary.DecodeToUint8(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToUint8(ve1), v)
|
||||
case uint16:
|
||||
gtest.Assert(gbinary.DecodeToUint16(ve1), v)
|
||||
gtest.Assert(gbinary.DecodeToUint16(ve), v)
|
||||
case uint32:
|
||||
gtest.Assert(gbinary.DecodeToUint32(ve1), v)
|
||||
gtest.Assert(gbinary.DecodeToUint32(ve), v)
|
||||
case uint64:
|
||||
gtest.Assert(gbinary.DecodeToUint64(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToUint64(ve1), v)
|
||||
case bool:
|
||||
gtest.Assert(gbinary.DecodeToBool(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToBool(ve1), v)
|
||||
case string:
|
||||
gtest.Assert(gbinary.DecodeToString(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToString(ve1), v)
|
||||
case float32:
|
||||
gtest.Assert(gbinary.DecodeToFloat32(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToFloat32(ve1), v)
|
||||
case float64:
|
||||
gtest.Assert(gbinary.DecodeToFloat64(ve), v)
|
||||
gtest.Assert(gbinary.DecodeToFloat64(ve1), v)
|
||||
default:
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
res := make([]byte, len(ve))
|
||||
err := gbinary.Decode(ve, res)
|
||||
if err != nil {
|
||||
t.Errorf("test data: %s, %v, error:%v", k, v, err)
|
||||
}
|
||||
gtest.Assert(res, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Age int
|
||||
Url string
|
||||
}
|
||||
|
||||
func TestEncodeStruct(t *testing.T) {
|
||||
user := User{"wenzi1", 999, "www.baidu.com"}
|
||||
ve := gbinary.Encode(user)
|
||||
s := gbinary.DecodeToString(ve)
|
||||
gtest.Assert(string(s), s)
|
||||
}
|
||||
|
||||
var testBitData = []int{0, 99, 122, 129, 222, 999, 22322}
|
||||
|
||||
func TestBits(t *testing.T) {
|
||||
for i := range testBitData {
|
||||
bits := make([]gbinary.Bit, 0)
|
||||
res := gbinary.EncodeBits(bits, testBitData[i], 64)
|
||||
|
||||
gtest.Assert(gbinary.DecodeBits(res), testBitData[i])
|
||||
gtest.Assert(gbinary.DecodeBitsToUint(res), uint(testBitData[i]))
|
||||
|
||||
gtest.Assert(gbinary.DecodeBytesToBits(gbinary.EncodeBitsToBytes(res)), res)
|
||||
}
|
||||
|
||||
}
|
||||
42
g/encoding/gcompress/gcompress_test.go
Normal file
42
g/encoding/gcompress/gcompress_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gcompress_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/encoding/gcompress"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestZlib(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
src := "hello, world\n"
|
||||
dst := []byte{120, 156, 202, 72, 205, 201, 201, 215, 81, 40, 207, 47, 202, 73, 225, 2, 4, 0, 0, 255, 255, 33, 231, 4, 147}
|
||||
gtest.Assert(gcompress.Zlib([]byte(src)), dst)
|
||||
|
||||
gtest.Assert(gcompress.UnZlib(dst), []byte(src))
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestGzip(t *testing.T) {
|
||||
src := "Hello World!!"
|
||||
|
||||
gzip := []byte{
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0xf2, 0x48, 0xcd, 0xc9, 0xc9,
|
||||
0x57, 0x08, 0xcf, 0x2f, 0xca,
|
||||
0x49, 0x51, 0x54, 0x04, 0x04,
|
||||
0x00, 0x00, 0xff, 0xff, 0x9d,
|
||||
0x24, 0xa8, 0xd1, 0x0d, 0x00,
|
||||
0x00, 0x00,
|
||||
}
|
||||
|
||||
gtest.Assert(gcompress.Gzip([]byte(src)), gzip)
|
||||
|
||||
gtest.Assert(gcompress.UnGzip(gzip), []byte(src))
|
||||
}
|
||||
32
g/encoding/ghtml/ghtml_test.go
Normal file
32
g/encoding/ghtml/ghtml_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 ghtml_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/encoding/ghtml"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStripTags(t *testing.T) {
|
||||
src := `<p>Test paragraph.</p><!-- Comment --> <a href="#fragment">Other text</a>`
|
||||
dst := `Test paragraph. Other text`
|
||||
gtest.Assert(ghtml.StripTags(src), dst)
|
||||
}
|
||||
|
||||
func TestEntities(t *testing.T) {
|
||||
src := `A 'quote' "is" <b>bold</b>`
|
||||
dst := `A 'quote' "is" <b>bold</b>`
|
||||
gtest.Assert(ghtml.Entities(src), dst)
|
||||
gtest.Assert(ghtml.EntitiesDecode(dst), src)
|
||||
}
|
||||
|
||||
func TestSpecialChars(t *testing.T) {
|
||||
src := `A 'quote' "is" <b>bold</b>`
|
||||
dst := `A 'quote' "is" <b>bold</b>`
|
||||
gtest.Assert(ghtml.SpecialChars(src), dst)
|
||||
gtest.Assert(ghtml.SpecialCharsDecode(dst), src)
|
||||
}
|
||||
142
g/encoding/gtoml/gtoml_test.go
Normal file
142
g/encoding/gtoml/gtoml_test.go
Normal file
@ -0,0 +1,142 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gtoml_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/encoding/gparser"
|
||||
"github.com/gogf/gf/g/encoding/gtoml"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var tomlStr string = `
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
# MySQL数据库配置
|
||||
[redis]
|
||||
disk = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
`
|
||||
|
||||
var tomlErr string = `
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
# MySQL数据库配置
|
||||
[redis]
|
||||
dd = 11
|
||||
[redis]
|
||||
disk = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
`
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := make(map[string]string)
|
||||
m["toml"] = tomlStr
|
||||
res, err := gtoml.Encode(m)
|
||||
if err != nil {
|
||||
t.Errorf("encode failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := gparser.LoadContent(res)
|
||||
if err != nil {
|
||||
t.Errorf("parser failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
gtest.Assert(p.GetString("toml"), tomlStr)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
_, err := gtoml.Encode(tomlErr)
|
||||
if err == nil {
|
||||
t.Errorf("encode should be failed. %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := make(map[string]string)
|
||||
m["toml"] = tomlStr
|
||||
res, err := gtoml.Encode(m)
|
||||
if err != nil {
|
||||
t.Errorf("encode failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
decodeStr, err := gtoml.Decode(res)
|
||||
if err != nil {
|
||||
t.Errorf("decode failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
gtest.Assert(decodeStr.(map[string]interface{})["toml"], tomlStr)
|
||||
|
||||
decodeStr1 := make(map[string]interface{})
|
||||
err = gtoml.DecodeTo(res, &decodeStr1)
|
||||
if err != nil {
|
||||
t.Errorf("decodeTo failed. %v", err)
|
||||
return
|
||||
}
|
||||
gtest.Assert(decodeStr1["toml"], tomlStr)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
_, err := gtoml.Decode([]byte(tomlErr))
|
||||
if err == nil {
|
||||
t.Errorf("decode failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
decodeStr1 := make(map[string]interface{})
|
||||
err = gtoml.DecodeTo([]byte(tomlErr), &decodeStr1)
|
||||
if err == nil {
|
||||
t.Errorf("decodeTo failed. %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestToJson(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := make(map[string]string)
|
||||
m["toml"] = tomlStr
|
||||
res, err := gtoml.Encode(m)
|
||||
if err != nil {
|
||||
t.Errorf("encode failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonToml, err := gtoml.ToJson(res)
|
||||
if err != nil {
|
||||
t.Errorf("ToJson failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := gparser.LoadContent(res)
|
||||
if err != nil {
|
||||
t.Errorf("parser failed. %v", err)
|
||||
return
|
||||
}
|
||||
expectJson, err := p.ToJson()
|
||||
if err != nil {
|
||||
t.Errorf("parser ToJson failed. %v", err)
|
||||
return
|
||||
}
|
||||
gtest.Assert(jsonToml, expectJson)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
_, err := gtoml.ToJson([]byte(tomlErr))
|
||||
if err == nil {
|
||||
t.Errorf("ToJson failed. %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
92
g/encoding/gurl/url_test.go
Normal file
92
g/encoding/gurl/url_test.go
Normal file
@ -0,0 +1,92 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gurl_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/encoding/gurl"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var urlStr string = `https://golang.org/x/crypto?go-get=1 +`
|
||||
var urlEncode string = `https%3A%2F%2Fgolang.org%2Fx%2Fcrypto%3Fgo-get%3D1+%2B`
|
||||
var rawUrlEncode string = `https%3A%2F%2Fgolang.org%2Fx%2Fcrypto%3Fgo-get%3D1%20%2B`
|
||||
|
||||
func TestEncodeAndDecode(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gurl.Encode(urlStr), urlEncode)
|
||||
|
||||
res, err := gurl.Decode(urlEncode)
|
||||
if err != nil {
|
||||
t.Errorf("decode failed. %v", err)
|
||||
return
|
||||
}
|
||||
gtest.Assert(res, urlStr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRowEncodeAndDecode(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gurl.RawEncode(urlStr), rawUrlEncode)
|
||||
|
||||
res, err := gurl.RawDecode(rawUrlEncode)
|
||||
if err != nil {
|
||||
t.Errorf("decode failed. %v", err)
|
||||
return
|
||||
}
|
||||
gtest.Assert(res, urlStr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildQuery(t *testing.T) {
|
||||
src := url.Values{
|
||||
"a": {"a2", "a1"},
|
||||
"b": {"b2", "b1"},
|
||||
"c": {"c1", "c2"},
|
||||
}
|
||||
expect := "a=a2&a=a1&b=b2&b=b1&c=c1&c=c2"
|
||||
|
||||
gtest.Assert(gurl.BuildQuery(src), expect)
|
||||
}
|
||||
|
||||
func TestParseURL(t *testing.T) {
|
||||
src := `http://username:password@hostname:9090/path?arg=value#anchor`
|
||||
expect := map[string]string{
|
||||
"scheme": "http",
|
||||
"host": "hostname",
|
||||
"port": "9090",
|
||||
"user": "username",
|
||||
"pass": "password",
|
||||
"path": "/path",
|
||||
"query": "arg=value",
|
||||
"fragment": "anchor",
|
||||
}
|
||||
|
||||
gtest.Case(t, func() {
|
||||
component := 0
|
||||
for k, v := range []string{"all", "scheme", "host", "port", "user", "pass", "path", "query", "fragment"} {
|
||||
if v == "all" {
|
||||
component = -1
|
||||
} else {
|
||||
component = 1 << (uint(k - 1))
|
||||
}
|
||||
|
||||
res, err := gurl.ParseURL(src, component)
|
||||
if err != nil {
|
||||
t.Errorf("ParseURL failed. component:%v, err:%v", component, err)
|
||||
return
|
||||
}
|
||||
|
||||
if v == "all" {
|
||||
gtest.Assert(res, expect)
|
||||
} else {
|
||||
gtest.Assert(res[v], expect[v])
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
143
g/encoding/gyaml/gyaml_test.go
Normal file
143
g/encoding/gyaml/gyaml_test.go
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gyaml_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/encoding/gparser"
|
||||
"github.com/gogf/gf/g/encoding/gyaml"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var yamlStr string = `
|
||||
#即表示url属性值;
|
||||
url: http://www.wolfcode.cn
|
||||
#即表示server.host属性的值;
|
||||
server:
|
||||
host: http://www.wolfcode.cn
|
||||
#数组,即表示server为[a,b,c]
|
||||
server:
|
||||
- 120.168.117.21
|
||||
- 120.168.117.22
|
||||
- 120.168.117.23
|
||||
#常量
|
||||
pi: 3.14 #定义一个数值3.14
|
||||
hasChild: true #定义一个boolean值
|
||||
name: '你好YAML' #定义一个字符串
|
||||
`
|
||||
|
||||
var yamlErr string = `
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
# MySQL数据库配置
|
||||
[redis]
|
||||
dd = 11
|
||||
[redis]
|
||||
disk = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
`
|
||||
|
||||
func TestEncode(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := make(map[string]string)
|
||||
m["yaml"] = yamlStr
|
||||
res, err := gyaml.Encode(m)
|
||||
if err != nil {
|
||||
t.Errorf("encode failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := gparser.LoadContent(res)
|
||||
if err != nil {
|
||||
t.Errorf("parser failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
gtest.Assert(p.GetString("yaml"), yamlStr)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestDecode(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := make(map[string]string)
|
||||
m["yaml"] = yamlStr
|
||||
res, err := gyaml.Encode(m)
|
||||
if err != nil {
|
||||
t.Errorf("encode failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
decodeStr, err := gyaml.Decode(res)
|
||||
if err != nil {
|
||||
t.Errorf("decode failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
gtest.Assert(decodeStr.(map[string]interface{})["yaml"], yamlStr)
|
||||
|
||||
decodeStr1 := make(map[string]interface{})
|
||||
err = gyaml.DecodeTo(res, &decodeStr1)
|
||||
if err != nil {
|
||||
t.Errorf("decodeTo failed. %v", err)
|
||||
return
|
||||
}
|
||||
gtest.Assert(decodeStr1["yaml"], yamlStr)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
_, err := gyaml.Decode([]byte(yamlErr))
|
||||
if err == nil {
|
||||
t.Errorf("decode failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
decodeStr1 := make(map[string]interface{})
|
||||
err = gyaml.DecodeTo([]byte(yamlErr), &decodeStr1)
|
||||
if err == nil {
|
||||
t.Errorf("decodeTo failed. %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestToJson(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := make(map[string]string)
|
||||
m["yaml"] = yamlStr
|
||||
res, err := gyaml.Encode(m)
|
||||
if err != nil {
|
||||
t.Errorf("encode failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonyaml, err := gyaml.ToJson(res)
|
||||
if err != nil {
|
||||
t.Errorf("ToJson failed. %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
p, err := gparser.LoadContent(res)
|
||||
if err != nil {
|
||||
t.Errorf("parser failed. %v", err)
|
||||
return
|
||||
}
|
||||
expectJson, err := p.ToJson()
|
||||
if err != nil {
|
||||
t.Errorf("parser ToJson failed. %v", err)
|
||||
return
|
||||
}
|
||||
gtest.Assert(jsonyaml, expectJson)
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
_, err := gyaml.ToJson([]byte(yamlErr))
|
||||
if err == nil {
|
||||
t.Errorf("ToJson failed. %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -18,7 +18,6 @@ type View struct {
|
||||
mu sync.RWMutex // 并发互斥锁
|
||||
view *gview.View // 底层视图对象
|
||||
data gview.Params // 视图数据/模板变量
|
||||
fmap gview.FuncMap // 绑定的模板函数
|
||||
response *ghttp.Response // 数据返回对象
|
||||
}
|
||||
|
||||
@ -27,7 +26,6 @@ func NewView(w *ghttp.Response) *View {
|
||||
return &View {
|
||||
view : gins.View(),
|
||||
data : make(gview.Params),
|
||||
fmap : make(gview.FuncMap),
|
||||
response : w,
|
||||
}
|
||||
}
|
||||
@ -48,26 +46,19 @@ func (view *View) Assign(key string, value interface{}) {
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// 绑定自定义模板函数
|
||||
func (view *View) BindFunc(name string, function interface{}){
|
||||
view.mu.Lock()
|
||||
view.fmap[name] = function
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// 解析模板,并返回解析后的内容
|
||||
func (view *View) Parse(file string) ([]byte, error) {
|
||||
func (view *View) Parse(file string) (string, error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
buffer, err := view.response.ParseTpl(file, view.data, view.fmap)
|
||||
buffer, err := view.response.ParseTpl(file, view.data)
|
||||
return buffer, err
|
||||
}
|
||||
|
||||
// 直接解析模板内容,并返回解析后的内容
|
||||
func (view *View) ParseContent(content string) ([]byte, error) {
|
||||
func (view *View) ParseContent(content string) (string, error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
buffer, err := view.response.ParseTplContent(content, view.data, view.fmap)
|
||||
buffer, err := view.response.ParseTplContent(content, view.data)
|
||||
return buffer, err
|
||||
}
|
||||
|
||||
|
||||
@ -38,8 +38,8 @@ func View(name...string) *gview.View {
|
||||
}
|
||||
|
||||
// Config returns an instance of config object with specified name.
|
||||
func Config(file...string) *gcfg.Config {
|
||||
return gins.Config(file...)
|
||||
func Config(name...string) *gcfg.Config {
|
||||
return gins.Config(name...)
|
||||
}
|
||||
|
||||
// Database returns an instance of database ORM object with specified configuration group name.
|
||||
|
||||
@ -12,85 +12,85 @@ func Get(url string) (*ClientResponse, error) {
|
||||
return DoRequest("GET", url)
|
||||
}
|
||||
|
||||
func Put(url string, data...string) (*ClientResponse, error) {
|
||||
func Put(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return DoRequest("PUT", url, data...)
|
||||
}
|
||||
|
||||
func Post(url string, data...string) (*ClientResponse, error) {
|
||||
func Post(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return DoRequest("POST", url, data...)
|
||||
}
|
||||
|
||||
func Delete(url string, data...string) (*ClientResponse, error) {
|
||||
func Delete(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return DoRequest("DELETE", url, data...)
|
||||
}
|
||||
|
||||
func Head(url string, data...string) (*ClientResponse, error) {
|
||||
func Head(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return DoRequest("HEAD", url, data...)
|
||||
}
|
||||
|
||||
func Patch(url string, data...string) (*ClientResponse, error) {
|
||||
func Patch(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return DoRequest("PATCH", url, data...)
|
||||
}
|
||||
|
||||
func Connect(url string, data...string) (*ClientResponse, error) {
|
||||
func Connect(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return DoRequest("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
func Options(url string, data...string) (*ClientResponse, error) {
|
||||
func Options(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return DoRequest("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
func Trace(url string, data...string) (*ClientResponse, error) {
|
||||
func Trace(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return DoRequest("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// 该方法支持二进制提交数据
|
||||
func DoRequest(method, url string, data...string) (*ClientResponse, error) {
|
||||
func DoRequest(method, url string, data...interface{}) (*ClientResponse, error) {
|
||||
return NewClient().DoRequest(method, url, data...)
|
||||
}
|
||||
|
||||
// GET请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func GetContent(url string, data...string) string {
|
||||
func GetContent(url string, data...interface{}) string {
|
||||
return RequestContent("GET", url, data...)
|
||||
}
|
||||
|
||||
// PUT请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func PutContent(url string, data...string) string {
|
||||
func PutContent(url string, data...interface{}) string {
|
||||
return RequestContent("PUT", url, data...)
|
||||
}
|
||||
|
||||
// POST请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func PostContent(url string, data...string) string {
|
||||
func PostContent(url string, data...interface{}) string {
|
||||
return RequestContent("POST", url, data...)
|
||||
}
|
||||
|
||||
// DELETE请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func DeleteContent(url string, data...string) string {
|
||||
func DeleteContent(url string, data...interface{}) string {
|
||||
return RequestContent("DELETE", url, data...)
|
||||
}
|
||||
|
||||
func HeadContent(url string, data...string) string {
|
||||
func HeadContent(url string, data...interface{}) string {
|
||||
return RequestContent("HEAD", url, data...)
|
||||
}
|
||||
|
||||
func PatchContent(url string, data...string) string {
|
||||
func PatchContent(url string, data...interface{}) string {
|
||||
return RequestContent("PATCH", url, data...)
|
||||
}
|
||||
|
||||
func ConnectContent(url string, data...string) string {
|
||||
func ConnectContent(url string, data...interface{}) string {
|
||||
return RequestContent("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
func OptionsContent(url string, data...string) string {
|
||||
func OptionsContent(url string, data...interface{}) string {
|
||||
return RequestContent("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
func TraceContent(url string, data...string) string {
|
||||
func TraceContent(url string, data...interface{}) string {
|
||||
return RequestContent("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// 请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func RequestContent(method string, url string, data...string) string {
|
||||
func RequestContent(method string, url string, data...interface{}) string {
|
||||
return NewClient().DoRequestContent(method, url, data...)
|
||||
}
|
||||
|
||||
|
||||
@ -69,20 +69,20 @@ func (c *Client) Get(url string) (*ClientResponse, error) {
|
||||
}
|
||||
|
||||
// PUT请求
|
||||
func (c *Client) Put(url string, data...string) (*ClientResponse, error) {
|
||||
func (c *Client) Put(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return c.DoRequest("PUT", url, data...)
|
||||
}
|
||||
|
||||
// POST请求提交数据,默认使用表单方式提交数据(绝大部分场景下也是如此)。
|
||||
// 如果服务端对Content-Type有要求,可使用Client对象进行请求,单独设置相关属性。
|
||||
// 支持文件上传,需要字段格式为:FieldName=@file:
|
||||
func (c *Client) Post(url string, data...string) (*ClientResponse, error) {
|
||||
func (c *Client) Post(url string, data...interface{}) (*ClientResponse, error) {
|
||||
if len(c.prefix) > 0 {
|
||||
url = c.prefix + url
|
||||
}
|
||||
param := ""
|
||||
if len(data) > 0 {
|
||||
param = data[0]
|
||||
param = BuildParams(data[0])
|
||||
}
|
||||
req := (*http.Request)(nil)
|
||||
if strings.Contains(param, "@file:") {
|
||||
@ -178,72 +178,72 @@ func (c *Client) Post(url string, data...string) (*ClientResponse, error) {
|
||||
}
|
||||
|
||||
// DELETE请求
|
||||
func (c *Client) Delete(url string, data...string) (*ClientResponse, error) {
|
||||
func (c *Client) Delete(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return c.DoRequest("DELETE", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) Head(url string, data...string) (*ClientResponse, error) {
|
||||
func (c *Client) Head(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return c.DoRequest("HEAD", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) Patch(url string, data...string) (*ClientResponse, error) {
|
||||
func (c *Client) Patch(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return c.DoRequest("PATCH", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) Connect(url string, data...string) (*ClientResponse, error) {
|
||||
func (c *Client) Connect(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return c.DoRequest("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) Options(url string, data...string) (*ClientResponse, error) {
|
||||
func (c *Client) Options(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return c.DoRequest("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) Trace(url string, data...string) (*ClientResponse, error) {
|
||||
func (c *Client) Trace(url string, data...interface{}) (*ClientResponse, error) {
|
||||
return c.DoRequest("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// GET请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) GetContent(url string, data...string) string {
|
||||
func (c *Client) GetContent(url string, data...interface{}) string {
|
||||
return c.DoRequestContent("GET", url, data...)
|
||||
}
|
||||
|
||||
// PUT请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) PutContent(url string, data...string) string {
|
||||
func (c *Client) PutContent(url string, data...interface{}) string {
|
||||
return c.DoRequestContent("PUT", url, data...)
|
||||
}
|
||||
|
||||
// POST请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) PostContent(url string, data...string) string {
|
||||
func (c *Client) PostContent(url string, data...interface{}) string {
|
||||
return c.DoRequestContent("POST", url, data...)
|
||||
}
|
||||
|
||||
// DELETE请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) DeleteContent(url string, data...string) string {
|
||||
func (c *Client) DeleteContent(url string, data...interface{}) string {
|
||||
return c.DoRequestContent("DELETE", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) HeadContent(url string, data...string) string {
|
||||
func (c *Client) HeadContent(url string, data...interface{}) string {
|
||||
return c.DoRequestContent("HEAD", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) PatchContent(url string, data...string) string {
|
||||
func (c *Client) PatchContent(url string, data...interface{}) string {
|
||||
return c.DoRequestContent("PATCH", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) ConnectContent(url string, data...string) string {
|
||||
func (c *Client) ConnectContent(url string, data...interface{}) string {
|
||||
return c.DoRequestContent("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) OptionsContent(url string, data...string) string {
|
||||
func (c *Client) OptionsContent(url string, data...interface{}) string {
|
||||
return c.DoRequestContent("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) TraceContent(url string, data...string) string {
|
||||
func (c *Client) TraceContent(url string, data...interface{}) string {
|
||||
return c.DoRequestContent("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// 请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) DoRequestContent(method string, url string, data...string) string {
|
||||
func (c *Client) DoRequestContent(method string, url string, data...interface{}) string {
|
||||
response, err := c.DoRequest(method, url, data...)
|
||||
if err != nil {
|
||||
return ""
|
||||
@ -253,7 +253,7 @@ func (c *Client) DoRequestContent(method string, url string, data...string) stri
|
||||
}
|
||||
|
||||
// 请求并返回response对象,该方法支持二进制提交数据
|
||||
func (c *Client) DoRequest(method, url string, data...string) (*ClientResponse, error) {
|
||||
func (c *Client) DoRequest(method, url string, data...interface{}) (*ClientResponse, error) {
|
||||
if strings.EqualFold("POST", method) {
|
||||
return c.Post(url, data...)
|
||||
}
|
||||
@ -262,7 +262,7 @@ func (c *Client) DoRequest(method, url string, data...string) (*ClientResponse,
|
||||
}
|
||||
param := ""
|
||||
if len(data) > 0 {
|
||||
param = data[0]
|
||||
param = BuildParams(data[0])
|
||||
}
|
||||
req, err := http.NewRequest(strings.ToUpper(method), url, bytes.NewReader([]byte(param)))
|
||||
if err != nil {
|
||||
|
||||
@ -8,21 +8,28 @@ package ghttp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/encoding/gurl"
|
||||
"strings"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 构建请求参数,将参数进行urlencode编码
|
||||
func BuildParams(params map[string]string) string {
|
||||
var s string
|
||||
for k, v := range params {
|
||||
if len(s) > 0 {
|
||||
s += "&"
|
||||
// 构建请求参数,参数支持任意数据类型,常见参数类型为string/map。
|
||||
// 如果参数为map类型,参数值将会进行urlencode编码。
|
||||
func BuildParams(params interface{}) (encodedParamStr string) {
|
||||
m := gconv.Map(params)
|
||||
if len(m) == 0 {
|
||||
return gconv.String(params)
|
||||
}
|
||||
s := ""
|
||||
for k, v := range m {
|
||||
if len(encodedParamStr) > 0 {
|
||||
encodedParamStr += "&"
|
||||
}
|
||||
if len(v) > 6 && strings.Compare(v[0 : 6], "@file:") == 0 {
|
||||
s += k + "=" + v
|
||||
s = gconv.String(v)
|
||||
if len(s) > 6 && strings.Compare(s[0 : 6], "@file:") == 0 {
|
||||
encodedParamStr += k + "=" + s
|
||||
} else {
|
||||
s += k + "=" + gurl.Encode(v)
|
||||
encodedParamStr += k + "=" + gurl.Encode(s)
|
||||
}
|
||||
}
|
||||
return s
|
||||
return
|
||||
}
|
||||
@ -13,13 +13,9 @@ import (
|
||||
)
|
||||
|
||||
// 展示模板,可以给定模板参数,及临时的自定义模板函数
|
||||
func (r *Response) WriteTpl(tpl string, params map[string]interface{}, funcMap...map[string]interface{}) error {
|
||||
fmap := make(gview.FuncMap)
|
||||
if len(funcMap) > 0 {
|
||||
fmap = funcMap[0]
|
||||
}
|
||||
if b, err := r.ParseTpl(tpl, params, fmap); err != nil {
|
||||
r.Write("Tpl Parsing Error: " + err.Error())
|
||||
func (r *Response) WriteTpl(tpl string, params...gview.Params) error {
|
||||
if b, err := r.ParseTpl(tpl, params...); err != nil {
|
||||
r.Write("Template Parsing Error: " + err.Error())
|
||||
return err
|
||||
} else {
|
||||
r.Write(b)
|
||||
@ -28,13 +24,9 @@ func (r *Response) WriteTpl(tpl string, params map[string]interface{}, funcMap..
|
||||
}
|
||||
|
||||
// 展示模板内容,可以给定模板参数,及临时的自定义模板函数
|
||||
func (r *Response) WriteTplContent(content string, params map[string]interface{}, funcMap...map[string]interface{}) error {
|
||||
fmap := make(gview.FuncMap)
|
||||
if len(funcMap) > 0 {
|
||||
fmap = funcMap[0]
|
||||
}
|
||||
if b, err := r.ParseTplContent(content, params, fmap); err != nil {
|
||||
r.Write("Tpl Parsing Error: " + err.Error())
|
||||
func (r *Response) WriteTplContent(content string, params...gview.Params) error {
|
||||
if b, err := r.ParseTplContent(content, params...); err != nil {
|
||||
r.Write("Template Parsing Error: " + err.Error())
|
||||
return err
|
||||
} else {
|
||||
r.Write(b)
|
||||
@ -43,61 +35,27 @@ func (r *Response) WriteTplContent(content string, params map[string]interface{}
|
||||
}
|
||||
|
||||
// 解析模板文件,并返回模板内容
|
||||
func (r *Response) ParseTpl(tpl string, params gview.Params, funcMap...map[string]interface{}) ([]byte, error) {
|
||||
fmap := make(gview.FuncMap)
|
||||
if len(funcMap) > 0 {
|
||||
fmap = funcMap[0]
|
||||
}
|
||||
return gins.View().Parse(tpl, r.buildInVars(params), r.buildInFuncs(fmap))
|
||||
func (r *Response) ParseTpl(tpl string, params...gview.Params) (string, error) {
|
||||
return gins.View().Parse(tpl, r.buildInVars(params...))
|
||||
}
|
||||
|
||||
// 解析并返回模板内容
|
||||
func (r *Response) ParseTplContent(content string, params gview.Params, funcMap...map[string]interface{}) ([]byte, error) {
|
||||
fmap := make(gview.FuncMap)
|
||||
if len(funcMap) > 0 {
|
||||
fmap = funcMap[0]
|
||||
}
|
||||
return gins.View().ParseContent(content, r.buildInVars(params), r.buildInFuncs(fmap))
|
||||
func (r *Response) ParseTplContent(content string, params...gview.Params) (string, error) {
|
||||
return gins.View().ParseContent(content, r.buildInVars(params...))
|
||||
}
|
||||
|
||||
// 内置变量
|
||||
func (r *Response) buildInVars(params map[string]interface{}) map[string]interface{} {
|
||||
if params == nil {
|
||||
params = make(map[string]interface{})
|
||||
}
|
||||
c := gins.Config()
|
||||
if c.GetFilePath() != "" {
|
||||
params["Config"] = c.GetMap("")
|
||||
// 内置变量/对象
|
||||
func (r *Response) buildInVars(params...map[string]interface{}) map[string]interface{} {
|
||||
vars := map[string]interface{}(nil)
|
||||
if len(params) > 0 {
|
||||
vars = params[0]
|
||||
} else {
|
||||
params["Config"] = nil
|
||||
vars = make(map[string]interface{})
|
||||
}
|
||||
params["Cookie"] = r.request.Cookie.Map()
|
||||
params["Session"] = r.request.Session.Data()
|
||||
return params
|
||||
}
|
||||
|
||||
// 内置函数
|
||||
func (r *Response) buildInFuncs(funcMap map[string]interface{}) map[string]interface{} {
|
||||
if funcMap == nil {
|
||||
funcMap = make(map[string]interface{})
|
||||
}
|
||||
funcMap["get"] = r.funcGet
|
||||
funcMap["post"] = r.funcPost
|
||||
funcMap["request"] = r.funcRequest
|
||||
return funcMap
|
||||
}
|
||||
|
||||
// 模板内置函数: get
|
||||
func (r *Response) funcGet(key string, def...string) string {
|
||||
return r.request.GetQueryString(key, def...)
|
||||
}
|
||||
|
||||
// 模板内置函数: post
|
||||
func (r *Response) funcPost(key string, def...string) string {
|
||||
return r.request.GetPostString(key, def...)
|
||||
}
|
||||
|
||||
// 模板内置函数: request
|
||||
func (r *Response) funcRequest(key string, def...string) string {
|
||||
return r.request.Get(key, def...)
|
||||
vars["Config"] = gins.Config().GetMap("")
|
||||
vars["Cookie"] = r.request.Cookie.Map()
|
||||
vars["Session"] = r.request.Session.Map()
|
||||
vars["Get"] = r.request.GetQueryMap()
|
||||
vars["Post"] = r.request.GetPostMap()
|
||||
return vars
|
||||
}
|
||||
@ -15,7 +15,8 @@ import (
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/os/gcache"
|
||||
"github.com/gogf/gf/g/os/genv"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gproc"
|
||||
"github.com/gogf/gf/g/os/gtimer"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
@ -108,9 +109,9 @@ const (
|
||||
HOOK_BEFORE_OUTPUT = "BeforeOutput"
|
||||
HOOK_AFTER_OUTPUT = "AfterOutput"
|
||||
|
||||
// deprecated.
|
||||
// Deprecated.
|
||||
HOOK_BEFORE_CLOSE = "BeforeClose"
|
||||
// deprecated.
|
||||
// Deprecated.
|
||||
HOOK_AFTER_CLOSE = "AfterClose"
|
||||
|
||||
HTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
|
||||
@ -188,10 +189,10 @@ func serverProcessInit() {
|
||||
go handleProcessMessage()
|
||||
}
|
||||
|
||||
// 是否处于开发环境
|
||||
//if gfile.MainPkgPath() != "" {
|
||||
// glog.Debug("GF notices that you're in develop environment, so error logs are auto enabled to stdout.")
|
||||
//}
|
||||
// 是否处于开发环境,这里调用该方法初始化main包路径值,
|
||||
// 防止异步服务goroutine获取main包路径失败,
|
||||
// 该方法只有在main协程中才会执行。
|
||||
gfile.MainPkgPath()
|
||||
}
|
||||
|
||||
// 获取/创建一个默认配置的HTTP Server(默认监听端口是80)
|
||||
|
||||
@ -47,7 +47,7 @@ func (d *Domain) Group(prefix...string) *RouterGroup {
|
||||
}
|
||||
|
||||
// 执行分组路由批量绑定
|
||||
func (g *RouterGroup) Bind(group string, items []GroupItem) {
|
||||
func (g *RouterGroup) Bind(items []GroupItem) {
|
||||
for _, item := range items {
|
||||
if len(item) < 3 {
|
||||
glog.Fatalfln("invalid router item: %s", item)
|
||||
|
||||
@ -50,7 +50,7 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) {
|
||||
if _, ok := v.Method(i).Interface().(func()); !ok {
|
||||
if len(methodMap) > 0 {
|
||||
// 指定的方法名称注册,那么需要使用错误提示
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required`,
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
pkgPath, ctlName, mname, v.Method(i).Type().String())
|
||||
} else {
|
||||
// 否则只是Debug提示
|
||||
@ -108,7 +108,7 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, method string
|
||||
ctlName = fmt.Sprintf(`(%s)`, ctlName)
|
||||
}
|
||||
if _, ok := fval.Interface().(func()); !ok {
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required`,
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
pkgPath, ctlName, mname, fval.Type().String())
|
||||
return
|
||||
}
|
||||
@ -147,7 +147,7 @@ func (s *Server)BindControllerRest(pattern string, c Controller) {
|
||||
ctlName = fmt.Sprintf(`(%s)`, ctlName)
|
||||
}
|
||||
if _, ok := v.Method(i).Interface().(func()); !ok {
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required`,
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
pkgPath, ctlName, mname, v.Method(i).Type().String())
|
||||
return
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) {
|
||||
if !ok {
|
||||
if len(methodMap) > 0 {
|
||||
// 指定的方法名称注册,那么需要使用错误提示
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required`,
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, mname, v.Method(i).Type().String())
|
||||
} else {
|
||||
// 否则只是Debug提示
|
||||
@ -127,7 +127,7 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string)
|
||||
}
|
||||
faddr, ok := fval.Interface().(func(*Request))
|
||||
if !ok {
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required`,
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, mname, fval.Type().String())
|
||||
return
|
||||
}
|
||||
@ -174,7 +174,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) {
|
||||
}
|
||||
faddr, ok := v.Method(i).Interface().(func(*Request))
|
||||
if !ok {
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required`,
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, mname, v.Method(i).Type().String())
|
||||
continue
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ func (s *Session) Id() string {
|
||||
}
|
||||
|
||||
// 获取当前session所有数据
|
||||
func (s *Session) Data() map[string]interface{} {
|
||||
func (s *Session) Map() map[string]interface{} {
|
||||
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
|
||||
s.init()
|
||||
return s.data.Map()
|
||||
|
||||
@ -123,7 +123,7 @@ func Test_Router_Basic2(t *testing.T) {
|
||||
obj := new(GroupObject)
|
||||
ctl := new(GroupController)
|
||||
// 分组路由批量注册
|
||||
s.Group("/api").Bind("/api", []ghttp.GroupItem{
|
||||
s.Group("/api").Bind([]ghttp.GroupItem{
|
||||
{"ALL", "/handler", Handler},
|
||||
{"ALL", "/ctl", ctl},
|
||||
{"GET", "/ctl/my-show", ctl, "Show"},
|
||||
|
||||
@ -18,13 +18,15 @@ import (
|
||||
type Conn struct {
|
||||
conn net.Conn // 底层tcp对象
|
||||
reader *bufio.Reader // 当前链接的缓冲读取对象
|
||||
buffer []byte // 读取缓冲区(用于数据读取时的缓冲区处理)
|
||||
recvDeadline time.Time // 读取超时时间
|
||||
sendDeadline time.Time // 写入超时时间
|
||||
recvBufferWait time.Duration // 读取全部缓冲区数据时,读取完毕后的写入等待间隔
|
||||
}
|
||||
|
||||
const (
|
||||
gRECV_ALL_WAIT_TIMEOUT = time.Millisecond // 读取全部缓冲数据时,没有缓冲数据时的等待间隔
|
||||
// 读取全部缓冲数据时,没有缓冲数据时的等待间隔
|
||||
gRECV_ALL_WAIT_TIMEOUT = time.Millisecond
|
||||
)
|
||||
|
||||
// 创建TCP链接
|
||||
@ -103,7 +105,7 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
// 缓冲区数据写入等待处理。
|
||||
// 如果已经读取到数据(这点很关键,表明缓冲区已经有数据,剩下的操作就是将所有数据读取完毕),
|
||||
// 那么可以设置读取全部缓冲数据的超时时间;如果没有接收到任何数据,那么将会进入读取阻塞(或者自定义的超时阻塞);
|
||||
// 仅对读取全部缓冲数据操作有效
|
||||
// 仅对读取全部缓冲区数据操作有效
|
||||
if length <= 0 && index > 0 {
|
||||
bufferWait = true
|
||||
c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait))
|
||||
@ -117,9 +119,14 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// 如果长度超过了自定义的读取缓冲区,那么自动增长
|
||||
if index >= gDEFAULT_READ_BUFFER_SIZE {
|
||||
// 如果长度超过了自定义的读取缓冲区,那么自动增长
|
||||
buffer = append(buffer, make([]byte, gDEFAULT_READ_BUFFER_SIZE)...)
|
||||
} else {
|
||||
// 如果第一次读取的数据并未达到缓冲变量长度,那么直接返回
|
||||
if !bufferWait {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -234,8 +241,8 @@ func (c *Conn) SetSendDeadline(t time.Time) error {
|
||||
|
||||
// 读取全部缓冲区数据时,读取完毕后的写入等待间隔,如果超过该等待时间后仍无可读数据,那么读取操作返回。
|
||||
// 该时间间隔不能设置得太大,会影响Recv读取时长(默认为1毫秒)。
|
||||
func (c *Conn) SetRecvBufferWait(d time.Duration) {
|
||||
c.recvBufferWait = d
|
||||
func (c *Conn) SetRecvBufferWait(bufferWaitDuration time.Duration) {
|
||||
c.recvBufferWait = bufferWaitDuration
|
||||
}
|
||||
|
||||
func (c *Conn) LocalAddr() net.Addr {
|
||||
|
||||
115
g/net/gtcp/gtcp_conn_pkg.go
Normal file
115
g/net/gtcp/gtcp_conn_pkg.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). 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 gtcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// 允许最大的简单协议包大小(byte), 15MB
|
||||
PKG_MAX_SIZE = 0xFFFFFF
|
||||
// 消息包头大小: "总长度"3字节+"校验码"4字节
|
||||
PKG_HEADER_SIZE = 7
|
||||
)
|
||||
|
||||
// 根据简单协议发送数据包。
|
||||
// 简单协议数据格式:总长度(24bit)|校验码(32bit)|数据(变长)。
|
||||
// 注意:
|
||||
// 1. "总长度"包含自身3字节及"校验码"4字节。
|
||||
// 2. 由于"总长度"为3字节,并且使用的BigEndian字节序,因此最后返回的buffer使用了buffer[1:]。
|
||||
func (c *Conn) SendPkg(data []byte, retry...Retry) error {
|
||||
length := uint32(len(data))
|
||||
if length > PKG_MAX_SIZE - PKG_HEADER_SIZE {
|
||||
return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, PKG_MAX_SIZE - PKG_HEADER_SIZE))
|
||||
}
|
||||
buffer := make([]byte, PKG_HEADER_SIZE + 1 + len(data))
|
||||
copy(buffer[PKG_HEADER_SIZE + 1 : ], data)
|
||||
binary.BigEndian.PutUint32(buffer[0 : ], PKG_HEADER_SIZE + length)
|
||||
binary.BigEndian.PutUint32(buffer[4 : ], Checksum(data))
|
||||
//fmt.Println("SendPkg:", buffer[1:])
|
||||
return c.Send(buffer[1:], retry...)
|
||||
}
|
||||
|
||||
// 简单协议: 带超时时间的数据发送
|
||||
func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error {
|
||||
c.SetSendDeadline(time.Now().Add(timeout))
|
||||
defer c.SetSendDeadline(time.Time{})
|
||||
return c.SendPkg(data, retry...)
|
||||
}
|
||||
|
||||
// 简单协议: 发送数据并等待接收返回数据
|
||||
func (c *Conn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) {
|
||||
if err := c.SendPkg(data, retry...); err == nil {
|
||||
return c.RecvPkg(retry...)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 简单协议: 发送数据并等待接收返回数据(带返回超时等待时间)
|
||||
func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
if err := c.SendPkg(data, retry...); err == nil {
|
||||
return c.RecvPkgWithTimeout(timeout, retry...)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 简单协议: 获取一个数据包。
|
||||
func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) {
|
||||
var temp []byte
|
||||
var length uint32
|
||||
for {
|
||||
// 先根据对象的缓冲区数据进行计算
|
||||
for {
|
||||
if len(c.buffer) >= PKG_HEADER_SIZE {
|
||||
// 注意"总长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0
|
||||
length = binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]})
|
||||
// 解析的大小是否符合规范
|
||||
if length == 0 || length + PKG_HEADER_SIZE > PKG_MAX_SIZE {
|
||||
c.buffer = c.buffer[1:]
|
||||
continue
|
||||
}
|
||||
// 不满足包大小,需要继续读取
|
||||
if uint32(len(c.buffer)) < length {
|
||||
break
|
||||
}
|
||||
// 数据校验
|
||||
if binary.BigEndian.Uint32(c.buffer[3 : PKG_HEADER_SIZE]) != Checksum(c.buffer[PKG_HEADER_SIZE : length]) {
|
||||
c.buffer = c.buffer[1:]
|
||||
continue
|
||||
}
|
||||
result = c.buffer[PKG_HEADER_SIZE : length]
|
||||
c.buffer = c.buffer[length: ]
|
||||
return
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 读取系统socket缓冲区的完整数据
|
||||
temp, err = c.Recv(-1, retry...)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(temp) > 0 {
|
||||
c.buffer = append(c.buffer, temp...)
|
||||
}
|
||||
//fmt.Println("RecvPkg:", c.buffer)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 简单协议: 带超时时间的消息包获取
|
||||
func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
c.SetRecvDeadline(time.Now().Add(timeout))
|
||||
defer c.SetRecvDeadline(time.Time{})
|
||||
return c.RecvPkg(retry...)
|
||||
}
|
||||
@ -51,12 +51,12 @@ func Send(addr string, data []byte, retry...Retry) error {
|
||||
|
||||
// (面向短链接)发送数据并等待接收返回数据
|
||||
func SendRecv(addr string, data []byte, receive int, retry...Retry) ([]byte, error) {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendRecv(data, receive, retry...)
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendRecv(data, receive, retry...)
|
||||
}
|
||||
|
||||
// (面向短链接)带超时时间的数据发送
|
||||
|
||||
49
g/net/gtcp/gtcp_func_pkg.go
Normal file
49
g/net/gtcp/gtcp_func_pkg.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gtcp
|
||||
|
||||
import "time"
|
||||
|
||||
// 简单协议: (面向短链接)发送消息包
|
||||
func SendPkg(addr string, data []byte, retry...Retry) error {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendPkg(data, retry...)
|
||||
}
|
||||
|
||||
// 简单协议: (面向短链接)发送数据并等待接收返回数据
|
||||
func SendRecvPkg(addr string, data []byte, retry...Retry) ([]byte, error) {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendRecvPkg(data, retry...)
|
||||
}
|
||||
|
||||
// 简单协议: (面向短链接)带超时时间的数据发送
|
||||
func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) error {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendPkgWithTimeout(data, timeout, retry...)
|
||||
}
|
||||
|
||||
// 简单协议: (面向短链接)发送数据并等待接收返回数据(带返回超时等待时间)
|
||||
func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendRecvPkgWithTimeout(data, timeout, retry...)
|
||||
}
|
||||
72
g/net/gtcp/gtcp_pool_pkg.go
Normal file
72
g/net/gtcp/gtcp_pool_pkg.go
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). 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 gtcp
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 简单协议: (方法覆盖)发送数据
|
||||
func (c *PoolConn) SendPkg(data []byte, retry...Retry) (err error) {
|
||||
if err = c.Conn.SendPkg(data, retry...); err != nil && c.status == gCONN_STATUS_UNKNOWN {
|
||||
if v, e := c.pool.NewFunc(); e == nil {
|
||||
c.Conn = v.(*PoolConn).Conn
|
||||
err = c.Conn.SendPkg(data, retry...)
|
||||
} else {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
c.status = gCONN_STATUS_ERROR
|
||||
} else {
|
||||
c.status = gCONN_STATUS_ACTIVE
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 简单协议: (方法覆盖)接收数据
|
||||
func (c *PoolConn) RecvPkg(retry...Retry) ([]byte, error) {
|
||||
data, err := c.Conn.RecvPkg(retry...)
|
||||
if err != nil {
|
||||
c.status = gCONN_STATUS_ERROR
|
||||
} else {
|
||||
c.status = gCONN_STATUS_ACTIVE
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
|
||||
// 简单协议: (方法覆盖)带超时时间的数据获取
|
||||
func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
c.SetRecvDeadline(time.Now().Add(timeout))
|
||||
defer c.SetRecvDeadline(time.Time{})
|
||||
return c.RecvPkg(retry...)
|
||||
}
|
||||
|
||||
// 简单协议: (方法覆盖)带超时时间的数据发送
|
||||
func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error {
|
||||
c.SetSendDeadline(time.Now().Add(timeout))
|
||||
defer c.SetSendDeadline(time.Time{})
|
||||
return c.SendPkg(data, retry...)
|
||||
}
|
||||
|
||||
// 简单协议: (方法覆盖)发送数据并等待接收返回数据
|
||||
func (c *PoolConn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) {
|
||||
if err := c.SendPkg(data, retry...); err == nil {
|
||||
return c.RecvPkg(retry...)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 简单协议: (方法覆盖)发送数据并等待接收返回数据(带返回超时等待时间)
|
||||
func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
if err := c.SendPkg(data, retry...); err == nil {
|
||||
return c.RecvPkgWithTimeout(timeout, retry...)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -14,11 +14,12 @@ import (
|
||||
|
||||
// 封装的链接对象
|
||||
type Conn struct {
|
||||
conn *net.UDPConn // 底层链接对象
|
||||
raddr *net.UDPAddr // 远程地址
|
||||
recvDeadline time.Time // 读取超时时间
|
||||
sendDeadline time.Time // 写入超时时间
|
||||
recvBufferWait time.Duration // 读取全部缓冲区数据时,读取完毕后的写入等待间隔
|
||||
conn *net.UDPConn // 底层链接对象
|
||||
raddr *net.UDPAddr // 远程地址
|
||||
buffer []byte // 读取缓冲区(用于数据读取时的缓冲区处理)
|
||||
recvDeadline time.Time // 读取超时时间
|
||||
sendDeadline time.Time // 写入超时时间
|
||||
recvBufferWait time.Duration // 读取全部缓冲区数据时,读取完毕后的写入等待间隔
|
||||
}
|
||||
|
||||
const (
|
||||
@ -119,9 +120,14 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// 如果长度超过了自定义的读取缓冲区,那么自动增长
|
||||
if index >= gDEFAULT_READ_BUFFER_SIZE {
|
||||
// 如果长度超过了自定义的读取缓冲区,那么自动增长
|
||||
buffer = append(buffer, make([]byte, gDEFAULT_READ_BUFFER_SIZE)...)
|
||||
} else {
|
||||
// 如果第一次读取的数据并未达到缓冲变量长度,那么直接返回
|
||||
if !bufferWait {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
115
g/net/gudp/gudp_conn_pkg.go
Normal file
115
g/net/gudp/gudp_conn_pkg.go
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). 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 gudp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// 允许最大的简单协议包大小(byte), 15MB
|
||||
PKG_MAX_SIZE = 0xFFFFFF
|
||||
// 消息包头大小: "总长度"3字节+"校验码"4字节
|
||||
PKG_HEADER_SIZE = 7
|
||||
)
|
||||
|
||||
// 根据简单协议发送数据包。
|
||||
// 简单协议数据格式:总长度(24bit)|校验码(32bit)|数据(变长)。
|
||||
// 注意:
|
||||
// 1. "总长度"包含自身3字节及"校验码"4字节。
|
||||
// 2. 由于"总长度"为3字节,并且使用的BigEndian字节序,因此最后返回的buffer使用了buffer[1:]。
|
||||
func (c *Conn) SendPkg(data []byte, retry...Retry) error {
|
||||
length := uint32(len(data))
|
||||
if length > PKG_MAX_SIZE - PKG_HEADER_SIZE {
|
||||
return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, PKG_MAX_SIZE - PKG_HEADER_SIZE))
|
||||
}
|
||||
buffer := make([]byte, PKG_HEADER_SIZE + 1 + len(data))
|
||||
copy(buffer[PKG_HEADER_SIZE + 1 : ], data)
|
||||
binary.BigEndian.PutUint32(buffer[0 : ], PKG_HEADER_SIZE + length)
|
||||
binary.BigEndian.PutUint32(buffer[4 : ], Checksum(data))
|
||||
//fmt.Println("SendPkg:", buffer[1:])
|
||||
return c.Send(buffer[1:], retry...)
|
||||
}
|
||||
|
||||
// 简单协议: 带超时时间的数据发送
|
||||
func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error {
|
||||
c.SetSendDeadline(time.Now().Add(timeout))
|
||||
defer c.SetSendDeadline(time.Time{})
|
||||
return c.SendPkg(data, retry...)
|
||||
}
|
||||
|
||||
// 简单协议: 发送数据并等待接收返回数据
|
||||
func (c *Conn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) {
|
||||
if err := c.SendPkg(data, retry...); err == nil {
|
||||
return c.RecvPkg(retry...)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 简单协议: 发送数据并等待接收返回数据(带返回超时等待时间)
|
||||
func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
if err := c.SendPkg(data, retry...); err == nil {
|
||||
return c.RecvPkgWithTimeout(timeout, retry...)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 简单协议: 获取一个数据包。
|
||||
func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) {
|
||||
var temp []byte
|
||||
var length uint32
|
||||
for {
|
||||
// 先根据对象的缓冲区数据进行计算
|
||||
for {
|
||||
if len(c.buffer) >= PKG_HEADER_SIZE {
|
||||
// 注意"总长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0
|
||||
length = binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]})
|
||||
// 解析的大小是否符合规范
|
||||
if length == 0 || length + PKG_HEADER_SIZE > PKG_MAX_SIZE {
|
||||
c.buffer = c.buffer[1:]
|
||||
continue
|
||||
}
|
||||
// 不满足包大小,需要继续读取
|
||||
if uint32(len(c.buffer)) < length {
|
||||
break
|
||||
}
|
||||
// 数据校验
|
||||
if binary.BigEndian.Uint32(c.buffer[3 : PKG_HEADER_SIZE]) != Checksum(c.buffer[PKG_HEADER_SIZE : length]) {
|
||||
c.buffer = c.buffer[1:]
|
||||
continue
|
||||
}
|
||||
result = c.buffer[PKG_HEADER_SIZE : length]
|
||||
c.buffer = c.buffer[length: ]
|
||||
return
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 读取系统socket缓冲区的完整数据
|
||||
temp, err = c.Recv(-1, retry...)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if len(temp) > 0 {
|
||||
c.buffer = append(c.buffer, temp...)
|
||||
}
|
||||
//fmt.Println("RecvPkg:", c.buffer)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 简单协议: 带超时时间的消息包获取
|
||||
func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
c.SetRecvDeadline(time.Now().Add(timeout))
|
||||
defer c.SetRecvDeadline(time.Time{})
|
||||
return c.RecvPkg(retry...)
|
||||
}
|
||||
@ -10,6 +10,15 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// 常见的二进制数据校验方式,生成校验结果
|
||||
func Checksum(buffer []byte) uint32 {
|
||||
var checksum uint32
|
||||
for _, b := range buffer {
|
||||
checksum += uint32(b)
|
||||
}
|
||||
return checksum
|
||||
}
|
||||
|
||||
// 创建标准库UDP链接操作对象
|
||||
func NewNetConn(raddr string, laddr...string) (*net.UDPConn, error) {
|
||||
var err error
|
||||
|
||||
49
g/net/gudp/gudp_func_pkg.go
Normal file
49
g/net/gudp/gudp_func_pkg.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gudp
|
||||
|
||||
import "time"
|
||||
|
||||
// 简单协议: (面向短链接)发送消息包
|
||||
func SendPkg(addr string, data []byte, retry...Retry) error {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendPkg(data, retry...)
|
||||
}
|
||||
|
||||
// 简单协议: (面向短链接)发送数据并等待接收返回数据
|
||||
func SendRecvPkg(addr string, data []byte, retry...Retry) ([]byte, error) {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendRecvPkg(data, retry...)
|
||||
}
|
||||
|
||||
// 简单协议: (面向短链接)带超时时间的数据发送
|
||||
func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) error {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendPkgWithTimeout(data, timeout, retry...)
|
||||
}
|
||||
|
||||
// 简单协议: (面向短链接)发送数据并等待接收返回数据(带返回超时等待时间)
|
||||
func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendRecvPkgWithTimeout(data, timeout, retry...)
|
||||
}
|
||||
@ -88,7 +88,7 @@ func (c *Config) filePath(file...string) (path string) {
|
||||
for _, v := range array {
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v))
|
||||
index++
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s%s%s", index, v, gfile.Separator, "config"))
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v + gfile.Separator + "config"))
|
||||
index++
|
||||
}
|
||||
})
|
||||
@ -217,12 +217,10 @@ func (c *Config) GetFilePath(file...string) (path string) {
|
||||
}
|
||||
c.paths.RLockFunc(func(array []string) {
|
||||
for _, v := range array {
|
||||
// 查找当前目录
|
||||
if path, _ = gspath.Search(v, name); path != "" {
|
||||
break
|
||||
}
|
||||
// 查找当前目录下的config子目录
|
||||
if path, _ = gspath.Search(v, "config" + gfile.Separator + name); path != "" {
|
||||
if path, _ = gspath.Search(v + gfile.Separator + "config", name); path != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,60 +15,60 @@ import (
|
||||
)
|
||||
|
||||
// Console values.
|
||||
type gCmdValue struct {
|
||||
values []string
|
||||
type gCmdValue struct {
|
||||
values []string
|
||||
}
|
||||
|
||||
// Console options.
|
||||
type gCmdOption struct {
|
||||
options map[string]string
|
||||
options map[string]string
|
||||
}
|
||||
|
||||
var Value = &gCmdValue{} // Console values.
|
||||
var Option = &gCmdOption{} // Console options.
|
||||
var Value = &gCmdValue{} // Console values.
|
||||
var Option = &gCmdOption{} // Console options.
|
||||
var cmdFuncMap = make(map[string]func()) // Registered callback functions.
|
||||
|
||||
func init() {
|
||||
reg := regexp.MustCompile(`\-\-{0,1}(.+?)=(.+)`)
|
||||
Option.options = make(map[string]string)
|
||||
for i := 0; i < len(os.Args); i++ {
|
||||
result := reg.FindStringSubmatch(os.Args[i])
|
||||
if len(result) > 1 {
|
||||
Option.options[result[1]] = result[2]
|
||||
} else {
|
||||
Value.values = append(Value.values, os.Args[i])
|
||||
}
|
||||
}
|
||||
reg := regexp.MustCompile(`\-\-{0,1}(.+?)=(.+)`)
|
||||
Option.options = make(map[string]string)
|
||||
for i := 0; i < len(os.Args); i++ {
|
||||
result := reg.FindStringSubmatch(os.Args[i])
|
||||
if len(result) > 1 {
|
||||
Option.options[result[1]] = result[2]
|
||||
} else {
|
||||
Value.values = append(Value.values, os.Args[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BindHandle registers callback function <f> with <cmd>.
|
||||
func BindHandle (cmd string, f func()) {
|
||||
if _, ok := cmdFuncMap[cmd]; ok {
|
||||
glog.Fatal("duplicated handle for command:" + cmd)
|
||||
} else {
|
||||
cmdFuncMap[cmd] = f
|
||||
}
|
||||
func BindHandle(cmd string, f func()) {
|
||||
if _, ok := cmdFuncMap[cmd]; ok {
|
||||
glog.Fatal("duplicated handle for command:" + cmd)
|
||||
} else {
|
||||
cmdFuncMap[cmd] = f
|
||||
}
|
||||
}
|
||||
|
||||
// RunHandle executes the callback function registered by <cmd>.
|
||||
func RunHandle (cmd string) {
|
||||
if handle, ok := cmdFuncMap[cmd]; ok {
|
||||
handle()
|
||||
} else {
|
||||
glog.Fatal("no handle found for command:" + cmd)
|
||||
}
|
||||
func RunHandle(cmd string) {
|
||||
if handle, ok := cmdFuncMap[cmd]; ok {
|
||||
handle()
|
||||
} else {
|
||||
glog.Fatal("no handle found for command:" + cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// AutoRun automatically recognizes and executes the callback function
|
||||
// by value of index 0 (the first console parameter).
|
||||
func AutoRun () {
|
||||
if cmd := Value.Get(1); cmd != "" {
|
||||
if handle, ok := cmdFuncMap[cmd]; ok {
|
||||
handle()
|
||||
} else {
|
||||
glog.Fatal("no handle found for command:" + cmd)
|
||||
}
|
||||
} else {
|
||||
glog.Fatal("no command found")
|
||||
}
|
||||
func AutoRun() {
|
||||
if cmd := Value.Get(1); cmd != "" {
|
||||
if handle, ok := cmdFuncMap[cmd]; ok {
|
||||
handle()
|
||||
} else {
|
||||
glog.Fatal("no handle found for command:" + cmd)
|
||||
}
|
||||
} else {
|
||||
glog.Fatal("no command found")
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,18 +15,22 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 定时任务项
|
||||
// Timed task entry.
|
||||
type Entry struct {
|
||||
cron *Cron // 所属定时任务
|
||||
entry *gtimer.Entry // 定时器任务对象
|
||||
schedule *cronSchedule // 定时任务配置对象
|
||||
jobName string // 任务注册方法名称
|
||||
Name string // 定时任务名称
|
||||
Job func() // 注册定时任务方法
|
||||
Time time.Time // 注册时间
|
||||
cron *Cron // Cron object belonged to.
|
||||
entry *gtimer.Entry // Associated gtimer.Entry.
|
||||
schedule *cronSchedule // Timed schedule object.
|
||||
jobName string // Callback function name(address info).
|
||||
Name string // Entry name.
|
||||
Job func() `json:"-"` // Callback function.
|
||||
Time time.Time // Registered time.
|
||||
}
|
||||
|
||||
// 创建定时任务
|
||||
// addEntry creates and returns a new Entry object.
|
||||
// Param <job> is the callback function for timed task execution.
|
||||
// Param <singleton> specifies whether timed task executing in singleton mode.
|
||||
// Param <times> limits the times for timed task executing.
|
||||
// Param <name> names this entry for manual control.
|
||||
func (c *Cron) addEntry(pattern string, job func(), singleton bool, times int, name ... string) (*Entry, error) {
|
||||
schedule, err := newSchedule(pattern)
|
||||
if err != nil {
|
||||
|
||||
@ -10,22 +10,21 @@
|
||||
package gfile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -441,48 +440,29 @@ func homeWindows() (string, error) {
|
||||
// Available in develop environment.
|
||||
//
|
||||
// 获取入口函数文件所在目录(main包文件目录),
|
||||
// **仅对源码开发环境有效(即仅对生成该可执行文件的系统下有效)**
|
||||
// **仅对源码开发环境有效(即仅对生成该可执行文件的系统下有效)**。
|
||||
// 注意:该方法被第一次调用时,如果是在异步的goroutine中,该方法可能无法获取到main包路径。
|
||||
func MainPkgPath() string {
|
||||
path := mainPkgPath.Val()
|
||||
if path != "" {
|
||||
if path == "-" {
|
||||
return ""
|
||||
}
|
||||
return path
|
||||
}
|
||||
f := ""
|
||||
goroot := runtime.GOROOT()
|
||||
// runtime.GOROOT() 在windows下有可能是以'\'符号分隔,
|
||||
// 而 runtime.Caller(i) 获取到的文件路径却是以'/'符号分隔,
|
||||
// 因此这里统一转换为'/'符号再进行比较
|
||||
goroot = gstr.Replace(goroot, "\\", "/")
|
||||
for i := 1; i < 10000; i++ {
|
||||
if _, file, _, ok := runtime.Caller(i); ok {
|
||||
// 不包含go源码路径
|
||||
if file != "" && goroot != "" &&
|
||||
!gregex.IsMatchString("^" + goroot, file) &&
|
||||
!strings.EqualFold("<autogenerated>", file) {
|
||||
f = file
|
||||
}
|
||||
if gregex.IsMatchString(`package\s+main`, GetContents(file)) {
|
||||
path = Dir(file)
|
||||
mainPkgPath.Set(path)
|
||||
return path
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if f != "" {
|
||||
for {
|
||||
p := Dir(f)
|
||||
if p == f {
|
||||
break
|
||||
}
|
||||
// 会自动扫描源码,寻找main包
|
||||
if paths, err := ScanDir(p, "*.go"); err == nil && len(paths) > 0 {
|
||||
for _, path := range paths {
|
||||
if gregex.IsMatchString(`package\s+main`, GetContents(path)) {
|
||||
mainPkgPath.Set(p)
|
||||
return p
|
||||
}
|
||||
}
|
||||
}
|
||||
f = p
|
||||
}
|
||||
}
|
||||
// 找不到,下次不用再检索了
|
||||
mainPkgPath.Set("-")
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
@ -3,11 +3,10 @@
|
||||
// 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.
|
||||
//
|
||||
// @author john, zseeker
|
||||
|
||||
// Package glog implements powerful and easy-to-use levelled logging functionality.
|
||||
//
|
||||
// 日志模块, 直接文件/输出操作,没有异步逻辑,没有使用缓存或者通道
|
||||
package glog
|
||||
|
||||
import (
|
||||
@ -29,10 +28,10 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// default level for log
|
||||
// Default level for log
|
||||
defaultLevel = gtype.NewInt(LEVEL_ALL)
|
||||
|
||||
// default logger object, for package method usage
|
||||
// Default logger object, for package method usage
|
||||
logger = New()
|
||||
)
|
||||
|
||||
@ -41,8 +40,6 @@ func init() {
|
||||
}
|
||||
|
||||
// SetPath sets the directory path for file logging.
|
||||
//
|
||||
// 日志日志目录绝对路径.
|
||||
func SetPath(path string) {
|
||||
logger.SetPath(path)
|
||||
}
|
||||
@ -50,151 +47,113 @@ func SetPath(path string) {
|
||||
// SetFile sets the file name <pattern> for file logging.
|
||||
// Datetime pattern can be used in <pattern>, eg: access-{Ymd}.log.
|
||||
// The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log
|
||||
//
|
||||
// 日志文件名称.
|
||||
func SetFile(pattern string) {
|
||||
logger.SetFile(pattern)
|
||||
}
|
||||
|
||||
// SetLevel sets the default logging level.
|
||||
//
|
||||
// 设置全局的日志记录等级.
|
||||
func SetLevel(level int) {
|
||||
logger.SetLevel(level)
|
||||
defaultLevel.Set(level)
|
||||
}
|
||||
|
||||
// SetWriter sets the customed logging <writer> for logging.
|
||||
// SetWriter sets the customized logging <writer> for logging.
|
||||
// The <writer> object should implements the io.Writer interface.
|
||||
// Developer can use customed logging <writer> to redirect logging output to another service,
|
||||
// Developer can use customized logging <writer> to redirect logging output to another service,
|
||||
// eg: kafka, mysql, mongodb, etc.
|
||||
//
|
||||
// 可自定义IO接口,IO可以是文件输出、标准输出、网络输出.
|
||||
func SetWriter(writer io.Writer) {
|
||||
logger.SetWriter(writer)
|
||||
}
|
||||
|
||||
// GetWriter returns the customed writer object, which implements the io.Writer interface.
|
||||
// It returns nil if no customed writer set.
|
||||
//
|
||||
// 返回自定义的IO,默认为nil.
|
||||
// GetWriter returns the customized writer object, which implements the io.Writer interface.
|
||||
// It returns nil if no customized writer set.
|
||||
func GetWriter() io.Writer {
|
||||
return logger.GetWriter()
|
||||
}
|
||||
|
||||
// GetLevel returns the default logging level value.
|
||||
//
|
||||
// 获取全局的日志记录等级.
|
||||
func GetLevel() int {
|
||||
return defaultLevel.Val()
|
||||
}
|
||||
|
||||
// SetDebug enables/disables the debug level for default logger.
|
||||
// The debug level is enbaled in default.
|
||||
//
|
||||
// 设置是否允许输出DEBUG信息.
|
||||
func SetDebug(debug bool) {
|
||||
logger.SetDebug(debug)
|
||||
}
|
||||
|
||||
// SetStdPrint sets whether ouptput the logging contents to stdout, which is false indefault.
|
||||
//
|
||||
// 设置写日志的同时开启or关闭控制台打印,默认是关闭的
|
||||
// SetStdPrint sets whether ouptput the logging contents to stdout, which is false in default.
|
||||
func SetStdPrint(open bool) {
|
||||
logger.SetStdPrint(open)
|
||||
}
|
||||
|
||||
// GetPath returns the logging directory path for file logging.
|
||||
// It returns empty string if no directory path set.
|
||||
//
|
||||
// 获取日志目录绝对路径
|
||||
func GetPath() string {
|
||||
return logger.GetPath()
|
||||
}
|
||||
|
||||
// PrintBacktrace prints the caller backtrace,
|
||||
// the optional parameter <skip> specify the skipped backtraces offset from the end point.
|
||||
//
|
||||
// 打印文件调用回溯信息
|
||||
// the optional parameter <skip> specify the skipped backtrace offset from the end point.
|
||||
func PrintBacktrace(skip...int) {
|
||||
logger.PrintBacktrace(skip...)
|
||||
}
|
||||
|
||||
// GetBacktrace returns the caller backtrace content,
|
||||
// the optional parameter <skip> specify the skipped backtraces offset from the end point.
|
||||
//
|
||||
// 获取文件调用回溯信息.
|
||||
// the optional parameter <skip> specify the skipped backtrace offset from the end point.
|
||||
func GetBacktrace(skip...int) string {
|
||||
return logger.GetBacktrace(skip...)
|
||||
}
|
||||
|
||||
// SetBacktrace enables/disables the backtrace feature in failure logging outputs.
|
||||
//
|
||||
// 是否关闭全局的backtrace信息
|
||||
func SetBacktrace(enabled bool) {
|
||||
logger.SetBacktrace(enabled)
|
||||
}
|
||||
|
||||
// To is a chaining function,
|
||||
// which redirects current logging content output to the sepecified <writer>.
|
||||
//
|
||||
// 链式操作,设置下一次写入日志内容的Writer
|
||||
func To(writer io.Writer) *Logger {
|
||||
return logger.To(writer)
|
||||
}
|
||||
|
||||
// Path is a chaining function,
|
||||
// which sets the directory path to <path> for current logging content output.
|
||||
//
|
||||
// 链式操作,设置下一次输出的日志路径。
|
||||
func Path(path string) *Logger {
|
||||
return logger.Path(path)
|
||||
}
|
||||
|
||||
// Cat is a chaining function,
|
||||
// which sets the category to <category> for current logging content output.
|
||||
//
|
||||
// 设置下一次输出的分类,支持多级分类设置.
|
||||
func Cat(category string) *Logger {
|
||||
return logger.Cat(category)
|
||||
}
|
||||
|
||||
// File is a chaining function,
|
||||
// which sets file name <pattern> for the current logging content output.
|
||||
//
|
||||
// 设置日志输出文件名称格式
|
||||
func File(pattern string) *Logger {
|
||||
return logger.File(pattern)
|
||||
}
|
||||
|
||||
// Level is a chaining function,
|
||||
// which sets logging level for the current logging content output.
|
||||
//
|
||||
// 设置日志打印等级.
|
||||
func Level(level int) *Logger {
|
||||
return logger.Level(level)
|
||||
}
|
||||
|
||||
// Backtrace is a chaining function,
|
||||
// which sets backtrace options for the current logging content output .
|
||||
//
|
||||
// 设置文件调用回溯信息.
|
||||
func Backtrace(enabled bool, skip...int) *Logger {
|
||||
return logger.Backtrace(enabled, skip...)
|
||||
}
|
||||
|
||||
// StdPrint is a chaining function,
|
||||
// which enables/disables stdout for the current logging content output.
|
||||
//
|
||||
// 是否允许在设置输出文件时同时也输出到终端
|
||||
func StdPrint(enabled bool) *Logger {
|
||||
return logger.StdPrint(enabled)
|
||||
}
|
||||
|
||||
// Header is a chaining function,
|
||||
// which enables/disables log header for the current logging content output.
|
||||
//
|
||||
// 是否打印每行日志头信息(默认开启)
|
||||
func Header(enabled bool) *Logger {
|
||||
return logger.Header(enabled)
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// 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.
|
||||
//
|
||||
// @author john, zseeker
|
||||
|
||||
package glog
|
||||
@ -26,15 +27,15 @@ import (
|
||||
|
||||
type Logger struct {
|
||||
mu sync.RWMutex
|
||||
pr *Logger // 父级Logger
|
||||
io io.Writer // 日志内容写入的IO接口
|
||||
path *gtype.String // 日志写入的目录路径
|
||||
file *gtype.String // 日志文件名称格式
|
||||
level *gtype.Int // 日志输出等级
|
||||
btSkip *gtype.Int // 错误产生时的backtrace回调信息skip条数
|
||||
btStatus *gtype.Int // 是否当打印错误时同时开启backtrace打印(默认-1,表示默认打印逻辑 - 错误才打印)
|
||||
printHeader *gtype.Bool // 是否不打印前缀信息(时间,级别等)
|
||||
alsoStdPrint *gtype.Bool // 控制台打印开关,当输出到文件/自定义输出时也同时打印到终端
|
||||
pr *Logger // Parent logger.
|
||||
writer io.Writer // Customized io.Writer.
|
||||
path *gtype.String // Logging directory path.
|
||||
file *gtype.String // Format for logging file.
|
||||
level *gtype.Int // Output level.
|
||||
btSkip *gtype.Int // Skip count for backtrace.
|
||||
btStatus *gtype.Int // Backtrace status(1: enabled - default; 0: disabled)
|
||||
printHeader *gtype.Bool // Print header or not(true in default).
|
||||
alsoStdPrint *gtype.Bool // Output to stdout or not(true in default).
|
||||
}
|
||||
|
||||
const (
|
||||
@ -45,70 +46,66 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// 默认的日志换行符
|
||||
// Default line break.
|
||||
ln = "\n"
|
||||
// 标准输出互斥锁,防止标准输出串日志
|
||||
// Mutex to ensure log output sequence.
|
||||
stdMu = sync.RWMutex{}
|
||||
)
|
||||
|
||||
// 初始化日志换行符
|
||||
func init() {
|
||||
// Initialize log line breaks depending on underlying os.
|
||||
if runtime.GOOS == "windows" {
|
||||
ln = "\r\n"
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a custom logger.
|
||||
//
|
||||
// 新建自定义的日志操作对象
|
||||
// New creates and returns a custom logger.
|
||||
func New() *Logger {
|
||||
return &Logger {
|
||||
io : nil,
|
||||
logger := &Logger {
|
||||
path : gtype.NewString(),
|
||||
file : gtype.NewString(gDEFAULT_FILE_FORMAT),
|
||||
level : gtype.NewInt(defaultLevel.Val()),
|
||||
btSkip : gtype.NewInt(),
|
||||
btStatus : gtype.NewInt(-1),
|
||||
btStatus : gtype.NewInt(1),
|
||||
printHeader : gtype.NewBool(true),
|
||||
alsoStdPrint : gtype.NewBool(true),
|
||||
}
|
||||
logger.writer = &Writer {
|
||||
logger : logger,
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// Clone returns a new logger, which is the clone the current logger.
|
||||
//
|
||||
// Logger拷贝.
|
||||
func (l *Logger) Clone() *Logger {
|
||||
return &Logger {
|
||||
logger := &Logger {
|
||||
pr : l,
|
||||
io : l.GetWriter(),
|
||||
path : l.path.Clone(),
|
||||
file : l.file.Clone(),
|
||||
level : l.level.Clone(),
|
||||
btSkip : l.btSkip.Clone(),
|
||||
btStatus : l.btStatus.Clone(),
|
||||
btStatus : l.btStatus.Clone(),
|
||||
printHeader : l.printHeader.Clone(),
|
||||
alsoStdPrint : l.alsoStdPrint.Clone(),
|
||||
}
|
||||
logger.writer = &Writer {
|
||||
logger : logger,
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// SetLevel sets the logging level.
|
||||
//
|
||||
// 设置日志记录等级
|
||||
func (l *Logger) SetLevel(level int) {
|
||||
l.level.Set(level)
|
||||
}
|
||||
|
||||
// GetLevel returns the logging level value.
|
||||
//
|
||||
// 获取日志记录等级
|
||||
func (l *Logger) GetLevel() int {
|
||||
return l.level.Val()
|
||||
}
|
||||
|
||||
// SetDebug enables/disables the debug level for logger.
|
||||
// The debug level is enbaled in default.
|
||||
//
|
||||
// 快捷方法,打开或关闭DEBU日志信息
|
||||
// The debug level is enabled in default.
|
||||
func (l *Logger) SetDebug(debug bool) {
|
||||
if debug {
|
||||
l.level.Set(l.level.Val() | LEVEL_DEBU)
|
||||
@ -124,7 +121,6 @@ func (l *Logger) SetBacktrace(enabled bool) {
|
||||
} else {
|
||||
l.btStatus.Set(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// SetBacktraceSkip sets the backtrace offset from the end point.
|
||||
@ -132,47 +128,41 @@ func (l *Logger) SetBacktraceSkip(skip int) {
|
||||
l.btSkip.Set(skip)
|
||||
}
|
||||
|
||||
// SetWriter sets the customed logging <writer> for logging.
|
||||
// SetWriter sets the customized logging <writer> for logging.
|
||||
// The <writer> object should implements the io.Writer interface.
|
||||
// Developer can use customed logging <writer> to redirect logging output to another service,
|
||||
// Developer can use customized logging <writer> to redirect logging output to another service,
|
||||
// eg: kafka, mysql, mongodb, etc.
|
||||
//
|
||||
// 可自定义IO接口,IO可以是文件输出、标准输出、网络输出
|
||||
func (l *Logger) SetWriter(writer io.Writer) {
|
||||
l.mu.Lock()
|
||||
l.io = writer
|
||||
l.writer = writer
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// GetWriter returns the customed writer object, which implements the io.Writer interface.
|
||||
// It returns nil if no customed writer set.
|
||||
//
|
||||
// 返回自定义的IO,默认为nil
|
||||
// GetWriter returns the customized writer object, which implements the io.Writer interface.
|
||||
// It returns a default writer if no customized writer set.
|
||||
func (l *Logger) GetWriter() io.Writer {
|
||||
l.mu.RLock()
|
||||
r := l.io
|
||||
r := l.writer
|
||||
l.mu.RUnlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// getFilePointer returns the file pinter for file logging.
|
||||
// It returns nil if file logging disabled, or file open fails.
|
||||
//
|
||||
// 获取默认的文件IO.
|
||||
// It returns nil if file logging is disabled, or file opening fails.
|
||||
func (l *Logger) getFilePointer() *gfpool.File {
|
||||
if path := l.path.Val(); path != "" {
|
||||
// 文件名称中使用"{}"包含的内容使用gtime格式化
|
||||
// Content containing "{}" in the file name is formatted using gtime
|
||||
file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.file.Val(), func(s string) string {
|
||||
return gtime.Now().Format(strings.Trim(s, "{}"))
|
||||
})
|
||||
// 如果日志目录不存在则创建目录路径
|
||||
// Create path if it does not exist。
|
||||
if !gfile.Exists(path) {
|
||||
if err := gfile.Mkdir(path); err != nil {
|
||||
fmt.Fprintln(os.Stderr, fmt.Sprintf(`[glog] mkdir "%s" failed: %s`, path, err.Error()))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
fpath := path + gfile.Separator + file
|
||||
fpath := path + gfile.Separator + file
|
||||
if fp, err := gfpool.Open(fpath, gDEFAULT_FILE_POOL_FLAGS, gDEFAULT_FPOOL_PERM, gDEFAULT_FPOOL_EXPIRE); err == nil {
|
||||
return fp
|
||||
} else {
|
||||
@ -183,14 +173,10 @@ func (l *Logger) getFilePointer() *gfpool.File {
|
||||
}
|
||||
|
||||
// SetPath sets the directory path for file logging.
|
||||
//
|
||||
// 设置日志文件的存储目录路径.
|
||||
func (l *Logger) SetPath(path string) error {
|
||||
// path必须有值
|
||||
if path == "" {
|
||||
return errors.New("path is empty")
|
||||
}
|
||||
// 如果目录不存在,则递归创建
|
||||
if !gfile.Exists(path) {
|
||||
if err := gfile.Mkdir(path); err != nil {
|
||||
fmt.Fprintln(os.Stderr, fmt.Sprintf(`[glog] mkdir "%s" failed: %s`, path, err.Error()))
|
||||
@ -203,8 +189,6 @@ func (l *Logger) SetPath(path string) error {
|
||||
|
||||
// GetPath returns the logging directory path for file logging.
|
||||
// It returns empty string if no directory path set.
|
||||
//
|
||||
// 获取设置的日志目录路径
|
||||
func (l *Logger) GetPath() string {
|
||||
return l.path.Val()
|
||||
}
|
||||
@ -212,29 +196,24 @@ func (l *Logger) GetPath() string {
|
||||
// SetFile sets the file name <pattern> for file logging.
|
||||
// Datetime pattern can be used in <pattern>, eg: access-{Ymd}.log.
|
||||
// The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log
|
||||
//
|
||||
// 设置日志文件名称格式.
|
||||
func (l *Logger) SetFile(pattern string) {
|
||||
l.file.Set(pattern)
|
||||
}
|
||||
|
||||
// SetStdPrint sets whether ouptput the logging contents to stdout, which is false indefault.
|
||||
//
|
||||
// 设置写日志时开启or关闭控制台打印,默认是关闭的
|
||||
// SetStdPrint sets whether output the logging contents to stdout, which is false in default.
|
||||
func (l *Logger) SetStdPrint(enabled bool) {
|
||||
l.alsoStdPrint.Set(enabled)
|
||||
}
|
||||
|
||||
// 这里的写锁保证统一时刻只会写入一行日志,防止串日志的情况
|
||||
// print prints <s> to defined writer, logging file or passed <std>.
|
||||
// It internally uses memory lock for file logging to ensure logging sequence.
|
||||
func (l *Logger) print(std io.Writer, s string) {
|
||||
// 优先使用自定义的IO输出
|
||||
// Customized writer has the most high priority.
|
||||
if l.printHeader.Val() {
|
||||
s = l.format(s)
|
||||
}
|
||||
writer := l.GetWriter()
|
||||
if writer == nil {
|
||||
// 如果设置的writer为空,那么其次判断是否有文件输出设置
|
||||
// 内部使用了内存锁,保证在glog中对同一个日志文件的并发写入不会串日志(并发安全)
|
||||
if _, ok := writer.(*Writer); ok {
|
||||
if f := l.getFilePointer(); f != nil {
|
||||
defer f.Close()
|
||||
key := l.path.Val()
|
||||
@ -245,7 +224,7 @@ func (l *Logger) print(std io.Writer, s string) {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
// 当没有设置writer时,需要判断是否允许输出到标准输出
|
||||
// Also output to stdout?
|
||||
if l.alsoStdPrint.Val() {
|
||||
l.doStdLockPrint(std, s)
|
||||
}
|
||||
@ -254,7 +233,7 @@ func (l *Logger) print(std io.Writer, s string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 并发安全打印到标准输出
|
||||
// doStdLockPrint prints <s> to <std> concurrent-safely.
|
||||
func (l *Logger) doStdLockPrint(std io.Writer, s string) {
|
||||
stdMu.Lock()
|
||||
if _, err := std.Write([]byte(s)); err != nil {
|
||||
@ -263,23 +242,21 @@ func (l *Logger) doStdLockPrint(std io.Writer, s string) {
|
||||
stdMu.Unlock()
|
||||
}
|
||||
|
||||
// 核心打印数据方法(标准输出)
|
||||
// stdPrint prints content <s> without backtrace.
|
||||
func (l *Logger) stdPrint(s string) {
|
||||
l.print(os.Stdout, s)
|
||||
}
|
||||
|
||||
// 核心打印数据方法(标准错误)
|
||||
// stdPrint prints content <s> with backtrace check.
|
||||
func (l *Logger) errPrint(s string) {
|
||||
// 记录调用回溯信息
|
||||
status := l.btStatus.Val()
|
||||
if status == -1 || status == 1 {
|
||||
if l.btStatus.Val() == 1 {
|
||||
s = l.appendBacktrace(s)
|
||||
}
|
||||
// 防止串日志情况,这里不使用stderr,而是使用stdout
|
||||
// In matter of sequence, do not use stderr here, but use the same stdout.
|
||||
l.print(os.Stdout, s)
|
||||
}
|
||||
|
||||
// 输出内容中添加回溯信息
|
||||
// appendBacktrace appends backtrace to the <s>.
|
||||
func (l *Logger) appendBacktrace(s string, skip...int) string {
|
||||
trace := l.GetBacktrace(skip...)
|
||||
if trace != "" {
|
||||
@ -298,17 +275,13 @@ func (l *Logger) appendBacktrace(s string, skip...int) string {
|
||||
}
|
||||
|
||||
// PrintBacktrace prints the caller backtrace,
|
||||
// the optional parameter <skip> specify the skipped backtraces offset from the end point.
|
||||
//
|
||||
// 直接打印回溯信息,参数skip表示调用端往上多少级开始回溯
|
||||
// the optional parameter <skip> specify the skipped backtrace offset from the end point.
|
||||
func (l *Logger) PrintBacktrace(skip...int) {
|
||||
l.Println(l.appendBacktrace("", skip...))
|
||||
}
|
||||
|
||||
// GetBacktrace returns the caller backtrace content,
|
||||
// the optional parameter <skip> specify the skipped backtraces offset from the end point.
|
||||
//
|
||||
// 获取文件调用回溯字符串,参数skip表示调用端往上多少级开始回溯
|
||||
// the optional parameter <skip> specify the skipped backtrace offset from the end point.
|
||||
func (l *Logger) GetBacktrace(skip...int) string {
|
||||
customSkip := 0
|
||||
if len(skip) > 0 {
|
||||
@ -507,8 +480,6 @@ func (l *Logger) Criticalfln(format string, v ...interface{}) {
|
||||
}
|
||||
|
||||
// checkLevel checks whether the given <level> could be output.
|
||||
//
|
||||
// 判断给定level是否满足
|
||||
func (l *Logger) checkLevel(level int) bool {
|
||||
return l.level.Val() & level > 0
|
||||
}
|
||||
@ -13,8 +13,6 @@ import (
|
||||
|
||||
// To is a chaining function,
|
||||
// which redirects current logging content output to the specified <writer>.
|
||||
//
|
||||
// 链式操作,设置下一次写入日志内容的Writer
|
||||
func (l *Logger) To(writer io.Writer) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.pr == nil {
|
||||
@ -28,8 +26,6 @@ func (l *Logger) To(writer io.Writer) *Logger {
|
||||
|
||||
// Path is a chaining function,
|
||||
// which sets the directory path to <path> for current logging content output.
|
||||
//
|
||||
// 链式操作,设置下一次输出的日志路径。
|
||||
func (l *Logger) Path(path string) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.pr == nil {
|
||||
@ -45,9 +41,7 @@ func (l *Logger) Path(path string) *Logger {
|
||||
|
||||
// Cat is a chaining function,
|
||||
// which sets the category to <category> for current logging content output.
|
||||
//
|
||||
// 链式操作,设置下一次输出的日志分类(可以按照文件目录层级设置),在当前logpath或者当前工作目录下创建category目录,
|
||||
// 这是一个链式操作,可以设置多个分类,将会创建层级的日志分类目录。
|
||||
// Param <category> can be hierarchical, eg: module/user.
|
||||
func (l *Logger) Cat(category string) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.pr == nil {
|
||||
@ -64,8 +58,6 @@ func (l *Logger) Cat(category string) *Logger {
|
||||
|
||||
// File is a chaining function,
|
||||
// which sets file name <pattern> for the current logging content output.
|
||||
//
|
||||
// 日志文件格式
|
||||
func (l *Logger) File(file string) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.pr == nil {
|
||||
@ -79,8 +71,6 @@ func (l *Logger) File(file string) *Logger {
|
||||
|
||||
// Level is a chaining function,
|
||||
// which sets logging level for the current logging content output.
|
||||
//
|
||||
// 设置日志打印等级
|
||||
func (l *Logger) Level(level int) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.pr == nil {
|
||||
@ -94,8 +84,6 @@ func (l *Logger) Level(level int) *Logger {
|
||||
|
||||
// Backtrace is a chaining function,
|
||||
// which sets backtrace options for the current logging content output .
|
||||
//
|
||||
// 设置文件调用回溯信息
|
||||
func (l *Logger) Backtrace(enabled bool, skip...int) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.pr == nil {
|
||||
@ -112,8 +100,6 @@ func (l *Logger) Backtrace(enabled bool, skip...int) *Logger {
|
||||
|
||||
// StdPrint is a chaining function,
|
||||
// which enables/disables stdout for the current logging content output.
|
||||
//
|
||||
// 是否允许在设置输出文件时同时也输出到终端
|
||||
func (l *Logger) StdPrint(enabled bool) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.pr == nil {
|
||||
@ -127,8 +113,6 @@ func (l *Logger) StdPrint(enabled bool) *Logger {
|
||||
|
||||
// Header is a chaining function,
|
||||
// which enables/disables log header for the current logging content output.
|
||||
//
|
||||
// 是否打印每行日志头信息(默认开启)
|
||||
func (l *Logger) Header(enabled bool) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.pr == nil {
|
||||
|
||||
18
g/os/glog/glog_logger_writer.go
Normal file
18
g/os/glog/glog_logger_writer.go
Normal file
@ -0,0 +1,18 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 glog
|
||||
|
||||
type Writer struct {
|
||||
logger *Logger
|
||||
}
|
||||
|
||||
// Write implements the io.Writer interface.
|
||||
// It just prints the content with header or level.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
w.logger.Header(false).Print(string(p))
|
||||
return len(p), nil
|
||||
}
|
||||
@ -7,142 +7,159 @@
|
||||
package gtime
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"bytes"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// 参考:http://php.net/manual/zh/function.date.php
|
||||
formats = map[byte]string {
|
||||
// ================== 日 ==================
|
||||
'd' : "02", // 月份中的第几天,有前导零的 2 位数字(01 到 31)
|
||||
'D' : "Mon", // 星期中的第几天,文本表示,3 个字母(Mon 到 Sun)
|
||||
'j' : "=j=02", // 月份中的第几天,没有前导零(1 到 31)
|
||||
'l' : "Monday", // ("L"的小写字母)星期几,完整的文本格式(Sunday 到 Saturday)
|
||||
// 参考:http://php.net/manual/zh/function.date.php
|
||||
formats = map[byte]string{
|
||||
// ================== 日 ==================
|
||||
'd': "02", // 月份中的第几天,有前导零的 2 位数字(01 到 31)
|
||||
'D': "Mon", // 星期中的第几天,文本表示,3 个字母(Mon 到 Sun)
|
||||
'w': "Monday", // 星期中的第几天,数字型式的文本表示 0为星期天 6为星期六
|
||||
'j': "=j=02", // 月份中的第几天,没有前导零(1 到 31)
|
||||
'l': "Monday", // ("L"的小写字母)星期几,完整的文本格式(Sunday 到 Saturday)
|
||||
|
||||
// ================== 月 ==================
|
||||
'F' : "January", // 月份,完整的文本格式,例如 January 或者 March January 到 December
|
||||
'm' : "01", // 数字表示的月份,有前导零(01 到 12)
|
||||
'M' : "Jan", // 三个字母缩写表示的月份(Jan 到 Dec)
|
||||
'n' : "1", // 数字表示的月份,没有前导零(1 到 12)
|
||||
// ================== 月 ==================
|
||||
'F': "January", // 月份,完整的文本格式,例如 January 或者 March January 到 December
|
||||
'm': "01", // 数字表示的月份,有前导零(01 到 12)
|
||||
'M': "Jan", // 三个字母缩写表示的月份(Jan 到 Dec)
|
||||
'n': "1", // 数字表示的月份,没有前导零(1 到 12)
|
||||
|
||||
// ================== 年 ==================
|
||||
'Y' : "2006", // 4 位数字完整表示的年份, 例如:1999 或 2003
|
||||
'y' : "06", // 2 位数字表示的年份, 例如:99 或 03
|
||||
// ================== 年 ==================
|
||||
'Y': "2006", // 4 位数字完整表示的年份, 例如:1999 或 2003
|
||||
'y': "06", // 2 位数字表示的年份, 例如:99 或 03
|
||||
|
||||
// ================== 时间 ==================
|
||||
'a' : "pm", // 小写的上午和下午值 am 或 pm
|
||||
'A' : "PM", // 大写的上午和下午值 AM 或 PM
|
||||
'g' : "3", // 小时,12 小时格式,没有前导零, 1 到 12
|
||||
'G' : "=G=15", // 小时,24 小时格式,没有前导零, 0 到 23
|
||||
'h' : "03", // 小时,12 小时格式,有前导零, 01 到 12
|
||||
'H' : "15", // 小时,24 小时格式,有前导零, 00 到 23
|
||||
'i' : "04", // 有前导零的分钟数, 00 到 59
|
||||
's' : "05", // 秒数,有前导零, 00 到 59
|
||||
'u' : "=u=.000", // 毫秒(3位)
|
||||
// ================== 时间 ==================
|
||||
'a': "pm", // 小写的上午和下午值 am 或 pm
|
||||
'A': "PM", // 大写的上午和下午值 AM 或 PM
|
||||
'g': "3", // 小时,12 小时格式,没有前导零, 1 到 12
|
||||
'G': "=G=15", // 小时,24 小时格式,没有前导零, 0 到 23
|
||||
'h': "03", // 小时,12 小时格式,有前导零, 01 到 12
|
||||
'H': "15", // 小时,24 小时格式,有前导零, 00 到 23
|
||||
'i': "04", // 有前导零的分钟数, 00 到 59
|
||||
's': "05", // 秒数,有前导零, 00 到 59
|
||||
'u': "=u=.000", // 毫秒(3位)
|
||||
|
||||
// ================== 时区 ==================
|
||||
'O' : "-0700", // 与UTC相差的小时数, 例如:+0200
|
||||
'P' : "-07:00", // 与UTC的差别,小时和分钟之间有冒号分隔, 例如:+02:00
|
||||
'T' : "MST", // 时区缩写, 例如:UTC,GMT,CST
|
||||
// ================== 时区 ==================
|
||||
'O': "-0700", // 与UTC相差的小时数, 例如:+0200
|
||||
'P': "-07:00", // 与UTC的差别,小时和分钟之间有冒号分隔, 例如:+02:00
|
||||
'T': "MST", // 时区缩写, 例如:UTC,GMT,CST
|
||||
|
||||
// ================== 完整的日期/时间 ==================
|
||||
'c' : "2006-01-02T15:04:05-07:00", // ISO 8601 格式的日期,例如:2004-02-12T15:19:21+00:00
|
||||
'r' : "Mon, 02 Jan 06 15:04 MST", // RFC 822 格式的日期,例如:Thu, 21 Dec 2000 16:01:07 +0200
|
||||
}
|
||||
// ================== 完整的日期/时间 ==================
|
||||
'c': "2006-01-02T15:04:05-07:00", // ISO 8601 格式的日期,例如:2004-02-12T15:19:21+00:00
|
||||
'r': "Mon, 02 Jan 06 15:04 MST", // RFC 822 格式的日期,例如:Thu, 21 Dec 2000 16:01:07 +0200
|
||||
}
|
||||
|
||||
// 星期的英文值和数字值对应map
|
||||
weekMap = map[string]string{
|
||||
"Sunday": "0",
|
||||
"Monday": "1",
|
||||
"Tuesday": "2",
|
||||
"Wednesday": "3",
|
||||
"Thursday": "4",
|
||||
"Friday": "5",
|
||||
"Saturday": "6",
|
||||
}
|
||||
)
|
||||
|
||||
// 将自定义的格式转换为标准库时间格式
|
||||
func formatToStdLayout(format string) string {
|
||||
b := bytes.NewBuffer(nil)
|
||||
for i := 0; i < len(format); {
|
||||
switch format[i] {
|
||||
case '\\':
|
||||
if i < len(format) - 1 {
|
||||
b.WriteByte(format[i + 1])
|
||||
i += 2
|
||||
continue
|
||||
} else {
|
||||
return b.String()
|
||||
}
|
||||
b := bytes.NewBuffer(nil)
|
||||
for i := 0; i < len(format); {
|
||||
switch format[i] {
|
||||
case '\\':
|
||||
if i < len(format)-1 {
|
||||
b.WriteByte(format[i+1])
|
||||
i += 2
|
||||
continue
|
||||
} else {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
default:
|
||||
if f, ok := formats[format[i]]; ok {
|
||||
// 有几个转换的符号需要特殊处理
|
||||
switch format[i] {
|
||||
case 'j':
|
||||
b.WriteString("02")
|
||||
case 'G':
|
||||
b.WriteString("15")
|
||||
case 'u':
|
||||
if i > 0 && format[i - 1] == '.' {
|
||||
b.WriteString("000")
|
||||
} else {
|
||||
b.WriteString(".000")
|
||||
}
|
||||
default:
|
||||
if f, ok := formats[format[i]]; ok {
|
||||
// 有几个转换的符号需要特殊处理
|
||||
switch format[i] {
|
||||
case 'j':
|
||||
b.WriteString("02")
|
||||
case 'G':
|
||||
b.WriteString("15")
|
||||
case 'u':
|
||||
if i > 0 && format[i-1] == '.' {
|
||||
b.WriteString("000")
|
||||
} else {
|
||||
b.WriteString(".000")
|
||||
}
|
||||
|
||||
default:
|
||||
b.WriteString(f)
|
||||
}
|
||||
} else {
|
||||
b.WriteByte(format[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
default:
|
||||
b.WriteString(f)
|
||||
}
|
||||
} else {
|
||||
b.WriteByte(format[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// 将format格式转换为正则表达式规则
|
||||
func formatToRegexPattern(format string) string {
|
||||
s := gregex.Quote(formatToStdLayout(format))
|
||||
s, _ = gregex.ReplaceString(`[0-9]`, `[0-9]`, s)
|
||||
s, _ = gregex.ReplaceString(`[A-Za-z]`, `[A-Za-z]`, s)
|
||||
return s
|
||||
s := gregex.Quote(formatToStdLayout(format))
|
||||
s, _ = gregex.ReplaceString(`[0-9]`, `[0-9]`, s)
|
||||
s, _ = gregex.ReplaceString(`[A-Za-z]`, `[A-Za-z]`, s)
|
||||
return s
|
||||
}
|
||||
|
||||
// 格式化,使用自定义日期格式
|
||||
func (t *Time) Format(format string) string {
|
||||
runes := []rune(format)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for i := 0; i < len(runes); {
|
||||
switch runes[i] {
|
||||
case '\\':
|
||||
if i < len(runes) - 1 {
|
||||
buffer.WriteRune(runes[i + 1])
|
||||
i += 2
|
||||
continue
|
||||
} else {
|
||||
return buffer.String()
|
||||
}
|
||||
runes := []rune(format)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for i := 0; i < len(runes); {
|
||||
switch runes[i] {
|
||||
case '\\':
|
||||
if i < len(runes)-1 {
|
||||
buffer.WriteRune(runes[i+1])
|
||||
i += 2
|
||||
continue
|
||||
} else {
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
default:
|
||||
if runes[i] > 255 {
|
||||
buffer.WriteRune(runes[i])
|
||||
break
|
||||
}
|
||||
if f, ok := formats[byte(runes[i])]; ok {
|
||||
result := t.Time.Format(f)
|
||||
// 有几个转换的符号需要特殊处理
|
||||
switch runes[i] {
|
||||
case 'j': buffer.WriteString(gstr.ReplaceByArray(result, []string{"=j=0", "", "=j=", ""}))
|
||||
case 'G': buffer.WriteString(gstr.ReplaceByArray(result, []string{"=G=0", "", "=G=", ""}))
|
||||
case 'u': buffer.WriteString(strings.Replace(result, "=u=.", "", -1))
|
||||
default:
|
||||
buffer.WriteString(result)
|
||||
}
|
||||
} else {
|
||||
buffer.WriteRune(runes[i])
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
return buffer.String()
|
||||
default:
|
||||
if runes[i] > 255 {
|
||||
buffer.WriteRune(runes[i])
|
||||
break
|
||||
}
|
||||
if f, ok := formats[byte(runes[i])]; ok {
|
||||
result := t.Time.Format(f)
|
||||
// 有几个转换的符号需要特殊处理
|
||||
switch runes[i] {
|
||||
case 'j':
|
||||
buffer.WriteString(gstr.ReplaceByArray(result, []string{"=j=0", "", "=j=", ""}))
|
||||
case 'G':
|
||||
buffer.WriteString(gstr.ReplaceByArray(result, []string{"=G=0", "", "=G=", ""}))
|
||||
case 'u':
|
||||
buffer.WriteString(strings.Replace(result, "=u=.", "", -1))
|
||||
case 'w':
|
||||
buffer.WriteString(weekMap[result])
|
||||
default:
|
||||
buffer.WriteString(result)
|
||||
}
|
||||
} else {
|
||||
buffer.WriteRune(runes[i])
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// 格式化,使用标准库格式
|
||||
func (t *Time) Layout(layout string) string {
|
||||
return t.Time.Format(layout)
|
||||
}
|
||||
return t.Time.Format(layout)
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
package gtime_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Format(t *testing.T) {
|
||||
@ -43,6 +42,20 @@ func Test_Format(t *testing.T) {
|
||||
}
|
||||
gtest.Assert(timeTemp2.Format("Y-n-j G:i:s"), "2006-1-2 3:04:05")
|
||||
|
||||
// 测试数字型的星期
|
||||
times := []map[string]string{
|
||||
{"k": "2019-04-22", "f": "w", "r": "1"},
|
||||
{"k": "2019-04-27", "f": "w", "r": "6"},
|
||||
{"k": "2019-03-10", "f": "w", "r": "0"},
|
||||
{"k": "2019-03-10", "f": "Y-m-d 星期:w", "r": "2019-03-10 星期:0"},
|
||||
}
|
||||
|
||||
for _, v := range times {
|
||||
t1, err1 := gtime.StrToTime(v["k"], "Y-m-d")
|
||||
gtest.Assert(err1, nil)
|
||||
gtest.Assert(t1.Format(v["f"]), v["r"])
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -8,33 +8,24 @@
|
||||
package gview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf"
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/encoding/ghash"
|
||||
"github.com/gogf/gf/g/encoding/ghtml"
|
||||
"github.com/gogf/gf/g/encoding/gurl"
|
||||
"github.com/gogf/gf/g/internal/cmdenv"
|
||||
"github.com/gogf/gf/g/os/gfcache"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gspath"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/os/gview/internal/text/template"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf"
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/internal/cmdenv"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gspath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type View struct {
|
||||
mu sync.RWMutex
|
||||
paths *garray.StringArray // 模板查找目录(绝对路径)
|
||||
data map[string]interface{} // 模板变量
|
||||
funcmap map[string]interface{} // FuncMap
|
||||
delimiters []string // 模板变量分隔符号
|
||||
paths *garray.StringArray // Searching path array.
|
||||
data map[string]interface{} // Global template variables.
|
||||
funcMap map[string]interface{} // Global template function map.
|
||||
delimiters []string // Customized template delimiters.
|
||||
}
|
||||
|
||||
// Template params type.
|
||||
@ -50,13 +41,13 @@ var defaultViewObj *View
|
||||
// The default view object will be initialized just once.
|
||||
func checkAndInitDefaultView() {
|
||||
if defaultViewObj == nil {
|
||||
defaultViewObj = New(gfile.Pwd())
|
||||
defaultViewObj = New()
|
||||
}
|
||||
}
|
||||
|
||||
// ParseContent parses the template content directly using the default view object
|
||||
// and returns the parsed content.
|
||||
func ParseContent(content string, params Params) ([]byte, error) {
|
||||
func ParseContent(content string, params Params) (string, error) {
|
||||
checkAndInitDefaultView()
|
||||
return defaultViewObj.ParseContent(content, params)
|
||||
}
|
||||
@ -67,7 +58,7 @@ func New(path...string) *View {
|
||||
view := &View {
|
||||
paths : garray.NewStringArray(),
|
||||
data : make(map[string]interface{}),
|
||||
funcmap : make(map[string]interface{}),
|
||||
funcMap : make(map[string]interface{}),
|
||||
delimiters : make([]string, 2),
|
||||
}
|
||||
if len(path) > 0 && len(path[0]) > 0 {
|
||||
@ -99,6 +90,12 @@ func New(path...string) *View {
|
||||
"version" : gf.VERSION,
|
||||
}
|
||||
// default build-in functions.
|
||||
view.BindFunc("eq", view.funcEq)
|
||||
view.BindFunc("ne", view.funcNe)
|
||||
view.BindFunc("lt", view.funcLt)
|
||||
view.BindFunc("le", view.funcLe)
|
||||
view.BindFunc("gt", view.funcGt)
|
||||
view.BindFunc("ge", view.funcGe)
|
||||
view.BindFunc("text", view.funcText)
|
||||
view.BindFunc("html", view.funcHtmlEncode)
|
||||
view.BindFunc("htmlencode", view.funcHtmlEncode)
|
||||
@ -215,247 +212,5 @@ func (view *View) AddPath(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Assign binds multiple template variables to current view object.
|
||||
// Each goroutine will take effect after the call, so it is concurrent-safe.
|
||||
func (view *View) Assigns(data Params) {
|
||||
view.mu.Lock()
|
||||
for k, v := range data {
|
||||
view.data[k] = v
|
||||
}
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// Assign binds a template variable to current view object.
|
||||
// Each goroutine will take effect after the call, so it is concurrent-safe.
|
||||
func (view *View) Assign(key string, value interface{}) {
|
||||
view.mu.Lock()
|
||||
view.data[key] = value
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// ParseContent parses given template file <file>
|
||||
// with given template parameters <params> and function map <funcMap>
|
||||
// and returns the parsed content in []byte.
|
||||
func (view *View) Parse(file string, params Params, funcMap...map[string]interface{}) ([]byte, error) {
|
||||
path := ""
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
for _, v := range array {
|
||||
if path, _ = gspath.Search(v, file); path != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
if path == "" {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if view.paths.Len() > 0 {
|
||||
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file))
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
for k, v := range array {
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" with no path set/add", file))
|
||||
}
|
||||
glog.Error(buffer.String())
|
||||
return nil, errors.New(fmt.Sprintf(`tpl "%s" not found`, file))
|
||||
}
|
||||
content := gfcache.GetContents(path)
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
tplObj := template.New(path).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcmap)
|
||||
if len(funcMap) > 0 {
|
||||
tplObj = tplObj.Funcs(funcMap[0])
|
||||
}
|
||||
if tpl, err := tplObj.Parse(content); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
// Note that the template variable assignment cannot change the value
|
||||
// of the existing <params> or view.data because both variables are pointers.
|
||||
// It's need to merge the values of the two maps into a new map.
|
||||
vars := (map[string]interface{})(nil)
|
||||
if len(view.data) > 0 {
|
||||
if len(params) > 0 {
|
||||
vars = make(map[string]interface{}, len(view.data) + len(params))
|
||||
for k, v := range params {
|
||||
vars[k] = v
|
||||
}
|
||||
for k, v := range view.data {
|
||||
vars[k] = v
|
||||
}
|
||||
} else {
|
||||
vars = view.data
|
||||
}
|
||||
} else {
|
||||
vars = params
|
||||
}
|
||||
if err := tpl.Execute(buffer, vars); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// ParseContent parses given template content <content>
|
||||
// with given template parameters <params> and function map <funcMap>
|
||||
// and returns the parsed content in []byte.
|
||||
func (view *View) ParseContent(content string, params Params, funcMap...map[string]interface{}) ([]byte, error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
name := gconv.String(ghash.BKDRHash64([]byte(content)))
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
tplObj := template.New(name).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcmap)
|
||||
if len(funcMap) > 0 {
|
||||
tplObj = tplObj.Funcs(funcMap[0])
|
||||
}
|
||||
if tpl, err := tplObj.Parse(content); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
// Note that the template variable assignment cannot change the value
|
||||
// of the existing <params> or view.data because both variables are pointers.
|
||||
// It's need to merge the values of the two maps into a new map.
|
||||
vars := (map[string]interface{})(nil)
|
||||
if len(view.data) > 0 {
|
||||
if len(params) > 0 {
|
||||
vars = make(map[string]interface{}, len(view.data) + len(params))
|
||||
for k, v := range params {
|
||||
vars[k] = v
|
||||
}
|
||||
for k, v := range view.data {
|
||||
vars[k] = v
|
||||
}
|
||||
} else {
|
||||
vars = view.data
|
||||
}
|
||||
} else {
|
||||
vars = params
|
||||
}
|
||||
if err := tpl.Execute(buffer, vars); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
// SetDelimiters sets customized delimiters for template parsing.
|
||||
func (view *View) SetDelimiters(left, right string) {
|
||||
view.delimiters[0] = left
|
||||
view.delimiters[1] = right
|
||||
}
|
||||
|
||||
// BindFunc registers customized 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.mu.Lock()
|
||||
view.funcmap[name] = function
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// BindFuncMap registers customized template functions by map to current view object.
|
||||
// The key of map is the template function name
|
||||
// and the value of map is the address of customized function.
|
||||
func (view *View) BindFuncMap(funcMap FuncMap) {
|
||||
view.mu.Lock()
|
||||
for k, v := range funcMap {
|
||||
view.funcmap[k] = v
|
||||
}
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// Build-in template function: include
|
||||
func (view *View) funcInclude(file string, data...map[string]interface{}) string {
|
||||
var m map[string]interface{} = nil
|
||||
if len(data) > 0 {
|
||||
m = data[0]
|
||||
}
|
||||
content, err := view.Parse(file, m)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return string(content)
|
||||
}
|
||||
|
||||
// Build-in template function: text
|
||||
func (view *View) funcText(html interface{}) string {
|
||||
return ghtml.StripTags(gconv.String(html))
|
||||
}
|
||||
|
||||
// Build-in template function: html
|
||||
func (view *View) funcHtmlEncode(html interface{}) string {
|
||||
return ghtml.Entities(gconv.String(html))
|
||||
}
|
||||
|
||||
// Build-in template function: htmldecode
|
||||
func (view *View) funcHtmlDecode(html interface{}) string {
|
||||
return ghtml.EntitiesDecode(gconv.String(html))
|
||||
}
|
||||
|
||||
// Build-in template function: url
|
||||
func (view *View) funcUrlEncode(url interface{}) string {
|
||||
return gurl.Encode(gconv.String(url))
|
||||
}
|
||||
|
||||
// Build-in template function: urldecode
|
||||
func (view *View) funcUrlDecode(url interface{}) string {
|
||||
if content, err := gurl.Decode(gconv.String(url)); err == nil {
|
||||
return content
|
||||
} else {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// Build-in template function: date
|
||||
func (view *View) funcDate(format string, timestamp...interface{}) string {
|
||||
t := int64(0)
|
||||
if len(timestamp) > 0 {
|
||||
t = gconv.Int64(timestamp[0])
|
||||
}
|
||||
if t == 0 {
|
||||
t = gtime.Millisecond()
|
||||
}
|
||||
return gtime.NewFromTimeStamp(t).Format(format)
|
||||
}
|
||||
|
||||
// Build-in template function: compare
|
||||
func (view *View) funcCompare(value1, value2 interface{}) int {
|
||||
return strings.Compare(gconv.String(value1), gconv.String(value2))
|
||||
}
|
||||
|
||||
// Build-in template function: substr
|
||||
func (view *View) funcSubStr(start, end int, str interface{}) string {
|
||||
return gstr.SubStr(gconv.String(str), start, end)
|
||||
}
|
||||
|
||||
// Build-in template function: strlimit
|
||||
func (view *View) funcStrLimit(length int, suffix string, str interface{}) string {
|
||||
return gstr.StrLimit(gconv.String(str), length, suffix)
|
||||
}
|
||||
|
||||
// Build-in template function: highlight
|
||||
func (view *View) funcHighlight(key string, color string, str interface{}) string {
|
||||
return gstr.Replace(gconv.String(str), key, fmt.Sprintf(`<span style="color:%s;">%s</span>`, color, key))
|
||||
}
|
||||
|
||||
// Build-in template function: hidestr
|
||||
func (view *View) funcHideStr(percent int, hide string, str interface{}) string {
|
||||
return gstr.HideStr(gconv.String(str), percent, hide)
|
||||
}
|
||||
|
||||
// Build-in template function: toupper
|
||||
func (view *View) funcToUpper(str interface{}) string {
|
||||
return gstr.ToUpper(gconv.String(str))
|
||||
}
|
||||
|
||||
// Build-in template function: toupper
|
||||
func (view *View) funcToLower(str interface{}) string {
|
||||
return gstr.ToLower(gconv.String(str))
|
||||
}
|
||||
|
||||
// Build-in template function: nl2br
|
||||
func (view *View) funcNl2Br(str interface{}) string {
|
||||
return gstr.Nl2Br(gconv.String(str))
|
||||
}
|
||||
|
||||
|
||||
|
||||
169
g/os/gview/gview_buildin.go
Normal file
169
g/os/gview/gview_buildin.go
Normal file
@ -0,0 +1,169 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gview
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/encoding/ghtml"
|
||||
"github.com/gogf/gf/g/encoding/gurl"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Build-in template function: eq
|
||||
func (view *View) funcEq(value interface{}, others...interface{}) bool {
|
||||
s := gconv.String(value)
|
||||
for _, v := range others {
|
||||
if strings.Compare(s, gconv.String(v)) != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Build-in template function: ne
|
||||
func (view *View) funcNe(value interface{}, other interface{}) bool {
|
||||
return strings.Compare(gconv.String(value), gconv.String(other)) != 0
|
||||
}
|
||||
|
||||
// Build-in template function: lt
|
||||
func (view *View) funcLt(value interface{}, other interface{}) bool {
|
||||
s1 := gconv.String(value)
|
||||
s2 := gconv.String(other)
|
||||
if gstr.IsNumeric(s1) && gstr.IsNumeric(s2) {
|
||||
return gconv.Int64(value) < gconv.Int64(other)
|
||||
}
|
||||
return strings.Compare(s1, s2) < 0
|
||||
}
|
||||
|
||||
// Build-in template function: le
|
||||
func (view *View) funcLe(value interface{}, other interface{}) bool {
|
||||
s1 := gconv.String(value)
|
||||
s2 := gconv.String(other)
|
||||
if gstr.IsNumeric(s1) && gstr.IsNumeric(s2) {
|
||||
return gconv.Int64(value) <= gconv.Int64(other)
|
||||
}
|
||||
return strings.Compare(s1, s2) <= 0
|
||||
}
|
||||
|
||||
// Build-in template function: gt
|
||||
func (view *View) funcGt(value interface{}, other interface{}) bool {
|
||||
s1 := gconv.String(value)
|
||||
s2 := gconv.String(other)
|
||||
if gstr.IsNumeric(s1) && gstr.IsNumeric(s2) {
|
||||
return gconv.Int64(value) > gconv.Int64(other)
|
||||
}
|
||||
return strings.Compare(s1, s2) > 0
|
||||
}
|
||||
|
||||
// Build-in template function: ge
|
||||
func (view *View) funcGe(value interface{}, other interface{}) bool {
|
||||
s1 := gconv.String(value)
|
||||
s2 := gconv.String(other)
|
||||
if gstr.IsNumeric(s1) && gstr.IsNumeric(s2) {
|
||||
return gconv.Int64(value) >= gconv.Int64(other)
|
||||
}
|
||||
return strings.Compare(s1, s2) >= 0
|
||||
}
|
||||
|
||||
// Build-in template function: include
|
||||
func (view *View) funcInclude(file string, data...map[string]interface{}) string {
|
||||
var m map[string]interface{} = nil
|
||||
if len(data) > 0 {
|
||||
m = data[0]
|
||||
}
|
||||
content, err := view.Parse(file, m)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// Build-in template function: text
|
||||
func (view *View) funcText(html interface{}) string {
|
||||
return ghtml.StripTags(gconv.String(html))
|
||||
}
|
||||
|
||||
// Build-in template function: html
|
||||
func (view *View) funcHtmlEncode(html interface{}) string {
|
||||
return ghtml.Entities(gconv.String(html))
|
||||
}
|
||||
|
||||
// Build-in template function: htmldecode
|
||||
func (view *View) funcHtmlDecode(html interface{}) string {
|
||||
return ghtml.EntitiesDecode(gconv.String(html))
|
||||
}
|
||||
|
||||
// Build-in template function: url
|
||||
func (view *View) funcUrlEncode(url interface{}) string {
|
||||
return gurl.Encode(gconv.String(url))
|
||||
}
|
||||
|
||||
// Build-in template function: urldecode
|
||||
func (view *View) funcUrlDecode(url interface{}) string {
|
||||
if content, err := gurl.Decode(gconv.String(url)); err == nil {
|
||||
return content
|
||||
} else {
|
||||
return err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// Build-in template function: date
|
||||
func (view *View) funcDate(format string, timestamp...interface{}) string {
|
||||
t := int64(0)
|
||||
if len(timestamp) > 0 {
|
||||
t = gconv.Int64(timestamp[0])
|
||||
}
|
||||
if t == 0 {
|
||||
t = gtime.Millisecond()
|
||||
}
|
||||
return gtime.NewFromTimeStamp(t).Format(format)
|
||||
}
|
||||
|
||||
// Build-in template function: compare
|
||||
func (view *View) funcCompare(value1, value2 interface{}) int {
|
||||
return strings.Compare(gconv.String(value1), gconv.String(value2))
|
||||
}
|
||||
|
||||
// Build-in template function: substr
|
||||
func (view *View) funcSubStr(start, end int, str interface{}) string {
|
||||
return gstr.SubStr(gconv.String(str), start, end)
|
||||
}
|
||||
|
||||
// Build-in template function: strlimit
|
||||
func (view *View) funcStrLimit(length int, suffix string, str interface{}) string {
|
||||
return gstr.StrLimit(gconv.String(str), length, suffix)
|
||||
}
|
||||
|
||||
// Build-in template function: highlight
|
||||
func (view *View) funcHighlight(key string, color string, str interface{}) string {
|
||||
return gstr.Replace(gconv.String(str), key, fmt.Sprintf(`<span style="color:%s;">%s</span>`, color, key))
|
||||
}
|
||||
|
||||
// Build-in template function: hidestr
|
||||
func (view *View) funcHideStr(percent int, hide string, str interface{}) string {
|
||||
return gstr.HideStr(gconv.String(str), percent, hide)
|
||||
}
|
||||
|
||||
// Build-in template function: toupper
|
||||
func (view *View) funcToUpper(str interface{}) string {
|
||||
return gstr.ToUpper(gconv.String(str))
|
||||
}
|
||||
|
||||
// Build-in template function: toupper
|
||||
func (view *View) funcToLower(str interface{}) string {
|
||||
return gstr.ToLower(gconv.String(str))
|
||||
}
|
||||
|
||||
// Build-in template function: nl2br
|
||||
func (view *View) funcNl2Br(str interface{}) string {
|
||||
return gstr.Nl2Br(gconv.String(str))
|
||||
}
|
||||
|
||||
|
||||
53
g/os/gview/gview_config.go
Normal file
53
g/os/gview/gview_config.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gview
|
||||
|
||||
// Assign binds multiple template variables to current view object.
|
||||
// Each goroutine will take effect after the call, so it is concurrent-safe.
|
||||
func (view *View) Assigns(data Params) {
|
||||
view.mu.Lock()
|
||||
for k, v := range data {
|
||||
view.data[k] = v
|
||||
}
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// Assign binds a template variable to current view object.
|
||||
// Each goroutine will take effect after the call, so it is concurrent-safe.
|
||||
func (view *View) Assign(key string, value interface{}) {
|
||||
view.mu.Lock()
|
||||
view.data[key] = value
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// SetDelimiters sets customized delimiters for template parsing.
|
||||
func (view *View) SetDelimiters(left, right string) {
|
||||
view.mu.Lock()
|
||||
view.delimiters[0] = left
|
||||
view.delimiters[1] = right
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// BindFunc registers customized 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.mu.Lock()
|
||||
view.funcMap[name] = function
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// BindFuncMap registers customized template functions by map to current view object.
|
||||
// The key of map is the template function name
|
||||
// and the value of map is the address of customized function.
|
||||
func (view *View) BindFuncMap(funcMap FuncMap) {
|
||||
view.mu.Lock()
|
||||
for k, v := range funcMap {
|
||||
view.funcMap[k] = v
|
||||
}
|
||||
view.mu.Unlock()
|
||||
}
|
||||
186
g/os/gview/gview_doparse.go
Normal file
186
g/os/gview/gview_doparse.go
Normal file
@ -0,0 +1,186 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). 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 gview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/os/gfcache"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/os/gfsnotify"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gspath"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
var (
|
||||
// Templates cache map for template folder.
|
||||
templates = gmap.NewStringInterfaceMap()
|
||||
)
|
||||
|
||||
// getTemplate returns the template object associated with given template folder <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 refresh the template cache
|
||||
// if the template files under <path> changes (recursively).
|
||||
func (view *View) getTemplate(path string, pattern string) (tpl *template.Template, err error) {
|
||||
r := templates.GetOrSetFuncLock(path, func() interface {} {
|
||||
files := ([]string)(nil)
|
||||
files, err = gfile.ScanDir(path, pattern, true)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
tpl = template.New(path).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
if tpl, err = tpl.ParseFiles(files...); err != nil {
|
||||
return nil
|
||||
}
|
||||
gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
templates.Remove(path)
|
||||
gfsnotify.Exit()
|
||||
})
|
||||
return tpl
|
||||
})
|
||||
if r != nil {
|
||||
return r.(*template.Template), nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// searchFile returns the found absolute path for <file>, and its template folder path.
|
||||
func (view *View) searchFile(file string) (path string, folder string, err error) {
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
for _, v := range array {
|
||||
if path, _ = gspath.Search(v, file); path != "" {
|
||||
folder = v
|
||||
break
|
||||
}
|
||||
if path, _ = gspath.Search(v + gfile.Separator + "template", file); path != "" {
|
||||
folder = v + gfile.Separator + "template"
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
if path == "" {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if view.paths.Len() > 0 {
|
||||
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file))
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
index := 1
|
||||
for _, v := range array {
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v))
|
||||
index++
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v + gfile.Separator + "template"))
|
||||
index++
|
||||
}
|
||||
})
|
||||
} else {
|
||||
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" with no path set/add", file))
|
||||
}
|
||||
glog.Error(buffer.String())
|
||||
err = errors.New(fmt.Sprintf(`template file "%s" not found`, file))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseContent parses given template file <file>
|
||||
// with given template parameters <params> and function map <funcMap>
|
||||
// and returns the parsed string content.
|
||||
func (view *View) Parse(file string, params...Params) (parsed string, err error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
path, folder, err := view.searchFile(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tpl, err := view.getTemplate(folder, fmt.Sprintf(`*%s`, gfile.Ext(path)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tpl, err = tpl.Parse(gfcache.GetContents(path))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Note that the template variable assignment cannot change the value
|
||||
// of the existing <params> or view.data because both variables are pointers.
|
||||
// It's need to merge the values of the two maps into a new map.
|
||||
vars := (map[string]interface{})(nil)
|
||||
length := len(view.data)
|
||||
if len(params) > 0 {
|
||||
length += len(params[0])
|
||||
}
|
||||
if length > 0 {
|
||||
vars = make(map[string]interface{}, length)
|
||||
}
|
||||
if len(view.data) > 0 {
|
||||
if len(params) > 0 {
|
||||
for k, v := range params[0] {
|
||||
vars[k] = v
|
||||
}
|
||||
for k, v := range view.data {
|
||||
vars[k] = v
|
||||
}
|
||||
} else {
|
||||
vars = view.data
|
||||
}
|
||||
} else {
|
||||
if len(params) > 0 {
|
||||
vars = params[0]
|
||||
}
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if err := tpl.Execute(buffer, vars); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gstr.Replace(buffer.String(), "<no value>", ""), nil
|
||||
}
|
||||
|
||||
// ParseContent parses given template content <content>
|
||||
// with given template parameters <params> and function map <funcMap>
|
||||
// and returns the parsed content in []byte.
|
||||
func (view *View) ParseContent(content string, params...Params) (string, error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
tpl := template.New("template content").Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
tpl, err := tpl.Parse(content)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Note that the template variable assignment cannot change the value
|
||||
// of the existing <params> or view.data because both variables are pointers.
|
||||
// It's need to merge the values of the two maps into a new map.
|
||||
vars := (map[string]interface{})(nil)
|
||||
length := len(view.data)
|
||||
if len(params) > 0 {
|
||||
length += len(params[0])
|
||||
}
|
||||
if length > 0 {
|
||||
vars = make(map[string]interface{}, length)
|
||||
}
|
||||
if len(view.data) > 0 {
|
||||
if len(params) > 0 {
|
||||
for k, v := range params[0] {
|
||||
vars[k] = v
|
||||
}
|
||||
for k, v := range view.data {
|
||||
vars[k] = v
|
||||
}
|
||||
} else {
|
||||
vars = view.data
|
||||
}
|
||||
} else {
|
||||
if len(params) > 0 {
|
||||
vars = params[0]
|
||||
}
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if err := tpl.Execute(buffer, vars); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gstr.Replace(buffer.String(), "<no value>", ""), nil
|
||||
}
|
||||
@ -1,449 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package template implements data-driven templates for generating textual output.
|
||||
|
||||
To generate HTML output, see package html/template, which has the same interface
|
||||
as this package but automatically secures HTML output against certain attacks.
|
||||
|
||||
Templates are executed by applying them to a data structure. Annotations in the
|
||||
template refer to elements of the data structure (typically a field of a struct
|
||||
or a key in a map) to control execution and derive values to be displayed.
|
||||
Execution of the template walks the structure and sets the cursor, represented
|
||||
by a period '.' and called "dot", to the value at the current location in the
|
||||
structure as execution proceeds.
|
||||
|
||||
The input text for a template is UTF-8-encoded text in any format.
|
||||
"Actions"--data evaluations or control structures--are delimited by
|
||||
"{{" and "}}"; all text outside actions is copied to the output unchanged.
|
||||
Except for raw strings, actions may not span newlines, although comments can.
|
||||
|
||||
Once parsed, a template may be executed safely in parallel, although if parallel
|
||||
executions share a Writer the output may be interleaved.
|
||||
|
||||
Here is a trivial example that prints "17 items are made of wool".
|
||||
|
||||
type Inventory struct {
|
||||
Material string
|
||||
Count uint
|
||||
}
|
||||
sweaters := Inventory{"wool", 17}
|
||||
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
|
||||
if err != nil { panic(err) }
|
||||
err = tmpl.Execute(os.Stdout, sweaters)
|
||||
if err != nil { panic(err) }
|
||||
|
||||
More intricate examples appear below.
|
||||
|
||||
Text and spaces
|
||||
|
||||
By default, all text between actions is copied verbatim when the template is
|
||||
executed. For example, the string " items are made of " in the example above appears
|
||||
on standard output when the program is run.
|
||||
|
||||
However, to aid in formatting template source code, if an action's left delimiter
|
||||
(by default "{{") is followed immediately by a minus sign and ASCII space character
|
||||
("{{- "), all trailing white space is trimmed from the immediately preceding text.
|
||||
Similarly, if the right delimiter ("}}") is preceded by a space and minus sign
|
||||
(" -}}"), all leading white space is trimmed from the immediately following text.
|
||||
In these trim markers, the ASCII space must be present; "{{-3}}" parses as an
|
||||
action containing the number -3.
|
||||
|
||||
For instance, when executing the template whose source is
|
||||
|
||||
"{{23 -}} < {{- 45}}"
|
||||
|
||||
the generated output would be
|
||||
|
||||
"23<45"
|
||||
|
||||
For this trimming, the definition of white space characters is the same as in Go:
|
||||
space, horizontal tab, carriage return, and newline.
|
||||
|
||||
Actions
|
||||
|
||||
Here is the list of actions. "Arguments" and "pipelines" are evaluations of
|
||||
data, defined in detail in the corresponding sections that follow.
|
||||
|
||||
*/
|
||||
// {{/* a comment */}}
|
||||
// {{- /* a comment with white space trimmed from preceding and following text */ -}}
|
||||
// A comment; discarded. May contain newlines.
|
||||
// Comments do not nest and must start and end at the
|
||||
// delimiters, as shown here.
|
||||
/*
|
||||
|
||||
{{pipeline}}
|
||||
The default textual representation (the same as would be
|
||||
printed by fmt.Print) of the value of the pipeline is copied
|
||||
to the output.
|
||||
|
||||
{{if pipeline}} T1 {{end}}
|
||||
If the value of the pipeline is empty, no output is generated;
|
||||
otherwise, T1 is executed. The empty values are false, 0, any
|
||||
nil pointer or interface value, and any array, slice, map, or
|
||||
string of length zero.
|
||||
Dot is unaffected.
|
||||
|
||||
{{if pipeline}} T1 {{else}} T0 {{end}}
|
||||
If the value of the pipeline is empty, T0 is executed;
|
||||
otherwise, T1 is executed. Dot is unaffected.
|
||||
|
||||
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
|
||||
To simplify the appearance of if-else chains, the else action
|
||||
of an if may include another if directly; the effect is exactly
|
||||
the same as writing
|
||||
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
|
||||
|
||||
{{range pipeline}} T1 {{end}}
|
||||
The value of the pipeline must be an array, slice, map, or channel.
|
||||
If the value of the pipeline has length zero, nothing is output;
|
||||
otherwise, dot is set to the successive elements of the array,
|
||||
slice, or map and T1 is executed. If the value is a map and the
|
||||
keys are of basic type with a defined order ("comparable"), the
|
||||
elements will be visited in sorted key order.
|
||||
|
||||
{{range pipeline}} T1 {{else}} T0 {{end}}
|
||||
The value of the pipeline must be an array, slice, map, or channel.
|
||||
If the value of the pipeline has length zero, dot is unaffected and
|
||||
T0 is executed; otherwise, dot is set to the successive elements
|
||||
of the array, slice, or map and T1 is executed.
|
||||
|
||||
{{template "name"}}
|
||||
The template with the specified name is executed with nil data.
|
||||
|
||||
{{template "name" pipeline}}
|
||||
The template with the specified name is executed with dot set
|
||||
to the value of the pipeline.
|
||||
|
||||
{{block "name" pipeline}} T1 {{end}}
|
||||
A block is shorthand for defining a template
|
||||
{{define "name"}} T1 {{end}}
|
||||
and then executing it in place
|
||||
{{template "name" pipeline}}
|
||||
The typical use is to define a set of root templates that are
|
||||
then customized by redefining the block templates within.
|
||||
|
||||
{{with pipeline}} T1 {{end}}
|
||||
If the value of the pipeline is empty, no output is generated;
|
||||
otherwise, dot is set to the value of the pipeline and T1 is
|
||||
executed.
|
||||
|
||||
{{with pipeline}} T1 {{else}} T0 {{end}}
|
||||
If the value of the pipeline is empty, dot is unaffected and T0
|
||||
is executed; otherwise, dot is set to the value of the pipeline
|
||||
and T1 is executed.
|
||||
|
||||
Arguments
|
||||
|
||||
An argument is a simple value, denoted by one of the following.
|
||||
|
||||
- A boolean, string, character, integer, floating-point, imaginary
|
||||
or complex constant in Go syntax. These behave like Go's untyped
|
||||
constants.
|
||||
- The keyword nil, representing an untyped Go nil.
|
||||
- The character '.' (period):
|
||||
.
|
||||
The result is the value of dot.
|
||||
- A variable name, which is a (possibly empty) alphanumeric string
|
||||
preceded by a dollar sign, such as
|
||||
$piOver2
|
||||
or
|
||||
$
|
||||
The result is the value of the variable.
|
||||
Variables are described below.
|
||||
- The name of a field of the data, which must be a struct, preceded
|
||||
by a period, such as
|
||||
.Field
|
||||
The result is the value of the field. Field invocations may be
|
||||
chained:
|
||||
.Field1.Field2
|
||||
Fields can also be evaluated on variables, including chaining:
|
||||
$x.Field1.Field2
|
||||
- The name of a key of the data, which must be a map, preceded
|
||||
by a period, such as
|
||||
.Key
|
||||
The result is the map element value indexed by the key.
|
||||
Key invocations may be chained and combined with fields to any
|
||||
depth:
|
||||
.Field1.Key1.Field2.Key2
|
||||
Although the key must be an alphanumeric identifier, unlike with
|
||||
field names they do not need to start with an upper case letter.
|
||||
Keys can also be evaluated on variables, including chaining:
|
||||
$x.key1.key2
|
||||
- The name of a niladic method of the data, preceded by a period,
|
||||
such as
|
||||
.Method
|
||||
The result is the value of invoking the method with dot as the
|
||||
receiver, dot.Method(). Such a method must have one return value (of
|
||||
any type) or two return values, the second of which is an error.
|
||||
If it has two and the returned error is non-nil, execution terminates
|
||||
and an error is returned to the caller as the value of Execute.
|
||||
Method invocations may be chained and combined with fields and keys
|
||||
to any depth:
|
||||
.Field1.Key1.Method1.Field2.Key2.Method2
|
||||
Methods can also be evaluated on variables, including chaining:
|
||||
$x.Method1.Field
|
||||
- The name of a niladic function, such as
|
||||
fun
|
||||
The result is the value of invoking the function, fun(). The return
|
||||
types and values behave as in methods. Functions and function
|
||||
names are described below.
|
||||
- A parenthesized instance of one the above, for grouping. The result
|
||||
may be accessed by a field or map key invocation.
|
||||
print (.F1 arg1) (.F2 arg2)
|
||||
(.StructValuedMethod "arg").Field
|
||||
|
||||
Arguments may evaluate to any type; if they are pointers the implementation
|
||||
automatically indirects to the base type when required.
|
||||
If an evaluation yields a function value, such as a function-valued
|
||||
field of a struct, the function is not invoked automatically, but it
|
||||
can be used as a truth value for an if action and the like. To invoke
|
||||
it, use the call function, defined below.
|
||||
|
||||
Pipelines
|
||||
|
||||
A pipeline is a possibly chained sequence of "commands". A command is a simple
|
||||
value (argument) or a function or method call, possibly with multiple arguments:
|
||||
|
||||
Argument
|
||||
The result is the value of evaluating the argument.
|
||||
.Method [Argument...]
|
||||
The method can be alone or the last element of a chain but,
|
||||
unlike methods in the middle of a chain, it can take arguments.
|
||||
The result is the value of calling the method with the
|
||||
arguments:
|
||||
dot.Method(Argument1, etc.)
|
||||
functionName [Argument...]
|
||||
The result is the value of calling the function associated
|
||||
with the name:
|
||||
function(Argument1, etc.)
|
||||
Functions and function names are described below.
|
||||
|
||||
A pipeline may be "chained" by separating a sequence of commands with pipeline
|
||||
characters '|'. In a chained pipeline, the result of each command is
|
||||
passed as the last argument of the following command. The output of the final
|
||||
command in the pipeline is the value of the pipeline.
|
||||
|
||||
The output of a command will be either one value or two values, the second of
|
||||
which has type error. If that second value is present and evaluates to
|
||||
non-nil, execution terminates and the error is returned to the caller of
|
||||
Execute.
|
||||
|
||||
Variables
|
||||
|
||||
A pipeline inside an action may initialize a variable to capture the result.
|
||||
The initialization has syntax
|
||||
|
||||
$variable := pipeline
|
||||
|
||||
where $variable is the name of the variable. An action that declares a
|
||||
variable produces no output.
|
||||
|
||||
Variables previously declared can also be assigned, using the syntax
|
||||
|
||||
$variable = pipeline
|
||||
|
||||
If a "range" action initializes a variable, the variable is set to the
|
||||
successive elements of the iteration. Also, a "range" may declare two
|
||||
variables, separated by a comma:
|
||||
|
||||
range $index, $element := pipeline
|
||||
|
||||
in which case $index and $element are set to the successive values of the
|
||||
array/slice index or map key and element, respectively. Note that if there is
|
||||
only one variable, it is assigned the element; this is opposite to the
|
||||
convention in Go range clauses.
|
||||
|
||||
A variable's scope extends to the "end" action of the control structure ("if",
|
||||
"with", or "range") in which it is declared, or to the end of the template if
|
||||
there is no such control structure. A template invocation does not inherit
|
||||
variables from the point of its invocation.
|
||||
|
||||
When execution begins, $ is set to the data argument passed to Execute, that is,
|
||||
to the starting value of dot.
|
||||
|
||||
Examples
|
||||
|
||||
Here are some example one-line templates demonstrating pipelines and variables.
|
||||
All produce the quoted word "output":
|
||||
|
||||
{{"\"output\""}}
|
||||
A string constant.
|
||||
{{`"output"`}}
|
||||
A raw string constant.
|
||||
{{printf "%q" "output"}}
|
||||
A function call.
|
||||
{{"output" | printf "%q"}}
|
||||
A function call whose final argument comes from the previous
|
||||
command.
|
||||
{{printf "%q" (print "out" "put")}}
|
||||
A parenthesized argument.
|
||||
{{"put" | printf "%s%s" "out" | printf "%q"}}
|
||||
A more elaborate call.
|
||||
{{"output" | printf "%s" | printf "%q"}}
|
||||
A longer chain.
|
||||
{{with "output"}}{{printf "%q" .}}{{end}}
|
||||
A with action using dot.
|
||||
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||
A with action that creates and uses a variable.
|
||||
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||
A with action that uses the variable in another action.
|
||||
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||
The same, but pipelined.
|
||||
|
||||
Functions
|
||||
|
||||
During execution functions are found in two function maps: first in the
|
||||
template, then in the global function map. By default, no functions are defined
|
||||
in the template but the Funcs method can be used to add them.
|
||||
|
||||
Predefined global functions are named as follows.
|
||||
|
||||
and
|
||||
Returns the boolean AND of its arguments by returning the
|
||||
first empty argument or the last argument, that is,
|
||||
"and x y" behaves as "if x then y else x". All the
|
||||
arguments are evaluated.
|
||||
call
|
||||
Returns the result of calling the first argument, which
|
||||
must be a function, with the remaining arguments as parameters.
|
||||
Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
|
||||
Y is a func-valued field, map entry, or the like.
|
||||
The first argument must be the result of an evaluation
|
||||
that yields a value of function type (as distinct from
|
||||
a predefined function such as print). The function must
|
||||
return either one or two result values, the second of which
|
||||
is of type error. If the arguments don't match the function
|
||||
or the returned error value is non-nil, execution stops.
|
||||
html
|
||||
Returns the escaped HTML equivalent of the textual
|
||||
representation of its arguments. This function is unavailable
|
||||
in html/template, with a few exceptions.
|
||||
index
|
||||
Returns the result of indexing its first argument by the
|
||||
following arguments. Thus "index x 1 2 3" is, in Go syntax,
|
||||
x[1][2][3]. Each indexed item must be a map, slice, or array.
|
||||
js
|
||||
Returns the escaped JavaScript equivalent of the textual
|
||||
representation of its arguments.
|
||||
len
|
||||
Returns the integer length of its argument.
|
||||
not
|
||||
Returns the boolean negation of its single argument.
|
||||
or
|
||||
Returns the boolean OR of its arguments by returning the
|
||||
first non-empty argument or the last argument, that is,
|
||||
"or x y" behaves as "if x then x else y". All the
|
||||
arguments are evaluated.
|
||||
print
|
||||
An alias for fmt.Sprint
|
||||
printf
|
||||
An alias for fmt.Sprintf
|
||||
println
|
||||
An alias for fmt.Sprintln
|
||||
urlquery
|
||||
Returns the escaped value of the textual representation of
|
||||
its arguments in a form suitable for embedding in a URL query.
|
||||
This function is unavailable in html/template, with a few
|
||||
exceptions.
|
||||
|
||||
The boolean functions take any zero value to be false and a non-zero
|
||||
value to be true.
|
||||
|
||||
There is also a set of binary comparison operators defined as
|
||||
functions:
|
||||
|
||||
eq
|
||||
Returns the boolean truth of arg1 == arg2
|
||||
ne
|
||||
Returns the boolean truth of arg1 != arg2
|
||||
lt
|
||||
Returns the boolean truth of arg1 < arg2
|
||||
le
|
||||
Returns the boolean truth of arg1 <= arg2
|
||||
gt
|
||||
Returns the boolean truth of arg1 > arg2
|
||||
ge
|
||||
Returns the boolean truth of arg1 >= arg2
|
||||
|
||||
For simpler multi-way equality tests, eq (only) accepts two or more
|
||||
arguments and compares the second and subsequent to the first,
|
||||
returning in effect
|
||||
|
||||
arg1==arg2 || arg1==arg3 || arg1==arg4 ...
|
||||
|
||||
(Unlike with || in Go, however, eq is a function call and all the
|
||||
arguments will be evaluated.)
|
||||
|
||||
The comparison functions work on basic types only (or named basic
|
||||
types, such as "type Celsius float32"). They implement the Go rules
|
||||
for comparison of values, except that size and exact type are
|
||||
ignored, so any integer value, signed or unsigned, may be compared
|
||||
with any other integer value. (The arithmetic value is compared,
|
||||
not the bit pattern, so all negative integers are less than all
|
||||
unsigned integers.) However, as usual, one may not compare an int
|
||||
with a float32 and so on.
|
||||
|
||||
Associated templates
|
||||
|
||||
Each template is named by a string specified when it is created. Also, each
|
||||
template is associated with zero or more other templates that it may invoke by
|
||||
name; such associations are transitive and form a name space of templates.
|
||||
|
||||
A template may use a template invocation to instantiate another associated
|
||||
template; see the explanation of the "template" action above. The name must be
|
||||
that of a template associated with the template that contains the invocation.
|
||||
|
||||
Nested template definitions
|
||||
|
||||
When parsing a template, another template may be defined and associated with the
|
||||
template being parsed. Template definitions must appear at the top level of the
|
||||
template, much like global variables in a Go program.
|
||||
|
||||
The syntax of such definitions is to surround each template declaration with a
|
||||
"define" and "end" action.
|
||||
|
||||
The define action names the template being created by providing a string
|
||||
constant. Here is a simple example:
|
||||
|
||||
`{{define "T1"}}ONE{{end}}
|
||||
{{define "T2"}}TWO{{end}}
|
||||
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
|
||||
{{template "T3"}}`
|
||||
|
||||
This defines two templates, T1 and T2, and a third T3 that invokes the other two
|
||||
when it is executed. Finally it invokes T3. If executed this template will
|
||||
produce the text
|
||||
|
||||
ONE TWO
|
||||
|
||||
By construction, a template may reside in only one association. If it's
|
||||
necessary to have a template addressable from multiple associations, the
|
||||
template definition must be parsed multiple times to create distinct *Template
|
||||
values, or must be copied with the Clone or AddParseTree method.
|
||||
|
||||
Parse may be called multiple times to assemble the various associated templates;
|
||||
see the ParseFiles and ParseGlob functions and methods for simple ways to parse
|
||||
related templates stored in files.
|
||||
|
||||
A template may be executed directly or through ExecuteTemplate, which executes
|
||||
an associated template identified by name. To invoke our example above, we
|
||||
might write,
|
||||
|
||||
err := tmpl.Execute(os.Stdout, "no data needed")
|
||||
if err != nil {
|
||||
log.Fatalf("execution failed: %s", err)
|
||||
}
|
||||
|
||||
or to invoke a particular template explicitly by name,
|
||||
|
||||
err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
|
||||
if err != nil {
|
||||
log.Fatalf("execution failed: %s", err)
|
||||
}
|
||||
|
||||
*/
|
||||
package template
|
||||
@ -1,986 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"github.com/gogf/gf/g/os/gview/internal/text/template/parse"
|
||||
)
|
||||
|
||||
// maxExecDepth specifies the maximum stack depth of templates within
|
||||
// templates. This limit is only practically reached by accidentally
|
||||
// recursive template invocations. This limit allows us to return
|
||||
// an error instead of triggering a stack overflow.
|
||||
var maxExecDepth = initMaxExecDepth()
|
||||
|
||||
func initMaxExecDepth() int {
|
||||
if runtime.GOARCH == "wasm" {
|
||||
return 1000
|
||||
}
|
||||
return 100000
|
||||
}
|
||||
|
||||
// state represents the state of an execution. It's not part of the
|
||||
// template so that multiple executions of the same template
|
||||
// can execute in parallel.
|
||||
type state struct {
|
||||
tmpl *Template
|
||||
wr io.Writer
|
||||
node parse.Node // current node, for errors
|
||||
vars []variable // push-down stack of variable values.
|
||||
depth int // the height of the stack of executing templates.
|
||||
}
|
||||
|
||||
// variable holds the dynamic value of a variable such as $, $x etc.
|
||||
type variable struct {
|
||||
name string
|
||||
value reflect.Value
|
||||
}
|
||||
|
||||
// push pushes a new variable on the stack.
|
||||
func (s *state) push(name string, value reflect.Value) {
|
||||
s.vars = append(s.vars, variable{name, value})
|
||||
}
|
||||
|
||||
// mark returns the length of the variable stack.
|
||||
func (s *state) mark() int {
|
||||
return len(s.vars)
|
||||
}
|
||||
|
||||
// pop pops the variable stack up to the mark.
|
||||
func (s *state) pop(mark int) {
|
||||
s.vars = s.vars[0:mark]
|
||||
}
|
||||
|
||||
// setVar overwrites the last declared variable with the given name.
|
||||
// Used by variable assignments.
|
||||
func (s *state) setVar(name string, value reflect.Value) {
|
||||
for i := s.mark() - 1; i >= 0; i-- {
|
||||
if s.vars[i].name == name {
|
||||
s.vars[i].value = value
|
||||
return
|
||||
}
|
||||
}
|
||||
s.errorf("undefined variable: %s", name)
|
||||
}
|
||||
|
||||
// setTopVar overwrites the top-nth variable on the stack. Used by range iterations.
|
||||
func (s *state) setTopVar(n int, value reflect.Value) {
|
||||
s.vars[len(s.vars)-n].value = value
|
||||
}
|
||||
|
||||
// varValue returns the value of the named variable.
|
||||
func (s *state) varValue(name string) reflect.Value {
|
||||
for i := s.mark() - 1; i >= 0; i-- {
|
||||
if s.vars[i].name == name {
|
||||
return s.vars[i].value
|
||||
}
|
||||
}
|
||||
s.errorf("undefined variable: %s", name)
|
||||
return zero
|
||||
}
|
||||
|
||||
var zero reflect.Value
|
||||
|
||||
type missingValType struct{}
|
||||
|
||||
var missingVal = reflect.ValueOf(missingValType{})
|
||||
|
||||
// at marks the state to be on node n, for error reporting.
|
||||
func (s *state) at(node parse.Node) {
|
||||
s.node = node
|
||||
}
|
||||
|
||||
// doublePercent returns the string with %'s replaced by %%, if necessary,
|
||||
// so it can be used safely inside a Printf format string.
|
||||
func doublePercent(str string) string {
|
||||
return strings.Replace(str, "%", "%%", -1)
|
||||
}
|
||||
|
||||
// TODO: It would be nice if ExecError was more broken down, but
|
||||
// the way ErrorContext embeds the template name makes the
|
||||
// processing too clumsy.
|
||||
|
||||
// ExecError is the custom error type returned when Execute has an
|
||||
// error evaluating its template. (If a write error occurs, the actual
|
||||
// error is returned; it will not be of type ExecError.)
|
||||
type ExecError struct {
|
||||
Name string // Name of template.
|
||||
Err error // Pre-formatted error.
|
||||
}
|
||||
|
||||
func (e ExecError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// errorf records an ExecError and terminates processing.
|
||||
func (s *state) errorf(format string, args ...interface{}) {
|
||||
name := doublePercent(s.tmpl.Name())
|
||||
if s.node == nil {
|
||||
format = fmt.Sprintf("template: %s: %s", name, format)
|
||||
} else {
|
||||
location, context := s.tmpl.ErrorContext(s.node)
|
||||
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
|
||||
}
|
||||
panic(ExecError{
|
||||
Name: s.tmpl.Name(),
|
||||
Err: fmt.Errorf(format, args...),
|
||||
})
|
||||
}
|
||||
|
||||
// writeError is the wrapper type used internally when Execute has an
|
||||
// error writing to its output. We strip the wrapper in errRecover.
|
||||
// Note that this is not an implementation of error, so it cannot escape
|
||||
// from the package as an error value.
|
||||
type writeError struct {
|
||||
Err error // Original error.
|
||||
}
|
||||
|
||||
func (s *state) writeError(err error) {
|
||||
panic(writeError{
|
||||
Err: err,
|
||||
})
|
||||
}
|
||||
|
||||
// errRecover is the handler that turns panics into returns from the top
|
||||
// level of Parse.
|
||||
func errRecover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
switch err := e.(type) {
|
||||
case runtime.Error:
|
||||
panic(e)
|
||||
case writeError:
|
||||
*errp = err.Err // Strip the wrapper.
|
||||
case ExecError:
|
||||
*errp = err // Keep the wrapper.
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteTemplate applies the template associated with t that has the given name
|
||||
// to the specified data object and writes the output to wr.
|
||||
// If an error occurs executing the template or writing its output,
|
||||
// execution stops, but partial results may already have been written to
|
||||
// the output writer.
|
||||
// A template may be executed safely in parallel, although if parallel
|
||||
// executions share a Writer the output may be interleaved.
|
||||
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
||||
var tmpl *Template
|
||||
if t.common != nil {
|
||||
tmpl = t.tmpl[name]
|
||||
}
|
||||
if tmpl == nil {
|
||||
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
|
||||
}
|
||||
return tmpl.Execute(wr, data)
|
||||
}
|
||||
|
||||
// Execute applies a parsed template to the specified data object,
|
||||
// and writes the output to wr.
|
||||
// If an error occurs executing the template or writing its output,
|
||||
// execution stops, but partial results may already have been written to
|
||||
// the output writer.
|
||||
// A template may be executed safely in parallel, although if parallel
|
||||
// executions share a Writer the output may be interleaved.
|
||||
//
|
||||
// If data is a reflect.Value, the template applies to the concrete
|
||||
// value that the reflect.Value holds, as in fmt.Print.
|
||||
func (t *Template) Execute(wr io.Writer, data interface{}) error {
|
||||
return t.execute(wr, data)
|
||||
}
|
||||
|
||||
func (t *Template) execute(wr io.Writer, data interface{}) (err error) {
|
||||
defer errRecover(&err)
|
||||
value, ok := data.(reflect.Value)
|
||||
if !ok {
|
||||
value = reflect.ValueOf(data)
|
||||
}
|
||||
state := &state{
|
||||
tmpl: t,
|
||||
wr: wr,
|
||||
vars: []variable{{"$", value}},
|
||||
}
|
||||
if t.Tree == nil || t.Root == nil {
|
||||
state.errorf("%q is an incomplete or empty template", t.Name())
|
||||
}
|
||||
state.walk(value, t.Root)
|
||||
return
|
||||
}
|
||||
|
||||
// DefinedTemplates returns a string listing the defined templates,
|
||||
// prefixed by the string "; defined templates are: ". If there are none,
|
||||
// it returns the empty string. For generating an error message here
|
||||
// and in html/template.
|
||||
func (t *Template) DefinedTemplates() string {
|
||||
if t.common == nil {
|
||||
return ""
|
||||
}
|
||||
var b bytes.Buffer
|
||||
for name, tmpl := range t.tmpl {
|
||||
if tmpl.Tree == nil || tmpl.Root == nil {
|
||||
continue
|
||||
}
|
||||
if b.Len() > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&b, "%q", name)
|
||||
}
|
||||
var s string
|
||||
if b.Len() > 0 {
|
||||
s = "; defined templates are: " + b.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Walk functions step through the major pieces of the template structure,
|
||||
// generating output as they go.
|
||||
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
||||
s.at(node)
|
||||
switch node := node.(type) {
|
||||
case *parse.ActionNode:
|
||||
// Do not pop variables so they persist until next end.
|
||||
// Also, if the action declares variables, don't print the result.
|
||||
val := s.evalPipeline(dot, node.Pipe)
|
||||
if len(node.Pipe.Decl) == 0 {
|
||||
s.printValue(node, val)
|
||||
}
|
||||
case *parse.IfNode:
|
||||
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
||||
case *parse.ListNode:
|
||||
for _, node := range node.Nodes {
|
||||
s.walk(dot, node)
|
||||
}
|
||||
case *parse.RangeNode:
|
||||
s.walkRange(dot, node)
|
||||
case *parse.TemplateNode:
|
||||
s.walkTemplate(dot, node)
|
||||
case *parse.TextNode:
|
||||
if _, err := s.wr.Write(node.Text); err != nil {
|
||||
s.writeError(err)
|
||||
}
|
||||
case *parse.WithNode:
|
||||
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
|
||||
default:
|
||||
s.errorf("unknown node: %s", node)
|
||||
}
|
||||
}
|
||||
|
||||
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
||||
// are identical in behavior except that 'with' sets dot.
|
||||
func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
|
||||
defer s.pop(s.mark())
|
||||
val := s.evalPipeline(dot, pipe)
|
||||
truth, ok := isTrue(val)
|
||||
if !ok {
|
||||
s.errorf("if/with can't use %v", val)
|
||||
}
|
||||
if truth {
|
||||
if typ == parse.NodeWith {
|
||||
s.walk(val, list)
|
||||
} else {
|
||||
s.walk(dot, list)
|
||||
}
|
||||
} else if elseList != nil {
|
||||
s.walk(dot, elseList)
|
||||
}
|
||||
}
|
||||
|
||||
// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||
// and whether the value has a meaningful truth value. This is the definition of
|
||||
// truth used by if and other such actions.
|
||||
func IsTrue(val interface{}) (truth, ok bool) {
|
||||
return isTrue(reflect.ValueOf(val))
|
||||
}
|
||||
|
||||
func isTrue(val reflect.Value) (truth, ok bool) {
|
||||
if !val.IsValid() {
|
||||
// Something like var x interface{}, never set. It's a form of nil.
|
||||
return false, true
|
||||
}
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
truth = val.Len() > 0
|
||||
case reflect.Bool:
|
||||
truth = val.Bool()
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
truth = val.Complex() != 0
|
||||
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
|
||||
truth = !val.IsNil()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
truth = val.Int() != 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
truth = val.Float() != 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
truth = val.Uint() != 0
|
||||
case reflect.Struct:
|
||||
truth = true // Struct values are always true.
|
||||
default:
|
||||
return
|
||||
}
|
||||
return truth, true
|
||||
}
|
||||
|
||||
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||
s.at(r)
|
||||
defer s.pop(s.mark())
|
||||
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
||||
// mark top of stack before any variables in the body are pushed.
|
||||
mark := s.mark()
|
||||
oneIteration := func(index, elem reflect.Value) {
|
||||
// Set top var (lexically the second if there are two) to the element.
|
||||
if len(r.Pipe.Decl) > 0 {
|
||||
s.setTopVar(1, elem)
|
||||
}
|
||||
// Set next var (lexically the first if there are two) to the index.
|
||||
if len(r.Pipe.Decl) > 1 {
|
||||
s.setTopVar(2, index)
|
||||
}
|
||||
s.walk(elem, r.List)
|
||||
s.pop(mark)
|
||||
}
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if val.Len() == 0 {
|
||||
break
|
||||
}
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
oneIteration(reflect.ValueOf(i), val.Index(i))
|
||||
}
|
||||
return
|
||||
case reflect.Map:
|
||||
if val.Len() == 0 {
|
||||
break
|
||||
}
|
||||
for _, key := range sortKeys(val.MapKeys()) {
|
||||
oneIteration(key, val.MapIndex(key))
|
||||
}
|
||||
return
|
||||
case reflect.Chan:
|
||||
if val.IsNil() {
|
||||
break
|
||||
}
|
||||
i := 0
|
||||
for ; ; i++ {
|
||||
elem, ok := val.Recv()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
oneIteration(reflect.ValueOf(i), elem)
|
||||
}
|
||||
if i == 0 {
|
||||
break
|
||||
}
|
||||
return
|
||||
case reflect.Invalid:
|
||||
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
||||
default:
|
||||
s.errorf("range can't iterate over %v", val)
|
||||
}
|
||||
if r.ElseList != nil {
|
||||
s.walk(dot, r.ElseList)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
|
||||
s.at(t)
|
||||
tmpl := s.tmpl.tmpl[t.Name]
|
||||
if tmpl == nil {
|
||||
s.errorf("template %q not defined", t.Name)
|
||||
}
|
||||
if s.depth == maxExecDepth {
|
||||
s.errorf("exceeded maximum template depth (%v)", maxExecDepth)
|
||||
}
|
||||
// Variables declared by the pipeline persist.
|
||||
dot = s.evalPipeline(dot, t.Pipe)
|
||||
newState := *s
|
||||
newState.depth++
|
||||
newState.tmpl = tmpl
|
||||
// No dynamic scoping: template invocations inherit no variables.
|
||||
newState.vars = []variable{{"$", dot}}
|
||||
newState.walk(dot, tmpl.Root)
|
||||
}
|
||||
|
||||
// Eval functions evaluate pipelines, commands, and their elements and extract
|
||||
// values from the data structure by examining fields, calling methods, and so on.
|
||||
// The printing of those values happens only through walk functions.
|
||||
|
||||
// evalPipeline returns the value acquired by evaluating a pipeline. If the
|
||||
// pipeline has a variable declaration, the variable will be pushed on the
|
||||
// stack. Callers should therefore pop the stack after they are finished
|
||||
// executing commands depending on the pipeline value.
|
||||
func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
|
||||
if pipe == nil {
|
||||
return
|
||||
}
|
||||
s.at(pipe)
|
||||
value = missingVal
|
||||
for _, cmd := range pipe.Cmds {
|
||||
value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
|
||||
// If the object has type interface{}, dig down one level to the thing inside.
|
||||
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
|
||||
value = reflect.ValueOf(value.Interface()) // lovely!
|
||||
}
|
||||
}
|
||||
for _, variable := range pipe.Decl {
|
||||
if pipe.IsAssign {
|
||||
s.setVar(variable.Ident[0], value)
|
||||
} else {
|
||||
s.push(variable.Ident[0], value)
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *state) notAFunction(args []parse.Node, final reflect.Value) {
|
||||
if len(args) > 1 || final != missingVal {
|
||||
s.errorf("can't give argument to non-function %s", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
|
||||
firstWord := cmd.Args[0]
|
||||
switch n := firstWord.(type) {
|
||||
case *parse.FieldNode:
|
||||
return s.evalFieldNode(dot, n, cmd.Args, final)
|
||||
case *parse.ChainNode:
|
||||
return s.evalChainNode(dot, n, cmd.Args, final)
|
||||
case *parse.IdentifierNode:
|
||||
// Must be a function.
|
||||
return s.evalFunction(dot, n, cmd, cmd.Args, final)
|
||||
case *parse.PipeNode:
|
||||
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
|
||||
return s.evalPipeline(dot, n)
|
||||
case *parse.VariableNode:
|
||||
return s.evalVariableNode(dot, n, cmd.Args, final)
|
||||
}
|
||||
s.at(firstWord)
|
||||
s.notAFunction(cmd.Args, final)
|
||||
switch word := firstWord.(type) {
|
||||
case *parse.BoolNode:
|
||||
return reflect.ValueOf(word.True)
|
||||
case *parse.DotNode:
|
||||
return dot
|
||||
case *parse.NilNode:
|
||||
s.errorf("nil is not a command")
|
||||
case *parse.NumberNode:
|
||||
return s.idealConstant(word)
|
||||
case *parse.StringNode:
|
||||
return reflect.ValueOf(word.Text)
|
||||
}
|
||||
s.errorf("can't evaluate command %q", firstWord)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// idealConstant is called to return the value of a number in a context where
|
||||
// we don't know the type. In that case, the syntax of the number tells us
|
||||
// its type, and we use Go rules to resolve. Note there is no such thing as
|
||||
// a uint ideal constant in this situation - the value must be of int type.
|
||||
func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
|
||||
// These are ideal constants but we don't know the type
|
||||
// and we have no context. (If it was a method argument,
|
||||
// we'd know what we need.) The syntax guides us to some extent.
|
||||
s.at(constant)
|
||||
switch {
|
||||
case constant.IsComplex:
|
||||
return reflect.ValueOf(constant.Complex128) // incontrovertible.
|
||||
case constant.IsFloat && !isHexConstant(constant.Text) && strings.ContainsAny(constant.Text, ".eE"):
|
||||
return reflect.ValueOf(constant.Float64)
|
||||
case constant.IsInt:
|
||||
n := int(constant.Int64)
|
||||
if int64(n) != constant.Int64 {
|
||||
s.errorf("%s overflows int", constant.Text)
|
||||
}
|
||||
return reflect.ValueOf(n)
|
||||
case constant.IsUint:
|
||||
s.errorf("%s overflows int", constant.Text)
|
||||
}
|
||||
return zero
|
||||
}
|
||||
|
||||
func isHexConstant(s string) bool {
|
||||
return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
|
||||
}
|
||||
|
||||
func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(field)
|
||||
return s.evalFieldChain(dot, dot, field, field.Ident, args, final)
|
||||
}
|
||||
|
||||
func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(chain)
|
||||
if len(chain.Field) == 0 {
|
||||
s.errorf("internal error: no fields in evalChainNode")
|
||||
}
|
||||
if chain.Node.Type() == parse.NodeNil {
|
||||
s.errorf("indirection through explicit nil in %s", chain)
|
||||
}
|
||||
// (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields.
|
||||
pipe := s.evalArg(dot, nil, chain.Node)
|
||||
return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
|
||||
}
|
||||
|
||||
func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
|
||||
s.at(variable)
|
||||
value := s.varValue(variable.Ident[0])
|
||||
if len(variable.Ident) == 1 {
|
||||
s.notAFunction(args, final)
|
||||
return value
|
||||
}
|
||||
return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final)
|
||||
}
|
||||
|
||||
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
|
||||
// dot is the environment in which to evaluate arguments, while
|
||||
// receiver is the value being walked along the chain.
|
||||
func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
n := len(ident)
|
||||
for i := 0; i < n-1; i++ {
|
||||
receiver = s.evalField(dot, ident[i], node, nil, missingVal, receiver)
|
||||
}
|
||||
// Now if it's a method, it gets the arguments.
|
||||
return s.evalField(dot, ident[n-1], node, args, final, receiver)
|
||||
}
|
||||
|
||||
func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(node)
|
||||
name := node.Ident
|
||||
function, ok := findFunction(name, s.tmpl)
|
||||
if !ok {
|
||||
s.errorf("%q is not a defined function", name)
|
||||
}
|
||||
return s.evalCall(dot, function, cmd, name, args, final)
|
||||
}
|
||||
|
||||
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
||||
// The 'final' argument represents the return value from the preceding
|
||||
// value of the pipeline, if any.
|
||||
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
|
||||
if !receiver.IsValid() {
|
||||
if s.tmpl.option.missingKey == mapError { // Treat invalid value as missing map key.
|
||||
s.errorf("nil data; no entry for key %q", fieldName)
|
||||
}
|
||||
return zero
|
||||
}
|
||||
typ := receiver.Type()
|
||||
receiver, isNil := indirect(receiver)
|
||||
// Unless it's an interface, need to get to a value of type *T to guarantee
|
||||
// we see all methods of T and *T.
|
||||
ptr := receiver
|
||||
if ptr.Kind() != reflect.Interface && ptr.Kind() != reflect.Ptr && ptr.CanAddr() {
|
||||
ptr = ptr.Addr()
|
||||
}
|
||||
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
||||
return s.evalCall(dot, method, node, fieldName, args, final)
|
||||
}
|
||||
hasArgs := len(args) > 1 || final != missingVal
|
||||
// It's not a method; must be a field of a struct or an element of a map.
|
||||
switch receiver.Kind() {
|
||||
case reflect.Struct:
|
||||
tField, ok := receiver.Type().FieldByName(fieldName)
|
||||
if ok {
|
||||
if isNil {
|
||||
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
|
||||
}
|
||||
field := receiver.FieldByIndex(tField.Index)
|
||||
if tField.PkgPath != "" { // field is unexported
|
||||
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
|
||||
}
|
||||
// If it's a function, we must call it.
|
||||
if hasArgs {
|
||||
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
|
||||
}
|
||||
return field
|
||||
}
|
||||
case reflect.Map:
|
||||
if isNil {
|
||||
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
|
||||
}
|
||||
// If it's a map, attempt to use the field name as a key.
|
||||
nameVal := reflect.ValueOf(fieldName)
|
||||
if nameVal.Type().AssignableTo(receiver.Type().Key()) {
|
||||
if hasArgs {
|
||||
s.errorf("%s is not a method but has arguments", fieldName)
|
||||
}
|
||||
result := receiver.MapIndex(nameVal)
|
||||
if !result.IsValid() {
|
||||
switch s.tmpl.option.missingKey {
|
||||
case mapInvalid:
|
||||
// Just use the invalid value.
|
||||
case mapZeroValue:
|
||||
result = reflect.Zero(receiver.Type().Elem())
|
||||
case mapError:
|
||||
s.errorf("map has no entry for key %q", fieldName)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
var (
|
||||
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
reflectValueType = reflect.TypeOf((*reflect.Value)(nil)).Elem()
|
||||
)
|
||||
|
||||
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
||||
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
||||
// as the function itself.
|
||||
func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
if args != nil {
|
||||
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||
}
|
||||
typ := fun.Type()
|
||||
numIn := len(args)
|
||||
if final != missingVal {
|
||||
numIn++
|
||||
}
|
||||
numFixed := len(args)
|
||||
if typ.IsVariadic() {
|
||||
numFixed = typ.NumIn() - 1 // last arg is the variadic one.
|
||||
if numIn < numFixed {
|
||||
s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
|
||||
}
|
||||
} else if numIn != typ.NumIn() {
|
||||
s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), numIn)
|
||||
}
|
||||
if !goodFunc(typ) {
|
||||
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
||||
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
||||
}
|
||||
// Build the arg list.
|
||||
argv := make([]reflect.Value, numIn)
|
||||
// Args must be evaluated. Fixed args first.
|
||||
i := 0
|
||||
for ; i < numFixed && i < len(args); i++ {
|
||||
argv[i] = s.evalArg(dot, typ.In(i), args[i])
|
||||
}
|
||||
// Now the ... args.
|
||||
if typ.IsVariadic() {
|
||||
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
|
||||
for ; i < len(args); i++ {
|
||||
argv[i] = s.evalArg(dot, argType, args[i])
|
||||
}
|
||||
}
|
||||
// Add final value if necessary.
|
||||
if final != missingVal {
|
||||
t := typ.In(typ.NumIn() - 1)
|
||||
if typ.IsVariadic() {
|
||||
if numIn-1 < numFixed {
|
||||
// The added final argument corresponds to a fixed parameter of the function.
|
||||
// Validate against the type of the actual parameter.
|
||||
t = typ.In(numIn - 1)
|
||||
} else {
|
||||
// The added final argument corresponds to the variadic part.
|
||||
// Validate against the type of the elements of the variadic slice.
|
||||
t = t.Elem()
|
||||
}
|
||||
}
|
||||
argv[i] = s.validateType(final, t)
|
||||
}
|
||||
result := fun.Call(argv)
|
||||
// If we have an error that is not nil, stop execution and return that error to the caller.
|
||||
if len(result) == 2 && !result[1].IsNil() {
|
||||
s.at(node)
|
||||
s.errorf("error calling %s: %s", name, result[1].Interface().(error))
|
||||
}
|
||||
v := result[0]
|
||||
if v.Type() == reflectValueType {
|
||||
v = v.Interface().(reflect.Value)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||
func canBeNil(typ reflect.Type) bool {
|
||||
switch typ.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return true
|
||||
case reflect.Struct:
|
||||
return typ == reflectValueType
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validateType guarantees that the value is valid and assignable to the type.
|
||||
func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
|
||||
if !value.IsValid() {
|
||||
if typ == nil {
|
||||
// An untyped nil interface{}. Accept as a proper nil value.
|
||||
return reflect.ValueOf(nil)
|
||||
}
|
||||
if canBeNil(typ) {
|
||||
// Like above, but use the zero value of the non-nil type.
|
||||
return reflect.Zero(typ)
|
||||
}
|
||||
s.errorf("invalid value; expected %s", typ)
|
||||
}
|
||||
if typ == reflectValueType && value.Type() != typ {
|
||||
return reflect.ValueOf(value)
|
||||
}
|
||||
if typ != nil && !value.Type().AssignableTo(typ) {
|
||||
if value.Kind() == reflect.Interface && !value.IsNil() {
|
||||
value = value.Elem()
|
||||
if value.Type().AssignableTo(typ) {
|
||||
return value
|
||||
}
|
||||
// fallthrough
|
||||
}
|
||||
// Does one dereference or indirection work? We could do more, as we
|
||||
// do with method receivers, but that gets messy and method receivers
|
||||
// are much more constrained, so it makes more sense there than here.
|
||||
// Besides, one is almost always all you need.
|
||||
switch {
|
||||
case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
|
||||
value = value.Elem()
|
||||
if !value.IsValid() {
|
||||
s.errorf("dereference of nil pointer of type %s", typ)
|
||||
}
|
||||
case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
|
||||
value = value.Addr()
|
||||
default:
|
||||
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
switch arg := n.(type) {
|
||||
case *parse.DotNode:
|
||||
return s.validateType(dot, typ)
|
||||
case *parse.NilNode:
|
||||
if canBeNil(typ) {
|
||||
return reflect.Zero(typ)
|
||||
}
|
||||
s.errorf("cannot assign nil to %s", typ)
|
||||
case *parse.FieldNode:
|
||||
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, missingVal), typ)
|
||||
case *parse.VariableNode:
|
||||
return s.validateType(s.evalVariableNode(dot, arg, nil, missingVal), typ)
|
||||
case *parse.PipeNode:
|
||||
return s.validateType(s.evalPipeline(dot, arg), typ)
|
||||
case *parse.IdentifierNode:
|
||||
return s.validateType(s.evalFunction(dot, arg, arg, nil, missingVal), typ)
|
||||
case *parse.ChainNode:
|
||||
return s.validateType(s.evalChainNode(dot, arg, nil, missingVal), typ)
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool:
|
||||
return s.evalBool(typ, n)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return s.evalComplex(typ, n)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return s.evalFloat(typ, n)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return s.evalInteger(typ, n)
|
||||
case reflect.Interface:
|
||||
if typ.NumMethod() == 0 {
|
||||
return s.evalEmptyInterface(dot, n)
|
||||
}
|
||||
case reflect.Struct:
|
||||
if typ == reflectValueType {
|
||||
return reflect.ValueOf(s.evalEmptyInterface(dot, n))
|
||||
}
|
||||
case reflect.String:
|
||||
return s.evalString(typ, n)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return s.evalUnsignedInteger(typ, n)
|
||||
}
|
||||
s.errorf("can't handle %s for arg of type %s", n, typ)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.BoolNode); ok {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetBool(n.True)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected bool; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.StringNode); ok {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetString(n.Text)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected string; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetInt(n.Int64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected integer; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetUint(n.Uint64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected unsigned integer; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetFloat(n.Float64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected float; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetComplex(n.Complex128)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected complex; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
switch n := n.(type) {
|
||||
case *parse.BoolNode:
|
||||
return reflect.ValueOf(n.True)
|
||||
case *parse.DotNode:
|
||||
return dot
|
||||
case *parse.FieldNode:
|
||||
return s.evalFieldNode(dot, n, nil, missingVal)
|
||||
case *parse.IdentifierNode:
|
||||
return s.evalFunction(dot, n, n, nil, missingVal)
|
||||
case *parse.NilNode:
|
||||
// NilNode is handled in evalArg, the only place that calls here.
|
||||
s.errorf("evalEmptyInterface: nil (can't happen)")
|
||||
case *parse.NumberNode:
|
||||
return s.idealConstant(n)
|
||||
case *parse.StringNode:
|
||||
return reflect.ValueOf(n.Text)
|
||||
case *parse.VariableNode:
|
||||
return s.evalVariableNode(dot, n, nil, missingVal)
|
||||
case *parse.PipeNode:
|
||||
return s.evalPipeline(dot, n)
|
||||
}
|
||||
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
|
||||
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
||||
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
||||
if v.IsNil() {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return v, false
|
||||
}
|
||||
|
||||
// indirectInterface returns the concrete value in an interface value,
|
||||
// or else the zero reflect.Value.
|
||||
// That is, if v represents the interface value x, the result is the same as reflect.ValueOf(x):
|
||||
// the fact that x was an interface value is forgotten.
|
||||
func indirectInterface(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Interface {
|
||||
return v
|
||||
}
|
||||
if v.IsNil() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
return v.Elem()
|
||||
}
|
||||
|
||||
// printValue writes the textual representation of the value to the output of
|
||||
// the template.
|
||||
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
||||
s.at(n)
|
||||
iface, ok := printableValue(v)
|
||||
if !ok {
|
||||
s.errorf("can't print %s of type %s", n, v.Type())
|
||||
}
|
||||
_, err := fmt.Fprint(s.wr, iface)
|
||||
if err != nil {
|
||||
s.writeError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// printableValue returns the, possibly indirected, interface value inside v that
|
||||
// is best for a call to formatted printer.
|
||||
func printableValue(v reflect.Value) (interface{}, bool) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v, _ = indirect(v) // fmt.Fprint handles nil.
|
||||
}
|
||||
if !v.IsValid() {
|
||||
return "", true
|
||||
}
|
||||
|
||||
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
||||
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
|
||||
v = v.Addr()
|
||||
} else {
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return v.Interface(), true
|
||||
}
|
||||
|
||||
// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys.
|
||||
func sortKeys(v []reflect.Value) []reflect.Value {
|
||||
if len(v) <= 1 {
|
||||
return v
|
||||
}
|
||||
switch v[0].Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
sort.Slice(v, func(i, j int) bool {
|
||||
return v[i].Float() < v[j].Float()
|
||||
})
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
sort.Slice(v, func(i, j int) bool {
|
||||
return v[i].Int() < v[j].Int()
|
||||
})
|
||||
case reflect.String:
|
||||
sort.Slice(v, func(i, j int) bool {
|
||||
return v[i].String() < v[j].String()
|
||||
})
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
sort.Slice(v, func(i, j int) bool {
|
||||
return v[i].Uint() < v[j].Uint()
|
||||
})
|
||||
}
|
||||
return v
|
||||
}
|
||||
@ -1,667 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// FuncMap is the type of the map defining the mapping from names to functions.
|
||||
// Each function must have either a single return value, or two return values of
|
||||
// which the second has type error. In that case, if the second (error)
|
||||
// return value evaluates to non-nil during execution, execution terminates and
|
||||
// Execute returns that error.
|
||||
//
|
||||
// When template execution invokes a function with an argument list, that list
|
||||
// must be assignable to the function's parameter types. Functions meant to
|
||||
// apply to arguments of arbitrary type can use parameters of type interface{} or
|
||||
// of type reflect.Value. Similarly, functions meant to return a result of arbitrary
|
||||
// type can return interface{} or reflect.Value.
|
||||
type FuncMap map[string]interface{}
|
||||
|
||||
var builtins = FuncMap{
|
||||
"and": and,
|
||||
"call": call,
|
||||
"html": HTMLEscaper,
|
||||
"index": index,
|
||||
"js": JSEscaper,
|
||||
"len": length,
|
||||
"not": not,
|
||||
"or": or,
|
||||
"print": fmt.Sprint,
|
||||
"printf": fmt.Sprintf,
|
||||
"println": fmt.Sprintln,
|
||||
"urlquery": URLQueryEscaper,
|
||||
|
||||
// Comparisons
|
||||
"eq": eq, // ==
|
||||
"ge": ge, // >=
|
||||
"gt": gt, // >
|
||||
"le": le, // <=
|
||||
"lt": lt, // <
|
||||
"ne": ne, // !=
|
||||
}
|
||||
|
||||
var builtinFuncs = createValueFuncs(builtins)
|
||||
|
||||
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
|
||||
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
|
||||
m := make(map[string]reflect.Value)
|
||||
addValueFuncs(m, funcMap)
|
||||
return m
|
||||
}
|
||||
|
||||
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
|
||||
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
|
||||
for name, fn := range in {
|
||||
if !goodName(name) {
|
||||
panic(fmt.Errorf("function name %s is not a valid identifier", name))
|
||||
}
|
||||
v := reflect.ValueOf(fn)
|
||||
if v.Kind() != reflect.Func {
|
||||
panic("value for " + name + " not a function")
|
||||
}
|
||||
if !goodFunc(v.Type()) {
|
||||
panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
|
||||
}
|
||||
out[name] = v
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncs adds to values the functions in funcs. It does no checking of the input -
|
||||
// call addValueFuncs first.
|
||||
func addFuncs(out, in FuncMap) {
|
||||
for name, fn := range in {
|
||||
out[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// goodFunc reports whether the function or method has the right result signature.
|
||||
func goodFunc(typ reflect.Type) bool {
|
||||
// We allow functions with 1 result or 2 results where the second is an error.
|
||||
switch {
|
||||
case typ.NumOut() == 1:
|
||||
return true
|
||||
case typ.NumOut() == 2 && typ.Out(1) == errorType:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// goodName reports whether the function name is a valid identifier.
|
||||
func goodName(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
for i, r := range name {
|
||||
switch {
|
||||
case r == '_':
|
||||
case i == 0 && !unicode.IsLetter(r):
|
||||
return false
|
||||
case !unicode.IsLetter(r) && !unicode.IsDigit(r):
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// findFunction looks for a function in the template, and global map.
|
||||
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
||||
if tmpl != nil && tmpl.common != nil {
|
||||
tmpl.muFuncs.RLock()
|
||||
defer tmpl.muFuncs.RUnlock()
|
||||
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
}
|
||||
if fn := builtinFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
return reflect.Value{}, false
|
||||
}
|
||||
|
||||
// prepareArg checks if value can be used as an argument of type argType, and
|
||||
// converts an invalid value to appropriate zero if possible.
|
||||
func prepareArg(value reflect.Value, argType reflect.Type) (reflect.Value, error) {
|
||||
if !value.IsValid() {
|
||||
if !canBeNil(argType) {
|
||||
return reflect.Value{}, fmt.Errorf("value is nil; should be of type %s", argType)
|
||||
}
|
||||
value = reflect.Zero(argType)
|
||||
}
|
||||
if value.Type().AssignableTo(argType) {
|
||||
return value, nil
|
||||
}
|
||||
if intLike(value.Kind()) && intLike(argType.Kind()) && value.Type().ConvertibleTo(argType) {
|
||||
value = value.Convert(argType)
|
||||
return value, nil
|
||||
}
|
||||
return reflect.Value{}, fmt.Errorf("value has type %s; should be %s", value.Type(), argType)
|
||||
}
|
||||
|
||||
func intLike(typ reflect.Kind) bool {
|
||||
switch typ {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return true
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Indexing.
|
||||
|
||||
// index returns the result of indexing its first argument by the following
|
||||
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
||||
// indexed item must be a map, slice, or array.
|
||||
func index(item reflect.Value, indices ...reflect.Value) (reflect.Value, error) {
|
||||
v := indirectInterface(item)
|
||||
if !v.IsValid() {
|
||||
return reflect.Value{}, fmt.Errorf("index of untyped nil")
|
||||
}
|
||||
for _, i := range indices {
|
||||
index := indirectInterface(i)
|
||||
var isNil bool
|
||||
if v, isNil = indirect(v); isNil {
|
||||
return reflect.Value{}, fmt.Errorf("index of nil pointer")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.String:
|
||||
var x int64
|
||||
switch index.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x = index.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
x = int64(index.Uint())
|
||||
case reflect.Invalid:
|
||||
return reflect.Value{}, fmt.Errorf("cannot index slice/array with nil")
|
||||
default:
|
||||
return reflect.Value{}, fmt.Errorf("cannot index slice/array with type %s", index.Type())
|
||||
}
|
||||
if x < 0 || x >= int64(v.Len()) {
|
||||
return reflect.Value{}, fmt.Errorf("index out of range: %d", x)
|
||||
}
|
||||
v = v.Index(int(x))
|
||||
case reflect.Map:
|
||||
index, err := prepareArg(index, v.Type().Key())
|
||||
if err != nil {
|
||||
return reflect.Value{}, err
|
||||
}
|
||||
if x := v.MapIndex(index); x.IsValid() {
|
||||
v = x
|
||||
} else {
|
||||
v = reflect.Zero(v.Type().Elem())
|
||||
}
|
||||
case reflect.Invalid:
|
||||
// the loop holds invariant: v.IsValid()
|
||||
panic("unreachable")
|
||||
default:
|
||||
return reflect.Value{}, fmt.Errorf("can't index item of type %s", v.Type())
|
||||
}
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Length
|
||||
|
||||
// length returns the length of the item, with an error if it has no defined length.
|
||||
func length(item interface{}) (int, error) {
|
||||
v := reflect.ValueOf(item)
|
||||
if !v.IsValid() {
|
||||
return 0, fmt.Errorf("len of untyped nil")
|
||||
}
|
||||
v, isNil := indirect(v)
|
||||
if isNil {
|
||||
return 0, fmt.Errorf("len of nil pointer")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len(), nil
|
||||
}
|
||||
return 0, fmt.Errorf("len of type %s", v.Type())
|
||||
}
|
||||
|
||||
// Function invocation
|
||||
|
||||
// call returns the result of evaluating the first argument as a function.
|
||||
// The function must return 1 result, or 2 results, the second of which is an error.
|
||||
func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error) {
|
||||
v := indirectInterface(fn)
|
||||
if !v.IsValid() {
|
||||
return reflect.Value{}, fmt.Errorf("call of nil")
|
||||
}
|
||||
typ := v.Type()
|
||||
if typ.Kind() != reflect.Func {
|
||||
return reflect.Value{}, fmt.Errorf("non-function of type %s", typ)
|
||||
}
|
||||
if !goodFunc(typ) {
|
||||
return reflect.Value{}, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
|
||||
}
|
||||
numIn := typ.NumIn()
|
||||
var dddType reflect.Type
|
||||
if typ.IsVariadic() {
|
||||
if len(args) < numIn-1 {
|
||||
return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
|
||||
}
|
||||
dddType = typ.In(numIn - 1).Elem()
|
||||
} else {
|
||||
if len(args) != numIn {
|
||||
return reflect.Value{}, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
|
||||
}
|
||||
}
|
||||
argv := make([]reflect.Value, len(args))
|
||||
for i, arg := range args {
|
||||
value := indirectInterface(arg)
|
||||
// Compute the expected type. Clumsy because of variadics.
|
||||
var argType reflect.Type
|
||||
if !typ.IsVariadic() || i < numIn-1 {
|
||||
argType = typ.In(i)
|
||||
} else {
|
||||
argType = dddType
|
||||
}
|
||||
|
||||
var err error
|
||||
if argv[i], err = prepareArg(value, argType); err != nil {
|
||||
return reflect.Value{}, fmt.Errorf("arg %d: %s", i, err)
|
||||
}
|
||||
}
|
||||
result := v.Call(argv)
|
||||
if len(result) == 2 && !result[1].IsNil() {
|
||||
return result[0], result[1].Interface().(error)
|
||||
}
|
||||
return result[0], nil
|
||||
}
|
||||
|
||||
// Boolean logic.
|
||||
|
||||
func truth(arg reflect.Value) bool {
|
||||
t, _ := isTrue(indirectInterface(arg))
|
||||
return t
|
||||
}
|
||||
|
||||
// and computes the Boolean AND of its arguments, returning
|
||||
// the first false argument it encounters, or the last argument.
|
||||
func and(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
|
||||
if !truth(arg0) {
|
||||
return arg0
|
||||
}
|
||||
for i := range args {
|
||||
arg0 = args[i]
|
||||
if !truth(arg0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return arg0
|
||||
}
|
||||
|
||||
// or computes the Boolean OR of its arguments, returning
|
||||
// the first true argument it encounters, or the last argument.
|
||||
func or(arg0 reflect.Value, args ...reflect.Value) reflect.Value {
|
||||
if truth(arg0) {
|
||||
return arg0
|
||||
}
|
||||
for i := range args {
|
||||
arg0 = args[i]
|
||||
if truth(arg0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return arg0
|
||||
}
|
||||
|
||||
// not returns the Boolean negation of its argument.
|
||||
func not(arg reflect.Value) bool {
|
||||
return !truth(arg)
|
||||
}
|
||||
|
||||
// Comparison.
|
||||
|
||||
// TODO: Perhaps allow comparison between signed and unsigned integers.
|
||||
|
||||
var (
|
||||
errBadComparisonType = errors.New("invalid type for comparison")
|
||||
errBadComparison = errors.New("incompatible types for comparison")
|
||||
errNoComparison = errors.New("missing argument for comparison")
|
||||
)
|
||||
|
||||
type kind int
|
||||
|
||||
const (
|
||||
invalidKind kind = iota
|
||||
boolKind
|
||||
complexKind
|
||||
intKind
|
||||
floatKind
|
||||
stringKind
|
||||
uintKind
|
||||
)
|
||||
|
||||
func basicKind(v reflect.Value) (kind, error) {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return boolKind, nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return intKind, nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return uintKind, nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return floatKind, nil
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return complexKind, nil
|
||||
case reflect.String:
|
||||
return stringKind, nil
|
||||
}
|
||||
return invalidKind, errBadComparisonType
|
||||
}
|
||||
|
||||
// eq evaluates the comparison a == b || a == c || ...
|
||||
func eq(arg1 reflect.Value, arg2 ...reflect.Value) (bool, error) {
|
||||
v1 := indirectInterface(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(arg2) == 0 {
|
||||
return false, errNoComparison
|
||||
}
|
||||
for _, arg := range arg2 {
|
||||
v2 := indirectInterface(arg)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
truth := false
|
||||
if k1 != k2 {
|
||||
// Special case: Can compare integer values regardless of type's sign.
|
||||
switch {
|
||||
case k1 == intKind && k2 == uintKind:
|
||||
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
|
||||
case k1 == uintKind && k2 == intKind:
|
||||
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
|
||||
default:
|
||||
return false, errBadComparison
|
||||
}
|
||||
} else {
|
||||
switch k1 {
|
||||
case boolKind:
|
||||
truth = v1.Bool() == v2.Bool()
|
||||
case complexKind:
|
||||
truth = v1.Complex() == v2.Complex()
|
||||
case floatKind:
|
||||
truth = v1.Float() == v2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() == v2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() == v2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() == v2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
}
|
||||
if truth {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ne evaluates the comparison a != b.
|
||||
func ne(arg1, arg2 reflect.Value) (bool, error) {
|
||||
// != is the inverse of ==.
|
||||
equal, err := eq(arg1, arg2)
|
||||
return !equal, err
|
||||
}
|
||||
|
||||
// lt evaluates the comparison a < b.
|
||||
func lt(arg1, arg2 reflect.Value) (bool, error) {
|
||||
v1 := indirectInterface(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
v2 := indirectInterface(arg2)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
truth := false
|
||||
if k1 != k2 {
|
||||
// Special case: Can compare integer values regardless of type's sign.
|
||||
switch {
|
||||
case k1 == intKind && k2 == uintKind:
|
||||
truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
|
||||
case k1 == uintKind && k2 == intKind:
|
||||
truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
|
||||
default:
|
||||
return false, errBadComparison
|
||||
}
|
||||
} else {
|
||||
switch k1 {
|
||||
case boolKind, complexKind:
|
||||
return false, errBadComparisonType
|
||||
case floatKind:
|
||||
truth = v1.Float() < v2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() < v2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() < v2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() < v2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
}
|
||||
return truth, nil
|
||||
}
|
||||
|
||||
// le evaluates the comparison <= b.
|
||||
func le(arg1, arg2 reflect.Value) (bool, error) {
|
||||
// <= is < or ==.
|
||||
lessThan, err := lt(arg1, arg2)
|
||||
if lessThan || err != nil {
|
||||
return lessThan, err
|
||||
}
|
||||
return eq(arg1, arg2)
|
||||
}
|
||||
|
||||
// gt evaluates the comparison a > b.
|
||||
func gt(arg1, arg2 reflect.Value) (bool, error) {
|
||||
// > is the inverse of <=.
|
||||
lessOrEqual, err := le(arg1, arg2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !lessOrEqual, nil
|
||||
}
|
||||
|
||||
// ge evaluates the comparison a >= b.
|
||||
func ge(arg1, arg2 reflect.Value) (bool, error) {
|
||||
// >= is the inverse of <.
|
||||
lessThan, err := lt(arg1, arg2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !lessThan, nil
|
||||
}
|
||||
|
||||
// HTML escaping.
|
||||
|
||||
var (
|
||||
htmlQuot = []byte(""") // shorter than """
|
||||
htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
|
||||
htmlAmp = []byte("&")
|
||||
htmlLt = []byte("<")
|
||||
htmlGt = []byte(">")
|
||||
htmlNull = []byte("\uFFFD")
|
||||
)
|
||||
|
||||
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
||||
func HTMLEscape(w io.Writer, b []byte) {
|
||||
last := 0
|
||||
for i, c := range b {
|
||||
var html []byte
|
||||
switch c {
|
||||
case '\000':
|
||||
html = htmlNull
|
||||
case '"':
|
||||
html = htmlQuot
|
||||
case '\'':
|
||||
html = htmlApos
|
||||
case '&':
|
||||
html = htmlAmp
|
||||
case '<':
|
||||
html = htmlLt
|
||||
case '>':
|
||||
html = htmlGt
|
||||
default:
|
||||
continue
|
||||
}
|
||||
w.Write(b[last:i])
|
||||
w.Write(html)
|
||||
last = i + 1
|
||||
}
|
||||
w.Write(b[last:])
|
||||
}
|
||||
|
||||
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
|
||||
func HTMLEscapeString(s string) string {
|
||||
// Avoid allocation if we can.
|
||||
if !strings.ContainsAny(s, "'\"&<>\000") {
|
||||
return s
|
||||
}
|
||||
var b bytes.Buffer
|
||||
HTMLEscape(&b, []byte(s))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
||||
// representation of its arguments.
|
||||
func HTMLEscaper(args ...interface{}) string {
|
||||
return HTMLEscapeString(evalArgs(args))
|
||||
}
|
||||
|
||||
// JavaScript escaping.
|
||||
|
||||
var (
|
||||
jsLowUni = []byte(`\u00`)
|
||||
hex = []byte("0123456789ABCDEF")
|
||||
|
||||
jsBackslash = []byte(`\\`)
|
||||
jsApos = []byte(`\'`)
|
||||
jsQuot = []byte(`\"`)
|
||||
jsLt = []byte(`\x3C`)
|
||||
jsGt = []byte(`\x3E`)
|
||||
)
|
||||
|
||||
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
||||
func JSEscape(w io.Writer, b []byte) {
|
||||
last := 0
|
||||
for i := 0; i < len(b); i++ {
|
||||
c := b[i]
|
||||
|
||||
if !jsIsSpecial(rune(c)) {
|
||||
// fast path: nothing to do
|
||||
continue
|
||||
}
|
||||
w.Write(b[last:i])
|
||||
|
||||
if c < utf8.RuneSelf {
|
||||
// Quotes, slashes and angle brackets get quoted.
|
||||
// Control characters get written as \u00XX.
|
||||
switch c {
|
||||
case '\\':
|
||||
w.Write(jsBackslash)
|
||||
case '\'':
|
||||
w.Write(jsApos)
|
||||
case '"':
|
||||
w.Write(jsQuot)
|
||||
case '<':
|
||||
w.Write(jsLt)
|
||||
case '>':
|
||||
w.Write(jsGt)
|
||||
default:
|
||||
w.Write(jsLowUni)
|
||||
t, b := c>>4, c&0x0f
|
||||
w.Write(hex[t : t+1])
|
||||
w.Write(hex[b : b+1])
|
||||
}
|
||||
} else {
|
||||
// Unicode rune.
|
||||
r, size := utf8.DecodeRune(b[i:])
|
||||
if unicode.IsPrint(r) {
|
||||
w.Write(b[i : i+size])
|
||||
} else {
|
||||
fmt.Fprintf(w, "\\u%04X", r)
|
||||
}
|
||||
i += size - 1
|
||||
}
|
||||
last = i + 1
|
||||
}
|
||||
w.Write(b[last:])
|
||||
}
|
||||
|
||||
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
|
||||
func JSEscapeString(s string) string {
|
||||
// Avoid allocation if we can.
|
||||
if strings.IndexFunc(s, jsIsSpecial) < 0 {
|
||||
return s
|
||||
}
|
||||
var b bytes.Buffer
|
||||
JSEscape(&b, []byte(s))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func jsIsSpecial(r rune) bool {
|
||||
switch r {
|
||||
case '\\', '\'', '"', '<', '>':
|
||||
return true
|
||||
}
|
||||
return r < ' ' || utf8.RuneSelf <= r
|
||||
}
|
||||
|
||||
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
||||
// representation of its arguments.
|
||||
func JSEscaper(args ...interface{}) string {
|
||||
return JSEscapeString(evalArgs(args))
|
||||
}
|
||||
|
||||
// URLQueryEscaper returns the escaped value of the textual representation of
|
||||
// its arguments in a form suitable for embedding in a URL query.
|
||||
func URLQueryEscaper(args ...interface{}) string {
|
||||
return url.QueryEscape(evalArgs(args))
|
||||
}
|
||||
|
||||
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
|
||||
// fmt.Sprint(args...)
|
||||
// except that each argument is indirected (if a pointer), as required,
|
||||
// using the same rules as the default string evaluation during template
|
||||
// execution.
|
||||
func evalArgs(args []interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
// Fast path for simple common case.
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
for i, arg := range args {
|
||||
a, ok := printableValue(reflect.ValueOf(arg))
|
||||
if ok {
|
||||
args[i] = a
|
||||
} // else let fmt do its thing
|
||||
}
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return s
|
||||
}
|
||||
@ -1,129 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Helper functions to make constructing templates easier.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Functions and methods to parse templates.
|
||||
|
||||
// Must is a helper that wraps a call to a function returning (*Template, error)
|
||||
// and panics if the error is non-nil. It is intended for use in variable
|
||||
// initializations such as
|
||||
// var t = template.Must(template.New("name").Parse("text"))
|
||||
func Must(t *Template, err error) *Template {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ParseFiles creates a new Template and parses the template definitions from
|
||||
// the named files. The returned template's name will have the base name and
|
||||
// parsed contents of the first file. There must be at least one file.
|
||||
// If an error occurs, parsing stops and the returned *Template is nil.
|
||||
//
|
||||
// When parsing multiple files with the same name in different directories,
|
||||
// the last one mentioned will be the one that results.
|
||||
// For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template
|
||||
// named "foo", while "a/foo" is unavailable.
|
||||
func ParseFiles(filenames ...string) (*Template, error) {
|
||||
return parseFiles(nil, filenames...)
|
||||
}
|
||||
|
||||
// ParseFiles parses the named files and associates the resulting templates with
|
||||
// t. If an error occurs, parsing stops and the returned template is nil;
|
||||
// otherwise it is t. There must be at least one file.
|
||||
// Since the templates created by ParseFiles are named by the base
|
||||
// names of the argument files, t should usually have the name of one
|
||||
// of the (base) names of the files. If it does not, depending on t's
|
||||
// contents before calling ParseFiles, t.Execute may fail. In that
|
||||
// case use t.ExecuteTemplate to execute a valid template.
|
||||
//
|
||||
// When parsing multiple files with the same name in different directories,
|
||||
// the last one mentioned will be the one that results.
|
||||
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
|
||||
t.init()
|
||||
return parseFiles(t, filenames...)
|
||||
}
|
||||
|
||||
// parseFiles is the helper for the method and function. If the argument
|
||||
// template is nil, it is created from the first file.
|
||||
func parseFiles(t *Template, filenames ...string) (*Template, error) {
|
||||
if len(filenames) == 0 {
|
||||
// Not really a problem, but be consistent.
|
||||
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
|
||||
}
|
||||
for _, filename := range filenames {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := string(b)
|
||||
name := filepath.Base(filename)
|
||||
// First template becomes return value if not already defined,
|
||||
// and we use that one for subsequent New calls to associate
|
||||
// all the templates together. Also, if this file has the same name
|
||||
// as t, this file becomes the contents of t, so
|
||||
// t, err := New(name).Funcs(xxx).ParseFiles(name)
|
||||
// works. Otherwise we create a new template associated with t.
|
||||
var tmpl *Template
|
||||
if t == nil {
|
||||
t = New(name)
|
||||
}
|
||||
if name == t.Name() {
|
||||
tmpl = t
|
||||
} else {
|
||||
tmpl = t.New(name)
|
||||
}
|
||||
_, err = tmpl.Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ParseGlob creates a new Template and parses the template definitions from the
|
||||
// files identified by the pattern, which must match at least one file. The
|
||||
// returned template will have the (base) name and (parsed) contents of the
|
||||
// first file matched by the pattern. ParseGlob is equivalent to calling
|
||||
// ParseFiles with the list of files matched by the pattern.
|
||||
//
|
||||
// When parsing multiple files with the same name in different directories,
|
||||
// the last one mentioned will be the one that results.
|
||||
func ParseGlob(pattern string) (*Template, error) {
|
||||
return parseGlob(nil, pattern)
|
||||
}
|
||||
|
||||
// ParseGlob parses the template definitions in the files identified by the
|
||||
// pattern and associates the resulting templates with t. The pattern is
|
||||
// processed by filepath.Glob and must match at least one file. ParseGlob is
|
||||
// equivalent to calling t.ParseFiles with the list of files matched by the
|
||||
// pattern.
|
||||
//
|
||||
// When parsing multiple files with the same name in different directories,
|
||||
// the last one mentioned will be the one that results.
|
||||
func (t *Template) ParseGlob(pattern string) (*Template, error) {
|
||||
t.init()
|
||||
return parseGlob(t, pattern)
|
||||
}
|
||||
|
||||
// parseGlob is the implementation of the function and method ParseGlob.
|
||||
func parseGlob(t *Template, pattern string) (*Template, error) {
|
||||
filenames, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(filenames) == 0 {
|
||||
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
|
||||
}
|
||||
return parseFiles(t, filenames...)
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains the code to handle template options.
|
||||
|
||||
package template
|
||||
|
||||
import "strings"
|
||||
|
||||
// missingKeyAction defines how to respond to indexing a map with a key that is not present.
|
||||
type missingKeyAction int
|
||||
|
||||
const (
|
||||
mapInvalid missingKeyAction = iota // Return an invalid reflect.Value.
|
||||
mapZeroValue // Return the zero value for the map element.
|
||||
mapError // Error out
|
||||
)
|
||||
|
||||
type option struct {
|
||||
missingKey missingKeyAction
|
||||
}
|
||||
|
||||
// Option sets options for the template. Options are described by
|
||||
// strings, either a simple string or "key=value". There can be at
|
||||
// most one equals sign in an option string. If the option string
|
||||
// is unrecognized or otherwise invalid, Option panics.
|
||||
//
|
||||
// Known options:
|
||||
//
|
||||
// missingkey: Control the behavior during execution if a map is
|
||||
// indexed with a key that is not present in the map.
|
||||
// "missingkey=default" or "missingkey=invalid"
|
||||
// The default behavior: Do nothing and continue execution.
|
||||
// If printed, the result of the index operation is the string
|
||||
// "<no value>".
|
||||
// "missingkey=zero"
|
||||
// The operation returns the zero value for the map type's element.
|
||||
// "missingkey=error"
|
||||
// Execution stops immediately with an error.
|
||||
//
|
||||
func (t *Template) Option(opt ...string) *Template {
|
||||
t.init()
|
||||
for _, s := range opt {
|
||||
t.setOption(s)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *Template) setOption(opt string) {
|
||||
if opt == "" {
|
||||
panic("empty option string")
|
||||
}
|
||||
elems := strings.Split(opt, "=")
|
||||
switch len(elems) {
|
||||
case 2:
|
||||
// key=value
|
||||
switch elems[0] {
|
||||
case "missingkey":
|
||||
switch elems[1] {
|
||||
case "invalid", "default":
|
||||
t.option.missingKey = mapInvalid
|
||||
return
|
||||
case "zero":
|
||||
t.option.missingKey = mapZeroValue
|
||||
return
|
||||
case "error":
|
||||
t.option.missingKey = mapError
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
panic("unrecognized option: " + opt)
|
||||
}
|
||||
@ -1,642 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// item represents a token or text string returned from the scanner.
|
||||
type item struct {
|
||||
typ itemType // The type of this item.
|
||||
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||
val string // The value of this item.
|
||||
line int // The line number at the start of this item.
|
||||
}
|
||||
|
||||
func (i item) String() string {
|
||||
switch {
|
||||
case i.typ == itemEOF:
|
||||
return "EOF"
|
||||
case i.typ == itemError:
|
||||
return i.val
|
||||
case i.typ > itemKeyword:
|
||||
return fmt.Sprintf("<%s>", i.val)
|
||||
case len(i.val) > 10:
|
||||
return fmt.Sprintf("%.10q...", i.val)
|
||||
}
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
}
|
||||
|
||||
// itemType identifies the type of lex items.
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota // error occurred; value is text of error
|
||||
itemBool // boolean constant
|
||||
itemChar // printable ASCII character; grab bag for comma etc.
|
||||
itemCharConstant // character constant
|
||||
itemComplex // complex constant (1+2i); imaginary is just a number
|
||||
itemAssign // equals ('=') introducing an assignment
|
||||
itemDeclare // colon-equals (':=') introducing a declaration
|
||||
itemEOF
|
||||
itemField // alphanumeric identifier starting with '.'
|
||||
itemIdentifier // alphanumeric identifier not starting with '.'
|
||||
itemLeftDelim // left action delimiter
|
||||
itemLeftParen // '(' inside action
|
||||
itemNumber // simple number, including imaginary
|
||||
itemPipe // pipe symbol
|
||||
itemRawString // raw quoted string (includes quotes)
|
||||
itemRightDelim // right action delimiter
|
||||
itemRightParen // ')' inside action
|
||||
itemSpace // run of spaces separating arguments
|
||||
itemString // quoted string (includes quotes)
|
||||
itemText // plain text
|
||||
itemVariable // variable starting with '$', such as '$' or '$1' or '$hello'
|
||||
// Keywords appear after all the rest.
|
||||
itemKeyword // used only to delimit the keywords
|
||||
itemBlock // block keyword
|
||||
itemDot // the cursor, spelled '.'
|
||||
itemDefine // define keyword
|
||||
itemElse // else keyword
|
||||
itemEnd // end keyword
|
||||
itemIf // if keyword
|
||||
itemNil // the untyped nil constant, easiest to treat as a keyword
|
||||
itemRange // range keyword
|
||||
itemTemplate // template keyword
|
||||
itemWith // with keyword
|
||||
)
|
||||
|
||||
var key = map[string]itemType{
|
||||
".": itemDot,
|
||||
"block": itemBlock,
|
||||
"define": itemDefine,
|
||||
"else": itemElse,
|
||||
"end": itemEnd,
|
||||
"if": itemIf,
|
||||
"range": itemRange,
|
||||
"nil": itemNil,
|
||||
"template": itemTemplate,
|
||||
"with": itemWith,
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
||||
// Trimming spaces.
|
||||
// If the action begins "{{- " rather than "{{", then all space/tab/newlines
|
||||
// preceding the action are trimmed; conversely if it ends " -}}" the
|
||||
// leading spaces are trimmed. This is done entirely in the lexer; the
|
||||
// parser never sees it happen. We require an ASCII space to be
|
||||
// present to avoid ambiguity with things like "{{-3}}". It reads
|
||||
// better with the space present anyway. For simplicity, only ASCII
|
||||
// space does the job.
|
||||
const (
|
||||
spaceChars = " \t\r\n" // These are the space characters defined by Go itself.
|
||||
leftTrimMarker = "- " // Attached to left delimiter, trims trailing spaces from preceding text.
|
||||
rightTrimMarker = " -" // Attached to right delimiter, trims leading spaces from following text.
|
||||
trimMarkerLen = Pos(len(leftTrimMarker))
|
||||
)
|
||||
|
||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
// lexer holds the state of the scanner.
|
||||
type lexer struct {
|
||||
name string // the name of the input; used only for error reports
|
||||
input string // the string being scanned
|
||||
leftDelim string // start of action
|
||||
rightDelim string // end of action
|
||||
pos Pos // current position in the input
|
||||
start Pos // start position of this item
|
||||
width Pos // width of last rune read from input
|
||||
items chan item // channel of scanned items
|
||||
parenDepth int // nesting depth of ( ) exprs
|
||||
line int // 1+number of newlines seen
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = Pos(w)
|
||||
l.pos += l.width
|
||||
if r == '\n' {
|
||||
l.line++
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
// Correct newline count.
|
||||
if l.width == 1 && l.input[l.pos] == '\n' {
|
||||
l.line--
|
||||
}
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t itemType) {
|
||||
l.items <- item{t, l.start, l.input[l.start:l.pos], l.line}
|
||||
// Some items contain text internally. If so, count their newlines.
|
||||
switch t {
|
||||
case itemText, itemRawString, itemLeftDelim, itemRightDelim:
|
||||
l.line += strings.Count(l.input[l.start:l.pos], "\n")
|
||||
}
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (l *lexer) ignore() {
|
||||
l.line += strings.Count(l.input[l.start:l.pos], "\n")
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's from the valid set.
|
||||
func (l *lexer) accept(valid string) bool {
|
||||
if strings.ContainsRune(valid, l.next()) {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun consumes a run of runes from the valid set.
|
||||
func (l *lexer) acceptRun(valid string) {
|
||||
for strings.ContainsRune(valid, l.next()) {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...), l.line}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextItem returns the next item from the input.
|
||||
// Called by the parser, not in the lexing goroutine.
|
||||
func (l *lexer) nextItem() item {
|
||||
return <-l.items
|
||||
}
|
||||
|
||||
// drain drains the output so the lexing goroutine will exit.
|
||||
// Called by the parser, not in the lexing goroutine.
|
||||
func (l *lexer) drain() {
|
||||
for range l.items {
|
||||
}
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(name, input, left, right string) *lexer {
|
||||
if left == "" {
|
||||
left = leftDelim
|
||||
}
|
||||
if right == "" {
|
||||
right = rightDelim
|
||||
}
|
||||
l := &lexer{
|
||||
name: name,
|
||||
input: input,
|
||||
leftDelim: left,
|
||||
rightDelim: right,
|
||||
items: make(chan item),
|
||||
line: 1,
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for state := lexText; state != nil; {
|
||||
state = state(l)
|
||||
}
|
||||
close(l.items)
|
||||
}
|
||||
|
||||
// state functions
|
||||
|
||||
const (
|
||||
leftDelim = "{{"
|
||||
rightDelim = "}}"
|
||||
leftComment = "/*"
|
||||
rightComment = "*/"
|
||||
)
|
||||
|
||||
// lexText scans until an opening action delimiter, "{{".
|
||||
func lexText(l *lexer) stateFn {
|
||||
l.width = 0
|
||||
if x := strings.Index(l.input[l.pos:], l.leftDelim); x >= 0 {
|
||||
ldn := Pos(len(l.leftDelim))
|
||||
l.pos += Pos(x)
|
||||
trimLength := Pos(0)
|
||||
if strings.HasPrefix(l.input[l.pos+ldn:], leftTrimMarker) {
|
||||
trimLength = rightTrimLength(l.input[l.start:l.pos])
|
||||
}
|
||||
l.pos -= trimLength
|
||||
if l.pos > l.start {
|
||||
l.emit(itemText)
|
||||
}
|
||||
l.pos += trimLength
|
||||
l.ignore()
|
||||
return lexLeftDelim
|
||||
} else {
|
||||
l.pos = Pos(len(l.input))
|
||||
}
|
||||
// Correctly reached EOF.
|
||||
if l.pos > l.start {
|
||||
l.emit(itemText)
|
||||
}
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
// rightTrimLength returns the length of the spaces at the end of the string.
|
||||
func rightTrimLength(s string) Pos {
|
||||
return Pos(len(s) - len(strings.TrimRight(s, spaceChars)))
|
||||
}
|
||||
|
||||
// atRightDelim reports whether the lexer is at a right delimiter, possibly preceded by a trim marker.
|
||||
func (l *lexer) atRightDelim() (delim, trimSpaces bool) {
|
||||
if strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||
return true, false
|
||||
}
|
||||
// The right delim might have the marker before.
|
||||
if strings.HasPrefix(l.input[l.pos:], rightTrimMarker) &&
|
||||
strings.HasPrefix(l.input[l.pos+trimMarkerLen:], l.rightDelim) {
|
||||
return true, true
|
||||
}
|
||||
return false, false
|
||||
}
|
||||
|
||||
// leftTrimLength returns the length of the spaces at the beginning of the string.
|
||||
func leftTrimLength(s string) Pos {
|
||||
return Pos(len(s) - len(strings.TrimLeft(s, spaceChars)))
|
||||
}
|
||||
|
||||
// lexLeftDelim scans the left delimiter, which is known to be present, possibly with a trim marker.
|
||||
func lexLeftDelim(l *lexer) stateFn {
|
||||
l.pos += Pos(len(l.leftDelim))
|
||||
trimSpace := strings.HasPrefix(l.input[l.pos:], leftTrimMarker)
|
||||
afterMarker := Pos(0)
|
||||
if trimSpace {
|
||||
afterMarker = trimMarkerLen
|
||||
}
|
||||
if strings.HasPrefix(l.input[l.pos+afterMarker:], leftComment) {
|
||||
l.pos += afterMarker
|
||||
l.ignore()
|
||||
return lexComment
|
||||
}
|
||||
l.emit(itemLeftDelim)
|
||||
l.pos += afterMarker
|
||||
l.ignore()
|
||||
l.parenDepth = 0
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexComment scans a comment. The left comment marker is known to be present.
|
||||
func lexComment(l *lexer) stateFn {
|
||||
l.pos += Pos(len(leftComment))
|
||||
i := strings.Index(l.input[l.pos:], rightComment)
|
||||
if i < 0 {
|
||||
return l.errorf("unclosed comment")
|
||||
}
|
||||
l.pos += Pos(i + len(rightComment))
|
||||
delim, trimSpace := l.atRightDelim()
|
||||
if !delim {
|
||||
return l.errorf("comment ends before closing delimiter")
|
||||
}
|
||||
if trimSpace {
|
||||
l.pos += trimMarkerLen
|
||||
}
|
||||
l.pos += Pos(len(l.rightDelim))
|
||||
if trimSpace {
|
||||
l.pos += leftTrimLength(l.input[l.pos:])
|
||||
}
|
||||
l.ignore()
|
||||
return lexText
|
||||
}
|
||||
|
||||
// lexRightDelim scans the right delimiter, which is known to be present, possibly with a trim marker.
|
||||
func lexRightDelim(l *lexer) stateFn {
|
||||
trimSpace := strings.HasPrefix(l.input[l.pos:], rightTrimMarker)
|
||||
if trimSpace {
|
||||
l.pos += trimMarkerLen
|
||||
l.ignore()
|
||||
}
|
||||
l.pos += Pos(len(l.rightDelim))
|
||||
l.emit(itemRightDelim)
|
||||
if trimSpace {
|
||||
l.pos += leftTrimLength(l.input[l.pos:])
|
||||
l.ignore()
|
||||
}
|
||||
return lexText
|
||||
}
|
||||
|
||||
// lexInsideAction scans the elements inside action delimiters.
|
||||
func lexInsideAction(l *lexer) stateFn {
|
||||
// Either number, quoted string, or identifier.
|
||||
// Spaces separate arguments; runs of spaces turn into itemSpace.
|
||||
// Pipe symbols separate and are emitted.
|
||||
delim, _ := l.atRightDelim()
|
||||
if delim {
|
||||
if l.parenDepth == 0 {
|
||||
return lexRightDelim
|
||||
}
|
||||
return l.errorf("unclosed left paren")
|
||||
}
|
||||
switch r := l.next(); {
|
||||
case r == eof || isEndOfLine(r):
|
||||
return l.errorf("unclosed action")
|
||||
case isSpace(r):
|
||||
return lexSpace
|
||||
case r == '=':
|
||||
l.emit(itemAssign)
|
||||
case r == ':':
|
||||
if l.next() != '=' {
|
||||
return l.errorf("expected :=")
|
||||
}
|
||||
l.emit(itemDeclare)
|
||||
case r == '|':
|
||||
l.emit(itemPipe)
|
||||
case r == '"':
|
||||
return lexQuote
|
||||
case r == '`':
|
||||
return lexRawQuote
|
||||
case r == '$':
|
||||
return lexVariable
|
||||
case r == '\'':
|
||||
return lexChar
|
||||
case r == '.':
|
||||
// special look-ahead for ".field" so we don't break l.backup().
|
||||
if l.pos < Pos(len(l.input)) {
|
||||
r := l.input[l.pos]
|
||||
if r < '0' || '9' < r {
|
||||
return lexField
|
||||
}
|
||||
}
|
||||
fallthrough // '.' can start a number.
|
||||
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
|
||||
l.backup()
|
||||
return lexNumber
|
||||
case isAlphaNumeric(r):
|
||||
l.backup()
|
||||
return lexIdentifier
|
||||
case r == '(':
|
||||
l.emit(itemLeftParen)
|
||||
l.parenDepth++
|
||||
case r == ')':
|
||||
l.emit(itemRightParen)
|
||||
l.parenDepth--
|
||||
if l.parenDepth < 0 {
|
||||
return l.errorf("unexpected right paren %#U", r)
|
||||
}
|
||||
case r <= unicode.MaxASCII && unicode.IsPrint(r):
|
||||
l.emit(itemChar)
|
||||
return lexInsideAction
|
||||
default:
|
||||
return l.errorf("unrecognized character in action: %#U", r)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexSpace scans a run of space characters.
|
||||
// One space has already been seen.
|
||||
func lexSpace(l *lexer) stateFn {
|
||||
for isSpace(l.peek()) {
|
||||
l.next()
|
||||
}
|
||||
l.emit(itemSpace)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexIdentifier scans an alphanumeric.
|
||||
func lexIdentifier(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case isAlphaNumeric(r):
|
||||
// absorb.
|
||||
default:
|
||||
l.backup()
|
||||
word := l.input[l.start:l.pos]
|
||||
if !l.atTerminator() {
|
||||
return l.errorf("bad character %#U", r)
|
||||
}
|
||||
switch {
|
||||
case key[word] > itemKeyword:
|
||||
l.emit(key[word])
|
||||
case word[0] == '.':
|
||||
l.emit(itemField)
|
||||
case word == "true", word == "false":
|
||||
l.emit(itemBool)
|
||||
default:
|
||||
l.emit(itemIdentifier)
|
||||
}
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexField scans a field: .Alphanumeric.
|
||||
// The . has been scanned.
|
||||
func lexField(l *lexer) stateFn {
|
||||
return lexFieldOrVariable(l, itemField)
|
||||
}
|
||||
|
||||
// lexVariable scans a Variable: $Alphanumeric.
|
||||
// The $ has been scanned.
|
||||
func lexVariable(l *lexer) stateFn {
|
||||
if l.atTerminator() { // Nothing interesting follows -> "$".
|
||||
l.emit(itemVariable)
|
||||
return lexInsideAction
|
||||
}
|
||||
return lexFieldOrVariable(l, itemVariable)
|
||||
}
|
||||
|
||||
// lexVariable scans a field or variable: [.$]Alphanumeric.
|
||||
// The . or $ has been scanned.
|
||||
func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
|
||||
if l.atTerminator() { // Nothing interesting follows -> "." or "$".
|
||||
if typ == itemVariable {
|
||||
l.emit(itemVariable)
|
||||
} else {
|
||||
l.emit(itemDot)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
var r rune
|
||||
for {
|
||||
r = l.next()
|
||||
if !isAlphaNumeric(r) {
|
||||
l.backup()
|
||||
break
|
||||
}
|
||||
}
|
||||
if !l.atTerminator() {
|
||||
return l.errorf("bad character %#U", r)
|
||||
}
|
||||
l.emit(typ)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// atTerminator reports whether the input is at valid termination character to
|
||||
// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases
|
||||
// like "$x+2" not being acceptable without a space, in case we decide one
|
||||
// day to implement arithmetic.
|
||||
func (l *lexer) atTerminator() bool {
|
||||
r := l.peek()
|
||||
if isSpace(r) || isEndOfLine(r) {
|
||||
return true
|
||||
}
|
||||
switch r {
|
||||
case eof, '.', ',', '|', ':', ')', '(':
|
||||
return true
|
||||
}
|
||||
// Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
|
||||
// succeed but should fail) but only in extremely rare cases caused by willfully
|
||||
// bad choice of delimiter.
|
||||
if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// lexChar scans a character constant. The initial quote is already
|
||||
// scanned. Syntax checking is done by the parser.
|
||||
func lexChar(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
if r := l.next(); r != eof && r != '\n' {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated character constant")
|
||||
case '\'':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemCharConstant)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
||||
// and "089" - but when it's wrong the input is invalid and the parser (via
|
||||
// strconv) will notice.
|
||||
func lexNumber(l *lexer) stateFn {
|
||||
if !l.scanNumber() {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
if sign := l.peek(); sign == '+' || sign == '-' {
|
||||
// Complex: 1+2i. No spaces, must end in 'i'.
|
||||
if !l.scanNumber() || l.input[l.pos-1] != 'i' {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.emit(itemComplex)
|
||||
} else {
|
||||
l.emit(itemNumber)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
func (l *lexer) scanNumber() bool {
|
||||
// Optional leading sign.
|
||||
l.accept("+-")
|
||||
// Is it hex?
|
||||
digits := "0123456789"
|
||||
if l.accept("0") && l.accept("xX") {
|
||||
digits = "0123456789abcdefABCDEF"
|
||||
}
|
||||
l.acceptRun(digits)
|
||||
if l.accept(".") {
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
if l.accept("eE") {
|
||||
l.accept("+-")
|
||||
l.acceptRun("0123456789")
|
||||
}
|
||||
// Is it imaginary?
|
||||
l.accept("i")
|
||||
// Next thing mustn't be alphanumeric.
|
||||
if isAlphaNumeric(l.peek()) {
|
||||
l.next()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// lexQuote scans a quoted string.
|
||||
func lexQuote(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
if r := l.next(); r != eof && r != '\n' {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated quoted string")
|
||||
case '"':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemString)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexRawQuote scans a raw quoted string.
|
||||
func lexRawQuote(l *lexer) stateFn {
|
||||
startLine := l.line
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case eof:
|
||||
// Restore line number to location of opening quote.
|
||||
// We will error out so it's ok just to overwrite the field.
|
||||
l.line = startLine
|
||||
return l.errorf("unterminated raw quoted string")
|
||||
case '`':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemRawString)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// isSpace reports whether r is a space character.
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
// isEndOfLine reports whether r is an end-of-line character.
|
||||
func isEndOfLine(r rune) bool {
|
||||
return r == '\r' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
||||
@ -1,841 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Parse nodes.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var textFormat = "%s" // Changed to "%q" in tests for better error messages.
|
||||
|
||||
// A Node is an element in the parse tree. The interface is trivial.
|
||||
// The interface contains an unexported method so that only
|
||||
// types local to this package can satisfy it.
|
||||
type Node interface {
|
||||
Type() NodeType
|
||||
String() string
|
||||
// Copy does a deep copy of the Node and all its components.
|
||||
// To avoid type assertions, some XxxNodes also have specialized
|
||||
// CopyXxx methods that return *XxxNode.
|
||||
Copy() Node
|
||||
Position() Pos // byte position of start of node in full original input string
|
||||
// tree returns the containing *Tree.
|
||||
// It is unexported so all implementations of Node are in this package.
|
||||
tree() *Tree
|
||||
}
|
||||
|
||||
// NodeType identifies the type of a parse tree node.
|
||||
type NodeType int
|
||||
|
||||
// Pos represents a byte position in the original input text from which
|
||||
// this template was parsed.
|
||||
type Pos int
|
||||
|
||||
func (p Pos) Position() Pos {
|
||||
return p
|
||||
}
|
||||
|
||||
// Type returns itself and provides an easy default implementation
|
||||
// for embedding in a Node. Embedded in all non-trivial Nodes.
|
||||
func (t NodeType) Type() NodeType {
|
||||
return t
|
||||
}
|
||||
|
||||
const (
|
||||
NodeText NodeType = iota // Plain text.
|
||||
NodeAction // A non-control action such as a field evaluation.
|
||||
NodeBool // A boolean constant.
|
||||
NodeChain // A sequence of field accesses.
|
||||
NodeCommand // An element of a pipeline.
|
||||
NodeDot // The cursor, dot.
|
||||
nodeElse // An else action. Not added to tree.
|
||||
nodeEnd // An end action. Not added to tree.
|
||||
NodeField // A field or method name.
|
||||
NodeIdentifier // An identifier; always a function name.
|
||||
NodeIf // An if action.
|
||||
NodeList // A list of Nodes.
|
||||
NodeNil // An untyped nil constant.
|
||||
NodeNumber // A numerical constant.
|
||||
NodePipe // A pipeline of commands.
|
||||
NodeRange // A range action.
|
||||
NodeString // A string constant.
|
||||
NodeTemplate // A template invocation action.
|
||||
NodeVariable // A $ variable.
|
||||
NodeWith // A with action.
|
||||
)
|
||||
|
||||
// Nodes.
|
||||
|
||||
// ListNode holds a sequence of nodes.
|
||||
type ListNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Nodes []Node // The element nodes in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newList(pos Pos) *ListNode {
|
||||
return &ListNode{tr: t, NodeType: NodeList, Pos: pos}
|
||||
}
|
||||
|
||||
func (l *ListNode) append(n Node) {
|
||||
l.Nodes = append(l.Nodes, n)
|
||||
}
|
||||
|
||||
func (l *ListNode) tree() *Tree {
|
||||
return l.tr
|
||||
}
|
||||
|
||||
func (l *ListNode) String() string {
|
||||
b := new(bytes.Buffer)
|
||||
for _, n := range l.Nodes {
|
||||
fmt.Fprint(b, n)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (l *ListNode) CopyList() *ListNode {
|
||||
if l == nil {
|
||||
return l
|
||||
}
|
||||
n := l.tr.newList(l.Pos)
|
||||
for _, elem := range l.Nodes {
|
||||
n.append(elem.Copy())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (l *ListNode) Copy() Node {
|
||||
return l.CopyList()
|
||||
}
|
||||
|
||||
// TextNode holds plain text.
|
||||
type TextNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Text []byte // The text; may span newlines.
|
||||
}
|
||||
|
||||
func (t *Tree) newText(pos Pos, text string) *TextNode {
|
||||
return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)}
|
||||
}
|
||||
|
||||
func (t *TextNode) String() string {
|
||||
return fmt.Sprintf(textFormat, t.Text)
|
||||
}
|
||||
|
||||
func (t *TextNode) tree() *Tree {
|
||||
return t.tr
|
||||
}
|
||||
|
||||
func (t *TextNode) Copy() Node {
|
||||
return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)}
|
||||
}
|
||||
|
||||
// PipeNode holds a pipeline with optional declaration
|
||||
type PipeNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input. Deprecated: Kept for compatibility.
|
||||
IsAssign bool // The variables are being assigned, not declared.
|
||||
Decl []*VariableNode // Variables in lexical order.
|
||||
Cmds []*CommandNode // The commands in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newPipeline(pos Pos, line int, vars []*VariableNode) *PipeNode {
|
||||
return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: vars}
|
||||
}
|
||||
|
||||
func (p *PipeNode) append(command *CommandNode) {
|
||||
p.Cmds = append(p.Cmds, command)
|
||||
}
|
||||
|
||||
func (p *PipeNode) String() string {
|
||||
s := ""
|
||||
if len(p.Decl) > 0 {
|
||||
for i, v := range p.Decl {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += v.String()
|
||||
}
|
||||
s += " := "
|
||||
}
|
||||
for i, c := range p.Cmds {
|
||||
if i > 0 {
|
||||
s += " | "
|
||||
}
|
||||
s += c.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *PipeNode) tree() *Tree {
|
||||
return p.tr
|
||||
}
|
||||
|
||||
func (p *PipeNode) CopyPipe() *PipeNode {
|
||||
if p == nil {
|
||||
return p
|
||||
}
|
||||
var vars []*VariableNode
|
||||
for _, d := range p.Decl {
|
||||
vars = append(vars, d.Copy().(*VariableNode))
|
||||
}
|
||||
n := p.tr.newPipeline(p.Pos, p.Line, vars)
|
||||
n.IsAssign = p.IsAssign
|
||||
for _, c := range p.Cmds {
|
||||
n.append(c.Copy().(*CommandNode))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *PipeNode) Copy() Node {
|
||||
return p.CopyPipe()
|
||||
}
|
||||
|
||||
// ActionNode holds an action (something bounded by delimiters).
|
||||
// Control actions have their own nodes; ActionNode represents simple
|
||||
// ones such as field evaluations and parenthesized pipelines.
|
||||
type ActionNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input. Deprecated: Kept for compatibility.
|
||||
Pipe *PipeNode // The pipeline in the action.
|
||||
}
|
||||
|
||||
func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
|
||||
return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe}
|
||||
}
|
||||
|
||||
func (a *ActionNode) String() string {
|
||||
return fmt.Sprintf("{{%s}}", a.Pipe)
|
||||
|
||||
}
|
||||
|
||||
func (a *ActionNode) tree() *Tree {
|
||||
return a.tr
|
||||
}
|
||||
|
||||
func (a *ActionNode) Copy() Node {
|
||||
return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
|
||||
|
||||
}
|
||||
|
||||
// CommandNode holds a command (a pipeline inside an evaluating action).
|
||||
type CommandNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Args []Node // Arguments in lexical order: Identifier, field, or constant.
|
||||
}
|
||||
|
||||
func (t *Tree) newCommand(pos Pos) *CommandNode {
|
||||
return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos}
|
||||
}
|
||||
|
||||
func (c *CommandNode) append(arg Node) {
|
||||
c.Args = append(c.Args, arg)
|
||||
}
|
||||
|
||||
func (c *CommandNode) String() string {
|
||||
s := ""
|
||||
for i, arg := range c.Args {
|
||||
if i > 0 {
|
||||
s += " "
|
||||
}
|
||||
if arg, ok := arg.(*PipeNode); ok {
|
||||
s += "(" + arg.String() + ")"
|
||||
continue
|
||||
}
|
||||
s += arg.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *CommandNode) tree() *Tree {
|
||||
return c.tr
|
||||
}
|
||||
|
||||
func (c *CommandNode) Copy() Node {
|
||||
if c == nil {
|
||||
return c
|
||||
}
|
||||
n := c.tr.newCommand(c.Pos)
|
||||
for _, c := range c.Args {
|
||||
n.append(c.Copy())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// IdentifierNode holds an identifier.
|
||||
type IdentifierNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident string // The identifier's name.
|
||||
}
|
||||
|
||||
// NewIdentifier returns a new IdentifierNode with the given identifier name.
|
||||
func NewIdentifier(ident string) *IdentifierNode {
|
||||
return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
|
||||
}
|
||||
|
||||
// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature.
|
||||
// Chained for convenience.
|
||||
// TODO: fix one day?
|
||||
func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
|
||||
i.Pos = pos
|
||||
return i
|
||||
}
|
||||
|
||||
// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature.
|
||||
// Chained for convenience.
|
||||
// TODO: fix one day?
|
||||
func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode {
|
||||
i.tr = t
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) String() string {
|
||||
return i.Ident
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) tree() *Tree {
|
||||
return i.tr
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) Copy() Node {
|
||||
return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos)
|
||||
}
|
||||
|
||||
// AssignNode holds a list of variable names, possibly with chained field
|
||||
// accesses. The dollar sign is part of the (first) name.
|
||||
type VariableNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident []string // Variable name and fields in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
|
||||
return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
|
||||
}
|
||||
|
||||
func (v *VariableNode) String() string {
|
||||
s := ""
|
||||
for i, id := range v.Ident {
|
||||
if i > 0 {
|
||||
s += "."
|
||||
}
|
||||
s += id
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (v *VariableNode) tree() *Tree {
|
||||
return v.tr
|
||||
}
|
||||
|
||||
func (v *VariableNode) Copy() Node {
|
||||
return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
|
||||
}
|
||||
|
||||
// DotNode holds the special identifier '.'.
|
||||
type DotNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newDot(pos Pos) *DotNode {
|
||||
return &DotNode{tr: t, NodeType: NodeDot, Pos: pos}
|
||||
}
|
||||
|
||||
func (d *DotNode) Type() NodeType {
|
||||
// Override method on embedded NodeType for API compatibility.
|
||||
// TODO: Not really a problem; could change API without effect but
|
||||
// api tool complains.
|
||||
return NodeDot
|
||||
}
|
||||
|
||||
func (d *DotNode) String() string {
|
||||
return "."
|
||||
}
|
||||
|
||||
func (d *DotNode) tree() *Tree {
|
||||
return d.tr
|
||||
}
|
||||
|
||||
func (d *DotNode) Copy() Node {
|
||||
return d.tr.newDot(d.Pos)
|
||||
}
|
||||
|
||||
// NilNode holds the special identifier 'nil' representing an untyped nil constant.
|
||||
type NilNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newNil(pos Pos) *NilNode {
|
||||
return &NilNode{tr: t, NodeType: NodeNil, Pos: pos}
|
||||
}
|
||||
|
||||
func (n *NilNode) Type() NodeType {
|
||||
// Override method on embedded NodeType for API compatibility.
|
||||
// TODO: Not really a problem; could change API without effect but
|
||||
// api tool complains.
|
||||
return NodeNil
|
||||
}
|
||||
|
||||
func (n *NilNode) String() string {
|
||||
return "nil"
|
||||
}
|
||||
|
||||
func (n *NilNode) tree() *Tree {
|
||||
return n.tr
|
||||
}
|
||||
|
||||
func (n *NilNode) Copy() Node {
|
||||
return n.tr.newNil(n.Pos)
|
||||
}
|
||||
|
||||
// FieldNode holds a field (identifier starting with '.').
|
||||
// The names may be chained ('.x.y').
|
||||
// The period is dropped from each ident.
|
||||
type FieldNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident []string // The identifiers in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newField(pos Pos, ident string) *FieldNode {
|
||||
return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
|
||||
}
|
||||
|
||||
func (f *FieldNode) String() string {
|
||||
s := ""
|
||||
for _, id := range f.Ident {
|
||||
s += "." + id
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (f *FieldNode) tree() *Tree {
|
||||
return f.tr
|
||||
}
|
||||
|
||||
func (f *FieldNode) Copy() Node {
|
||||
return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)}
|
||||
}
|
||||
|
||||
// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
|
||||
// The names may be chained ('.x.y').
|
||||
// The periods are dropped from each ident.
|
||||
type ChainNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Node Node
|
||||
Field []string // The identifiers in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newChain(pos Pos, node Node) *ChainNode {
|
||||
return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node}
|
||||
}
|
||||
|
||||
// Add adds the named field (which should start with a period) to the end of the chain.
|
||||
func (c *ChainNode) Add(field string) {
|
||||
if len(field) == 0 || field[0] != '.' {
|
||||
panic("no dot in field")
|
||||
}
|
||||
field = field[1:] // Remove leading dot.
|
||||
if field == "" {
|
||||
panic("empty field")
|
||||
}
|
||||
c.Field = append(c.Field, field)
|
||||
}
|
||||
|
||||
func (c *ChainNode) String() string {
|
||||
s := c.Node.String()
|
||||
if _, ok := c.Node.(*PipeNode); ok {
|
||||
s = "(" + s + ")"
|
||||
}
|
||||
for _, field := range c.Field {
|
||||
s += "." + field
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *ChainNode) tree() *Tree {
|
||||
return c.tr
|
||||
}
|
||||
|
||||
func (c *ChainNode) Copy() Node {
|
||||
return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)}
|
||||
}
|
||||
|
||||
// BoolNode holds a boolean constant.
|
||||
type BoolNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
True bool // The value of the boolean constant.
|
||||
}
|
||||
|
||||
func (t *Tree) newBool(pos Pos, true bool) *BoolNode {
|
||||
return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true}
|
||||
}
|
||||
|
||||
func (b *BoolNode) String() string {
|
||||
if b.True {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (b *BoolNode) tree() *Tree {
|
||||
return b.tr
|
||||
}
|
||||
|
||||
func (b *BoolNode) Copy() Node {
|
||||
return b.tr.newBool(b.Pos, b.True)
|
||||
}
|
||||
|
||||
// NumberNode holds a number: signed or unsigned integer, float, or complex.
|
||||
// The value is parsed and stored under all the types that can represent the value.
|
||||
// This simulates in a small amount of code the behavior of Go's ideal constants.
|
||||
type NumberNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
IsInt bool // Number has an integral value.
|
||||
IsUint bool // Number has an unsigned integral value.
|
||||
IsFloat bool // Number has a floating-point value.
|
||||
IsComplex bool // Number is complex.
|
||||
Int64 int64 // The signed integer value.
|
||||
Uint64 uint64 // The unsigned integer value.
|
||||
Float64 float64 // The floating-point value.
|
||||
Complex128 complex128 // The complex value.
|
||||
Text string // The original textual representation from the input.
|
||||
}
|
||||
|
||||
func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
|
||||
n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text}
|
||||
switch typ {
|
||||
case itemCharConstant:
|
||||
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tail != "'" {
|
||||
return nil, fmt.Errorf("malformed character constant: %s", text)
|
||||
}
|
||||
n.Int64 = int64(rune)
|
||||
n.IsInt = true
|
||||
n.Uint64 = uint64(rune)
|
||||
n.IsUint = true
|
||||
n.Float64 = float64(rune) // odd but those are the rules.
|
||||
n.IsFloat = true
|
||||
return n, nil
|
||||
case itemComplex:
|
||||
// fmt.Sscan can parse the pair, so let it do the work.
|
||||
if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.IsComplex = true
|
||||
n.simplifyComplex()
|
||||
return n, nil
|
||||
}
|
||||
// Imaginary constants can only be complex unless they are zero.
|
||||
if len(text) > 0 && text[len(text)-1] == 'i' {
|
||||
f, err := strconv.ParseFloat(text[:len(text)-1], 64)
|
||||
if err == nil {
|
||||
n.IsComplex = true
|
||||
n.Complex128 = complex(0, f)
|
||||
n.simplifyComplex()
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
// Do integer test first so we get 0x123 etc.
|
||||
u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below.
|
||||
if err == nil {
|
||||
n.IsUint = true
|
||||
n.Uint64 = u
|
||||
}
|
||||
i, err := strconv.ParseInt(text, 0, 64)
|
||||
if err == nil {
|
||||
n.IsInt = true
|
||||
n.Int64 = i
|
||||
if i == 0 {
|
||||
n.IsUint = true // in case of -0.
|
||||
n.Uint64 = u
|
||||
}
|
||||
}
|
||||
// If an integer extraction succeeded, promote the float.
|
||||
if n.IsInt {
|
||||
n.IsFloat = true
|
||||
n.Float64 = float64(n.Int64)
|
||||
} else if n.IsUint {
|
||||
n.IsFloat = true
|
||||
n.Float64 = float64(n.Uint64)
|
||||
} else {
|
||||
f, err := strconv.ParseFloat(text, 64)
|
||||
if err == nil {
|
||||
// If we parsed it as a float but it looks like an integer,
|
||||
// it's a huge number too large to fit in an int. Reject it.
|
||||
if !strings.ContainsAny(text, ".eE") {
|
||||
return nil, fmt.Errorf("integer overflow: %q", text)
|
||||
}
|
||||
n.IsFloat = true
|
||||
n.Float64 = f
|
||||
// If a floating-point extraction succeeded, extract the int if needed.
|
||||
if !n.IsInt && float64(int64(f)) == f {
|
||||
n.IsInt = true
|
||||
n.Int64 = int64(f)
|
||||
}
|
||||
if !n.IsUint && float64(uint64(f)) == f {
|
||||
n.IsUint = true
|
||||
n.Uint64 = uint64(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !n.IsInt && !n.IsUint && !n.IsFloat {
|
||||
return nil, fmt.Errorf("illegal number syntax: %q", text)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// simplifyComplex pulls out any other types that are represented by the complex number.
|
||||
// These all require that the imaginary part be zero.
|
||||
func (n *NumberNode) simplifyComplex() {
|
||||
n.IsFloat = imag(n.Complex128) == 0
|
||||
if n.IsFloat {
|
||||
n.Float64 = real(n.Complex128)
|
||||
n.IsInt = float64(int64(n.Float64)) == n.Float64
|
||||
if n.IsInt {
|
||||
n.Int64 = int64(n.Float64)
|
||||
}
|
||||
n.IsUint = float64(uint64(n.Float64)) == n.Float64
|
||||
if n.IsUint {
|
||||
n.Uint64 = uint64(n.Float64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NumberNode) String() string {
|
||||
return n.Text
|
||||
}
|
||||
|
||||
func (n *NumberNode) tree() *Tree {
|
||||
return n.tr
|
||||
}
|
||||
|
||||
func (n *NumberNode) Copy() Node {
|
||||
nn := new(NumberNode)
|
||||
*nn = *n // Easy, fast, correct.
|
||||
return nn
|
||||
}
|
||||
|
||||
// StringNode holds a string constant. The value has been "unquoted".
|
||||
type StringNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Quoted string // The original text of the string, with quotes.
|
||||
Text string // The string, after quote processing.
|
||||
}
|
||||
|
||||
func (t *Tree) newString(pos Pos, orig, text string) *StringNode {
|
||||
return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
|
||||
}
|
||||
|
||||
func (s *StringNode) String() string {
|
||||
return s.Quoted
|
||||
}
|
||||
|
||||
func (s *StringNode) tree() *Tree {
|
||||
return s.tr
|
||||
}
|
||||
|
||||
func (s *StringNode) Copy() Node {
|
||||
return s.tr.newString(s.Pos, s.Quoted, s.Text)
|
||||
}
|
||||
|
||||
// endNode represents an {{end}} action.
|
||||
// It does not appear in the final parse tree.
|
||||
type endNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newEnd(pos Pos) *endNode {
|
||||
return &endNode{tr: t, NodeType: nodeEnd, Pos: pos}
|
||||
}
|
||||
|
||||
func (e *endNode) String() string {
|
||||
return "{{end}}"
|
||||
}
|
||||
|
||||
func (e *endNode) tree() *Tree {
|
||||
return e.tr
|
||||
}
|
||||
|
||||
func (e *endNode) Copy() Node {
|
||||
return e.tr.newEnd(e.Pos)
|
||||
}
|
||||
|
||||
// elseNode represents an {{else}} action. Does not appear in the final tree.
|
||||
type elseNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input. Deprecated: Kept for compatibility.
|
||||
}
|
||||
|
||||
func (t *Tree) newElse(pos Pos, line int) *elseNode {
|
||||
return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line}
|
||||
}
|
||||
|
||||
func (e *elseNode) Type() NodeType {
|
||||
return nodeElse
|
||||
}
|
||||
|
||||
func (e *elseNode) String() string {
|
||||
return "{{else}}"
|
||||
}
|
||||
|
||||
func (e *elseNode) tree() *Tree {
|
||||
return e.tr
|
||||
}
|
||||
|
||||
func (e *elseNode) Copy() Node {
|
||||
return e.tr.newElse(e.Pos, e.Line)
|
||||
}
|
||||
|
||||
// BranchNode is the common representation of if, range, and with.
|
||||
type BranchNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input. Deprecated: Kept for compatibility.
|
||||
Pipe *PipeNode // The pipeline to be evaluated.
|
||||
List *ListNode // What to execute if the value is non-empty.
|
||||
ElseList *ListNode // What to execute if the value is empty (nil if absent).
|
||||
}
|
||||
|
||||
func (b *BranchNode) String() string {
|
||||
name := ""
|
||||
switch b.NodeType {
|
||||
case NodeIf:
|
||||
name = "if"
|
||||
case NodeRange:
|
||||
name = "range"
|
||||
case NodeWith:
|
||||
name = "with"
|
||||
default:
|
||||
panic("unknown branch type")
|
||||
}
|
||||
if b.ElseList != nil {
|
||||
return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
|
||||
}
|
||||
return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
|
||||
}
|
||||
|
||||
func (b *BranchNode) tree() *Tree {
|
||||
return b.tr
|
||||
}
|
||||
|
||||
func (b *BranchNode) Copy() Node {
|
||||
switch b.NodeType {
|
||||
case NodeIf:
|
||||
return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
case NodeRange:
|
||||
return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
case NodeWith:
|
||||
return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
default:
|
||||
panic("unknown branch type")
|
||||
}
|
||||
}
|
||||
|
||||
// IfNode represents an {{if}} action and its commands.
|
||||
type IfNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
|
||||
return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (i *IfNode) Copy() Node {
|
||||
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// RangeNode represents a {{range}} action and its commands.
|
||||
type RangeNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
|
||||
return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (r *RangeNode) Copy() Node {
|
||||
return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// WithNode represents a {{with}} action and its commands.
|
||||
type WithNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
|
||||
return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (w *WithNode) Copy() Node {
|
||||
return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// TemplateNode represents a {{template}} action.
|
||||
type TemplateNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input. Deprecated: Kept for compatibility.
|
||||
Name string // The name of the template (unquoted).
|
||||
Pipe *PipeNode // The command to evaluate as dot for the template.
|
||||
}
|
||||
|
||||
func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
|
||||
return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe}
|
||||
}
|
||||
|
||||
func (t *TemplateNode) String() string {
|
||||
if t.Pipe == nil {
|
||||
return fmt.Sprintf("{{template %q}}", t.Name)
|
||||
}
|
||||
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
|
||||
}
|
||||
|
||||
func (t *TemplateNode) tree() *Tree {
|
||||
return t.tr
|
||||
}
|
||||
|
||||
func (t *TemplateNode) Copy() Node {
|
||||
return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
|
||||
}
|
||||
@ -1,741 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package parse builds parse trees for templates as defined by text/template
|
||||
// and html/template. Clients should use those packages to construct templates
|
||||
// rather than this one, which provides shared internal data structures not
|
||||
// intended for general use.
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Tree is the representation of a single parsed template.
|
||||
type Tree struct {
|
||||
Name string // name of the template represented by the tree.
|
||||
ParseName string // name of the top-level template during parsing, for error messages.
|
||||
Root *ListNode // top-level root of the tree.
|
||||
text string // text parsed to create the template (or its parent)
|
||||
// Parsing only; cleared after parse.
|
||||
funcs []map[string]interface{}
|
||||
lex *lexer
|
||||
token [3]item // three-token lookahead for parser.
|
||||
peekCount int
|
||||
vars []string // variables defined at the moment.
|
||||
treeSet map[string]*Tree
|
||||
}
|
||||
|
||||
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
||||
func (t *Tree) Copy() *Tree {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return &Tree{
|
||||
Name: t.Name,
|
||||
ParseName: t.ParseName,
|
||||
Root: t.Root.CopyList(),
|
||||
text: t.text,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse returns a map from template name to parse.Tree, created by parsing the
|
||||
// templates described in the argument string. The top-level template will be
|
||||
// given the specified name. If an error is encountered, parsing stops and an
|
||||
// empty map is returned with the error.
|
||||
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (map[string]*Tree, error) {
|
||||
treeSet := make(map[string]*Tree)
|
||||
t := New(name)
|
||||
t.text = text
|
||||
_, err := t.Parse(text, leftDelim, rightDelim, treeSet, funcs...)
|
||||
return treeSet, err
|
||||
}
|
||||
|
||||
// next returns the next token.
|
||||
func (t *Tree) next() item {
|
||||
if t.peekCount > 0 {
|
||||
t.peekCount--
|
||||
} else {
|
||||
t.token[0] = t.lex.nextItem()
|
||||
}
|
||||
return t.token[t.peekCount]
|
||||
}
|
||||
|
||||
// backup backs the input stream up one token.
|
||||
func (t *Tree) backup() {
|
||||
t.peekCount++
|
||||
}
|
||||
|
||||
// backup2 backs the input stream up two tokens.
|
||||
// The zeroth token is already there.
|
||||
func (t *Tree) backup2(t1 item) {
|
||||
t.token[1] = t1
|
||||
t.peekCount = 2
|
||||
}
|
||||
|
||||
// backup3 backs the input stream up three tokens
|
||||
// The zeroth token is already there.
|
||||
func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back.
|
||||
t.token[1] = t1
|
||||
t.token[2] = t2
|
||||
t.peekCount = 3
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next token.
|
||||
func (t *Tree) peek() item {
|
||||
if t.peekCount > 0 {
|
||||
return t.token[t.peekCount-1]
|
||||
}
|
||||
t.peekCount = 1
|
||||
t.token[0] = t.lex.nextItem()
|
||||
return t.token[0]
|
||||
}
|
||||
|
||||
// nextNonSpace returns the next non-space token.
|
||||
func (t *Tree) nextNonSpace() (token item) {
|
||||
for {
|
||||
token = t.next()
|
||||
if token.typ != itemSpace {
|
||||
break
|
||||
}
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// peekNonSpace returns but does not consume the next non-space token.
|
||||
func (t *Tree) peekNonSpace() (token item) {
|
||||
for {
|
||||
token = t.next()
|
||||
if token.typ != itemSpace {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.backup()
|
||||
return token
|
||||
}
|
||||
|
||||
// Parsing.
|
||||
|
||||
// New allocates a new parse tree with the given name.
|
||||
func New(name string, funcs ...map[string]interface{}) *Tree {
|
||||
return &Tree{
|
||||
Name: name,
|
||||
funcs: funcs,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorContext returns a textual representation of the location of the node in the input text.
|
||||
// The receiver is only used when the node does not have a pointer to the tree inside,
|
||||
// which can occur in old code.
|
||||
func (t *Tree) ErrorContext(n Node) (location, context string) {
|
||||
pos := int(n.Position())
|
||||
tree := n.tree()
|
||||
if tree == nil {
|
||||
tree = t
|
||||
}
|
||||
text := tree.text[:pos]
|
||||
byteNum := strings.LastIndex(text, "\n")
|
||||
if byteNum == -1 {
|
||||
byteNum = pos // On first line.
|
||||
} else {
|
||||
byteNum++ // After the newline.
|
||||
byteNum = pos - byteNum
|
||||
}
|
||||
lineNum := 1 + strings.Count(text, "\n")
|
||||
context = n.String()
|
||||
if len(context) > 20 {
|
||||
context = fmt.Sprintf("%.20s...", context)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context
|
||||
}
|
||||
|
||||
// errorf formats the error and terminates processing.
|
||||
func (t *Tree) errorf(format string, args ...interface{}) {
|
||||
t.Root = nil
|
||||
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.token[0].line, format)
|
||||
panic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// error terminates processing.
|
||||
func (t *Tree) error(err error) {
|
||||
t.errorf("%s", err)
|
||||
}
|
||||
|
||||
// expect consumes the next token and guarantees it has the required type.
|
||||
func (t *Tree) expect(expected itemType, context string) item {
|
||||
token := t.nextNonSpace()
|
||||
if token.typ != expected {
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// expectOneOf consumes the next token and guarantees it has one of the required types.
|
||||
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
|
||||
token := t.nextNonSpace()
|
||||
if token.typ != expected1 && token.typ != expected2 {
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// unexpected complains about the token and terminates processing.
|
||||
func (t *Tree) unexpected(token item, context string) {
|
||||
t.errorf("unexpected %s in %s", token, context)
|
||||
}
|
||||
|
||||
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||
func (t *Tree) recover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
if _, ok := e.(runtime.Error); ok {
|
||||
panic(e)
|
||||
}
|
||||
if t != nil {
|
||||
t.lex.drain()
|
||||
t.stopParse()
|
||||
}
|
||||
*errp = e.(error)
|
||||
}
|
||||
}
|
||||
|
||||
// startParse initializes the parser, using the lexer.
|
||||
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer, treeSet map[string]*Tree) {
|
||||
t.Root = nil
|
||||
t.lex = lex
|
||||
t.vars = []string{"$"}
|
||||
t.funcs = funcs
|
||||
t.treeSet = treeSet
|
||||
}
|
||||
|
||||
// stopParse terminates parsing.
|
||||
func (t *Tree) stopParse() {
|
||||
t.lex = nil
|
||||
t.vars = nil
|
||||
t.funcs = nil
|
||||
t.treeSet = nil
|
||||
}
|
||||
|
||||
// Parse parses the template definition string to construct a representation of
|
||||
// the template for execution. If either action delimiter string is empty, the
|
||||
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
||||
// the treeSet map.
|
||||
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
|
||||
defer t.recover(&err)
|
||||
t.ParseName = t.Name
|
||||
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim), treeSet)
|
||||
t.text = text
|
||||
t.parse()
|
||||
t.add()
|
||||
t.stopParse()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// add adds tree to t.treeSet.
|
||||
func (t *Tree) add() {
|
||||
tree := t.treeSet[t.Name]
|
||||
if tree == nil || IsEmptyTree(tree.Root) {
|
||||
t.treeSet[t.Name] = t
|
||||
return
|
||||
}
|
||||
if !IsEmptyTree(t.Root) {
|
||||
t.errorf("template: multiple definition of template %q", t.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEmptyTree reports whether this tree (node) is empty of everything but space.
|
||||
func IsEmptyTree(n Node) bool {
|
||||
switch n := n.(type) {
|
||||
case nil:
|
||||
return true
|
||||
case *ActionNode:
|
||||
case *IfNode:
|
||||
case *ListNode:
|
||||
for _, node := range n.Nodes {
|
||||
if !IsEmptyTree(node) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *RangeNode:
|
||||
case *TemplateNode:
|
||||
case *TextNode:
|
||||
return len(bytes.TrimSpace(n.Text)) == 0
|
||||
case *WithNode:
|
||||
default:
|
||||
panic("unknown node: " + n.String())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parse is the top-level parser for a template, essentially the same
|
||||
// as itemList except it also parses {{define}} actions.
|
||||
// It runs to EOF.
|
||||
func (t *Tree) parse() {
|
||||
t.Root = t.newList(t.peek().pos)
|
||||
for t.peek().typ != itemEOF {
|
||||
if t.peek().typ == itemLeftDelim {
|
||||
delim := t.next()
|
||||
if t.nextNonSpace().typ == itemDefine {
|
||||
newT := New("definition") // name will be updated once we know it.
|
||||
newT.text = t.text
|
||||
newT.ParseName = t.ParseName
|
||||
newT.startParse(t.funcs, t.lex, t.treeSet)
|
||||
newT.parseDefinition()
|
||||
continue
|
||||
}
|
||||
t.backup2(delim)
|
||||
}
|
||||
switch n := t.textOrAction(); n.Type() {
|
||||
case nodeEnd, nodeElse:
|
||||
t.errorf("unexpected %s", n)
|
||||
default:
|
||||
t.Root.append(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseDefinition parses a {{define}} ... {{end}} template definition and
|
||||
// installs the definition in t.treeSet. The "define" keyword has already
|
||||
// been scanned.
|
||||
func (t *Tree) parseDefinition() {
|
||||
const context = "define clause"
|
||||
name := t.expectOneOf(itemString, itemRawString, context)
|
||||
var err error
|
||||
t.Name, err = strconv.Unquote(name.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
t.expect(itemRightDelim, context)
|
||||
var end Node
|
||||
t.Root, end = t.itemList()
|
||||
if end.Type() != nodeEnd {
|
||||
t.errorf("unexpected %s in %s", end, context)
|
||||
}
|
||||
t.add()
|
||||
t.stopParse()
|
||||
}
|
||||
|
||||
// itemList:
|
||||
// textOrAction*
|
||||
// Terminates at {{end}} or {{else}}, returned separately.
|
||||
func (t *Tree) itemList() (list *ListNode, next Node) {
|
||||
list = t.newList(t.peekNonSpace().pos)
|
||||
for t.peekNonSpace().typ != itemEOF {
|
||||
n := t.textOrAction()
|
||||
switch n.Type() {
|
||||
case nodeEnd, nodeElse:
|
||||
return list, n
|
||||
}
|
||||
list.append(n)
|
||||
}
|
||||
t.errorf("unexpected EOF")
|
||||
return
|
||||
}
|
||||
|
||||
// textOrAction:
|
||||
// text | action
|
||||
func (t *Tree) textOrAction() Node {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemText:
|
||||
return t.newText(token.pos, token.val)
|
||||
case itemLeftDelim:
|
||||
return t.action()
|
||||
default:
|
||||
t.unexpected(token, "input")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Action:
|
||||
// control
|
||||
// command ("|" command)*
|
||||
// Left delim is past. Now get actions.
|
||||
// First word could be a keyword such as range.
|
||||
func (t *Tree) action() (n Node) {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemBlock:
|
||||
return t.blockControl()
|
||||
case itemElse:
|
||||
return t.elseControl()
|
||||
case itemEnd:
|
||||
return t.endControl()
|
||||
case itemIf:
|
||||
return t.ifControl()
|
||||
case itemRange:
|
||||
return t.rangeControl()
|
||||
case itemTemplate:
|
||||
return t.templateControl()
|
||||
case itemWith:
|
||||
return t.withControl()
|
||||
}
|
||||
t.backup()
|
||||
token := t.peek()
|
||||
// Do not pop variables; they persist until "end".
|
||||
return t.newAction(token.pos, token.line, t.pipeline("command"))
|
||||
}
|
||||
|
||||
// Pipeline:
|
||||
// declarations? command ('|' command)*
|
||||
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
|
||||
decl := false
|
||||
var vars []*VariableNode
|
||||
token := t.peekNonSpace()
|
||||
pos := token.pos
|
||||
// Are there declarations or assignments?
|
||||
for {
|
||||
if v := t.peekNonSpace(); v.typ == itemVariable {
|
||||
t.next()
|
||||
// Since space is a token, we need 3-token look-ahead here in the worst case:
|
||||
// in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an
|
||||
// argument variable rather than a declaration. So remember the token
|
||||
// adjacent to the variable so we can push it back if necessary.
|
||||
tokenAfterVariable := t.peek()
|
||||
next := t.peekNonSpace()
|
||||
switch {
|
||||
case next.typ == itemAssign, next.typ == itemDeclare,
|
||||
next.typ == itemChar && next.val == ",":
|
||||
t.nextNonSpace()
|
||||
variable := t.newVariable(v.pos, v.val)
|
||||
vars = append(vars, variable)
|
||||
t.vars = append(t.vars, v.val)
|
||||
if next.typ == itemDeclare {
|
||||
decl = true
|
||||
}
|
||||
if next.typ == itemChar && next.val == "," {
|
||||
if context == "range" && len(vars) < 2 {
|
||||
continue
|
||||
}
|
||||
t.errorf("too many declarations in %s", context)
|
||||
}
|
||||
case tokenAfterVariable.typ == itemSpace:
|
||||
t.backup3(v, tokenAfterVariable)
|
||||
default:
|
||||
t.backup2(v)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
pipe = t.newPipeline(pos, token.line, vars)
|
||||
pipe.IsAssign = !decl
|
||||
for {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemRightDelim, itemRightParen:
|
||||
// At this point, the pipeline is complete
|
||||
t.checkPipeline(pipe, context)
|
||||
if token.typ == itemRightParen {
|
||||
t.backup()
|
||||
}
|
||||
return
|
||||
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
|
||||
itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen:
|
||||
t.backup()
|
||||
pipe.append(t.command())
|
||||
default:
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) checkPipeline(pipe *PipeNode, context string) {
|
||||
// Reject empty pipelines
|
||||
if len(pipe.Cmds) == 0 {
|
||||
t.errorf("missing value for %s", context)
|
||||
}
|
||||
// Only the first command of a pipeline can start with a non executable operand
|
||||
for i, c := range pipe.Cmds[1:] {
|
||||
switch c.Args[0].Type() {
|
||||
case NodeBool, NodeDot, NodeNil, NodeNumber, NodeString:
|
||||
// With A|B|C, pipeline stage 2 is B
|
||||
t.errorf("non executable command in pipeline stage %d", i+2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
|
||||
defer t.popVars(len(t.vars))
|
||||
pipe = t.pipeline(context)
|
||||
var next Node
|
||||
list, next = t.itemList()
|
||||
switch next.Type() {
|
||||
case nodeEnd: //done
|
||||
case nodeElse:
|
||||
if allowElseIf {
|
||||
// Special case for "else if". If the "else" is followed immediately by an "if",
|
||||
// the elseControl will have left the "if" token pending. Treat
|
||||
// {{if a}}_{{else if b}}_{{end}}
|
||||
// as
|
||||
// {{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
|
||||
// To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
|
||||
// is assumed. This technique works even for long if-else-if chains.
|
||||
// TODO: Should we allow else-if in with and range?
|
||||
if t.peek().typ == itemIf {
|
||||
t.next() // Consume the "if" token.
|
||||
elseList = t.newList(next.Position())
|
||||
elseList.append(t.ifControl())
|
||||
// Do not consume the next item - only one {{end}} required.
|
||||
break
|
||||
}
|
||||
}
|
||||
elseList, next = t.itemList()
|
||||
if next.Type() != nodeEnd {
|
||||
t.errorf("expected end; found %s", next)
|
||||
}
|
||||
}
|
||||
return pipe.Position(), pipe.Line, pipe, list, elseList
|
||||
}
|
||||
|
||||
// If:
|
||||
// {{if pipeline}} itemList {{end}}
|
||||
// {{if pipeline}} itemList {{else}} itemList {{end}}
|
||||
// If keyword is past.
|
||||
func (t *Tree) ifControl() Node {
|
||||
return t.newIf(t.parseControl(true, "if"))
|
||||
}
|
||||
|
||||
// Range:
|
||||
// {{range pipeline}} itemList {{end}}
|
||||
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
||||
// Range keyword is past.
|
||||
func (t *Tree) rangeControl() Node {
|
||||
return t.newRange(t.parseControl(false, "range"))
|
||||
}
|
||||
|
||||
// With:
|
||||
// {{with pipeline}} itemList {{end}}
|
||||
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
||||
// If keyword is past.
|
||||
func (t *Tree) withControl() Node {
|
||||
return t.newWith(t.parseControl(false, "with"))
|
||||
}
|
||||
|
||||
// End:
|
||||
// {{end}}
|
||||
// End keyword is past.
|
||||
func (t *Tree) endControl() Node {
|
||||
return t.newEnd(t.expect(itemRightDelim, "end").pos)
|
||||
}
|
||||
|
||||
// Else:
|
||||
// {{else}}
|
||||
// Else keyword is past.
|
||||
func (t *Tree) elseControl() Node {
|
||||
// Special case for "else if".
|
||||
peek := t.peekNonSpace()
|
||||
if peek.typ == itemIf {
|
||||
// We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
|
||||
return t.newElse(peek.pos, peek.line)
|
||||
}
|
||||
token := t.expect(itemRightDelim, "else")
|
||||
return t.newElse(token.pos, token.line)
|
||||
}
|
||||
|
||||
// Block:
|
||||
// {{block stringValue pipeline}}
|
||||
// Block keyword is past.
|
||||
// The name must be something that can evaluate to a string.
|
||||
// The pipeline is mandatory.
|
||||
func (t *Tree) blockControl() Node {
|
||||
const context = "block clause"
|
||||
|
||||
token := t.nextNonSpace()
|
||||
name := t.parseTemplateName(token, context)
|
||||
pipe := t.pipeline(context)
|
||||
|
||||
block := New(name) // name will be updated once we know it.
|
||||
block.text = t.text
|
||||
block.ParseName = t.ParseName
|
||||
block.startParse(t.funcs, t.lex, t.treeSet)
|
||||
var end Node
|
||||
block.Root, end = block.itemList()
|
||||
if end.Type() != nodeEnd {
|
||||
t.errorf("unexpected %s in %s", end, context)
|
||||
}
|
||||
block.add()
|
||||
block.stopParse()
|
||||
|
||||
return t.newTemplate(token.pos, token.line, name, pipe)
|
||||
}
|
||||
|
||||
// Template:
|
||||
// {{template stringValue pipeline}}
|
||||
// Template keyword is past. The name must be something that can evaluate
|
||||
// to a string.
|
||||
func (t *Tree) templateControl() Node {
|
||||
const context = "template clause"
|
||||
token := t.nextNonSpace()
|
||||
name := t.parseTemplateName(token, context)
|
||||
var pipe *PipeNode
|
||||
if t.nextNonSpace().typ != itemRightDelim {
|
||||
t.backup()
|
||||
// Do not pop variables; they persist until "end".
|
||||
pipe = t.pipeline(context)
|
||||
}
|
||||
return t.newTemplate(token.pos, token.line, name, pipe)
|
||||
}
|
||||
|
||||
func (t *Tree) parseTemplateName(token item, context string) (name string) {
|
||||
switch token.typ {
|
||||
case itemString, itemRawString:
|
||||
s, err := strconv.Unquote(token.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
name = s
|
||||
default:
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// command:
|
||||
// operand (space operand)*
|
||||
// space-separated arguments up to a pipeline character or right delimiter.
|
||||
// we consume the pipe character but leave the right delim to terminate the action.
|
||||
func (t *Tree) command() *CommandNode {
|
||||
cmd := t.newCommand(t.peekNonSpace().pos)
|
||||
for {
|
||||
t.peekNonSpace() // skip leading spaces.
|
||||
operand := t.operand()
|
||||
if operand != nil {
|
||||
cmd.append(operand)
|
||||
}
|
||||
switch token := t.next(); token.typ {
|
||||
case itemSpace:
|
||||
continue
|
||||
case itemError:
|
||||
t.errorf("%s", token.val)
|
||||
case itemRightDelim, itemRightParen:
|
||||
t.backup()
|
||||
case itemPipe:
|
||||
default:
|
||||
t.errorf("unexpected %s in operand", token)
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(cmd.Args) == 0 {
|
||||
t.errorf("empty command")
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// operand:
|
||||
// term .Field*
|
||||
// An operand is a space-separated component of a command,
|
||||
// a term possibly followed by field accesses.
|
||||
// A nil return means the next item is not an operand.
|
||||
func (t *Tree) operand() Node {
|
||||
node := t.term()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
if t.peek().typ == itemField {
|
||||
chain := t.newChain(t.peek().pos, node)
|
||||
for t.peek().typ == itemField {
|
||||
chain.Add(t.next().val)
|
||||
}
|
||||
// Compatibility with original API: If the term is of type NodeField
|
||||
// or NodeVariable, just put more fields on the original.
|
||||
// Otherwise, keep the Chain node.
|
||||
// Obvious parsing errors involving literal values are detected here.
|
||||
// More complex error cases will have to be handled at execution time.
|
||||
switch node.Type() {
|
||||
case NodeField:
|
||||
node = t.newField(chain.Position(), chain.String())
|
||||
case NodeVariable:
|
||||
node = t.newVariable(chain.Position(), chain.String())
|
||||
case NodeBool, NodeString, NodeNumber, NodeNil, NodeDot:
|
||||
t.errorf("unexpected . after term %q", node.String())
|
||||
default:
|
||||
node = chain
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// term:
|
||||
// literal (number, string, nil, boolean)
|
||||
// function (identifier)
|
||||
// .
|
||||
// .Field
|
||||
// $
|
||||
// '(' pipeline ')'
|
||||
// A term is a simple "expression".
|
||||
// A nil return means the next item is not a term.
|
||||
func (t *Tree) term() Node {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemError:
|
||||
t.errorf("%s", token.val)
|
||||
case itemIdentifier:
|
||||
if !t.hasFunction(token.val) {
|
||||
t.errorf("function %q not defined", token.val)
|
||||
}
|
||||
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
||||
case itemDot:
|
||||
return t.newDot(token.pos)
|
||||
case itemNil:
|
||||
return t.newNil(token.pos)
|
||||
case itemVariable:
|
||||
return t.useVar(token.pos, token.val)
|
||||
case itemField:
|
||||
return t.newField(token.pos, token.val)
|
||||
case itemBool:
|
||||
return t.newBool(token.pos, token.val == "true")
|
||||
case itemCharConstant, itemComplex, itemNumber:
|
||||
number, err := t.newNumber(token.pos, token.val, token.typ)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
return number
|
||||
case itemLeftParen:
|
||||
pipe := t.pipeline("parenthesized pipeline")
|
||||
if token := t.next(); token.typ != itemRightParen {
|
||||
t.errorf("unclosed right paren: unexpected %s", token)
|
||||
}
|
||||
return pipe
|
||||
case itemString, itemRawString:
|
||||
s, err := strconv.Unquote(token.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
return t.newString(token.pos, token.val, s)
|
||||
}
|
||||
t.backup()
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasFunction reports if a function name exists in the Tree's maps.
|
||||
func (t *Tree) hasFunction(name string) bool {
|
||||
for _, funcMap := range t.funcs {
|
||||
if funcMap == nil {
|
||||
continue
|
||||
}
|
||||
if funcMap[name] != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// popVars trims the variable list to the specified length
|
||||
func (t *Tree) popVars(n int) {
|
||||
t.vars = t.vars[:n]
|
||||
}
|
||||
|
||||
// useVar returns a node for a variable reference. It errors if the
|
||||
// variable is not defined.
|
||||
func (t *Tree) useVar(pos Pos, name string) Node {
|
||||
v := t.newVariable(pos, name)
|
||||
for _, varName := range t.vars {
|
||||
if varName == v.Ident[0] {
|
||||
return v
|
||||
}
|
||||
}
|
||||
t.errorf("undefined variable %q", v.Ident[0])
|
||||
return nil
|
||||
}
|
||||
@ -1,224 +0,0 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
"github.com/gogf/gf/g/os/gview/internal/text/template/parse"
|
||||
)
|
||||
|
||||
// common holds the information shared by related templates.
|
||||
type common struct {
|
||||
tmpl map[string]*Template // Map from name to defined templates.
|
||||
option option
|
||||
// We use two maps, one for parsing and one for execution.
|
||||
// This separation makes the API cleaner since it doesn't
|
||||
// expose reflection to the client.
|
||||
muFuncs sync.RWMutex // protects parseFuncs and execFuncs
|
||||
parseFuncs FuncMap
|
||||
execFuncs map[string]reflect.Value
|
||||
}
|
||||
|
||||
// Template is the representation of a parsed template. The *parse.Tree
|
||||
// field is exported only for use by html/template and should be treated
|
||||
// as unexported by all other clients.
|
||||
type Template struct {
|
||||
name string
|
||||
*parse.Tree
|
||||
*common
|
||||
leftDelim string
|
||||
rightDelim string
|
||||
}
|
||||
|
||||
// New allocates a new, undefined template with the given name.
|
||||
func New(name string) *Template {
|
||||
t := &Template{
|
||||
name: name,
|
||||
}
|
||||
t.init()
|
||||
return t
|
||||
}
|
||||
|
||||
// Name returns the name of the template.
|
||||
func (t *Template) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// New allocates a new, undefined template associated with the given one and with the same
|
||||
// delimiters. The association, which is transitive, allows one template to
|
||||
// invoke another with a {{template}} action.
|
||||
func (t *Template) New(name string) *Template {
|
||||
t.init()
|
||||
nt := &Template{
|
||||
name: name,
|
||||
common: t.common,
|
||||
leftDelim: t.leftDelim,
|
||||
rightDelim: t.rightDelim,
|
||||
}
|
||||
return nt
|
||||
}
|
||||
|
||||
// init guarantees that t has a valid common structure.
|
||||
func (t *Template) init() {
|
||||
if t.common == nil {
|
||||
c := new(common)
|
||||
c.tmpl = make(map[string]*Template)
|
||||
c.parseFuncs = make(FuncMap)
|
||||
c.execFuncs = make(map[string]reflect.Value)
|
||||
t.common = c
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a duplicate of the template, including all associated
|
||||
// templates. The actual representation is not copied, but the name space of
|
||||
// associated templates is, so further calls to Parse in the copy will add
|
||||
// templates to the copy but not to the original. Clone can be used to prepare
|
||||
// common templates and use them with variant definitions for other templates
|
||||
// by adding the variants after the clone is made.
|
||||
func (t *Template) Clone() (*Template, error) {
|
||||
nt := t.copy(nil)
|
||||
nt.init()
|
||||
if t.common == nil {
|
||||
return nt, nil
|
||||
}
|
||||
for k, v := range t.tmpl {
|
||||
if k == t.name {
|
||||
nt.tmpl[t.name] = nt
|
||||
continue
|
||||
}
|
||||
// The associated templates share nt's common structure.
|
||||
tmpl := v.copy(nt.common)
|
||||
nt.tmpl[k] = tmpl
|
||||
}
|
||||
t.muFuncs.RLock()
|
||||
defer t.muFuncs.RUnlock()
|
||||
for k, v := range t.parseFuncs {
|
||||
nt.parseFuncs[k] = v
|
||||
}
|
||||
for k, v := range t.execFuncs {
|
||||
nt.execFuncs[k] = v
|
||||
}
|
||||
return nt, nil
|
||||
}
|
||||
|
||||
// copy returns a shallow copy of t, with common set to the argument.
|
||||
func (t *Template) copy(c *common) *Template {
|
||||
nt := New(t.name)
|
||||
nt.Tree = t.Tree
|
||||
nt.common = c
|
||||
nt.leftDelim = t.leftDelim
|
||||
nt.rightDelim = t.rightDelim
|
||||
return nt
|
||||
}
|
||||
|
||||
// AddParseTree adds parse tree for template with given name and associates it with t.
|
||||
// If the template does not already exist, it will create a new one.
|
||||
// If the template does exist, it will be replaced.
|
||||
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
||||
t.init()
|
||||
// If the name is the name of this template, overwrite this template.
|
||||
nt := t
|
||||
if name != t.name {
|
||||
nt = t.New(name)
|
||||
}
|
||||
// Even if nt == t, we need to install it in the common.tmpl map.
|
||||
if t.associate(nt, tree) || nt.Tree == nil {
|
||||
nt.Tree = tree
|
||||
}
|
||||
return nt, nil
|
||||
}
|
||||
|
||||
// Templates returns a slice of defined templates associated with t.
|
||||
func (t *Template) Templates() []*Template {
|
||||
if t.common == nil {
|
||||
return nil
|
||||
}
|
||||
// Return a slice so we don't expose the map.
|
||||
m := make([]*Template, 0, len(t.tmpl))
|
||||
for _, v := range t.tmpl {
|
||||
m = append(m, v)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Delims sets the action delimiters to the specified strings, to be used in
|
||||
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
|
||||
// definitions will inherit the settings. An empty delimiter stands for the
|
||||
// corresponding default: {{ or }}.
|
||||
// The return value is the template, so calls can be chained.
|
||||
func (t *Template) Delims(left, right string) *Template {
|
||||
t.init()
|
||||
t.leftDelim = left
|
||||
t.rightDelim = right
|
||||
return t
|
||||
}
|
||||
|
||||
// Funcs adds the elements of the argument map to the template's function map.
|
||||
// It must be called before the template is parsed.
|
||||
// It panics if a value in the map is not a function with appropriate return
|
||||
// type or if the name cannot be used syntactically as a function in a template.
|
||||
// It is legal to overwrite elements of the map. The return value is the template,
|
||||
// so calls can be chained.
|
||||
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||
t.init()
|
||||
t.muFuncs.Lock()
|
||||
defer t.muFuncs.Unlock()
|
||||
addValueFuncs(t.execFuncs, funcMap)
|
||||
addFuncs(t.parseFuncs, funcMap)
|
||||
return t
|
||||
}
|
||||
|
||||
// Lookup returns the template with the given name that is associated with t.
|
||||
// It returns nil if there is no such template or the template has no definition.
|
||||
func (t *Template) Lookup(name string) *Template {
|
||||
if t.common == nil {
|
||||
return nil
|
||||
}
|
||||
return t.tmpl[name]
|
||||
}
|
||||
|
||||
// Parse parses text as a template body for t.
|
||||
// Named template definitions ({{define ...}} or {{block ...}} statements) in text
|
||||
// define additional templates associated with t and are removed from the
|
||||
// definition of t itself.
|
||||
//
|
||||
// Templates can be redefined in successive calls to Parse.
|
||||
// A template definition with a body containing only white space and comments
|
||||
// is considered empty and will not replace an existing template's body.
|
||||
// This allows using Parse to add new named template definitions without
|
||||
// overwriting the main template body.
|
||||
func (t *Template) Parse(text string) (*Template, error) {
|
||||
t.init()
|
||||
t.muFuncs.RLock()
|
||||
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
|
||||
t.muFuncs.RUnlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Add the newly parsed trees, including the one for t, into our common structure.
|
||||
for name, tree := range trees {
|
||||
if _, err := t.AddParseTree(name, tree); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// associate installs the new template into the group of templates associated
|
||||
// with t. The two are already known to share the common structure.
|
||||
// The boolean return value reports whether to store this tree as t.Tree.
|
||||
func (t *Template) associate(new *Template, tree *parse.Tree) bool {
|
||||
if new.common != t.common {
|
||||
panic("internal error: associate not common")
|
||||
}
|
||||
if old := t.tmpl[new.name]; old != nil && parse.IsEmptyTree(tree.Root) && old.Tree != nil {
|
||||
// If a template by that name exists,
|
||||
// don't replace it with an empty template.
|
||||
return false
|
||||
}
|
||||
t.tmpl[new.name] = new
|
||||
return true
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
// from golang-1.11.2 text/template
|
||||
// 1. remove "<no value>" when template variable does not exist;
|
||||
package text
|
||||
@ -111,10 +111,16 @@ func AssertGT(value, expect interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// AssertGTE checks <value> is GREATER OR EQUAL THAN <expect>.
|
||||
// See AssertGE.
|
||||
// Deprecated.
|
||||
func AssertGTE(value, expect interface{}) {
|
||||
AssertGE(value, expect)
|
||||
}
|
||||
|
||||
// AssertGE checks <value> is GREATER OR EQUAL THAN <expect>.
|
||||
// Notice that, only string, integer and float types can be compared by AssertGTE,
|
||||
// others are invalid.
|
||||
func AssertGTE(value, expect interface{}) {
|
||||
func AssertGE(value, expect interface{}) {
|
||||
passed := false
|
||||
switch reflect.ValueOf(expect).Kind() {
|
||||
case reflect.String:
|
||||
@ -157,10 +163,16 @@ func AssertLT(value, expect interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// AssertLTE checks <value> is LESS OR EQUAL THAN <expect>.
|
||||
// See AssertLE.
|
||||
// Deprecated.
|
||||
func AssertLTE(value, expect interface{}) {
|
||||
AssertLE(value, expect)
|
||||
}
|
||||
|
||||
// AssertLE checks <value> is LESS OR EQUAL THAN <expect>.
|
||||
// Notice that, only string, integer and float types can be compared by AssertLTE,
|
||||
// others are invalid.
|
||||
func AssertLTE(value, expect interface{}) {
|
||||
func AssertLE(value, expect interface{}) {
|
||||
passed := false
|
||||
switch reflect.ValueOf(expect).Kind() {
|
||||
case reflect.String:
|
||||
|
||||
@ -5,8 +5,6 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gregex provides high performance API for regular expression functionality.
|
||||
//
|
||||
// 正则表达式.
|
||||
package gregex
|
||||
|
||||
import (
|
||||
@ -17,22 +15,18 @@ import (
|
||||
// to match the rules of regular expression pattern.
|
||||
// And returns the copy.
|
||||
//
|
||||
// 转移正则规则字符串,例如:Quote(`[foo]`) 返回 `\[foo\]`
|
||||
// Eg: Quote(`[foo]`) returns `\[foo\]`.
|
||||
func Quote(s string) string {
|
||||
return regexp.QuoteMeta(s)
|
||||
}
|
||||
|
||||
// Validate checks whether given regular expression pattern <pattern> valid.
|
||||
//
|
||||
// 校验所给定的正则表达式是否符合规范
|
||||
func Validate(pattern string) error {
|
||||
_, err := getRegexp(pattern)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsMatch checks whether given bytes <src> matches <pattern>.
|
||||
//
|
||||
// 正则表达式是否匹配
|
||||
func IsMatch(pattern string, src []byte) bool {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.Match(src)
|
||||
@ -41,15 +35,11 @@ func IsMatch(pattern string, src []byte) bool {
|
||||
}
|
||||
|
||||
// IsMatchString checks whether given string <src> matches <pattern>.
|
||||
//
|
||||
// 判断给定的字符串<src>是否满足正则表达式<pattern>.
|
||||
func IsMatchString(pattern string, src string) bool {
|
||||
return IsMatch(pattern, []byte(src))
|
||||
}
|
||||
|
||||
// MatchString return bytes slice that matched <pattern>.
|
||||
//
|
||||
// 正则匹配,并返回匹配的列表(参数[]byte)
|
||||
func Match(pattern string, src []byte) ([][]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.FindSubmatch(src), nil
|
||||
@ -59,8 +49,6 @@ func Match(pattern string, src []byte) ([][]byte, error) {
|
||||
}
|
||||
|
||||
// MatchString return strings that matched <pattern>.
|
||||
//
|
||||
// 正则匹配,并返回匹配的列表(参数[]string)
|
||||
func MatchString(pattern string, src string) ([]string, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.FindStringSubmatch(src), nil
|
||||
@ -70,8 +58,6 @@ func MatchString(pattern string, src string) ([]string, error) {
|
||||
}
|
||||
|
||||
// MatchAll return all bytes slices that matched <pattern>.
|
||||
//
|
||||
// 正则匹配,并返回所有匹配的列表(参数[]string)
|
||||
func MatchAll(pattern string, src []byte) ([][][]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.FindAllSubmatch(src, -1), nil
|
||||
@ -81,8 +67,6 @@ func MatchAll(pattern string, src []byte) ([][][]byte, error) {
|
||||
}
|
||||
|
||||
// MatchAllString return all strings that matched <pattern>.
|
||||
//
|
||||
// 正则匹配,并返回所有匹配的列表(参数[][]string).
|
||||
func MatchAllString(pattern string, src string) ([][]string, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.FindAllStringSubmatch(src, -1), nil
|
||||
@ -92,8 +76,6 @@ func MatchAllString(pattern string, src string) ([][]string, error) {
|
||||
}
|
||||
|
||||
// ReplaceString replace all matched <pattern> in bytes <src> with bytes <replace>.
|
||||
//
|
||||
// 正则替换(全部替换).
|
||||
func Replace(pattern string, replace, src []byte) ([]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.ReplaceAll(src, replace), nil
|
||||
@ -103,8 +85,6 @@ func Replace(pattern string, replace, src []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// ReplaceString replace all matched <pattern> in string <src> with string <replace>.
|
||||
//
|
||||
// 正则替换(全部替换),字符串
|
||||
func ReplaceString(pattern, replace, src string) (string, error) {
|
||||
r, e := Replace(pattern, []byte(replace), []byte(src))
|
||||
return string(r), e
|
||||
@ -112,20 +92,31 @@ func ReplaceString(pattern, replace, src string) (string, error) {
|
||||
|
||||
// ReplaceFunc replace all matched <pattern> in bytes <src>
|
||||
// with custom replacement function <replaceFunc>.
|
||||
//
|
||||
// 正则替换(全部替换),给定自定义替换方法
|
||||
func ReplaceFunc(pattern string, src []byte, replaceFunc func(b []byte) []byte) ([]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.ReplaceAllFunc(src, replaceFunc), nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.ReplaceAllFunc(src, replaceFunc), nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceFunc replace all matched <pattern> in bytes <src>
|
||||
// with custom replacement function <replaceFunc>.
|
||||
// The parameter <match> type for <replaceFunc> is [][]byte,
|
||||
// which is the result contains all sub-patterns of <pattern> using Match function.
|
||||
func ReplaceFuncMatch(pattern string, src []byte, replaceFunc func(match [][]byte) []byte) ([]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.ReplaceAllFunc(src, func(bytes []byte) []byte {
|
||||
match, _ := Match(pattern, src)
|
||||
return replaceFunc(match)
|
||||
}), nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceStringFunc replace all matched <pattern> in string <src>
|
||||
// with custom replacement function <replaceFunc>.
|
||||
//
|
||||
// 正则替换(全部替换),给定自定义替换方法
|
||||
func ReplaceStringFunc(pattern string, src string, replaceFunc func(s string) string) (string, error) {
|
||||
bytes, err := ReplaceFunc(pattern, []byte(src), func(bytes []byte) []byte {
|
||||
return []byte(replaceFunc(string(bytes)))
|
||||
@ -133,10 +124,23 @@ func ReplaceStringFunc(pattern string, src string, replaceFunc func(s string) st
|
||||
return string(bytes), err
|
||||
}
|
||||
|
||||
// Split slices s into substrings separated by the expression and returns a slice of
|
||||
// ReplaceStringFuncMatch replace all matched <pattern> in string <src>
|
||||
// with custom replacement function <replaceFunc>.
|
||||
// The parameter <match> type for <replaceFunc> is []string,
|
||||
// which is the result contains all sub-patterns of <pattern> using MatchString function.
|
||||
func ReplaceStringFuncMatch(pattern string, src string, replaceFunc func(match []string) string) (string, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return string(r.ReplaceAllFunc([]byte(src), func(bytes []byte) []byte {
|
||||
match, _ := MatchString(pattern, src)
|
||||
return []byte(replaceFunc(match))
|
||||
})), nil
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Split slices <src> into substrings separated by the expression and returns a slice of
|
||||
// the substrings between those expression matches.
|
||||
//
|
||||
// 通过一个正则表达式分隔字符串.
|
||||
func Split(pattern string, src string) []string {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.Split(src, -1)
|
||||
|
||||
@ -11,13 +11,15 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 缓存对象,主要用于缓存底层regx对象
|
||||
var (
|
||||
regexMu = sync.RWMutex{}
|
||||
regexMap = make(map[string]*regexp.Regexp)
|
||||
)
|
||||
|
||||
// 根据pattern生成对应的regexp正则对象
|
||||
// getRegexp returns *regexp.Regexp object with given <pattern>.
|
||||
// It uses cache to enhance the performance for compiling regular expression pattern,
|
||||
// which means, it will return the same *regexp.Regexp object with the same regular
|
||||
// expression pattern.
|
||||
func getRegexp(pattern string) (*regexp.Regexp, error) {
|
||||
if r := getCache(pattern); r != nil {
|
||||
return r, nil
|
||||
@ -30,7 +32,7 @@ func getRegexp(pattern string) (*regexp.Regexp, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获得正则缓存对象
|
||||
// getCache returns *regexp.Regexp object from cache by given <pattern>, for internal usage.
|
||||
func getCache(pattern string) (regex *regexp.Regexp) {
|
||||
regexMu.RLock()
|
||||
regex = regexMap[pattern]
|
||||
@ -38,7 +40,7 @@ func getCache(pattern string) (regex *regexp.Regexp) {
|
||||
return
|
||||
}
|
||||
|
||||
// 设置正则缓存对象
|
||||
// setCache stores *regexp.Regexp object into cache, for internal usage.
|
||||
func setCache(pattern string, regex *regexp.Regexp) {
|
||||
regexMu.Lock()
|
||||
regexMap[pattern] = regex
|
||||
|
||||
@ -185,6 +185,33 @@ func Test_ReplaceFun(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ReplaceFuncMatch(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := []byte("1234567890")
|
||||
p := `(\d{3})(\d{3})(.+)`
|
||||
s0, e0 := gregex.ReplaceFuncMatch(p, s, func(match [][]byte) []byte {
|
||||
return match[0]
|
||||
})
|
||||
gtest.Assert(e0, nil)
|
||||
gtest.Assert(s0, s)
|
||||
s1, e1 := gregex.ReplaceFuncMatch(p, s, func(match [][]byte) []byte {
|
||||
return match[1]
|
||||
})
|
||||
gtest.Assert(e1, nil)
|
||||
gtest.Assert(s1, []byte("123"))
|
||||
s2, e2 := gregex.ReplaceFuncMatch(p, s, func(match [][]byte) []byte {
|
||||
return match[2]
|
||||
})
|
||||
gtest.Assert(e2, nil)
|
||||
gtest.Assert(s2, []byte("456"))
|
||||
s3, e3 := gregex.ReplaceFuncMatch(p, s, func(match [][]byte) []byte {
|
||||
return match[3]
|
||||
})
|
||||
gtest.Assert(e3, nil)
|
||||
gtest.Assert(s3, []byte("7890"))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ReplaceStringFunc(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
re := "a(a+b+)b"
|
||||
@ -206,6 +233,33 @@ func Test_ReplaceStringFunc(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ReplaceStringFuncMatch(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
s := "1234567890"
|
||||
p := `(\d{3})(\d{3})(.+)`
|
||||
s0, e0 := gregex.ReplaceStringFuncMatch(p, s, func(match []string) string {
|
||||
return match[0]
|
||||
})
|
||||
gtest.Assert(e0, nil)
|
||||
gtest.Assert(s0, s)
|
||||
s1, e1 := gregex.ReplaceStringFuncMatch(p, s, func(match []string) string {
|
||||
return match[1]
|
||||
})
|
||||
gtest.Assert(e1, nil)
|
||||
gtest.Assert(s1, "123")
|
||||
s2, e2 := gregex.ReplaceStringFuncMatch(p, s, func(match []string) string {
|
||||
return match[2]
|
||||
})
|
||||
gtest.Assert(e2, nil)
|
||||
gtest.Assert(s2, "456")
|
||||
s3, e3 := gregex.ReplaceStringFuncMatch(p, s, func(match []string) string {
|
||||
return match[3]
|
||||
})
|
||||
gtest.Assert(e3, nil)
|
||||
gtest.Assert(s3, "7890")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Split(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
re := "a(a+b+)b"
|
||||
|
||||
@ -332,14 +332,14 @@ func ContainsAny(s, chars string) bool {
|
||||
func CountWords(str string) map[string]int {
|
||||
m := make(map[string]int)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for _, rune := range []rune(str) {
|
||||
if unicode.IsSpace(rune) {
|
||||
for _, r := range []rune(str) {
|
||||
if unicode.IsSpace(r) {
|
||||
if buffer.Len() > 0 {
|
||||
m[buffer.String()]++
|
||||
buffer.Reset()
|
||||
}
|
||||
} else {
|
||||
buffer.WriteRune(rune)
|
||||
buffer.WriteRune(r)
|
||||
}
|
||||
}
|
||||
if buffer.Len() > 0 {
|
||||
@ -349,19 +349,17 @@ func CountWords(str string) map[string]int {
|
||||
}
|
||||
|
||||
// CountChars returns information about chars' count used in a string.
|
||||
//
|
||||
// 返回字符串中字符的使用情况。
|
||||
func CountChars(str string, noSpace...bool) map[string]int {
|
||||
m := make(map[string]int)
|
||||
countSpace := true
|
||||
if len(noSpace) > 0 && noSpace[0] {
|
||||
countSpace = false
|
||||
}
|
||||
for _, rune := range []rune(str) {
|
||||
if !countSpace && unicode.IsSpace(rune) {
|
||||
for _, r := range []rune(str) {
|
||||
if !countSpace && unicode.IsSpace(r) {
|
||||
continue
|
||||
}
|
||||
m[string(rune)]++
|
||||
m[string(r)]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
@ -484,7 +482,7 @@ func Implode(glue string, pieces []string) string {
|
||||
|
||||
// Chr return the ascii string of a number(0-255).
|
||||
func Chr(ascii int) string {
|
||||
return string(ascii)
|
||||
return string([]byte{byte(ascii%256)})
|
||||
}
|
||||
|
||||
// Ord converts the first byte of a string to a value between 0 and 255.
|
||||
|
||||
@ -39,7 +39,7 @@ func Export(i...interface{}) string {
|
||||
// 这里强制对所有map进行反射处理转换
|
||||
refValue := reflect.ValueOf(v)
|
||||
if refValue.Kind() == reflect.Map {
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]interface{})
|
||||
keys := refValue.MapKeys()
|
||||
for _, k := range keys {
|
||||
m[gconv.String(k.Interface())] = refValue.MapIndex(k).Interface()
|
||||
@ -62,7 +62,7 @@ func Export(i...interface{}) string {
|
||||
func PrintBacktrace() {
|
||||
index := 1
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for i := 0; i < 10000; i++ {
|
||||
for i := 1; i < 10000; i++ {
|
||||
if _, path, line, ok := runtime.Caller(i); ok {
|
||||
buffer.WriteString(fmt.Sprintf(`%d. %s:%d%s`, index, path, line, "\n"))
|
||||
index++
|
||||
|
||||
@ -7,13 +7,13 @@ import (
|
||||
|
||||
func main() {
|
||||
// 创建一个非并发安全的集合对象
|
||||
s := gset.New(false)
|
||||
s := gset.New(true)
|
||||
|
||||
// 添加数据项
|
||||
s.Add(1)
|
||||
|
||||
// 批量添加数据项
|
||||
s.BatchAdd([]interface{}{1, 2, 3})
|
||||
s.Add([]interface{}{1, 2, 3}...)
|
||||
|
||||
// 集合数据项大小
|
||||
fmt.Println(s.Size())
|
||||
22
geg/container/gset/gset2.go
Normal file
22
geg/container/gset/gset2.go
Normal file
@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/container/gset"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s1 := gset.NewFrom(g.Slice{1, 2, 3})
|
||||
s2 := gset.NewFrom(g.Slice{4, 5, 6})
|
||||
s3 := gset.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7})
|
||||
|
||||
// 交集
|
||||
fmt.Println(s3.Intersect(s1).Slice())
|
||||
// 差集
|
||||
fmt.Println(s3.Diff(s1).Slice())
|
||||
// 并集
|
||||
fmt.Println(s1.Union(s2).Slice())
|
||||
// 补集
|
||||
fmt.Println(s1.Complement(s3).Slice())
|
||||
}
|
||||
@ -10,7 +10,10 @@ func main() {
|
||||
// 开启调试模式,以便于记录所有执行的SQL
|
||||
db.SetDebug(true)
|
||||
|
||||
r, _ := db.Table("test").Where("id IN (?)", []interface{}{1, 2}).All()
|
||||
r, e := db.Table("test").Where("id IN (?)", []interface{}{1, 2}).All()
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
if r != nil {
|
||||
fmt.Println(r.ToList())
|
||||
}
|
||||
|
||||
@ -6,13 +6,12 @@ import (
|
||||
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.EnableAdmin()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Writeln("您可以同时通过HTTP和HTTPS方式看到该内容!")
|
||||
})
|
||||
s.EnableHTTPS("./server.crt", "./server.key")
|
||||
s.SetHTTPSPort(8198, 8199)
|
||||
s.SetPort(8200, 8300)
|
||||
s.SetHTTPSPort(8100, 8200)
|
||||
s.SetPort(8300, 8400)
|
||||
s.EnableAdmin()
|
||||
s.Run()
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ func main() {
|
||||
//g.REST("/obj/rest", obj)
|
||||
|
||||
// 分组路由批量注册
|
||||
s.Group("/api").Bind("/api", []ghttp.GroupItem{
|
||||
s.Group("/api").Bind([]ghttp.GroupItem{
|
||||
|
||||
{"ALL", "/handler", Handler},
|
||||
{"ALL", "/ctl", ctl},
|
||||
|
||||
16
geg/net/ghttp/server/template/build-in/objects/objects.go
Normal file
16
geg/net/ghttp/server/template/build-in/objects/objects.go
Normal file
@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
content := `{{.Request.Get "name"}}`
|
||||
r.Response.WriteTplContent(content)
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
@ -10,8 +10,13 @@ func main() {
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Cookie.Set("theme", "default")
|
||||
r.Session.Set("name", "john")
|
||||
content := `Config:{{.Config.redis.cache}}, Cookie:{{.Cookie.theme}}, Session:{{.Session.name}}`
|
||||
r.Response.WriteTplContent(content, nil)
|
||||
content := `
|
||||
Get: {{.Get.name}}
|
||||
Post: {{.Post.name}}
|
||||
Config: {{.Config.redis}}
|
||||
Cookie: {{.Cookie.theme}},
|
||||
Session: {{.Session.name}}`
|
||||
r.Response.WriteTplContent(content)
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/frame/gmvc"
|
||||
"github.com/gogf/gf/g/net/ghttp"
|
||||
)
|
||||
@ -11,7 +12,7 @@ type ControllerIndex struct {
|
||||
|
||||
func (c *ControllerIndex) Info() {
|
||||
c.View.Assign("title", "Go Frame 第一个网站")
|
||||
c.View.Assigns(map[string]interface{}{
|
||||
c.View.Assigns(g.Map{
|
||||
"name": "很开心1",
|
||||
"score": 100,
|
||||
})
|
||||
|
||||
@ -14,9 +14,6 @@ func main() {
|
||||
s.SetAccessLogEnabled(true)
|
||||
s.SetPort(2333)
|
||||
|
||||
v := g.View()
|
||||
v.AddPath("template")
|
||||
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
content, _ := gins.View().Parse("test.html", nil)
|
||||
r.Response.Write(content)
|
||||
|
||||
38
geg/net/gtcp/pkg_operations/common/funcs/funcs.go
Normal file
38
geg/net/gtcp/pkg_operations/common/funcs/funcs.go
Normal file
@ -0,0 +1,38 @@
|
||||
package funcs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/net/gtcp"
|
||||
"github.com/gogf/gf/geg/net/gtcp/pkg_operations/common/types"
|
||||
)
|
||||
|
||||
// 自定义格式发送消息包
|
||||
func SendPkg(conn *gtcp.Conn, act string, data...string) error {
|
||||
s := ""
|
||||
if len(data) > 0 {
|
||||
s = data[0]
|
||||
}
|
||||
msg, err := json.Marshal(types.Msg{
|
||||
Act : act,
|
||||
Data : s,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return conn.SendPkg(msg)
|
||||
}
|
||||
|
||||
// 自定义格式接收消息包
|
||||
func RecvPkg(conn *gtcp.Conn) (msg *types.Msg, err error) {
|
||||
if data, err := conn.RecvPkg(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
msg = &types.Msg{}
|
||||
err = json.Unmarshal(data, msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid package structure: %s", err.Error())
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user