feat: cache template environment data

This commit is contained in:
Jan De Dobbeleer 2022-01-18 09:48:47 +01:00 committed by Jan De Dobbeleer
parent 9e77d0f939
commit e5dd07fb9a
17 changed files with 142 additions and 141 deletions

View file

@ -1,7 +1,6 @@
package main package main
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -57,11 +56,17 @@ func TestGetConsoleTitle(t *testing.T) {
env.On("getcwd").Return(tc.Cwd) env.On("getcwd").Return(tc.Cwd)
env.On("homeDir").Return("/usr/home") env.On("homeDir").Return("/usr/home")
env.On("getPathSeperator").Return(tc.PathSeperator) env.On("getPathSeperator").Return(tc.PathSeperator)
env.On("isRunningAsRoot").Return(tc.Root) env.On("templateCache").Return(&templateCache{
env.On("getShellName").Return(tc.ShellName) Env: map[string]string{
env.On("getenv", "USERDOMAIN").Return("MyCompany") "USERDOMAIN": "MyCompany",
env.On("getCurrentUser").Return("MyUser") },
env.On("getHostName").Return("MyHost", nil) Shell: tc.ShellName,
UserName: "MyUser",
Root: tc.Root,
HostName: "MyHost",
PWD: tc.Cwd,
Folder: base(tc.Cwd, env),
})
env.onTemplate() env.onTemplate()
ansi := &ansiUtils{} ansi := &ansiUtils{}
ansi.init(tc.ShellName) ansi.init(tc.ShellName)
@ -112,12 +117,15 @@ func TestGetConsoleTitleIfGethostnameReturnsError(t *testing.T) {
env := new(MockedEnvironment) env := new(MockedEnvironment)
env.On("getcwd").Return(tc.Cwd) env.On("getcwd").Return(tc.Cwd)
env.On("homeDir").Return("/usr/home") env.On("homeDir").Return("/usr/home")
env.On("getPathSeperator").Return(tc.PathSeperator) env.On("templateCache").Return(&templateCache{
env.On("isRunningAsRoot").Return(tc.Root) Env: map[string]string{
env.On("getShellName").Return(tc.ShellName) "USERDOMAIN": "MyCompany",
env.On("getenv", "USERDOMAIN").Return("MyCompany") },
env.On("getCurrentUser").Return("MyUser") Shell: tc.ShellName,
env.On("getHostName").Return("", fmt.Errorf("I have a bad feeling about this")) UserName: "MyUser",
Root: tc.Root,
HostName: "",
})
env.onTemplate() env.onTemplate()
ansi := &ansiUtils{} ansi := &ansiUtils{}
ansi.init(tc.ShellName) ansi.init(tc.ShellName)

View file

@ -90,7 +90,6 @@ type wifiInfo struct {
type Environment interface { type Environment interface {
getenv(key string) string getenv(key string) string
environ() map[string]string
getcwd() string getcwd() string
homeDir() string homeDir() string
hasFiles(pattern string) bool hasFiles(pattern string) bool
@ -128,6 +127,7 @@ type Environment interface {
convertToLinuxPath(path string) string convertToLinuxPath(path string) string
convertToWindowsPath(path string) string convertToWindowsPath(path string) string
getWifiNetwork() (*wifiInfo, error) getWifiNetwork() (*wifiInfo, error)
templateCache() *templateCache
} }
type commandCache struct { type commandCache struct {
@ -159,7 +159,7 @@ type environment struct {
cwd string cwd string
cmdCache *commandCache cmdCache *commandCache
fileCache *fileCache fileCache *fileCache
environCache map[string]string tmplCache *templateCache
logBuilder strings.Builder logBuilder strings.Builder
debug bool debug bool
} }
@ -176,18 +176,6 @@ func (env *environment) init(args *args) {
} }
env.fileCache = &fileCache{} env.fileCache = &fileCache{}
env.fileCache.init(env.getCachePath()) 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() { func (env *environment) resolveConfigPath() {
@ -236,11 +224,6 @@ func (env *environment) getenv(key string) string {
return val return val
} }
func (env *environment) environ() map[string]string {
defer env.trace(time.Now(), "environ")
return env.environCache
}
func (env *environment) getcwd() string { func (env *environment) getcwd() string {
defer env.trace(time.Now(), "getcwd") defer env.trace(time.Now(), "getcwd")
if env.cwd != "" { if env.cwd != "" {
@ -563,6 +546,42 @@ func (env *environment) logs() string {
return env.logBuilder.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 { func cleanHostName(hostName string) string {
garbage := []string{ garbage := []string{
".lan", ".lan",

View file

@ -53,6 +53,9 @@ func (env *environment) getTerminalWidth() (int, error) {
} }
func (env *environment) getPlatform() string { func (env *environment) getPlatform() string {
if wsl := env.getenv("WSL_DISTRO_NAME"); len(wsl) != 0 {
return strings.ToLower(wsl)
}
p, _, _, _ := host.PlatformInformation() p, _, _, _ := host.PlatformInformation()
return p return p
} }

View file

@ -336,18 +336,9 @@ func getConsoleBackgroundColor(env Environment, backgroundColorTemplate string)
if len(backgroundColorTemplate) == 0 { if len(backgroundColorTemplate) == 0 {
return backgroundColorTemplate 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 := &textTemplate{
Template: backgroundColorTemplate, Template: backgroundColorTemplate,
Context: context, Context: nil,
Env: env, Env: env,
} }
text, err := template.render() text, err := template.render()

View file

@ -18,7 +18,11 @@ func TestConsoleBackgroundColorTemplate(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
env := new(MockedEnvironment) 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() env.onTemplate()
color := getConsoleBackgroundColor(env, "{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}") color := getConsoleBackgroundColor(env, "{{ if eq \"vscode\" .Env.TERM_PROGRAM }}#123456{{end}}")
assert.Equal(t, tc.Expected, color, tc.Case) assert.Equal(t, tc.Expected, color, tc.Case)

View file

@ -70,6 +70,8 @@ func TestGetBatteryColors(t *testing.T) {
}, },
} }
for _, tc := range cases { for _, tc := range cases {
env := new(MockedEnvironment)
env.onTemplate()
batt := &batt{ batt := &batt{
Percentage: tc.Percentage, Percentage: tc.Percentage,
} }
@ -78,6 +80,7 @@ func TestGetBatteryColors(t *testing.T) {
} }
segment := &Segment{ segment := &Segment{
writer: batt, writer: batt,
env: env,
} }
segment.Foreground = tc.DefaultColor segment.Foreground = tc.DefaultColor
segment.ForegroundTemplates = tc.Templates segment.ForegroundTemplates = tc.Templates

View file

@ -77,6 +77,9 @@ func TestExitWriterTemplateString(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
env := new(MockedEnvironment) env := new(MockedEnvironment)
env.On("lastErrorCode").Return(tc.ExitCode) env.On("lastErrorCode").Return(tc.ExitCode)
env.On("templateCache").Return(&templateCache{
Code: tc.ExitCode,
})
env.onTemplate() env.onTemplate()
props := properties{ props := properties{
SegmentTemplate: tc.Template, SegmentTemplate: tc.Template,

View file

@ -738,6 +738,9 @@ func TestGitTemplateString(t *testing.T) {
props := properties{ props := properties{
FetchStatus: true, FetchStatus: true,
} }
env := new(MockedEnvironment)
env.onTemplate()
tc.Git.env = env
tc.Git.props = props tc.Git.props = props
assert.Equal(t, tc.Expected, tc.Git.templateString(tc.Template), tc.Case) assert.Equal(t, tc.Expected, tc.Git.templateString(tc.Template), tc.Case)
} }

View file

@ -2,7 +2,6 @@ package main
import ( import (
"fmt" "fmt"
"strings"
) )
type osInfo struct { type osInfo struct {
@ -79,17 +78,14 @@ func (n *osInfo) string() string {
n.os = darwinPlatform n.os = darwinPlatform
return n.props.getString(MacOS, "\uF179") return n.props.getString(MacOS, "\uF179")
case linuxPlatform: case linuxPlatform:
wsl := n.env.getenv("WSL_DISTRO_NAME") n.os = n.env.getPlatform()
p := n.env.getPlatform() if !n.env.isWsl() {
if len(wsl) == 0 { return n.getDistroName(n.os, "")
n.os = p
return n.getDistroName(p, "")
} }
n.os = strings.ToLower(wsl)
return fmt.Sprintf("%s%s%s", return fmt.Sprintf("%s%s%s",
n.props.getString(WSL, "WSL"), n.props.getString(WSL, "WSL"),
n.props.getString(WSLSeparator, " - "), n.props.getString(WSLSeparator, " - "),
n.getDistroName(p, wsl)) n.getDistroName(n.os, n.os))
default: default:
n.os = goos n.os = goos
return goos return goos

View file

@ -11,7 +11,7 @@ func TestOSInfo(t *testing.T) {
Case string Case string
ExpectedString string ExpectedString string
GOOS string GOOS string
WSLDistro string IsWSL bool
Platform string Platform string
DisplayDistroName bool DisplayDistroName bool
}{ }{
@ -19,14 +19,14 @@ func TestOSInfo(t *testing.T) {
Case: "WSL debian - icon", Case: "WSL debian - icon",
ExpectedString: "WSL at \uf306", ExpectedString: "WSL at \uf306",
GOOS: "linux", GOOS: "linux",
WSLDistro: "debian", IsWSL: true,
Platform: "debian", Platform: "debian",
}, },
{ {
Case: "WSL debian - name", Case: "WSL debian - name",
ExpectedString: "WSL at burps", ExpectedString: "WSL at debian",
GOOS: "linux", GOOS: "linux",
WSLDistro: "burps", IsWSL: true,
Platform: "debian", Platform: "debian",
DisplayDistroName: true, DisplayDistroName: true,
}, },
@ -62,7 +62,7 @@ func TestOSInfo(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
env := new(MockedEnvironment) env := new(MockedEnvironment)
env.On("getRuntimeGOOS").Return(tc.GOOS) 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) env.On("getPlatform").Return(tc.Platform)
osInfo := &osInfo{ osInfo := &osInfo{
env: env, env: env,
@ -75,9 +75,7 @@ func TestOSInfo(t *testing.T) {
}, },
} }
assert.Equal(t, tc.ExpectedString, osInfo.string(), tc.Case) assert.Equal(t, tc.ExpectedString, osInfo.string(), tc.Case)
if tc.WSLDistro != "" { if tc.Platform != "" {
assert.Equal(t, tc.WSLDistro, osInfo.os, tc.Case)
} else if tc.Platform != "" {
assert.Equal(t, tc.Platform, osInfo.os, tc.Case) assert.Equal(t, tc.Platform, osInfo.os, tc.Case)
} else { } else {
assert.Equal(t, tc.GOOS, osInfo.os, tc.Case) assert.Equal(t, tc.GOOS, osInfo.os, tc.Case)

View file

@ -19,11 +19,6 @@ func (env *MockedEnvironment) getenv(key string) string {
return args.String(0) 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 { func (env *MockedEnvironment) getcwd() string {
args := env.Called() args := env.Called()
return args.String(0) return args.String(0)
@ -208,38 +203,20 @@ func (env *MockedEnvironment) getWifiNetwork() (*wifiInfo, error) {
return args.Get(0).(*wifiInfo), args.Error(1) 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() { func (env *MockedEnvironment) onTemplate() {
patchMethodIfNotSpecified := func(method string, returnArguments ...interface{}) {
for _, call := range env.Mock.ExpectedCalls { for _, call := range env.Mock.ExpectedCalls {
if call.Method == method { if call.Method == "templateCache" {
return return
} }
} }
env.On(method).Return(returnArguments...) env.On("templateCache").Return(&templateCache{
} Env: make(map[string]string),
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())
} }
const ( const (

View file

@ -328,6 +328,9 @@ func TestPlasticTemplateString(t *testing.T) {
FetchStatus: true, FetchStatus: true,
} }
tc.Plastic.props = props 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) assert.Equal(t, tc.Expected, tc.Plastic.templateString(tc.Template), tc.Case)
} }
} }

View file

@ -105,9 +105,17 @@ func TestSessionSegmentTemplate(t *testing.T) {
} }
env.On("getenv", "SSH_CONNECTION").Return(SSHSession) env.On("getenv", "SSH_CONNECTION").Return(SSHSession)
env.On("getenv", "SSH_CLIENT").Return(SSHSession) env.On("getenv", "SSH_CLIENT").Return(SSHSession)
env.On("isRunningAsRoot").Return(tc.Root)
env.On("getenv", defaultUserEnvVar).Return(tc.DefaultUserName) 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{ session := &session{
env: env, env: env,
props: properties{ props: properties{

View file

@ -190,11 +190,14 @@ func TestGetColors(t *testing.T) {
}, },
} }
for _, tc := range cases { for _, tc := range cases {
env := new(MockedEnvironment)
env.onTemplate()
segment := &Segment{ segment := &Segment{
writer: &aws{ writer: &aws{
Profile: tc.Profile, Profile: tc.Profile,
Region: tc.Region, Region: tc.Region,
}, },
env: env,
} }
if tc.Background { if tc.Background {
segment.Background = tc.DefaultColor segment.Background = tc.DefaultColor

View file

@ -24,16 +24,18 @@ func TestTextSegment(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
env := new(MockedEnvironment) env := new(MockedEnvironment)
env.On("getcwd").Return("/usr/home/posh")
env.On("homeDir").Return("/usr/home")
env.On("getPathSeperator").Return("/") env.On("getPathSeperator").Return("/")
env.On("isRunningAsRoot").Return(true) env.On("templateCache").Return(&templateCache{
env.On("getShellName").Return("terminal") UserName: "Posh",
env.On("getenv", "HELLO").Return("hello") Env: map[string]string{
env.On("getenv", "WORLD").Return("") "HELLO": "hello",
env.On("getCurrentUser").Return("Posh") "WORLD": "",
env.On("getHostName").Return("MyHost", nil) },
env.onTemplate() HostName: "MyHost",
Shell: "terminal",
Root: true,
Folder: base("/usr/home/posh", env),
})
txt := &text{ txt := &text{
env: env, env: env,
props: properties{ props: properties{

View file

@ -12,9 +12,6 @@ const (
// Errors to show when the template handling fails // Errors to show when the template handling fails
invalidTemplate = "invalid template text" invalidTemplate = "invalid template text"
incorrectTemplate = "unable to create text based on template" incorrectTemplate = "unable to create text based on template"
// nostruct = "unable to create map from non-struct type"
templateEnvRegex = `\.Env\.(?P<ENV>[^ \.}]*)`
) )
type textTemplate struct { type textTemplate struct {
@ -26,6 +23,13 @@ type textTemplate struct {
type Data interface{} type Data interface{}
type Context struct { type Context struct {
templateCache
// Simple container to hold ANY object
Data
}
type templateCache struct {
Root bool Root bool
PWD string PWD string
Folder string Folder string
@ -35,37 +39,14 @@ type Context struct {
Code int Code int
Env map[string]string Env map[string]string
OS string OS string
// Simple container to hold ANY object
Data
} }
func (c *Context) init(t *textTemplate) { func (c *Context) init(t *textTemplate) {
c.Data = t.Context c.Data = t.Context
if t.Env == nil { if cache := t.Env.templateCache(); cache != nil {
c.templateCache = *cache
return 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) { func (t *textTemplate) render() (string, error) {

View file

@ -121,10 +121,9 @@ func TestRenderTemplateEnvVar(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
env := &MockedEnvironment{} env := &MockedEnvironment{}
for name, value := range tc.Env { env.On("templateCache").Return(&templateCache{
env.On("getenv", name).Return(value) Env: tc.Env,
} })
env.onTemplate()
template := &textTemplate{ template := &textTemplate{
Template: tc.Template, Template: tc.Template,
Context: tc.Context, Context: tc.Context,