From bb07fa15b7faf09538737e103405246b35ed58c0 Mon Sep 17 00:00:00 2001 From: earncef Date: Thu, 11 Apr 2019 09:23:30 +0200 Subject: [PATCH] text.trim, text.pad_left and text.pad_right (#183) * Added trim, pad_left and pad_right to text * Check MaxStringLen * Added doc * Fixed doc * Moved length check --- docs/stdlib-text.md | 6 +- stdlib/text.go | 131 +++++++++++++++++++++++++++++++++++++++++++- stdlib/text_test.go | 7 +++ 3 files changed, 142 insertions(+), 2 deletions(-) diff --git a/docs/stdlib-text.md b/docs/stdlib-text.md index db7d1b4..eb825f5 100644 --- a/docs/stdlib-text.md +++ b/docs/stdlib-text.md @@ -26,6 +26,7 @@ text := import("text") - `last_index_any(s string, chars string) => int`: returns the index of the last instance of any Unicode code point from chars in s, or -1 if no Unicode code point from chars is present in s. - `repeat(s string, count int) => string`: returns a new string consisting of count copies of the string s. - `replace(s string, old string, new string, n int) => string`: returns a copy of the string s with the first n non-overlapping instances of old replaced by new. +- `substr(s string, lower int, upper int) => string => string`: returns a substring of the string s specified by the lower and upper parameters. - `split(s string, sep string) => [string]`: slices s into all substrings separated by sep and returns a slice of the substrings between those separators. - `split_after(s string, sep string) => [string]`: slices s into all substrings after each instance of sep and returns a slice of those substrings. - `split_after_n(s string, sep string, n int) => [string]`: slices s into substrings after each instance of sep and returns a slice of those substrings. @@ -34,6 +35,9 @@ text := import("text") - `to_lower(s string) => string`: returns a copy of the string s with all Unicode letters mapped to their lower case. - `to_title(s string) => string`: returns a copy of the string s with all Unicode letters mapped to their title case. - `to_upper(s string) => string`: returns a copy of the string s with all Unicode letters mapped to their upper case. +- `pad_left(s string, pad_len int, pad_with string) => string`: returns a copy of the string s padded on the left with the contents of the string pad_with to length pad_len. If pad_with is not specified, white space is used as the default padding. +- `pad_right(s string, pad_len int, pad_with string) => string`: returns a copy of the string s padded on the right with the contents of the string pad_with to length pad_len. If pad_with is not specified, white space is used as the default padding. +- `trim(s string, cutset string) => string`: returns a slice of the string s with all leading and trailing Unicode code points contained in cutset removed. - `trim_left(s string, cutset string) => string`: returns a slice of the string s with all leading Unicode code points contained in cutset removed. - `trim_prefix(s string, prefix string) => string`: returns s without the provided leading prefix string. - `trim_right(s string, cutset string) => string`: returns a slice of the string s, with all trailing Unicode code points contained in cutset removed. @@ -55,4 +59,4 @@ text := import("text") - `match(text string) => bool`: reports whether the string s contains any match of the regular expression pattern. - `find(text string, count int) => [[{text: string, begin: int, end: int}]]/undefined`: returns an array holding all matches, each of which is an array of map object that contains matching text, begin and end (exclusive) index. - `replace(src string, repl string) => string`: returns a copy of src, replacing matches of the pattern with the replacement string repl. -- `split(text string, count int) => [string]`: slices s into substrings separated by the expression and returns a slice of the substrings between those expression matches. \ No newline at end of file +- `split(text string, count int) => [string]`: slices s into substrings separated by the expression and returns a slice of the substrings between those expression matches. diff --git a/stdlib/text.go b/stdlib/text.go index 2e66a5a..4b5729e 100644 --- a/stdlib/text.go +++ b/stdlib/text.go @@ -32,7 +32,7 @@ var textModule = map[string]objects.Object{ "last_index_any": &objects.UserFunction{Name: "last_index_any", Value: FuncASSRI(strings.LastIndexAny)}, // last_index_any(s, chars) => int "repeat": &objects.UserFunction{Name: "repeat", Value: textRepeat}, // repeat(s, count) => string "replace": &objects.UserFunction{Name: "replace", Value: textReplace}, // replace(s, old, new, n) => string - "substr": &objects.UserFunction{Name: "substr", Value: textSubstring}, // substring(s, lower, upper) => string + "substr": &objects.UserFunction{Name: "substr", Value: textSubstring}, // substr(s, lower, upper) => string "split": &objects.UserFunction{Name: "split", Value: FuncASSRSs(strings.Split)}, // split(s, sep) => [string] "split_after": &objects.UserFunction{Name: "split_after", Value: FuncASSRSs(strings.SplitAfter)}, // split_after(s, sep) => [string] "split_after_n": &objects.UserFunction{Name: "split_after_n", Value: FuncASSIRSs(strings.SplitAfterN)}, // split_after_n(s, sep, n) => [string] @@ -41,6 +41,9 @@ var textModule = map[string]objects.Object{ "to_lower": &objects.UserFunction{Name: "to_lower", Value: FuncASRS(strings.ToLower)}, // to_lower(s) => string "to_title": &objects.UserFunction{Name: "to_title", Value: FuncASRS(strings.ToTitle)}, // to_title(s) => string "to_upper": &objects.UserFunction{Name: "to_upper", Value: FuncASRS(strings.ToUpper)}, // to_upper(s) => string + "pad_left": &objects.UserFunction{Name: "pad_left", Value: textPadLeft}, // pad_left(s, pad_len, pad_with) => string + "pad_right": &objects.UserFunction{Name: "pad_right", Value: textPadRight}, // pad_right(s, pad_len, pad_with) => string + "trim": &objects.UserFunction{Name: "trim", Value: FuncASSRS(strings.Trim)}, // trim(s, cutset) => string "trim_left": &objects.UserFunction{Name: "trim_left", Value: FuncASSRS(strings.TrimLeft)}, // trim_left(s, cutset) => string "trim_prefix": &objects.UserFunction{Name: "trim_prefix", Value: FuncASSRS(strings.TrimPrefix)}, // trim_prefix(s, prefix) => string "trim_right": &objects.UserFunction{Name: "trim_right", Value: FuncASSRS(strings.TrimRight)}, // trim_right(s, cutset) => string @@ -440,6 +443,132 @@ func textSubstring(args ...objects.Object) (ret objects.Object, err error) { return } +func textPadLeft(args ...objects.Object) (ret objects.Object, err error) { + argslen := len(args) + if argslen != 2 && argslen != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + if i2 > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + sLen := len(s1) + if sLen >= i2 { + ret = &objects.String{Value: s1} + return + } + + s3 := " " + if argslen == 3 { + s3, ok = objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + padStrLen := len(s3) + if padStrLen == 0 { + ret = &objects.String{Value: s1} + return + } + + padCount := ((i2 - padStrLen) / padStrLen) + 1 + retStr := strings.Repeat(s3, int(padCount)) + s1 + ret = &objects.String{Value: retStr[len(retStr)-i2:]} + + return +} + +func textPadRight(args ...objects.Object) (ret objects.Object, err error) { + argslen := len(args) + if argslen != 2 && argslen != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + if i2 > tengo.MaxStringLen { + return nil, objects.ErrStringLimit + } + + sLen := len(s1) + if sLen >= i2 { + ret = &objects.String{Value: s1} + return + } + + s3 := " " + if argslen == 3 { + s3, ok = objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + padStrLen := len(s3) + if padStrLen == 0 { + ret = &objects.String{Value: s1} + return + } + + padCount := ((i2 - padStrLen) / padStrLen) + 1 + retStr := s1 + strings.Repeat(s3, int(padCount)) + ret = &objects.String{Value: retStr[:i2]} + + return +} + func textRepeat(args ...objects.Object) (ret objects.Object, err error) { if len(args) != 2 { return nil, objects.ErrWrongNumArguments diff --git a/stdlib/text_test.go b/stdlib/text_test.go index fb03460..f8e14ee 100644 --- a/stdlib/text_test.go +++ b/stdlib/text_test.go @@ -263,3 +263,10 @@ func TestSubstr(t *testing.T) { module(t, "text").call("substr", 123, 0, 1).expect("1") module(t, "text").call("substr", 123.456, 4, 7).expect("456") } + +func TestPadLeft(t *testing.T) { + module(t, "text").call("pad_left", "ab", 7, 0).expect("00000ab") + module(t, "text").call("pad_right", "ab", 7, 0).expect("ab00000") + module(t, "text").call("pad_left", "ab", 7, "+-").expect("-+-+-ab") + module(t, "text").call("pad_right", "ab", 7, "+-").expect("ab+-+-+") +}