diff --git a/docs/lexicon.html b/docs/lexicon.html index 7a2ecf710..ba838f200 100644 --- a/docs/lexicon.html +++ b/docs/lexicon.html @@ -411,6 +411,32 @@

- Replaces named parameters in the string. +
  • + + ljust(width[, + fillchar]) + - returns a copy of this string left-padded with the character given + in fillchar (or the default ' ' if no value is given) until it is width characters long. + +
  • +
  • + + rjust(width[, + fillchar]) + - returns a copy of this string right-padded with the character given + in fillchar (or the default ' ' if no value is given) until it is width characters long. + +
  • bool: pass def format(self:str) -> str: pass +def ljust(self:str, width:int, fillchar:str=' ') -> str: + pass +def rjust(self:str, width:int, fillchar:str=' ') -> str: + pass def lstrip(self:str, cutset:str=' \n') -> str: pass def rstrip(self:str, cutset:str=' \n') -> str: diff --git a/src/parse/asp/builtins.go b/src/parse/asp/builtins.go index 353478b07..67f3d5cea 100644 --- a/src/parse/asp/builtins.go +++ b/src/parse/asp/builtins.go @@ -14,6 +14,7 @@ import ( "strings" "sync" "unicode" + "unicode/utf8" "github.com/Masterminds/semver/v3" "github.com/manifoldco/promptui" @@ -88,6 +89,8 @@ func registerBuiltins(s *scope) { "rpartition": setNativeCode(s, "rpartition", strRPartition), "startswith": setNativeCode(s, "startswith", strStartsWith), "endswith": setNativeCode(s, "endswith", strEndsWith), + "ljust": setNativeCode(s, "ljust", strLJust), + "rjust": setNativeCode(s, "rjust", strRJust), "lstrip": setNativeCode(s, "lstrip", strLStrip), "rstrip": setNativeCode(s, "rstrip", strRStrip), "removeprefix": setNativeCode(s, "removeprefix", strRemovePrefix), @@ -542,6 +545,35 @@ func strEndsWith(s *scope, args []pyObject) pyObject { return newPyBool(strings.HasSuffix(string(self), string(x))) } +func strLJust(s *scope, args []pyObject) pyObject { + return strJust(s, args, true) +} + +func strRJust(s *scope, args []pyObject) pyObject { + return strJust(s, args, false) +} + +func strJust(s *scope, args []pyObject, left bool) pyObject { + self := string(args[0].(pyString)) + width := int(args[1].(pyInt)) + fillchar := string(args[2].(pyString)) + + // utf8.RuneCountInString rather than objLen, because objLen returns the length of a string in + // bytes rather than characters: + s.Assert(utf8.RuneCountInString(fillchar) == 1, "fillchar must be exactly one character long") + + count := width - utf8.RuneCountInString(self) + // strings.Repeat's second operand must be non-negative, otherwise it panics. In that case, no + // changes need to be made to the input string anyway, so just return a copy of it. + if count <= 0 { + return pyString(self) + } + if left { + return pyString(strings.Repeat(fillchar, count) + self) + } + return pyString(self + strings.Repeat(fillchar, count)) +} + func strLStrip(s *scope, args []pyObject) pyObject { self := args[0].(pyString) cutset := args[1].(pyString) diff --git a/src/parse/asp/interpreter_test.go b/src/parse/asp/interpreter_test.go index 75c18f76c..aed64561c 100644 --- a/src/parse/asp/interpreter_test.go +++ b/src/parse/asp/interpreter_test.go @@ -730,3 +730,43 @@ func TestBreakLoop(t *testing.T) { assert.NoError(t, err) assert.EqualValues(t, pyString("ok"), s.Lookup("x")) } + +func TestStrLjust(t *testing.T) { + s, err := parseFile("src/parse/asp/test_data/interpreter/str/ljust.build") + assert.NoError(t, err) + assert.EqualValues(t, pyString(" kittens"), s.Lookup("s1")) + assert.EqualValues(t, pyString("+++kittens"), s.Lookup("s2")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s3")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s4")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s5")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s6")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s7")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s8")) + assert.EqualValues(t, pyString("£££kittens"), s.Lookup("s9")) + assert.EqualValues(t, pyString("++++££kittens££"), s.Lookup("s10")) + // Make sure the original string wasn't modified: + assert.EqualValues(t, pyString("kittens"), s.Lookup("orig")) + + _, err = parseFile("src/parse/asp/test_data/interpreter/str/ljust_multiple_fillchars.build") + assert.Error(t, err, "fillchar must be exactly one character long") +} + +func TestStrRjust(t *testing.T) { + s, err := parseFile("src/parse/asp/test_data/interpreter/str/rjust.build") + assert.NoError(t, err) + assert.EqualValues(t, pyString("kittens "), s.Lookup("s1")) + assert.EqualValues(t, pyString("kittens+++"), s.Lookup("s2")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s3")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s4")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s5")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s6")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s7")) + assert.EqualValues(t, pyString("kittens"), s.Lookup("s8")) + assert.EqualValues(t, pyString("kittens£££"), s.Lookup("s9")) + assert.EqualValues(t, pyString("££kittens££++++"), s.Lookup("s10")) + // Make sure the original string wasn't modified: + assert.EqualValues(t, pyString("kittens"), s.Lookup("orig")) + + _, err = parseFile("src/parse/asp/test_data/interpreter/str/rjust_multiple_fillchars.build") + assert.Error(t, err, "fillchar must be exactly one character long") +} diff --git a/src/parse/asp/test_data/interpreter/str/ljust.build b/src/parse/asp/test_data/interpreter/str/ljust.build new file mode 100644 index 000000000..1c6e84b81 --- /dev/null +++ b/src/parse/asp/test_data/interpreter/str/ljust.build @@ -0,0 +1,16 @@ +orig = "kittens" + +s1 = orig.ljust(10) +s2 = orig.ljust(10, "+") +s3 = orig.ljust(len(orig)) +s4 = orig.ljust(len(orig), "+") +s5 = orig.ljust(0) +s6 = orig.ljust(0, "+") +s7 = orig.ljust(-5) +s8 = orig.ljust(-5, "+") + +# 1-character, 2-byte fillchar: +s9 = orig.ljust(10, "£") + +# Input string containing a multi-byte character: +s10 = "££kittens££".ljust(15, "+") diff --git a/src/parse/asp/test_data/interpreter/str/ljust_multiple_fillchars.build b/src/parse/asp/test_data/interpreter/str/ljust_multiple_fillchars.build new file mode 100644 index 000000000..982ae11ba --- /dev/null +++ b/src/parse/asp/test_data/interpreter/str/ljust_multiple_fillchars.build @@ -0,0 +1 @@ +s = "kittens".ljust(10, "!!") diff --git a/src/parse/asp/test_data/interpreter/str/rjust.build b/src/parse/asp/test_data/interpreter/str/rjust.build new file mode 100644 index 000000000..871d25861 --- /dev/null +++ b/src/parse/asp/test_data/interpreter/str/rjust.build @@ -0,0 +1,16 @@ +orig = "kittens" + +s1 = orig.rjust(10) +s2 = orig.rjust(10, "+") +s3 = orig.rjust(len(orig)) +s4 = orig.rjust(len(orig), "+") +s5 = orig.rjust(0) +s6 = orig.rjust(0, "+") +s7 = orig.rjust(-5) +s8 = orig.rjust(-5, "+") + +# 1-character, 2-byte fillchar: +s9 = orig.rjust(10, "£") + +# Input string containing a multi-byte character: +s10 = "££kittens££".rjust(15, "+") diff --git a/src/parse/asp/test_data/interpreter/str/rjust_multiple_fillchars.build b/src/parse/asp/test_data/interpreter/str/rjust_multiple_fillchars.build new file mode 100644 index 000000000..678f6bb0b --- /dev/null +++ b/src/parse/asp/test_data/interpreter/str/rjust_multiple_fillchars.build @@ -0,0 +1 @@ +s = "kittens".rjust(10, "!!")