Repository: JanDeDobbeleer/oh-my-posh
Pull request #1370 (2021-12-03 19:20:40)

feat(language): expose version info
### Prerequisites

- [x] I have read and understood the `CONTRIBUTING` guide
- [x] The commit message follows the [conventional commits][cc] guidelines
- [x] Tests for the changes have been added (for bug fixes/features)
- [x] Docs have been added / updated (for bug fixes/features)

### Description

Without this `foreground_templates` and `background_templates` do not work.

<!---

Tips:

If you're not comfortable with working with Git, we're working a guide (https://ohmyposh.dev/docs/contributing_git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary



Patch:
diff --git a/src/segment_angular.go b/src/segment_angular.go
index 1723a15b..52ffa21c 100644
--- a/src/segment_angular.go
+++ b/src/segment_angular.go
@@ -1,7 +1,7 @@
 package main
 
 type angular struct {
-	language *language
+	language
 }
 
 func (a *angular) string() string {
@@ -9,7 +9,7 @@ func (a *angular) string() string {
 }
 
 func (a *angular) init(props properties, env environmentInfo) {
-	a.language = &language{
+	a.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"angular.json"},
diff --git a/src/segment_az_functions.go b/src/segment_az_functions.go
index c6143cc9..397fc2ae 100644
--- a/src/segment_az_functions.go
+++ b/src/segment_az_functions.go
@@ -1,7 +1,7 @@
 package main
 
 type azfunc struct {
-	language *language
+	language
 }
 
 func (az *azfunc) string() string {
@@ -9,7 +9,7 @@ func (az *azfunc) string() string {
 }
 
 func (az *azfunc) init(props properties, env environmentInfo) {
-	az.language = &language{
+	az.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"host.json", "local.settings.json", "function.json"},
diff --git a/src/segment_crystal.go b/src/segment_crystal.go
index 99a5ba0b..caef827a 100644
--- a/src/segment_crystal.go
+++ b/src/segment_crystal.go
@@ -1,7 +1,7 @@
 package main
 
 type crystal struct {
-	language *language
+	language
 }
 
 func (c *crystal) string() string {
@@ -9,7 +9,7 @@ func (c *crystal) string() string {
 }
 
 func (c *crystal) init(props properties, env environmentInfo) {
-	c.language = &language{
+	c.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"*.cr", "shard.yml"},
diff --git a/src/segment_dart.go b/src/segment_dart.go
index 69683296..a653f930 100644
--- a/src/segment_dart.go
+++ b/src/segment_dart.go
@@ -1,7 +1,7 @@
 package main
 
 type dart struct {
-	language *language
+	language
 }
 
 func (d *dart) string() string {
@@ -9,7 +9,7 @@ func (d *dart) string() string {
 }
 
 func (d *dart) init(props properties, env environmentInfo) {
-	d.language = &language{
+	d.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"*.dart", "pubspec.yaml", "pubspec.yml", "pubspec.lock", ".dart_tool"},
diff --git a/src/segment_dotnet.go b/src/segment_dotnet.go
index 3217bc1b..50fb4255 100644
--- a/src/segment_dotnet.go
+++ b/src/segment_dotnet.go
@@ -1,7 +1,7 @@
 package main
 
 type dotnet struct {
-	language *language
+	language
 }
 
 const (
@@ -22,7 +22,7 @@ func (d *dotnet) string() string {
 }
 
 func (d *dotnet) init(props properties, env environmentInfo) {
-	d.language = &language{
+	d.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"*.cs", "*.csx", "*.vb", "*.sln", "*.csproj", "*.vbproj", "*.fs", "*.fsx", "*.fsproj", "global.json"},
diff --git a/src/segment_golang.go b/src/segment_golang.go
index 437354d6..c8a620a5 100644
--- a/src/segment_golang.go
+++ b/src/segment_golang.go
@@ -1,7 +1,7 @@
 package main
 
 type golang struct {
-	language *language
+	language
 }
 
 func (g *golang) string() string {
@@ -9,7 +9,7 @@ func (g *golang) string() string {
 }
 
 func (g *golang) init(props properties, env environmentInfo) {
-	g.language = &language{
+	g.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"*.go", "go.mod"},
diff --git a/src/segment_java.go b/src/segment_java.go
index b179a3c0..c3aa2ba9 100644
--- a/src/segment_java.go
+++ b/src/segment_java.go
@@ -3,7 +3,7 @@ package main
 import "fmt"
 
 type java struct {
-	language *language
+	language
 }
 
 func (j *java) string() string {
@@ -17,7 +17,7 @@ func (j *java) init(props properties, env environmentInfo) {
 		args:       []string{"-Xinternalversion"},
 		regex:      javaRegex,
 	}
-	j.language = &language{
+	j.language = language{
 		env:   env,
 		props: props,
 		extensions: []string{
diff --git a/src/segment_julia.go b/src/segment_julia.go
index c49ee2f1..b67f9599 100644
--- a/src/segment_julia.go
+++ b/src/segment_julia.go
@@ -1,7 +1,7 @@
 package main
 
 type julia struct {
-	language *language
+	language
 }
 
 func (j *julia) string() string {
@@ -9,7 +9,7 @@ func (j *julia) string() string {
 }
 
 func (j *julia) init(props properties, env environmentInfo) {
-	j.language = &language{
+	j.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"*.jl"},
diff --git a/src/segment_language.go b/src/segment_language.go
index 6fa84a2c..d9ced7b2 100644
--- a/src/segment_language.go
+++ b/src/segment_language.go
@@ -25,44 +25,23 @@ type cmd struct {
 	executable string
 	args       []string
 	regex      string
-
-	version
 }
 
-func (c *cmd) parse(versionInfo string) error {
+func (c *cmd) parse(versionInfo string) (*version, error) {
 	values := findNamedRegexMatch(c.regex, versionInfo)
 	if len(values) == 0 {
-		return errors.New("cannot parse version string")
+		return nil, errors.New("cannot parse version string")
 	}
 
-	c.version.Full = values["version"]
-	c.version.Major = values["major"]
-	c.version.Minor = values["minor"]
-	c.version.Patch = values["patch"]
-	c.version.Prerelease = values["prerelease"]
-	c.version.BuildMetadata = values["buildmetadata"]
-	return nil
-}
-
-func (c *cmd) buildVersionURL(text, template string) string {
-	if template == "" {
-		return text
-	}
-	truncatingSprintf := func(str string, args ...interface{}) (string, error) {
-		n := strings.Count(str, "%s")
-		if n > len(args) {
-			return "", errors.New("Too many parameters")
-		}
-		if n == 0 {
-			return fmt.Sprintf(str, args...), nil
-		}
-		return fmt.Sprintf(str, args[:n]...), nil
+	version := &version{
+		Full:          values["version"],
+		Major:         values["major"],
+		Minor:         values["minor"],
+		Patch:         values["patch"],
+		Prerelease:    values["prerelease"],
+		BuildMetadata: values["buildmetadata"],
 	}
-	version, err := truncatingSprintf(template, text, c.version.Major, c.version.Minor, c.version.Patch)
-	if err != nil {
-		return text
-	}
-	return version
+	return version, nil
 }
 
 type language struct {
@@ -71,13 +50,14 @@ type language struct {
 	extensions         []string
 	commands           []*cmd
 	versionURLTemplate string
-	activeCommand      *cmd
 	exitCode           int
 	loadContext        loadContext
 	inContext          inContext
 	matchesVersionFile matchesVersionFile
 	homeEnabled        bool
 	displayMode        string
+
+	version
 }
 
 const (
@@ -120,7 +100,7 @@ func (l *language) string() string {
 	segmentTemplate := l.props.getString(SegmentTemplate, "{{.Full}}")
 	template := &textTemplate{
 		Template: segmentTemplate,
-		Context:  l.activeCommand.version,
+		Context:  l.version,
 		Env:      l.env,
 	}
 	text, err := template.render()
@@ -132,11 +112,11 @@ func (l *language) string() string {
 		versionURLTemplate := l.props.getString(VersionURLTemplate, "")
 		// backward compatibility
 		if versionURLTemplate == "" {
-			text = l.activeCommand.buildVersionURL(text, l.versionURLTemplate)
+			text = l.buildVersionURL(text)
 		} else {
 			template := &textTemplate{
 				Template: versionURLTemplate,
-				Context:  l.activeCommand.version,
+				Context:  l.version,
 				Env:      l.env,
 			}
 			url, err := template.render()
@@ -203,19 +183,19 @@ func (l *language) setVersion() error {
 		if !l.env.hasCommand(command.executable) {
 			continue
 		}
-		version, err := l.env.runCommand(command.executable, command.args...)
+		versionStr, err := l.env.runCommand(command.executable, command.args...)
 		if exitErr, ok := err.(*commandError); ok {
 			l.exitCode = exitErr.exitCode
 			return fmt.Errorf("err executing %s with %s", command.executable, command.args)
 		}
-		if version == "" {
+		if versionStr == "" {
 			continue
 		}
-		err = command.parse(version)
+		version, err := command.parse(versionStr)
 		if err != nil {
-			return fmt.Errorf("err parsing info from %s with %s", command.executable, version)
+			return fmt.Errorf("err parsing info from %s with %s", command.executable, versionStr)
 		}
-		l.activeCommand = command
+		l.version = *version
 		return nil
 	}
 	return errors.New(l.props.getString(MissingCommandText, ""))
@@ -245,3 +225,24 @@ func (l *language) setVersionFileMismatch() {
 	}
 	l.props[ForegroundOverride] = l.props.getColor(VersionMismatchColor, l.props.getColor(ForegroundOverride, ""))
 }
+
+func (l *language) buildVersionURL(text string) string {
+	if l.versionURLTemplate == "" {
+		return text
+	}
+	truncatingSprintf := func(str string, args ...interface{}) (string, error) {
+		n := strings.Count(str, "%s")
+		if n > len(args) {
+			return "", errors.New("Too many parameters")
+		}
+		if n == 0 {
+			return fmt.Sprintf(str, args...), nil
+		}
+		return fmt.Sprintf(str, args[:n]...), nil
+	}
+	version, err := truncatingSprintf(l.versionURLTemplate, text, l.version.Major, l.version.Minor, l.version.Patch)
+	if err != nil {
+		return text
+	}
+	return version
+}
diff --git a/src/segment_node.go b/src/segment_node.go
index bfef9ac1..1a3fc1f1 100644
--- a/src/segment_node.go
+++ b/src/segment_node.go
@@ -3,8 +3,9 @@ package main
 import "fmt"
 
 type node struct {
-	language           *language
 	packageManagerIcon string
+
+	language
 }
 
 const (
@@ -22,7 +23,7 @@ func (n *node) string() string {
 }
 
 func (n *node) init(props properties, env environmentInfo) {
-	n.language = &language{
+	n.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"*.js", "*.ts", "package.json", ".nvmrc", "pnpm-workspace.yaml", ".pnpmfile.cjs", ".npmrc"},
@@ -64,9 +65,9 @@ func (n *node) matchesVersionFile() bool {
 
 	regex := fmt.Sprintf(
 		`(?im)^v?%s(\.?%s)?(\.?%s)?$`,
-		n.language.activeCommand.version.Major,
-		n.language.activeCommand.version.Minor,
-		n.language.activeCommand.version.Patch,
+		n.language.version.Major,
+		n.language.version.Minor,
+		n.language.version.Patch,
 	)
 
 	return matchString(regex, fileVersion)
diff --git a/src/segment_php.go b/src/segment_php.go
index d2a55198..04304f70 100644
--- a/src/segment_php.go
+++ b/src/segment_php.go
@@ -1,7 +1,7 @@
 package main
 
 type php struct {
-	language *language
+	language
 }
 
 func (n *php) string() string {
@@ -9,7 +9,7 @@ func (n *php) string() string {
 }
 
 func (n *php) init(props properties, env environmentInfo) {
-	n.language = &language{
+	n.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"*.php", "composer.json", "composer.lock", ".php-version"},
diff --git a/src/segment_python.go b/src/segment_python.go
index fd8e7b3d..c36010ed 100644
--- a/src/segment_python.go
+++ b/src/segment_python.go
@@ -3,7 +3,7 @@ package main
 import "fmt"
 
 type python struct {
-	language *language
+	language
 	venvName string
 }
 
@@ -24,7 +24,7 @@ func (p *python) string() string {
 }
 
 func (p *python) init(props properties, env environmentInfo) {
-	p.language = &language{
+	p.language = language{
 		env:         env,
 		props:       props,
 		extensions:  []string{"*.py", "*.ipynb", "pyproject.toml", "venv.bak", "venv", ".venv"},
diff --git a/src/segment_ruby.go b/src/segment_ruby.go
index 8178932f..d37cc6d3 100644
--- a/src/segment_ruby.go
+++ b/src/segment_ruby.go
@@ -1,7 +1,7 @@
 package main
 
 type ruby struct {
-	language *language
+	language
 }
 
 func (r *ruby) string() string {
@@ -14,7 +14,7 @@ func (r *ruby) string() string {
 }
 
 func (r *ruby) init(props properties, env environmentInfo) {
-	r.language = &language{
+	r.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"*.rb", "Rakefile", "Gemfile"},
diff --git a/src/segment_rust.go b/src/segment_rust.go
index 730bf4ff..b2f094e0 100644
--- a/src/segment_rust.go
+++ b/src/segment_rust.go
@@ -1,7 +1,7 @@
 package main
 
 type rust struct {
-	language *language
+	language
 }
 
 func (r *rust) string() string {
@@ -9,7 +9,7 @@ func (r *rust) string() string {
 }
 
 func (r *rust) init(props properties, env environmentInfo) {
-	r.language = &language{
+	r.language = language{
 		env:        env,
 		props:      props,
 		extensions: []string{"*.rs", "Cargo.toml", "Cargo.lock"},


Test patch:
diff --git a/src/segment_node_test.go b/src/segment_node_test.go
index de14b3ba..ecc183f3 100644
--- a/src/segment_node_test.go
+++ b/src/segment_node_test.go
@@ -33,11 +33,9 @@ func TestNodeMatchesVersionFile(t *testing.T) {
 		env.On("getFileContent", ".nvmrc").Return(tc.RCVersion)
 
 		node := &node{
-			language: &language{
-				env: env,
-				activeCommand: &cmd{
-					version: nodeVersion,
-				},
+			language: language{
+				env:     env,
+				version: nodeVersion,
 			},
 		}
 		assert.Equal(t, tc.Expected, node.matchesVersionFile(), tc.Case)
@@ -67,7 +65,7 @@ func TestNodeInContext(t *testing.T) {
 		env.On("hasFiles", "package-lock.json").Return(tc.hasNPM)
 		env.On("hasFiles", "package.json").Return(tc.hasDefault)
 		node := &node{
-			language: &language{
+			language: language{
 				env: env,
 				props: map[Property]interface{}{
 					YarnIcon:              "yarn",