mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2026-08-24 10:14:12 -05:00
refactor: drop x/text cases, language and message packages
Three call sites pulled in x/text's casing and message-formatting machinery (with its plural and number-format tables) for ASCII-only work: title-casing segment names and taskwarrior command keys, and comma-grouping a number in the execution time segment. Replace them with text.Title and a local thousands-separator helper, both verified against the exact x/text output for the inputs these paths handle, including the compatibility-sensitive segment template names (nix-shell -> Nix-Shell, ui5tooling -> Ui5tooling). x/text itself stays: the terminal writer's rune width tables use x/text/width by design. Shrinks the stripped linux/amd64 binary by 545 kB. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qiyvpiy5jR2tyzwZ3zUki5
This commit is contained in:
+14
-15
@@ -18,10 +18,9 @@ import (
|
||||
runjobs "github.com/jandedobbeleer/oh-my-posh/src/runtime/jobs"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/segments/options"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/template"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/text"
|
||||
|
||||
"go.yaml.in/yaml/v3"
|
||||
c "golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type SegmentStyle string
|
||||
@@ -178,7 +177,7 @@ func (segment *Segment) Name() string {
|
||||
|
||||
name := segment.Alias
|
||||
if name == "" {
|
||||
name = c.Title(language.English).String(string(segment.Type))
|
||||
name = text.Title(string(segment.Type))
|
||||
}
|
||||
|
||||
segment.name = name
|
||||
@@ -325,11 +324,11 @@ func (segment *Segment) Render(index int, force bool) bool {
|
||||
|
||||
segment.setIndex(index)
|
||||
|
||||
text := segment.string()
|
||||
rendered := segment.string()
|
||||
|
||||
// Only update Enabled if segment is NOT pending (avoid race with Execute goroutine)
|
||||
if !segment.Pending {
|
||||
segment.Enabled = segment.Force || strings.ContainsFunc(text, func(r rune) bool { return r != ' ' })
|
||||
segment.Enabled = segment.Force || strings.ContainsFunc(rendered, func(r rune) bool { return r != ' ' })
|
||||
|
||||
if !segment.Enabled {
|
||||
template.Cache.RemoveSegmentData(segment.Name())
|
||||
@@ -337,7 +336,7 @@ func (segment *Segment) Render(index int, force bool) bool {
|
||||
}
|
||||
}
|
||||
|
||||
segment.SetText(text)
|
||||
segment.SetText(rendered)
|
||||
segment.setCache()
|
||||
|
||||
// We do this to make `.Text` available for a cross-segment reference in an extra prompt.
|
||||
@@ -359,12 +358,12 @@ func (segment *Segment) renderFallback(index int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
text, err := template.RenderTrusted(segment.FallbackTemplate, segment.writer)
|
||||
rendered, err := template.RenderTrusted(segment.FallbackTemplate, segment.writer)
|
||||
if err != nil {
|
||||
text = err.Error()
|
||||
rendered = err.Error()
|
||||
}
|
||||
|
||||
if !strings.ContainsFunc(text, func(r rune) bool { return r != ' ' }) {
|
||||
if !strings.ContainsFunc(rendered, func(r rune) bool { return r != ' ' }) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -375,7 +374,7 @@ func (segment *Segment) renderFallback(index int) bool {
|
||||
|
||||
segment.setIndex(index)
|
||||
segment.Enabled = true
|
||||
segment.SetText(text)
|
||||
segment.SetText(rendered)
|
||||
|
||||
// Intentionally skip setCache(): the writer is zero/partially hydrated
|
||||
// here, and caching it would make restoreCache() later resurrect a
|
||||
@@ -394,13 +393,13 @@ func (segment *Segment) Text() string {
|
||||
return segment.writer.Text()
|
||||
}
|
||||
|
||||
func (segment *Segment) SetText(text string) {
|
||||
func (segment *Segment) SetText(value string) {
|
||||
if segment.writer == nil {
|
||||
segment.text = text
|
||||
segment.text = value
|
||||
return
|
||||
}
|
||||
|
||||
segment.writer.SetText(text)
|
||||
segment.writer.SetText(value)
|
||||
}
|
||||
|
||||
func (segment *Segment) setIndex(index int) {
|
||||
@@ -814,12 +813,12 @@ func (segment *Segment) string() string {
|
||||
segment.Template = segment.writer.Template()
|
||||
}
|
||||
|
||||
text, err := template.RenderTrusted(segment.Template, context)
|
||||
rendered, err := template.RenderTrusted(segment.Template, context)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return text
|
||||
return rendered
|
||||
}
|
||||
|
||||
func (segment *Segment) shouldIncludeFolder() bool {
|
||||
|
||||
@@ -3,10 +3,9 @@ package segments
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/segments/options"
|
||||
lang "golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
)
|
||||
|
||||
type Executiontime struct {
|
||||
@@ -177,6 +176,35 @@ func (t *Executiontime) formatDurationHouston() string {
|
||||
return result
|
||||
}
|
||||
|
||||
// groupThousands renders n with comma thousand separators, matching what
|
||||
// x/text/message's English printer produced for %d.
|
||||
func groupThousands(n int64) string {
|
||||
s := strconv.FormatInt(n, 10)
|
||||
|
||||
start := 0
|
||||
if s[0] == '-' {
|
||||
start = 1
|
||||
}
|
||||
|
||||
if len(s)-start <= 3 {
|
||||
return s
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
first := start + (len(s)-start)%3
|
||||
if first == start {
|
||||
first = start + 3
|
||||
}
|
||||
|
||||
sb.WriteString(s[:first])
|
||||
for i := first; i < len(s); i += 3 {
|
||||
sb.WriteByte(',')
|
||||
sb.WriteString(s[i : i+3])
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (t *Executiontime) formatDurationAmarillo() string {
|
||||
// wholeNumber represents the value to the left of the decimal point (seconds)
|
||||
wholeNumber := t.Ms / second
|
||||
@@ -184,8 +212,7 @@ func (t *Executiontime) formatDurationAmarillo() string {
|
||||
decimalNumber := float64(t.Ms%second) / second
|
||||
|
||||
// format wholeNumber as a string with thousands separators
|
||||
printer := message.NewPrinter(lang.English)
|
||||
result := printer.Sprintf("%d", wholeNumber)
|
||||
result := groupThousands(wholeNumber)
|
||||
|
||||
if decimalNumber > 0 {
|
||||
// format decimalNumber as a string with truncated trailing zeros
|
||||
|
||||
@@ -418,3 +418,23 @@ func TestExecutionTimeFormatISO8601Ms(t *testing.T) {
|
||||
assert.Equal(t, tc.Expected, output, "Input: %s", tc.Input)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGroupThousands(t *testing.T) {
|
||||
cases := map[int64]string{
|
||||
0: "0",
|
||||
1: "1",
|
||||
999: "999",
|
||||
1000: "1,000",
|
||||
12345: "12,345",
|
||||
123456: "123,456",
|
||||
1234567: "1,234,567",
|
||||
1000000000: "1,000,000,000",
|
||||
-999: "-999",
|
||||
-1000: "-1,000",
|
||||
-1234567: "-1,234,567",
|
||||
}
|
||||
|
||||
for input, expected := range cases {
|
||||
assert.Equal(t, expected, groupThousands(input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ package segments
|
||||
import (
|
||||
"strings"
|
||||
|
||||
c "golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/log"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/segments/options"
|
||||
"github.com/jandedobbeleer/oh-my-posh/src/text"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -45,7 +43,7 @@ func (t *Taskwarrior) Enabled() bool {
|
||||
t.Commands = make(map[string]string, len(configuredCommands))
|
||||
|
||||
for name, args := range configuredCommands {
|
||||
key := c.Title(language.English).String(name)
|
||||
key := text.Title(name)
|
||||
t.Commands[key] = t.runCommand(cmd, args)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Title uppercases the first letter of each word and lowercases every other
|
||||
// letter. Words break on any character other than a letter, digit or
|
||||
// underscore, and digits never count as a word's first letter - matching how
|
||||
// x/text's cases.Title(language.English) handled the ASCII identifiers this
|
||||
// replaces it for: "nix-shell" -> "Nix-Shell", "ui5tooling" -> "Ui5tooling",
|
||||
// "foo_bar" -> "Foo_bar".
|
||||
func Title(s string) string {
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(s))
|
||||
|
||||
first := true
|
||||
|
||||
for _, r := range s {
|
||||
switch {
|
||||
case unicode.IsLetter(r):
|
||||
if first {
|
||||
sb.WriteRune(unicode.ToUpper(r))
|
||||
first = false
|
||||
continue
|
||||
}
|
||||
|
||||
sb.WriteRune(unicode.ToLower(r))
|
||||
case unicode.IsDigit(r) || r == '_':
|
||||
sb.WriteRune(r)
|
||||
default:
|
||||
sb.WriteRune(r)
|
||||
first = true
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package text
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTitle(t *testing.T) {
|
||||
// expected values are what x/text's cases.Title(language.English)
|
||||
// produced for the same input before it was replaced
|
||||
cases := map[string]string{
|
||||
"git": "Git",
|
||||
"nix-shell": "Nix-Shell",
|
||||
"ui5tooling": "Ui5tooling",
|
||||
"GIT": "Git",
|
||||
"foo_bar": "Foo_bar",
|
||||
"os": "Os",
|
||||
"context": "Context",
|
||||
"Nice2see": "Nice2see",
|
||||
"hello world": "Hello World",
|
||||
"a-1b": "A-1B",
|
||||
"": "",
|
||||
}
|
||||
|
||||
for input, expected := range cases {
|
||||
assert.Equal(t, expected, Title(input), input)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user