From e5dd07fb9aed0e1a89b181a13870014d72711e98 Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Tue, 18 Jan 2022 09:48:47 +0100 Subject: [PATCH] feat: cache template environment data --- src/console_title_test.go | 32 ++++++++++------- src/environment.go | 69 +++++++++++++++++++++++-------------- src/environment_unix.go | 3 ++ src/main.go | 11 +----- src/main_test.go | 6 +++- src/segment_battery_test.go | 3 ++ src/segment_exit_test.go | 3 ++ src/segment_git_test.go | 3 ++ src/segment_os.go | 12 +++---- src/segment_os_test.go | 14 ++++---- src/segment_path_test.go | 45 ++++++------------------ src/segment_plastic_test.go | 3 ++ src/segment_session_test.go | 12 +++++-- src/segment_test.go | 3 ++ src/segment_text_test.go | 20 ++++++----- src/template.go | 37 +++++--------------- src/template_test.go | 7 ++-- 17 files changed, 142 insertions(+), 141 deletions(-) diff --git a/src/console_title_test.go b/src/console_title_test.go index 34708d28..711d94db 100644 --- a/src/console_title_test.go +++ b/src/console_title_test.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -57,11 +56,17 @@ func TestGetConsoleTitle(t *testing.T) { env.On("getcwd").Return(tc.Cwd) env.On("homeDir").Return("/usr/home") env.On("getPathSeperator").Return(tc.PathSeperator) - env.On("isRunningAsRoot").Return(tc.Root) - env.On("getShellName").Return(tc.ShellName) - env.On("getenv", "USERDOMAIN").Return("MyCompany") - env.On("getCurrentUser").Return("MyUser") - env.On("getHostName").Return("MyHost", nil) + env.On("templateCache").Return(&templateCache{ + Env: map[string]string{ + "USERDOMAIN": "MyCompany", + }, + Shell: tc.ShellName, + UserName: "MyUser", + Root: tc.Root, + HostName: "MyHost", + PWD: tc.Cwd, + Folder: base(tc.Cwd, env), + }) env.onTemplate() ansi := &ansiUtils{} ansi.init(tc.ShellName) @@ -112,12 +117,15 @@ func TestGetConsoleTitleIfGethostnameReturnsError(t *testing.T) { env := new(MockedEnvironment) env.On("getcwd").Return(tc.Cwd) env.On("homeDir").Return("/usr/home") - env.On("getPathSeperator").Return(tc.PathSeperator) - env.On("isRunningAsRoot").Return(tc.Root) - env.On("getShellName").Return(tc.ShellName) - env.On("getenv", "USERDOMAIN").Return("MyCompany") - env.On("getCurrentUser").Return("MyUser") - env.On("getHostName").Return("", fmt.Errorf("I have a bad feeling about this")) + env.On("templateCache").Return(&templateCache{ + Env: map[string]string{ + "USERDOMAIN": "MyCompany", + }, + Shell: tc.ShellName, + UserName: "MyUser", + Root: tc.Root, + HostName: "", + }) env.onTemplate() ansi := &ansiUtils{} ansi.init(tc.ShellName) diff --git a/src/environment.go b/src/environment.go index e18e2fde..cb4ee4ea 100644 --- a/src/environment.go +++ b/src/environment.go @@ -90,7 +90,6 @@ type wifiInfo struct { type Environment interface { getenv(key string) string - environ() map[string]string getcwd() string homeDir() string hasFiles(pattern string) bool @@ -128,6 +127,7 @@ type Environment interface { convertToLinuxPath(path string) string convertToWindowsPath(path string) string getWifiNetwork() (*wifiInfo, error) + templateCache() *templateCache } type commandCache struct { @@ -155,13 +155,13 @@ const ( ) type environment struct { - args *args - cwd string - cmdCache *commandCache - fileCache *fileCache - environCache map[string]string - logBuilder strings.Builder - debug bool + args *args + cwd string + cmdCache *commandCache + fileCache *fileCache + tmplCache *templateCache + logBuilder strings.Builder + debug bool } func (env *environment) init(args *args) { @@ -176,18 +176,6 @@ func (env *environment) init(args *args) { } env.fileCache = &fileCache{} env.fileCache.init(env.getCachePath()) - env.environCache = make(map[string]string) - const separator = "=" - values := os.Environ() - for value := range values { - splitted := strings.Split(values[value], separator) - if len(splitted) != 2 { - continue - } - key := splitted[0] - val := splitted[1:] - env.environCache[key] = strings.Join(val, separator) - } } func (env *environment) resolveConfigPath() { @@ -236,11 +224,6 @@ func (env *environment) getenv(key string) string { return val } -func (env *environment) environ() map[string]string { - defer env.trace(time.Now(), "environ") - return env.environCache -} - func (env *environment) getcwd() string { defer env.trace(time.Now(), "getcwd") if env.cwd != "" { @@ -563,6 +546,42 @@ func (env *environment) logs() string { return env.logBuilder.String() } +func (env *environment) templateCache() *templateCache { + defer env.trace(time.Now(), "templateCache") + if env.tmplCache != nil { + return env.tmplCache + } + tmplCache := &templateCache{ + Root: env.isRunningAsRoot(), + Shell: env.getShellName(), + Code: env.lastErrorCode(), + } + tmplCache.Env = make(map[string]string) + const separator = "=" + values := os.Environ() + for value := range values { + splitted := strings.Split(values[value], separator) + if len(splitted) != 2 { + continue + } + key := splitted[0] + val := splitted[1:] + tmplCache.Env[key] = strings.Join(val, separator) + } + pwd := env.getcwd() + pwd = strings.Replace(pwd, env.homeDir(), "~", 1) + tmplCache.PWD = pwd + tmplCache.Folder = base(pwd, env) + tmplCache.UserName = env.getCurrentUser() + if host, err := env.getHostName(); err == nil { + tmplCache.HostName = host + } + goos := env.getRuntimeGOOS() + tmplCache.OS = goos + env.tmplCache = tmplCache + return env.tmplCache +} + func cleanHostName(hostName string) string { garbage := []string{ ".lan", diff --git a/src/environment_unix.go b/src/environment_unix.go index b05325b1..d2660e74 100644 --- a/src/environment_unix.go +++ b/src/environment_unix.go @@ -53,6 +53,9 @@ func (env *environment) getTerminalWidth() (int, error) { } func (env *environment) getPlatform() string { + if wsl := env.getenv("WSL_DISTRO_NAME"); len(wsl) != 0 { + return strings.ToLower(wsl) + } p, _, _, _ := host.PlatformInformation() return p } diff --git a/src/main.go b/src/main.go index bb7a4ed3..e4f5519c 100644 --- a/src/main.go +++ b/src/main.go @@ -336,18 +336,9 @@ func getConsoleBackgroundColor(env Environment, backgroundColorTemplate string) if len(backgroundColorTemplate) == 0 { return backgroundColorTemplate } - context := struct { - Env map[string]string - }{ - Env: map[string]string{}, - } - matches := findAllNamedRegexMatch(templateEnvRegex, backgroundColorTemplate) - for _, match := range matches { - context.Env[match["ENV"]] = env.getenv(match["ENV"]) - } template := &textTemplate{ Template: backgroundColorTemplate, - Context: context, + Context: nil, Env: env, } text, err := template.render() diff --git a/src/main_test.go b/src/main_test.go index ae4e6f28..89b6264e 100644 --- a/src/main_test.go +++ b/src/main_test.go @@ -18,7 +18,11 @@ func TestConsoleBackgroundColorTemplate(t *testing.T) { for _, tc := range cases { env := new(MockedEnvironment) - env.On("getenv", "TERM_PROGRAM").Return(tc.Term) + env.On("templateCache").Return(&templateCache{ + Env: map[string]string{ + "TERM_PROGRAM": tc.Term, + }, + }) env.onTemplate() color := getConsoleBackgroundColor(env, "{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}") assert.Equal(t, tc.Expected, color, tc.Case) diff --git a/src/segment_battery_test.go b/src/segment_battery_test.go index 35c14db7..68e131e5 100644 --- a/src/segment_battery_test.go +++ b/src/segment_battery_test.go @@ -70,6 +70,8 @@ func TestGetBatteryColors(t *testing.T) { }, } for _, tc := range cases { + env := new(MockedEnvironment) + env.onTemplate() batt := &batt{ Percentage: tc.Percentage, } @@ -78,6 +80,7 @@ func TestGetBatteryColors(t *testing.T) { } segment := &Segment{ writer: batt, + env: env, } segment.Foreground = tc.DefaultColor segment.ForegroundTemplates = tc.Templates diff --git a/src/segment_exit_test.go b/src/segment_exit_test.go index b1b3aa56..f8e39249 100644 --- a/src/segment_exit_test.go +++ b/src/segment_exit_test.go @@ -77,6 +77,9 @@ func TestExitWriterTemplateString(t *testing.T) { for _, tc := range cases { env := new(MockedEnvironment) env.On("lastErrorCode").Return(tc.ExitCode) + env.On("templateCache").Return(&templateCache{ + Code: tc.ExitCode, + }) env.onTemplate() props := properties{ SegmentTemplate: tc.Template, diff --git a/src/segment_git_test.go b/src/segment_git_test.go index d98a06cd..532dbeb8 100644 --- a/src/segment_git_test.go +++ b/src/segment_git_test.go @@ -738,6 +738,9 @@ func TestGitTemplateString(t *testing.T) { props := properties{ FetchStatus: true, } + env := new(MockedEnvironment) + env.onTemplate() + tc.Git.env = env tc.Git.props = props assert.Equal(t, tc.Expected, tc.Git.templateString(tc.Template), tc.Case) } diff --git a/src/segment_os.go b/src/segment_os.go index d60c96ad..19d0186e 100644 --- a/src/segment_os.go +++ b/src/segment_os.go @@ -2,7 +2,6 @@ package main import ( "fmt" - "strings" ) type osInfo struct { @@ -79,17 +78,14 @@ func (n *osInfo) string() string { n.os = darwinPlatform return n.props.getString(MacOS, "\uF179") case linuxPlatform: - wsl := n.env.getenv("WSL_DISTRO_NAME") - p := n.env.getPlatform() - if len(wsl) == 0 { - n.os = p - return n.getDistroName(p, "") + n.os = n.env.getPlatform() + if !n.env.isWsl() { + return n.getDistroName(n.os, "") } - n.os = strings.ToLower(wsl) return fmt.Sprintf("%s%s%s", n.props.getString(WSL, "WSL"), n.props.getString(WSLSeparator, " - "), - n.getDistroName(p, wsl)) + n.getDistroName(n.os, n.os)) default: n.os = goos return goos diff --git a/src/segment_os_test.go b/src/segment_os_test.go index d074e946..a2292da0 100644 --- a/src/segment_os_test.go +++ b/src/segment_os_test.go @@ -11,7 +11,7 @@ func TestOSInfo(t *testing.T) { Case string ExpectedString string GOOS string - WSLDistro string + IsWSL bool Platform string DisplayDistroName bool }{ @@ -19,14 +19,14 @@ func TestOSInfo(t *testing.T) { Case: "WSL debian - icon", ExpectedString: "WSL at \uf306", GOOS: "linux", - WSLDistro: "debian", + IsWSL: true, Platform: "debian", }, { Case: "WSL debian - name", - ExpectedString: "WSL at burps", + ExpectedString: "WSL at debian", GOOS: "linux", - WSLDistro: "burps", + IsWSL: true, Platform: "debian", DisplayDistroName: true, }, @@ -62,7 +62,7 @@ func TestOSInfo(t *testing.T) { for _, tc := range cases { env := new(MockedEnvironment) env.On("getRuntimeGOOS").Return(tc.GOOS) - env.On("getenv", "WSL_DISTRO_NAME").Return(tc.WSLDistro) + env.On("isWsl").Return(tc.IsWSL) env.On("getPlatform").Return(tc.Platform) osInfo := &osInfo{ env: env, @@ -75,9 +75,7 @@ func TestOSInfo(t *testing.T) { }, } assert.Equal(t, tc.ExpectedString, osInfo.string(), tc.Case) - if tc.WSLDistro != "" { - assert.Equal(t, tc.WSLDistro, osInfo.os, tc.Case) - } else if tc.Platform != "" { + if tc.Platform != "" { assert.Equal(t, tc.Platform, osInfo.os, tc.Case) } else { assert.Equal(t, tc.GOOS, osInfo.os, tc.Case) diff --git a/src/segment_path_test.go b/src/segment_path_test.go index 0ede23d9..7ce962d6 100644 --- a/src/segment_path_test.go +++ b/src/segment_path_test.go @@ -19,11 +19,6 @@ func (env *MockedEnvironment) getenv(key string) string { return args.String(0) } -func (env *MockedEnvironment) environ() map[string]string { - args := env.Called() - return args.Get(0).(map[string]string) -} - func (env *MockedEnvironment) getcwd() string { args := env.Called() return args.String(0) @@ -208,38 +203,20 @@ func (env *MockedEnvironment) getWifiNetwork() (*wifiInfo, error) { return args.Get(0).(*wifiInfo), args.Error(1) } +func (env *MockedEnvironment) templateCache() *templateCache { + args := env.Called() + return args.Get(0).(*templateCache) +} + func (env *MockedEnvironment) onTemplate() { - patchMethodIfNotSpecified := func(method string, returnArguments ...interface{}) { - for _, call := range env.Mock.ExpectedCalls { - if call.Method == method { - return - } + for _, call := range env.Mock.ExpectedCalls { + if call.Method == "templateCache" { + return } - env.On(method).Return(returnArguments...) } - patchEnvVars := func() map[string]string { - keyValueArray := make(map[string]string) - for _, call := range env.Mock.ExpectedCalls { - if call.Method == "getenv" { - keyValueArray[call.Arguments.String(0)] = call.ReturnArguments.String(0) - } - } - return keyValueArray - } - patchMethodIfNotSpecified("isRunningAsRoot", false) - patchMethodIfNotSpecified("getcwd", "/usr/home/dev/my-app") - patchMethodIfNotSpecified("homeDir", "/usr/home/dev") - patchMethodIfNotSpecified("getPathSeperator", "/") - patchMethodIfNotSpecified("getShellName", "pwsh") - patchMethodIfNotSpecified("getCurrentUser", "dev") - patchMethodIfNotSpecified("getHostName", "laptop", nil) - patchMethodIfNotSpecified("lastErrorCode", 0) - patchMethodIfNotSpecified("getRuntimeGOOS", darwinPlatform) - if env.getRuntimeGOOS() == linuxPlatform { - env.On("getenv", "WSL_DISTRO_NAME").Return("ubuntu") - env.On("getPlatform").Return("ubuntu") - } - patchMethodIfNotSpecified("environ", patchEnvVars()) + env.On("templateCache").Return(&templateCache{ + Env: make(map[string]string), + }) } const ( diff --git a/src/segment_plastic_test.go b/src/segment_plastic_test.go index 465bb0c3..df51d4b4 100644 --- a/src/segment_plastic_test.go +++ b/src/segment_plastic_test.go @@ -328,6 +328,9 @@ func TestPlasticTemplateString(t *testing.T) { FetchStatus: true, } tc.Plastic.props = props + env := new(MockedEnvironment) + env.onTemplate() + tc.Plastic.env = env assert.Equal(t, tc.Expected, tc.Plastic.templateString(tc.Template), tc.Case) } } diff --git a/src/segment_session_test.go b/src/segment_session_test.go index 24945ea2..acff040f 100644 --- a/src/segment_session_test.go +++ b/src/segment_session_test.go @@ -105,9 +105,17 @@ func TestSessionSegmentTemplate(t *testing.T) { } env.On("getenv", "SSH_CONNECTION").Return(SSHSession) env.On("getenv", "SSH_CLIENT").Return(SSHSession) - env.On("isRunningAsRoot").Return(tc.Root) env.On("getenv", defaultUserEnvVar).Return(tc.DefaultUserName) - env.onTemplate() + env.On("templateCache").Return(&templateCache{ + UserName: tc.UserName, + HostName: tc.ComputerName, + Env: map[string]string{ + "SSH_CONNECTION": SSHSession, + "SSH_CLIENT": SSHSession, + defaultUserEnvVar: tc.DefaultUserName, + }, + Root: tc.Root, + }) session := &session{ env: env, props: properties{ diff --git a/src/segment_test.go b/src/segment_test.go index 69c7efb0..025011e5 100644 --- a/src/segment_test.go +++ b/src/segment_test.go @@ -190,11 +190,14 @@ func TestGetColors(t *testing.T) { }, } for _, tc := range cases { + env := new(MockedEnvironment) + env.onTemplate() segment := &Segment{ writer: &aws{ Profile: tc.Profile, Region: tc.Region, }, + env: env, } if tc.Background { segment.Background = tc.DefaultColor diff --git a/src/segment_text_test.go b/src/segment_text_test.go index 09d3804e..8671cf59 100644 --- a/src/segment_text_test.go +++ b/src/segment_text_test.go @@ -24,16 +24,18 @@ func TestTextSegment(t *testing.T) { for _, tc := range cases { env := new(MockedEnvironment) - env.On("getcwd").Return("/usr/home/posh") - env.On("homeDir").Return("/usr/home") env.On("getPathSeperator").Return("/") - env.On("isRunningAsRoot").Return(true) - env.On("getShellName").Return("terminal") - env.On("getenv", "HELLO").Return("hello") - env.On("getenv", "WORLD").Return("") - env.On("getCurrentUser").Return("Posh") - env.On("getHostName").Return("MyHost", nil) - env.onTemplate() + env.On("templateCache").Return(&templateCache{ + UserName: "Posh", + Env: map[string]string{ + "HELLO": "hello", + "WORLD": "", + }, + HostName: "MyHost", + Shell: "terminal", + Root: true, + Folder: base("/usr/home/posh", env), + }) txt := &text{ env: env, props: properties{ diff --git a/src/template.go b/src/template.go index e23c6c9d..e74e02e6 100644 --- a/src/template.go +++ b/src/template.go @@ -12,9 +12,6 @@ const ( // Errors to show when the template handling fails invalidTemplate = "invalid template text" incorrectTemplate = "unable to create text based on template" - // nostruct = "unable to create map from non-struct type" - - templateEnvRegex = `\.Env\.(?P[^ \.}]*)` ) type textTemplate struct { @@ -26,6 +23,13 @@ type textTemplate struct { type Data interface{} type Context struct { + templateCache + + // Simple container to hold ANY object + Data +} + +type templateCache struct { Root bool PWD string Folder string @@ -35,37 +39,14 @@ type Context struct { Code int Env map[string]string OS string - - // Simple container to hold ANY object - Data } func (c *Context) init(t *textTemplate) { c.Data = t.Context - if t.Env == nil { + if cache := t.Env.templateCache(); cache != nil { + c.templateCache = *cache return } - c.Root = t.Env.isRunningAsRoot() - pwd := t.Env.getcwd() - pwd = strings.Replace(pwd, t.Env.homeDir(), "~", 1) - c.PWD = pwd - c.Folder = base(c.PWD, t.Env) - c.Shell = t.Env.getShellName() - c.UserName = t.Env.getCurrentUser() - if host, err := t.Env.getHostName(); err == nil { - c.HostName = host - } - c.Code = t.Env.lastErrorCode() - c.Env = t.Env.environ() - goos := t.Env.getRuntimeGOOS() - if goos == linuxPlatform { - wsl := t.Env.getenv("WSL_DISTRO_NAME") - goos = t.Env.getPlatform() - if len(wsl) != 0 { - goos = strings.ToLower(wsl) - } - } - c.OS = goos } func (t *textTemplate) render() (string, error) { diff --git a/src/template_test.go b/src/template_test.go index ad87a7d4..5be86c93 100644 --- a/src/template_test.go +++ b/src/template_test.go @@ -121,10 +121,9 @@ func TestRenderTemplateEnvVar(t *testing.T) { } for _, tc := range cases { env := &MockedEnvironment{} - for name, value := range tc.Env { - env.On("getenv", name).Return(value) - } - env.onTemplate() + env.On("templateCache").Return(&templateCache{ + Env: tc.Env, + }) template := &textTemplate{ Template: tc.Template, Context: tc.Context,