feat: Style.Transform for altering strings at render time (#232)

* feat: Style.Transform for altering strings at render time

* feat: add `UnsetTransform`

---------

Co-authored-by: Maas Lalani <maas@lalani.dev>
This commit is contained in:
Christian Rocha 2023-11-27 14:33:38 -05:00 committed by GitHub
parent 59fcf6fa80
commit d354842a40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 0 deletions

17
get.go
View File

@ -408,6 +408,12 @@ func (s Style) GetFrameSize() (x, y int) {
return s.GetHorizontalFrameSize(), s.GetVerticalFrameSize()
}
// GetTransform returns the transform set on the style. If no transform is set
// nil is returned.
func (s Style) GetTransform() func(string) string {
return s.getAsTransform(transformKey)
}
// Returns whether or not the given property is set.
func (s Style) isSet(k propKey) bool {
_, exists := s.rules[k]
@ -469,6 +475,17 @@ func (s Style) getBorderStyle() Border {
return noBorder
}
func (s Style) getAsTransform(k propKey) func(string) string {
v, ok := s.rules[k]
if !ok {
return nil
}
if fn, ok := v.(func(string) string); ok {
return fn
}
return nil
}
// Split a string into lines, additionally returning the size of the widest
// line.
func getLines(s string) (lines []string, widest int) {

12
set.go
View File

@ -544,6 +544,18 @@ func (s Style) StrikethroughSpaces(v bool) Style {
return s
}
// Transform applies a given function to a string at render time, allowing for
// the string being rendered to be manipuated.
//
// Example:
//
// s := NewStyle().Transform(strings.ToUpper)
// fmt.Println(s.Render("raow!") // "RAOW!"
func (s Style) Transform(fn func(string) string) Style {
s.set(transformKey, fn)
return s
}
// Renderer sets the renderer for the style. This is useful for changing the
// renderer for a style that is being used in a different context.
func (s Style) Renderer(r *Renderer) Style {

View File

@ -73,6 +73,8 @@ const (
tabWidthKey
underlineSpacesKey
strikethroughSpacesKey
transformKey
)
// A set of properties.
@ -225,6 +227,8 @@ func (s Style) Render(strs ...string) string {
// Do we need to style spaces separately?
useSpaceStyler = underlineSpaces || strikethroughSpaces
transform = s.getAsTransform(transformKey)
)
if len(s.rules) == 0 {
@ -401,6 +405,10 @@ func (s Style) Render(strs ...string) string {
str = strings.Join(lines[:min(maxHeight, len(lines))], "\n")
}
if transform != nil {
return transform(str)
}
return str
}

View File

@ -3,6 +3,7 @@ package lipgloss
import (
"io"
"reflect"
"strings"
"testing"
"github.com/muesli/termenv"
@ -374,6 +375,42 @@ func TestTabConversion(t *testing.T) {
requireEqual(t, "[\t]", s.Render("[\t]"))
}
func TestStringTransform(t *testing.T) {
for i, tc := range []struct {
input string
fn func(string) string
expected string
}{
{
"raow",
strings.ToUpper,
"RAOW",
},
{
"The quick brown 狐 jumped over the lazy 犬",
func(s string) string {
n := 0
rune := make([]rune, len(s))
for _, r := range s {
rune[n] = r
n++
}
rune = rune[0:n]
for i := 0; i < n/2; i++ {
rune[i], rune[n-1-i] = rune[n-1-i], rune[i]
}
return string(rune)
},
"犬 yzal eht revo depmuj 狐 nworb kciuq ehT",
},
} {
res := NewStyle().Transform(tc.fn).Render(tc.input)
if res != tc.expected {
t.Errorf("Test #%d:\nExpected: %q\nGot: %q", i+1, tc.expected, res)
}
}
}
func BenchmarkStyleRender(b *testing.B) {
s := NewStyle().
Bold(true).

View File

@ -305,6 +305,12 @@ func (s Style) UnsetStrikethroughSpaces() Style {
return s
}
// UnsetTransform removes the value set by Transform.
func (s Style) UnsetTransform() Style {
delete(s.rules, transformKey)
return s
}
// UnsetString sets the underlying string value to the empty string.
func (s Style) UnsetString() Style {
s.value = ""