Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: Style.Transform for altering strings at render time
  • Loading branch information
meowgorithm committed Oct 5, 2023
commit 6300d964fbdb510f32d5366ccfb377ea6c7bd2bb
17 changes: 17 additions & 0 deletions get.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have an UnsetTransform for consistency?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% — thanks for catching this!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 9d6896c

return s.getAsTransform(transformKey)
}

// Returns whether or not the given property is set.
func (s Style) isSet(k propKey) bool {
_, exists := s.rules[k]
Expand Down Expand Up @@ -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) {
Expand Down
12 changes: 12 additions & 0 deletions set.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 8 additions & 0 deletions style.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ const (
tabWidthKey
underlineSpacesKey
strikethroughSpacesKey

transformKey
)

// A set of properties.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down
37 changes: 37 additions & 0 deletions style_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package lipgloss
import (
"io"
"reflect"
"strings"
"testing"

"github.com/muesli/termenv"
Expand Down Expand Up @@ -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).
Expand Down