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": [
"prompt",
"print",
"primary",
"right",
"--shell=pwsh",
"--terminal-width=200"
]
],
"env": {
"POSH_THEME": "C:\\Users\\jande\\.posh.omp.jsonc"
}
},
{
"name": "Tooltip",

11
src/cache/template.go vendored
View file

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

View file

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

View file

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

View file

@ -19,14 +19,15 @@ const (
)
var (
shell string
env runtime.Environment
knownVariables []string
shell string
env runtime.Environment
knownFields *maps.Concurrent
)
func Init(environment runtime.Environment, vars maps.Simple) {
env = environment
shell = env.Shell()
knownFields = maps.NewConcurrent()
renderPool = sync.Pool{
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 {
return
}

View file

@ -7,7 +7,6 @@ import (
"time"
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/regex"
)
type Text struct {
@ -31,29 +30,6 @@ func (t *Text) Render() (string, error) {
}
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.init(t.Context)
@ -70,10 +46,12 @@ func (t *Text) patchTemplate() {
inTemplate = false
}
}
if !inTemplate {
result += string(char)
continue
}
switch char {
case '.':
var lastChar rune
@ -96,9 +74,6 @@ func (t *Text) patchTemplate() {
}
switch {
case !isKnownVariable(property):
// end of a variable, needs to be appended
result += ".Data" + property
case strings.HasPrefix(property, ".Segments") && !strings.HasSuffix(property, ".Contains"):
// as we can't provide a clean way to access the list
// of segments, we need to replace the property with
@ -149,10 +124,48 @@ func (f *fields) init(data any) {
val := reflect.TypeOf(data)
switch val.Kind() { //nolint:exhaustive
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()
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:
m, ok := data.(map[string]any)
if !ok {
@ -168,6 +181,11 @@ func (f *fields) init(data any) {
func (f fields) hasField(field string) bool {
field = strings.TrimPrefix(field, ".")
// get the first part of the field
splitted := strings.Split(field, ".")
field = splitted[0]
_, ok := f[field]
return ok
}

View file

@ -14,6 +14,7 @@ func TestRenderTemplate(t *testing.T) {
type Me struct {
Name string
}
cases := []struct {
Context any
Case string
@ -161,6 +162,11 @@ func TestRenderTemplate(t *testing.T) {
Context: tc.Context,
}
env := new(mock.Environment)
env.On("Shell").Return("foo")
Cache = new(cache.Template)
Init(env, nil)
text, err := tmpl.Render()
if tc.ShouldError {
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: "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",
Expected: "posh",
@ -335,15 +341,22 @@ func TestPatchTemplate(t *testing.T) {
},
}
env := &mock.Environment{}
env := new(mock.Environment)
env.On("Shell").Return("foo")
Cache = new(cache.Template)
Init(env, nil)
for _, tc := range cases {
tmpl := &Text{
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()
@ -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) {
cases := []struct {
Case string

View file

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