feat(template): add global Version property

This commit is contained in:
Jan De Dobbeleer 2025-02-26 14:54:45 +01:00 committed by Jan De Dobbeleer
parent 85a86be703
commit 93649abc21
8 changed files with 107 additions and 70 deletions

7
.vscode/launch.json vendored
View file

@ -10,10 +10,13 @@
"args": [ "args": [
"prompt", "prompt",
"print", "print",
"primary", "right",
"--shell=pwsh", "--shell=pwsh",
"--terminal-width=200" "--terminal-width=200"
] ],
"env": {
"POSH_THEME": "C:\\Users\\jande\\.posh.omp.jsonc"
}
}, },
{ {
"name": "Tooltip", "name": "Tooltip",

11
src/cache/template.go vendored
View file

@ -8,19 +8,20 @@ type Template struct {
SegmentsCache maps.Simple SegmentsCache maps.Simple
Segments *maps.Concurrent Segments *maps.Concurrent
Var maps.Simple Var maps.Simple
ShellVersion string PWD string
AbsolutePWD string Folder string
PSWD string PSWD string
UserName string UserName string
HostName string HostName string
PWD string ShellVersion string
Shell string Shell string
Folder string AbsolutePWD string
OS string OS string
Code int Version string
PromptCount int PromptCount int
SHLVL int SHLVL int
Jobs int Jobs int
Code int
WSL bool WSL bool
Root bool Root bool
} }

View file

@ -7,10 +7,9 @@ import (
type Battery struct { type Battery struct {
base base
*battery.Info
Error string Error string
Icon string Icon string
battery.Info
} }
const ( const (
@ -34,15 +33,16 @@ func (b *Battery) Enabled() bool {
return false return false
} }
var err error info, err := b.env.BatteryState()
b.Info, err = b.env.BatteryState()
if !b.enabledWhileError(err) { if !b.enabledWhileError(err) {
return false return false
} }
b.Info = *info
// case on computer without batteries(no error, empty array) // case on computer without batteries(no error, empty array)
if err == nil && b.Info == nil { if err == nil && b.Info.Percentage == 0 {
return false return false
} }

View file

@ -6,6 +6,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/jandedobbeleer/oh-my-posh/src/build"
"github.com/jandedobbeleer/oh-my-posh/src/cache" "github.com/jandedobbeleer/oh-my-posh/src/cache"
"github.com/jandedobbeleer/oh-my-posh/src/log" "github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/maps" "github.com/jandedobbeleer/oh-my-posh/src/maps"
@ -36,6 +37,7 @@ func loadCache(vars maps.Simple) {
Cache.PromptCount = env.Flags().PromptCount Cache.PromptCount = env.Flags().PromptCount
Cache.Var = make(map[string]any) Cache.Var = make(map[string]any)
Cache.Jobs = env.Flags().JobCount Cache.Jobs = env.Flags().JobCount
Cache.Version = build.Version
if vars != nil { if vars != nil {
Cache.Var = vars Cache.Var = vars

View file

@ -19,14 +19,15 @@ const (
) )
var ( var (
shell string shell string
env runtime.Environment env runtime.Environment
knownVariables []string knownFields *maps.Concurrent
) )
func Init(environment runtime.Environment, vars maps.Simple) { func Init(environment runtime.Environment, vars maps.Simple) {
env = environment env = environment
shell = env.Shell() shell = env.Shell()
knownFields = maps.NewConcurrent()
renderPool = sync.Pool{ renderPool = sync.Pool{
New: func() any { New: func() any {
@ -34,29 +35,6 @@ func Init(environment runtime.Environment, vars maps.Simple) {
}, },
} }
knownVariables = []string{
"Root",
"PWD",
"AbsolutePWD",
"PSWD",
"Folder",
"Shell",
"ShellVersion",
"UserName",
"HostName",
"Code",
"Env",
"OS",
"WSL",
"PromptCount",
"Segments",
"SHLVL",
"Templates",
"Var",
"Data",
"Jobs",
}
if Cache != nil { if Cache != nil {
return return
} }

View file

@ -7,7 +7,6 @@ import (
"time" "time"
"github.com/jandedobbeleer/oh-my-posh/src/log" "github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/regex"
) )
type Text struct { type Text struct {
@ -31,29 +30,6 @@ func (t *Text) Render() (string, error) {
} }
func (t *Text) patchTemplate() { func (t *Text) patchTemplate() {
isKnownVariable := func(variable string) bool {
variable = strings.TrimPrefix(variable, ".")
splitted := strings.Split(variable, ".")
if len(splitted) == 0 {
return true
}
variable = splitted[0]
// check if alphanumeric
if !regex.MatchString(`^[a-zA-Z0-9]+$`, variable) {
return true
}
for _, b := range knownVariables {
if variable == b {
return true
}
}
return false
}
fields := make(fields) fields := make(fields)
fields.init(t.Context) fields.init(t.Context)
@ -70,10 +46,12 @@ func (t *Text) patchTemplate() {
inTemplate = false inTemplate = false
} }
} }
if !inTemplate { if !inTemplate {
result += string(char) result += string(char)
continue continue
} }
switch char { switch char {
case '.': case '.':
var lastChar rune var lastChar rune
@ -96,9 +74,6 @@ func (t *Text) patchTemplate() {
} }
switch { switch {
case !isKnownVariable(property):
// end of a variable, needs to be appended
result += ".Data" + property
case strings.HasPrefix(property, ".Segments") && !strings.HasSuffix(property, ".Contains"): case strings.HasPrefix(property, ".Segments") && !strings.HasSuffix(property, ".Contains"):
// as we can't provide a clean way to access the list // as we can't provide a clean way to access the list
// of segments, we need to replace the property with // of segments, we need to replace the property with
@ -149,10 +124,48 @@ func (f *fields) init(data any) {
val := reflect.TypeOf(data) val := reflect.TypeOf(data)
switch val.Kind() { //nolint:exhaustive switch val.Kind() { //nolint:exhaustive
case reflect.Struct: case reflect.Struct:
name := val.Name()
// ignore the base struct
if name == "base" {
return
}
// check if we already know the fields of this struct
if kf, OK := knownFields.Get(name); OK {
for key := range kf.(fields) {
(*f)[key] = true
}
return
}
// Get struct fields and check embedded types
fieldsNum := val.NumField() fieldsNum := val.NumField()
for i := 0; i < fieldsNum; i++ { for i := 0; i < fieldsNum; i++ {
(*f)[val.Field(i).Name] = true field := val.Field(i)
(*f)[field.Name] = true
// If this is an embedded field, get its methods too
if !field.Anonymous {
continue
}
embeddedType := field.Type
// Recursively check if the embedded type is also a struct
if embeddedType.Kind() == reflect.Struct {
f.init(reflect.New(embeddedType).Elem().Interface())
}
} }
// Get pointer methods
ptrType := reflect.PointerTo(val)
methodsNum := ptrType.NumMethod()
for i := 0; i < methodsNum; i++ {
(*f)[ptrType.Method(i).Name] = true
}
knownFields.Set(name, *f)
case reflect.Map: case reflect.Map:
m, ok := data.(map[string]any) m, ok := data.(map[string]any)
if !ok { if !ok {
@ -168,6 +181,11 @@ func (f *fields) init(data any) {
func (f fields) hasField(field string) bool { func (f fields) hasField(field string) bool {
field = strings.TrimPrefix(field, ".") field = strings.TrimPrefix(field, ".")
// get the first part of the field
splitted := strings.Split(field, ".")
field = splitted[0]
_, ok := f[field] _, ok := f[field]
return ok return ok
} }

View file

@ -14,6 +14,7 @@ func TestRenderTemplate(t *testing.T) {
type Me struct { type Me struct {
Name string Name string
} }
cases := []struct { cases := []struct {
Context any Context any
Case string Case string
@ -161,6 +162,11 @@ func TestRenderTemplate(t *testing.T) {
Context: tc.Context, Context: tc.Context,
} }
env := new(mock.Environment)
env.On("Shell").Return("foo")
Cache = new(cache.Template)
Init(env, nil)
text, err := tmpl.Render() text, err := tmpl.Render()
if tc.ShouldError { if tc.ShouldError {
assert.Error(t, err) assert.Error(t, err)
@ -204,7 +210,7 @@ func TestRenderTemplateEnvVar(t *testing.T) {
}, },
{Case: "no env var", Expected: "hello world", Template: "{{.Text}} world", Context: struct{ Text string }{Text: "hello"}}, {Case: "no env var", Expected: "hello world", Template: "{{.Text}} world", Context: struct{ Text string }{Text: "hello"}},
{Case: "map", Expected: "hello world", Template: "{{.Text}} world", Context: map[string]any{"Text": "hello"}}, {Case: "map", Expected: "hello world", Template: "{{.Text}} world", Context: map[string]any{"Text": "hello"}},
{Case: "empty map", Expected: " world", Template: "{{.Text}} world", Context: map[string]string{}}, {Case: "empty map", Expected: " world", Template: "{{.Text}} world", Context: map[string]string{}, ShouldError: true},
{ {
Case: "Struct with duplicate property", Case: "Struct with duplicate property",
Expected: "posh", Expected: "posh",
@ -335,15 +341,22 @@ func TestPatchTemplate(t *testing.T) {
}, },
} }
env := &mock.Environment{} env := new(mock.Environment)
env.On("Shell").Return("foo") env.On("Shell").Return("foo")
Cache = new(cache.Template)
Init(env, nil) Init(env, nil)
for _, tc := range cases { for _, tc := range cases {
tmpl := &Text{ tmpl := &Text{
Template: tc.Template, Template: tc.Template,
Context: map[string]any{"OS": "posh"}, Context: map[string]any{
"OS": true,
"World": true,
"WorldTrend": "chaos",
"Working": true,
"Staging": true,
"CPU": true,
},
} }
tmpl.patchTemplate() tmpl.patchTemplate()
@ -351,6 +364,27 @@ func TestPatchTemplate(t *testing.T) {
} }
} }
type Foo struct{}
func (f *Foo) Hello() string {
return "hello"
}
func TestPatchTemplateStruct(t *testing.T) {
env := new(mock.Environment)
env.On("Shell").Return("foo")
Cache = new(cache.Template)
Init(env, nil)
tmpl := &Text{
Template: "{{ .Hello }}",
Context: Foo{},
}
tmpl.patchTemplate()
assert.Equal(t, "{{ .Data.Hello }}", tmpl.Template)
}
func TestSegmentContains(t *testing.T) { func TestSegmentContains(t *testing.T) {
cases := []struct { cases := []struct {
Case string Case string

View file

@ -35,6 +35,7 @@ it with `.$` to reference it directly.
| `.WSL` | `boolean` | in WSL yes/no | | `.WSL` | `boolean` | in WSL yes/no |
| `.Templates` | `string` | the [templates][templates] result | | `.Templates` | `string` | the [templates][templates] result |
| `.PromptCount` | `int` | the prompt counter, increments with 1 for every prompt invocation | | `.PromptCount` | `int` | the prompt counter, increments with 1 for every prompt invocation |
| `.Version` | `string` | the Oh My Posh version |
## Environment variables ## Environment variables