Implement substr template function

Its behavior is similar to that in JavaScript
with special handling of negative length as found in in PHP.

Fixes #991
This commit is contained in:
Anthony Fok 2015-03-23 11:23:13 -06:00 committed by bep
parent beb423a2d9
commit 5b0245ca59
2 changed files with 104 additions and 0 deletions

View file

@ -210,6 +210,69 @@ func Slicestr(a interface{}, start, end int) (string, error) {
}
// Substr extracts parts of a string, beginning at the character at the specified
// position, and returns the specified number of characters.
//
// It normally takes two parameters: start and length.
// It can also take one parameter: start, i.e. length is omitted, in which case
// the substring starting from start until the end of the string will be returned.
//
// To extract characters from the end of the string, use a negative start number.
//
// In addition, borrowing from the extended behavior described at http://php.net/substr,
// if length is given and is negative, then that many characters will be omitted from
// the end of string.
func Substr(a interface{}, nums ...int) (string, error) {
aStr, err := cast.ToStringE(a)
if err != nil {
return "", err
}
var start, length int
switch len(nums) {
case 1:
start = nums[0]
length = len(aStr)
case 2:
start = nums[0]
length = nums[1]
default:
return "", errors.New("too many arguments")
}
if start < -len(aStr) {
start = 0
}
if start > len(aStr) {
return "", errors.New(fmt.Sprintf("start position out of bounds for %d-byte string", len(aStr)))
}
var s, e int
if start >= 0 && length >= 0 {
s = start
e = start + length
} else if start < 0 && length >= 0 {
s = len(aStr) + start - length + 1
e = len(aStr) + start + 1
} else if start >= 0 && length < 0 {
s = start
e = len(aStr) + length
} else {
s = len(aStr) + start
e = len(aStr) + length
}
if s > e {
return "", errors.New(fmt.Sprintf("calculated start position greater than end position: %d > %d", s, e))
}
if e > len(aStr) {
e = len(aStr)
}
return aStr[s:e], nil
}
func Split(a interface{}, delimiter string) ([]string, error) {
aStr, err := cast.ToStringE(a)
if err != nil {
@ -1339,6 +1402,7 @@ func init() {
"le": Le,
"in": In,
"slicestr": Slicestr,
"substr": Substr,
"split": Split,
"intersect": Intersect,
"isSet": IsSet,

View file

@ -310,6 +310,46 @@ func TestSlicestr(t *testing.T) {
}
}
func TestSubstr(t *testing.T) {
for i, this := range []struct {
v1 interface{}
v2 int
v3 int
expect interface{}
}{
{"abc", 1, 2, "bc"},
{"abc", 0, 1, "a"},
{"abcdef", -1, 2, "ef"},
{"abcdef", -3, 3, "bcd"},
{"abcdef", 0, -1, "abcde"},
{"abcdef", 2, -1, "cde"},
{"abcdef", 4, -4, false},
{"abcdef", 7, 1, false},
{"abcdef", 1, 100, "bcdef"},
{"abcdef", -100, 3, "abc"},
{"abcdef", -3, -1, "de"},
{123, 1, 3, "23"},
{1.2e3, 0, 4, "1200"},
{tstNoStringer{}, 0, 1, false},
} {
result, err := Substr(this.v1, this.v2, this.v3)
if b, ok := this.expect.(bool); ok && !b {
if err == nil {
t.Errorf("[%d] Substr didn't return an expected error", i)
}
} else {
if err != nil {
t.Errorf("[%d] failed: %s", i, err)
continue
}
if !reflect.DeepEqual(result, this.expect) {
t.Errorf("[%d] Got %s but expected %s", i, result, this.expect)
}
}
}
}
func TestSplit(t *testing.T) {
for i, this := range []struct {
v1 interface{}