shithub: hugo

Download patch

ref: 5b0245ca59e22d90add28b11898ecfd602429e43
parent: beb423a2d9683dfc07b2f69766e17c32589d489b
author: Anthony Fok <[email protected]>
date: Mon Mar 23 07:23:13 EDT 2015

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

--- a/tpl/template.go
+++ b/tpl/template.go
@@ -210,6 +210,69 @@
 
 }
 
+// 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 @@
 		"le":          Le,
 		"in":          In,
 		"slicestr":    Slicestr,
+		"substr":      Substr,
 		"split":       Split,
 		"intersect":   Intersect,
 		"isSet":       IsSet,
--- a/tpl/template_test.go
+++ b/tpl/template_test.go
@@ -310,6 +310,46 @@
 	}
 }
 
+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{}