mirror of
https://github.com/gohugoio/hugo.git
synced 2025-03-20 02:31:50 +00:00
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:
parent
beb423a2d9
commit
5b0245ca59
2 changed files with 104 additions and 0 deletions
|
@ -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) {
|
func Split(a interface{}, delimiter string) ([]string, error) {
|
||||||
aStr, err := cast.ToStringE(a)
|
aStr, err := cast.ToStringE(a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1339,6 +1402,7 @@ func init() {
|
||||||
"le": Le,
|
"le": Le,
|
||||||
"in": In,
|
"in": In,
|
||||||
"slicestr": Slicestr,
|
"slicestr": Slicestr,
|
||||||
|
"substr": Substr,
|
||||||
"split": Split,
|
"split": Split,
|
||||||
"intersect": Intersect,
|
"intersect": Intersect,
|
||||||
"isSet": IsSet,
|
"isSet": IsSet,
|
||||||
|
|
|
@ -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) {
|
func TestSplit(t *testing.T) {
|
||||||
for i, this := range []struct {
|
for i, this := range []struct {
|
||||||
v1 interface{}
|
v1 interface{}
|
||||||
|
|
Loading…
Reference in a new issue