diff --git a/src/segment_dotnet.go b/src/segment_dotnet.go index 765a6629..3fea937f 100644 --- a/src/segment_dotnet.go +++ b/src/segment_dotnet.go @@ -30,7 +30,10 @@ func (d *dotnet) init(props *properties, env environmentInfo) { commands: []string{"dotnet"}, versionParam: "--version", extensions: []string{"*.cs", "*.vb", "*.sln", "*.csproj", "*.vbproj"}, - versionRegex: `(?P[0-9]+.[0-9]+.[0-9]+)`, + version: &version{ + regex: `(?:(?P((?P[0-9]+).(?P[0-9]+).(?:\d{2})(?P[0-9]{1}))))`, + urlTemplate: "[%1s](https://github.com/dotnet/core/blob/master/release-notes/%[2]s.%[3]s/%[2]s.%[3]s.%[4]s/%[2]s.%[3]s.%[4]s.md)", + }, } } diff --git a/src/segment_golang.go b/src/segment_golang.go index 82578a32..ade18c2d 100644 --- a/src/segment_golang.go +++ b/src/segment_golang.go @@ -15,7 +15,10 @@ func (g *golang) init(props *properties, env environmentInfo) { commands: []string{"go"}, versionParam: "version", extensions: []string{"*.go", "go.mod"}, - versionRegex: `go(?P[0-9]+.[0-9]+.[0-9]+)`, + version: &version{ + regex: `(?:go(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, + urlTemplate: "[%s](https://golang.org/doc/go%s.%s)", + }, } } diff --git a/src/segment_julia.go b/src/segment_julia.go index e2cabeb2..26141028 100644 --- a/src/segment_julia.go +++ b/src/segment_julia.go @@ -15,7 +15,9 @@ func (j *julia) init(props *properties, env environmentInfo) { commands: []string{"julia"}, versionParam: "--version", extensions: []string{"*.jl"}, - versionRegex: `julia version (?P[0-9]+.[0-9]+.[0-9]+)`, + version: &version{ + regex: `julia version (?P[0-9]+.[0-9]+.[0-9]+)`, + }, } } diff --git a/src/segment_language.go b/src/segment_language.go index 2a2294a4..c94cf8ac 100644 --- a/src/segment_language.go +++ b/src/segment_language.go @@ -1,9 +1,37 @@ package main +import ( + "errors" + "fmt" + "strings" +) + type loadContext func() type inContext func() bool +type version struct { + full string + major string + minor string + patch string + regex string + urlTemplate string +} + +func (v *version) parse(versionInfo string) error { + values := findNamedRegexMatch(v.regex, versionInfo) + if len(values) == 0 { + return errors.New("cannot parse version string") + } + + v.full = values["version"] + v.major = values["major"] + v.minor = values["minor"] + v.patch = values["patch"] + return nil +} + type language struct { props *properties env environmentInfo @@ -11,8 +39,7 @@ type language struct { commands []string executable string versionParam string - versionRegex string - version string + version *version exitCode int loadContext loadContext inContext inContext @@ -42,8 +69,21 @@ func (l *language) string() string { if !l.hasCommand() { return l.props.getString(MissingCommandTextProperty, MissingCommandText) } - l.setVersion() - return l.version + + err := l.setVersion() + if err != nil { + return "" + } + + // build release notes hyperlink + if l.props.getBool(EnableHyperlink, false) && l.version.urlTemplate != "" { + version, err := TruncatingSprintf(l.version.urlTemplate, l.version.full, l.version.major, l.version.minor, l.version.patch) + if err != nil { + return l.version.full + } + return version + } + return l.version.full } func (l *language) enabled() bool { @@ -77,16 +117,18 @@ func (l *language) hasLanguageFiles() bool { return true } -// getVersion returns the version and exit code returned by the executable -func (l *language) setVersion() { +// setVersion parses the version string returned by the command +func (l *language) setVersion() error { versionInfo, err := l.env.runCommand(l.executable, l.versionParam) if exitErr, ok := err.(*commandError); ok { l.exitCode = exitErr.exitCode - return + return errors.New("error executing command") } - values := findNamedRegexMatch(l.versionRegex, versionInfo) - l.exitCode = 0 - l.version = values["version"] + err = l.version.parse(versionInfo) + if err != nil { + return err + } + return nil } // hasCommand checks if one of the commands exists and sets it as executable @@ -116,3 +158,14 @@ func (l *language) inLanguageContext() bool { } return l.inContext() } + +func TruncatingSprintf(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 +} diff --git a/src/segment_language_test.go b/src/segment_language_test.go index 363a77b2..7c57b86f 100644 --- a/src/segment_language_test.go +++ b/src/segment_language_test.go @@ -7,7 +7,7 @@ import ( ) const ( - universion = "1.3.3.7" + universion = "1.3.307" uni = "*.uni" corn = "*.corn" ) @@ -23,6 +23,8 @@ type languageArgs struct { versionParam string versionRegex string missingCommandText string + urlTemplate string + enableHyperlink bool } func (l *languageArgs) hasvalue(value string, list []string) bool { @@ -45,8 +47,9 @@ func bootStrapLanguageTest(args *languageArgs) *language { } props := &properties{ values: map[Property]interface{}{ - DisplayVersion: args.displayVersion, - DisplayMode: args.displayMode, + DisplayVersion: args.displayVersion, + DisplayMode: args.displayMode, + EnableHyperlink: args.enableHyperlink, }, } if args.missingCommandText != "" { @@ -58,7 +61,10 @@ func bootStrapLanguageTest(args *languageArgs) *language { extensions: args.extensions, commands: args.commands, versionParam: args.versionParam, - versionRegex: args.versionRegex, + version: &version{ + regex: args.versionRegex, + urlTemplate: args.urlTemplate, + }, } return l } @@ -222,3 +228,75 @@ func TestLanguageEnabledMissingCommandCustomText(t *testing.T) { assert.True(t, lang.enabled()) assert.Equal(t, args.missingCommandText, lang.string(), "unicorn is available and uni and corn files are found") } + +func TestLanguageHyperlinkEnabled(t *testing.T) { + args := &languageArgs{ + versionParam: "--version", + commands: []string{"uni", "corn"}, + enabledCommands: []string{"corn"}, + extensions: []string{uni, corn}, + versionRegex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, + urlTemplate: "[%s](https://unicor.org/doc/%s.%s.%s)", + version: universion, + enabledExtensions: []string{corn}, + displayVersion: true, + enableHyperlink: true, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.enabled()) + assert.Equal(t, "[1.3.307](https://unicor.org/doc/1.3.307)", lang.string()) +} + +func TestLanguageHyperlinkEnabledWrongRegex(t *testing.T) { + args := &languageArgs{ + versionParam: "--version", + commands: []string{"uni", "corn"}, + enabledCommands: []string{"corn"}, + extensions: []string{uni, corn}, + versionRegex: `wrong`, + urlTemplate: "[%s](https://unicor.org/doc/%s.%s.%s)", + version: universion, + enabledExtensions: []string{corn}, + displayVersion: true, + enableHyperlink: true, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.enabled()) + assert.Equal(t, "", lang.string()) +} + +func TestLanguageHyperlinkEnabledLessParamInTemplate(t *testing.T) { + args := &languageArgs{ + versionParam: "--version", + commands: []string{"uni", "corn"}, + enabledCommands: []string{"corn"}, + extensions: []string{uni, corn}, + versionRegex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, + urlTemplate: "[%s](https://unicor.org/doc/%s)", + version: universion, + enabledExtensions: []string{corn}, + displayVersion: true, + enableHyperlink: true, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.enabled()) + assert.Equal(t, "[1.3.307](https://unicor.org/doc/1)", lang.string()) +} + +func TestLanguageHyperlinkEnabledMoreParamInTemplate(t *testing.T) { + args := &languageArgs{ + versionParam: "--version", + commands: []string{"uni", "corn"}, + enabledCommands: []string{"corn"}, + extensions: []string{uni, corn}, + versionRegex: `(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+)))`, + urlTemplate: "[%s](https://unicor.org/doc/%s.%s.%s.%s)", + version: universion, + enabledExtensions: []string{corn}, + displayVersion: true, + enableHyperlink: true, + } + lang := bootStrapLanguageTest(args) + assert.True(t, lang.enabled()) + assert.Equal(t, "1.3.307", lang.string()) +} diff --git a/src/segment_node.go b/src/segment_node.go index 6c8cb887..bd8ad888 100644 --- a/src/segment_node.go +++ b/src/segment_node.go @@ -15,7 +15,10 @@ func (n *node) init(props *properties, env environmentInfo) { commands: []string{"node"}, versionParam: "--version", extensions: []string{"*.js", "*.ts", "package.json"}, - versionRegex: `(?P[0-9]+.[0-9]+.[0-9]+)`, + version: &version{ + regex: `(?:v(?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, + urlTemplate: "[%[1]s](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V%[2]s.md#%[1]s)", + }, } } diff --git a/src/segment_python.go b/src/segment_python.go index de2d932e..5a162d42 100644 --- a/src/segment_python.go +++ b/src/segment_python.go @@ -32,9 +32,12 @@ func (p *python) init(props *properties, env environmentInfo) { commands: []string{"python", "python3"}, versionParam: "--version", extensions: []string{"*.py", "*.ipynb", "pyproject.toml", "venv.bak", "venv", ".venv"}, - versionRegex: `Python (?P[0-9]+.[0-9]+.[0-9]+)`, loadContext: p.loadContext, inContext: p.inContext, + version: &version{ + regex: `(?:Python (?P((?P[0-9]+).(?P[0-9]+).(?P[0-9]+))))`, + urlTemplate: "[%s](https://www.python.org/downloads/release/python-%s%s%s/)", + }, } }